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 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 = {
+48 -1
View File
@@ -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"<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)