api: reduce number of sql queries in datetimes API (#42142)

This commit is contained in:
Frédéric Péters 2020-04-27 18:52:43 +02:00
parent 124ff4b0aa
commit 9d0b76c82a
4 changed files with 35 additions and 16 deletions

View File

@ -434,9 +434,9 @@ class TimePeriod(models.Model):
return effective_timeperiods
def get_time_slots(self, min_datetime, max_datetime, meeting_type):
def get_time_slots(self, min_datetime, max_datetime, meeting_type, base_duration):
meeting_duration = datetime.timedelta(minutes=meeting_type.duration)
duration = datetime.timedelta(minutes=self.desk.agenda.get_base_meeting_duration())
duration = datetime.timedelta(minutes=base_duration)
real_min_datetime = min_datetime + datetime.timedelta(days=self.weekday - min_datetime.weekday())
if real_min_datetime < min_datetime:

View File

@ -79,20 +79,26 @@ def get_all_slots(agenda, meeting_type):
base_agenda = agenda
open_slots = {}
time_periods_by_agenda = {}
for agenda in agendas:
open_slots[agenda] = defaultdict(lambda: Intervals())
open_slots[agenda.id] = defaultdict(lambda: Intervals())
time_periods_by_agenda[agenda.id] = []
# preload time periods
for period in TimePeriod.objects.filter(desk__agenda__in=agendas).prefetch_related('desk__agenda'):
time_periods_by_agenda[period.desk.agenda_id].append(period)
base_agenda_excluded_timeperiods = base_agenda.excluded_timeperiods.all()
for agenda in agendas:
base_duration = agenda.get_base_meeting_duration()
used_time_period_filters = time_period_filters.copy()
if used_time_period_filters['min_datetime'] is None:
used_time_period_filters['min_datetime'] = get_min_datetime(agenda)
if used_time_period_filters['max_datetime'] is None:
used_time_period_filters['max_datetime'] = get_max_datetime(agenda)
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()
):
for raw_time_period in time_periods_by_agenda[agenda.id]:
for time_period in raw_time_period.get_effective_timeperiods(base_agenda_excluded_timeperiods):
duration = (
datetime.datetime.combine(base_date, time_period.end_time)
- datetime.datetime.combine(base_date, time_period.start_time)
@ -100,18 +106,22 @@ def get_all_slots(agenda, meeting_type):
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):
for slot in time_period.get_time_slots(
base_duration=base_duration, **used_time_period_filters
):
slot.full = False
open_slots[agenda][time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot)
open_slots[agenda.id][time_period.desk_id].add(
slot.start_datetime, slot.end_datetime, slot
)
# remove excluded slot
for agenda in agendas:
excluded_slot_by_desk = get_exceptions_by_desk(agenda)
for desk, excluded_interval in excluded_slot_by_desk.items():
for desk_id, excluded_interval in excluded_slot_by_desk.items():
for interval in excluded_interval:
begin, end = interval
open_slots[agenda][desk].remove_overlap(localtime(begin), localtime(end))
open_slots[agenda.id][desk_id].remove_overlap(localtime(begin), localtime(end))
for agenda in agendas:
used_min_datetime = min_datetime
@ -130,15 +140,15 @@ def get_all_slots(agenda, meeting_type):
.select_related('meeting_type')
.exclude(booking__cancellation_datetime__isnull=False)
):
for slot in open_slots[agenda][event.desk_id].search_data(
for slot in open_slots[agenda.id][event.desk_id].search_data(
event.start_datetime, event.end_datetime
):
slot.full = True
slots = []
for agenda in agendas:
for desk in open_slots[agenda]:
slots.extend(open_slots[agenda][desk].iter_data())
for desk_id in open_slots[agenda.id]:
slots.extend(open_slots[agenda.id][desk_id].iter_data())
slots.sort(key=lambda slot: slot.start_datetime)
return slots

View File

@ -2913,8 +2913,10 @@ def test_virtual_agendas_meetings_datetimes_multiple_agendas(app, time_zone, moc
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
create_time_perdiods(bar_desk_1, end=13) # bar_agenda has two more slots each day
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
resp = app.get(api_url)
assert len(resp.json['data']) == 12
with CaptureQueriesContext(connection) as ctx:
resp = app.get(api_url)
assert len(resp.json['data']) == 12
assert len(ctx.captured_queries) == 16
# simulate booking
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M')

View File

@ -25,6 +25,7 @@ def test_timeperiod_time_slots():
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=meeting_type,
base_duration=agenda.get_base_meeting_duration(),
)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 5, 9, 0)
@ -43,6 +44,7 @@ def test_timeperiod_time_slots():
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=meeting_type,
base_duration=agenda.get_base_meeting_duration(),
)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 6, 9, 0)
@ -57,6 +59,7 @@ def test_timeperiod_time_slots():
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=meeting_type,
base_duration=agenda.get_base_meeting_duration(),
)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 1, 9, 0)
@ -71,6 +74,7 @@ def test_timeperiod_time_slots():
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=meeting_type,
base_duration=agenda.get_base_meeting_duration(),
)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 2, 9, 0)
@ -85,6 +89,7 @@ def test_timeperiod_time_slots():
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=meeting_type,
base_duration=agenda.get_base_meeting_duration(),
)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 3, 9, 0)
@ -101,6 +106,7 @@ def test_timeperiod_time_slots():
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=meeting_type,
base_duration=agenda.get_base_meeting_duration(),
)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 3, 9, 0)
@ -230,6 +236,7 @@ def test_timeperiod_midnight_overlap_time_slots():
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=meeting_type,
base_duration=agenda.get_base_meeting_duration(),
)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 5, 21, 0)