From d889b003de00ad2da68df81bfd9c62925b0d34a6 Mon Sep 17 00:00:00 2001 From: Leonardo Lazzaro Date: Tue, 17 Jan 2023 20:05:20 +0100 Subject: [PATCH] fix(periods): performance improvement to avoid nplus queries --- CHANGELOG | 4 ++++ schedule/models/events.py | 9 +++++---- tests/test_periods.py | 40 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 969e9afb..b45c0c45 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +0.10.1 - 2023-01-29 + +- Periods performance improvement avoid nplus queries + 0.10.0 - 2022-08-21 =================== diff --git a/schedule/models/events.py b/schedule/models/events.py index c450f952..bf6fa5f1 100644 --- a/schedule/models/events.py +++ b/schedule/models/events.py @@ -598,10 +598,11 @@ class Meta: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if not self.title and self.event_id: - self.title = self.event.title - if not self.description and self.event_id: - self.description = self.event.description + event = kwargs.get("event", None) + if not self.title and event: + self.title = event.title + if not self.description and event: + self.description = event.description def moved(self): return self.original_start != self.start or self.original_end != self.end diff --git a/tests/test_periods.py b/tests/test_periods.py index b55ef60f..eb8cd3a6 100644 --- a/tests/test_periods.py +++ b/tests/test_periods.py @@ -2,10 +2,12 @@ import pytz from django.conf import settings +from django.db import connection from django.test import TestCase -from django.test.utils import override_settings +from django.test.utils import CaptureQueriesContext, override_settings from schedule.models import Calendar, Event, Rule +from schedule.models.events import Occurrence from schedule.periods import Day, Month, Period, Week, Year @@ -38,6 +40,42 @@ def test_get_occurrences(self): ], ) + def test_nplus_one_queries_event_period(self): + """Reproduces bug 420 occurences without title or desc will generate additional queries + to retrieve the event model + """ + rule = Rule.objects.create(frequency="WEEKLY") + cal = Calendar.objects.get(name="MyCal") + + event = Event.objects.create( + title="TEST", + start=datetime.datetime(2008, 1, 5, 8, 0, tzinfo=pytz.utc), + end=datetime.datetime(2023, 1, 5, 9, 0, tzinfo=pytz.utc), + end_recurring_period=datetime.datetime(2008, 5, 5, 0, 0, tzinfo=pytz.utc), + rule=rule, + calendar=cal, + ) + for _ in range(0, 21): + # lets set occ without title and desc to use the event.title or event.desc + Occurrence.objects.create( + event=event, + start=datetime.datetime(2008, 1, 7, 8, 0, tzinfo=pytz.utc), + end=datetime.datetime(2008, 1, 7, 8, 0, tzinfo=pytz.utc), + original_start=datetime.datetime(2008, 1, 7, 8, 0, tzinfo=pytz.utc), + original_end=datetime.datetime(2008, 1, 7, 8, 0, tzinfo=pytz.utc), + ) + period = Period( + events=[event], + start=datetime.datetime(2008, 1, 4, 7, 0, tzinfo=pytz.utc), + end=datetime.datetime(2023, 1, 21, 7, 0, tzinfo=pytz.utc), + ) + with CaptureQueriesContext(connection) as ctx: + for occurrence in period.get_occurrences(): + pass + + executed_queries = len(ctx.captured_queries) + assert executed_queries == 1, len(ctx.captured_queries) + def test_get_occurrences_with_sorting_options(self): period = Period( events=Event.objects.all(),