Remove Edition
Some checks failed
Django CI/CD / test (push) Failing after 54s
Django CI/CD / build-and-push (push) Has been skipped

This commit is contained in:
2025-01-29 22:05:06 +01:00
parent e571feadef
commit 6bd8271291
29 changed files with 286 additions and 467 deletions

View File

@ -1,150 +0,0 @@
from typing import Any
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.urls import reverse
from common.components import (
A,
Button,
Icon,
NameWithIcon,
PopoverTruncated,
)
from common.time import dateformat, local_strftime
from games.forms import EditionForm
from games.models import Edition, Game
@login_required
def list_editions(request: HttpRequest) -> HttpResponse:
context: dict[Any, Any] = {}
page_number = request.GET.get("page", 1)
limit = request.GET.get("limit", 10)
editions = Edition.objects.order_by("-created_at")
page_obj = None
if int(limit) != 0:
paginator = Paginator(editions, limit)
page_obj = paginator.get_page(page_number)
editions = page_obj.object_list
context = {
"title": "Manage editions",
"page_obj": page_obj or None,
"elided_page_range": (
page_obj.paginator.get_elided_page_range(
page_number, on_each_side=1, on_ends=1
)
if page_obj
else None
),
"data": {
"header_action": A([], Button([], "Add edition"), url="add_edition"),
"columns": [
"Game",
"Name",
"Sort Name",
"Year",
"Wikidata",
"Created",
"Actions",
],
"rows": [
[
NameWithIcon(edition_id=edition.pk),
PopoverTruncated(
edition.name
if edition.game.name != edition.name
else "(identical)"
),
PopoverTruncated(
edition.sort_name
if edition.sort_name is not None
and edition.game.name != edition.sort_name
else "(identical)"
),
edition.year_released,
edition.wikidata,
local_strftime(edition.created_at, dateformat),
render_to_string(
"cotton/button_group.html",
{
"buttons": [
{
"href": reverse("edit_edition", args=[edition.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse(
"delete_edition", args=[edition.pk]
),
"slot": Icon("delete"),
"color": "red",
},
]
},
),
]
for edition in editions
],
},
}
return render(request, "list_purchases.html", context)
@login_required
def edit_edition(request: HttpRequest, edition_id: int = 0) -> HttpResponse:
edition = get_object_or_404(Edition, id=edition_id)
form = EditionForm(request.POST or None, instance=edition)
if form.is_valid():
form.save()
return redirect("list_editions")
context: dict[str, Any] = {"form": form, "title": "Edit edition"}
return render(request, "add.html", context)
@login_required
def delete_edition(request: HttpRequest, edition_id: int) -> HttpResponse:
edition = get_object_or_404(Edition, id=edition_id)
edition.delete()
return redirect("list_editions")
@login_required
def add_edition(request: HttpRequest, game_id: int = 0) -> HttpResponse:
context: dict[str, Any] = {}
if request.method == "POST":
form = EditionForm(request.POST or None)
if form.is_valid():
edition = form.save()
if "submit_and_redirect" in request.POST:
return HttpResponseRedirect(
reverse(
"add_purchase_for_edition", kwargs={"edition_id": edition.id}
)
)
else:
return redirect("index")
else:
if game_id:
game = get_object_or_404(Game, id=game_id)
form = EditionForm(
initial={
"game": game,
"name": game.name,
"sort_name": game.sort_name,
"year_released": game.year_released,
}
)
else:
form = EditionForm()
context["form"] = form
context["title"] = "Add New Edition"
context["script_name"] = "add_edition.js"
return render(request, "add_edition.html", context)

View File

@ -29,7 +29,7 @@ from common.time import (
)
from common.utils import safe_division, truncate
from games.forms import GameForm
from games.models import Edition, Game, Purchase, Session
from games.models import Game, Purchase
from games.views.general import use_custom_redirect
@ -109,7 +109,7 @@ def add_game(request: HttpRequest) -> HttpResponse:
game = form.save()
if "submit_and_redirect" in request.POST:
return HttpResponseRedirect(
reverse("add_edition_for_game", kwargs={"game_id": game.id})
reverse("add_purchase_for_game", kwargs={"game_id": game.id})
)
else:
return redirect("list_games")
@ -158,21 +158,12 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
),
to_attr="game_purchases",
)
editions = (
Edition.objects.filter(game=game)
.prefetch_related(game_purchases_prefetch)
.order_by("year_released")
)
purchases = Purchase.objects.filter(editions__game=game).order_by("date_purchased")
purchases = game.purchases.order_by("date_purchased")
sessions = Session.objects.prefetch_related("device").filter(
purchase__editions__game=game
)
sessions = game.sessions
session_count = sessions.count()
session_count_without_manual = (
Session.objects.without_manual().filter(purchase__editions__game=game).count()
)
session_count_without_manual = game.sessions.without_manual().count()
if sessions:
playrange_start = local_strftime(sessions.earliest().timestamp_start, "%b %Y")
@ -193,38 +184,6 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
format_duration(sessions.calculated_duration_unformatted(), "%2.1H")
)
edition_data: dict[str, Any] = {
"columns": [
"Name",
"Year Released",
"Actions",
],
"rows": [
[
NameWithIcon(edition_id=edition.pk),
edition.year_released,
render_to_string(
"cotton/button_group.html",
{
"buttons": [
{
"href": reverse("edit_edition", args=[edition.pk]),
"slot": Icon("edit"),
"color": "gray",
},
{
"href": reverse("delete_edition", args=[edition.pk]),
"slot": Icon("delete"),
"color": "red",
},
]
},
),
]
for edition in editions
],
}
purchase_data: dict[str, Any] = {
"columns": ["Name", "Type", "Date", "Price", "Actions"],
"rows": [
@ -255,9 +214,8 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
],
}
sessions_all = Session.objects.filter(purchase__editions__game=game).order_by(
"-timestamp_start"
)
sessions_all = game.sessions.order_by("-timestamp_start")
last_session = None
if sessions_all.exists():
last_session = sessions_all.latest()
@ -284,7 +242,7 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
args=[last_session.pk],
),
children=Popover(
popover_content=last_session.purchase.first_edition.name,
popover_content=last_session.game.name,
children=[
Button(
icon=True,
@ -292,9 +250,7 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
size="xs",
children=[
Icon("play"),
truncate(
f"{last_session.purchase.first_edition.name}"
),
truncate(f"{last_session.game.name}"),
],
)
],
@ -304,7 +260,7 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
else "",
],
),
"columns": ["Edition", "Date", "Duration", "Actions"],
"columns": ["Game", "Date", "Duration", "Actions"],
"rows": [
[
NameWithIcon(
@ -354,11 +310,9 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
}
context: dict[str, Any] = {
"edition_count": editions.count(),
"editions": editions,
"game": game,
"playrange": playrange,
"purchase_count": Purchase.objects.filter(editions__game=game).count(),
"purchase_count": game.purchases.count(),
"session_average_without_manual": round(
safe_division(
total_hours_without_manual, int(session_count_without_manual)
@ -369,7 +323,6 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
"sessions": sessions,
"title": f"Game Overview - {game.name}",
"hours_sum": total_hours,
"edition_data": edition_data,
"purchase_data": purchase_data,
"session_data": session_data,
"session_page_obj": session_page_obj,

View File

@ -11,13 +11,12 @@ from django.urls import reverse
from common.time import available_stats_year_range, dateformat, format_duration
from common.utils import safe_division
from games.models import Edition, Game, Platform, Purchase, Session
from games.models import Game, Platform, Purchase, Session
def model_counts(request: HttpRequest) -> dict[str, bool]:
return {
"game_available": Game.objects.exists(),
"edition_available": Edition.objects.exists(),
"platform_available": Platform.objects.exists(),
"purchase_available": Purchase.objects.exists(),
"session_count": Session.objects.exists(),
@ -49,9 +48,7 @@ def use_custom_redirect(
@login_required
def stats_alltime(request: HttpRequest) -> HttpResponse:
year = "Alltime"
this_year_sessions = Session.objects.all().prefetch_related(
Prefetch("purchase__editions")
)
this_year_sessions = Session.objects.all().prefetch_related(Prefetch("game"))
this_year_sessions_with_durations = this_year_sessions.annotate(
duration=ExpressionWrapper(
F("timestamp_end") - F("timestamp_start"),
@ -59,11 +56,9 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
)
)
longest_session = this_year_sessions_with_durations.order_by("-duration").first()
this_year_games = Game.objects.filter(
editions__purchase__session__in=this_year_sessions
).distinct()
this_year_games = Game.objects.filter(sessions__in=this_year_sessions).distinct()
this_year_games_with_session_counts = this_year_games.annotate(
session_count=Count("editions__purchase__session"),
session_count=Count("sessions"),
)
game_highest_session_count = this_year_games_with_session_counts.order_by(
"-session_count"
@ -76,11 +71,11 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
.aggregate(dates=Count("date"))
)
this_year_played_purchases = Purchase.objects.filter(
session__in=this_year_sessions
games__sessions__in=this_year_sessions
).distinct()
this_year_purchases = Purchase.objects.all()
this_year_purchases_with_currency = this_year_purchases.select_related("editions")
this_year_purchases_with_currency = this_year_purchases.select_related("games")
this_year_purchases_without_refunded = this_year_purchases_with_currency.filter(
date_refunded=None
)
@ -129,11 +124,10 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
total_spent = this_year_spendings["total_spent"] or 0
games_with_playtime = (
Game.objects.filter(editions__purchase__session__in=this_year_sessions)
Game.objects.filter(sessions__in=this_year_sessions)
.annotate(
total_playtime=Sum(
F("editions__purchase__session__duration_calculated")
+ F("editions__purchase__session__duration_manual")
F("sessions__duration_calculated") + F("sessions__duration_manual")
)
)
.values("id", "name", "total_playtime")
@ -148,10 +142,8 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
month["playtime"] = format_duration(month["playtime"], "%2.0H")
highest_session_average_game = (
Game.objects.filter(editions__purchase__session__in=this_year_sessions)
.annotate(
session_average=Avg("editions__purchase__session__duration_calculated")
)
Game.objects.filter(sessions__in=this_year_sessions)
.annotate(session_average=Avg("sessions__duration_calculated"))
.order_by("-session_average")
.first()
)
@ -160,9 +152,9 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
game["formatted_playtime"] = format_duration(game["total_playtime"], "%2.0H")
total_playtime_per_platform = (
this_year_sessions.values("purchase__platform__name")
this_year_sessions.values("game__platform__name")
.annotate(total_playtime=Sum(F("duration_calculated") + F("duration_manual")))
.annotate(platform_name=F("purchase__platform__name"))
.annotate(platform_name=F("game__platform__name"))
.values("platform_name", "total_playtime")
.order_by("-total_playtime")
)
@ -177,10 +169,10 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
last_play_date = "N/A"
if this_year_sessions:
first_session = this_year_sessions.earliest()
first_play_game = first_session.purchase.first_edition.game
first_play_game = first_session.game
first_play_date = first_session.timestamp_start.strftime(dateformat)
last_session = this_year_sessions.latest()
last_play_game = last_session.purchase.first_edition.game
last_play_game = last_session.game
last_play_date = last_session.timestamp_start.strftime(dateformat)
all_purchased_this_year_count = this_year_purchases_with_currency.count()
@ -228,9 +220,7 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
if longest_session
else 0
),
"longest_session_game": (
longest_session.purchase.first_edition.game if longest_session else None
),
"longest_session_game": (longest_session.game if longest_session else None),
"highest_session_count": (
game_highest_session_count.session_count
if game_highest_session_count
@ -268,7 +258,7 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
return HttpResponseRedirect(reverse("stats_alltime"))
this_year_sessions = Session.objects.filter(
timestamp_start__year=year
).prefetch_related("purchase__editions")
).prefetch_related("game")
this_year_sessions_with_durations = this_year_sessions.annotate(
duration=ExpressionWrapper(
F("timestamp_end") - F("timestamp_start"),
@ -276,13 +266,11 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
)
)
longest_session = this_year_sessions_with_durations.order_by("-duration").first()
this_year_games = Game.objects.filter(
edition__purchases__session__in=this_year_sessions
).distinct()
this_year_games = Game.objects.filter(sessions__in=this_year_sessions).distinct()
this_year_games_with_session_counts = this_year_games.annotate(
session_count=Count(
"edition__purchases__session",
filter=Q(edition__purchases__session__timestamp_start__year=year),
"sessions",
filter=Q(sessions__timestamp_start__year=year),
)
)
game_highest_session_count = this_year_games_with_session_counts.order_by(
@ -296,11 +284,11 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
.aggregate(dates=Count("date"))
)
this_year_played_purchases = Purchase.objects.filter(
session__in=this_year_sessions
games__sessions__in=this_year_sessions
).distinct()
this_year_purchases = Purchase.objects.filter(date_purchased__year=year)
this_year_purchases_with_currency = this_year_purchases.prefetch_related("editions")
this_year_purchases_with_currency = this_year_purchases.prefetch_related("games")
this_year_purchases_without_refunded = this_year_purchases_with_currency.filter(
date_refunded=None
).exclude(ownership_type=Purchase.DEMO)
@ -337,7 +325,7 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
purchases_finished_this_year = Purchase.objects.filter(date_finished__year=year)
purchases_finished_this_year_released_this_year = (
purchases_finished_this_year.filter(editions__year_released=year).order_by(
purchases_finished_this_year.filter(games__year_released=year).order_by(
"date_finished"
)
)
@ -351,11 +339,10 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
total_spent = this_year_spendings["total_spent"] or 0
games_with_playtime = (
Game.objects.filter(edition__purchases__session__in=this_year_sessions)
Game.objects.filter(sessions__in=this_year_sessions)
.annotate(
total_playtime=Sum(
F("edition__purchases__session__duration_calculated")
+ F("edition__purchases__session__duration_manual")
F("sessions__duration_calculated") + F("sessions__duration_manual")
)
)
.values("id", "name", "total_playtime")
@ -370,10 +357,8 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
month["playtime"] = format_duration(month["playtime"], "%2.0H")
highest_session_average_game = (
Game.objects.filter(edition__purchases__session__in=this_year_sessions)
.annotate(
session_average=Avg("edition__purchases__session__duration_calculated")
)
Game.objects.filter(sessions__in=this_year_sessions)
.annotate(session_average=Avg("sessions__duration_calculated"))
.order_by("-session_average")
.first()
)
@ -382,9 +367,9 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
game["formatted_playtime"] = format_duration(game["total_playtime"], "%2.0H")
total_playtime_per_platform = (
this_year_sessions.values("purchase__platform__name")
this_year_sessions.values("game__platform__name")
.annotate(total_playtime=Sum(F("duration_calculated") + F("duration_manual")))
.annotate(platform_name=F("purchase__platform__name"))
.annotate(platform_name=F("game__platform__name"))
.values("platform_name", "total_playtime")
.order_by("-total_playtime")
)
@ -403,10 +388,10 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
last_play_game = None
if this_year_sessions:
first_session = this_year_sessions.earliest()
first_play_game = first_session.purchase.first_edition.game
first_play_game = first_session.game
first_play_date = first_session.timestamp_start.strftime(dateformat)
last_session = this_year_sessions.latest()
last_play_game = last_session.purchase.first_edition.game
last_play_game = last_session.game
last_play_date = last_session.timestamp_start.strftime(dateformat)
all_purchased_this_year_count = this_year_purchases_with_currency.count()
@ -423,7 +408,7 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
),
"total_games": this_year_played_purchases.count(),
"total_2023_games": this_year_played_purchases.filter(
editions__year_released=year
games__year_released=year
).count(),
"top_10_games_by_playtime": top_10_games_by_playtime,
"year": year,
@ -435,15 +420,15 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
safe_division(total_spent, this_year_purchases_without_refunded_count)
),
"all_finished_this_year": purchases_finished_this_year.prefetch_related(
"editions"
"games"
).order_by("date_finished"),
"all_finished_this_year_count": purchases_finished_this_year.count(),
"this_year_finished_this_year": purchases_finished_this_year_released_this_year.prefetch_related(
"editions"
"games"
).order_by("date_finished"),
"this_year_finished_this_year_count": purchases_finished_this_year_released_this_year.count(),
"purchased_this_year_finished_this_year": purchased_this_year_finished_this_year.prefetch_related(
"editions"
"games"
).order_by("date_finished"),
"total_sessions": this_year_sessions.count(),
"unique_days": unique_days["dates"],
@ -472,9 +457,7 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
if longest_session
else 0
),
"longest_session_game": (
longest_session.purchase.first_edition.game if longest_session else None
),
"longest_session_game": (longest_session.game if longest_session else None),
"highest_session_count": (
game_highest_session_count.session_count
if game_highest_session_count

View File

@ -16,7 +16,7 @@ from django.utils import timezone
from common.components import A, Button, Icon, LinkedPurchase, PurchasePrice
from common.time import dateformat
from games.forms import PurchaseForm
from games.models import Edition, Purchase
from games.models import Game, Purchase
from games.views.general import use_custom_redirect
@ -138,7 +138,7 @@ def list_purchases(request: HttpRequest) -> HttpResponse:
@login_required
def add_purchase(request: HttpRequest, edition_id: int = 0) -> HttpResponse:
def add_purchase(request: HttpRequest, game_id: int = 0) -> HttpResponse:
context: dict[str, Any] = {}
initial = {"date_purchased": timezone.now()}
@ -149,19 +149,20 @@ def add_purchase(request: HttpRequest, edition_id: int = 0) -> HttpResponse:
if "submit_and_redirect" in request.POST:
return HttpResponseRedirect(
reverse(
"add_session_for_purchase", kwargs={"purchase_id": purchase.id}
"add_session_for_game",
kwargs={"game_id": purchase.first_game.id},
)
)
else:
return redirect("list_purchases")
else:
if edition_id:
edition = Edition.objects.get(id=edition_id)
if game_id:
game = Game.objects.get(id=game_id)
form = PurchaseForm(
initial={
**initial,
"edition": edition,
"platform": edition.platform,
"games": [game],
"platform": game.platform,
}
)
else:
@ -226,12 +227,14 @@ def finish_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
return redirect("list_purchases")
def related_purchase_by_edition(request: HttpRequest) -> HttpResponse:
edition_id = request.GET.get("edition")
if not edition_id:
return HttpResponseBadRequest("Invalid edition_id")
def related_purchase_by_game(request: HttpRequest) -> HttpResponse:
games = request.GET.getlist("games")
if not games:
return HttpResponseBadRequest("Invalid game_id")
if isinstance(games, int) or isinstance(games, str):
games = [games]
form = PurchaseForm()
form.fields["related_purchase"].queryset = Purchase.objects.filter(
edition_id=edition_id, type=Purchase.GAME
).order_by("edition__sort_name")
games__in=games, type=Purchase.GAME
).order_by("games__sort_name")
return render(request, "partials/related_purchase_field.html", {"form": form})

View File

@ -28,7 +28,7 @@ from common.time import (
)
from common.utils import truncate
from games.forms import SessionForm
from games.models import Purchase, Session
from games.models import Game, Session
from games.views.general import use_custom_redirect
@ -37,13 +37,13 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse
context: dict[Any, Any] = {}
page_number = request.GET.get("page", 1)
limit = request.GET.get("limit", 10)
sessions = Session.objects.order_by("-timestamp_start")
sessions = Session.objects.order_by("-timestamp_start", "created_at")
search_string = request.GET.get("search_string", search_string)
if search_string != "":
sessions = sessions.filter(
Q(purchase__edition__name__icontains=search_string)
| Q(purchase__edition__game__name__icontains=search_string)
| Q(purchase__platform__name__icontains=search_string)
Q(game__name__icontains=search_string)
| Q(game__name__icontains=search_string)
| Q(game__platform__name__icontains=search_string)
| Q(device__name__icontains=search_string)
| Q(device__type__icontains=search_string)
)
@ -97,7 +97,7 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse
args=[last_session.pk],
),
children=Popover(
popover_content=last_session.purchase.first_edition.name,
popover_content=last_session.game.name,
children=[
Button(
icon=True,
@ -105,9 +105,7 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse
size="xs",
children=[
Icon("play"),
truncate(
f"{last_session.purchase.first_edition.name}"
),
truncate(f"{last_session.game.name}"),
],
)
],
@ -191,13 +189,13 @@ def search_sessions(request: HttpRequest) -> HttpResponse:
@login_required
def add_session(request: HttpRequest, purchase_id: int = 0) -> HttpResponse:
def add_session(request: HttpRequest, game_id: int = 0) -> HttpResponse:
context = {}
initial: dict[str, Any] = {"timestamp_start": timezone.now()}
last = Session.objects.last()
if last != None:
initial["purchase"] = last.purchase
initial["game"] = last.game
if request.method == "POST":
form = SessionForm(request.POST or None, initial=initial)
@ -205,12 +203,12 @@ def add_session(request: HttpRequest, purchase_id: int = 0) -> HttpResponse:
form.save()
return redirect("list_sessions")
else:
if purchase_id:
purchase = Purchase.objects.get(id=purchase_id)
if game_id:
game = Game.objects.get(id=game_id)
form = SessionForm(
initial={
**initial,
"purchase": purchase,
"game": game,
}
)
else: