Skip to content

Prefetch the artifact-family admin changelists (#1346, Phase 3)#1350

Merged
jonfroehlich merged 1 commit into
masterfrom
1346-admin-perf-phase3
Jun 19, 2026
Merged

Prefetch the artifact-family admin changelists (#1346, Phase 3)#1350
jonfroehlich merged 1 commit into
masterfrom
1346-admin-perf-phase3

Conversation

@jonfroehlich

Copy link
Copy Markdown
Member

Phase 3 of #1346 (admin changelist audit). The artifact list pages — Publication, Talk, Poster, Video, Grant, Award — each render M2M/FK callable columns (author & speaker lists, projects, sponsor, recipients) with no prefetch, so they fired 1–3 queries per row. Now each is constant-query.

Changes

  • get_queryset() prefetch on each admin for exactly what its changelist renders:
    • Publication → authors, projects
    • Talk → authors (speakers)
    • Poster → authors
    • Video → projects
    • Grant → authors (sponsor FK handled by list_select_related)
    • Award → recipients, projects
  • Artifact.get_first_author_last_name rewritten to read list(self.authors.all()) instead of .exists() + .first() (both bypass a prefetch cache). authors is a SortedManyToManyField, so .all() preserves the editor-defined order — [0] is still the first author. (Used by Poster & Grant columns; kept behavior-identical.)
  • PublicationAdmin.display_authors now reads the cached list instead of a [:5] queryset slice + .count() (both re-queried per row).

Heads-up / gotcha

There is no list_prefetch_related ModelAdmin option in Django (only list_select_related) — I initially used it, the tests caught that it's a silent no-op, and I moved the prefetch into get_queryset(). Worth knowing for future changelist work.

Tests (test_admin_perf_artifacts.py)

  • Correctness of the two rewritten methods (first-author ordering, "Unknown" when authorless; display_authors five-names-plus-ellipsis and the no-authors case).
  • A steady-state N+1 guard that each of the five prefetched changelists stays flat as rows grow 3 → 6 (these failed before the get_queryset fix — that's how the list_prefetch_related no-op was caught).

Full suite green locally (401 tests). Branches cleanly off current master (Phases 1 & 2 already merged).

🤖 Generated with Claude Code

The Publication/Talk/Poster/Video/Grant/Award list pages each rendered
M2M/FK callable columns (author & speaker lists, projects, sponsor,
recipients) with no prefetch, firing 1-3 queries per row.

- get_queryset() now prefetch_related()s the relations each changelist
  renders: Publication (authors, projects), Talk (authors), Poster
  (authors), Video (projects), Grant (authors), Award (recipients,
  projects). Grant's sponsor FK uses list_select_related.
- Artifact.get_first_author_last_name rewritten to read
  list(self.authors.all()) instead of .exists()/.first() (which bypass a
  prefetch cache); SortedManyToManyField preserves order so [0] is still
  the first author. PublicationAdmin.display_authors likewise reads the
  cached list instead of a [:5] queryset slice + .count().

Note: Django has no `list_prefetch_related` ModelAdmin option (only
list_select_related), so prefetching must go through get_queryset().

Tests (test_admin_perf_artifacts.py): correctness of the two rewritten
methods, plus a steady-state guard that each of the five changelists
stays flat as rows grow 3 -> 6. Full suite green (401).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jonfroehlich jonfroehlich merged commit 0ef0454 into master Jun 19, 2026
3 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