Anchor DLC purchases to a base game instead of a parent purchase

Add-on purchases (DLC, Season Pass, Battle Pass) previously linked to a
parent *purchase* via the `related_purchase` self-FK. When the base game
was bought inside a multi-game purchase (e.g. a bundle), there was no
per-game purchase to point at — only the whole bundle.

Replace it with a `related_game` FK (Game -> Game): an add-on belongs to
a *game*, which is unambiguous regardless of how the base game was bought.

- models: drop `related_purchase`; add `related_game`
  (SET_NULL, related_name="addon_purchases"); require it for non-GAME
  types in `save()`.
- forms: replace the parent-purchase picker with a flat `related_game`
  game search (reusing SearchSelectWidget/_game_options); drop the now
  unused related_purchase_queryset/RelatedPurchaseChoiceField.
- views/urls: remove the obsolete related_purchase_by_game endpoint.
- add_purchase.js: drop the parent-dropdown refetch; keep platform
  auto-fill; retarget the type toggle to #id_related_game.
- migration 0020: add -> backfill (related_game = parent's first game by
  sort_name) -> remove related_purchase.
- tests: model validation unit tests + an e2e test for the flat picker.

related_game is deliberately game->game so it can later be synced from
IGDB's parent_game without schema changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 23:17:27 +02:00
parent 62f0c6c261
commit dfccfbff51
9 changed files with 130 additions and 86 deletions
+15 -1
View File
@@ -120,7 +120,7 @@ def test_widgets_initialize_inside_htmx_swapped_content(
def test_add_purchase_type_toggles_disabled_fields(
authenticated_page: Page, live_server
):
"""add_purchase.js disables name/related-purchase while type is "game"
"""add_purchase.js disables name/related-game while type is "game"
and re-enables them for other types."""
page = authenticated_page
page.goto(f"{live_server.url}{reverse('games:add_purchase')}")
@@ -133,3 +133,17 @@ def test_add_purchase_type_toggles_disabled_fields(
page.select_option("#id_type", "game")
expect(name_input).to_be_disabled()
def test_add_purchase_related_game_is_flat_game_search(
authenticated_page: Page, live_server
):
"""The DLC/Season-Pass anchor is now a flat game search (related_game),
wired to the games search API and present regardless of which games are
selected — not the old parent-purchase dropdown filtered by chosen games."""
page = authenticated_page
page.goto(f"{live_server.url}{reverse('games:add_purchase')}")
related = page.locator('[data-search-select][data-name="related_game"]')
expect(related).to_have_count(1)
expect(related).to_have_attribute("data-search-url", "/api/games/search")