remove old planning app

This commit is contained in:
Élie Bouttier 2017-09-18 13:41:35 +02:00
parent d14cd37248
commit f63b0b9cda
15 changed files with 0 additions and 620 deletions

View File

View File

@ -1,11 +0,0 @@
from django.contrib import admin
from planning.models import Room
from ponyconf.admin import SiteAdminMixin
class RoomAdmin(SiteAdminMixin, admin.ModelAdmin):
list_display = ('name', 'label', 'capacity')
admin.site.register(Room, RoomAdmin)

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class PlanningConfig(AppConfig):
name = 'planning'

View File

@ -1,20 +0,0 @@
from django import forms
from .models import Room
class RoomForm(forms.ModelForm):
class Meta:
model = Room
fields = ['name', 'label', 'capacity']
def __init__(self, *args, **kwargs):
self.site = kwargs.pop('site')
super().__init__(*args, **kwargs)
def clean_name(self):
name = self.cleaned_data['name']
if (not self.instance or self.instance.name != name) \
and Room.objects.filter(site=self.site, name=name).exists():
raise self.instance.unique_error_message(self._meta.model, ['name'])
return name

View File

@ -1,38 +0,0 @@
from django.db import models
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
from django.db.models import Q
from autoslug import AutoSlugField
class Room(models.Model):
site = models.ForeignKey(Site, on_delete=models.CASCADE)
slug = AutoSlugField(populate_from='name')
name = models.CharField(max_length=256, blank=True, default="")
label = models.CharField(max_length=256, blank=True, default="")
capacity = models.IntegerField(default=0)
class Meta:
unique_together = ['site', 'name']
ordering = ['name']
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('show-room', kwargs={'slug': self.slug})
@property
def talks(self):
return self.talk_set.exclude(accepted=False)
@property
def talks_by_date(self):
return self.talks.filter(start_date__isnull=False).exclude(duration=0, event__duration=0).order_by('start_date').all()
@property
def unscheduled_talks(self):
return self.talks.filter(Q(start_date__isnull=True) | Q(duration=0, event__duration=0)).all()

View File

@ -1,14 +0,0 @@
{% extends '_base.html' %}
{% load i18n %}
{% block body %}
<div class="container">
<h1>{{ site.name }} {% trans "Schedule" %}</h1>
{{ program }}
</div>
{% endblock %}

View File

@ -1,37 +0,0 @@
{% extends 'staff.html' %}
{% load i18n %}
{% block roomstab %} class="active"{% endblock %}
{% block content %}
<h1>{{ room.name }}
<small>{{ room.label }}</small>
<span class="glyphicon glyphicon-volume-{% if room.sound %}up{% else %}off{% endif %}"></span>
</h1>
<h2>{% trans "Scheduled talks" %}</h2>
{% for talk in room.talks_by_date %}
{% if forloop.first %}<ul>{% endif %}
<li>
<a href="{% url 'show-talk' talk.slug %}"><strong>{{ talk }}</strong></a> &ndash; <em>{{ talk.get_speakers_str }}</em><br />
<span>{{ talk.start_date }} &ndash; {% if talk.end_date %}{{ talk.end_date|date:"H:i" }}{% else %}?{% endif %}</span>
</li>
{% if forloop.last %}</ul>{% endif %}
{% empty %}
<em>{% trans "No talks." %}</em>
{% endfor %}
<h3>{% trans "Unscheduled talks" %}</h3>
{% for talk in room.unscheduled_talks %}
{% if forloop.first %}<ul>{% endif %}
<li>
<a href="{% url 'show-talk' talk.slug %}"><strong>{{ talk }}</strong></a> &ndash; <em>{{ talk.get_speakers_str }}</em>
</li>
{% if forloop.last %}</ul>{% endif %}
{% empty %}
<em>{% trans "No talks." %}</em>
{% endfor %}
{% endblock %}

View File

@ -1,23 +0,0 @@
{% extends 'staff.html' %}
{% load i18n %}
{% block roomstab %} class="active"{% endblock %}
{% block css %}
{{ block.super }}
{{ form.media.css }}
{% endblock %}
{% block content %}
<h1>{% if room %}{% trans "Modify the room" %}{% else %}{% trans "Add a room" %}{% endif %}</h1>
{% include "_form.html" %}
{% endblock %}
{% block js_end %}
{{ block.super }}
{{ form.media.js }}
{% endblock %}

View File

