0011-manager-use-prefeched-objects-is-available-48924.patch
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[qs_name] |
|
603 |
for inst in prefetched_qs: |
|
604 |
# queryset is prefetched, fake values_list |
|
605 |
if for_exception and inst.source_id is not None: |
|
606 |
continue |
|
607 |
values.append(tuple(getattr(inst, f) for f in fields)) |
|
608 |
return values |
|
609 | ||
585 | 610 |
period_fields = ['weekday', 'start_time', 'end_time'] |
586 | 611 |
exception_fields = ['label', 'start_datetime', 'end_datetime'] |
587 | 612 |
source_fields = ['ics_filename', 'ics_url', 'settings_slug', 'enabled'] |
588 |
desk_time_periods = set(desk.timeperiod_set.values_list(*period_fields))
|
|
613 |
desk_time_periods = set(values_list(desk, 'timeperiod', 'timeperiod_set', period_fields))
|
|
589 | 614 |
desk_exceptions = set( |
590 |
desk.timeperiodexception_set.filter(source__isnull=True).values_list(*exception_fields) |
|
615 |
values_list( |
|
616 |
desk, 'timeperiodexception', 'timeperiodexception_set', exception_fields, for_exception=True |
|
617 |
) |
|
618 |
) |
|
619 |
desk_sources = set( |
|
620 |
values_list(desk, 'timeperiodexceptionsource', 'timeperiodexceptionsource_set', source_fields) |
|
621 |
) |
|
622 |
desk_unavaibility_calendars = set( |
|
623 |
values_list(desk, 'unavailability_calendars', 'unavailability_calendars', ['pk']) |
|
591 | 624 |
) |
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 | 625 |
for other_desk in desks[1:]: |
595 | 626 |
# compare time periods |
596 |
other_desk_time_periods = set(other_desk.timeperiod_set.values_list(*period_fields)) |
|
627 |
other_desk_time_periods = set( |
|
628 |
values_list(other_desk, 'timeperiod', 'timeperiod_set', period_fields) |
|
629 |
) |
|
597 | 630 |
if desk_time_periods != other_desk_time_periods: |
598 | 631 |
return False |
599 | 632 | |
600 | 633 |
# compare exceptions |
601 | 634 |
other_desk_exceptions = set( |
602 |
other_desk.timeperiodexception_set.filter(source__isnull=True).values_list(*exception_fields) |
|
635 |
values_list( |
|
636 |
other_desk, |
|
637 |
'timeperiodexception', |
|
638 |
'timeperiodexception_set', |
|
639 |
exception_fields, |
|
640 |
for_exception=True, |
|
641 |
) |
|
603 | 642 |
) |
604 | 643 |
if desk_exceptions != other_desk_exceptions: |
605 | 644 |
return False |
606 | 645 | |
607 | 646 |
# compare sources |
608 |
other_desk_sources = set(other_desk.timeperiodexceptionsource_set.values_list(*source_fields)) |
|
647 |
other_desk_sources = set( |
|
648 |
values_list( |
|
649 |
other_desk, 'timeperiodexceptionsource', 'timeperiodexceptionsource_set', source_fields |
|
650 |
) |
|
651 |
) |
|
609 | 652 |
if desk_sources != other_desk_sources: |
610 | 653 |
return False |
611 | 654 | |
612 | 655 |
# compare unavailability calendars |
613 | 656 |
other_desk_unavaibility_calendars = set( |
614 |
other_desk.unavailability_calendars.values_list('pk', flat=True)
|
|
657 |
values_list(other_desk, 'unavailability_calendars', 'unavailability_calendars', ['pk'])
|
|
615 | 658 |
) |
616 | 659 |
if desk_unavaibility_calendars != other_desk_unavaibility_calendars: |
617 | 660 |
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 |
- |