Improve how editions and purchases are displayed
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
8982fc5086
commit
e158bc0623
|
@ -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
|
## 1.5.1 / 2023-11-14 21:10+01:00
|
||||||
|
|
||||||
## Improved
|
## Improved
|
||||||
|
|
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -124,7 +124,12 @@ class Purchase(models.Model):
|
||||||
max_length=255, default="Unknown Name", null=True, blank=True
|
max_length=255, default="Unknown Name", null=True, blank=True
|
||||||
)
|
)
|
||||||
related_purchase = models.ForeignKey(
|
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):
|
def __str__(self):
|
||||||
|
|
|
@ -1429,6 +1429,10 @@ th label {
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sm\:pl-12 {
|
||||||
|
padding-left: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
.sm\:pl-2 {
|
.sm\:pl-2 {
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,51 +13,47 @@
|
||||||
{% include 'components/edit_button.html' with edit_url=edit_url %}
|
{% include 'components/edit_button.html' with edit_url=edit_url %}
|
||||||
</h1>
|
</h1>
|
||||||
<h2 class="text-lg my-2 ml-2">
|
<h2 class="text-lg my-2 ml-2">
|
||||||
{{ total_hours }} <span class="dark:text-slate-500">total</span>
|
{{ hours_sum }} <span class="dark:text-slate-500">total</span>
|
||||||
{{ session_average }} <span class="dark:text-slate-500">avg</span>
|
{{ session_average }} <span class="dark:text-slate-500">avg</span>
|
||||||
({{ playrange }}) </h2>
|
({{ playrange }}) </h2>
|
||||||
<hr class="border-slate-500">
|
<hr class="border-slate-500">
|
||||||
<h1 class="text-3xl mt-4 mb-1">Editions <span class="dark:text-slate-500">({{ editions.count }})</span></h1>
|
<h1 class="text-3xl mt-4 mb-1">Editions <span class="dark:text-slate-500">({{ edition_count }})</span> and Purchases <span class="dark:text-slate-500">({{ purchase_count }})</span></h1>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{% for edition in editions %}
|
{% for edition in editions %}
|
||||||
<li class="sm:pl-2 flex items-center">
|
<li class="sm:pl-2 flex items-center">
|
||||||
{{ edition.name }} ({{ edition.platform }}, {{ edition.year_released }})
|
{{ edition.name }} ({{ edition.platform }}, {{ edition.year_released }})
|
||||||
{% if edition.wikidata %}
|
{% if edition.wikidata %}
|
||||||
<span class="hidden sm:inline">
|
<span class="hidden sm:inline">
|
||||||
<a href="https://www.wikidata.org/wiki/{{ edition.wikidata }}">
|
<a href="https://www.wikidata.org/wiki/{{ edition.wikidata }}">
|
||||||
<img class="inline mx-2 w-6" src="{% static 'icons/wikidata.png' %}"/>
|
<img class="inline mx-2 w-6" src="{% static 'icons/wikidata.png' %}"/>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% url 'edit_edition' edition.id as edit_url %}
|
{% 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 %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
<ul>
|
||||||
</ul>
|
{% for purchase in edition.game_purchases %}
|
||||||
<h1 class="text-3xl mt-4 mb-1">Purchases <span class="dark:text-slate-500">({{ purchases.count }})</span></h1>
|
<li class="sm:pl-6 flex items-center">
|
||||||
<ul>
|
{{ purchase.get_ownership_type_display }}, {{ purchase.date_purchased | date:"Y" }}, {{ purchase.price }} {{ purchase.price_currency}}
|
||||||
{% for purchase in purchases %}
|
{% url 'edit_purchase' purchase.id as edit_url %}
|
||||||
<li class="sm:pl-2 flex items-center">
|
{% include 'components/edit_button.html' with edit_url=edit_url %}
|
||||||
{{ 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 %}
|
|
||||||
<li>
|
|
||||||
<ul>
|
|
||||||
{% for related_purchase in purchase.related_purchases %}
|
|
||||||
<li class="sm:pl-6 flex items-center">
|
|
||||||
{{ 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 %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
<ul>
|
||||||
</li>
|
{% for related_purchase in purchase.nongame_related_purchases %}
|
||||||
|
<li class="sm:pl-12 flex items-center">
|
||||||
|
{{ 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 %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h1 class="text-3xl mt-4 mb-1 flex gap-2 items-center">
|
<h1 class="text-3xl mt-4 mb-1 flex gap-2 items-center">
|
||||||
Sessions
|
Sessions
|
||||||
<span class="dark:text-slate-500">
|
<span class="dark:text-slate-500">
|
||||||
|
|
|
@ -2,7 +2,7 @@ from common.time import format_duration, now as now_with_tz
|
||||||
from common.utils import safe_division
|
from common.utils import safe_division
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from django.conf import settings
|
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.db.models.functions import TruncDate
|
||||||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
|
@ -136,46 +136,52 @@ def edit_game(request, game_id=None):
|
||||||
|
|
||||||
|
|
||||||
def view_game(request, game_id=None):
|
def view_game(request, game_id=None):
|
||||||
context = {}
|
|
||||||
game = Game.objects.get(id=game_id)
|
game = Game.objects.get(id=game_id)
|
||||||
context["title"] = "View Game"
|
nongame_related_purchases_prefetch = Prefetch(
|
||||||
context["game"] = game
|
"related_purchases",
|
||||||
context["editions"] = Edition.objects.filter(game_id=game_id).order_by(
|
queryset=Purchase.objects.exclude(type=Purchase.GAME),
|
||||||
"year_released"
|
to_attr="nongame_related_purchases",
|
||||||
)
|
)
|
||||||
game_purchases = (
|
game_purchases_prefetch = Prefetch(
|
||||||
Purchase.objects.filter(edition__game_id=game_id)
|
"purchase_set",
|
||||||
.filter(type=Purchase.GAME)
|
queryset=Purchase.objects.filter(type=Purchase.GAME).prefetch_related(
|
||||||
.order_by("date_purchased")
|
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
|
sessions = Session.objects.filter(purchase__edition__game=game)
|
||||||
context["sessions"] = Session.objects.filter(
|
session_count = sessions.count()
|
||||||
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")
|
|
||||||
|
|
||||||
context["playrange"] = (
|
playrange_start = sessions.first().timestamp_start.strftime("%b %Y")
|
||||||
|
playrange_end = sessions.last().timestamp_start.strftime("%b %Y")
|
||||||
|
|
||||||
|
playrange = (
|
||||||
playrange_start
|
playrange_start
|
||||||
if playrange_start == playrange_end
|
if playrange_start == playrange_end
|
||||||
else f"{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
|
request.session["return_path"] = request.path
|
||||||
return render(request, "view_game.html", context)
|
return render(request, "view_game.html", context)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue