Add UW grant tracking: worktag/award fields + admin links to the official UW trackers (#1448) - #1449
Merged
Merged
Conversation
) Two admin-only additions around grants. 1. Three internal UW/Workday fields on Grant: uw_grant_id (the grant worktag, comma-separated when an award spans several), uw_award_number, and uw_award_name. All nullable. Shown in a "UW Internal Tracking" fieldset, with the worktag as a changelist column and all three searchable, since pasting a worktag from an email into the search box is the main use. These are internal administrative codes. GrantSerializer's field allowlist already excludes them; test_api.py now pins that, including a scan of the whole response body. Grant.grant_id is untouched: it is the *sponsor's* award ID (the NSF number), it is public, and it stays in the API. It gains a verbose_name of "Sponsor grant ID" so the two are distinguishable on the form. 2. A GrantTrackingLink model holding bookmarks to the official UW CSE and UW Award Portal trackers, rendered as a link bar atop the Grant changelist. These live in the database rather than in settings.py because the real URLs embed per-PI SharePoint sharing tokens and this repository is public; the URLField is max_length=1000 for the same reason (the SharePoint link blows past the 200-char default). Permissions: Editors (PhD students, staff) now hold view_grant so they can look up a worktag without pinging the PI, but nothing more. GrantAdmin hides the funding amounts, proposal PDFs/raw files, the total-funding rollup, and the tracking links from anyone who isn't a superuser, so the #1125 decision that funding data stays with the superuser still holds. GrantTrackingLink is superuser-only by the same mechanism as Award: absent from both group specs. Note that Django's read-only rendering resolves labels through admin.utils.label_for_field, which reads the model's verbose_name and ignores ModelAdmin form labels — hence the model-level verbose_names, which is what makes the labels correct on the read-only page Editors actually see. Version 2.34.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From QA on localhost: - Reorder the changelist so the worktag sits right beside the title, which is what people open this page for: Title, UW Grant ID (worktag), Sponsor, Date. Superusers additionally get First Author (Last Name) and Funding Amount. - Make the Editor column set an allowlist (EDITOR_LIST_DISPLAY) rather than the superuser list with sensitive columns filtered out, so a column added for superusers later cannot leak into the PhD-student view by omission. - Prefetch authors only when the first-author column is actually rendered. The prefetch exists to keep that column from firing a query per row (#1346); Editors don't get the column, so they shouldn't pay for the join. - Color the tracking-links/funding panel from the admin's own CSS custom properties instead of literal hex, so it follows the light/dark theme toggle. The hard-coded #f8f9fa/#ddd rendered as a light box with light text in dark mode (this predates the branch; the panel just grew enough to make it show). Also verified against the real UW CSE SharePoint URL: 425 characters, which passes URLValidator and needs the max_length=1000 field (Django's 200-char default would reject it). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AdminBackupWarningTests writes a status fixture and then hits the real admin views, which call get_backup_status() with the real clock. The fixture's timestamps were hard-coded to 2026-08-07, so with BACKUP_STALE_AFTER_HOURS=36 a "healthy" fixture started reading as stale on 2026-08-08 and two tests have failed on every run since: test_no_warning_when_backup_healthy test_data_health_panel_always_shown_even_when_healthy Master's CI last passed on the #1443 merge itself and has been red ever since; this surfaced while trying to merge #1449. Timestamp the view suite's fixture relative to datetime.now(timezone.utc) instead. The SimpleTestCase suites keep the fixed 2026-08-07 payload — they pass an explicit now=NOW, so they were never time-dependent and pinning exact values there is the point. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1448.
Two admin-only additions around grants, sharing one privacy boundary.
1. UW internal tracking codes on each grant
Three nullable fields on
Grant, in a new UW Internal Tracking fieldset:uw_grant_idGR…); comma-separate when an award spans severaluw_award_numberAWD-…)uw_award_nameAll three are searchable — pasting a worktag out of an email into the search box
is the main way this page gets used — and the worktag sits right beside the title
in the changelist.
These never leave the admin.
GrantSerializeruses an explicit fieldallowlist, so they're excluded structurally;
test_api.pynow pins that, bothper-field and by scanning the whole response body for a worktag.
Naming
Grant.grant_idalready existed and means the sponsor's award ID (the NSFnumber). It's public and stays in the API untouched — hence the
uw_prefix onthe new field. It gains a
verbose_nameof "Sponsor grant ID" so the two aretold apart on the form.
2. Links to the official UW trackers
A
GrantTrackingLinkmodel (label / url / notes / display order) rendered as alink bar atop the Grant changelist. These are database rows, not code: the
real URLs embed per-PI SharePoint sharing tokens and this repo is public. Same
reason the
URLFieldismax_length=1000— the real UW CSE SharePoint link is425 characters, so Django's 200-char default would reject it outright
(verified against the actual URL locally; there's a regression test).
They must be entered once per environment via Grants & Funding → Grant
tracking links; the panel shows an "add one" hint until they are.
Permissions change
Editors (PhD students, long-term staff) now hold
view_grant— read-only —so they can look up a worktag for a purchase, trip, or appointment without
pinging the PI. Nothing more. Two audiences, two views:
So the #1125 decision that funding data stays with the superuser still holds.
Contributors get nothing.
GrantTrackingLinkis superuser-only by the samemechanism as
Award— absent from both group specs.EDITOR_LIST_DISPLAYis an allowlist rather than the superuser list withsensitive columns filtered out, so a column added for superusers later can't leak
into the PhD-student view by omission. Relatedly,
prefetch_related('authors')(the #1346 fix for the first-author column) now applies only when that column is
actually rendered — Editors don't get it, so they don't pay for the join.
One Django subtlety worth knowing: read-only rendering resolves labels through
admin.utils.label_for_field, which reads the model'sverbose_nameandignores
ModelAdminform labels. So the labels live on the model — that's whatmakes them correct on the read-only page Editors actually see. A test guards it.
Testing
website/tests/test_grant_tracking.py(18 tests): optional fields,long-URL round-trip, link ordering, the per-audience column lists, the prefetch
behavior, and the full superuser-vs-Editor-vs-Contributor visibility matrix on
both the changelist and the change form.
test_api.py: theuw_*omission guards.test_setup_admin_groups.py: the Grant/Award/GrantTrackingLink boundaries,rewritten to compare exact codenames (
add_granttrackinglinkcontains thesubstring
_grant, so the old substring assertion would have been misleading).test_logging_config,test_backup_status) are pre-existing and environmental — verified identical ona stashed baseline; the local image predates
concurrent-log-handler.which aren't in
.pa11yci.json's scan set. The panel is colored from theadmin's CSS custom properties so it follows the light/dark theme toggle (it had
been hard-coded
#f8f9fa, which rendered light-on-light in dark mode).Deploy note
No migration to commit (
website/migrations/is gitignored); the entrypoint'smakemigrations/migratecreates the three columns and the new table on eachenvironment. Locally it generated
0044_granttrackinglink_grant_uw_award_name_and_moreand
0045_alter_grant_grant_idcleanly.After merging and tagging, add the two tracking links in prod's
/admin— they'redata, not code, so they don't ride along with the deploy.
🤖 Generated with Claude Code — Opus 5 (1M context), claude-opus-5[1m]