api: restrict slots with exluded timeperiods (#40058)

This commit is contained in:
Emmanuel Cazenave 2020-02-26 18:18:15 +01:00 committed by Frédéric Péters
parent aec5b30fd9
commit ec474b46d2
4 changed files with 263 additions and 11 deletions

View File

@ -361,6 +361,47 @@ class TimePeriod(models.Model):
'end_time': self.end_time.strftime('%H:%M'),
}
def get_effective_timeperiods(self, excluded_timeperiods):
effective_timeperiods = [self]
for excluded_timeperiod in excluded_timeperiods:
res = []
for effective_timeperiod in effective_timeperiods:
if (
excluded_timeperiod.weekday != effective_timeperiod.weekday
or excluded_timeperiod.start_time >= effective_timeperiod.end_time
or excluded_timeperiod.end_time <= effective_timeperiod.start_time
):
res.append(effective_timeperiod)
continue
if (
excluded_timeperiod.start_time <= effective_timeperiod.start_time
and excluded_timeperiod.end_time >= effective_timeperiod.end_time
):
# completely exclude
continue
if excluded_timeperiod.start_time > effective_timeperiod.start_time:
res.append(
TimePeriod(
weekday=effective_timeperiod.weekday,
start_time=effective_timeperiod.start_time,
end_time=excluded_timeperiod.start_time,
desk=effective_timeperiod.desk,
)
)
if excluded_timeperiod.end_time < effective_timeperiod.end_time:
res.append(
TimePeriod(
weekday=effective_timeperiod.weekday,
start_time=excluded_timeperiod.end_time,
end_time=effective_timeperiod.end_time,
desk=effective_timeperiod.desk,
)
)
effective_timeperiods = res
return effective_timeperiods
def get_time_slots(self, min_datetime, max_datetime, meeting_type):
meeting_duration = datetime.timedelta(minutes=meeting_type.duration)
duration = datetime.timedelta(minutes=self.desk.agenda.get_base_meeting_duration())

View File

@ -76,6 +76,7 @@ def get_all_slots(agenda, meeting_type):
base_date = now().date()
agendas = agenda.get_real_agendas()
base_agenda = agenda
open_slots = {}
for agenda in agendas:
@ -88,17 +89,20 @@ def get_all_slots(agenda, meeting_type):
if used_time_period_filters['max_datetime'] is None:
used_time_period_filters['max_datetime'] = get_max_datetime(agenda)
for time_period in TimePeriod.objects.filter(desk__agenda=agenda):
duration = (
datetime.datetime.combine(base_date, time_period.end_time)
- datetime.datetime.combine(base_date, time_period.start_time)
).seconds / 60
if duration < meeting_type.duration:
# skip time period that can't even hold a single meeting
continue
for slot in time_period.get_time_slots(**used_time_period_filters):
slot.full = False
open_slots[agenda][time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot)
for raw_time_period in TimePeriod.objects.filter(desk__agenda=agenda):
for time_period in raw_time_period.get_effective_timeperiods(
base_agenda.excluded_timeperiods.all()
):
duration = (
datetime.datetime.combine(base_date, time_period.end_time)
- datetime.datetime.combine(base_date, time_period.start_time)
).seconds / 60
if duration < meeting_type.duration:
# skip time period that can't even hold a single meeting
continue
for slot in time_period.get_time_slots(**used_time_period_filters):
slot.full = False
open_slots[agenda][time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot)
# remove excluded slot
for agenda in agendas:

View File

