from django.db.models import Q from django.utils.safestring import mark_safe from django.utils.html import escape from django.utils.timezone import localtime from datetime import datetime, timedelta from copy import deepcopy from collections import OrderedDict, namedtuple from itertools import islice from .models import Room from proposals.models import Conference, Talk Event = namedtuple('Event', ['talk', 'row', 'rowcount']) class Program: def __init__(self, site, empty_rooms=False, talk_filter=None): self.site = site self.talks = Talk.objects.\ filter(site=site, room__isnull=False, start_date__isnull=False).\ filter(Q(duration__gt=0) | Q(event__duration__gt=0)).\ exclude(accepted=False).\ order_by('start_date') if talk_filter: self.talks = self.talks.filter(talk_filter) if empty_rooms: self.rooms = Room.objects.filter(site=site) else: self.rooms = Room.objects.filter(talk__in=self.talks.all()).order_by('name').distinct() self.days = {} for talk in self.talks.all(): duration = talk.estimated_duration assert(duration) dt1 = talk.start_date d1 = localtime(dt1).date() if d1 not in self.days.keys(): self.days[d1] = {'timeslots': []} dt2 = dt1 + timedelta(minutes=duration) d2 = localtime(dt2).date() if d2 not in self.days.keys(): self.days[d2] = {'timeslots': []} if dt1 not in self.days[d1]['timeslots']: self.days[d1]['timeslots'].append(dt1) if dt2 not in self.days[d2]['timeslots']: self.days[d2]['timeslots'].append(dt2) def _add_talk(self, talk): room = talk.room dt1 = talk.start_date d1 = localtime(dt1).date() dt2 = talk.start_date + timedelta(minutes=talk.estimated_duration) d2 = localtime(dt2).date() assert(d1 == d2) # this is a current limitation dt1 = self.days[d1]['timeslots'].index(dt1) dt2 = self.days[d1]['timeslots'].index(dt2) col = None for row, timeslot in enumerate(islice(self.days[d1]['timeslots'], dt1, dt2)): if col is None: col = 0 while col < len(self.days[d1]['rows'][timeslot][room]) and self.days[d1]['rows'][timeslot][room][col]: col += 1 self.cols[room] = max(self.cols[room], col+1) event = Event(talk=talk, row=row, rowcount=dt2-dt1) while len(self.days[d1]['rows'][timeslot][room]) <= col: self.days[d1]['rows'][timeslot][room].append(None) self.days[d1]['rows'][timeslot][room][col] = event def _header(self): output = 'Room' room_cell = '%(name)s
%(label)s' for room, colspan in self.cols.items(): options = ' style="min-width: 100px;" colspan="%d"' % colspan output += room_cell % {'name': escape(room.name), 'label': escape(room.label), 'options': options} return '%s' % output def _body(self): output = '' for day in sorted(self.days.keys()): output += self._day_header(day) output += self._day(day) return output def _day_header(self, day): row = '

%(day)s

' colcount = 1 for room, col in self.cols.items(): colcount += col return row % { 'colcount': colcount, 'day': datetime.strftime(day, '%A %d %B'), } def _day(self, day): output = [] rows = self.days[day]['rows'] for ts, rooms in rows.items(): output.append(self._row(day, ts, rooms)) return '\n'.join(output) def _row(self, day, ts, rooms): row = '%(timeslot)s%(content)s' cell = '%(content)s' content = '' for room, events in rooms.items(): colspan = 1 for i in range(self.cols[room]): options = ' colspan="%d"' % colspan cellcontent = '' if i < len(events) and events[i]: event = events[i] if event.row != 0: continue options = ' rowspan="%d" bgcolor="%s"' % (event.rowcount, event.talk.event.color) cellcontent = escape(str(event.talk)) + '
' + escape(event.talk.get_speakers_str()) + '' elif (i+1 > len(events) or not events[i+1]) and i+1 < self.cols[room]: colspan += 1 continue colspan = 1 content += cell % {'options': options, 'content': mark_safe(cellcontent)} style, timeslot = self._timeslot(day, ts) return row % { 'style': style, 'timeslot': timeslot, 'content': content, } def _timeslot(self, day, ts): template = '%(content)s' start = ts end = self.days[day]['timeslots'][self.days[day]['timeslots'].index(ts)+1] duration = (end - start).seconds / 60 date_to_string = lambda date: datetime.strftime(localtime(date), '%H:%M') style = 'height: %dpx;' % int(duration * 1.2) timeslot = '%s – %s' % tuple(map(date_to_string, [start, end])) return style, timeslot def as_html(self): template = """\n%(header)s\n%(body)s\n
""" self.cols = OrderedDict([(room, 1) for room in self.rooms]) for day in self.days.keys(): self.days[day]['timeslots'] = sorted(self.days[day]['timeslots']) self.days[day]['rows'] = OrderedDict([(timeslot, OrderedDict([(room, []) for room in self.rooms])) for timeslot in self.days[day]['timeslots'][:-1]]) for talk in self.talks.exclude(plenary=True).all(): self._add_talk(talk) for talk in self.talks.filter(plenary=True).all(): self._add_talk(talk) return mark_safe(template % { 'header': self._header(), 'body': self._body(), }) def as_xml(self): result = """ %(conference)s %(days)s """ if not len(self.days): return result % {'conference': '', 'days': ''} conference = Conference.objects.get(site=self.site) conference_xml = """ %(title)s %(venue)s %(city)s %(start_date)s %(end_date)s %(days_count)s 09:00:00 00:05:00 """ % { 'title': self.site.name, 'venue': ', '.join(conference.venue.split('\n')), 'city': conference.city, 'start_date': sorted(self.days.keys())[0].strftime('%Y-%m-%d'), 'end_date': sorted(self.days.keys(), reverse=True)[0].strftime('%Y-%m-%d'), 'days_count': len(self.days), } days_xml = '' for index, day in enumerate(sorted(self.days.keys())): days_xml += '\n' % { 'index': index + 1, 'date': day.strftime('%Y-%m-%d'), } for room in self.rooms.all(): days_xml += ' \n' % room.name for talk in self.talks.filter(room=room).order_by('start_date'): if localtime(talk.start_date).date() != day: continue duration = talk.estimated_duration persons = '' for speaker in talk.speakers.all(): persons += ' %(person)s\n' % { 'person_id': speaker.id, 'person': str(speaker), } days_xml += """ %(start)s %(duration)s %(room)s %(slug)s %(title)s %(track)s %(type)s %(abstract)s %(description)s %(persons)s \n""" % { 'id': talk.id, 'start': localtime(talk.start_date).strftime('%H:%M'), 'duration': '%02d:%02d' % (talk.estimated_duration / 60, talk.estimated_duration % 60), 'room': escape(room.name), 'slug': escape(talk.slug), 'title': escape(talk.title), 'track': escape(talk.track), 'type': escape(talk.event.name), 'abstract': escape(talk.abstract), 'description': escape(talk.description), 'persons': persons, } days_xml += ' \n' days_xml += '\n' return result % { 'conference': '\n'.join(map(lambda x: ' ' + x, conference_xml.split('\n'))), 'days': '\n'.join(map(lambda x: ' ' + x, days_xml.split('\n'))), } def __str__(self): return self.as_html()