0001-start-virtual-agendas-37123.patch
chrono/agendas/migrations/0038_start_virtual_agendas.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2020-02-20 12:15 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
from django.db import migrations, models |
|
6 |
import django.db.models.deletion |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('agendas', '0037_timeperiodexceptionsource_ics_file'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.CreateModel( |
|
17 |
name='VirtualMember', |
|
18 |
fields=[ |
|
19 |
( |
|
20 |
'id', |
|
21 |
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), |
|
22 |
), |
|
23 |
], |
|
24 |
), |
|
25 |
migrations.AlterField( |
|
26 |
model_name='agenda', |
|
27 |
name='kind', |
|
28 |
field=models.CharField( |
|
29 |
choices=[('events', 'Events'), ('meetings', 'Meetings'), ('virtual', 'Virtual')], |
|
30 |
default='events', |
|
31 |
max_length=20, |
|
32 |
verbose_name='Kind', |
|
33 |
), |
|
34 |
), |
|
35 |
migrations.AddField( |
|
36 |
model_name='virtualmember', |
|
37 |
name='real_agenda', |
|
38 |
field=models.ForeignKey( |
|
39 |
on_delete=django.db.models.deletion.CASCADE, |
|
40 |
related_name='virtual_members', |
|
41 |
to='agendas.Agenda', |
|
42 |
), |
|
43 |
), |
|
44 |
migrations.AddField( |
|
45 |
model_name='virtualmember', |
|
46 |
name='virtual_agenda', |
|
47 |
field=models.ForeignKey( |
|
48 |
on_delete=django.db.models.deletion.CASCADE, related_name='real_members', to='agendas.Agenda' |
|
49 |
), |
|
50 |
), |
|
51 |
migrations.AddField( |
|
52 |
model_name='agenda', |
|
53 |
name='real_agendas', |
|
54 |
field=models.ManyToManyField( |
|
55 |
related_name='virtual_agendas', through='agendas.VirtualMember', to='agendas.Agenda' |
|
56 |
), |
|
57 |
), |
|
58 |
] |
chrono/agendas/models.py | ||
---|---|---|
43 | 43 |
AGENDA_KINDS = ( |
44 | 44 |
('events', _('Events')), |
45 | 45 |
('meetings', _('Meetings')), |
46 |
('virtual', _('Virtual')), |
|
46 | 47 |
) |
47 | 48 | |
48 | 49 | |
... | ... | |
77 | 78 |
maximal_booking_delay = models.PositiveIntegerField( |
78 | 79 |
_('Maximal booking delay (in days)'), default=56 |
79 | 80 |
) # eight weeks |
81 |
real_agendas = models.ManyToManyField( |
|
82 |
'self', |
|
83 |
related_name='virtual_agendas', |
|
84 |
symmetrical=False, |
|
85 |
through='VirtualMember', |
|
86 |
through_fields=('virtual_agenda', 'real_agenda'), |
|
87 |
) |
|
80 | 88 |
edit_role = models.ForeignKey( |
81 | 89 |
Group, |
82 | 90 |
blank=True, |
... | ... | |
119 | 127 |
group_ids = [x.id for x in user.groups.all()] |
120 | 128 |
return bool(self.view_role_id in group_ids) |
121 | 129 | |
130 |
def accept_meetings(self): |
|
131 |
if self.kind == 'virtual': |
|
132 |
return not self.real_agendas.filter(kind='events').exists() |
|
133 |
return self.kind == 'meetings' |
|
134 | ||
135 |
def get_real_agendas(self): |
|
136 |
if self.kind == 'virtual': |
|
137 |
return self.real_agendas.all() |
|
138 |
return [self] |
|
139 | ||
140 |
def iter_meetingtypes(self): |
|
141 |
""" Expose agenda's meetingtypes. |
|
142 |
straighforward on a real agenda |
|
143 |
On a virtual agenda we expose transient meeting types based on on the |
|
144 |
the real ones shared by every real agendas. |
|
145 |
""" |
|
146 |
if self.kind == 'virtual': |
|
147 |
queryset = ( |
|
148 |
MeetingType.objects.filter(agenda__virtual_agendas__in=[self]) |
|
149 |
.values('slug', 'duration', 'label') |
|
150 |
.annotate(total=Count('*')) |
|
151 |
.filter(total=self.real_agendas.count()) |
|
152 |
) |
|
153 |
return [ |
|
154 |
MeetingType(duration=mt['duration'], label=mt['label'], slug=mt['slug']) |
|
155 |
for mt in queryset.order_by('slug') |
|
156 |
] |
|
157 | ||
158 |
return self.meetingtype_set.all().order_by('slug') |
|
159 | ||
160 |
def get_meetingtype(self, id_=None, slug=None): |
|
161 |
match = id_ or slug |
|
162 |
assert match, 'an identifier or a slug should be specified' |
|
163 | ||
164 |
if self.kind == 'virtual': |
|
165 |
match = id_ or slug |
|
166 |
meeting_type = None |
|
167 |
for mt in self.iter_meetingtypes(): |
|
168 |
if mt.slug == match: |
|
169 |
meeting_type = mt |
|
170 |
break |
|
171 |
if meeting_type is None: |
|
172 |
raise MeetingType.DoesNotExist() |
|
173 |
return meeting_type |
|
174 | ||
175 |
if id_: |
|
176 |
return MeetingType.objects.get(id=id_, agenda=self) |
|
177 |
return MeetingType.objects.get(slug=slug, agenda=self) |
|
178 | ||
122 | 179 |
def get_base_meeting_duration(self): |
123 |
durations = [x.duration for x in MeetingType.objects.filter(agenda=self)]
|
|
180 |
durations = [x.duration for x in self.iter_meetingtypes()]
|
|
124 | 181 |
if not durations: |
125 | 182 |
raise ValueError() |
126 | 183 |
gcd = durations[0] |
... | ... | |
129 | 186 |
return gcd |
130 | 187 | |
131 | 188 |
def export_json(self): |
189 |
# TODO VIRTUAL |
|
132 | 190 |
agenda = { |
133 | 191 |
'label': self.label, |
134 | 192 |
'slug': self.slug, |
... | ... | |
185 | 243 |
return created |
186 | 244 | |
187 | 245 | |
246 |
class VirtualMember(models.Model): |
|
247 |
virtual_agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE, related_name='real_members') |
|
248 |
real_agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE, related_name='virtual_members') |
|
249 | ||
250 | ||
188 | 251 |
WEEKDAYS_LIST = sorted(WEEKDAYS.items(), key=lambda x: x[0]) |
189 | 252 | |
190 | 253 | |
... | ... | |
193 | 256 |
self.start_datetime = start_datetime |
194 | 257 |
self.end_datetime = start_datetime + datetime.timedelta(minutes=meeting_type.duration) |
195 | 258 |
self.meeting_type = meeting_type |
196 |
self.id = '%s:%s' % (self.meeting_type.id, start_datetime.strftime('%Y-%m-%d-%H%M'))
|
|
259 |
self.id = '%s:%s' % (meeting_type.id or meeting_type.slug, start_datetime.strftime('%Y-%m-%d-%H%M'))
|
|
197 | 260 |
self.desk = desk |
198 | 261 | |
199 | 262 |
def __str__(self): |
... | ... | |
276 | 339 |
unique_together = ['agenda', 'slug'] |
277 | 340 | |
278 | 341 |
def save(self, *args, **kwargs): |
342 |
assert self.agenda.kind != 'virtual', "a meetingtype can't reference a virtual agenda" |
|
279 | 343 |
if not self.slug: |
280 | 344 |
self.slug = generate_slug(self, agenda=self.agenda) |
281 | 345 |
super(MeetingType, self).save(*args, **kwargs) |
... | ... | |
330 | 394 |
return date_format(localtime(self.start_datetime), format='DATETIME_FORMAT') |
331 | 395 | |
332 | 396 |
def save(self, *args, **kwargs): |
397 |
assert self.agenda.kind != 'virtual', "an event can't reference a virtual agenda" |
|
333 | 398 |
self.check_full() |
334 | 399 |
return super(Event, self).save(*args, **kwargs) |
335 | 400 | |
... | ... | |
515 | 580 |
unique_together = ['agenda', 'slug'] |
516 | 581 | |
517 | 582 |
def save(self, *args, **kwargs): |
583 |
assert self.agenda.kind != 'virtual', "a desk can't reference a virtual agenda" |
|
518 | 584 |
if not self.slug: |
519 | 585 |
self.slug = generate_slug(self, agenda=self.agenda) |
520 | 586 |
super(Desk, self).save(*args, **kwargs) |
chrono/api/views.py | ||
---|---|---|
63 | 63 |
} |
64 | 64 | |
65 | 65 |
base_date = now().date() |
66 |
open_slots_by_desk = defaultdict(lambda: Intervals()) |
|
67 |
for time_period in TimePeriod.objects.filter(desk__agenda=agenda): |
|
68 |
duration = ( |
|
69 |
datetime.datetime.combine(base_date, time_period.end_time) |
|
70 |
- datetime.datetime.combine(base_date, time_period.start_time) |
|
71 |
).seconds / 60 |
|
72 |
if duration < meeting_type.duration: |
|
73 |
# skip time period that can't even hold a single meeting |
|
74 |
continue |
|
75 |
for slot in time_period.get_time_slots(**time_period_filters): |
|
76 |
slot.full = False |
|
77 |
open_slots_by_desk[time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot) |
|
66 | ||
67 |
agendas = agenda.get_real_agendas() |
|
68 | ||
69 |
open_slots = {} |
|
70 |
for agenda in agendas: |
|
71 |
open_slots[agenda] = defaultdict(lambda: Intervals()) |
|
72 | ||
73 |
for agenda in agendas: |
|
74 |
for time_period in TimePeriod.objects.filter(desk__agenda=agenda): |
|
75 |
duration = ( |
|
76 |
datetime.datetime.combine(base_date, time_period.end_time) |
|
77 |
- datetime.datetime.combine(base_date, time_period.start_time) |
|
78 |
).seconds / 60 |
|
79 |
if duration < meeting_type.duration: |
|
80 |
# skip time period that can't even hold a single meeting |
|
81 |
continue |
|
82 |
for slot in time_period.get_time_slots(**time_period_filters): |
|
83 |
slot.full = False |
|
84 |
open_slots[agenda][time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot) |
|
78 | 85 | |
79 | 86 |
# remove excluded slot |
80 |
excluded_slot_by_desk = get_exceptions_by_desk(agenda) |
|
81 |
for desk, excluded_interval in excluded_slot_by_desk.items(): |
|
82 |
for interval in excluded_interval: |
|
83 |
begin, end = interval |
|
84 |
open_slots_by_desk[desk].remove_overlap(localtime(begin), localtime(end)) |
|
85 | ||
86 |
for event in ( |
|
87 |
agenda.event_set.filter( |
|
88 |
agenda=agenda, |
|
89 |
start_datetime__gte=min_datetime, |
|
90 |
start_datetime__lte=max_datetime + datetime.timedelta(meeting_type.duration), |
|
91 |
) |
|
92 |
.select_related('meeting_type') |
|
93 |
.exclude(booking__cancellation_datetime__isnull=False) |
|
94 |
): |
|
95 |
for slot in open_slots_by_desk[event.desk_id].search_data(event.start_datetime, event.end_datetime): |
|
96 |
slot.full = True |
|
87 |
for agenda in agendas: |
|
88 |
excluded_slot_by_desk = get_exceptions_by_desk(agenda) |
|
89 | ||
90 |
for desk, excluded_interval in excluded_slot_by_desk.items(): |
|
91 |
for interval in excluded_interval: |
|
92 |
begin, end = interval |
|
93 |
open_slots[agenda][desk].remove_overlap(localtime(begin), localtime(end)) |
|
94 | ||
95 |
for agenda in agendas: |
|
96 |
for event in ( |
|
97 |
agenda.event_set.filter( |
|
98 |
agenda=agenda, |
|
99 |
start_datetime__gte=min_datetime, |
|
100 |
start_datetime__lte=max_datetime + datetime.timedelta(meeting_type.duration), |
|
101 |
) |
|
102 |
.select_related('meeting_type') |
|
103 |
.exclude(booking__cancellation_datetime__isnull=False) |
|
104 |
): |
|
105 |
for slot in open_slots[agenda][event.desk_id].search_data( |
|
106 |
event.start_datetime, event.end_datetime |
|
107 |
): |
|
108 |
slot.full = True |
|
97 | 109 | |
98 | 110 |
slots = [] |
99 |
for desk in open_slots_by_desk: |
|
100 |
slots.extend(open_slots_by_desk[desk].iter_data()) |
|
111 |
for agenda in agendas: |
|
112 |
for desk in open_slots[agenda]: |
|
113 |
slots.extend(open_slots[agenda][desk].iter_data()) |
|
101 | 114 |
slots.sort(key=lambda slot: slot.start_datetime) |
102 | 115 |
return slots |
103 | 116 | |
... | ... | |
118 | 131 |
reverse('api-agenda-datetimes', kwargs={'agenda_identifier': agenda.slug}) |
119 | 132 |
) |
120 | 133 |
} |
121 |
elif agenda.kind == 'meetings':
|
|
134 |
elif agenda.accept_meetings():
|
|
122 | 135 |
agenda_detail['api'] = { |
123 | 136 |
'meetings_url': request.build_absolute_uri( |
124 | 137 |
reverse('api-agenda-meetings', kwargs={'agenda_identifier': agenda.slug}) |
... | ... | |
270 | 283 |
if agenda_identifier is None: |
271 | 284 |
# legacy access by meeting id |
272 | 285 |
meeting_type = MeetingType.objects.get(id=meeting_identifier) |
286 |
agenda = meeting_type.agenda |
|
273 | 287 |
else: |
274 |
meeting_type = MeetingType.objects.get( |
|
275 |
slug=meeting_identifier, agenda__slug=agenda_identifier |
|
276 |
) |
|
277 |
except (ValueError, MeetingType.DoesNotExist): |
|
278 |
raise Http404() |
|
288 |
agenda = Agenda.objects.get(slug=agenda_identifier) |
|
289 |
meeting_type = agenda.get_meetingtype(slug=meeting_identifier) |
|
279 | 290 | |
280 |
agenda = meeting_type.agenda |
|
291 |
except (ValueError, MeetingType.DoesNotExist, Agenda.DoesNotExist): |
|
292 |
raise Http404() |
|
281 | 293 | |
282 | 294 |
now_datetime = now() |
283 | 295 | |
... | ... | |
328 | 340 |
agenda = Agenda.objects.get(slug=agenda_identifier) |
329 | 341 |
except Agenda.DoesNotExist: |
330 | 342 |
raise Http404() |
331 |
if agenda.kind != 'meetings':
|
|
332 |
raise Http404('agenda found, but it was not a meetings agenda')
|
|
343 |
if not agenda.accept_meetings():
|
|
344 |
raise Http404('agenda found, but it does not accept meetings')
|
|
333 | 345 | |
334 | 346 |
meeting_types = [] |
335 |
for meeting_type in agenda.meetingtype_set.all():
|
|
347 |
for meeting_type in agenda.iter_meetingtypes():
|
|
336 | 348 |
meeting_types.append( |
337 | 349 |
{ |
338 | 350 |
'text': meeting_type.label, |
... | ... | |
517 | 529 | |
518 | 530 |
available_desk = None |
519 | 531 | |
520 |
if agenda.kind == 'meetings':
|
|
532 |
if agenda.accept_meetings():
|
|
521 | 533 |
# slots are actually timeslot ids (meeting_type:start_datetime), not events ids. |
522 | 534 |
# split them back to get both parts |
523 | 535 |
meeting_type_id = slots[0].split(':')[0] |
... | ... | |
548 | 560 |
datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M'))) |
549 | 561 | |
550 | 562 |
# get all free slots and separate them by desk |
551 |
all_slots = get_all_slots(agenda, MeetingType.objects.get(id=meeting_type_id))
|
|
563 |
all_slots = get_all_slots(agenda, agenda.get_meetingtype(id_=meeting_type_id))
|
|
552 | 564 |
all_slots = [slot for slot in all_slots if not slot.full] |
553 | 565 |
datetimes_by_desk = defaultdict(set) |
554 | 566 |
for slot in all_slots: |
555 | 567 |
datetimes_by_desk[slot.desk.id].add(slot.start_datetime) |
556 | 568 | |
569 |
# TODO: fill policy for virtual agendas |
|
557 | 570 |
# search first desk where all requested slots are free |
558 | 571 |
for available_desk_id in sorted(datetimes_by_desk.keys()): |
559 | 572 |
if datetimes.issubset(datetimes_by_desk[available_desk_id]): |
... | ... | |
572 | 585 |
datetimes = list(datetimes) |
573 | 586 |
datetimes.sort() |
574 | 587 | |
588 |
# get a real meeting_type for virtual agenda |
|
589 |
if agenda.kind == 'virtual': |
|
590 |
meeting_type_id = MeetingType.objects.get( |
|
591 |
agenda=available_desk.agenda, slug=meeting_type_id |
|
592 |
).pk |
|
593 | ||
575 | 594 |
# booking requires real Event objects (not lazy Timeslots); |
576 | 595 |
# create them now, with data from the slots and the desk we found. |
577 | 596 |
events = [] |
578 | 597 |
for start_datetime in datetimes: |
579 | 598 |
events.append( |
580 | 599 |
Event.objects.create( |
581 |
agenda=agenda, |
|
600 |
agenda=available_desk.agenda,
|
|
582 | 601 |
meeting_type_id=meeting_type_id, |
583 | 602 |
start_datetime=start_datetime, |
584 | 603 |
full=False, |
... | ... | |
658 | 677 |
response['api']['accept_url'] = request.build_absolute_uri( |
659 | 678 |
reverse('api-accept-booking', kwargs={'booking_pk': primary_booking.id}) |
660 | 679 |
) |
661 |
if agenda.kind == 'meetings':
|
|
680 |
if agenda.accept_meetings():
|
|
662 | 681 |
response['end_datetime'] = format_response_datetime(events[-1].end_datetime) |
663 | 682 |
response['duration'] = (events[-1].end_datetime - events[-1].start_datetime).seconds // 60 |
664 | 683 |
if available_desk: |
tests/test_agendas.py | ||
---|---|---|
18 | 18 |
MeetingType, |
19 | 19 |
TimePeriodException, |
20 | 20 |
TimePeriodExceptionSource, |
21 |
VirtualMember, |
|
21 | 22 |
) |
22 | 23 | |
23 | 24 |
pytestmark = pytest.mark.django_db |
... | ... | |
537 | 538 |
elif event.label == 'bar': |
538 | 539 |
assert event.booked_places_count == 3 |
539 | 540 |
assert event.waiting_list_count == 0 |
541 | ||
542 | ||
543 |
def test_virtual_agenda_init(): |
|
544 |
agenda1 = Agenda.objects.create(label=u'Agenda 1', kind='meetings') |
|
545 |
agenda2 = Agenda.objects.create(label=u'Agenda 2', kind='meetings') |
|
546 |
virt_agenda = Agenda.objects.create(label=u'Virtual agenda', kind='virtual') |
|
547 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda1) |
|
548 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda2) |
|
549 |
virt_agenda.save() |
|
550 | ||
551 |
assert virt_agenda.real_agendas.count() == 2 |
|
552 |
assert virt_agenda.real_agendas.get(pk=agenda1.pk) |
|
553 |
assert virt_agenda.real_agendas.get(pk=agenda2.pk) |
|
554 | ||
555 |
for agenda in (agenda1, agenda2): |
|
556 |
assert agenda.virtual_agendas.count() == 1 |
|
557 |
assert agenda.virtual_agendas.get() == virt_agenda |
|
558 | ||
559 | ||
560 |
def test_virtual_agenda_base_meeting_duration(): |
|
561 |
virt_agenda = Agenda.objects.create(label=u'Virtual agenda', kind='virtual') |
|
562 | ||
563 |
with pytest.raises(ValueError): |
|
564 |
virt_agenda.get_base_meeting_duration() |
|
565 | ||
566 |
agenda1 = Agenda.objects.create(label='Agenda 1', kind='meetings') |
|
567 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda1) |
|
568 | ||
569 |
with pytest.raises(ValueError): |
|
570 |
virt_agenda.get_base_meeting_duration() |
|
571 | ||
572 |
meeting_type = MeetingType(agenda=agenda1, label='Foo', duration=30) |
|
573 |
meeting_type.save() |
|
574 |
assert virt_agenda.get_base_meeting_duration() == 30 |
|
575 | ||
576 |
meeting_type = MeetingType(agenda=agenda1, label='Bar', duration=60) |
|
577 |
meeting_type.save() |
|
578 |
assert virt_agenda.get_base_meeting_duration() == 30 |
|
579 | ||
580 |
agenda2 = Agenda.objects.create(label='Agenda 2', kind='meetings') |
|
581 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda2) |
|
582 |
virt_agenda.save() |
|
583 | ||
584 |
meeting_type = MeetingType(agenda=agenda2, label='Bar', duration=60) |
|
585 |
meeting_type.save() |
|
586 |
assert virt_agenda.get_base_meeting_duration() == 60 |
tests/test_api.py | ||
---|---|---|
11 | 11 |
from django.test.utils import CaptureQueriesContext |
12 | 12 |
from django.utils.timezone import now, make_aware, localtime |
13 | 13 | |
14 |
from chrono.agendas.models import Agenda, Event, Booking, MeetingType, TimePeriod, Desk, TimePeriodException |
|
14 |
from chrono.agendas.models import ( |
|
15 |
Agenda, |
|
16 |
Event, |
|
17 |
Booking, |
|
18 |
MeetingType, |
|
19 |
TimePeriod, |
|
20 |
Desk, |
|
21 |
TimePeriodException, |
|
22 |
VirtualMember, |
|
23 |
) |
|
15 | 24 |
import chrono.api.views |
16 | 25 | |
17 | 26 | |
... | ... | |
38 | 47 |
settings.TIME_ZONE = request.param |
39 | 48 | |
40 | 49 | |
50 |
# 2017-05-20 -> saturday |
|
41 | 51 |
@pytest.fixture( |
42 | 52 |
params=[ |
43 | 53 |
datetime.datetime(2017, 5, 20, 1, 12), |
... | ... | |
109 | 119 |
return agenda |
110 | 120 | |
111 | 121 | |
122 |
@pytest.fixture |
|
123 |
def virtual_meetings_agenda(meetings_agenda): |
|
124 |
agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual') |
|
125 |
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meetings_agenda) |
|
126 |
return agenda |
|
127 | ||
128 | ||
112 | 129 |
def test_agendas_api(app, some_data, meetings_agenda): |
113 | 130 |
agenda1 = Agenda.objects.filter(label=u'Foo bar')[0] |
114 | 131 |
agenda2 = Agenda.objects.filter(label=u'Foo bar2')[0] |
132 |
virtual_agenda = Agenda.objects.create( |
|
133 |
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=56 |
|
134 |
) |
|
115 | 135 |
resp = app.get('/api/agenda/') |
116 | 136 |
assert resp.json == { |
117 | 137 |
'data': [ |
... | ... | |
152 | 172 |
'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % agenda2.slug, |
153 | 173 |
}, |
154 | 174 |
}, |
175 |
{ |
|
176 |
'text': 'Virtual Agenda', |
|
177 |
'id': 'virtual-agenda', |
|
178 |
'slug': 'virtual-agenda', |
|
179 |
'minimal_booking_delay': 1, |
|
180 |
'maximal_booking_delay': 56, |
|
181 |
'kind': 'virtual', |
|
182 |
'api': { |
|
183 |
'meetings_url': 'http://testserver/api/agenda/%s/meetings/' % virtual_agenda.slug, |
|
184 |
'desks_url': 'http://testserver/api/agenda/%s/desks/' % virtual_agenda.slug, |
|
185 |
'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % virtual_agenda.slug, |
|
186 |
}, |
|
187 |
}, |
|
155 | 188 |
] |
156 | 189 |
} |
157 | 190 | |
... | ... | |
2138 | 2171 |
api_url = '/api/agenda/%s/' % agenda.slug |
2139 | 2172 |
resp = app.get(api_url) |
2140 | 2173 |
assert type(resp.json['data']) is dict |
2174 | ||
2175 | ||
2176 |
def test_virtual_agenda_detail(app, virtual_meetings_agenda): |
|
2177 |
resp = app.get('/api/agenda/%s/' % virtual_meetings_agenda.slug) |
|
2178 |
assert resp.json == { |
|
2179 |
'data': { |
|
2180 |
'text': 'Virtual Agenda', |
|
2181 |
'id': 'virtual-agenda', |
|
2182 |
'slug': 'virtual-agenda', |
|
2183 |
'minimal_booking_delay': 1, |
|
2184 |
'maximal_booking_delay': 56, |
|
2185 |
'kind': 'virtual', |
|
2186 |
'api': { |
|
2187 |
'meetings_url': 'http://testserver/api/agenda/%s/meetings/' % virtual_meetings_agenda.slug, |
|
2188 |
'desks_url': 'http://testserver/api/agenda/%s/desks/' % virtual_meetings_agenda.slug, |
|
2189 |
'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % virtual_meetings_agenda.slug, |
|
2190 |
}, |
|
2191 |
}, |
|
2192 |
} |
|
2193 | ||
2194 | ||
2195 |
def test_virtual_agendas_meetingtypes_api(app): |
|
2196 |
virt_agenda = Agenda.objects.create(label=u'Virtual agenda', kind='virtual') |
|
2197 | ||
2198 |
# No meetings because no real agenda |
|
2199 |
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug) |
|
2200 |
assert resp.json == {'data': []} |
|
2201 | ||
2202 |
# One real agenda : every meetings exposed |
|
2203 |
foo_agenda = Agenda.objects.create(label=u'Foo', kind='meetings') |
|
2204 |
MeetingType.objects.create(agenda=foo_agenda, label='Meeting1', duration=30) |
|
2205 |
MeetingType.objects.create(agenda=foo_agenda, label='Meeting2', duration=15) |
|
2206 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda) |
|
2207 |
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug) |
|
2208 |
assert resp.json == { |
|
2209 |
'data': [ |
|
2210 |
{ |
|
2211 |
'text': 'Meeting1', |
|
2212 |
'id': 'meeting1', |
|
2213 |
'duration': 30, |
|
2214 |
'api': { |
|
2215 |
'datetimes_url': 'http://testserver/api/agenda/virtual-agenda/meetings/meeting1/datetimes/', |
|
2216 |
}, |
|
2217 |
}, |
|
2218 |
{ |
|
2219 |
'text': 'Meeting2', |
|
2220 |
'id': 'meeting2', |
|
2221 |
'duration': 15, |
|
2222 |
'api': { |
|
2223 |
'datetimes_url': 'http://testserver/api/agenda/virtual-agenda/meetings/meeting2/datetimes/', |
|
2224 |
}, |
|
2225 |
}, |
|
2226 |
] |
|
2227 |
} |
|
2228 | ||
2229 |
# Several real agendas |
|
2230 | ||
2231 |
bar_agenda = Agenda.objects.create(label=u'Bar', kind='meetings') |
|
2232 |
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Bar', duration=30) |
|
2233 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda) |
|
2234 | ||
2235 |
# Bar agenda has no meeting type: no meetings exposed |
|
2236 |
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug) |
|
2237 |
assert resp.json == {'data': []} |
|
2238 | ||
2239 |
# Bar agenda has a meetings wih different label, slug, duration: no meetings exposed |
|
2240 |
mt = MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type Bar', duration=15) |
|
2241 |
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug) |
|
2242 |
assert resp.json == {'data': []} |
|
2243 | ||
2244 |
# Bar agenda has a meetings wih same label, but different slug and duration: no meetings exposed |
|
2245 |
mt.label = 'Meeting1' |
|
2246 |
mt.save() |
|
2247 |
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug) |
|
2248 |
assert resp.json == {'data': []} |
|
2249 | ||
2250 |
# Bar agenda has a meetings wih same label and slug, but different duration: no meetings exposed |
|
2251 |
mt.slug = 'meeting1' |
|
2252 |
mt.save() |
|
2253 |
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug) |
|
2254 |
assert resp.json == {'data': []} |
|
2255 | ||
2256 |
# Bar agenda has a meetings wih same label, slug and duration: only this meeting exposed |
|
2257 |
mt.duration = 30 |
|
2258 |
mt.save() |
|
2259 |
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug) |
|
2260 |
assert resp.json == { |
|
2261 |
'data': [ |
|
2262 |
{ |
|
2263 |
'text': 'Meeting1', |
|
2264 |
'id': 'meeting1', |
|
2265 |
'duration': 30, |
|
2266 |
'api': { |
|
2267 |
'datetimes_url': 'http://testserver/api/agenda/virtual-agenda/meetings/meeting1/datetimes/', |
|
2268 |
}, |
|
2269 |
}, |
|
2270 |
] |
|
2271 |
} |
|
2272 | ||
2273 | ||
2274 |
def test_virtual_agendas_meetings_datetimes_api(app, virtual_meetings_agenda): |
|
2275 |
real_agenda = virtual_meetings_agenda.real_agendas.first() |
|
2276 |
meeting_type = real_agenda.meetingtype_set.first() |
|
2277 |
default_desk = real_agenda.desk_set.first() |
|
2278 |
# Unkown meeting |
|
2279 |
app.get('/api/agenda/%s/meetings/xxx/datetimes/' % virtual_meetings_agenda.slug, status=404) |
|
2280 | ||
2281 |
virt_meeting_type = virtual_meetings_agenda.iter_meetingtypes()[0] |
|
2282 |
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_meetings_agenda.slug, virt_meeting_type.slug) |
|
2283 |
resp = app.get(api_url) |
|
2284 |
assert len(resp.json['data']) == 144 |
|
2285 | ||
2286 |
virtual_meetings_agenda.minimal_booking_delay = 7 |
|
2287 |
virtual_meetings_agenda.maximal_booking_delay = 28 |
|
2288 |
virtual_meetings_agenda.save() |
|
2289 |
resp = app.get(api_url) |
|
2290 |
assert len(resp.json['data']) == 54 |
|
2291 | ||
2292 |
virtual_meetings_agenda.minimal_booking_delay = 1 |
|
2293 |
virtual_meetings_agenda.maximal_booking_delay = 56 |
|
2294 |
virtual_meetings_agenda.save() |
|
2295 |
resp = app.get(api_url) |
|
2296 |
assert len(resp.json['data']) == 144 |
|
2297 | ||
2298 |
resp = app.get(api_url) |
|
2299 |
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M') |
|
2300 |
ev = Event( |
|
2301 |
agenda=real_agenda, |
|
2302 |
meeting_type=meeting_type, |
|
2303 |
places=1, |
|
2304 |
full=False, |
|
2305 |
start_datetime=make_aware(dt), |
|
2306 |
desk=default_desk, |
|
2307 |
) |
|
2308 |
ev.save() |
|
2309 |
booking = Booking(event=ev) |
|
2310 |
booking.save() |
|
2311 |
resp2 = app.get(api_url) |
|
2312 |
assert len(resp2.json['data']) == 144 |
|
2313 |
assert resp.json['data'][0] == resp2.json['data'][0] |
|
2314 |
assert resp.json['data'][1] == resp2.json['data'][1] |
|
2315 |
assert resp.json['data'][2] != resp2.json['data'][2] |
|
2316 |
assert resp.json['data'][2]['disabled'] is False |
|
2317 |
assert resp2.json['data'][2]['disabled'] is True |
|
2318 |
assert resp.json['data'][3] == resp2.json['data'][3] |
|
2319 | ||
2320 |
# test with a timeperiod overlapping current moment, it should get one |
|
2321 |
# datetime for the current timeperiod + two from the next week. |
|
2322 |
if now().time().hour == 23: |
|
2323 |
# skip this part of the test as it would require support for events |
|
2324 |
# crossing midnight |
|
2325 |
return |
|
2326 | ||
2327 |
TimePeriod.objects.filter(desk=default_desk).delete() |
|
2328 |
start_time = localtime(now()) - datetime.timedelta(minutes=10) |
|
2329 |
time_period = TimePeriod( |
|
2330 |
weekday=localtime(now()).weekday(), |
|
2331 |
start_time=start_time, |
|
2332 |
end_time=start_time + datetime.timedelta(hours=1), |
|
2333 |
desk=default_desk, |
|
2334 |
) |
|
2335 |
time_period.save() |
|
2336 |
virtual_meetings_agenda.minimal_booking_delay = 0 |
|
2337 |
virtual_meetings_agenda.maximal_booking_delay = 10 |
|
2338 |
virtual_meetings_agenda.save() |
|
2339 |
resp = app.get(api_url) |
|
2340 |
assert len(resp.json['data']) == 3 |
|
2341 | ||
2342 | ||
2343 |
def test_virtual_agendas_meetings_exception(app, user, virtual_meetings_agenda): |
|
2344 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
2345 |
real_agenda = virtual_meetings_agenda.real_agendas.first() |
|
2346 |
desk = real_agenda.desk_set.first() |
|
2347 |
virt_meeting_type = virtual_meetings_agenda.iter_meetingtypes()[0] |
|
2348 |
datetimes_url = '/api/agenda/%s/meetings/%s/datetimes/' % ( |
|
2349 |
virtual_meetings_agenda.slug, |
|
2350 |
virt_meeting_type.slug, |
|
2351 |
) |
|
2352 |
resp = app.get(datetimes_url) |
|
2353 | ||
2354 |
# test exception at the lowest limit |
|
2355 |
excp1 = TimePeriodException.objects.create( |
|
2356 |
desk=desk, |
|
2357 |
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)), |
|
2358 |
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)), |
|
2359 |
) |
|
2360 |
resp2 = app.get(datetimes_url) |
|
2361 |
assert len(resp.json['data']) == len(resp2.json['data']) + 4 |
|
2362 | ||
2363 |
# test exception at the highest limit |
|
2364 |
excp1.end_datetime = make_aware(datetime.datetime(2017, 5, 22, 11, 0)) |
|
2365 |
excp1.save() |
|
2366 |
resp2 = app.get(datetimes_url) |
|
2367 |
assert len(resp.json['data']) == len(resp2.json['data']) + 2 |
|
2368 | ||
2369 |
# add an exception with an end datetime less than excp1 end datetime |
|
2370 |
# and make sure that excp1 end datetime preveil |
|
2371 |
excp1.end_datetime = make_aware(datetime.datetime(2017, 5, 23, 11, 0)) |
|
2372 |
excp1.save() |
|
2373 | ||
2374 |
TimePeriodException.objects.create( |
|
2375 |
desk=excp1.desk, |
|
2376 |
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 15, 0)), |
|
2377 |
end_datetime=make_aware(datetime.datetime(2017, 5, 23, 9, 0)), |
|
2378 |
) |
|
2379 | ||
2380 |
resp2 = app.get(datetimes_url) |
|
2381 |
assert len(resp.json['data']) == len(resp2.json['data']) + 6 |
|
2382 | ||
2383 |
# with a second desk |
|
2384 |
desk2 = Desk.objects.create(label='Desk 2', agenda=real_agenda) |
|
2385 |
time_period = desk.timeperiod_set.first() |
|
2386 |
TimePeriod.objects.create( |
|
2387 |
desk=desk2, |
|
2388 |
start_time=time_period.start_time, |
|
2389 |
end_time=time_period.end_time, |
|
2390 |
weekday=time_period.weekday, |
|
2391 |
) |
|
2392 |
resp3 = app.get(datetimes_url) |
|
2393 |
assert len(resp.json['data']) == len(resp3.json['data']) + 2 # +2 because excp1 changed |
|
2394 | ||
2395 | ||
2396 |
def test_virtual_agendas_meetings_datetimes_multiple_agendas(app, time_zone, mock_now): |
|
2397 |
foo_agenda = Agenda.objects.create( |
|
2398 |
label='Foo Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5 |
|
2399 |
) |
|
2400 |
foo_meeting_type = MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30) |
|
2401 |
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1') |
|
2402 | ||
2403 |
test_1st_weekday = (localtime(now()).weekday() + 2) % 7 |
|
2404 |
test_2nd_weekday = (localtime(now()).weekday() + 3) % 7 |
|
2405 |
test_3rd_weekday = (localtime(now()).weekday() + 4) % 7 |
|
2406 |
test_4th_weekday = (localtime(now()).weekday() + 5) % 7 |
|
2407 | ||
2408 |
def create_time_perdiods(desk, end=12): |
|
2409 |
TimePeriod.objects.create( |
|
2410 |
weekday=test_1st_weekday, |
|
2411 |
start_time=datetime.time(10, 0), |
|
2412 |
end_time=datetime.time(end, 0), |
|
2413 |
desk=desk, |
|
2414 |
) |
|
2415 |
TimePeriod.objects.create( |
|
2416 |
weekday=test_2nd_weekday, |
|
2417 |
start_time=datetime.time(10, 0), |
|
2418 |
end_time=datetime.time(end, 0), |
|
2419 |
desk=desk, |
|
2420 |
) |
|
2421 | ||
2422 |
create_time_perdiods(foo_desk_1) |
|
2423 |
virt_agenda = Agenda.objects.create( |
|
2424 |
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=5 |
|
2425 |
) |
|
2426 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda) |
|
2427 |
virt_meeting_type = virt_agenda.iter_meetingtypes()[0] |
|
2428 | ||
2429 |
# We are saturday and we can book for next monday and tuesday, 4 slots available each day |
|
2430 |
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, virt_meeting_type.slug) |
|
2431 |
resp = app.get(api_url) |
|
2432 |
assert len(resp.json['data']) == 8 |
|
2433 |
assert resp.json['data'][0]['id'] == 'meeting-type:2017-05-22-1000' |
|
2434 | ||
2435 |
virt_agenda.maximal_booking_delay = 9 # another monday comes in |
|
2436 |
virt_agenda.save() |
|
2437 |
resp = app.get(api_url) |
|
2438 |
assert len(resp.json['data']) == 12 |
|
2439 | ||
2440 |
# Back to next monday and tuesday restriction |
|
2441 |
virt_agenda.maximal_booking_delay = 5 |
|
2442 |
virt_agenda.save() |
|
2443 | ||
2444 |
# Add another agenda |
|
2445 |
bar_agenda = Agenda.objects.create( |
|
2446 |
label='Bar Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5 |
|
2447 |
) |
|
2448 |
bar_meeting_type = MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30) |
|
2449 |
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1') |
|
2450 |
create_time_perdiods(bar_desk_1, end=13) # bar_agenda has two more slots each day |
|
2451 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda) |
|
2452 |
resp = app.get(api_url) |
|
2453 |
assert len(resp.json['data']) == 12 |
|
2454 | ||
2455 |
# simulate booking |
|
2456 |
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M') |
|
2457 |
ev = Event.objects.create( |
|
2458 |
agenda=foo_agenda, |
|
2459 |
meeting_type=foo_meeting_type, |
|
2460 |
places=1, |
|
2461 |
full=False, |
|
2462 |
start_datetime=make_aware(dt), |
|
2463 |
desk=foo_desk_1, |
|
2464 |
) |
|
2465 |
booking1 = Booking.objects.create(event=ev) |
|
2466 | ||
2467 |
resp = app.get(api_url) |
|
2468 |
assert len(resp.json['data']) == 12 |
|
2469 |
# No disabled slot, because the booked slot is still available in second agenda |
|
2470 |
for slot in resp.json['data']: |
|
2471 |
assert slot['disabled'] is False |
|
2472 | ||
2473 |
ev = Event.objects.create( |
|
2474 |
agenda=bar_agenda, |
|
2475 |
meeting_type=bar_meeting_type, |
|
2476 |
places=1, |
|
2477 |
full=False, |
|
2478 |
start_datetime=make_aware(dt), |
|
2479 |
desk=bar_desk_1, |
|
2480 |
) |
|
2481 |
booking2 = Booking.objects.create(event=ev) |
|
2482 | ||
2483 |
resp = app.get(api_url) |
|
2484 |
assert len(resp.json['data']) == 12 |
|
2485 |
# now one slot is disabled |
|
2486 |
for i, slot in enumerate(resp.json['data']): |
|
2487 |
if i == 2: |
|
2488 |
assert slot['disabled'] |
|
2489 |
else: |
|
2490 |
assert slot['disabled'] is False |
|
2491 | ||
2492 |
# Cancel booking, every slot available |
|
2493 |
booking1.cancel() |
|
2494 |
booking2.cancel() |
|
2495 |
resp = app.get(api_url) |
|
2496 |
assert len(resp.json['data']) == 12 |
|
2497 |
for slot in resp.json['data']: |
|
2498 |
assert slot['disabled'] is False |
|
2499 | ||
2500 |
# Add new desk on foo_agenda, open on wednesday |
|
2501 |
foo_desk_2 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 2') |
|
2502 |
TimePeriod.objects.create( |
|
2503 |
weekday=test_3rd_weekday, |
|
2504 |
start_time=datetime.time(10, 0), |
|
2505 |
end_time=datetime.time(12, 0), |
|
2506 |
desk=foo_desk_2, |
|
2507 |
) |
|
2508 |
resp = app.get(api_url) |
|
2509 |
assert len(resp.json['data']) == 16 |
|
2510 | ||
2511 |
# Add new desk on bar_agenda, open on thursday |
|
2512 |
bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2') |
|
2513 |
TimePeriod.objects.create( |
|
2514 |
weekday=test_4th_weekday, |
|
2515 |
start_time=datetime.time(10, 0), |
|
2516 |
end_time=datetime.time(12, 0), |
|
2517 |
desk=bar_desk_2, |
|
2518 |
) |
|
2519 |
resp = app.get(api_url) |
|
2520 |
assert len(resp.json['data']) == 20 |
|
2521 | ||
2522 | ||
2523 |
def test_virtual_agendas_meetings_booking(app, mock_now, user): |
|
2524 |
foo_agenda = Agenda.objects.create( |
|
2525 |
label='Foo Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5 |
|
2526 |
) |
|
2527 |
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30) |
|
2528 |
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1') |
|
2529 | ||
2530 |
TimePeriod.objects.create( |
|
2531 |
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1, |
|
2532 |
) |
|
2533 |
TimePeriod.objects.create( |
|
2534 |
weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1, |
|
2535 |
) |
|
2536 |
bar_agenda = Agenda.objects.create( |
|
2537 |
label='Bar Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5 |
|
2538 |
) |
|
2539 |
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30) |
|
2540 |
bar_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Bar desk 1') |
|
2541 | ||
2542 |
TimePeriod.objects.create( |
|
2543 |
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_1, |
|
2544 |
) |
|
2545 |
TimePeriod.objects.create( |
|
2546 |
weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_1, |
|
2547 |
) |
|
2548 | ||
2549 |
virt_agenda = Agenda.objects.create( |
|
2550 |
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=5 |
|
2551 |
) |
|
2552 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda) |
|
2553 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda) |
|
2554 |
virt_meeting_type = virt_agenda.iter_meetingtypes()[0] |
|
2555 |
# We are saturday and we can book for next monday and tuesday, 4 slots available each day |
|
2556 |
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, virt_meeting_type.slug) |
|
2557 |
resp = app.get(api_url) |
|
2558 |
assert len(resp.json['data']) == 8 |
|
2559 | ||
2560 |
# make a booking |
|
2561 |
fillslot_url = resp.json['data'][0]['api']['fillslot_url'] |
|
2562 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
2563 |
resp_booking = app.post(fillslot_url) |
|
2564 |
assert Booking.objects.count() == 1 |
|
2565 |
booking = Booking.objects.get(pk=resp_booking.json['booking_id']) |
|
2566 |
assert ( |
|
2567 |
resp_booking.json['datetime'] |
|
2568 |
== localtime(booking.event.start_datetime).strftime('%Y-%m-%d %H:%M:%S') |
|
2569 |
== resp.json['data'][0]['datetime'] |
|
2570 |
) |
|
2571 | ||
2572 |
assert resp_booking.json['end_datetime'] == localtime( |
|
2573 |
Booking.objects.all()[0].event.end_datetime |
|
2574 |
).strftime('%Y-%m-%d %H:%M:%S') |
|
2575 |
assert resp_booking.json['duration'] == 30 |
|
2576 | ||
2577 |
# second booking on the same slot (available on the second real agenda) |
|
2578 |
resp_booking = app.post(fillslot_url) |
|
2579 |
assert Booking.objects.count() == 2 |
|
2580 |
booking = Booking.objects.get(pk=resp_booking.json['booking_id']) |
|
2581 |
assert ( |
|
2582 |
resp_booking.json['datetime'] |
|
2583 |
== localtime(booking.event.start_datetime).strftime('%Y-%m-%d %H:%M:%S') |
|
2584 |
== resp.json['data'][0]['datetime'] |
|
2585 |
) |
|
2586 | ||
2587 |
# try booking the same timeslot a third time: full |
|
2588 |
resp_booking = app.post(fillslot_url) |
|
2589 |
assert resp_booking.json['err'] == 1 |
|
2590 |
assert resp_booking.json['err_class'] == 'no more desk available' |
|
2591 |
assert resp_booking.json['err_desc'] == 'no more desk available' |
|
2141 |
- |