diff --git a/chrono/agendas/migrations/0016_desk.py b/chrono/agendas/migrations/0016_desk.py
new file mode 100644
index 00000000..5fa87fb3
--- /dev/null
+++ b/chrono/agendas/migrations/0016_desk.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('agendas', '0015_auto_20170628_1137'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Desk',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('label', models.CharField(max_length=150, verbose_name='Label')),
+ ('slug', models.SlugField(max_length=150, verbose_name='Slug')),
+ ('agenda', models.ForeignKey(to='agendas.Agenda')),
+ ],
+ options={
+ 'ordering': ['label'],
+ },
+ ),
+ ]
diff --git a/chrono/agendas/migrations/0017_timeperiod_desk.py b/chrono/agendas/migrations/0017_timeperiod_desk.py
new file mode 100644
index 00000000..eb96b4bf
--- /dev/null
+++ b/chrono/agendas/migrations/0017_timeperiod_desk.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+def set_timeperiod_desk(apps, schema_editor):
+ TimePeriod = apps.get_model('agendas', 'TimePeriod')
+ Desk = apps.get_model('agendas', 'Desk')
+ for time_period in TimePeriod.objects.all():
+ desk, created = Desk.objects.get_or_create(
+ label='Guichet 1', slug='guichet-1', agenda=time_period.agenda)
+ time_period.desk = desk
+ time_period.save()
+
+
+def unset_timeperiod_desk(apps, schema_editor):
+ pass
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('agendas', '0016_desk'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='timeperiod',
+ name='desk',
+ field=models.ForeignKey(to='agendas.Desk', null=True),
+ ),
+ migrations.RunPython(set_timeperiod_desk, unset_timeperiod_desk),
+ migrations.AlterField(
+ model_name='timeperiod',
+ name='desk',
+ field=models.ForeignKey(to='agendas.Desk'),
+ ),
+ migrations.RemoveField(
+ model_name='timeperiod',
+ name='agenda',
+ ),
+ ]
diff --git a/chrono/agendas/migrations/0018_event_desk.py b/chrono/agendas/migrations/0018_event_desk.py
new file mode 100644
index 00000000..b298d093
--- /dev/null
+++ b/chrono/agendas/migrations/0018_event_desk.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+def set_event_desk(apps, schema_editor):
+ Events = apps.get_model('agendas', 'Event')
+ Desk = apps.get_model('agendas', 'Desk')
+ for event in Events.objects.all():
+ if not event.agenda.kind == 'meetings':
+ continue
+
+ desk, created = Desk.objects.get_or_create(
+ label='Guichet 1', slug='guichet-1', agenda=event.agenda)
+ event.desk = desk
+ event.save()
+
+
+def unset_event_desk(apps, schema_editor):
+ pass
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('agendas', '0017_timeperiod_desk'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='event',
+ name='desk',
+ field=models.ForeignKey(to='agendas.Desk', null=True),
+ ),
+ migrations.RunPython(set_event_desk, unset_event_desk)
+ ]
diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index f52af113..1dbf7c97 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -33,6 +33,7 @@ AGENDA_KINDS = (
('meetings', _('Meetings')),
)
+
class Agenda(models.Model):
label = models.CharField(_('Label'), max_length=150)
slug = models.SlugField(_('Slug'))
@@ -95,7 +96,7 @@ class Agenda(models.Model):
agenda['events'] = [x.export_json() for x in self.event_set.all()]
elif self.kind == 'meetings':
agenda['meetingtypes'] = [x.export_json() for x in self.meetingtype_set.all()]
- agenda['timeperiods'] = [x.export_json() for x in self.timeperiod_set.all()]
+ agenda['desks'] = [desk.export_json() for desk in self.desk_set.all()]
return agenda
@classmethod
@@ -106,7 +107,7 @@ class Agenda(models.Model):
events = data.pop('events')
elif data['kind'] == 'meetings':
meetingtypes = data.pop('meetingtypes')
- timeperiods = data.pop('timeperiods')
+ desks = data.pop('desks')
agenda, created = cls.objects.get_or_create(slug=data['slug'], defaults=data)
if data['kind'] == 'events':
if overwrite:
@@ -117,40 +118,34 @@ class Agenda(models.Model):
elif data['kind'] == 'meetings':
if overwrite:
MeetingType.objects.filter(agenda=agenda).delete()
- TimePeriod.objects.filter(agenda=agenda).delete()
+ Desk.objects.filter(agenda=agenda).delete()
for type_data in meetingtypes:
type_data['agenda'] = agenda
MeetingType.import_json(type_data).save()
- for period_data in timeperiods:
- period_data['agenda'] = agenda
- TimePeriod.import_json(period_data).save()
+ for desk in desks:
+ desk['agenda'] = agenda
+ Desk.import_json(desk).save()
WEEKDAYS_LIST = sorted(WEEKDAYS.items(), key=lambda x: x[0])
class TimeSlot(object):
- def __init__(self, start_datetime, meeting_type):
+ def __init__(self, start_datetime, meeting_type, desk):
self.start_datetime = start_datetime
self.end_datetime = start_datetime + datetime.timedelta(minutes=meeting_type.duration)
self.meeting_type = meeting_type
self.id = '%s:%s' % (self.meeting_type.id, start_datetime.strftime('%Y-%m-%d-%H%M'))
-
- def intersects(self, timeslot):
- if self.start_datetime >= timeslot.end_datetime:
- return False
- if self.end_datetime <= timeslot.start_datetime:
- return False
- return True
+ self.desk = desk
def __unicode__(self):
return date_format(self.start_datetime, format='DATETIME_FORMAT')
class TimePeriod(models.Model):
- agenda = models.ForeignKey(Agenda)
weekday = models.IntegerField(_('Week day'), choices=WEEKDAYS_LIST)
start_time = models.TimeField(_('Start'))
end_time = models.TimeField(_('End'))
+ desk = models.ForeignKey('Desk', on_delete=models.CASCADE)
class Meta:
ordering = ['weekday', 'start_time']
@@ -192,7 +187,7 @@ class TimePeriod(models.Model):
if event_datetime > max_datetime:
break
- yield TimeSlot(start_datetime=event_datetime, meeting_type=meeting_type)
+ yield TimeSlot(start_datetime=event_datetime, meeting_type=meeting_type, desk=self.desk)
event_datetime = end_time
@@ -242,6 +237,7 @@ class Event(models.Model):
help_text=_('Optional label to identify this date.'))
full = models.BooleanField(default=False)
meeting_type = models.ForeignKey(MeetingType, null=True)
+ desk = models.ForeignKey('Desk', null=True)
class Meta:
ordering = ['agenda', 'start_datetime', 'label']
@@ -334,3 +330,45 @@ class Booking(models.Model):
with transaction.atomic():
self.secondary_booking_set.update(in_waiting_list=False)
self.save()
+
+
+class Desk(models.Model):
+ agenda = models.ForeignKey(Agenda)
+ label = models.CharField(_('Label'), max_length=150)
+ slug = models.SlugField(_('Slug'), max_length=150)
+
+ def __unicode__(self):
+ return self.label
+
+ class Meta:
+ ordering = ['label']
+
+ def save(self, *args, **kwargs):
+ if not self.slug:
+ base_slug = slugify(self.label)
+ slug = base_slug
+ i = 1
+ while True:
+ try:
+ Desk.objects.get(slug=slug, agenda=self.agenda)
+ except self.DoesNotExist:
+ break
+ slug = '%s-%s' % (base_slug, i)
+ i += 1
+ self.slug = slug
+ super(Desk, self).save(*args, **kwargs)
+
+ @classmethod
+ def import_json(cls, data):
+ timeperiods = data.pop('timeperiods')
+ instance, created = cls.objects.get_or_create(**data)
+ for timeperiod in timeperiods:
+ timeperiod['desk'] = instance
+ TimePeriod.import_json(timeperiod).save()
+ return instance
+
+ def export_json(self):
+ return {'label': self.label,
+ 'slug': self.slug,
+ 'timeperiods': [time_period.export_json() for time_period in self.timeperiod_set.all()]
+ }
diff --git a/chrono/api/views.py b/chrono/api/views.py
index f4e9a576..13b3ea57 100644
--- a/chrono/api/views.py
+++ b/chrono/api/views.py
@@ -14,7 +14,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from collections import defaultdict
import datetime
+import operator
+
+from intervaltree import IntervalTree
from django.core.urlresolvers import reverse
from django.http import Http404
@@ -27,7 +31,38 @@ from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from rest_framework.views import APIView
-from ..agendas.models import Agenda, Event, Booking, MeetingType, TimePeriod
+from ..agendas.models import (Agenda, Event, Booking, MeetingType,
+ TimePeriod, Desk)
+
+
+def get_open_slots(agenda, meeting_type):
+ min_datetime = now() + datetime.timedelta(days=agenda.minimal_booking_delay)
+ max_datetime = now() + datetime.timedelta(days=agenda.maximal_booking_delay)
+ time_period_filters = {
+ 'min_datetime': min_datetime,
+ 'max_datetime': max_datetime,
+ 'meeting_type': meeting_type
+ }
+
+ open_slots_by_desk = defaultdict(lambda: IntervalTree())
+ for time_period in TimePeriod.objects.filter(desk__agenda=agenda):
+ for slot in time_period.get_time_slots(**time_period_filters):
+ open_slots_by_desk[time_period.desk_id].addi(slot.start_datetime, slot.end_datetime, slot.desk)
+
+ for event in agenda.event_set.filter(
+ agenda=agenda, start_datetime__gte=min_datetime,
+ start_datetime__lte=max_datetime + datetime.timedelta(meeting_type.duration)).select_related(
+ 'meeting_type').extra(
+ select={
+ 'booking_count': """SELECT COUNT(*) FROM agendas_booking
+ WHERE agendas_booking.event_id = agendas_event.id
+ AND agendas_booking.cancellation_datetime IS NOT NULL"""}):
+ if event.booking_count:
+ continue
+ open_slots_by_desk[event.desk_id].remove_overlap(event.start_datetime, event.end_datetime)
+
+ open_slots = reduce(operator.__or__, open_slots_by_desk.values())
+ return open_slots
def get_agenda_detail(request, agenda):
@@ -151,27 +186,26 @@ class MeetingDatetimes(GenericAPIView):
max_datetime = now() + datetime.timedelta(days=agenda.maximal_booking_delay)
all_time_slots = []
- for time_period in TimePeriod.objects.filter(agenda=agenda):
- all_time_slots.extend(time_period.get_time_slots(
- min_datetime=min_datetime,
- max_datetime=max_datetime,
- meeting_type=meeting_type))
+ for time_period in TimePeriod.objects.filter(desk__agenda=agenda):
+ all_time_slots.extend(time_period.get_time_slots(min_datetime=min_datetime, max_datetime=max_datetime,
+ meeting_type=meeting_type))
- busy_time_slots = Event.objects.filter(agenda=agenda,
- start_datetime__gte=min_datetime,
- start_datetime__lte=max_datetime + datetime.timedelta(meeting_type.duration))
- busy_time_slots = list(busy_time_slots)
-
- entries = []
- # there's room for optimisations here, for a start both lists
- # could be presorted and past busy time slots removed along the way.
+ open_slots = get_open_slots(agenda, meeting_type)
+ open_entries = {}
+ closed_entries = {}
for time_slot in all_time_slots:
if time_slot.start_datetime < now_datetime:
continue
- time_slot.full = bool(any((x for x in busy_time_slots if x.full and time_slot.intersects(x))))
- entries.append(time_slot)
+ key = '%s-%s' % (time_slot.start_datetime, time_slot.end_datetime)
+ if open_slots.search(time_slot.start_datetime, time_slot.end_datetime, strict=True):
+ time_slot.full = False
+ open_entries[key] = time_slot
+ else:
+ time_slot.full = True
+ closed_entries[key] = time_slot
- entries.sort(key=lambda x: x.start_datetime)
+ closed_entries.update(open_entries)
+ entries = sorted(closed_entries.values(), key=lambda x: x.start_datetime)
fake_event_pk = '__event_id__'
fillslot_url = request.build_absolute_uri(
@@ -251,12 +285,24 @@ class Fillslot(GenericAPIView):
meeting_type_id, start_datetime_str = event_pk.split(':')
start_datetime = make_aware(datetime.datetime.strptime(
start_datetime_str, '%Y-%m-%d-%H%M'))
- event, created = Event.objects.get_or_create(agenda=agenda,
+
+ event = Event.objects.create(agenda=agenda,
meeting_type_id=meeting_type_id,
start_datetime=start_datetime,
- defaults={'full': False, 'places': 1})
- if created:
- event.save()
+ full=False, places=1)
+
+ available_desk = None
+
+ open_slots = get_open_slots(agenda, MeetingType.objects.get(id=meeting_type_id))
+ slot = open_slots[event.start_datetime:event.end_datetime]
+ if slot:
+ available_desk = slot.pop().data
+
+ if not available_desk:
+ return Response({'err': 1, 'reason': 'no more desk available'})
+
+ event.desk = available_desk
+ event.save()
event_pk = event.id
event = Event.objects.filter(id=event_pk)[0]
diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index 5c73daa2..c60cb066 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -21,7 +21,7 @@ from django import forms
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
-from chrono.agendas.models import Event, MeetingType, TimePeriod
+from chrono.agendas.models import Event, MeetingType, TimePeriod, Desk
from . import widgets
@@ -44,7 +44,7 @@ class EventForm(forms.ModelForm):
'agenda': forms.HiddenInput(),
'start_datetime': DateTimeWidget(),
}
- exclude = ['full', 'meeting_type']
+ exclude = ['full', 'meeting_type', 'desk']
class NewMeetingTypeForm(forms.ModelForm):
@@ -69,9 +69,27 @@ class TimePeriodForm(forms.ModelForm):
class Meta:
model = TimePeriod
widgets = {
- 'agenda': forms.HiddenInput(),
'start_time': widgets.TimeWidget(),
'end_time': widgets.TimeWidget(),
+ 'desk': forms.HiddenInput(),
+ }
+ exclude = []
+
+
+class NewDeskForm(forms.ModelForm):
+ class Meta:
+ model = Desk
+ widgets = {
+ 'agenda': forms.HiddenInput(),
+ }
+ exclude = ['slug']
+
+
+class DeskForm(forms.ModelForm):
+ class Meta:
+ model = Desk
+ widgets = {
+ 'agenda': forms.HiddenInput(),
}
exclude = []
diff --git a/chrono/manager/static/css/style.css b/chrono/manager/static/css/style.css
index 0c888929..047ae198 100644
--- a/chrono/manager/static/css/style.css
+++ b/chrono/manager/static/css/style.css
@@ -38,3 +38,13 @@ h2 span.identifier {
.time-only-picker tbody td {
width: 200px;
}
+
+.timeperiods {
+ position: relative;
+}
+
+.timeperiod {
+ float: left;
+ display: block;
+ margin: 10px;
+}
diff --git a/chrono/manager/templates/chrono/manager_agenda_view.html b/chrono/manager/templates/chrono/manager_agenda_view.html
index 5643aa02..3363a412 100644
--- a/chrono/manager/templates/chrono/manager_agenda_view.html
+++ b/chrono/manager/templates/chrono/manager_agenda_view.html
@@ -15,7 +15,7 @@
{% trans 'New Event' %}
{% else %}
{% trans 'New Meeting Type' %}
- {% trans 'New Time Period' %}
+ {% trans 'New Desk' %}
{% endif %}
{% endif %}
{% endblock %}
@@ -105,25 +105,41 @@
{% endif %}
{% trans 'Time Periods' %}
-{% if object.timeperiod_set.count %}
-
-{% else %}
-
- {% blocktrans %}
- This agenda doesn't have any time period yet. Click on the "New Time Period" button in
- the top right of the page to add a first one.
- {% endblocktrans %}
-
+ {% if object.desk_set.count %}
+
+ {% for desk in object.desk_set.all %}
+
+ {% if object.desk_set.count > 1 %}
+
+ {% endif %}
+ {% url 'chrono-manager-agenda-add-time-period' agenda_pk=object.pk pk=desk.pk as add_time_period_url %}
+ {% if desk.timeperiod_set.count %}
+
+ {% else %}
+
+ {% endif %}
+
+ {% endfor %}
+
+ {% else %}
+
+ {% blocktrans %}
+ This agenda doesn't have any desk yet. Click on the "New Desk" button in
+ the top right of the page to add a first one.
+ {% endblocktrans %}
+
{% endif %}
diff --git a/chrono/manager/templates/chrono/manager_desk_form.html b/chrono/manager/templates/chrono/manager_desk_form.html
new file mode 100644
index 00000000..fadb2a9c
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_desk_form.html
@@ -0,0 +1,35 @@
+{% extends "chrono/manager_agenda_view.html" %}
+{% load i18n %}
+
+{% block extrascripts %}
+{{ block.super }}
+{{ form.media }}
+{% endblock %}
+
+{% block breadcrumb %}
+{{ block.super }}
+{% if object.id %}
+{{object.label}}
+{% endif %}
+{% endblock %}
+
+{% block appbar %}
+{% if object.id %}
+{% trans "Edit Desk" %}
+{% trans 'Delete' %}
+{% else %}
+{% trans "New Desk" %}
+{% endif %}
+{% endblock %}
+
+{% block content %}
+
+
+{% endblock %}
diff --git a/chrono/manager/templates/chrono/manager_time_period_form.html b/chrono/manager/templates/chrono/manager_time_period_form.html
index 42c14697..b3659035 100644
--- a/chrono/manager/templates/chrono/manager_time_period_form.html
+++ b/chrono/manager/templates/chrono/manager_time_period_form.html
@@ -9,7 +9,7 @@
{% block breadcrumb %}
{{ block.super }}
{% if object.id %}
-{{object.agenda.label}}
+{{object.desk.agenda.label}}
{% endif %}
{% endblock %}
@@ -29,7 +29,7 @@
{{ form.as_p }}
{% endblock %}
diff --git a/chrono/manager/urls.py b/chrono/manager/urls.py
index 612b9f48..a5cf372d 100644
--- a/chrono/manager/urls.py
+++ b/chrono/manager/urls.py
@@ -44,13 +44,20 @@ urlpatterns = [
url(r'^meetingtypes/(?P\w+)/delete$', views.meeting_type_delete,
name='chrono-manager-meeting-type-delete'),
- url(r'^agendas/(?P\w+)/add-time-period$', views.agenda_add_time_period,
+ url(r'^agendas/(?P\d+)/desk/(?P\d+)/add-time-period$', views.agenda_add_time_period,
name='chrono-manager-agenda-add-time-period'),
url(r'^timeperiods/(?P\w+)/edit$', views.time_period_edit,
name='chrono-manager-time-period-edit'),
url(r'^timeperiods/(?P\w+)/delete$', views.time_period_delete,
name='chrono-manager-time-period-delete'),
+ url(r'^agendas/(?P\w+)/add-desk$', views.agenda_add_desk,
+ name='chrono-manager-agenda-add-desk'),
+ url(r'^desks/(?P\w+)/edit$', views.desk_edit,
+ name='chrono-manager-desk-edit'),
+ url(r'^desks/(?P\w+)/delete$', views.desk_delete,
+ name='chrono-manager-desk-delete'),
+
url(r'^agendas/events.csv$', views.agenda_import_events_sample_csv,
name='chrono-manager-sample-events-csv'),
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index 701c21e5..cc05e62e 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -27,10 +27,11 @@ from django.utils.encoding import force_text
from django.views.generic import (DetailView, CreateView, UpdateView,
ListView, DeleteView, FormView, TemplateView)
-from chrono.agendas.models import Agenda, Event, MeetingType, TimePeriod, Booking
+from chrono.agendas.models import (Agenda, Event, MeetingType, TimePeriod,
+ Booking, Desk)
from .forms import (EventForm, NewMeetingTypeForm, MeetingTypeForm,
- TimePeriodForm, ImportEventsForm)
+ TimePeriodForm, ImportEventsForm, NewDeskForm, DeskForm)
class HomepageView(ListView):
@@ -57,6 +58,13 @@ class AgendaAddView(CreateView):
raise PermissionDenied()
return super(AgendaAddView, self).dispatch(request, *args, **kwargs)
+ def form_valid(self, form):
+ model_form = super(AgendaAddView, self).form_valid(form)
+ if self.object.kind == 'meetings':
+ default_desk = Desk(agenda=self.object, label=_('Desk 1'))
+ default_desk.save()
+ return model_form
+
agenda_add = AgendaAddView.as_view()
@@ -160,6 +168,50 @@ class ManagedAgendaSubobjectMixin(object):
return self.agenda.get_absolute_url()
+class ManagedDeskMixin(object):
+ desk = None
+
+ def dispatch(self, request, *args, **kwargs):
+ try:
+ self.desk = Desk.objects.get(id=kwargs.get('pk'))
+ except Desk.DoesNotExist:
+ raise Http404()
+ if not self.desk.agenda.can_be_managed(request.user):
+ raise PermissionDenied()
+ return super(ManagedDeskMixin, self).dispatch(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super(ManagedDeskMixin, self).get_context_data(**kwargs)
+ context['desk'] = self.desk
+ return context
+
+ def get_initial(self):
+ initial = super(ManagedDeskMixin, self).get_initial()
+ initial['desk'] = self.desk
+ return initial
+
+ def get_success_url(self):
+ return self.desk.agenda.get_absolute_url()
+
+
+class ManagedDeskSubobjectMixin(object):
+ desk = None
+
+ def dispatch(self, request, *args, **kwargs):
+ self.desk = self.get_object().desk
+ if not self.desk.agenda.can_be_managed(request.user):
+ raise PermissionDenied()
+ return super(ManagedDeskSubobjectMixin, self).dispatch(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super(ManagedDeskSubobjectMixin, self).get_context_data(**kwargs)
+ context['desk'] = self.object.desk
+ return context
+
+ def get_success_url(self):
+ return self.desk.agenda.get_absolute_url()
+
+
class AgendaAddEventView(ManagedAgendaMixin, CreateView):
template_name = 'chrono/manager_event_form.html'
model = Event
@@ -242,15 +294,15 @@ class MeetingTypeDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
meeting_type_delete = MeetingTypeDeleteView.as_view()
-class AgendaAddTimePeriodView(ManagedAgendaMixin, CreateView):
+class AgendaAddTimePeriodView(ManagedDeskMixin, CreateView):
template_name = 'chrono/manager_time_period_form.html'
- model = Event
+ model = TimePeriod
form_class = TimePeriodForm
agenda_add_time_period = AgendaAddTimePeriodView.as_view()
-class TimePeriodEditView(ManagedAgendaSubobjectMixin, UpdateView):
+class TimePeriodEditView(ManagedDeskSubobjectMixin, UpdateView):
template_name = 'chrono/manager_time_period_form.html'
model = TimePeriod
form_class = TimePeriodForm
@@ -258,13 +310,39 @@ class TimePeriodEditView(ManagedAgendaSubobjectMixin, UpdateView):
time_period_edit = TimePeriodEditView.as_view()
-class TimePeriodDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
+class TimePeriodDeleteView(ManagedDeskSubobjectMixin, DeleteView):
template_name = 'chrono/manager_confirm_delete.html'
model = TimePeriod
time_period_delete = TimePeriodDeleteView.as_view()
+class AgendaAddDesk(ManagedAgendaMixin, CreateView):
+ template_name = 'chrono/manager_desk_form.html'
+ model = Desk
+ form_class = NewDeskForm
+
+
+agenda_add_desk = AgendaAddDesk.as_view()
+
+
+class DeskEditView(ManagedAgendaSubobjectMixin, UpdateView):
+ template_name = 'chrono/manager_desk_form.html'
+ model = Desk
+ form_class = DeskForm
+
+
+desk_edit = DeskEditView.as_view()
+
+
+class DeskDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
+ template_name = 'chrono/manager_confirm_delete.html'
+ model = Desk
+
+
+desk_delete = DeskDeleteView.as_view()
+
+
def menu_json(request):
response = HttpResponse(content_type='application/json')
label = _('Agendas')
diff --git a/debian/control b/debian/control
index ae205de2..87b73586 100644
--- a/debian/control
+++ b/debian/control
@@ -10,7 +10,8 @@ Package: python-chrono
Architecture: all
Depends: ${misc:Depends}, ${python:Depends},
python-django (>= 1.8),
- python-gadjo
+ python-gadjo,
+ python-intervaltree
Recommends: python-django-mellon
Description: Agendas System (Python module)
diff --git a/setup.py b/setup.py
index e8b5a395..1455e0d5 100644
--- a/setup.py
+++ b/setup.py
@@ -106,6 +106,7 @@ setup(
'gadjo',
'djangorestframework>=3.1',
'django-jsonfield >= 0.9.3',
+ 'intervaltree',
],
zip_safe=False,
cmdclass={
diff --git a/tests/test_agendas.py b/tests/test_agendas.py
index 93900f9d..50c1a34e 100644
--- a/tests/test_agendas.py
+++ b/tests/test_agendas.py
@@ -1,8 +1,6 @@
import pytest
import datetime
-from django.db import connection
-from django.db.migrations.executor import MigrationExecutor
from django.utils.timezone import now
from chrono.agendas.models import Agenda, Event, Booking, MeetingType
@@ -68,27 +66,6 @@ def test_event_bookable_period():
event.save()
assert event.in_bookable_period() is False
-def test_meeting_type_slug_migration():
- executor = MigrationExecutor(connection)
- migrate_from = [('agendas', '0011_meetingtype_slug')]
- migrate_to = [('agendas', '0013_auto_20161028_1603')]
- executor.migrate(migrate_from)
- executor.loader.build_graph()
-
- old_apps = executor.loader.project_state(migrate_from).apps
- OldAgenda = old_apps.get_model('agendas', 'Agenda')
- OldMeetingType = old_apps.get_model('agendas', 'MeetingType')
-
- agenda = OldAgenda(label=u'Foo bar')
- agenda.save()
-
- meeting_type = OldMeetingType(agenda=agenda, label=u'Baz')
- meeting_type.save()
-
- executor.migrate(migrate_to)
-
- assert MeetingType.objects.get(id=meeting_type.id).slug == 'baz'
-
def test_meeting_type_slugs():
agenda1 = Agenda(label=u'Foo bar')
agenda1.save()
diff --git a/tests/test_api.py b/tests/test_api.py
index f92f4dfa..86a63619 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -5,10 +5,12 @@ import urlparse
from webtest import TestApp
from django.contrib.auth import get_user_model
+from django.db import connection
from django.test import override_settings
+from django.test.utils import CaptureQueriesContext
from django.utils.timezone import now, make_aware, localtime
-from chrono.agendas.models import Agenda, Event, Booking, MeetingType, TimePeriod
+from chrono.agendas.models import Agenda, Event, Booking, MeetingType, TimePeriod, Desk
import chrono.api.views
@@ -76,11 +78,13 @@ def meetings_agenda(time_zone, mock_now):
test_1st_weekday = (localtime(now()).weekday() + 2) % 7
test_2nd_weekday = (localtime(now()).weekday() + 3) % 7
- time_period = TimePeriod(agenda=agenda, weekday=test_1st_weekday,
- start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
+ default_desk, created = Desk.objects.get_or_create(agenda=agenda, label='Desk 1')
+
+ time_period = TimePeriod(weekday=test_1st_weekday,
+ start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=default_desk)
time_period.save()
- time_period = TimePeriod(agenda=agenda, weekday=test_2nd_weekday,
- start_time=datetime.time(10, 0), end_time=datetime.time(17, 0))
+ time_period = TimePeriod(weekday=test_2nd_weekday,
+ start_time=datetime.time(10, 0), end_time=datetime.time(17, 0), desk=default_desk)
time_period.save()
return agenda
@@ -204,7 +208,7 @@ def test_datetimes_api_meetings_agenda(app, meetings_agenda):
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M')
ev = Event(agenda=meetings_agenda, meeting_type=meeting_type,
- places=1, full=False, start_datetime=make_aware(dt))
+ places=1, full=False, start_datetime=make_aware(dt), desk=Desk.objects.first())
ev.save()
booking = Booking(event=ev)
booking.save()
@@ -224,11 +228,12 @@ def test_datetimes_api_meetings_agenda(app, meetings_agenda):
# skip this part of the test as it would require support for events
# crossing midnight
return
- TimePeriod.objects.filter(agenda=meetings_agenda).delete()
+ default_desk, _ = Desk.objects.get_or_create(agenda=meetings_agenda, slug='desk-1')
+ TimePeriod.objects.filter(desk=default_desk).delete()
start_time = localtime(now()) - datetime.timedelta(minutes=10)
- time_period = TimePeriod(agenda=meetings_agenda, weekday=localtime(now()).weekday(),
+ time_period = TimePeriod(weekday=localtime(now()).weekday(),
start_time=start_time,
- end_time=start_time + datetime.timedelta(hours=1))
+ end_time=start_time + datetime.timedelta(hours=1), desk=default_desk)
time_period.save()
meetings_agenda.minimal_booking_delay = 0
meetings_agenda.maximal_booking_delay = 10
@@ -284,7 +289,7 @@ def test_booking_api_meeting(app, meetings_agenda, user):
# try booking the same timeslot
resp2 = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
assert resp2.json['err'] == 1
- assert resp2.json['reason'] == 'sold out'
+ assert resp2.json['reason'] == 'no more desk available'
# try booking another timeslot
event_id = resp.json['data'][3]['id']
@@ -690,3 +695,86 @@ def test_agenda_api_date_range(app, some_data):
assert len(resp.json['data']) == 3
assert resp.json['data'][0]['datetime'] == '2017-05-30 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-05-30 11:00:00'
+
+def test_agenda_meeting_api_multiple_desk(app, meetings_agenda, user):
+ app.authorization = ('Basic', ('john.doe', 'password'))
+ agenda_id = meetings_agenda.slug
+ meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
+
+ # add booking of another meeting type
+ meeting_type2 = MeetingType.objects.create(agenda=meetings_agenda, label='Tux kart', duration=60)
+ resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type2.id)
+ event_id = resp.json['data'][0]['id']
+ resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
+ cancel_url = resp.json['api']['cancel_url']
+
+ # add a second desk
+ time_period = meetings_agenda.desk_set.first().timeperiod_set.first()
+ desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda)
+ TimePeriod.objects.create(
+ start_time=time_period.start_time, end_time=time_period.end_time,
+ weekday=time_period.weekday, desk=desk2)
+
+ resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
+ event_id = resp.json['data'][1]['id']
+ resp_booking = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
+ assert Booking.objects.count() == 2
+ assert resp_booking.json['datetime'][:16] == localtime(Booking.objects.last().event.start_datetime
+ ).isoformat()[:16]
+
+ resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
+ assert len(resp.json['data']) == len([x for x in resp2.json['data'] if not x['disabled']]) + 1
+
+ # try booking the same timeslot and fail
+ resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
+ assert Booking.objects.count() == 2
+ assert resp.json['err'] == 1
+ assert resp.json['reason'] == 'no more desk available'
+
+ # cancel first booking and retry
+ resp = app.post(cancel_url)
+ # capture number of queries made for datetime endpoint with few bookings
+ with CaptureQueriesContext(connection) as ctx:
+ resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
+ queries_count_datetime1 = len(ctx.captured_queries)
+ assert len(resp2.json['data']) == len([x for x in resp.json['data'] if not x['disabled']])
+
+ # capture number of queries made for fillslot endpoint with few bookings
+ with CaptureQueriesContext(connection) as ctx:
+ resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
+ queries_count_fillslot1 = len(ctx.captured_queries)
+
+ assert resp_booking.json['datetime'][:16] == localtime(Booking.objects.last().event.start_datetime
+ ).isoformat()[:16]
+ cancel_url = resp.json['api']['cancel_url']
+
+ resp3 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
+ assert len(resp2.json['data']) == len([x for x in resp3.json['data'] if not x['disabled']]) + 1
+
+ # cancel a booking
+ resp = app.post(cancel_url)
+ resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
+ assert len(resp.json['data']) == len(resp2.json['data'])
+
+ # try booking the same slot to make sure that cancelled booking has freed the slot
+ resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
+ assert Booking.objects.count() == 4
+ assert Booking.objects.exclude(cancellation_datetime__isnull=True).count() == 2
+ assert resp_booking.json['datetime'][:16] == localtime(Booking.objects.last().event.start_datetime
+ ).isoformat()[:16]
+
+ # try booking the same timeslot again and fail
+ resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
+ assert resp.json['err'] == 1
+ assert resp.json['reason'] == 'no more desk available'
+
+ # fill the agenda and make sure big O is O(1)
+ for idx, event_data in enumerate(resp2.json['data'][2:10]):
+ booking_url = event_data['api']['fillslot_url']
+ with CaptureQueriesContext(connection) as ctx:
+ app.post(booking_url)
+ assert len(ctx.captured_queries) == queries_count_fillslot1
+
+ with CaptureQueriesContext(connection) as ctx:
+ app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
+ assert queries_count_datetime1 == len(ctx.captured_queries)
diff --git a/tests/test_data_migrations.py b/tests/test_data_migrations.py
new file mode 100644
index 00000000..51eeb769
--- /dev/null
+++ b/tests/test_data_migrations.py
@@ -0,0 +1,77 @@
+import datetime
+import pytest
+
+from django.db import connection
+from django.db.migrations.executor import MigrationExecutor
+
+from chrono.agendas.models import MeetingType
+
+pytestmark = pytest.mark.django_db
+
+
+def test_meeting_type_slug_migration():
+ executor = MigrationExecutor(connection)
+ migrate_from = [('agendas', '0011_meetingtype_slug')]
+ migrate_to = [('agendas', '0013_auto_20161028_1603')]
+ executor.migrate(migrate_from)
+ executor.loader.build_graph()
+
+ old_apps = executor.loader.project_state(migrate_from).apps
+ OldAgenda = old_apps.get_model('agendas', 'Agenda')
+ OldMeetingType = old_apps.get_model('agendas', 'MeetingType')
+
+ agenda = OldAgenda(label=u'Foo bar')
+ agenda.save()
+
+ meeting_type = OldMeetingType(agenda=agenda, label=u'Baz')
+ meeting_type.save()
+
+ executor.migrate(migrate_to)
+
+ assert MeetingType.objects.get(id=meeting_type.id).slug == 'baz'
+
+
+def test_timepriod_data_migrations(transactional_db):
+ executor = MigrationExecutor(connection)
+ app = 'agendas'
+ migrate_from = [(app, '0016_desk')]
+ migrate_to = [(app, '0018_event_desk')]
+ executor.migrate(migrate_from)
+ old_apps = executor.loader.project_state(migrate_from).apps
+ Agenda = old_apps.get_model(app, 'Agenda')
+ MeetingType = old_apps.get_model(app, 'MeetingType')
+ TimePeriod = old_apps.get_model(app, 'TimePeriod')
+ Event = old_apps.get_model(app, 'Event')
+ agenda = Agenda.objects.create(label='foo', slug='foo', kind='meetings')
+ agenda2 = Agenda.objects.create(label='bar', slug='bar', kind='events')
+ TimePeriod.objects.create(agenda=agenda, weekday=1,
+ start_time=datetime.time(8, 0),
+ end_time=datetime.time(12, 0))
+ TimePeriod.objects.create(agenda=agenda, weekday=2,
+ start_time=datetime.time(8, 0),
+ end_time=datetime.time(10, 0))
+ TimePeriod.objects.create(agenda=agenda, weekday=3,
+ start_time=datetime.time(9, 0),
+ end_time=datetime.time(12, 0))
+ meeting_type = MeetingType.objects.create(agenda=agenda, label='foo',
+ slug='foo', duration=60)
+ Event.objects.create(agenda=agenda, places=1, meeting_type=meeting_type,
+ start_datetime=datetime.datetime(2017, 5, 22, 9, 30))
+ Event.objects.create(agenda=agenda, places=1, meeting_type=meeting_type,
+ start_datetime=datetime.datetime(2017, 5, 22, 10, 0))
+ Event.objects.create(agenda=agenda2, places=5,
+ start_datetime=datetime.datetime(2017, 5, 22, 10, 0))
+ executor.loader.build_graph()
+ executor.migrate(migrate_to)
+ new_apps = executor.loader.project_state(migrate_to).apps
+ Agenda = new_apps.get_model(app, 'Agenda')
+ agenda = Agenda.objects.get(slug='foo')
+ TimePeriod = new_apps.get_model(app, 'TimePeriod')
+ for time_period in TimePeriod.objects.all():
+ assert time_period.desk.label == 'Guichet 1'
+ assert time_period.desk.slug == 'guichet-1'
+
+ Event = new_apps.get_model(app, 'Event')
+ for event in Event.objects.filter(agenda=agenda):
+ assert event.desk.label == 'Guichet 1'
+ assert event.desk.slug == 'guichet-1'
diff --git a/tests/test_import_export.py b/tests/test_import_export.py
index 9870140a..19a4113a 100644
--- a/tests/test_import_export.py
+++ b/tests/test_import_export.py
@@ -9,7 +9,7 @@ import tempfile
import pytest
from django.core.management import call_command
-from chrono.agendas.models import Agenda, Event, MeetingType, TimePeriod
+from chrono.agendas.models import Agenda, Event, MeetingType, TimePeriod, Desk
from chrono.manager.utils import export_site, import_site
from test_api import some_data, meetings_agenda, time_zone, mock_now
@@ -51,17 +51,19 @@ def test_import_export(app, some_data, meetings_agenda):
agenda2 = Agenda.objects.get(label=u'Foo bar Meeting')
event = Event(agenda=agenda1, start_datetime= datetime.datetime.now(), places=10)
event.save()
- timeperiod = TimePeriod(agenda=agenda2, weekday=2,
+ desk, _ = Desk.objects.get_or_create(agenda=agenda2, label='Desk A', slug='desk-a')
+ timeperiod = TimePeriod(weekday=2, desk=desk,
start_time=datetime.time(10, 0), end_time=datetime.time(11, 0))
timeperiod.save()
import_site(json.loads(output), overwrite=True)
assert Event.objects.filter(id=event.id).count() == 0
+ assert Desk.objects.filter(slug='desk-a').count() == 0
assert TimePeriod.objects.filter(id=timeperiod.id).count() == 0
event = Event(agenda=agenda1, start_datetime= datetime.datetime.now(), places=10)
event.save()
- timeperiod = TimePeriod(agenda=agenda2, weekday=2,
+ timeperiod = TimePeriod(weekday=2, desk=desk,
start_time=datetime.time(10, 0), end_time=datetime.time(11, 0))
timeperiod.save()
import_site(json.loads(output), overwrite=False)
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 34518dd5..2b261c38 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -8,7 +8,8 @@ from webtest import TestApp, Upload
from chrono.wsgi import application
-from chrono.agendas.models import Agenda, Event, Booking, MeetingType, TimePeriod
+from chrono.agendas.models import (Agenda, Event, Booking, MeetingType,
+ TimePeriod, Desk)
pytestmark = pytest.mark.django_db
@@ -569,23 +570,24 @@ def test_meetings_agenda_delete_meeting_type(app, admin_user):
def test_meetings_agenda_add_time_period(app, admin_user):
agenda = Agenda(label=u'Foo bar', kind='meetings')
agenda.save()
+ desk = Desk.objects.create(agenda=agenda, label='Desk A')
app = login(app)
resp = app.get('/manage/agendas/%s/' % agenda.id, status=200)
- assert "This agenda doesn't have any time period yet." in resp.body
+ assert "This desk doesn't have any time period yet." in resp.body
resp = resp.click('New Time Period')
resp.form['weekday'].select(text='Wednesday')
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '17:00'
resp = resp.form.submit()
- assert TimePeriod.objects.get(agenda=agenda).weekday == 2
- assert TimePeriod.objects.get(agenda=agenda).start_time.hour == 10
- assert TimePeriod.objects.get(agenda=agenda).start_time.minute == 0
- assert TimePeriod.objects.get(agenda=agenda).end_time.hour == 17
- assert TimePeriod.objects.get(agenda=agenda).end_time.minute == 0
+ assert TimePeriod.objects.get(desk=desk).weekday == 2
+ assert TimePeriod.objects.get(desk=desk).start_time.hour == 10
+ assert TimePeriod.objects.get(desk=desk).start_time.minute == 0
+ assert TimePeriod.objects.get(desk=desk).end_time.hour == 17
+ assert TimePeriod.objects.get(desk=desk).end_time.minute == 0
resp = resp.follow()
# add a second time period
- resp = resp.click('New Time Period')
+ resp = resp.click('Add')
resp.form['weekday'].select(text='Monday')
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '13:00'
@@ -600,13 +602,15 @@ def test_meetings_agenda_add_time_period(app, admin_user):
assert 'Edit Time Period' in resp.body
resp.form['start_time'] = '9:00'
resp = resp.form.submit()
- assert TimePeriod.objects.get(agenda=agenda, weekday=2).start_time.hour == 9
+ assert TimePeriod.objects.get(desk=desk, weekday=2).start_time.hour == 9
+
def test_meetings_agenda_delete_time_period(app, admin_user):
agenda = Agenda(label=u'Foo bar', kind='meetings')
agenda.save()
- time_period = TimePeriod(agenda=agenda, weekday=2,
+ desk = Desk.objects.create(agenda=agenda, label='Desk A')
+ time_period = TimePeriod(desk=desk, weekday=2,
start_time=datetime.time(10, 0),
end_time=datetime.time(18, 0))
time_period.save()
@@ -618,3 +622,83 @@ def test_meetings_agenda_delete_time_period(app, admin_user):
resp = resp.form.submit()
assert resp.location == 'http://testserver/manage/agendas/%s/' % agenda.id
assert TimePeriod.objects.count() == 0
+
+
+def test_meetings_agenda_add_time_period_on_missing_desk(app, admin_user):
+ app = login(app)
+ agenda = Agenda(label=u'Foo bar', kind='meetings')
+ agenda.save()
+ app.get('/manage/agendas/1/desk/777/add-time-period', status=404)
+
+
+def test_meetings_agenda_add_time_period_as_manager(app, manager_user):
+ agenda = Agenda(label='Foo bar', kind='meetings')
+ agenda.view_role = manager_user.groups.all()[0]
+ agenda.save()
+ desk = Desk.objects.create(agenda=agenda, label='Desk A')
+ app = login(app, username='manager', password='manager')
+ resp = app.get('/manage/agendas/%d/' % agenda.id)
+ assert 'New Meeting Type' in resp.content
+ assert 'New Time Period' in resp.content
+ assert 'This desk doesn\'t have any time period yet.' in resp.content
+ app.get('/manage/agendas/%d/desk/%d/add-time-period' % (agenda.id, desk.id), status=403)
+ time_period = TimePeriod(desk=desk, weekday=0, start_time=datetime.time(9, 0),
+ end_time=datetime.time(12, 0))
+ time_period.save()
+ app.get('/manage/timeperiods/%d/edit' % time_period.id, status=403)
+ # grant edit right to manager
+ agenda.edit_role = manager_user.groups.all()[0]
+ agenda.save()
+ app.get('/manage/agendas/%d/desk/%d/add-time-period' % (agenda.id, desk.id), status=200)
+ app.get('/manage/timeperiods/%d/edit' % time_period.id, status=200)
+
+
+def test_meetings_agenda_add_desk(app, admin_user):
+ app = login(app)
+ resp = app.get('/manage/', status=200)
+ resp = resp.click('New')
+ resp.form['label'] = 'Foo bar'
+ resp.form['kind'] = 'meetings'
+ resp = resp.form.submit()
+ assert Desk.objects.count() == 1
+ assert str(Desk.objects.first()) == 'Desk 1'
+ agenda = Agenda.objects.get(slug='foo-bar')
+
+ resp = app.get('/manage/agendas/%s/' % agenda.id, status=200)
+ resp = resp.click('New Desk')
+ resp.form['label'] = 'Desk A'
+ resp = resp.form.submit().follow()
+ assert Desk.objects.count() == 2
+ resp = resp.click('New Desk')
+ resp.form['label'] = 'Desk A'
+ resp = resp.form.submit().follow()
+ assert Desk.objects.count() == 3
+ assert Desk.objects.filter(slug='desk-a-1').count() == 1
+ assert 'Desk A' in resp.text
+
+ resp = resp.click('Desk A', index=1)
+ resp.form['label'] = 'Desk B'
+ resp = resp.form.submit().follow()
+ assert 'Desk A' in resp.text
+ assert 'Desk B' in resp.text
+
+
+def test_meetings_agenda_delete_desk(app, admin_user):
+ app = login(app)
+ resp = app.get('/manage/', status=200)
+ resp = resp.click('New')
+ resp.form['label'] = 'Foo bar'
+ resp.form['kind'] = 'meetings'
+ resp = resp.form.submit()
+ assert Desk.objects.count() == 1
+ agenda = Agenda.objects.get(slug='foo-bar')
+ resp = app.get('/manage/agendas/%s/' % agenda.id, status=200)
+ resp = resp.click('New Desk')
+ resp.form['label'] = 'Desk A'
+ resp = resp.form.submit().follow()
+ assert Desk.objects.count() == 2
+ resp = resp.click('Desk A', index=0)
+ resp = resp.click('Delete')
+ resp = resp.form.submit()
+ assert resp.location == 'http://testserver/manage/agendas/%s/' % agenda.id
+ assert Desk.objects.count() == 1
diff --git a/tests/test_time_periods.py b/tests/test_time_periods.py
index f0dd9ef5..bfcb02dc 100644
--- a/tests/test_time_periods.py
+++ b/tests/test_time_periods.py
@@ -3,7 +3,7 @@ import pytest
from django.utils.timezone import make_aware
-from chrono.agendas.models import Agenda, TimePeriod, MeetingType
+from chrono.agendas.models import Agenda, TimePeriod, MeetingType, Desk
pytestmark = pytest.mark.django_db
@@ -11,7 +11,8 @@ pytestmark = pytest.mark.django_db
def test_timeperiod_time_slots():
agenda = Agenda(label=u'Foo bar', slug='bar')
agenda.save()
- timeperiod = TimePeriod(agenda=agenda, weekday=0,
+ desk = Desk.objects.create(label='Desk 1', agenda=agenda)
+ timeperiod = TimePeriod(desk=desk, weekday=0,
start_time=datetime.time(9, 0),
end_time=datetime.time(12, 0))
events = timeperiod.get_time_slots(
@@ -28,7 +29,7 @@ def test_timeperiod_time_slots():
assert len(events) == 12
# another start before the timeperiod
- timeperiod = TimePeriod(agenda=agenda, weekday=1,
+ timeperiod = TimePeriod(desk=desk, weekday=1,
start_time=datetime.time(9, 0),
end_time=datetime.time(12, 0))
events = timeperiod.get_time_slots(
@@ -41,7 +42,7 @@ def test_timeperiod_time_slots():
assert len(events) == 12
# a start on the day of the timeperiod
- timeperiod = TimePeriod(agenda=agenda, weekday=3,
+ timeperiod = TimePeriod(desk=desk, weekday=3,
start_time=datetime.time(9, 0),
end_time=datetime.time(12, 0))
events = timeperiod.get_time_slots(
@@ -54,7 +55,7 @@ def test_timeperiod_time_slots():
assert len(events) == 15
# a start after the day of the timeperiod
- timeperiod = TimePeriod(agenda=agenda, weekday=4,
+ timeperiod = TimePeriod(desk=desk, weekday=4,
start_time=datetime.time(9, 0),
end_time=datetime.time(12, 0))
events = timeperiod.get_time_slots(
@@ -67,7 +68,7 @@ def test_timeperiod_time_slots():
assert len(events) == 15
# another start after the day of the timeperiod
- timeperiod = TimePeriod(agenda=agenda, weekday=5,
+ timeperiod = TimePeriod(desk=desk, weekday=5,
start_time=datetime.time(9, 0),
end_time=datetime.time(12, 0))
events = timeperiod.get_time_slots(
@@ -80,7 +81,7 @@ def test_timeperiod_time_slots():
assert len(events) == 12
# shorter duration -> double the events
- timeperiod = TimePeriod(agenda=agenda, weekday=5,
+ timeperiod = TimePeriod(desk=desk, weekday=5,
start_time=datetime.time(9, 0),
end_time=datetime.time(12, 0))
events = timeperiod.get_time_slots(