Skip to content

Commit 9af2548

Browse files
committed
Scope participant cache by conference
1 parent e0a95ce commit 9af2548

6 files changed

Lines changed: 175 additions & 47 deletions

File tree

backend/api/conferences/types.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,30 @@ def speakers(self, info: Info) -> list[ScheduleItemUser]:
102102
keynote_speakers = [
103103
speaker for speaker in self.speakers.all() if speaker.user_id
104104
]
105-
participants_data = info.context._participants_data
106-
if participants_data is None:
107-
participants_data = {
108-
participant.user_id: participant
109-
for participant in participant_models.Participant.objects.filter(
110-
user_id__in=[speaker.user_id for speaker in keynote_speakers],
111-
conference_id=self.conference_id,
112-
).all()
113-
}
114-
info.context._participants_data = participants_data
105+
participants_by_conference = info.context._participants_data
106+
if participants_by_conference is None:
107+
participants_by_conference = {}
108+
info.context._participants_data = participants_by_conference
109+
110+
participants_data = participants_by_conference.setdefault(
111+
self.conference_id, {}
112+
)
113+
missing_user_ids = [
114+
speaker.user_id
115+
for speaker in keynote_speakers
116+
if speaker.user_id not in participants_data
117+
]
118+
if missing_user_ids:
119+
participants_data.update({user_id: None for user_id in missing_user_ids})
120+
participants_data.update(
121+
{
122+
participant.user_id: participant
123+
for participant in participant_models.Participant.objects.filter(
124+
user_id__in=missing_user_ids,
125+
conference_id=self.conference_id,
126+
).all()
127+
}
128+
)
115129

