Skip to content

Commit b5ad74e

Browse files
fix: remove expand_fields from conference list and add server-side pagination (#19)
Part A — Remove expand_fields from conference list: - List view no longer inlines participants/issues (N+1 eliminated) - Detail view (single conference) still returns expanded fields Part B — Add opt-in server-side pagination: - ?limit= and ?offset= params on conferences, sessions, participants, events - Returns { results: [...], count: N } when pagination params are present - Returns original { data: [...] } format when no params (backward compatible) - Default limit=50, max=200, with input validation Part C — Fix BaseModel.filter() eager materialization: - Removed .exists() call (result was discarded) - Removed eager for-loop that called prepare() on every row - filter() now returns a lazy QuerySet so LIMIT/OFFSET work at DB level - prepare() moved into serialize() to preserve App.prepare() behavior Closes #17
1 parent 804f9b2 commit b5ad74e

7 files changed

Lines changed: 45 additions & 24 deletions

File tree

app/models/basemodel.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,19 +153,15 @@ def get_or_create(cls, *args, **kwargs):
153153
def filter(cls, *args, **kwargs):
154154
"""
155155
Wrapper over the standard filter method that adds the is_active flag.
156+
Returns a lazy QuerySet so that pagination (LIMIT/OFFSET) is applied
157+
at the database level rather than in Python.
156158
157159
:param args: the anonymous query criteria
158160
:param kwargs: the named (key word) query criteria
159161
:return the filtered QuerySet object
160162
"""
161163
kwargs['is_active'] = True
162-
filtered = cls.objects.filter(*args, **kwargs).order_by('created_at')
163-
filtered.exists()
164-
165-
for obj in filtered:
166-
obj.prepare()
167-
168-
return filtered
164+
return cls.objects.filter(*args, **kwargs).order_by('created_at')
169165

170166
def prepare(self):
171167
"""

app/utils.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ def serialize(
167167
final_alias_list.update(alias_list)
168168

169169
for obj in objs:
170+
if hasattr(obj, 'prepare'):
171+
obj.prepare()
170172
d = {}
171173
for field in obj._meta.get_fields():
172174
key = field.name
@@ -344,4 +346,31 @@ def build_conference_summary(conference):
344346
pass
345347

346348
def clamp(n, smallest, largest):
347-
return max(smallest, min(n, largest))
349+
return max(smallest, min(n, largest))
350+
351+
352+
def paginate_and_serialize(request, queryset, default_limit=50, max_limit=200, **serialize_kwargs):
353+
"""
354+
Adds opt-in server-side pagination to list endpoints.
355+
If ?limit= or ?offset= is present, returns { results: [...], count: N }.
356+
Otherwise returns the original { data: [...] } format for backward compatibility.
357+
"""
358+
limit_param = request.GET.get('limit')
359+
offset_param = request.GET.get('offset')
360+
361+
if limit_param is not None or offset_param is not None:
362+
try:
363+
limit = clamp(int(limit_param or default_limit), 1, max_limit)
364+
offset = max(0, int(offset_param or 0))
365+
except (ValueError, TypeError):
366+
from .errors import INVALID_PARAMETERS
367+
raise PMError(status=400, app_error=INVALID_PARAMETERS)
368+
369+
count = queryset.count()
370+
page = queryset[offset:offset + limit]
371+
return {
372+
'results': serialize(page, **serialize_kwargs)['data'],
373+
'count': count,
374+
}
375+
else:
376+
return serialize(queryset, **serialize_kwargs)

app/views/conference_events_view.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ def get(cls, request, pk=None):
1717
except Conference.DoesNotExist:
1818
raise PMError(status=400, app_error=CONFERENCE_NOT_FOUND)
1919

20-
return EventView.retrieve_events(event_type=request.GET.get('type'), conference=conference)
20+
return EventView.retrieve_events(request, event_type=request.GET.get('type'), conference=conference)

app/views/conference_view.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from ..errors import (INVALID_PARAMETERS, CONFERENCE_NOT_FOUND,
66
MISSING_PARAMETERS, PMError)
7-
from ..utils import JSONHttpResponse, serialize
7+
from ..utils import JSONHttpResponse, serialize, paginate_and_serialize
88
from ..models.conference import Conference
99
from .generic_view import GenericView
1010

@@ -45,10 +45,7 @@ def filter(cls, request):
4545
raise PMError(status=400, app_error=INVALID_PARAMETERS)
4646

4747
return JSONHttpResponse(
48-
content=serialize(
49-
objs=objs,
50-
expand_fields=('participants', 'issues'),
51-
),
48+
content=paginate_and_serialize(request, objs),
5249
)
5350

5451
@classmethod

app/views/event_view.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from ..models.connection import Connection
1313
from ..models.generic_event import GenericEvent
1414
from ..models.participant import Participant
15-
from ..utils import JSONHttpResponse, serialize, validate_string, validate_positive_number
15+
from ..utils import JSONHttpResponse, serialize, paginate_and_serialize, validate_string, validate_positive_number
1616

1717

1818
class EventView(GenericView):
@@ -52,12 +52,12 @@ def query_events(event_type=None, conference=None, participant=None, filters=Non
5252
raise PMError(status=400, app_error=INVALID_PARAMETERS)
5353

5454
@staticmethod
55-
def retrieve_events(event_type=None, conference=None, participant=None, filters=None):
55+
def retrieve_events(request, event_type=None, conference=None, participant=None, filters=None):
5656
"""Used to return a JSON response of a specific event query"""
5757

5858
objs = EventView.query_events(event_type, conference, participant, filters)
5959

60-
return JSONHttpResponse(content=serialize(objs))
60+
return JSONHttpResponse(content=paginate_and_serialize(request, objs))
6161

6262
@classmethod
6363
def get(cls, request):
@@ -97,6 +97,7 @@ def get(cls, request):
9797
filters[key] = request.GET.get(rkey)
9898

9999
return cls.retrieve_events(
100+
request,
100101
event_type=request.GET.get('type'),
101102
conference=conference,
102103
participant=participant,

app/views/participant_view.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from ..errors import (INVALID_PARAMETERS, PARTICIPANT_NOT_FOUND,
44
MISSING_PARAMETERS, PMError)
5-
from ..utils import JSONHttpResponse, serialize
5+
from ..utils import JSONHttpResponse, serialize, paginate_and_serialize
66
from ..models.participant import Participant
77
from .generic_view import GenericView
88

@@ -43,9 +43,7 @@ def filter(cls, request):
4343
raise PMError(status=400, app_error=INVALID_PARAMETERS)
4444

4545
return JSONHttpResponse(
46-
content=serialize(
47-
objs=objs,
48-
),
46+
content=paginate_and_serialize(request, objs),
4947
)
5048

5149
@classmethod

app/views/session_view.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from ..models.participant import Participant
1212
from ..models.session import Session
1313
from ..utils import (JSONHttpResponse, generate_session_token, get_client_ip, get_geoip_data,
14-
serialize, validate_string, validate_meta, validate_positive_number)
14+
serialize, paginate_and_serialize, validate_string, validate_meta, validate_positive_number)
1515
from .generic_view import GenericView
1616

1717

@@ -75,8 +75,8 @@ def get(cls, request):
7575
if objs is not None:
7676
return JSONHttpResponse(
7777
status=200,
78-
content=serialize(
79-
objs,
78+
content=paginate_and_serialize(
79+
request, objs,
8080
blacklist=('constraints',),
8181
expand_fields=('issues',),
8282
)

0 commit comments

Comments
 (0)