Foundation work for a v2 REST API at /api/v2/, sitting alongside the existing v1 API at /api/ which is untouched. No data endpoints in this PR — it's pure scaffolding so the structure can be reviewed before serializer logic starts piling up. Series, Issues, and Publishers come in the next PR.
A new apps/api_v2/ Django app with a read-only base viewset, token auth wired at /api/v2/auth/token/, drf-spectacular schema + Swagger UI + ReDoc, page-number pagination (default 50, max 200), three throttle buckets (anon 30/hr, token-or-basic 2000/day, session 2000/day), and conditional-request helpers ready for endpoints to plug into. Tests are set up via pytest-django with shared fixtures in conftest.py. CI is a small lint-only GitHub Actions workflow that triggers on changes inside apps/api_v2/ only — it deliberately stays out of the rest of the codebase.
The URL routing is split into urls_www.py and urls_my.py and dispatched on settings.MYCOMICS. This mirrors the existing two-instance deployment model: www.comics.org (MYCOMICS=False) serves approved GCD data, my.comics.org (MYCOMICS=True) serves user data. The dispatcher includes exactly one surface module per instance, plus the cross-cutting routes (token, schema) on both. Right now both surface modules have empty routers, so the externally visible behavior is the same as before, but the structure is in place for collections and reading-orders to land cleanly on the my-side in a later PR.
apps/api_v2/Touch list kept minimal:
settings.py: add apps.api_v2 and rest_framework.authtoken to INSTALLED_APPS (two lines).urls.py: one line, path('api/v2/', include('apps.api_v2.urls')).requirements.txt: one line, the conditional package.pyproject.toml: new file, ruff + pytest config scoped to apps/api_v2/.The authtoken migration runs on first deploy and creates the three-column authtoken_token table.
https://github.com/GrandComicsDatabase/gcd-django/pull/711
(21 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 establishes the foundation for the v2 API, introducing custom routers, pagination, throttling, and a base viewset that handles soft-delete filtering. It also includes utility helpers for conditional requests and OpenAPI schema generation, along with a comprehensive testing setup using pytest. Feedback was provided to ensure fixture isolation in tests by avoiding side effects on shared API clients and to use tuples for class attributes in viewsets to maintain consistency with the rest of the codebase.
In apps/api_v2/tests/conftest.py:
> +def authenticated_client(api_client, authenticated_token):
+ """Return an ``APIClient`` pre-configured with the test user's token."""
+ api_client.credentials(
+ HTTP_AUTHORIZATION=f'Token {authenticated_token.key}',
+ )
+ return api_client
The authenticated_client fixture modifies the api_client fixture instance it receives. This creates a side effect: any other test or fixture that uses api_client within the same scope will unexpectedly get an authenticated client, breaking the "unauthenticated" contract of the api_client fixture. This can lead to subtle and hard-to-debug test failures.
To ensure fixture isolation and prevent side effects, authenticated_client should create its own APIClient instance instead of modifying the shared one.
-def authenticated_client(api_client, authenticated_token):
- """Return an ``APIClient`` pre-configured with the test user's token."""
- api_client.credentials(
- HTTP_AUTHORIZATION=f'Token {authenticated_token.key}',
- )
- return api_client
+def authenticated_client(authenticated_token):
+ """Return an APIClient pre-configured with the test user's token."""
+ client = APIClient()
+ client.credentials(
+ HTTP_AUTHORIZATION=f"Token {authenticated_token.key}",
+ )
+ return client
In apps/api_v2/views/__init__.py:
> + authentication_classes = [ + TokenAuthentication, + BasicAuthentication, + SessionAuthentication, + ] + permission_classes = [AllowAny] + pagination_class = V2PageNumberPagination + throttle_classes = [ + V2AnonRateThrottle, + V2TokenUserRateThrottle, + V2SessionUserRateThrottle, + ]
For consistency and to signal immutability, it's better to use tuples instead of lists for class attributes like authentication_classes, permission_classes, and throttle_classes. This matches the style used in apps/api_v2/routers.py for similar attributes.
- authentication_classes = [ - TokenAuthentication, - BasicAuthentication, - SessionAuthentication, - ] - permission_classes = [AllowAny] - pagination_class = V2PageNumberPagination - throttle_classes = [ - V2AnonRateThrottle, - V2TokenUserRateThrottle, - V2SessionUserRateThrottle, - ] + authentication_classes = ( + TokenAuthentication, + BasicAuthentication, + SessionAuthentication, + ) + permission_classes = (AllowAny,) + pagination_class = V2PageNumberPagination + throttle_classes = ( + V2AnonRateThrottle, + V2TokenUserRateThrottle, + V2SessionUserRateThrottle, + )
—
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/tests/conftest.py:
> +def authenticated_client(api_client, authenticated_token):
+ """Return an ``APIClient`` pre-configured with the test user's token."""
+ api_client.credentials(
+ HTTP_AUTHORIZATION=f'Token {authenticated_token.key}',
+ )
+ return api_client
Fixed in 81f620f6. I changed authenticated_client to create its own APIClient instead of mutating the shared api_client fixture, so auth state no longer leaks into tests that expect an anonymous client.
—
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/__init__.py:
> + authentication_classes = [ + TokenAuthentication, + BasicAuthentication, + SessionAuthentication, + ] + permission_classes = [AllowAny] + pagination_class = V2PageNumberPagination + throttle_classes = [ + V2AnonRateThrottle, + V2TokenUserRateThrottle, + V2SessionUserRateThrottle, + ]
Fixed in 81f620f6. I switched those base viewset class attributes to tuples so they match the existing V2APIRootView style and read as fixed class-level configuration rather than mutable lists.
—
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.![]()
@LegalizeAdulthood approved this pull request.
I only skimmed over it, but other than a couple nits, I have no other comments.
I think your approach is good: bring things in one bit at a time in reasonably sized commits that can be reviewed without reading code longer than War and Peace. :)
Probably should get a review from Jochen before merging.
On apps/api_v2/filters/__init__.py:
What's changing if you're only changing whitespace?
In .github/workflows/api-v2-ci.yml:
> @@ -0,0 +1,35 @@ +name: API v2 CI
The existing files don't have it, but it probably wouldn't hurt for these new files to put SPDX license identifier comments on them. Top-level license file is GPL v3, I think.
—
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.
On apps/api_v2/filters/__init__.py:
Nothing is changing its just creation of the init file.
—
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 .github/workflows/api-v2-ci.yml:
> @@ -0,0 +1,35 @@ +name: API v2 CI
How about I add them in the next PR so I don't create a lot of noise here? If you want them included now, I’m happy to add them here.
—
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.![]()
I created an api-v2 branch, where this should go to. For longer devs, we use specific branches.
Please change the target branch to that one.
—
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.![]()
@jochengcd commented on this pull request.
> + + scope = 'v2_anon' + rate = '30/hour' + + +class V2TokenUserRateThrottle(UserRateThrottle): + """2000 requests per day for Token or Basic authenticated users. + + Both methods carry credentials per request and target programmatic + consumers (third-party tools, scripts, CI). The PRD lists the rate + explicitly for Token; Basic is grouped here because it is the same + use case. + """ + + scope = 'v2_user_token' + rate = '2000/day'
down the road these configs should be in settings. Authenticated users likely should also have a short-term burst limit.
Both these are fines as TODOs in the file for now
—
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.
> + + scope = 'v2_anon' + rate = '30/hour' + + +class V2TokenUserRateThrottle(UserRateThrottle): + """2000 requests per day for Token or Basic authenticated users. + + Both methods carry credentials per request and target programmatic + consumers (third-party tools, scripts, CI). The PRD lists the rate + explicitly for Token; Basic is grouped here because it is the same + use case. + """ + + scope = 'v2_user_token' + rate = '2000/day'
TODO an minor modification 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.![]()
—
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.![]()