Skip to content

DRF Tokens migration#4075

Merged
hlbmtc merged 8 commits intomainfrom
feat/api-tokens-last-used-at
Jan 16, 2026
Merged

DRF Tokens migration#4075
hlbmtc merged 8 commits intomainfrom
feat/api-tokens-last-used-at

Conversation

@hlbmtc
Copy link
Contributor

@hlbmtc hlbmtc commented Jan 15, 2026

  • Moved the DRF Token model into the Authentication app and renamed it to ApiKey
  • Added a last_used_at field to track token usage
  • Added backend endpoints and a settings UI button for API key rotation (for future use)
  • Added a UI to generate an API key if the user doesn’t have one yet (for future use)
  • Updated the AIB page to fetch the API key from the new endpoint

The migration handles existing authtoken_token data and supports rollback. Translations were added for all supported languages.

Part of #3977

Summary by CodeRabbit

  • New Features

    • API key endpoints and server-side rotation; create, view, rotate keys; last-used timestamps recorded
  • UI / Localization

    • Improved API key management UI (visibility, copy, rotate + confirm modal)
    • Added translations for API key flows (en, cs, es, pt, zh, zh‑TW)
  • Chores / Migration

    • New ApiKey model and migration to preserve existing tokens
    • Removed legacy token admin integration and authtoken registration

✏️ Tip: You can customize this high-level summary in your review settings.

… to control table columns

- Added `last_used_at` column to track last api call timestamp
- Renamed `Token` to `ApiToken`
- Added endpoint to retrieve user's Api Token -- `/api/auth/api-token/`
- Small fixes
- changed settings page for on-demand api key generation;
- Adjusted aib page to take your current api key from the api
- Added ApiKey rotation endpoint
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

📝 Walkthrough

Walkthrough

Replaces DRF Token with a new ApiKey model (tracks last_used_at), migrates existing tokens, updates authentication to persist last_used_at, removes authtoken/admin registration, adds GET/POST API endpoints to fetch/rotate keys, updates backend/frontend usages, and adds UI + i18n for API key management. (50 words)

Changes

