add multiple desk management (#15729)
This commit is contained in:
parent
23fc248cf3
commit
f851de07df
|
@ -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'],
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
]
|
|
@ -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()]
|
||||
}
|
||||
|
|
|
@ -14,7 +14,11 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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]
|
||||
|
|
|
@ -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 = []
|
||||
|
||||
|
|
|
@ -38,3 +38,13 @@ h2 span.identifier {
|
|||
.time-only-picker tbody td {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.timeperiods {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.timeperiod {
|
||||
float: left;
|
||||
display: block;
|
||||
margin: 10px;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<a rel="popup" href="{% url 'chrono-manager-agenda-add-event' pk=object.id %}">{% trans 'New Event' %}</a>
|
||||
{% else %}
|
||||
<a rel="popup" href="{% url 'chrono-manager-agenda-add-meeting-type' pk=object.id %}">{% trans 'New Meeting Type' %}</a>
|
||||
<a rel="popup" href="{% url 'chrono-manager-agenda-add-time-period' pk=object.id %}">{% trans 'New Time Period' %}</a>
|
||||
<a rel="popup" href="{% url 'chrono-manager-agenda-add-desk' pk=object.id %}">{% trans 'New Desk' %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -105,25 +105,41 @@
|
|||
{% endif %}
|
||||
|
||||
<h3>{% trans 'Time Periods' %}</h3>
|
||||
{% if object.timeperiod_set.count %}
|
||||
<div>
|
||||
<ul class="objects-list single-links">
|
||||
{% for time_period in object.timeperiod_set.all %}
|
||||
<li><a rel="popup" href="{% if user_can_manage %}{% url 'chrono-manager-time-period-edit' pk=time_period.id %}{% else %}#{% endif %}">
|
||||
{{time_period.weekday_str}} / {{time_period.start_time}} → {{time_period.end_time}}
|
||||
</a>
|
||||
{% if user_can_manage %}<a rel="popup" class="delete" href="{% url 'chrono-manager-time-period-delete' pk=time_period.id %}">{% trans "remove" %}</a>{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="big-msg-info">
|
||||
{% 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 %}
|
||||
</div>
|
||||
{% if object.desk_set.count %}
|
||||
<div class="timeperiods">
|
||||
{% for desk in object.desk_set.all %}
|
||||
<div class="timeperiod">
|
||||
{% if object.desk_set.count > 1 %}
|
||||
<h4>
|
||||
<a href="{% if user_can_manage %}{% url 'chrono-manager-desk-edit' pk=desk.pk %}{% else %}#{% endif %}">
|
||||
{{ desk.label }}</a>
|
||||
</h4>
|
||||
{% 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 %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for time_period in desk.timeperiod_set.all %}
|
||||
<li><a href="{% if user_can_manage %}{% url 'chrono-manager-time-period-edit' pk=time_period.id %}{% else %}#{% endif %}">
|
||||
{{time_period.weekday_str}} / {{time_period.start_time}} → {{time_period.end_time}} </a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<a rel="popup" href="{{add_time_period_url}}">{% trans 'Add' %}</a>
|
||||
</ul>
|
||||
{% else %}
|
||||
<div>
|
||||
{% trans "This desk doesn't have any time period yet." %} <a rel="popup" href="{{add_time_period_url}}">{% trans "New Time Period" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div>
|
||||
{% 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 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
<a href="{% url 'chrono-manager-agenda-view' pk=object.agenda.pk %}">{{object.label}}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
{% if object.id %}
|
||||
<h2>{% trans "Edit Desk" %}</h2>
|
||||
<a rel="popup" href="{% url 'chrono-manager-desk-delete' pk=object.id %}">{% trans 'Delete' %}</a>
|
||||
{% else %}
|
||||
<h2>{% trans "New Desk" %}</h2>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Save" %}</button>
|
||||
<a class="cancel" href="{% url 'chrono-manager-agenda-view' pk=agenda.id %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -9,7 +9,7 @@
|
|||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
{% if object.id %}
|
||||
<a href="{% url 'chrono-manager-agenda-view' pk=object.agenda.id %}">{{object.agenda.label}}</a>
|
||||
<a href="{% url 'chrono-manager-agenda-view' pk=desk.agenda.id %}">{{object.desk.agenda.label}}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
|||
{{ form.as_p }}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Save" %}</button>
|
||||
<a class="cancel" href="{% url 'chrono-manager-agenda-view' pk=agenda.id %}">{% trans 'Cancel' %}</a>
|
||||
<a class="cancel" href="{% url 'chrono-manager-agenda-view' pk=desk.agenda.id %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -44,13 +44,20 @@ urlpatterns = [
|
|||
url(r'^meetingtypes/(?P<pk>\w+)/delete$', views.meeting_type_delete,
|
||||
name='chrono-manager-meeting-type-delete'),
|
||||
|
||||
url(r'^agendas/(?P<pk>\w+)/add-time-period$', views.agenda_add_time_period,
|
||||
url(r'^agendas/(?P<agenda_pk>\d+)/desk/(?P<pk>\d+)/add-time-period$', views.agenda_add_time_period,
|
||||
name='chrono-manager-agenda-add-time-period'),
|
||||
url(r'^timeperiods/(?P<pk>\w+)/edit$', views.time_period_edit,
|
||||
name='chrono-manager-time-period-edit'),
|
||||
url(r'^timeperiods/(?P<pk>\w+)/delete$', views.time_period_delete,
|
||||
name='chrono-manager-time-period-delete'),
|
||||
|
||||
url(r'^agendas/(?P<pk>\w+)/add-desk$', views.agenda_add_desk,
|
||||
name='chrono-manager-agenda-add-desk'),
|
||||
url(r'^desks/(?P<pk>\w+)/edit$', views.desk_edit,
|
||||
name='chrono-manager-desk-edit'),
|
||||
url(r'^desks/(?P<pk>\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'),
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
1
setup.py
1
setup.py
|
@ -106,6 +106,7 @@ setup(
|
|||
'gadjo',
|
||||
'djangorestframework>=3.1',
|
||||
'django-jsonfield >= 0.9.3',
|
||||
'intervaltree',
|
||||
],
|
||||
zip_safe=False,
|
||||
cmdclass={
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue