from django.core.management.base import BaseCommand from symposion.conference.models import Section, current_conference from symposion.schedule.models import (Day, Presentation, Room, SlotKind, Schedule, Slot, SlotRoom) from symposion.proposals.models import ProposalBase from dateutil.parser import parse from collections import Counter from pathlib import Path import csv class Command(BaseCommand): known_headers = ["date", "start time", "end time", "kind", "rooms"] SLOTS = 'slots' TALKS = 'talks' help = "Updates the schedule based on two csv files, "\ "one that gives all the talk slots, the other the talks." def add_arguments(self, parser): parser.add_argument(self.SLOTS, type=Path) parser.add_argument(self.TALKS, type=Path) def parse_slots(self, options): self.date_strs = set() self.room_names = set() self.slotkind_names = set() self.slot_details = {} slot_type_count = Counter() with open(options[self.SLOTS]) as csv_file: csv_reader = csv.reader(csv_file) headers = next(csv_reader) assert headers == self.known_headers for row in csv_reader: assert len(row) == len(self.known_headers) rowdate, start_time, end_time, kind, rooms = row self.slotkind_names.add(kind) if rowdate: date = rowdate self.date_strs.add(date) assert kind, "kind cannot be blank" slot_type_count[(date, kind)] += 1 kindslot = f"{kind} {slot_type_count[(date, kind)]}" if rooms.strip(): for room in rooms.split(';'): room = room.strip() self.room_names.add(room) self.slot_details[(date, kindslot, room)] = (start_time, end_time) elif self.exclusive(kind): self.slot_details[(date, kindslot, None)] = (start_time, end_time) def parse_talks(self, options): self.talks = {} with open(options[self.TALKS]) as csv_file: csv_reader = csv.reader(csv_file) self.used_rooms = next(csv_reader) assert self.used_rooms[0] == '', "Cell (1, 1) must be empty" for room in self.used_rooms[1:]: assert room in self.room_names, f"Unknown room: {room}" for row in csv_reader: cell = row[0] if cell.rsplit(' ', 1)[0] in self.slotkind_names: kindslot = cell for i, room in enumerate(self.used_rooms[1:], 1): talk_id = row[i] if not talk_id: continue assert (date, kindslot, room) in self.slot_details,\ f"Slot ({date}, '{kindslot}', '{room}') not found" self.talks[(date, kindslot, room)] = int(talk_id) else: assert parse(row[0]), "Not a date: {row[0]}" date = row[0] for col in row[1:]: assert col == '', f"All other columns must be empty: {date}" def create_simple_models(self): self.days = {} for date in self.date_strs: day, _created = Day.objects.get_or_create( schedule=self.schedule, date=date) self.days[date] = day self.rooms = {} for room_name in self.room_names: room, _created = Room.objects.get_or_create( schedule=self.schedule, name=room_name, order=self.used_rooms.index(room_name)) self.rooms[room_name] = room self.slotkinds = {} for slotkind_name in self.slotkind_names: slotkind, _created = SlotKind.objects.get_or_create( schedule=self.schedule, label=slotkind_name) self.slotkinds[slotkind_name] = slotkind def create_complex_models(self): for details, talk_id in self.talks.items(): date, kindslot, room_name = details kind_name = kindslot.rsplit(' ', 1)[0] (start_time, end_time) = self.slot_details[(date, kindslot, room_name)] proposal = ProposalBase.objects.filter(pk=talk_id).first() assert proposal, f"Could not find proposal {talk_id}" preso = Presentation.objects.filter(proposal_base=proposal).first() assert preso, f"Could not find Presentation for talk {talk_id}" if not preso.slot: exclusive = self.exclusive(kind_name) preso.slot = Slot.objects.create( day=self.days[date], kind=self.slotkinds[kind_name], start=start_time, end=end_time, exclusive=exclusive) # FIXME update the preso.slot, e.g. if the day/time changes preso.save() # FIXME what happens if the room changes? slotroom, _create = SlotRoom.objects.get_or_create( slot=preso.slot, room=self.rooms[room_name]) def create_exclusive_models(self): for (date, kindslot, room) in self.slot_details.keys(): if room is None: start_time, end_time = self.slot_details[(date, kindslot, room)] kind_name = kindslot.rsplit(' ', 1)[0] slot = Slot.objects.create( day=self.days[date], kind=self.slotkinds[kind_name], start=start_time, end=end_time, exclusive=True) slot.save() def exclusive(self, kind_name): # TODO the exclusive list should not be hard coded, another csv file maybe. return kind_name in ["plenary", "morning tea", "lunch", "afternoon tea"] def handle(self, *args, **options): self.parse_slots(options) self.parse_talks(options) conf = current_conference() section = Section.objects.filter(conference=conf, slug="main").all().first() self.schedule, _created = Schedule.objects.get_or_create(section=section) self.create_simple_models() self.create_complex_models() self.create_exclusive_models()