Skip to content

(Story #2486) Task: Manual / Non-Automated Achievement Sources#2515

Draft
herzog0 wants to merge 46 commits into
developfrom
teo/2486-base-achievements
Draft

(Story #2486) Task: Manual / Non-Automated Achievement Sources#2515
herzog0 wants to merge 46 commits into
developfrom
teo/2486-base-achievements

Conversation

@herzog0

@herzog0 herzog0 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Issue: #2486

Summary & Context

Implements the full Achievements & Badges system: a new badges/ Django app where every achievement a user earns is a UserAchievement row (automatic or manual), and badge tiers (UserBadge) are derived state reconciled by a single recalculation service. Ingestion is batch-based (weekly backfill from historical Boost data) plus manual admin grants; users are emailed when they earn a badge (opt-in) and their earned badges show on the v3 profile page. Also reworks the review importer in versions/ to scrape the new boost.org review-results page, which supplies the data for the Library Review badge.

Changes

Badges app (new)

  • Models: Achievement, UserAchievement (generic FK to the source record, soft invalidation with audit fields), Badge, BadgeTier (append-only, soft delete, one active tier per rank), UserBadge (soft revocation with audit fields and a cascade/manual revocation_source)
  • recalculate_badges service - the single writer of UserBadge state: idempotent, race-safe (get_or_create), awards, re-earns, and cascade-revokes from the count of valid achievements; manually revoked badges are never silently re-earned (a "Reinstate" admin action is the explicit undo)
  • Catalogue of 8 achievement types with tier thresholds seeded from the Boost mapping spreadsheet (data migration + shared seed_catalogue helper)
  • Backfill sources for 6 automatic achievement types (library authoring/maintenance/versioning, commits, reviews, news posts); maintenance is deduplicated to one grant per library, matching its thresholds
  • backfill_achievements and recalculate_badges management commands with Celery task wrappers; backfill bulk-inserts idempotently and only recalculates pairs that actually gained rows
  • Admin for all five models: manual grants auto-set source_type/granted_by; invalidation and revocation require an audit note; tier choices are scoped to the selected badge server-side; changelist buttons queue backfill/recalculation on Celery (debounced via cache)
  • Achievement-awarded email (opt-in preference, skips unclaimed/invalid-email users), sent after commit via Celery

Integration

  • Weekly release_tasks and update_commits run the backfill after imports; live per-row signals were deliberately dropped for bulk paths (achievements are batch, not real-time)
  • v3 profile shows real earned badges; highest badge ordered by tier rank across badge types (not raw threshold)
  • Removed the stub users.Badge model and User.badges M2M (users/0022)

Review importer (supports the reviewer badge)

  • import_reviews scrapes the new boost.org review-results page (UTF-8 forced), deduplicates via a flexible multi-field fingerprint, and links submitters to users
  • Review admin: import button, id column, review manager columns ("Scraped review manager")

Risks & Considerations

  • Derived data needs a post-deploy nudge: migration 0006 deletes the inflated per-version maintenance achievements, but existing UserBadge rows stay until a recalculation. After deploy, press "Recalculate badges" in the UserBadge admin (or wait for the weekly backfill to settle new grants).
  • Bulk email risk is gated, not eliminated: notifications are skipped for revoked badges, unclaimed users, invalid emails, and anyone not opted in - and the backfill/rebuild paths never notify (notify=False). Worth double-checking the preference default before the first production backfill.
  • recalculate_badges runs synchronously in the UserAchievement signal - fine for manual admin volumes; bulk ingestion bypasses signals entirely (bulk_create).
  • Grandfathering: threshold changes never revoke existing badges, but a later achievement event recalculates against current thresholds and can revoke a tier that no longer qualifies. Matches the spike wording; flag if stronger grandfathering is wanted.
  • The button debounce and award race-safety assume the shared Redis cache/DB; both degrade to duplicate-but-idempotent work, not corruption.

Screenshots

TBD

herzog0 added 30 commits June 30, 2026 17:18
herzog0 added 16 commits June 30, 2026 17:18
Add two columns to the Review admin changelist so reviewers can see
the associated review manager at a glance:

- get_review_manager: the resolved CommitAuthor FK (sortable by name)
- review_manager_raw: the raw imported string, useful when the FK
  hasn't been linked yet

Extend search_fields so admins can find reviews by either the linked
manager or the raw text, and prefetch the FK in get_queryset to avoid
an N+1 on the changelist.
…anager'

Wrap review_manager_raw in a display method so the column header is the
explicit 'Scraped review manager' instead of the auto-derived
'Review manager raw'. The new label makes it clearer that the column
shows the verbatim string scraped from boost.org's review-results page,
not a curated value.
boost.org serves /doc/formal-reviews/review-results.html with
`Content-Type: text/html` and no charset, so `requests` falls back to
ISO-8859-1 per RFC 2616 even though the body is actually UTF-8. Every
accented name in the scraped table was therefore being mojibake-decoded
into the raw fields - e.g. "Joaquín M López Muñoz" turned into
"Joaquín M López Muñoz" - which both polluted the admin's
"Scraped review manager" column and broke the exact-match FK lookup
against CommitAuthor.name.

Override `response.encoding` to "utf-8" before reading `.text`. The
next `import_reviews` run rewrites existing rows in place (their
fingerprints survive the special-character normaliser in `_normalize`),
so previously-corrupted rows heal automatically.

Add a regression test that drives the importer with a UTF-8 body served
under a charsetless / ISO-8859-1 default response and asserts the raw
field stores the proper accented string, not the mojibake artefacts.
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 953f7079-b36b-4f69-aea5-7d71a71cba50

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch teo/2486-base-achievements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@herzog0 herzog0 linked an issue Jul 6, 2026 that may be closed by this pull request
17 tasks
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.

Task: Manual / Non-Automated Achievement Sources

1 participant