Projet

Général

Profil

0011-manager-use-prefeched-objects-is-available-48924.patch

Lauréline Guérin, 02 février 2021 16:02

Télécharger (18,7 ko)

Voir les différences:

Subject: [PATCH 11/11] manager: use prefeched objects is available (#48924)

 chrono/agendas/models.py | 65 ++++++++++++++++++++++++-----
 chrono/manager/views.py  |  2 +-
 tests/test_agendas.py    | 77 ++++++++++++++++++----------------
 tests/test_manager.py    | 89 +++++++++++++++++++++++++++-------------
 4 files changed, 158 insertions(+), 75 deletions(-)
chrono/agendas/models.py
544 544
        except (VariableDoesNotExist, TemplateSyntaxError):
545 545
            return
546 546

  
547
    def prefetch_desks_and_exceptions(self):
547
    def prefetch_desks_and_exceptions(self, with_sources=False):
548 548
        if self.kind == 'meetings':
549 549
            desks = self.desk_set.all()
550 550
        elif self.kind == 'virtual':
......
557 557
            raise ValueError('does not work with kind %r' % self.kind)
558 558

  
559 559
        self.prefetched_desks = desks.prefetch_related('timeperiod_set', 'unavailability_calendars')
560
        if with_sources:
561
            self.prefetched_desks = self.prefetched_desks.prefetch_related('timeperiodexceptionsource_set')
560 562
        unavailability_calendar_ids = UnavailabilityCalendar.objects.filter(
561 563
            desks__in=self.prefetched_desks
562 564
        ).values('pk')
......
575 577
        if self.kind != 'meetings':
576 578
            return False
577 579

  
578
        desks = self.desk_set.all()
580
        was_prefetched = False
581
        if hasattr(self, 'prefetched_desks'):
582
            desks = self.prefetched_desks
583
            was_prefetched = True
584
        else:
585
            desks = self.desk_set.all()
579 586
        if len(desks) < 2:
580 587
            # no desk or just on, it's ok
581 588
            return True
582 589

  
583 590
        desk = desks[0]
584 591

  
592
        def values_list(obj, qs_name, qs, fields, for_exception=False):
593
            if not was_prefetched:
594
                prefetched_qs = getattr(obj, qs).values_list(*fields)
595
                if for_exception:
596
                    prefetched_qs = prefetched_qs.filter(source__isnull=True)
597
                return prefetched_qs
598
            values = []
599
            if for_exception:
600
                prefetched_qs = obj.prefetched_exceptions
601
            else:
602
                prefetched_qs = obj._prefetched_objects_cache.get(qs_name)  # XXX django 1.11 compat
603
                if prefetched_qs is None:
604
                    prefetched_qs = obj._prefetched_objects_cache.get(qs)
605
            for inst in prefetched_qs:
606
                # queryset is prefetched, fake values_list
607
                if for_exception and inst.source_id is not None:
608
                    continue
609
                values.append(tuple(getattr(inst, f) for f in fields))
610
            return values
611

  
585 612
        period_fields = ['weekday', 'start_time', 'end_time']
586 613
        exception_fields = ['label', 'start_datetime', 'end_datetime']
587 614
        source_fields = ['ics_filename', 'ics_url', 'settings_slug', 'enabled']
588
        desk_time_periods = set(desk.timeperiod_set.values_list(*period_fields))
615
        desk_time_periods = set(values_list(desk, 'timeperiod', 'timeperiod_set', period_fields))
589 616
        desk_exceptions = set(
590
            desk.timeperiodexception_set.filter(source__isnull=True).values_list(*exception_fields)
617
            values_list(
618
                desk, 'timeperiodexception', 'timeperiodexception_set', exception_fields, for_exception=True
619
            )
620
        )
621
        desk_sources = set(
622
            values_list(desk, 'timeperiodexceptionsource', 'timeperiodexceptionsource_set', source_fields)
623
        )
624
        desk_unavaibility_calendars = set(
625
            values_list(desk, 'unavailability_calendars', 'unavailability_calendars', ['pk'])
591 626
        )
592
        desk_sources = set(desk.timeperiodexceptionsource_set.values_list(*source_fields))
593
        desk_unavaibility_calendars = set(desk.unavailability_calendars.values_list('pk', flat=True))
594 627
        for other_desk in desks[1:]:
595 628
            # compare time periods
596
            other_desk_time_periods = set(other_desk.timeperiod_set.values_list(*period_fields))
629
            other_desk_time_periods = set(
630
                values_list(other_desk, 'timeperiod', 'timeperiod_set', period_fields)
631
            )
597 632
            if desk_time_periods != other_desk_time_periods:
598 633
                return False
599 634

  
600 635
            # compare exceptions
601 636
            other_desk_exceptions = set(
602
                other_desk.timeperiodexception_set.filter(source__isnull=True).values_list(*exception_fields)
637
                values_list(
638
                    other_desk,
639
                    'timeperiodexception',
640
                    'timeperiodexception_set',
641
                    exception_fields,
642
                    for_exception=True,
643
                )
603 644
            )
604 645
            if desk_exceptions != other_desk_exceptions:
605 646
                return False
606 647

  
607 648
            # compare sources
608
            other_desk_sources = set(other_desk.timeperiodexceptionsource_set.values_list(*source_fields))
649
            other_desk_sources = set(
650
                values_list(
651
                    other_desk, 'timeperiodexceptionsource', 'timeperiodexceptionsource_set', source_fields
652
                )
653
            )
609 654
            if desk_sources != other_desk_sources:
610 655
                return False
611 656

  
612 657
            # compare unavailability calendars
613 658
            other_desk_unavaibility_calendars = set(
614
                other_desk.unavailability_calendars.values_list('pk', flat=True)
659
                values_list(other_desk, 'unavailability_calendars', 'unavailability_calendars', ['pk'])
615 660
            )
616 661
            if desk_unavaibility_calendars != other_desk_unavaibility_calendars:
617 662
                return False
chrono/manager/views.py
1360 1360

  
1361 1361
    def get_object(self, *args, **kwargs):
1362 1362
        if self.agenda.kind == 'meetings':
1363
            self.agenda.prefetch_desks_and_exceptions()
1363
            self.agenda.prefetch_desks_and_exceptions(with_sources=True)
1364 1364
        return self.agenda
1365 1365

  
1366 1366
    def get_context_data(self, **kwargs):
tests/test_agendas.py
177 177
    assert category.slug == 'foo-baz-2'
178 178

  
179 179

  
180
def test_agenda_is_available_for_simple_management(settings):
180
@pytest.mark.parametrize('with_prefetch', [True, False])
181
def test_agenda_is_available_for_simple_management(settings, with_prefetch):
181 182
    settings.EXCEPTIONS_SOURCES = {
182 183
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
183 184
    }
184 185

  
186
    def check_is_available(result, use_prefetch=True):
187
        agenda = Agenda.objects.get()
188
        if with_prefetch and use_prefetch:
189
            agenda.prefetch_desks_and_exceptions(with_sources=True)
190
        assert agenda.is_available_for_simple_management() == result
191

  
185 192
    agenda = Agenda.objects.create(label='Agenda', kind='meetings')
186 193
    # no desks
187
    assert agenda.is_available_for_simple_management() is True
194
    check_is_available(True)
188 195

  
189 196
    # check kind
190 197
    agenda.kind = 'events'
191 198
    agenda.save()
192
    assert agenda.is_available_for_simple_management() is False
199
    check_is_available(False, use_prefetch=False)
193 200
    agenda.kind = 'virtual'
194 201
    agenda.save()
195
    assert agenda.is_available_for_simple_management() is False
202
    check_is_available(False, use_prefetch=False)
196 203

  
197 204
    # only one desk
198 205
    agenda.kind = 'meetings'
199 206
    agenda.save()
200 207
    desk = Desk.objects.create(label='Desk', agenda=agenda)
201
    assert agenda.is_available_for_simple_management() is True
208
    check_is_available(True)
202 209

  
203 210
    # create some related data for this desk
204 211
    time_period = TimePeriod.objects.create(
......
224 231
    unavailability_calendar2 = UnavailabilityCalendar.objects.create(label='Calendar 2')
225 232

  
226 233
    # still ok
227
    assert agenda.is_available_for_simple_management() is True
234
    check_is_available(True)
228 235

  
229 236
    # duplicate the desk twice
230 237
    desk2 = desk.duplicate()
231 238
    desk.duplicate()
232 239

  
233 240
    # still ok
234
    assert agenda.is_available_for_simple_management() is True
241
    check_is_available(True)
235 242

  
236 243
    # changes on time periods
237 244
    for _desk in [desk, desk2]:
238 245
        time_period2 = TimePeriod.objects.create(
239 246
            weekday=2, desk=_desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
240 247
        )
241
        assert agenda.is_available_for_simple_management() is False
248
        check_is_available(False)
242 249
        time_period2.delete()
243
        assert agenda.is_available_for_simple_management() is True
250
        check_is_available(True)
244 251
    time_period.weekday = 2
245 252
    time_period.save()
246
    assert agenda.is_available_for_simple_management() is False
253
    check_is_available(False)
247 254
    time_period.weekday = 1
248 255
    time_period.start_time = datetime.time(10, 1)
249 256
    time_period.save()
250
    assert agenda.is_available_for_simple_management() is False
257
    check_is_available(False)
251 258
    time_period.start_time = datetime.time(10, 0)
252 259
    time_period.end_time = datetime.time(12, 1)
253 260
    time_period.save()
254
    assert agenda.is_available_for_simple_management() is False
261
    check_is_available(False)
255 262
    time_period.end_time = datetime.time(12, 0)
256 263
    time_period.save()
257
    assert agenda.is_available_for_simple_management() is True
264
    check_is_available(True)
258 265

  
259 266
    # changes on exceptions
260 267
    for _desk in [desk, desk2]:
......
264 271
            start_datetime=date_now + datetime.timedelta(days=3),
265 272
            end_datetime=date_now + datetime.timedelta(days=4),
266 273
        )
267
        assert agenda.is_available_for_simple_management() is False
274
        check_is_available(False)
268 275
        exception2.delete()
269
        assert agenda.is_available_for_simple_management() is True
276
        check_is_available(True)
270 277
    exception.label = 'Exception blah'
271 278
    exception.save()
272
    assert agenda.is_available_for_simple_management() is False
279
    check_is_available(False)
273 280
    exception.label = 'Exception'
274 281
    exception.start_datetime = date_now + datetime.timedelta(days=3)
275 282
    exception.save()
276
    assert agenda.is_available_for_simple_management() is False
283
    check_is_available(False)
277 284
    exception.start_datetime = date_now + datetime.timedelta(days=1)
278 285
    exception.end_datetime = date_now + datetime.timedelta(days=1)
279 286
    exception.save()
280
    assert agenda.is_available_for_simple_management() is False
287
    check_is_available(False)
281 288
    exception.end_datetime = date_now + datetime.timedelta(days=2)
282 289
    exception.save()
283
    assert agenda.is_available_for_simple_management() is True
290
    check_is_available(True)
284 291
    # exceptions from source are not checked
285 292
    exception3 = TimePeriodException.objects.create(
286 293
        label='Exception',
......
289 296
        end_datetime=date_now + datetime.timedelta(days=4),
290 297
        source=source2,
291 298
    )
292
    assert agenda.is_available_for_simple_management() is True
299
    check_is_available(True)
293 300
    exception3.delete()
294 301

  
295 302
    # changes on sources - from settings
......
297 304
        source = TimePeriodExceptionSource.objects.create(
298 305
            desk=_desk, settings_slug='holidays-bis', enabled=True
299 306
        )
300
        assert agenda.is_available_for_simple_management() is False
307
        check_is_available(False)
301 308
        source.delete()
302
        assert agenda.is_available_for_simple_management() is True
309
        check_is_available(True)
303 310
    source1.enabled = False
304 311
    source1.save()
305
    assert agenda.is_available_for_simple_management() is False
312
    check_is_available(False)
306 313
    source1.enabled = True
307 314
    source1.settings_slug = 'holidays-bis'
308 315
    source1.save()
309
    assert agenda.is_available_for_simple_management() is False
316
    check_is_available(False)
310 317
    source1.settings_slug = 'holidays'
311 318
    source1.save()
312
    assert agenda.is_available_for_simple_management() is True
319
    check_is_available(True)
313 320

  
314 321
    # changes on sources - from url
315 322
    for _desk in [desk, desk2]:
316 323
        source = TimePeriodExceptionSource.objects.create(
317 324
            desk=_desk, ics_url='http://example.com/sample-bis.ics'
318 325
        )
319
        assert agenda.is_available_for_simple_management() is False
326
        check_is_available(False)
320 327
        source.delete()
321
        assert agenda.is_available_for_simple_management() is True
328
        check_is_available(True)
322 329
    source2.ics_url = 'http://example.com/sample-bis.ics'
323 330
    source2.save()
324
    assert agenda.is_available_for_simple_management() is False
331
    check_is_available(False)
325 332
    source2.ics_url = 'http://example.com/sample.ics'
326 333
    source2.save()
327
    assert agenda.is_available_for_simple_management() is True
334
    check_is_available(True)
328 335

  
329 336
    # changes on sources - from file
330 337
    for _desk in [desk, desk2]:
......
333 340
            ics_filename='sample-bis.ics',
334 341
            ics_file=ContentFile(ICS_SAMPLE, name='sample-bis.ics'),
335 342
        )
