import csv import time from io import TextIOWrapper 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, ProposalBase) class SlotEditForm(forms.Form): required_css_class = 'label-required' 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, "widget": forms.Textarea, } return forms.CharField(**kwargs) class ScheduleSectionForm(forms.Form): required_css_class = 'label-required' ROOM_KEY = 'room' DATE_KEY = 'date' START_KEY = 'time_start' END_KEY = 'time_end' EXCLUSIVE = 'exclusive' PROPOSAL = 'proposal_id' KIND = 'kind' filename = forms.FileField( label='Select a CSV file to import:', required=False ) def __init__(self, *args, **kwargs): self.schedule = kwargs.pop("schedule") if 'encoding' in kwargs: self.encoding = kwargs['encoding'] kwargs.pop('encoding') else: self.encoding = "utf-8" 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" times = [] for x in [data[self.START_KEY], data[self.END_KEY]]: try: time_obj = time.strptime(x, '%I:%M %p') except: return messages.ERROR, u'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): try: room = Room.objects.get(schedule=self.schedule, name=room) created = False except Room.DoesNotExist: room = Room.objects.create( schedule=self.schedule, name=room, order=i ) created = True 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, "%Y-%m-%d") except ValueError: [x.delete() for x in created_days] return messages.ERROR, u'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 = [] f = TextIOWrapper(self.cleaned_data.get('filename'), encoding=self.encoding) reader = csv.DictReader(f) 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], "%Y-%m-%d") 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, exclusive=bool(int(row[self.EXCLUSIVE])) ) if created: created_items.append(slot) else: slot = Slot.objects.create( kind=slot_kind, day=day, start=start, end=end, exclusive=bool(int(row[self.EXCLUSIVE])) ) 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, u'An overlap occurred; the import was cancelled.' if row[self.PROPOSAL]: proposal = ProposalBase.objects.get(id=row[self.PROPOSAL]) Presentation.objects.get_or_create( slot=slot, section_id=1, proposal_base=proposal, speaker=proposal.speaker, additional_speakers=proposal.additional_speakers, title=proposal.title, abstract=proposal.abstract ) 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.'