@ -1,53 +0,0 @@
{% extends 'staff.html' %}
{% load bootstrap3 accounts_tags i18n %}
{% block roomstab %} class="active"{% endblock %}
{% block content %}
<h1>{% trans "Rooms" %}</h1>
{% if request|orga %}
<p><a href="{% url 'add-room' %}" class="btn btn-success">{% trans "Add a room" %}</a><p>
{% endif %}
<div class="row">
{% for room in room_list %}
<div class="col-xs-6 col-sm-4">
<h2>
<a href="{% url 'show-room' room.slug %}">{{ room }}</a>
<span class="glyphicon glyphicon-volume-{% if room.sound %}up{% else %}off{% endif %}"></span>
</h2>
{% if room.label %}<p>{{ room.label }}</p>{% endif %}
<p>
{{ room.capacity }} {% trans "place" %}{{ room.capacity|pluralize }}
{% if request|staff %}
|
<span{% if room.unscheduled_talks %} class="text-danger" data-toggle="tooltip" data-placement="bottom" title="{% trans "Some talks are not scheduled yet." %}"{% endif %}>
{{ room.talks.count }} {% trans "talk" %}{{ room.talks.count|pluralize }}
</span>
{% if request|orga %}
|
<a href="{% url 'edit-room' room.slug %}">{% bootstrap_icon "pencil" %}</a>
{% endif %}
{% endif %}
</p>
</div>
{% cycle '' '<div class="clearfix visible-xs"></div>' %}
{% cycle '' '' '<div class="clearfix hidden-xs"></div>' %}
{% empty %}
<div class="col-xs-12"><em>{% trans "No rooms." %}</em></div>
{% endfor %}
</div>
{% endblock %}
{% block js_end %}
{{ block.super }}
<script type="text/javascript">
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
</script>
{% endblock %}

View File

@ -1,13 +0,0 @@
{% extends 'staff.html' %}
{% load i18n %}
{% block scheduletab %} class="active"{% endblock %}
{% block content %}
<h1>{% trans "Schedule" %}</h1>
{{ program }}
{% endblock %}

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,14 +0,0 @@
from django.conf.urls import url
from planning import views
urlpatterns = [
url(r'^room/$', views.RoomList.as_view(), name='list-rooms'),
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'^schedule/$', views.program_pending, name='show-schedule'),
url(r'^schedule/public/$', views.program_public, name='public-schedule'),
url(r'^planning/program/public/$', views.program_public),
]

View File