116130
return [
117131
ScheduleItemUser(

backend/api/context.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
from dataclasses import dataclass
2-
from typing import Any, Dict, Optional, TypeAlias
2+
from typing import Any
33

44
from django.http.request import HttpRequest
55
from strawberry.types import Info as StrawberryInfo
66

7+
from participants.models import Participant
78
from voting.models.vote import Vote
89

910

1011
@dataclass
1112
class Context:
1213
request: HttpRequest
1314
response: Any
14-
_user_can_vote: Optional[bool] = None
15-
_participants_data: Optional[Any] = None
16-
_my_votes: Optional[Dict[int, Vote]] = None
15+
_user_can_vote: bool | None = None
16+
_participants_data: dict[int, dict[int, Participant | None]] | None = None
17+
_my_votes: dict[int, Vote] | None = None
1718

1819

19-
Info: TypeAlias = StrawberryInfo[Context, Any]
20+
type Info = StrawberryInfo[Context, Any]

backend/api/schedule/queries/search_events_for_schedule.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
@strawberry.type
1515
class SearchEventsForScheduleResult:
16-
conference_id: strawberry.Private[strawberry.ID]
16+
conference_id: strawberry.Private[int]
1717
proposals: strawberry.Private[QuerySet[SubmissionModel]]
1818
keynotes: strawberry.Private[QuerySet[KeynoteModel]]
1919

@@ -27,11 +27,17 @@ def results(self, info: Info) -> list[Submission | Keynote]:
2727
Q(user_id__in=self.proposals.values("speaker_id"))
2828
| Q(user_id__in=self.keynotes.values("speakers__user_id"))
2929
)
30-
participants_data = info.context._participants_data or {}
30+
participants_by_conference = info.context._participants_data
31+
if participants_by_conference is None:
32+
participants_by_conference = {}
33+
info.context._participants_data = participants_by_conference
34+
35+
participants_data = participants_by_conference.setdefault(
36+
self.conference_id, {}
37+
)
3138
participants_data.update(
3239
{participant.user_id: participant for participant in participants}
3340
)
34-
info.context._participants_data = participants_data
3541

3642
# The mixed union has to become a list here, so optimize each queryset
3743
# before Strawberry loses the opportunity to inspect its model type.
@@ -71,7 +77,7 @@ def search_events_for_schedule(
7177
)
7278

7379
return SearchEventsForScheduleResult(
74-
conference_id=conference_id,
80+
conference_id=int(conference_id),
7581
proposals=proposals,
7682
keynotes=keynotes,
7783
)

backend/api/schedule/tests/test_search_events_for_schedule.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from conferences.tests.factories import (
4+
ConferenceFactory,
45
DurationFactory,
56
KeynoteFactory,
67
KeynoteSpeakerFactory,
@@ -60,6 +61,40 @@
6061
"""
6162

6263

64+
MULTI_CONFERENCE_SEARCH_EVENTS_QUERY = """
65+
query SearchEvents($firstConferenceId: ID!, $secondConferenceId: ID!) {
66+
first: searchEventsForSchedule(
67+
conferenceId: $firstConferenceId
68+
query: "Shared"
69+
) {
70+
results {
71+
... on Submission {
72+
speaker {
73+
participant {
74+
speakerAvailabilities
75+
}
76+
}
77+
}
78+
}
79+
}
80+
second: searchEventsForSchedule(
81+
conferenceId: $secondConferenceId
82+
query: "Shared"
83+
) {
84+
results {
85+
... on Submission {
86+
speaker {
87+
participant {
88+
speakerAvailabilities
89+
}
90+
}
91+
}
92+
}
93+
}
94+
}
95+
"""
96+
97+
6398
def _search_events_for_schedule(client, **input):
6499
return client.query(
65100
"""query SearchEventsForSchedule($conferenceId: ID!, $query: String!) {
@@ -151,7 +186,7 @@ def test_frontend_search_events_query(
151186
)
152187

153188
assert "errors" not in response
154-
assert response["data"] == {
189+
expected_data = {
155190
"searchEvents": {
156191
"results": [
157192
*[
@@ -202,6 +237,63 @@ def test_frontend_search_events_query(
202237
]
203238
}
204239
}
240+
response["data"]["searchEvents"]["results"].sort(key=lambda result: result["id"])
241+
expected_data["searchEvents"]["results"].sort(key=lambda result: result["id"])
242+
assert response["data"] == expected_data
243+
244+
245+
def test_frontend_search_events_query_keeps_participants_scoped_by_conference(
246+
admin_graphql_api_client,
247+
admin_superuser,
248+
django_assert_num_queries,
249+
):
250+
conferences = [ConferenceFactory(), ConferenceFactory()]
251+
speaker = UserFactory(full_name="Shared Speaker")
252+
253+
for index, conference in enumerate(conferences, start=1):
254+
ParticipantFactory(
255+
conference=conference,
256+
user=speaker,
257+
speaker_availabilities={"conference": index},
258+
)
259+
SubmissionFactory(
260+
conference=conference,
261+
speaker=speaker,
262+
status=Submission.STATUS.accepted,
263+
title=LazyI18nString({"en": f"Shared Talk {index}", "it": ""}),
264+
)
265+
266+
admin_graphql_api_client.force_login(admin_superuser)
267+
with django_assert_num_queries(10):
268+
response = admin_graphql_api_client.query(
269+
MULTI_CONFERENCE_SEARCH_EVENTS_QUERY,
270+
variables={
271+
"firstConferenceId": str(conferences[0].id),
272+
"secondConferenceId": str(conferences[1].id),
273+
},
274+
)
275+
276+
assert "errors" not in response
277+
assert response["data"] == {
278+
"first": {
279+
"results": [
280+
{
281+
"speaker": {
282+
"participant": {"speakerAvailabilities": {"conference": 1}}
283+
}
284+
}
285+
]
286+
},
287+
"second": {
288+
"results": [
289+
{
290+
"speaker": {
291+
"participant": {"speakerAvailabilities": {"conference": 2}}
292+
}
293+
}
294+
]
295+
},
296+
}
205297

206298

207299
@pytest.mark.parametrize("user_to_test", ["admin_user", "user", "not_authenticated"])

backend/api/schedule/types/schedule_item.py

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -173,30 +173,6 @@ def user_is_talk_manager(self, info: Info) -> bool:
173173
],
174174
)
175175
def speakers(self, info: Info) -> list[ScheduleItemUser]:
176-
speakers = []
177-
178-
participants_data = info.context._participants_data
179-
if participants_data is None:
180-
schedule_items = models.ScheduleItem.objects.filter(
181-
conference_id=self.conference_id
182-
)
183-
submission_speakers = schedule_items.values("submission__speaker_id")
184-
keynote_speakers = schedule_items.values("keynote__speakers__user_id")
185-
additional_speakers = schedule_items.values("additional_speakers__user_id")
186-
participants_data = {
187-
participant.user_id: participant
188-
for participant in participant_models.Participant.objects.filter(
189-
conference_id=self.conference_id
190-
)
191-
.filter(
192-
django_models.Q(user_id__in=submission_speakers)
193-
| django_models.Q(user_id__in=keynote_speakers)
194-
| django_models.Q(user_id__in=additional_speakers)
195-
)
196-
.select_related("user")
197-
}
198-
info.context._participants_data = participants_data
199-
200176
schedule_item_speakers = []
201177
if self.submission_id:
202178
schedule_item_speakers.append(self.submission.speaker)
@@ -209,7 +185,42 @@ def speakers(self, info: Info) -> list[ScheduleItemUser]:
209185
schedule_item_speakers.extend(
210186
speaker.user for speaker in self.additional_speakers.all()
211187
)
188+
speaker_ids = {
189+
speaker.id for speaker in schedule_item_speakers if speaker is not None
190+
}
212191

192+
participants_by_conference = info.context._participants_data
193+
if participants_by_conference is None:
194+
participants_by_conference = {}
195+
info.context._participants_data = participants_by_conference
196+
197+
participants_data = participants_by_conference.setdefault(
198+
self.conference_id, {}
199+
)
200+
if not speaker_ids.issubset(participants_data):
201+
participants_data.update({speaker_id: None for speaker_id in speaker_ids})
202+
schedule_items = models.ScheduleItem.objects.filter(
203+
conference_id=self.conference_id
204+
)
205+
submission_speakers = schedule_items.values("submission__speaker_id")
206+
keynote_speakers = schedule_items.values("keynote__speakers__user_id")
207+
additional_speakers = schedule_items.values("additional_speakers__user_id")
208+
participants_data.update(
209+
{
210+
participant.user_id: participant
211+
for participant in participant_models.Participant.objects.filter(
212+
conference_id=self.conference_id
213+
)
214+
.filter(
215+
django_models.Q(user_id__in=submission_speakers)
216+
| django_models.Q(user_id__in=keynote_speakers)
217+
| django_models.Q(user_id__in=additional_speakers)
218+
)
219+
.select_related("user")
220+
}
221+
)
222+
223+
speakers = []
213224
for speaker in schedule_item_speakers:
214225
if speaker is None:
215226
continue

backend/api/submissions/types.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,19 @@ class SubmissionSpeaker:
5252
id: strawberry.ID
5353
full_name: str
5454
gender: str
55-
_conference_id: strawberry.Private[str]
55+
_conference_id: strawberry.Private[int]
5656

5757
@strawberry_django.field
5858
def participant(
5959
self,
6060
info: Info,
6161
) -> Annotated["Participant", strawberry.lazy("api.participants.types")] | None:
62-
if info.context._participants_data is not None:
63-
return info.context._participants_data.get(self.id)
62+
participants_by_conference = info.context._participants_data
63+
if participants_by_conference is not None:
64+
participants_data = participants_by_conference.get(self._conference_id)
65+
user_id = int(self.id)
66+
if participants_data is not None and user_id in participants_data:
67+
return participants_data[user_id]
6468

6569
return participant_models.Participant.objects.for_conference(
6670
self._conference_id

0 commit comments

Comments
 (0)