From 0798ca62f07cc3fa8ef4b47a1416b76d8680abed Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Thu, 10 Dec 2020 14:57:30 +0100 Subject: [PATCH 2/2] manager: unavailability calendars exceptions in timetable views (#48306) --- chrono/agendas/models.py | 18 ++++++++++----- chrono/manager/views.py | 33 ++++++++++++--------------- tests/test_manager.py | 48 +++++++++++++++++++++++++++++++--------- 3 files changed, 64 insertions(+), 35 deletions(-) diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index 8b8981e..f73e601 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -530,11 +530,18 @@ class Agenda(models.Model): return def prefetch_desks_and_exceptions(self): - assert self.kind != 'events' + if self.kind == 'meetings': + desks = self.desk_set.all() + elif self.kind == 'virtual': + desks = ( + Desk.objects.filter(agenda__virtual_agendas=self) + .select_related('agenda') + .order_by('agenda', 'label') + ) + else: + raise TypeError('This method cannot be called on events agendas.') - self.prefetched_desks = self.desk_set.all().prefetch_related( - 'timeperiod_set', 'unavailability_calendars', - ) + self.prefetched_desks = desks.prefetch_related('timeperiod_set', 'unavailability_calendars') all_desks_exceptions = TimePeriodException.objects.filter( Q(desk__in=self.prefetched_desks) | Q(unavailability_calendar__desks__in=self.prefetched_desks) ).select_related('source') @@ -1382,8 +1389,7 @@ class Desk(models.Model): aware_date = make_aware(datetime.datetime(date.year, date.month, date.day)) exceptions = IntervalSet() aware_next_date = aware_date + datetime.timedelta(days=1) - for exception in self.timeperiodexception_set.all(): - # timeperiodexception_set.all() are prefetched, do not filter in queryset + for exception in self.prefetched_exceptions: if exception.end_datetime < aware_date: continue if exception.start_datetime > aware_next_date: diff --git a/chrono/manager/views.py b/chrono/manager/views.py index 8bb6ec3..529996d 100644 --- a/chrono/manager/views.py +++ b/chrono/manager/views.py @@ -877,23 +877,18 @@ class AgendaDateView(DateMixin, ViewableAgendaMixin): def get_queryset(self): if self.agenda.kind == 'events': queryset = self.agenda.event_set.all() - elif self.agenda.kind == 'meetings': - self.agenda.prefetched_desks = self.agenda.desk_set.all().prefetch_related( - 'timeperiod_set', 'timeperiodexception_set' - ) - queryset = self.agenda.event_set.select_related('meeting_type').prefetch_related('booking_set') else: - self.agenda.prefetched_desks = ( - Desk.objects.filter(agenda__virtual_agendas=self.agenda) - .prefetch_related('timeperiod_set', 'timeperiodexception_set') - .select_related('agenda') - .order_by('agenda', 'label') - ) - queryset = ( - Event.objects.filter(agenda__virtual_agendas=self.agenda) - .select_related('meeting_type') - .prefetch_related('booking_set') - ) + self.agenda.prefetch_desks_and_exceptions() + if self.agenda.kind == 'meetings': + queryset = self.agenda.event_set.select_related('meeting_type').prefetch_related( + 'booking_set' + ) + else: + queryset = ( + Event.objects.filter(agenda__virtual_agendas=self.agenda) + .select_related('meeting_type') + .prefetch_related('booking_set') + ) return queryset @@ -977,8 +972,8 @@ class AgendaDayView(AgendaDateView, DayArchiveView): # and exceptions for this desk info['exceptions'] = [] - for exception in desk.timeperiodexception_set.all(): - if exception.end_datetime < start_date: + for exception in desk.prefetched_exceptions: + if exception.end_datetime < current_date: continue if exception.start_datetime > max_date: continue @@ -1177,7 +1172,7 @@ class AgendaMonthView(AgendaDateView, MonthArchiveView): 'css_left': left, } ) - for exception in desk.timeperiodexception_set.all(): + for exception in desk.prefetched_exceptions: if exception.end_datetime < current_date: continue if exception.start_datetime > max_date: diff --git a/tests/test_manager.py b/tests/test_manager.py index 64ab7f4..6791f8c 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -2984,25 +2984,40 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user): resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200) # display exception + unavailability_calendar = UnavailabilityCalendar.objects.create(label='calendar') + TimePeriodException.objects.create( + label='Calendar exception', + unavailability_calendar=unavailability_calendar, + start_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 13, 0)), + end_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 15, 0)), + ) + unavailability_calendar.desks.add(desk) TimePeriodException.objects.create( label='Exception for the afternoon', desk=desk, - start_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 13, 0)), + start_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 16, 0)), end_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 23, 0)), ) with CaptureQueriesContext(connection) as ctx: resp = app.get( '/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200 ) - assert len(ctx.captured_queries) == 14 + assert len(ctx.captured_queries) == 15 # day is displaying rows from 10am to 6pm, # opening hours, 10am to 1pm gives top: 300% - # rest of the day, 1pm to 6(+1)pm gives 600% + # calendar exception, 1pm to 3pm gives heigh: 200% assert resp.pyquery.find('.exception-hours')[0].attrib == { 'class': 'exception-hours', - 'style': 'height: 600%; top: 300%;', + 'style': 'height: 200%; top: 300%;', } - assert resp.pyquery.find('.exception-hours span')[0].text == 'Exception for the afternoon' + assert resp.pyquery.find('.exception-hours span')[0].text == 'Calendar exception' + # rest of the day, opened from 3pm to 4pm, since we left off at 500% it gives top: 600% + # then exception from 4pm to 6pm included, gives height: 300% + assert resp.pyquery.find('.exception-hours')[1].attrib == { + 'class': 'exception-hours', + 'style': 'height: 300%; top: 600%;', + } + assert resp.pyquery.find('.exception-hours span')[1].text == 'Exception for the afternoon' @pytest.mark.parametrize('kind', ['meetings', 'virtual']) @@ -3295,18 +3310,31 @@ def test_agenda_month_view(app, admin_user, manager_user, api_user): assert 'No opening hours this month.' not in resp.text # display exception + unavailability_calendar = UnavailabilityCalendar.objects.create(label='calendar') + TimePeriodException.objects.create( + label='Calendar exception', + unavailability_calendar=unavailability_calendar, + start_datetime=make_aware(datetime.datetime(2018, 12, 15, 5, 0)), + end_datetime=make_aware(datetime.datetime(2018, 12, 15, 14, 0)), + ) + unavailability_calendar.desks.add(desk) TimePeriodException.objects.create( label='Exception for a December day', desk=desk, - start_datetime=make_aware(datetime.datetime(2018, 12, 15, 5, 0)), + start_datetime=make_aware(datetime.datetime(2018, 12, 15, 14, 0)), end_datetime=make_aware(datetime.datetime(2018, 12, 15, 23, 0)), ) with CaptureQueriesContext(connection) as ctx: resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month)) - assert len(ctx.captured_queries) == 9 + assert len(ctx.captured_queries) == 10 assert resp.pyquery.find('.exception-hours')[0].attrib == { 'class': 'exception-hours', - 'style': 'height:800.0%;top:0.0%;width:48.0%;left:50.0%;', + 'style': 'height:400.0%;top:0.0%;width:48.0%;left:50.0%;', + 'title': 'Calendar exception', + } + assert resp.pyquery.find('.exception-hours')[1].attrib == { + 'class': 'exception-hours', + 'style': 'height:400.0%;top:400.0%;width:48.0%;left:50.0%;', 'title': 'Exception for a December day', } @@ -3934,7 +3962,7 @@ def test_virtual_agenda_day_view(app, admin_user, manager_user): resp = app.get( '/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200 ) - assert len(ctx.captured_queries) == 15 + assert len(ctx.captured_queries) == 16 # day is displaying rows from 10am to 6pm, # opening hours, 10am to 1pm gives top: 300% # rest of the day, 1pm to 6(+1)pm gives 600% @@ -4032,7 +4060,7 @@ def test_virtual_agenda_month_view(app, admin_user): ) with CaptureQueriesContext(connection) as ctx: resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month)) - assert len(ctx.captured_queries) == 10 + assert len(ctx.captured_queries) == 11 assert resp.pyquery.find('.exception-hours')[0].attrib == { 'class': 'exception-hours', 'style': 'height:800.0%;top:0.0%;width:48.0%;left:1.0%;', -- 2.20.1