from __future__ import unicode_literals import csv from io import TextIOWrapper 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 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) # @@@ TODO - Make this configurable if self.slot.kind.label in ["talk", "tutorial", "keynote"]: 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() queryset = queryset.exclude(cancelled=True) queryset = queryset.order_by("proposal_base__pk") if self.slot.content: queryset = queryset.filter( Q(slot=None) | Q(pk=self.slot.content.pk) ) kwargs["required"] = False kwargs["initial"] = self.slot.content else: queryset = queryset.filter(slot=None) kwargs["required"] = True kwargs["queryset"] = queryset return forms.ModelChoiceField(**kwargs) def build_content_override_field(self): kwargs = { "label": "Content", "required": False, "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("Please upload a .csv file") return fname def _get_start_end_times(self, data): "Return start and end time objects" times = [] for x in [data[self.START_KEY], data[self.END_KEY]]: try: time_obj = time.strptime(x, "%I:%M %p") except (ValueError, TypeError): return messages.ERROR, "Malformed time found: %s." % x time_obj = datetime( 100, 1, 1, time_obj.tm_hour, time_obj.tm_min, 00 ) times.append(time_obj.time()) return times 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: try: date = datetime.strptime(day, "%m/%d/%Y") except ValueError: [x.delete() for x in created_days] return messages.ERROR, "Malformed data found: %s." % day 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 = [] csv_file = TextIOWrapper(self.cleaned_data.get("filename")) reader = csv.DictReader(csv_file) 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: with transaction.atomic(): SlotRoom.objects.create(slot=slot, room=room) except IntegrityError: # delete all created objects and report error for x in created_items: x.delete() return ( messages.ERROR, "An overlap occurred; the import was cancelled.", ) return messages.SUCCESS, "Your schedule has been imported." def delete_schedule(self): self.schedule.day_set.all().delete() return messages.SUCCESS, "Your schedule has been deleted."