0003-api-restrict-slots-with-exluded-timeperiods-40058.patch
chrono/agendas/models.py | ||
---|---|---|
62 | 62 |
return slug |
63 | 63 | |
64 | 64 | |
65 |
def compute_effective_timeperiods(effective_timeperiods, excluded_timeperiods): |
|
66 |
if not excluded_timeperiods: |
|
67 |
return effective_timeperiods |
|
68 | ||
69 |
res = [] |
|
70 |
excluded_timeperiod = excluded_timeperiods[0] |
|
71 | ||
72 |
for effective_timeperiod in effective_timeperiods: |
|
73 |
if ( |
|
74 |
excluded_timeperiod.weekday != effective_timeperiod.weekday |
|
75 |
or excluded_timeperiod.start_time >= effective_timeperiod.end_time |
|
76 |
or excluded_timeperiod.end_time <= effective_timeperiod.start_time |
|
77 |
): |
|
78 |
res.append(effective_timeperiod) |
|
79 |
continue |
|
80 |
if ( |
|
81 |
excluded_timeperiod.start_time <= effective_timeperiod.start_time |
|
82 |
and excluded_timeperiod.end_time >= effective_timeperiod.end_time |
|
83 |
): |
|
84 |
# completely exclude |
|
85 |
continue |
|
86 |
if excluded_timeperiod.start_time > effective_timeperiod.start_time: |
|
87 |
res.append( |
|
88 |
TimePeriod( |
|
89 |
weekday=effective_timeperiod.weekday, |
|
90 |
start_time=effective_timeperiod.start_time, |
|
91 |
end_time=excluded_timeperiod.start_time, |
|
92 |
desk=effective_timeperiod.desk, |
|
93 |
) |
|
94 |
) |
|
95 |
if excluded_timeperiod.end_time < effective_timeperiod.end_time: |
|
96 |
res.append( |
|
97 |
TimePeriod( |
|
98 |
weekday=effective_timeperiod.weekday, |
|
99 |
start_time=excluded_timeperiod.end_time, |
|
100 |
end_time=effective_timeperiod.end_time, |
|
101 |
desk=effective_timeperiod.desk, |
|
102 |
) |
|
103 |
) |
|
104 | ||
105 |
return compute_effective_timeperiods(res, excluded_timeperiods[1:]) |
|
106 | ||
107 | ||
65 | 108 |
class ICSError(Exception): |
66 | 109 |
pass |
67 | 110 | |
... | ... | |
331 | 374 |
'end_time': self.end_time.strftime('%H:%M'), |
332 | 375 |
} |
333 | 376 | |
377 |
def get_effective_timeperiods(self, excluded_timeperiods): |
|
378 |
return compute_effective_timeperiods([self], excluded_timeperiods) |
|
379 | ||
334 | 380 |
def get_time_slots(self, min_datetime, max_datetime, meeting_type): |
335 | 381 |
meeting_duration = datetime.timedelta(minutes=meeting_type.duration) |
336 | 382 |
duration = datetime.timedelta(minutes=self.desk.agenda.get_base_meeting_duration()) |
chrono/api/views.py | ||
---|---|---|
76 | 76 |
base_date = now().date() |
77 | 77 | |
78 | 78 |
agendas = agenda.get_real_agendas() |
79 |
base_agenda = agenda |
|
79 | 80 | |
80 | 81 |
open_slots = {} |
81 | 82 |
for agenda in agendas: |
... | ... | |
88 | 89 |
if used_time_period_filters['max_datetime'] is None: |
89 | 90 |
used_time_period_filters['max_datetime'] = get_max_datetime(agenda) |
90 | 91 | |
91 |
for time_period in TimePeriod.objects.filter(desk__agenda=agenda): |
|
92 |
duration = ( |
|
93 |
datetime.datetime.combine(base_date, time_period.end_time) |
|
94 |
- datetime.datetime.combine(base_date, time_period.start_time) |
|
95 |
).seconds / 60 |
|
96 |
if duration < meeting_type.duration: |
|
97 |
# skip time period that can't even hold a single meeting |
|
98 |
continue |
|
99 |
for slot in time_period.get_time_slots(**used_time_period_filters): |
|
100 |
slot.full = False |
|
101 |
open_slots[agenda][time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot) |
|
92 |
for raw_time_period in TimePeriod.objects.filter(desk__agenda=agenda): |
|
93 |
for time_period in raw_time_period.get_effective_timeperiods( |
|
94 |
base_agenda.excluded_timeperiods.all() |
|
95 |
): |
|
96 |
duration = ( |
|
97 |
datetime.datetime.combine(base_date, time_period.end_time) |
|
98 |
- datetime.datetime.combine(base_date, time_period.start_time) |
|
99 |
).seconds / 60 |
|
100 |
if duration < meeting_type.duration: |
|
101 |
# skip time period that can't even hold a single meeting |
|
102 |
continue |
|
103 |
for slot in time_period.get_time_slots(**used_time_period_filters): |
|
104 |
slot.full = False |
|
105 |
open_slots[agenda][time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot) |
|
102 | 106 | |
103 | 107 |
# remove excluded slot |
104 | 108 |
for agenda in agendas: |
tests/test_agendas.py | ||
---|---|---|
16 | 16 |
Event, |
17 | 17 |
ICSError, |
18 | 18 |
MeetingType, |
19 |
TimePeriod, |
|
19 | 20 |
TimePeriodException, |
20 | 21 |
TimePeriodExceptionSource, |
21 | 22 |
VirtualMember, |
... | ... | |
584 | 585 |
meeting_type = MeetingType(agenda=agenda2, label='Bar', duration=60) |
585 | 586 |
meeting_type.save() |
586 | 587 |
assert virt_agenda.get_base_meeting_duration() == 60 |
588 | ||
589 | ||
590 |
def test_get_effective_timeperiods(): |
|
591 |
time_period = TimePeriod(weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)) |
|
592 |
# empty exclusion set |
|
593 |
effective_timeperiods = time_period.get_effective_timeperiods(TimePeriod.objects.none()) |
|
594 |
assert len(effective_timeperiods) == 1 |
|
595 |
effective_timeperiod = effective_timeperiods[0] |
|
596 |
assert effective_timeperiod.weekday == time_period.weekday |
|
597 |
assert effective_timeperiod.start_time == time_period.start_time |
|
598 |
assert effective_timeperiod.end_time == time_period.end_time |
|
599 | ||
600 |
# exclusions are on a different day |
|
601 |
excluded_timeperiods = [ |
|
602 |
TimePeriod(weekday=1, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)), |
|
603 |
TimePeriod(weekday=2, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)), |
|
604 |
] |
|
605 |
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods) |
|
606 |
assert len(effective_timeperiods) == 1 |
|
607 |
effective_timeperiod = effective_timeperiods[0] |
|
608 |
assert effective_timeperiod.weekday == time_period.weekday |
|
609 |
assert effective_timeperiod.start_time == time_period.start_time |
|
610 |
assert effective_timeperiod.end_time == time_period.end_time |
|
611 | ||
612 |
# one exclusion, end_time should be earlier |
|
613 |
excluded_timeperiods = [ |
|
614 |
TimePeriod(weekday=0, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)) |
|
615 |
] |
|
616 |
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods) |
|
617 |
assert len(effective_timeperiods) == 1 |
|
618 |
effective_timeperiod = effective_timeperiods[0] |
|
619 |
assert effective_timeperiod.weekday == time_period.weekday |
|
620 |
assert effective_timeperiod.start_time == datetime.time(10, 0) |
|
621 |
assert effective_timeperiod.end_time == datetime.time(17, 0) |
|
622 | ||
623 |
# one exclusion, start_time should be later |
|
624 |
excluded_timeperiods = [ |
|
625 |
TimePeriod(weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(16, 0)) |
|
626 |
] |
|
627 |
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods) |
|
628 |
assert len(effective_timeperiods) == 1 |
|
629 |
effective_timeperiod = effective_timeperiods[0] |
|
630 |
assert effective_timeperiod.weekday == time_period.weekday |
|
631 |
assert effective_timeperiod.start_time == datetime.time(16, 0) |
|
632 |
assert effective_timeperiod.end_time == datetime.time(18, 0) |
|
633 | ||
634 |
# one exclusion, splits effective timeperiod in two |
|
635 |
excluded_timeperiods = [ |
|
636 |
TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(16, 0)) |
|
637 |
] |
|
638 |
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods) |
|
639 |
assert len(effective_timeperiods) == 2 |
|
640 |
effective_timeperiod = effective_timeperiods[0] |
|
641 |
assert effective_timeperiod.weekday == time_period.weekday |
|
642 |
assert effective_timeperiod.start_time == datetime.time(10, 0) |
|
643 |
assert effective_timeperiod.end_time == datetime.time(12, 0) |
|
644 |
effective_timeperiod = effective_timeperiods[1] |
|
645 |
assert effective_timeperiod.weekday == time_period.weekday |
|
646 |
assert effective_timeperiod.start_time == datetime.time(16, 0) |
|
647 |
assert effective_timeperiod.end_time == datetime.time(18, 0) |
|
648 | ||
649 |
# several exclusion, splits effective timeperiod into pieces |
|
650 |
excluded_timeperiods = [ |
|
651 |
TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(13, 0)), |
|
652 |
TimePeriod(weekday=0, start_time=datetime.time(10, 30), end_time=datetime.time(11, 30)), |
|
653 |
TimePeriod(weekday=0, start_time=datetime.time(16, 30), end_time=datetime.time(17, 00)), |
|
654 |
] |
|
655 |
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods) |
|
656 |
assert len(effective_timeperiods) == 4 |
|
657 | ||
658 |
effective_timeperiod = effective_timeperiods[0] |
|
659 |
assert effective_timeperiod.weekday == time_period.weekday |
|
660 |
assert effective_timeperiod.start_time == datetime.time(10, 0) |
|
661 |
assert effective_timeperiod.end_time == datetime.time(10, 30) |
|
662 | ||
663 |
effective_timeperiod = effective_timeperiods[1] |
|
664 |
assert effective_timeperiod.weekday == time_period.weekday |
|
665 |
assert effective_timeperiod.start_time == datetime.time(11, 30) |
|
666 |
assert effective_timeperiod.end_time == datetime.time(12, 0) |
|
667 | ||
668 |
effective_timeperiod = effective_timeperiods[2] |
|
669 |
assert effective_timeperiod.weekday == time_period.weekday |
|
670 |
assert effective_timeperiod.start_time == datetime.time(13, 0) |
|
671 |
assert effective_timeperiod.end_time == datetime.time(16, 30) |
|
672 | ||
673 |
effective_timeperiod = effective_timeperiods[3] |
|
674 |
assert effective_timeperiod.weekday == time_period.weekday |
|
675 |
assert effective_timeperiod.start_time == datetime.time(17, 0) |
|
676 |
assert effective_timeperiod.end_time == datetime.time(18, 0) |
tests/test_api.py | ||
---|---|---|
2391 | 2391 |
assert len(resp.json['data']) == 12 |
2392 | 2392 | |
2393 | 2393 | |
2394 |
def test_virtual_agendas_meetings_datetimes_exluded_periods(app, mock_now): |
|
2395 |
foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7) |
|
2396 |
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30) |
|
2397 |
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1') |
|
2398 |
TimePeriod.objects.create( |
|
2399 |
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1, |
|
2400 |
) |
|
2401 |
TimePeriod.objects.create( |
|
2402 |
weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1, |
|
2403 |
) |
|
2404 |
virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual') |
|
2405 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda) |
|
2406 | ||
2407 |
api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (virt_agenda.slug) |
|
2408 |
resp = app.get(api_url) |
|
2409 |
# 8 slots |
|
2410 |
data = resp.json['data'] |
|
2411 |
assert len(data) == 8 |
|
2412 |
assert data[0]['datetime'] == '2017-05-22 10:00:00' |
|
2413 |
assert data[1]['datetime'] == '2017-05-22 10:30:00' |
|
2414 |
assert data[2]['datetime'] == '2017-05-22 11:00:00' |
|
2415 | ||
2416 |
# exclude one hour the first day |
|
2417 |
tp1 = TimePeriod.objects.create( |
|
2418 |
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda |
|
2419 |
) |
|
2420 |
resp = app.get(api_url) |
|
2421 |
data = resp.json['data'] |
|
2422 |
assert len(data) == 6 |
|
2423 |
assert data[0]['datetime'] == '2017-05-22 10:00:00' |
|
2424 |
assert data[1]['datetime'] == '2017-05-22 10:30:00' |
|
2425 |
# no more slots the 22 thanks to the exclusion period |
|
2426 |
assert data[2]['datetime'] == '2017-05-23 10:00:00' |
|
2427 | ||
2428 |
# exclude the second day |
|
2429 |
tp2 = TimePeriod.objects.create( |
|
2430 |
weekday=1, start_time=datetime.time(9, 0), end_time=datetime.time(18, 0), agenda=virt_agenda |
|
2431 |
) |
|
2432 |
resp = app.get(api_url) |
|
2433 |
data = resp.json['data'] |
|
2434 |
assert len(data) == 2 |
|
2435 |
assert data[0]['datetime'] == '2017-05-22 10:00:00' |
|
2436 |
assert data[1]['datetime'] == '2017-05-22 10:30:00' |
|
2437 | ||
2438 |
# go back to no restriction |
|
2439 |
tp1.delete() |
|
2440 |
tp2.delete() |
|
2441 |
resp = app.get(api_url) |
|
2442 |
data = resp.json['data'] |
|
2443 |
assert len(data) == 8 |
|
2444 | ||
2445 |
# excluded period applies to every desk |
|
2446 |
foo_desk_2 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 2') |
|
2447 |
TimePeriod.objects.create( |
|
2448 |
weekday=3, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_2, |
|
2449 |
) |
|
2450 |
TimePeriod.objects.create( |
|
2451 |
weekday=4, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_2, |
|
2452 |
) |
|
2453 |
resp = app.get(api_url) |
|
2454 |
data = resp.json['data'] |
|
2455 |
assert len(data) == 16 |
|
2456 | ||
2457 |
# exclude one hour the first day |
|
2458 |
tp1 = TimePeriod.objects.create( |
|
2459 |
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda |
|
2460 |
) |
|
2461 |
resp = app.get(api_url) |
|
2462 |
data = resp.json['data'] |
|
2463 |
assert len(data) == 14 |
|
2464 | ||
2465 |
# exclude one hour the last day |
|
2466 |
tp2 = TimePeriod.objects.create( |
|
2467 |
weekday=4, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda |
|
2468 |
) |
|
2469 |
resp = app.get(api_url) |
|
2470 |
data = resp.json['data'] |
|
2471 |
assert len(data) == 12 |
|
2472 | ||
2473 |
# go back to no restriction |
|
2474 |
tp1.delete() |
|
2475 |
tp2.delete() |
|
2476 |
resp = app.get(api_url) |
|
2477 |
data = resp.json['data'] |
|
2478 |
assert len(data) == 16 |
|
2479 | ||
2480 |
# add a second real agenda |
|
2481 |
bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7) |
|
2482 |
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda) |
|
2483 |
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30) |
|
2484 |
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1') |
|
2485 |
bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2') |
|
2486 |
TimePeriod.objects.create( |
|
2487 |
weekday=0, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_1, |
|
2488 |
) |
|
2489 |
TimePeriod.objects.create( |
|
2490 |
weekday=1, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_1, |
|
2491 |
) |
|
2492 |
TimePeriod.objects.create( |
|
2493 |
weekday=2, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_2, |
|
2494 |
) |
|
2495 |
TimePeriod.objects.create( |
|
2496 |
weekday=3, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_2, |
|
2497 |
) |
|
2498 |
resp = app.get(api_url) |
|
2499 |
data = resp.json['data'] |
|
2500 |
assert len(data) == 32 |
|
2501 | ||
2502 |
# exclude the first day, 11 to 15 : 4 slots |
|
2503 |
tp1 = TimePeriod.objects.create( |
|
2504 |
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(15, 0), agenda=virt_agenda |
|
2505 |
) |
|
2506 |
resp = app.get(api_url) |
|
2507 |
data = resp.json['data'] |
|
2508 |
assert len(data) == 28 |
|
2509 | ||
2510 | ||
2394 | 2511 |
def test_virtual_agendas_meetings_exception(app, user, virtual_meetings_agenda): |
2395 | 2512 |
app.authorization = ('Basic', ('john.doe', 'password')) |
2396 | 2513 |
real_agenda = virtual_meetings_agenda.real_agendas.first() |
2397 |
- |