Skip to content

Commit 88cbd8e

Browse files
authored
Optimize public schedule speaker queries (#4810)
1 parent b2a9499 commit 88cbd8e

2 files changed

Lines changed: 240 additions & 3 deletions

File tree

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
from datetime import date, time
2+
3+
import pytest
4+
5+
from conferences.tests.factories import (
6+
AudienceLevelFactory,
7+
ConferenceFactory,
8+
DurationFactory,
9+
KeynoteFactory,
10+
KeynoteSpeakerFactory,
11+
)
12+
from files_upload.tests.factories import ParticipantAvatarFileFactory
13+
from participants.tests.factories import ParticipantFactory
14+
from schedule.models import DayRoomThroughModel
15+
from schedule.tests.factories import (
16+
DayFactory,
17+
RoomFactory,
18+
ScheduleItemAdditionalSpeakerFactory,
19+
ScheduleItemFactory,
20+
SlotFactory,
21+
)
22+
from submissions.tests.factories import SubmissionFactory, SubmissionTypeFactory
23+
24+
SCHEDULE_QUERY = """
25+
query Schedule($code: String!, $language: String!) {
26+
conference(code: $code) {
27+
id
28+
timezone
29+
days {
30+
day
31+
rooms {
32+
id
33+
name
34+
type
35+
}
36+
slots {
37+
id
38+
hour
39+
endHour
40+
duration
41+
type
42+
items {
43+
id
44+
title
45+
slug
46+
type
47+
duration
48+
hasLimitedCapacity
49+
userHasSpot
50+
hasSpacesLeft
51+
spacesLeft
52+
linkTo
53+
audienceLevel {
54+
id
55+
name
56+
}
57+
language {
58+
id
59+
name
60+
code
61+
}
62+
submission {
63+
id
64+
title(language: $language)
65+
duration {
66+
id
67+
duration
68+
}
69+
audienceLevel {
70+
id
71+
name
72+
}
73+
speaker {
74+
id
75+
fullName
76+
}
77+
type {
78+
id
79+
name
80+
}
81+
tags {
82+
id
83+
name
84+
}
85+
}
86+
keynote {
87+
id
88+
title(language: "en")
89+
slug(language: "en")
90+
speakers {
91+
id
92+
fullName
93+
}
94+
}
95+
speakers {
96+
id
97+
fullname
98+
participant {
99+
id
100+
photo
101+
}
102+
}
103+
rooms {
104+
id
105+
name
106+
type
107+
}
108+
}
109+
}
110+
}
111+
audienceLevels {
112+
id
113+
name
114+
}
115+
durations {
116+
id
117+
duration
118+
allowedSubmissionTypes {
119+
name
120+
}
121+
}
122+
}
123+
}
124+
"""
125+
126+
127+
@pytest.mark.parametrize("item_count", [1, 4])
128+
@pytest.mark.parametrize(
129+
("item_source", "expected_queries"),
130+
[("submission", 13), ("keynote", 14), ("additional", 13)],
131+
)
132+
@pytest.mark.django_db
133+
def test_frontend_schedule_query_is_constant(
134+
graphql_client,
135+
django_assert_num_queries,
136+
item_count,
137+
item_source,
138+
expected_queries,
139+
):
140+
conference = ConferenceFactory()
141+
day = DayFactory(conference=conference, day=date(2026, 5, 29))
142+
room = RoomFactory(name="Main room")
143+
DayRoomThroughModel.objects.create(day=day, room=room)
144+
145+
audience_level = AudienceLevelFactory()
146+
conference.audience_levels.add(audience_level)
147+
submission_type = SubmissionTypeFactory()
148+
conference.submission_types.add(submission_type)
149+
duration = DurationFactory(conference=conference, duration=30)
150+
duration.allowed_submission_types.add(submission_type)
151+
152+
for index in range(item_count):
153+
slot = SlotFactory(day=day, hour=time(9 + index), duration=30)
154+
if item_source == "submission":
155+
submission = SubmissionFactory(
156+
conference=conference,
157+
audience_level=audience_level,
158+
duration=duration,
159+
type=submission_type,
160+
status="accepted",
161+
tags=[f"tag-{index}"],
162+
)
163+
speaker = submission.speaker
164+
ScheduleItemFactory(
165+
conference=conference,
166+
slot=slot,
167+
submission=submission,
168+
type="submission",
169+
rooms=[room],
170+
language="en",
171+
)
172+
elif item_source == "keynote":
173+
keynote = KeynoteFactory(conference=conference)
174+
keynote_speaker = KeynoteSpeakerFactory(keynote=keynote)
175+
speaker = keynote_speaker.user
176+
ScheduleItemFactory(
177+
conference=conference,
178+
slot=slot,
179+
submission=None,
180+
keynote=keynote,
181+
type="keynote",
182+
rooms=[room],
183+
language="en",
184+
)
185+
else:
186+
schedule_item = ScheduleItemFactory(
187+
conference=conference,
188+
slot=slot,
189+
submission=None,
190+
type="custom",
191+
rooms=[room],
192+
language="en",
193+
)
194+
additional_speaker = ScheduleItemAdditionalSpeakerFactory(
195+
scheduleitem=schedule_item
196+
)
197+
speaker = additional_speaker.user
198+
199+
ParticipantFactory(
200+
conference=conference,
201+
user=speaker,
202+
photo_file=ParticipantAvatarFileFactory(uploaded_by=speaker),
203+
)
204+
205+
with django_assert_num_queries(expected_queries):
206+
response = graphql_client.query(
207+
SCHEDULE_QUERY,
208+
variables={"code": conference.code, "language": "en"},
209+
)
210+
211+
assert "errors" not in response
212+
items = [
213+
item
214+
for slot in response["data"]["conference"]["days"][0]["slots"]
215+
for item in slot["items"]
216+
]
217+
assert len(items) == item_count
218+
assert all(item["speakers"][0]["participant"]["photo"] for item in items)

backend/api/schedule/types/schedule_item.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import strawberry
55
import strawberry_django
66
from django.db import models as django_models
7+
from django.db.models import Prefetch
78
from django.db.models.functions import Coalesce
89

910
from api.context import Info
@@ -12,6 +13,7 @@
1213
from api.schedule.types.room import Room
1314
from api.schedule.types.schedule_item_user import ScheduleItemUser
1415
from api.submissions.types import Submission
16+
from participants import models as participant_models
1517
from schedule import models
1618

1719
if TYPE_CHECKING: # pragma: no cover
@@ -163,13 +165,30 @@ def user_is_talk_manager(self, info: Info) -> bool:
163165

164166
return self.talk_manager_id == user_id
165167

168+
# ScheduleItemUser is a DTO, so Strawberry Django cannot propagate the
169+
# Participant.photo optimization through it to these reverse relations.
166170
@strawberry_django.field(
167171
only=["conference_id"],
168172
select_related=["submission__speaker"],
169173
prefetch_related=[
170-
"submission__speaker__participants",
171-
"keynote__speakers__user__participants",
172-
"additional_speakers__user__participants",
174+
Prefetch(
175+
"submission__speaker__participants",
176+
queryset=participant_models.Participant.objects.select_related(
177+
"photo_file"
178+
),
179+
),
180+
Prefetch(
181+
"keynote__speakers__user__participants",
182+
queryset=participant_models.Participant.objects.select_related(
183+
"photo_file"
184+
),
185+
),
186+
Prefetch(
187+
"additional_speakers__user__participants",
188+
queryset=participant_models.Participant.objects.select_related(
189+
"photo_file"
190+
),
191+
),
173192
],
174193
)
175194
def speakers(self) -> list[ScheduleItemUser]:

0 commit comments

Comments
 (0)