Skip to content

refactor!: assign accounts one by one instead of a many-to-many - #1

Merged
urtzai merged 6 commits into
mainfrom
feat/client-app-account
Oct 8, 2026
Merged

urtzai merged 6 commits into
mainfrom
feat/client-app-account

Conversation

@urtzai

@urtzai urtzai commented Oct 8, 2026

Copy link
Copy Markdown
Member

Why

ClientApp.accounts was a many-to-many holding the account list of every application. The resolver had to load that whole list into memory in every process (by_user dict), which does not scale with the number of users — and the mobile-app readers never needed to be in that list at all.

What

Accounts are now assigned one row at a time:

  • ClientAppAccount: OneToOneField on the account (one application per token, so the assignment is unambiguous), a notes field and a client_app FK.
  • The resolver does a single indexed point lookup; nothing is loaded into memory. Assignments are not cached (they do not need to be), only the small rule table (domains / networks / user agents) is.
  • The order is unchanged and explicit: account → host → network → user agent. An assignment always wins; accounts without one (an app user reading the news) fall through to the user agent, which is what identifies the native apps.
  • Admin: a ClientAppAccount changelist you can search (username, email, name) and filter by application, with a raw-id account field. ClientApp keeps the rule fields and shows an accounts count.

Migration

0004_... copies the assignments made through the removed field before dropping it, so nothing is lost.

⚠️ Breaking change

ClientApp.accounts is gone. Version 0.1.7 (which shipped the many-to-many) is being yanked on PyPI and its GitHub release removed: it never reached production, so no project depends on it.

Tests

71 tests, ruff and black clean, makemigrations --check with no pending changes.

Not in this PR

The version bump and the release: they will be done separately once this is reviewed.

urtzai added 6 commits October 8, 2026 14:13
ClientApp.accounts (a many-to-many holding every account) forced the resolver to
load the whole list into memory on every process. Accounts are now assigned one
row at a time (ClientAppAccount), so the resolver does a single indexed point
lookup and the table only holds real integrations.

- ClientAppAccount: OneToOne on the account (one application per token, so the
  assignment is unambiguous) + notes
- resolver: the account rule is checked first, then host, network and user agent
- admin: ClientAppAccount changelist (search, filter by application, raw id for
  the account); ClientApp keeps the rule fields and shows an accounts count
- migration 0004 copies the assignments made through the removed field
- 71 tests

BREAKING CHANGE: ClientApp.accounts is gone; assignments live in
ClientAppAccount. Version 0.1.7, which shipped the many-to-many, is being yanked
and never reached production.
Version bump for the account-assignment redesign. The release itself (GitHub
release -> PyPI) is created separately, after review.
- Endpoint stats: Application (endpoint.app_label) and Method columns, so the
  risky verbs can be spotted without reading the endpoint string
- The verb is rendered as a pill (reads green/blue, writes amber, deletes red)
- Same badge in the Endpoints changelist, for consistency
- 73 tests
The verb and the application now have their own columns, so repeating them
inside the endpoint cell ("GET api/3.0/herriak/ (api)") was noise. The cell
shows the route path, with the Django regex anchors stripped for readability,
and falls back to the route name for endpoints whose path is not known yet.

75 tests.
Endpoint.route_path only exists since 0.1.3, so older rows carry just their
route name and the stats column showed 'kirolak_berriak-list'. The new
api_usage_backfill_paths command resolves the name back to its path and leaves
(and reports) whatever it cannot resolve.

- works from the admin? no: it is a one-off management command, --dry-run first
- 80 tests
Django 5.1+ repeats str(obj) in the action checkbox aria-label, and
EndpointStat.__str__ contains the endpoint ('DELETE api/3.0/gida/ (gida)'), so
the page-wide assertNotIn failed on Django 5.2 while passing on 4.2.

The test now checks the cells of the column. Verified on Django 4.2 and 5.2.
@urtzai
urtzai merged commit 40e3b48 into main Oct 8, 2026
7 checks passed
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.

1 participant