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 }})
+
{% for edition in editions %}
-
{{ edition.name }} ({{ edition.platform }}, {{ edition.year_released }})
{% if edition.wikidata %}
-
-
-
-
-
+
+
+
+
+
{% endif %}
{% url 'edit_edition' edition.id as edit_url %}
- {% include 'components/edit_button.html' with edit_url=edit_url %}
+ {% include 'components/edit_button.html' with edit_url=edit_url %}
- {% endfor %}
-
- Purchases ({{ purchases.count }})
-
- {% for purchase in purchases %}
- -
- {{ purchase.platform }}
- ({{ purchase.get_ownership_type_display }}, {{ purchase.date_purchased | date:"Y" }}, {{ purchase.price }} {{ purchase.price_currency}})
- {% url 'edit_purchase' purchase.id as edit_url %}
- {% include 'components/edit_button.html' with edit_url=edit_url %}
- {% if purchase.related_purchases %}
-
-
-
- {% for related_purchase in purchase.related_purchases %}
- -
- {{ related_purchase.name}} ({{ related_purchase.get_type_display }}, {{ related_purchase.date_purchased | date:"Y" }}, {{ related_purchase.price }} {{ related_purchase.price_currency}})
- {% url 'edit_purchase' related_purchase.id as edit_url %}
- {% include 'components/edit_button.html' with edit_url=edit_url %}
-
- {% endfor %}
-
+
+ {% for purchase in edition.game_purchases %}
+ -
+ {{ purchase.get_ownership_type_display }}, {{ purchase.date_purchased | date:"Y" }}, {{ purchase.price }} {{ purchase.price_currency}}
+ {% url 'edit_purchase' purchase.id as edit_url %}
+ {% include 'components/edit_button.html' with edit_url=edit_url %}
- {% endif %}
-
+
+ {% for related_purchase in purchase.nongame_related_purchases %}
+ -
+ {{ related_purchase.name }} ({{ related_purchase.get_type_display }}, {{ purchase.platform }}, {{ related_purchase.date_purchased | date:"Y" }}, {{ related_purchase.price }} {{ related_purchase.price_currency}})
+ {% url 'edit_purchase' related_purchase.id as edit_url %}
+ {% include 'components/edit_button.html' with edit_url=edit_url %}
+
+ {% endfor %}
+
+ {% endfor %}
+
{% endfor %}
+
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)