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.core.exceptions import ValidationError
|
||||
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.html import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -45,7 +45,23 @@ class EventIndex(HeadlessMixin, Page):
|
||||
subpage_types = ["events.EventPage"]
|
||||
|
||||
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 = [
|
||||
GraphQLCollection(
|
||||
|
||||
@@ -2,6 +2,8 @@ from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import connection
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
from django.utils import timezone
|
||||
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
now = timezone.now()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user