diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py index 63316743..3e4c9ff4 100644 --- a/symposion/schedule/forms.py +++ b/symposion/schedule/forms.py @@ -1,13 +1,21 @@ +import csv +import time + +from datetime import datetime + from django import forms +from django.contrib import messages +from django.db import IntegrityError, transaction from django.db.models import Q from markitup.widgets import MarkItUpWidget -from symposion.schedule.models import Presentation +from symposion.schedule.models import (Day, Presentation, Room, SlotKind, Slot, + SlotRoom) class SlotEditForm(forms.Form): - + def __init__(self, *args, **kwargs): self.slot = kwargs.pop("slot") super(SlotEditForm, self).__init__(*args, **kwargs) @@ -16,7 +24,7 @@ class SlotEditForm(forms.Form): self.fields["presentation"] = self.build_presentation_field() else: self.fields["content_override"] = self.build_content_override_field() - + def build_presentation_field(self): kwargs = {} queryset = Presentation.objects.all() @@ -31,7 +39,7 @@ class SlotEditForm(forms.Form): kwargs["required"] = True kwargs["queryset"] = queryset return forms.ModelChoiceField(**kwargs) - + def build_content_override_field(self): kwargs = { "label": "Content", @@ -40,3 +48,106 @@ class SlotEditForm(forms.Form): "initial": self.slot.content_override, } return forms.CharField(**kwargs) + + +class ScheduleSectionForm(forms.Form): + ROOM_KEY = 'room' + DATE_KEY = 'date' + START_KEY = 'time_start' + END_KEY = 'time_end' + KIND = 'kind' + + filename = forms.FileField( + label='Select a CSV file to import:', + required=False + ) + + def __init__(self, *args, **kwargs): + self.schedule = kwargs.pop("schedule") + super(ScheduleSectionForm, self).__init__(*args, **kwargs) + + def clean_filename(self): + if 'submit' in self.data: + fname = self.cleaned_data.get('filename') + if not fname or not fname.name.endswith('.csv'): + raise forms.ValidationError(u'Please upload a .csv file') + return fname + + def _get_start_end_times(self, data): + "Return start and end time objects" + start_time = time.strptime(data[self.START_KEY], '%I:%M %p') + start = datetime(100, 1, 1, start_time.tm_hour, start_time.tm_min, 00) + end_time = time.strptime(data[self.END_KEY], '%I:%M %p') + end = datetime(100, 1, 1, end_time.tm_hour, end_time.tm_min, 00) + return start.time(), end.time() + + def _build_rooms(self, data): + "Get or Create Rooms based on schedule type and set of Tracks" + created_rooms = [] + rooms = sorted(set([x[self.ROOM_KEY] for x in data])) + for i, room in enumerate(rooms): + room, created = Room.objects.get_or_create( + schedule=self.schedule, name=room, order=i + ) + if created: + created_rooms.append(room) + return created_rooms + + def _build_days(self, data): + "Get or Create Days based on schedule type and set of Days" + created_days = [] + days = set([x[self.DATE_KEY] for x in data]) + for day in days: + date = datetime.strptime(day, "%m/%d/%Y") + day, created = Day.objects.get_or_create( + schedule=self.schedule, date=date + ) + if created: + created_days.append(day) + return created_days + + def build_schedule(self): + created_items = [] + reader = csv.DictReader(self.cleaned_data.get('filename')) + data = [dict((k.strip(), v.strip()) for k, v in x.items()) for x in reader] + # build rooms + created_items.extend(self._build_rooms(data)) + # build_days + created_items.extend(self._build_days(data)) + # build Slot -> SlotRoom + for row in data: + room = Room.objects.get( + schedule=self.schedule, name=row[self.ROOM_KEY] + ) + date = datetime.strptime(row[self.DATE_KEY], "%m/%d/%Y") + day = Day.objects.get(schedule=self.schedule, date=date) + start, end = self._get_start_end_times(row) + slot_kind, created = SlotKind.objects.get_or_create( + label=row[self.KIND], schedule=self.schedule + ) + if created: + created_items.append(slot_kind) + if row[self.KIND] == 'plenary': + slot, created = Slot.objects.get_or_create( + kind=slot_kind, day=day, start=start, end=end + ) + if created: + created_items.append(slot) + else: + slot = Slot.objects.create( + kind=slot_kind, day=day, start=start, end=end + ) + created_items.append(slot) + try: + SlotRoom.objects.create(slot=slot, room=room) + except IntegrityError: + transaction.rollback() + # delete all created objects and report error + for x in created_items: + x.delete() + return messages.ERROR, u'An overlap occurred; the import was cancelled.' + return messages.SUCCESS, u'Your schedule has been imported.' + + def delete_schedule(self): + self.schedule.day_set.all().delete() + return messages.SUCCESS, u'Your schedule has been deleted.' diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 1367808b..6b229c09 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -3,8 +3,9 @@ from django.shortcuts import render, get_object_or_404, redirect from django.template import loader, Context from django.contrib.auth.decorators import login_required +from django.contrib import messages -from symposion.schedule.forms import SlotEditForm +from symposion.schedule.forms import SlotEditForm, ScheduleSectionForm from symposion.schedule.models import Schedule, Day, Slot, Presentation from symposion.schedule.timetable import TimeTable @@ -100,11 +101,24 @@ def schedule_edit(request, slug=None): schedule = fetch_schedule(slug) + if request.method == "POST": + form = ScheduleSectionForm( + request.POST, request.FILES, schedule=schedule + ) + if form.is_valid(): + if 'submit' in form.data: + msg = form.build_schedule() + elif 'delete' in form.data: + msg = form.delete_schedule() + messages.add_message(request, msg[0], msg[1]) + else: + form = ScheduleSectionForm(schedule=schedule) days_qs = Day.objects.filter(schedule=schedule) days = [TimeTable(day) for day in days_qs] ctx = { "schedule": schedule, "days": days, + "form": form } return render(request, "schedule/schedule_edit.html", ctx) diff --git a/symposion/templates/schedule/schedule_edit.html b/symposion/templates/schedule/schedule_edit.html index 5de11af2..d6713aa5 100644 --- a/symposion/templates/schedule/schedule_edit.html +++ b/symposion/templates/schedule/schedule_edit.html @@ -18,13 +18,19 @@