Cohort / File(s) Summary
Backend: ApiKey model & migration
authentication/models.py, authentication/migrations/0001_add_token_model.py
Add ApiKey model (40-char key PK, OneToOne user, created, last_used_at) and a reversible migration to migrate data from authtoken_token and create the new table.
Backend: Authentication logic, admin, settings
authentication/auth.py, authentication/admin.py, metaculus_web/settings.py
FallbackTokenAuthentication.model = ApiKey and override to update last_used_at on authenticate; removed admin auto-registration of Token and removed rest_framework.authtoken from INSTALLED_APPS.
Backend: API endpoints & auth views
authentication/urls.py, authentication/views/common.py
Add GET /auth/api-key/ and POST /auth/api-key/rotate/; replace Token usage with ApiKey across auth-related views (login/signup/password reset/activation); add endpoints/handlers for fetch/rotate/create.
References & cleanup across project
projects/admin.py, users/models.py, users/views.py, tests/unit/conftest.py
Replace imports/usages of rest_framework.authtoken.models.Token with authentication.models.ApiKey; update create/get_or_create/delete calls and cleanup logic accordingly.
Frontend: API client & server actions
front_end/src/services/api/auth/auth.server.ts, front_end/src/app/(main)/accounts/settings/actions.tsx
Add ServerAuthApi.getApiKey() and rotateApiKey(); add server actions getApiKeyAction and rotateApiKeyAction wrapping those endpoints with existing error handling.
Frontend: Settings UI & pages
front_end/src/app/(main)/accounts/settings/account/components/api_access.tsx, .../account/page.tsx, .../aib/2026/spring/page.tsx
ApiAccess prop changed from tokenapiKey (string
Frontend: i18n
front_end/messages/en.json, .../cs.json, .../es.json, .../pt.json, .../zh.json, .../zh-TW.json
Add translation keys per locale for API key management/rotation: rotateApiKey, noApiKeyYet, createApiKey, apiKeyRotated, apiKeyCreated, rotateApiKeyTitle, rotateApiKeyDescription, rotate.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Frontend
  participant Server
  participant DB
  Client->>Frontend: Click "Rotate API Key"
  Frontend->>Server: POST /auth/api-key/rotate/
  Server->>DB: DELETE FROM authentication_apikey WHERE user_id=...
  Server->>DB: INSERT INTO authentication_apikey (key,user_id,created,last_used_at)
  DB-->>Server: new key row
  Server-->>Frontend: 201 { key }
  Frontend-->>Client: show new key / toast
Loading
sequenceDiagram
  participant Client
  participant Frontend
  participant Server
  participant Authenticator
  participant DB
  Client->>Frontend: Request with Authorization: Token <key>
  Frontend->>Server: API request
  Server->>Authenticator: FallbackTokenAuthentication.authenticate_credentials(key)
  Authenticator->>DB: SELECT * FROM authentication_apikey WHERE key=...
  DB-->>Authenticator: ApiKey (user)
  Authenticator->>DB: UPDATE authentication_apikey SET last_used_at=now() WHERE key=...
  DB-->>Authenticator: update OK
  Authenticator-->>Server: (user, token)
  Server-->>Frontend: 200 / resource
  Frontend-->>Client: response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I nibble keys both new and bright,
I stamp last-used at morning light.
Old tokens hopped away today,
I spin a key — then run and play!
Multilingual carrots — hip hooray!

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'DRF Tokens migration' accurately describes the main change: migrating from Django REST Framework's Token model to a new ApiKey model in the authentication app.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@authentication/migrations/0001_add_token_model.py`:
- Around line 8-26: The reverse migration hardcodes the user table as users_user
which breaks rollbacks for custom AUTH_USER_MODEL; in
migrate_tokens_and_drop_old_table build the real user table name dynamically by
resolving settings.AUTH_USER_MODEL via apps.get_model(...) and using its
_meta.db_table (or schema_editor.quote_name/_meta.db_table) when constructing
the SQL that creates/references the user FK instead of the literal 'users_user';
update the SQL string assembly to interpolate that resolved table name (using
schema_editor or connection quoting APIs to avoid SQL injection) so rollback
works with custom user models.

In `@authentication/urls.py`:
- Around line 24-26: The API key endpoints are missing authentication
enforcement; add the DRF permission decorator
`@permission_classes`([IsAuthenticated]) to the view functions api_key_api_view
and api_key_rotate_api_view (same pattern used for invite_user_api_view) so both
endpoints require authenticated users; update the corresponding view definitions
in common to import and apply IsAuthenticated/@permission_classes to each
function.

In
`@front_end/src/app/`(main)/accounts/settings/account/components/api_access.tsx:
- Around line 57-69: handleCreateKey currently lacks error handling similar to
handleRotate; wrap the rotateApiKeyAction call in a try/catch (or add a catch
block) to catch failures, log the error and call toast with a failure message
(using t for translation) so the user gets feedback, while leaving the existing
finally block that calls setIsRotating(false); refer to handleCreateKey,
rotateApiKeyAction, setApiKey, setIsVisible, toast and t when applying the
change.
- Around line 142-149: The ConfirmModal remains open after a successful API key
rotation because handleRotate does not close the modal; update the rotation flow
so that after handleRotate completes successfully you call
setShowRotateModal(false) to close the modal. Locate the ConfirmModal usage and
the handleRotate function (and any async rotation helper it calls) and add logic
to setShowRotateModal(false) on success (e.g., in the .then/await success path
or after try block) while keeping error handling unchanged.
- Around line 43-55: handleRotate currently only checks for response.key and
ignores failure cases from rotateApiKeyAction; update handleRotate to detect
when the action returns errors (response.errors) or a falsy key, call
setIsRotating appropriately, and surface the failure to the user (e.g., call
toast with an error message and optionally log the error). Specifically, inside
the try block after awaiting rotateApiKeyAction(), if response.errors exists or
response.key is falsy, call toast(t("apiKeyRotateFailed") or a suitable message)
and log the error, otherwise proceed to setApiKey(response.key) and
setIsVisible(true); ensure setIsRotating(false) still runs in the finally block.
🧹 Nitpick comments (6)
tests/unit/conftest.py (1)

72-73: Consider renaming the variable for clarity.

The code is functionally correct. The "Token" prefix in the Authorization header is the authentication scheme name (not the model name), so it correctly remains unchanged.

However, the variable name token could be renamed to api_key for consistency with the new model name, improving code readability.

♻️ Optional: Rename variable for clarity
         if user:
-            token = ApiKey.objects.create(user=user)
-            client.credentials(HTTP_AUTHORIZATION=f"Token {token.key}")
+            api_key = ApiKey.objects.create(user=user)
+            client.credentials(HTTP_AUTHORIZATION=f"Token {api_key.key}")
authentication/migrations/0001_add_token_model.py (1)

29-49: Hardcoded user table reference in rollback may cause issues.

The rollback function hardcodes users_user(id) as the foreign key reference (line 37). If AUTH_USER_MODEL is ever changed or differs in some deployment, this rollback would fail.

Consider dynamically resolving the user table name:

♻️ Suggested improvement
 def restore_old_table_from_new(apps, schema_editor):
     """Recreate old authtoken_token table and copy data back for rollback."""
+    User = apps.get_model(settings.AUTH_USER_MODEL)
+    user_table = User._meta.db_table
     with schema_editor.connection.cursor() as cursor:
         cursor.execute(
-            """
+            f"""
             CREATE TABLE IF NOT EXISTS authtoken_token (
                 key VARCHAR(40) PRIMARY KEY,
                 created TIMESTAMP WITH TIME ZONE NOT NULL,
-                user_id INTEGER NOT NULL UNIQUE REFERENCES users_user(id)
+                user_id INTEGER NOT NULL UNIQUE REFERENCES {user_table}(id)
                     ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
             )
             """
         )
front_end/src/app/(main)/aib/2026/spring/page.tsx (1)

19-22: Consider adding error handling for the API key fetch.

If ServerAuthApi.getApiKey() throws an error (e.g., network failure, authentication issue), this will cause an unhandled exception. The non-bot path with ServerProfileApi.getBotToken() could have similar issues, but adding error handling here would improve robustness.

♻️ Suggested improvement
   if (user.is_bot) {
-    const { key } = await ServerAuthApi.getApiKey();
-    return key;
+    try {
+      const { key } = await ServerAuthApi.getApiKey();
+      return key;
+    } catch {
+      return null;
+    }
   }
users/views.py (1)

6-7: Import ordering could be improved.

The ApiKey import from authentication.models is placed between rest_framework imports, which breaks the standard import grouping convention (stdlib → third-party → local). Consider moving it to the local imports section.

♻️ Suggested reordering
 from django.contrib.auth.password_validation import validate_password
 from django.utils import timezone
 from rest_framework import serializers, status
-from authentication.models import ApiKey
 from rest_framework.decorators import api_view, permission_classes
 from rest_framework.exceptions import ValidationError
 from rest_framework.generics import get_object_or_404
 from rest_framework.permissions import AllowAny, IsAdminUser
 from rest_framework.request import Request
 from rest_framework.response import Response

+from authentication.models import ApiKey
 from users.models import User, UserSpamActivity
authentication/auth.py (1)

33-37: Consider debouncing last_used_at updates for high-traffic scenarios.

The current implementation issues a database write on every authenticated API request. While the filter().update() approach is efficient (single query, no model instantiation), this could become a performance bottleneck under high load.

Consider one of these alternatives:

  1. Time-based debouncing: Only update if last_used_at is older than a threshold (e.g., 1 hour)
  2. Async update: Queue the update to happen asynchronously
  3. Sampling: Update on a percentage of requests
Example: Time-based debouncing approach
 def authenticate_credentials(self, key):
     user, token = super().authenticate_credentials(key)
-    ApiKey.objects.filter(key=key).update(last_used_at=timezone.now())
+    # Only update if last_used_at is older than 1 hour to reduce DB writes
+    one_hour_ago = timezone.now() - timezone.timedelta(hours=1)
+    ApiKey.objects.filter(
+        key=key,
+        last_used_at__lt=one_hour_ago
+    ).update(last_used_at=timezone.now())
 
     return user, token

Note: This would require handling the case where last_used_at is NULL (first use):

from django.db.models import Q

ApiKey.objects.filter(
    Q(key=key) & (Q(last_used_at__isnull=True) | Q(last_used_at__lt=one_hour_ago))
).update(last_used_at=timezone.now())
authentication/views/common.py (1)

276-283: Add explicit permission decorator for consistency.

This endpoint retrieves sensitive API key information and lacks an explicit @permission_classes decorator, which is inconsistent with the codebase pattern where all other endpoints declare their permission requirements explicitly. Add @permission_classes([IsAuthenticated]) to improve clarity and align with project conventions.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 880c7e0 and 1c96e1d.

📒 Files selected for processing (23)
  • authentication/admin.py
  • authentication/auth.py
  • authentication/migrations/0001_add_token_model.py
  • authentication/migrations/__init__.py
  • authentication/models.py
  • authentication/urls.py
  • authentication/views/common.py
  • front_end/messages/cs.json
  • front_end/messages/en.json
  • front_end/messages/es.json
  • front_end/messages/pt.json
  • front_end/messages/zh-TW.json
  • front_end/messages/zh.json
  • front_end/src/app/(main)/accounts/settings/account/components/api_access.tsx
  • front_end/src/app/(main)/accounts/settings/account/page.tsx
  • front_end/src/app/(main)/accounts/settings/actions.tsx
  • front_end/src/app/(main)/aib/2026/spring/page.tsx
  • front_end/src/services/api/auth/auth.server.ts
  • metaculus_web/settings.py
  • projects/admin.py
  • tests/unit/conftest.py
  • users/models.py
  • users/views.py
💤 Files with no reviewable changes (2)
  • metaculus_web/settings.py
  • authentication/admin.py
🧰 Additional context used
🧬 Code graph analysis (9)
front_end/src/app/(main)/accounts/settings/actions.tsx (1)
front_end/src/utils/core/errors.ts (1)
  • ApiError (22-74)
authentication/views/common.py (1)
authentication/models.py (1)
  • ApiKey (8-34)
authentication/urls.py (1)
authentication/views/common.py (2)
  • api_key_api_view (277-283)
  • api_key_rotate_api_view (287-292)
front_end/src/app/(main)/accounts/settings/account/components/api_access.tsx (2)
front_end/src/app/(main)/accounts/settings/actions.tsx (1)
  • rotateApiKeyAction (138-154)
front_end/src/components/ui/form_field.tsx (1)
  • Input (111-128)
authentication/auth.py (1)
authentication/models.py (1)
  • ApiKey (8-34)
users/models.py (1)
authentication/models.py (1)
  • ApiKey (8-34)
projects/admin.py (1)
authentication/models.py (1)
  • ApiKey (8-34)
tests/unit/conftest.py (1)
authentication/models.py (1)
  • ApiKey (8-34)
users/views.py (1)
authentication/models.py (1)
  • ApiKey (8-34)
🪛 Ruff (0.14.11)
authentication/migrations/0001_add_token_model.py

8-8: Unused function argument: apps

(ARG001)


29-29: Unused function argument: apps

(ARG001)


56-58: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


60-84: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Frontend Checks
  • GitHub Check: Backend Checks
  • GitHub Check: integration-tests
🔇 Additional comments (24)
front_end/messages/pt.json (1)

1783-1790: LGTM!

The Portuguese translations for the new API key management features are grammatically correct and consistent with the feature requirements. The translations appropriately convey the rotation and creation workflows.

authentication/models.py (1)

8-34: LGTM!

The ApiKey model implementation is sound:

  • Uses cryptographically secure os.urandom(20) for key generation (160 bits of entropy)
  • OneToOneField correctly enforces one API key per user
  • CASCADE delete behavior is appropriate for user cleanup
  • Auto-generation in save() is a clean pattern

The implementation correctly mirrors DRF's Token model while adding the last_used_at tracking capability.

users/models.py (2)

11-11: LGTM!

Import correctly updated to use the new ApiKey model from the authentication app.


250-251: LGTM!

The cleanup logic correctly uses ApiKey.objects.filter(user=self).delete() to remove the user's API key during data deletion. This pattern safely handles cases where no API key exists.

projects/admin.py (3)

13-13: LGTM!

Import correctly updated to use the new ApiKey model.


564-564: LGTM!

API key creation correctly uses the new ApiKey model. The key will be auto-generated via the model's save() method.


622-622: LGTM!

Consistent with the change on line 564 - correctly uses ApiKey for the complex password user generation action.

tests/unit/conftest.py (1)

5-5: LGTM!

Import correctly updated to use the new ApiKey model.

front_end/messages/zh.json (1)

1787-1794: LGTM!

The Simplified Chinese translations for API key management are accurate and well-formed. The terminology is consistent throughout (using "轮换" for rotate and "API 密钥" for API key), and the warning message in rotateApiKeyDescription properly conveys the impact of rotating the key.

front_end/messages/cs.json (1)

1785-1792: LGTM!

The Czech translations for API key management are accurate and grammatically correct. The terminology is consistent throughout (using "Rotovat" for rotate and "API klíč" for API key), and the warning message in rotateApiKeyDescription properly conveys the impact of key rotation.

front_end/messages/en.json (1)

1809-1816: LGTM!

The new internationalization entries for API key management are clear, descriptive, and follow the existing naming conventions. The warning message in rotateApiKeyDescription appropriately informs users about the consequences of rotating their API key.

authentication/migrations/0001_add_token_model.py (1)

60-84: LGTM!

The migration structure is correct:

  1. Creates the ApiKey model first with appropriate fields matching the DRF Token structure plus the new last_used_at field
  2. Then runs the data migration to copy existing tokens

The apps parameter in RunPython functions is required by Django's signature even when unused, so the static analysis hints are false positives.

users/views.py (2)

236-239: LGTM!

The migration from Token to ApiKey is correctly implemented. The get_or_create pattern and response format remain unchanged, ensuring backward compatibility for API consumers.


286-289: LGTM!

Same pattern as create_bot_api_view - clean replacement of Token with ApiKey while maintaining the same interface.

front_end/messages/es.json (1)

1785-1792: LGTM!

The Spanish translations for the API key management UI are complete and grammatically correct. The keys align with the frontend components that handle API key creation and rotation flows.

front_end/src/app/(main)/accounts/settings/account/page.tsx (1)

16-29: LGTM!

The API key fetching is correctly integrated. The apiKey can be null when a user hasn't created one yet, which aligns with the UI flow that allows key creation. The server component context ensures the user is authenticated before reaching this code.

front_end/src/app/(main)/accounts/settings/actions.tsx (1)

120-154: LGTM!

Both getApiKeyAction and rotateApiKeyAction follow the established error handling pattern used throughout this file. The consistent approach of returning { key } on success and { errors: err.data } on ApiError maintains uniformity with other actions like changePassword and createBot.

front_end/src/services/api/auth/auth.server.ts (1)

131-138: LGTM!

The new API methods are well-typed and follow the existing patterns in ServerAuthApiClass. The return type distinction (key: string | null for getApiKey vs key: string for rotateApiKey) correctly reflects the backend behavior where retrieval may return null for users without a key, while rotation always produces a valid key.

authentication/auth.py (1)

8-14: LGTM!

Setting model = ApiKey correctly configures the TokenAuthentication base class to use the custom ApiKey model instead of DRF's default Token. This is the standard approach for customizing token models in Django REST Framework.

front_end/messages/zh-TW.json (1)

1782-1789: LGTM!

The new translation keys for API key management are complete and align well with the UI functionality. The Traditional Chinese translations appear semantically correct for the create/rotate key flows.

authentication/views/common.py (3)

16-16: LGTM!

The import change from Token to ApiKey is consistent with the model migration, and all usages throughout the file have been updated accordingly.


65-67: LGTM!

The migration from Token to ApiKey maintains backward compatibility by preserving the token key in the response payload while using the new ApiKey model internally.


286-291: Rotation logic is correct; consider explicit permission decorator.

The delete-then-create pattern correctly handles both creating a new key (when none exists) and rotating an existing key. The filter().delete() is safe when no key exists. Same suggestion as above: consider adding explicit @permission_classes([IsAuthenticated]) for this security-sensitive endpoint.

front_end/src/app/(main)/accounts/settings/account/components/api_access.tsx (1)

82-139: LGTM!

The conditional UI rendering is well-structured with appropriate accessibility labels. The confirmation modal for key rotation provides good UX for this potentially disruptive action. The visibility toggle and copy functionality follow expected patterns.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@authentication/migrations/0001_add_token_model.py`:
- Around line 37-59: The rollback creates authtoken_token with user_id hardcoded
as INTEGER which mismatches a BigAutoField primary key; update
restore_old_table_from_new to dynamically determine the user PK DB type (use
apps.get_model(...) to get the user model and obtain its _meta.pk or use
schema_editor.connection.introspection or the PK field's
db_type(schema_editor.connection)) and interpolate that resolved type into the
CREATE TABLE user_id column and its REFERENCES clause, and also update
migrate_tokens_and_drop_old_table to call get_user_table_name (and/or return the
resolved PK type) accordingly so the created foreign key uses the correct BIGINT
(or actual) type instead of INTEGER.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c96e1d and fa4b621.

📒 Files selected for processing (1)
  • authentication/migrations/0001_add_token_model.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-15T19:29:58.940Z
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4075
File: authentication/urls.py:24-26
Timestamp: 2026-01-15T19:29:58.940Z
Learning: In this codebase, DRF is configured to use IsAuthenticated as the default in REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] within metaculus_web/settings.py. Therefore, explicit permission_classes([IsAuthenticated]) decorators are unnecessary on DRF views unless a view needs to override the default. When reviewing Python files, verify that views relying on the default are not redundantly decorated, and flag cases where permissions are being over-specified or when a non-default permission is explicitly required.

Applied to files:

  • authentication/migrations/0001_add_token_model.py
🪛 Ruff (0.14.11)
authentication/migrations/0001_add_token_model.py

8-8: Unused function argument: apps

(ARG001)


29-29: Unused function argument: apps

(ARG001)


56-58: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


60-84: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Backend Checks
  • GitHub Check: integration-tests
  • GitHub Check: Frontend Checks
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (python)
🔇 Additional comments (3)
authentication/migrations/0001_add_token_model.py (3)

8-12: LGTM - Dynamic user table resolution is properly implemented.

This helper correctly resolves the user model's table name from settings.AUTH_USER_MODEL and properly quotes it using Django's connection ops, ensuring compatibility with custom user models and preventing SQL injection.

Note: The static analysis warning (ARG001) about unused apps is a false positive—apps is used on line 11.


15-34: LGTM - Forward migration handles data transfer correctly.

The forward migration properly:

  1. Checks for the existence of authtoken_token before operating (supports fresh installs).
  2. Copies tokens with ON CONFLICT DO NOTHING to handle any edge cases.
  3. Drops the old table after successful data transfer.

The unused apps parameter (flagged by Ruff) is required by Django's RunPython signature and is not a concern.


62-94: LGTM - Migration class structure is correct.

The migration:

  • Properly declares initial = True for the first migration in the app.
  • Correctly specifies swappable_dependency to handle custom user models.
  • Orders operations correctly—CreateModel runs before RunPython, ensuring the authentication_apikey table exists before data migration.
  • The ApiKey model definition includes all required fields including the new last_used_at field.

The Ruff warnings (RUF012) about mutable class attributes are false positives—dependencies and operations are standard Django migration patterns.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@elisescu elisescu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, only had one minor comment. Pls have a look

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@authentication/migrations/0001_add_token_model.py`:
- Around line 29-38: The reverse migration function restore_old_table_from_new
should guard against the absence of authtoken_token before running the INSERT;
update restore_old_table_from_new to first check for the existence of the
authtoken_token table (using the same approach as the forward migration, e.g.
inspect schema_editor.connection or run a quick existence query such as checking
to_regclass('public.authtoken_token')) and only execute the INSERT INTO
authtoken_token ... SELECT ... WHEN the table exists; if the table is missing,
simply return/skip to avoid raising an error during rollback.
🧹 Nitpick comments (1)
authentication/migrations/0001_add_token_model.py (1)

8-10: Function name/docstring implies a drop that never happens.

migrate_tokens_and_drop_old_table only copies data; there is no DROP. Consider renaming to match behavior (and update the RunPython reference) or implement the drop if that’s intended.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d894b1 and 7b72f50.

📒 Files selected for processing (1)
  • authentication/migrations/0001_add_token_model.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-15T19:29:58.940Z
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4075
File: authentication/urls.py:24-26
Timestamp: 2026-01-15T19:29:58.940Z
Learning: In this codebase, DRF is configured to use IsAuthenticated as the default in REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] within metaculus_web/settings.py. Therefore, explicit permission_classes([IsAuthenticated]) decorators are unnecessary on DRF views unless a view needs to override the default. When reviewing Python files, verify that views relying on the default are not redundantly decorated, and flag cases where permissions are being over-specified or when a non-default permission is explicitly required.

Applied to files:

  • authentication/migrations/0001_add_token_model.py
🪛 Ruff (0.14.11)
authentication/migrations/0001_add_token_model.py

8-8: Unused function argument: apps

(ARG001)


29-29: Unused function argument: apps

(ARG001)


45-47: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


49-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Frontend Checks
  • GitHub Check: Backend Checks
  • GitHub Check: integration-tests
  • GitHub Check: Analyze (javascript-typescript)

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@hlbmtc hlbmtc merged commit 3ce461d into main Jan 16, 2026
10 checks passed
@hlbmtc hlbmtc deleted the feat/api-tokens-last-used-at branch January 16, 2026 12:37
@coderabbitai coderabbitai bot mentioned this pull request Jan 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants