From 939d3a9e33b471b22296ee86b5debacbdc300a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Sun, 21 Jun 2026 14:11:45 +0200 Subject: [PATCH] feat(purchases): honor ?sort= on list_purchases + eager-load games (#68) --- games/views/purchase.py | 13 ++++++++++- tests/test_sorting.py | 49 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/games/views/purchase.py b/games/views/purchase.py index 3600eb1..f72fa5b 100644 --- a/games/views/purchase.py +++ b/games/views/purchase.py @@ -44,6 +44,12 @@ from common.time import dateformat from common.utils import paginate from games.forms import PurchaseForm from games.models import Game, Purchase +from games.sorting import ( + PURCHASE_DEFAULT_SORT, + PURCHASE_SORTS, + apply_sort, + parse_find_filter, +) from games.views.general import use_custom_redirect @@ -119,7 +125,7 @@ def _render_purchase_row(purchase): @login_required def list_purchases(request: HttpRequest) -> HttpResponse: - purchases = Purchase.objects.order_by("-date_purchased", "-created_at") + purchases = Purchase.objects.prefetch_related("games", "games__platform") filter_json = request.GET.get("filter", "") if filter_json: @@ -129,6 +135,11 @@ def list_purchases(request: HttpRequest) -> HttpResponse: if pf is not None: purchases = purchases.filter(pf.to_q()) + sort = apply_sort(purchases, parse_find_filter(request), PURCHASE_SORTS, PURCHASE_DEFAULT_SORT) + purchases = sort.queryset + for key in sort.unknown: + messages.warning(request, f"Unknown sort field '{key}' was ignored.") + purchases, page_obj, elided_page_range = paginate(request, purchases) data = { diff --git a/tests/test_sorting.py b/tests/test_sorting.py index 57c0317..f21cdc6 100644 --- a/tests/test_sorting.py +++ b/tests/test_sorting.py @@ -10,7 +10,9 @@ from django.test import RequestFactory from django.urls import reverse from games.filters import FindFilter -from games.models import Game, Platform, Session +import re + +from games.models import Game, Platform, Purchase, Session from games.sorting import ( GAME_DEFAULT_SORT, GAME_SORTS, @@ -201,3 +203,48 @@ class TestListSessionsSort: response = logged_client.get(reverse("games:list_sessions"), {"sort": "nope"}) warnings = [str(m) for m in get_messages(response.wsgi_request)] assert any("nope" in w for w in warnings) + + +class TestListPurchasesSort: + @pytest.fixture + def two_purchases(self, db, two_games): + alpha, beta = two_games + # cheap (Alpha, price=10) purchased LATER so default -purchased order would show Alpha first + # dear (Beta, price=90) purchased EARLIER — so -price must override default order to pass + dear = Purchase.objects.create( + date_purchased=datetime(2022, 1, 1, tzinfo=ZONEINFO), + price=90, + converted_price=90, + platform=beta.platform, + ) + dear.games.add(beta) + cheap = Purchase.objects.create( + date_purchased=datetime(2022, 1, 2, tzinfo=ZONEINFO), + price=10, + converted_price=10, + platform=alpha.platform, + ) + cheap.games.add(alpha) + return cheap, dear + + def test_sort_by_price_descending(self, logged_client, two_purchases): + # default -purchased puts Alpha (later date) first; -price must override to show Beta (90) first + response = logged_client.get(reverse("games:list_purchases"), {"sort": "-price"}) + assert response.status_code == 200 + body = response.content.decode() + tbody = re.search(r"]*>(.*?)", body, re.DOTALL).group(1) + assert tbody.index("Beta") < tbody.index("Alpha") # 90 before 10 + + def test_name_aggregate_sort_no_duplicate_rows(self, logged_client, two_purchases): + # a multi-game purchase must still render exactly one row + cheap, _ = two_purchases + extra = Game.objects.create(name="Aaa", sort_name="Aaa", platform=cheap.platform) + cheap.games.add(extra) + response = logged_client.get(reverse("games:list_purchases"), {"sort": "name"}) + body = response.content.decode() + assert body.count("purchase-row-") == 2 # exactly two purchase rows + + def test_unknown_sort_emits_warning(self, logged_client, two_purchases): + response = logged_client.get(reverse("games:list_purchases"), {"sort": "nope"}) + warnings = [str(m) for m in get_messages(response.wsgi_request)] + assert any("nope" in w for w in warnings)