This adds the first public data endpoints on api_v2:
publishersseriesissuesIt also wires them into the www v2 surface, adds test coverage for filters/serializers/views/perf, and updates the v2 CI workflow to run lint + pytest for beta and api-v2.
urls_my.py stays unchanged in this passETag and Last-Modifiedissues detail includes nested storiesWhile testing against a local production-data clone, two broad-list cases stood out, so this branch also includes a couple of follow-up fixes:
issues list ordering is now pinned to series_id, sort_code, idmodified lookups instead of MAX(modified)series queryset projections were narrowed to cut down broad-list overheadThat brought the worst issues list path down from multi-second / tens-of-seconds territory into a much more reasonable range locally.
Ran locally in Docker:
ruff check apps/api_v2/ruff format --check apps/api_v2/python manage.py checkpytest apps/api_v2/tests/ -q --tb=short --reuse-dbCurrent result: 50 passed
I also did manual endpoint checks against a local production-data copy for:
304 behaviorhttps://github.com/GrandComicsDatabase/gcd-django/pull/712
(44 files)
—
Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.
You are receiving this because you are subscribed to this thread.![]()
@gemini-code-assist[bot] commented on this pull request.
This pull request implements the v2 API for issues, publishers, and series, featuring comprehensive filtering, serialization, and conditional request support. The implementation includes optimized querysets and custom logic for handling legacy credit strings. Feedback identifies a performance regression in IssueViewSet where incomplete prefetching for variant covers causes N+1 queries. Additionally, concerns were raised about the maintenance burden of using .only() in SeriesViewSet and the potential performance costs in the credit collection utility's fallback logic.
In apps/api_v2/views/issues.py:
> + +class IssueViewSet(GCDBaseViewSet): + """Read-only issue endpoints for the public v2 API surface.""" + + queryset = ( + Issue.objects.select_related( + 'series', + 'indicia_publisher', + 'variant_of', + ) + .prefetch_related( + 'brand_emblem', + 'keywords', + ACTIVE_ISSUE_CREDIT_PREFETCH, + ACTIVE_COVER_PREFETCH, + 'variant_of__cover_set',
The prefetch for variant_of__cover_set is currently a simple string. This will prefetch all related covers, including deleted ones, and won't apply the ordering expected by the serializer. This leads to an N+1 query issue in the _cover_url helper because it calls .filter(deleted=False).order_by('id') on the related manager, which bypasses the prefetch cache.
ACTIVE_ISSUE_CREDIT_PREFETCH, ACTIVE_COVER_PREFETCH, Prefetch( 'variant_of__cover_set', queryset=Cover.objects.filter(deleted=False).order_by('id'), to_attr='active_cover_list', ), )
In apps/api_v2/views/series.py:
> + .only( + 'id', + 'created', + 'modified', + 'deleted', + 'name', + 'sort_name', + 'year_began', + 'year_ended', + 'color', + 'dimensions', + 'paper_stock', + 'binding', + 'publishing_format', + 'notes', + 'issue_count', + 'country_id', + 'language_id', + 'publisher_id', + 'publication_type_id', + 'country__id', + 'country__code', + 'language__id', + 'language__code', + 'publisher__id', + 'publisher__name', + 'publication_type__id', + 'publication_type__name', + )
Using .only() can be a maintenance burden. If the SeriesSerializer is updated to include new fields, this list must be manually updated to avoid deferred attribute loading, which causes N+1 query problems. Consider if the performance gain of narrowing the projection is worth the risk of future regressions.
In apps/api_v2/utils/credits.py:
> + if credits is None: + credits = obj.credits.filter(deleted=False).select_related( + 'creator', + 'credit_type', + )
The collect_credit_strings function falls back to a database query if the prefetched_attr is not found. While the current viewsets seem to provide this attribute via prefetching, this fallback logic performs a select_related on every call, which could be expensive if prefetching is accidentally omitted in future views.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.
You are receiving this because you are subscribed to this thread.![]()
@DeusExTaco pushed 1 commit.
—
View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.![]()
@DeusExTaco commented on this pull request.
In apps/api_v2/views/issues.py:
> + +class IssueViewSet(GCDBaseViewSet): + """Read-only issue endpoints for the public v2 API surface.""" + + queryset = ( + Issue.objects.select_related( + 'series', + 'indicia_publisher', + 'variant_of', + ) + .prefetch_related( + 'brand_emblem', + 'keywords', + ACTIVE_ISSUE_CREDIT_PREFETCH, + ACTIVE_COVER_PREFETCH, + 'variant_of__cover_set',
Good catch. I switched the nested variant cover prefetch to an explicit
Prefetch(...) with the same deleted=False / order_by('id') behavior as
the primary issue cover prefetch, and attached it via active_cover_list so
_cover_url() can use the prefetched data directly.
I also added:
—
Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.
You are receiving this because you are subscribed to this thread.![]()
@DeusExTaco commented on this pull request.
In apps/api_v2/views/series.py:
Fair point. The .only() here is a deliberate tradeoff rather than an
accidental micro-optimization.
I added it after profiling broad series list requests against a local
production-data clone, where narrowing the selected columns materially reduced
the response cost. The current serializer field set is covered by tests, so we
should catch accidental drift if the serializer changes.
I’m happy to drop it if you’d rather favor lower maintenance over the measured
list-performance gain, but I left it in because the improvement was
significant in local profiling.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.
You are receiving this because you are subscribed to this thread.![]()
@DeusExTaco commented on this pull request.
In apps/api_v2/utils/credits.py:
> + if credits is None: + credits = obj.credits.filter(deleted=False).select_related( + 'creator', + 'credit_type', + )
Agreed that the fallback would be more expensive if it became part of a hot path.
In the current v2 endpoints, the relevant credit relationships are prefetched
before serialization, so the fallback is there as a defensive correctness path
rather than the normal execution path. I left it in so the helper still
behaves safely if it gets reused outside the fully-prefetched viewsets.
If we add more callers later, we can revisit whether to make that expectation
stricter.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.
You are receiving this because you are subscribed to this thread.![]()
—
Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.
You are receiving this because you are subscribed to this thread.![]()