dnscms: prefetch and select related when quering future events
This commit is contained in:
+18
-2
@@ -1,7 +1,7 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Min, Q, UniqueConstraint
|
from django.db.models import Min, Prefetch, Q, UniqueConstraint
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.html import mark_safe
|
from django.utils.html import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -45,7 +45,23 @@ class EventIndex(HeadlessMixin, Page):
|
|||||||
subpage_types = ["events.EventPage"]
|
subpage_types = ["events.EventPage"]
|
||||||
|
|
||||||
def future_events(self, info, **kwargs):
|
def future_events(self, info, **kwargs):
|
||||||
return EventPage.objects.live().future().order_by("next_occurrence")
|
return (
|
||||||
|
EventPage.objects.live()
|
||||||
|
.future()
|
||||||
|
.order_by("next_occurrence")
|
||||||
|
.select_related("featured_image")
|
||||||
|
.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
"occurrences",
|
||||||
|
queryset=EventOccurrence.objects.select_related("venue"),
|
||||||
|
),
|
||||||
|
"categories",
|
||||||
|
Prefetch(
|
||||||
|
"organizers",
|
||||||
|
queryset=EventOrganizer.objects.select_related("association"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
graphql_fields = [
|
graphql_fields = [
|
||||||
GraphQLCollection(
|
GraphQLCollection(
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import connection
|
||||||
|
from django.test.utils import CaptureQueriesContext
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from events.admin import EventDateColumn, OrganizersColumn
|
from events.admin import EventDateColumn, OrganizersColumn
|
||||||
@@ -188,6 +190,64 @@ def test_graphql_event_index_future_events_query(event_index, graphql_post):
|
|||||||
assert "Upcoming gig" in titles
|
assert "Upcoming gig" in titles
|
||||||
|
|
||||||
|
|
||||||
|
def test_future_events_does_not_have_n_plus_one_queries(
|
||||||
|
event_index, venue, association_index, graphql_post
|
||||||
|
):
|
||||||
|
"""Regression test: query count for futureEvents stays bounded as events grow."""
|
||||||
|
konsert = EventCategory.objects.create(name="Konsert", slug="konsert")
|
||||||
|
association = AssociationPageFactory(parent=association_index, title="DNS")
|
||||||
|
org = EventOrganizer.objects.create(name="Forening", slug="forening", association=association)
|
||||||
|
image = CustomImageFactory(title="Cover")
|
||||||
|
now = timezone.now()
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
event = EventPageFactory(
|
||||||
|
parent=event_index,
|
||||||
|
title=f"Event {i}",
|
||||||
|
body=[("paragraph", "<p>x</p>")],
|
||||||
|
featured_image=image,
|
||||||
|
)
|
||||||
|
event.categories.add(konsert)
|
||||||
|
EventOrganizerLink.objects.create(event=event, organizer=org)
|
||||||
|
EventOccurrence.objects.create(
|
||||||
|
event=event,
|
||||||
|
start=now + timedelta(days=i + 1),
|
||||||
|
venue=venue,
|
||||||
|
)
|
||||||
|
|
||||||
|
home_query = """
|
||||||
|
query {
|
||||||
|
eventIndex {
|
||||||
|
futureEvents {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
subtitle
|
||||||
|
body { blockType }
|
||||||
|
featuredImage { url }
|
||||||
|
occurrences { start end venueCustom venue { title } }
|
||||||
|
categories { name slug }
|
||||||
|
organizers { name slug association { title } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
with CaptureQueriesContext(connection) as ctx:
|
||||||
|
response, body = graphql_post(home_query)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "errors" not in body, body
|
||||||
|
assert len(body["data"]["eventIndex"]["futureEvents"]) == 5
|
||||||
|
|
||||||
|
# Bump only alongside an intentional resolver change.
|
||||||
|
max_queries = 6
|
||||||
|
assert len(ctx) <= max_queries, (
|
||||||
|
f"futureEvents took {len(ctx)} queries for 5 events — likely N+1. "
|
||||||
|
f"Captured queries:\n"
|
||||||
|
+ "\n".join(f" {i + 1}. {q['sql'][:120]}" for i, q in enumerate(ctx.captured_queries))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_graphql_event_index_future_events_ordered_by_next_occurrence(event_index, graphql_post):
|
def test_graphql_event_index_future_events_ordered_by_next_occurrence(event_index, graphql_post):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user