@ -16,6 +16,7 @@ from chrono.agendas.models import (
Event,
ICSError,
MeetingType,
TimePeriod,
TimePeriodException,
TimePeriodExceptionSource,
VirtualMember,
@ -594,3 +595,92 @@ def test_virtual_agenda_base_meeting_duration():
meeting_type = MeetingType(agenda=agenda2, label='Bar', duration=60)
meeting_type.save()
assert virt_agenda.get_base_meeting_duration() == 60
def test_get_effective_timeperiods():
time_period = TimePeriod(weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0))
# empty exclusion set
effective_timeperiods = time_period.get_effective_timeperiods(TimePeriod.objects.none())
assert len(effective_timeperiods) == 1
effective_timeperiod = effective_timeperiods[0]
assert effective_timeperiod.weekday == time_period.weekday
assert effective_timeperiod.start_time == time_period.start_time
assert effective_timeperiod.end_time == time_period.end_time
# exclusions are on a different day
excluded_timeperiods = [
TimePeriod(weekday=1, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)),
TimePeriod(weekday=2, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)),
]
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
assert len(effective_timeperiods) == 1
effective_timeperiod = effective_timeperiods[0]
assert effective_timeperiod.weekday == time_period.weekday
assert effective_timeperiod.start_time == time_period.start_time
assert effective_timeperiod.end_time == time_period.end_time
# one exclusion, end_time should be earlier
excluded_timeperiods = [
TimePeriod(weekday=0, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0))
]
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
assert len(effective_timeperiods) == 1
effective_timeperiod = effective_timeperiods[0]
assert effective_timeperiod.weekday == time_period.weekday
assert effective_timeperiod.start_time == datetime.time(10, 0)
assert effective_timeperiod.end_time == datetime.time(17, 0)
# one exclusion, start_time should be later
excluded_timeperiods = [
TimePeriod(weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(16, 0))
]
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
assert len(effective_timeperiods) == 1
effective_timeperiod = effective_timeperiods[0]
assert effective_timeperiod.weekday == time_period.weekday
assert effective_timeperiod.start_time == datetime.time(16, 0)
assert effective_timeperiod.end_time == datetime.time(18, 0)
# one exclusion, splits effective timeperiod in two
excluded_timeperiods = [
TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(16, 0))
]
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
assert len(effective_timeperiods) == 2
effective_timeperiod = effective_timeperiods[0]
assert effective_timeperiod.weekday == time_period.weekday
assert effective_timeperiod.start_time == datetime.time(10, 0)
assert effective_timeperiod.end_time == datetime.time(12, 0)
effective_timeperiod = effective_timeperiods[1]
assert effective_timeperiod.weekday == time_period.weekday
assert effective_timeperiod.start_time == datetime.time(16, 0)
assert effective_timeperiod.end_time == datetime.time(18, 0)
# several exclusion, splits effective timeperiod into pieces
excluded_timeperiods = [
TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(13, 0)),
TimePeriod(weekday=0, start_time=datetime.time(10, 30), end_time=datetime.time(11, 30)),
TimePeriod(weekday=0, start_time=datetime.time(16, 30), end_time=datetime.time(17, 00)),
]
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
assert len(effective_timeperiods) == 4
effective_timeperiod = effective_timeperiods[0]
assert effective_timeperiod.weekday == time_period.weekday
assert effective_timeperiod.start_time == datetime.time(10, 0)
assert effective_timeperiod.end_time == datetime.time(10, 30)
effective_timeperiod = effective_timeperiods[1]
assert effective_timeperiod.weekday == time_period.weekday
assert effective_timeperiod.start_time == datetime.time(11, 30)
assert effective_timeperiod.end_time == datetime.time(12, 0)
effective_timeperiod = effective_timeperiods[2]
assert effective_timeperiod.weekday == time_period.weekday
assert effective_timeperiod.start_time == datetime.time(13, 0)
assert effective_timeperiod.end_time == datetime.time(16, 30)
effective_timeperiod = effective_timeperiods[3]
assert effective_timeperiod.weekday == time_period.weekday
assert effective_timeperiod.start_time == datetime.time(17, 0)
assert effective_timeperiod.end_time == datetime.time(18, 0)

View File

@ -2676,6 +2676,123 @@ def test_virtual_agendas_meetings_datetimes_delays_api(app, mock_now):
assert len(resp.json['data']) == 12
def test_virtual_agendas_meetings_datetimes_exluded_periods(app, mock_now):
foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
)
TimePeriod.objects.create(
weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
)
virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (virt_agenda.slug)
resp = app.get(api_url)
# 8 slots
data = resp.json['data']
assert len(data) == 8
assert data[0]['datetime'] == '2017-05-22 10:00:00'
assert data[1]['datetime'] == '2017-05-22 10:30:00'
assert data[2]['datetime'] == '2017-05-22 11:00:00'
# exclude one hour the first day
tp1 = TimePeriod.objects.create(
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 6
assert data[0]['datetime'] == '2017-05-22 10:00:00'
assert data[1]['datetime'] == '2017-05-22 10:30:00'
# no more slots the 22 thanks to the exclusion period
assert data[2]['datetime'] == '2017-05-23 10:00:00'
# exclude the second day
tp2 = TimePeriod.objects.create(
weekday=1, start_time=datetime.time(9, 0), end_time=datetime.time(18, 0), agenda=virt_agenda
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 2
assert data[0]['datetime'] == '2017-05-22 10:00:00'
assert data[1]['datetime'] == '2017-05-22 10:30:00'
# go back to no restriction
tp1.delete()
tp2.delete()
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 8
# excluded period applies to every desk
foo_desk_2 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 2')
TimePeriod.objects.create(
weekday=3, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_2,
)
TimePeriod.objects.create(
weekday=4, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_2,
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 16
# exclude one hour the first day
tp1 = TimePeriod.objects.create(
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 14
# exclude one hour the last day
tp2 = TimePeriod.objects.create(
weekday=4, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 12
# go back to no restriction
tp1.delete()
tp2.delete()
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 16
# add a second real agenda
bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2')
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_1,
)
TimePeriod.objects.create(
weekday=1, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_1,
)
TimePeriod.objects.create(
weekday=2, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_2,
)
TimePeriod.objects.create(
weekday=3, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_2,
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 32
# exclude the first day, 11 to 15 : 4 slots
tp1 = TimePeriod.objects.create(
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(15, 0), agenda=virt_agenda
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 28
def test_virtual_agendas_meetings_exception(app, user, virtual_meetings_agenda):
app.authorization = ('Basic', ('john.doe', 'password'))
real_agenda = virtual_meetings_agenda.real_agendas.first()