@ -1,328 +0,0 @@
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 django.core.cache import cache
from django.core.urlresolvers import reverse
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, pending=False, cache=True):
self.site = site
self.pending = pending
self.cache = cache
self.initialized = False
def _lazy_init(self):
self.conference = Conference.objects.get(site=self.site)
self.talks = Talk.objects.\
exclude(event__label__exact='').\
filter(site=self.site, room__isnull=False, start_date__isnull=False).\
filter(Q(duration__gt=0) | Q(event__duration__gt=0))
if self.pending:
self.talks = self.talks.exclude(accepted=False)
else:
self.talks = self.talks.filter(accepted=True)
self.talks = self.talks.order_by('start_date')
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)
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)
self.initialized = True
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 _html_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 = ' style="min-width: 100px;" colspan="%d"' % colspan
output += room_cell % {'name': escape(room.name), 'label': escape(room.label), 'options': options}
return '<tr>%s</tr>' % output
def _html_body(self):
output = ''
for day in sorted(self.days.keys()):
output += self._html_day_header(day)
output += self._html_day(day)
return output
def _html_day_header(self, day):
row = '<tr><td colspan="%(colcount)s"><h3>%(day)s</h3></td></tr>'
colcount = 1
for room, col in self.cols.items():
colcount += col
return row % {
'colcount': colcount,
'day': datetime.strftime(day, '%A %d %B'),
}
def _html_day(self, day):
output = []
rows = self.days[day]['rows']
for ts, rooms in rows.items():
output.append(self._html_row(day, ts, rooms))
return '\n'.join(output)
def _html_row(self, day, ts, rooms):
row = '<tr style="%(style)s">%(timeslot)s%(content)s</tr>'
cell = '<td%(options)s>%(content)s</td>'
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)) + '<br><em>' + escape(event.talk.get_speakers_str()) + '</em>'
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._html_timeslot(day, ts)
return row % {
'style': style,
'timeslot': timeslot,
'content': content,
}
def _html_timeslot(self, day, ts):
template = '<td>%(content)s</td>'
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 = '<td>%s %s</td>' % tuple(map(date_to_string, [start, end]))
return style, timeslot
def _as_html(self):
template = """<table class="table table-bordered text-center">\n%(header)s\n%(body)s\n</table>"""
if not self.initialized:
self._lazy_init()
return template % {
'header': self._html_header(),
'body': self._html_body(),
}
def _as_xml(self):
if not self.initialized:
self._lazy_init()
result = """<?xml version="1.0" encoding="UTF-8"?>
<schedule>
%(conference)s
%(days)s
</schedule>
"""
if not len(self.days):
return result % {'conference': '', 'days': ''}
conference_xml = """<conference>
<title>%(title)s</title>
<subtitle></subtitle>
<venue>%(venue)s</venue>
<city>%(city)s</city>
<start>%(start_date)s</start>
<end>%(end_date)s</end>
<days>%(days_count)s</days>
<day_change>09:00:00</day_change>
<timeslot_duration>00:05:00</timeslot_duration>
</conference>
""" % {
'title': self.site.name,
'venue': ', '.join(map(lambda x: x.strip(), self.conference.venue.split('\n'))),
'city': self.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 += '<day index="%(index)s" date="%(date)s">\n' % {
'index': index + 1,
'date': day.strftime('%Y-%m-%d'),
}
for room in self.rooms.all():
days_xml += ' <room name="%s">\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 id="%(person_id)s">%(person)s</person>\n' % {
'person_id': speaker.id,
'person': str(speaker.profile),
}
links = ''
registration = ''
if talk.registration_required and self.conference.subscriptions_open:
links += mark_safe("""
<link tag="registration">%(link)s</link>""" % {
'link': reverse('register-for-a-talk', args=[talk.slug]),
})
registration = """
<attendees_max>%(max)s</attendees_max>
<attendees_remain>%(remain)s</attendees_remain>""" % {
'max': talk.attendees_limit,
'remain': talk.remaining_attendees or 0,
}
if talk.materials:
links += mark_safe("""
<link tag="slides">%(link)s</link>""" % {
'link': talk.materials.url,
})
if talk.video:
links += mark_safe("""
<link tag="video">%(link)s</link>""" % {
'link': talk.video,
})
days_xml += """ <event id="%(id)s">
<start>%(start)s</start>
<duration>%(duration)s</duration>
<room>%(room)s</room>
<slug>%(slug)s</slug>
<title>%(title)s</title>
<subtitle/>
<track>%(track)s</track>
<type>%(type)s</type>
<language/>
<abstract>%(abstract)s</abstract>
<description>%(description)s</description>
<persons>
%(persons)s </persons>
<links>%(links)s
</links>%(registration)s
</event>\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 or ''),
'type': escape(talk.event.label),
'abstract': escape(talk.abstract),
'description': escape(talk.description),
'persons': persons,
'links': links,
'registration': registration,
}
days_xml += ' </room>\n'
days_xml += '</day>\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 _as_ics(self):
if not self.initialized:
self._lazy_init()
talks = [ICS_TALK.format(site=self.site, talk=talk) for talk in self.talks]
return ICS_MAIN.format(site=self.site, talks='\n'.join(talks))
def render(self, output='html'):
if self.cache:
cache_entry = 'program.%s.%s' % ('pending' if self.pending else 'final', output)
result = cache.get(cache_entry)
if not result:
result = getattr(self, '_as_%s' % output)()
cache.set(cache_entry, result, 3 * 60 * 60) # 3H
return mark_safe(result)
else:
return mark_safe(getattr(self, '_as_%s' % output)())
def __str__(self):
return self.render()
# FIXME definitely the wrong place for this, but hey, other templates are already here :P
ICS_MAIN = """BEGIN:VCALENDAR
PRODID:-//{site.domain}//{site.name}//FR
X-WR-CALNAME:PonyConf
X-WR-TIMEZONE:Europe/Paris
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
{talks}
END:VCALENDAR"""
ICS_TALK = """BEGIN:VEVENT
DTSTART:{talk.dtstart}
DTEND:{talk.dtend}
SUMMARY:{talk.title}
LOCATION:{talk.room}
STATUS: CONFIRMED
DESCRIPTION:{talk.abstract}\n---\n\n{talk.description}
UID:{site.domain}/{talk.id}
END:VEVENT
"""

View File

@ -1,61 +0,0 @@
from django.shortcuts import render
from django.contrib.sites.shortcuts import get_current_site
from django.views.generic import CreateView, DetailView, ListView, UpdateView
from django.http import HttpResponse, Http404
from ponyconf.mixins import OnSiteFormMixin
from accounts.decorators import orga_required, staff_required
from accounts.mixins import OrgaRequiredMixin, StaffRequiredMixin
from proposals.models import Talk
from .models import Room
from .forms import RoomForm
from .utils import Program
class RoomMixin(object):
def get_queryset(self):
return Room.objects.filter(site=get_current_site(self.request)).all()
class RoomFormMixin(OnSiteFormMixin):
form_class = RoomForm
class RoomList(StaffRequiredMixin, RoomMixin, ListView):
pass
class RoomCreate(OrgaRequiredMixin, RoomMixin, RoomFormMixin, CreateView):
model = Room
class RoomUpdate(OrgaRequiredMixin, RoomMixin, RoomFormMixin, UpdateView):
pass
class RoomDetail(StaffRequiredMixin, RoomMixin, DetailView):
pass
@staff_required
def program_pending(request):
output = request.GET.get('format', 'html')
return program(request, pending=True, output=output, html_template='schedule.html', cache=False)
def program_public(request):
output = request.GET.get('format', 'html')
return program(request, pending=False, output=output)
def program(request, pending=False, output='html', html_template='public-program.html', cache=True):
program = Program(site=get_current_site(request), pending=pending, cache=cache)
if output == 'html':
return render(request, 'planning/' + html_template, {'program': program.render('html')})
elif output == 'xml':
return HttpResponse(program.render('xml'), content_type="application/xml")
elif output == 'ics':
return HttpResponse(program.render('ics'), content_type="text/calendar")
else:
raise Http404("Format not available")