feat(purchases): honor ?sort= on list_purchases + eager-load games (#68)

This commit is contained in:
2026-06-21 14:11:45 +02:00
parent 6c704c40a3
commit 939d3a9e33
2 changed files with 60 additions and 2 deletions
+12 -1
View File
@@ -44,6 +44,12 @@ from common.time import dateformat
from common.utils import paginate from common.utils import paginate
from games.forms import PurchaseForm from games.forms import PurchaseForm
from games.models import Game, Purchase 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 from games.views.general import use_custom_redirect
@@ -119,7 +125,7 @@ def _render_purchase_row(purchase):
@login_required @login_required
def list_purchases(request: HttpRequest) -> HttpResponse: 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", "") filter_json = request.GET.get("filter", "")
if filter_json: if filter_json:
@@ -129,6 +135,11 @@ def list_purchases(request: HttpRequest) -> HttpResponse:
if pf is not None: if pf is not None:
purchases = purchases.filter(pf.to_q()) 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) purchases, page_obj, elided_page_range = paginate(request, purchases)
data = { data = {
+48 -1
View File
@@ -10,7 +10,9 @@ from django.test import RequestFactory
from django.urls import reverse from django.urls import reverse
from games.filters import FindFilter 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 ( from games.sorting import (
GAME_DEFAULT_SORT, GAME_DEFAULT_SORT,
GAME_SORTS, GAME_SORTS,
@@ -201,3 +203,48 @@ class TestListSessionsSort:
response = logged_client.get(reverse("games:list_sessions"), {"sort": "nope"}) response = logged_client.get(reverse("games:list_sessions"), {"sort": "nope"})
warnings = [str(m) for m in get_messages(response.wsgi_request)] warnings = [str(m) for m in get_messages(response.wsgi_request)]
assert any("nope" in w for w in warnings) 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"<tbody[^>]*>(.*?)</tbody>", 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)