program
This commit is contained in:
parent
bd53789ad7
commit
7b9e252a97
|
@ -0,0 +1,13 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block planningtab %} active{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Program" %}</h1>
|
||||
|
||||
{{ program }}
|
||||
|
||||
{% endblock %}
|
|
@ -8,4 +8,5 @@ urlpatterns = [
|
|||
url(r'^room/add/$', views.RoomCreate.as_view(), name='add-room'),
|
||||
url(r'^room/(?P<slug>[-\w]+)/$', views.RoomDetail.as_view(), name='show-room'),
|
||||
url(r'^room/(?P<slug>[-\w]+)/edit/$', views.RoomUpdate.as_view(), name='edit-room'),
|
||||
url(r'^program/$', views.program, name='program'),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
from django.db.models import Q
|
||||
from django.utils.safestring import mark_safe
|
||||
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 Talk
|
||||
|
||||
|
||||
Event = namedtuple('Event', ['talk', 'row', 'rowcount'])
|
||||
|
||||
|
||||
class Program:
|
||||
def __init__(self, site):
|
||||
self.rooms = Room.objects.filter(site=site)
|
||||
self.talks = Talk.objects.\
|
||||
filter(site=site, room__in=self.rooms.all(), start_date__isnull=False).\
|
||||
filter(Q(duration__gt=0) | Q(event__duration__gt=0)).\
|
||||
exclude(accepted=False).\
|
||||
order_by('start_date')
|
||||
|
||||
self.timeslots = []
|
||||
for talk in self.talks.all():
|
||||
duration = talk.estimated_duration()
|
||||
assert(duration)
|
||||
d1 = talk.start_date
|
||||
d2 = d1 + timedelta(minutes=duration)
|
||||
if d1 not in self.timeslots:
|
||||
self.timeslots.append(d1)
|
||||
if d2 not in self.timeslots:
|
||||
self.timeslots.append(d2)
|
||||
self.timeslots = sorted(self.timeslots)
|
||||
|
||||
self.cols = OrderedDict([(room, 1) for room in self.rooms])
|
||||
self.rows = OrderedDict([(timeslot, OrderedDict([(room, []) for room in self.rooms])) for timeslot in self.timeslots[:-1]])
|
||||
|
||||
for talk in self.talks:
|
||||
self._add_talk(talk)
|
||||
|
||||
def _add_talk(self, talk):
|
||||
room = talk.room
|
||||
d1 = self.timeslots.index(talk.start_date)
|
||||
d2 = self.timeslots.index(talk.start_date + timedelta(minutes=talk.duration))
|
||||
col = None
|
||||
for row, timeslot in enumerate(islice(self.timeslots, d1, d2)):
|
||||
if col is None:
|
||||
col = 0
|
||||
while col < len(self.rows[timeslot][room]) and self.rows[timeslot][room][col]:
|
||||
col += 1
|
||||
self.cols[room] = max(self.cols[room], col+1)
|
||||
event = Event(talk=talk, row=row, rowcount=d2-d1)
|
||||
while len(self.rows[timeslot][room]) <= col:
|
||||
self.rows[timeslot][room].append(None)
|
||||
self.rows[timeslot][room][col] = event
|
||||
|
||||
def _header(self):
|
||||
output = '<td>Room</td>'
|
||||
room_cell = '<td%(options)s>%(name)s<br><b>%(label)s</b></td>'
|
||||
for room, colspan in self.cols.items():
|
||||
options = ' colspan="%d"' % colspan
|
||||
output += room_cell % {'name': room.name, 'label': room.label, 'options': options}
|
||||
return '<tr>%s</tr>' % output
|
||||
|
||||
def _body(self):
|
||||
row = '<tr style="%(style)s">%(timeslot)s%(content)s</tr>'
|
||||
cell = '<td%(options)s>%(content)s</td>'
|
||||
output = []
|
||||
for ts, rooms in self.rows.items():
|
||||
content = ''
|
||||
for room, events in rooms.items():
|
||||
for i in range(self.cols[room]):
|
||||
options = ''
|
||||
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 = str(event.talk) + ' — ' + event.talk.get_speakers_str()
|
||||
content += cell % {'options': options, 'content': cellcontent}
|
||||
style, timeslot = self._timeslot(ts)
|
||||
output.append(row % {
|
||||
'style': style,
|
||||
'timeslot': timeslot,
|
||||
'content': content,
|
||||
})
|
||||
return '\n'.join(output)
|
||||
|
||||
def _timeslot(self, ts):
|
||||
template = '<td>%(content)s</td>'
|
||||
start = ts
|
||||
end = self.timeslots[self.timeslots.index(ts)+1]
|
||||
duration = (end - start).seconds / 60
|
||||
print(start, end, duration)
|
||||
date_to_string = lambda date: datetime.strftime(localtime(date), '%H:%M')
|
||||
style = 'height: %dpx;' % int(duration * 0.8)
|
||||
timeslot = '<td>%s – %s</td>' % tuple(map(date_to_string, [start, end]))
|
||||
return style, timeslot
|
||||
|
||||
def __str__(self):
|
||||
template = """<table class="table table-bordered text-center">\n%(header)s\n%(body)s\n</table>"""
|
||||
return mark_safe(template % {
|
||||
'header': self._header(),
|
||||
'body': self._body(),
|
||||
})
|
|
@ -5,9 +5,11 @@ from django.views.generic import CreateView, DetailView, ListView, UpdateView
|
|||
from accounts.mixins import OrgaRequiredMixin, StaffRequiredMixin
|
||||
from proposals.mixins import OnSiteFormMixin
|
||||
|
||||
from proposals.models import Talk
|
||||
|
||||
from .models import Room
|
||||
from .forms import RoomForm
|
||||
|
||||
from .utils import Program
|
||||
|
||||
class RoomMixin(object):
|
||||
def get_queryset(self):
|
||||
|
@ -30,3 +32,10 @@ class RoomUpdate(OrgaRequiredMixin, RoomMixin, RoomFormMixin, UpdateView):
|
|||
|
||||
class RoomDetail(StaffRequiredMixin, RoomMixin, DetailView):
|
||||
pass
|
||||
|
||||
|
||||
def program(request):
|
||||
program = Program(site=get_current_site(request))
|
||||
return render(request, 'planning/program.html', {
|
||||
'program': program,
|
||||
})
|
||||
|
|
|
@ -71,6 +71,9 @@
|
|||
<li role="presentation">
|
||||
<a role="menuitem" tabindex="-1" href="{% url 'list-rooms' %}"><span class="glyphicon glyphicon-tent"></span> {% trans "Rooms" %}</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a role="menuitem" tabindex="-1" href="{% url 'program' %}"><span class="glyphicon glyphicon-list-alt"></span> {% trans "Program" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown{% block admintab %}{% endblock %}">
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-10-16 21:21
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import colorful.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('proposals', '0020_auto_20161015_1201'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='color',
|
||||
field=colorful.fields.RGBColorField(default='#ffffff', verbose_name='Color on program'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='duration',
|
||||
field=models.IntegerField(default=0, verbose_name='Default duration (min)'),
|
||||
),
|
||||
]
|
|
@ -10,6 +10,7 @@ from django.utils.translation import ugettext
|
|||
from django.utils import timezone
|
||||
|
||||
from autoslug import AutoSlugField
|
||||
from colorful.fields import RGBColorField
|
||||
|
||||
from accounts.models import Participation
|
||||
from ponyconf.utils import PonyConfModel, enum_to_choices
|
||||
|
@ -85,7 +86,8 @@ class Event(models.Model):
|
|||
|
||||
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=64)
|
||||
duration = models.IntegerField(default=0, verbose_name=_('Duration (min)'))
|
||||
duration = models.IntegerField(default=0, verbose_name=_('Default duration (min)'))
|
||||
color = RGBColorField(default='#ffffff', verbose_name=_("Color on program"))
|
||||
|
||||
class Meta:
|
||||
unique_together = ('site', 'name')
|
||||
|
@ -123,6 +125,13 @@ class Talk(PonyConfModel):
|
|||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_speakers_str(self):
|
||||
speakers = [str(speaker) for speaker in self.speakers.all()]
|
||||
if len(speakers) == 1:
|
||||
return speakers[0]
|
||||
else:
|
||||
return ', '.join(speakers[:-1]) + ' & ' + str(speakers[-1])
|
||||
|
||||
def estimated_duration(self):
|
||||
return self.duration or self.event.duration
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ django-bower
|
|||
django-registration-redux
|
||||
django-select2
|
||||
django-avatar
|
||||
django-colorful
|
||||
|
||||
markdown
|
||||
bleach
|
||||
|
|
Loading…
Reference in New Issue