From 1f9b3d5682d0948a5580591ee103cb283671706c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Tue, 14 Nov 2023 23:45:41 +0100 Subject: [PATCH] Improve how editions and purchases are displayed --- CHANGELOG.md | 5 ++ .../0029_alter_purchase_related_purchase.py | 26 +++++++ games/models.py | 7 +- games/static/base.css | 4 ++ games/templates/view_game.html | 58 ++++++++-------- games/views.py | 68 ++++++++++--------- 6 files changed, 105 insertions(+), 63 deletions(-) create mode 100644 games/migrations/0029_alter_purchase_related_purchase.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f2f26f8..43b2d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## Unreleased + +## Improved +* game overview: improve how editions and purchases are displayed + ## 1.5.1 / 2023-11-14 21:10+01:00 ## Improved diff --git a/games/migrations/0029_alter_purchase_related_purchase.py b/games/migrations/0029_alter_purchase_related_purchase.py new file mode 100644 index 0000000..f98e69a --- /dev/null +++ b/games/migrations/0029_alter_purchase_related_purchase.py @@ -0,0 +1,26 @@ +# Generated by Django 4.1.5 on 2023-11-14 21:19 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("games", "0028_purchase_name"), + ] + + operations = [ + migrations.AlterField( + model_name="purchase", + name="related_purchase", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="related_purchases", + to="games.purchase", + ), + ), + ] diff --git a/games/models.py b/games/models.py index 223c3f6..547a745 100644 --- a/games/models.py +++ b/games/models.py @@ -124,7 +124,12 @@ class Purchase(models.Model): max_length=255, default="Unknown Name", null=True, blank=True ) related_purchase = models.ForeignKey( - "Purchase", on_delete=models.SET_NULL, default=None, null=True, blank=True + "Purchase", + on_delete=models.SET_NULL, + default=None, + null=True, + blank=True, + related_name="related_purchases", ) def __str__(self): diff --git a/games/static/base.css b/games/static/base.css index 76ccf3e..0dfa762 100644 --- a/games/static/base.css +++ b/games/static/base.css @@ -1429,6 +1429,10 @@ th label { padding-right: 1rem; } + .sm\:pl-12 { + padding-left: 3rem; + } + .sm\:pl-2 { padding-left: 0.5rem; } diff --git a/games/templates/view_game.html b/games/templates/view_game.html index ecec827..ca9d6c3 100644 --- a/games/templates/view_game.html +++ b/games/templates/view_game.html @@ -13,51 +13,47 @@ {% include 'components/edit_button.html' with edit_url=edit_url %}

- {{ total_hours }} total + {{ hours_sum }} total {{ session_average }} avg ({{ playrange }})


-

Editions ({{ editions.count }})

+

Editions ({{ edition_count }}) and Purchases ({{ purchase_count }})

+ -

Purchases ({{ purchases.count }})

- +

Sessions diff --git a/games/views.py b/games/views.py index b614953..8046ff5 100644 --- a/games/views.py +++ b/games/views.py @@ -2,7 +2,7 @@ from common.time import format_duration, now as now_with_tz from common.utils import safe_division from datetime import datetime, timedelta from django.conf import settings -from django.db.models import Sum, F, Count +from django.db.models import Sum, F, Count, Prefetch from django.db.models.functions import TruncDate from django.http import HttpRequest, HttpResponse, HttpResponseRedirect from django.shortcuts import redirect, render @@ -136,46 +136,52 @@ def edit_game(request, game_id=None): def view_game(request, game_id=None): - context = {} game = Game.objects.get(id=game_id) - context["title"] = "View Game" - context["game"] = game - context["editions"] = Edition.objects.filter(game_id=game_id).order_by( - "year_released" + nongame_related_purchases_prefetch = Prefetch( + "related_purchases", + queryset=Purchase.objects.exclude(type=Purchase.GAME), + to_attr="nongame_related_purchases", ) - game_purchases = ( - Purchase.objects.filter(edition__game_id=game_id) - .filter(type=Purchase.GAME) - .order_by("date_purchased") + game_purchases_prefetch = Prefetch( + "purchase_set", + queryset=Purchase.objects.filter(type=Purchase.GAME).prefetch_related( + nongame_related_purchases_prefetch + ), + to_attr="game_purchases", + ) + editions = ( + Edition.objects.filter(game=game) + .prefetch_related(game_purchases_prefetch) + .order_by("year_released") ) - for purchase in game_purchases: - purchase.related_purchases = Purchase.objects.exclude( - type=Purchase.GAME - ).filter(related_purchase=purchase.id) - context["purchases"] = game_purchases - context["sessions"] = Session.objects.filter( - purchase__edition__game_id=game_id - ).order_by("-timestamp_start") - context["total_hours"] = float( - format_duration(context["sessions"].total_duration_unformatted(), "%2.1H") - ) - context["session_average"] = round( - (context["total_hours"]) / int(context["sessions"].count()), 1 - ) - # here first and last is flipped - # because sessions are ordered from newest to oldest - # so the most recent are on top - playrange_start = context["sessions"].last().timestamp_start.strftime("%b %Y") - playrange_end = context["sessions"].first().timestamp_start.strftime("%b %Y") + sessions = Session.objects.filter(purchase__edition__game=game) + session_count = sessions.count() - context["playrange"] = ( + playrange_start = sessions.first().timestamp_start.strftime("%b %Y") + playrange_end = sessions.last().timestamp_start.strftime("%b %Y") + + playrange = ( playrange_start if playrange_start == playrange_end else f"{playrange_start} — {playrange_end}" ) + total_hours = float(format_duration(sessions.total_duration_unformatted(), "%2.1H")) + + context = { + "edition_count": editions.count(), + "editions": editions, + "game": game, + "playrange": playrange, + "purchase_count": Purchase.objects.filter(edition__game=game).count(), + "session_average": round(total_hours / int(session_count), 1), + "session_count": session_count, + "sessions_with_notes": sessions.exclude(note=""), + "sessions": sessions.order_by("-timestamp_start"), + "title": f"Game Overview - {game.name}", + "hours_sum": total_hours, + } - context["sessions_with_notes"] = context["sessions"].exclude(note="") request.session["return_path"] = request.path return render(request, "view_game.html", context)