336
        assert agenda.is_available_for_simple_management() is False
343
        check_is_available(False)
337 344
        source.delete()
338
        assert agenda.is_available_for_simple_management() is True
345
        check_is_available(True)
339 346
    source3.ics_filename = 'sample-bis.ics'
340 347
    source3.save()
341
    assert agenda.is_available_for_simple_management() is False
348
    check_is_available(False)
342 349
    source3.ics_filename = 'sample.ics'
343 350
    source3.save()
344
    assert agenda.is_available_for_simple_management() is True
351
    check_is_available(True)
345 352
    # ics_file content is not checked
346 353

  
347 354
    # changes on unavailability calendars
348 355
    for _desk in [desk, desk2]:
349 356
        unavailability_calendar2.desks.add(_desk)
350
        assert agenda.is_available_for_simple_management() is False
357
        check_is_available(False)
351 358
        unavailability_calendar2.desks.remove(_desk)
352
        assert agenda.is_available_for_simple_management() is True
359
        check_is_available(True)
353 360

  
354 361

  
355 362
def test_event_slug():
tests/test_manager.py
1043 1043
    )
1044 1044
    resource = Resource.objects.create(label='Resource')
1045 1045
    agenda.resources.add(resource)
1046

  
1046 1047
    for i in range(0, 10):
1047 1048
        MeetingType.objects.create(agenda=agenda, label='MT %s' % i)
1048
        desk = Desk.objects.create(agenda=agenda, label='Desk %s' % i)
1049
        source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
1050
        calendar = UnavailabilityCalendar.objects.create(label='foo')
1051
        calendar.desks.add(desk)
1052
        for weekday in (0, 6):
1053
            TimePeriod.objects.create(
1054
                weekday=weekday, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1055
            )
1056
        # exception starts and ends in the past
1057
        TimePeriodException.objects.create(
1058
            desk=desk,
1059
            start_datetime=now() - datetime.timedelta(days=2),
1060
            end_datetime=now() - datetime.timedelta(days=1),
1049

  
1050
    desk = Desk.objects.create(agenda=agenda, label='Desk')
1051
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
1052
    calendar = UnavailabilityCalendar.objects.create(label='foo')
1053
    calendar.desks.add(desk)
1054
    for weekday in (0, 6):
1055
        TimePeriod.objects.create(
1056
            weekday=weekday, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1061 1057
        )
1062
        if i % 2:
1063
            # exception starts in the past but ends in the futur
1064
            TimePeriodException.objects.create(
1065
                desk=desk,
1066
                source=source,
1067
                start_datetime=now() - datetime.timedelta(days=1),
1068
                end_datetime=now() + datetime.timedelta(days=1),
1069
            )
1070
        else:
1071
            # exception in more than 2 weeks
1072
            TimePeriodException.objects.create(
1073
                unavailability_calendar=calendar,
1074
                start_datetime=now() + datetime.timedelta(days=20),
1075
                end_datetime=now() + datetime.timedelta(days=21),
1076
            )
1058

  
1059
    desk2 = desk.duplicate()
1060
    assert agenda.is_available_for_simple_management() is True
1077 1061

  
1078 1062
    app = login(app)
1079 1063
    with CaptureQueriesContext(connection) as ctx:
1080 1064
        app.get('/manage/agendas/%s/settings' % agenda.pk)
1081
        assert len(ctx.captured_queries) == 19
1065
        assert len(ctx.captured_queries) == 13
1066

  
1067
    # check with different kind of exceptions:
1068

  
1069
    # exception starts and ends in the past
1070
    exception = TimePeriodException.objects.create(
1071
        desk=desk,
1072
        start_datetime=now() - datetime.timedelta(days=2),
1073
        end_datetime=now() - datetime.timedelta(days=1),
1074
    )
1075
    desk2.delete()
1076
    desk2 = desk.duplicate()
1077
    assert agenda.is_available_for_simple_management() is True
1078

  
1079
    with CaptureQueriesContext(connection) as ctx:
1080
        app.get('/manage/agendas/%s/settings' % agenda.pk)
1081
        assert len(ctx.captured_queries) == 13
1082

  
1083
    # exception starts in the past but ends in the futur
1084
    exception.delete()
1085
    exception = TimePeriodException.objects.create(
1086
        desk=desk,
1087
        source=source,
1088
        start_datetime=now() - datetime.timedelta(days=1),
1089
        end_datetime=now() + datetime.timedelta(days=1),
1090
    )
1091
    desk2.delete()
1092
    desk2 = desk.duplicate()
1093
    assert agenda.is_available_for_simple_management() is True
1094

  
1095
    with CaptureQueriesContext(connection) as ctx:
1096
        app.get('/manage/agendas/%s/settings' % agenda.pk)
1097
        assert len(ctx.captured_queries) == 13
1098

  
1099
    # exception in more than 2 weeks
1100
    exception.delete()
1101
    exception = TimePeriodException.objects.create(
1102
        unavailability_calendar=calendar,
1103
        start_datetime=now() + datetime.timedelta(days=20),
1104
        end_datetime=now() + datetime.timedelta(days=21),
1105
    )
1106
    desk2.delete()
1107
    desk2 = desk.duplicate()
1108
    assert agenda.is_available_for_simple_management() is True
1109

  
1110
    with CaptureQueriesContext(connection) as ctx:
1111
        app.get('/manage/agendas/%s/settings' % agenda.pk)
1112
        assert len(ctx.captured_queries) == 13
1082 1113

  
1083 1114

  
1084 1115
@mock.patch('chrono.agendas.models.Agenda.is_available_for_simple_management')
1085
-