diff --git a/games/deviceviews.py b/games/deviceviews.py index 6a68933..db40913 100644 --- a/games/deviceviews.py +++ b/games/deviceviews.py @@ -87,3 +87,16 @@ def delete_device(request: HttpRequest, device_id: int) -> HttpResponse: device = get_object_or_404(Device, id=device_id) device.delete() return redirect("list_sessions") + + +@login_required +def add_device(request: HttpRequest) -> HttpResponse: + context: dict[str, Any] = {} + form = DeviceForm(request.POST or None) + if form.is_valid(): + form.save() + return redirect("index") + + context["form"] = form + context["title"] = "Add New Device" + return render(request, "add.html", context) diff --git a/games/editionviews.py b/games/editionviews.py index ad1769f..d69be5a 100644 --- a/games/editionviews.py +++ b/games/editionviews.py @@ -2,14 +2,14 @@ from typing import Any from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator -from django.http import HttpRequest, HttpResponse +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.utils import truncate_with_popover from games.forms import EditionForm -from games.models import Edition +from games.models import Edition, Game from games.views import dateformat @@ -91,7 +91,7 @@ def list_editions(request: HttpRequest) -> HttpResponse: @login_required -def edit_device(request: HttpRequest, edition_id: int = 0) -> HttpResponse: +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(): @@ -107,3 +107,38 @@ 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) diff --git a/games/gameviews.py b/games/gameviews.py index 0f0284f..03b590d 100644 --- a/games/gameviews.py +++ b/games/gameviews.py @@ -2,14 +2,16 @@ from typing import Any from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator -from django.http import HttpRequest, HttpResponse -from django.shortcuts import render +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.utils import truncate_with_popover -from games.models import Game -from games.views import dateformat +from common.time import format_duration +from common.utils import safe_division, safe_getattr, truncate_with_popover +from games.forms import GameForm +from games.models import Game, Purchase, Session +from games.views import dateformat, use_custom_redirect @login_required @@ -76,3 +78,116 @@ def list_games(request: HttpRequest) -> HttpResponse: }, } return render(request, "list_purchases.html", context) + + +@login_required +def add_game(request: HttpRequest) -> HttpResponse: + context: dict[str, Any] = {} + form = GameForm(request.POST or None) + if form.is_valid(): + game = form.save() + if "submit_and_redirect" in request.POST: + return HttpResponseRedirect( + reverse("add_edition_for_game", kwargs={"game_id": game.id}) + ) + else: + return redirect("list_games") + + context["form"] = form + context["title"] = "Add New Game" + context["script_name"] = "add_game.js" + return render(request, "add_game.html", context) + + +@login_required +def delete_game(request: HttpRequest, game_id: int) -> HttpResponse: + game = get_object_or_404(Game, id=game_id) + game.delete() + return redirect("list_sessions") + + +@login_required +@use_custom_redirect +def edit_game(request: HttpRequest, game_id: int) -> HttpResponse: + context = {} + purchase = get_object_or_404(Game, id=game_id) + form = GameForm(request.POST or None, instance=purchase) + if form.is_valid(): + form.save() + return redirect("list_sessions") + context["title"] = "Edit Game" + context["form"] = form + return render(request, "add.html", context) + + +@login_required +def view_game(request: HttpRequest, game_id: int) -> HttpResponse: + game = Game.objects.get(id=game_id) + nongame_related_purchases_prefetch: Prefetch[Purchase] = Prefetch( + "related_purchases", + queryset=Purchase.objects.exclude(type=Purchase.GAME).order_by( + "date_purchased" + ), + to_attr="nongame_related_purchases", + ) + game_purchases_prefetch: Prefetch[Purchase] = 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") + ) + + sessions = Session.objects.prefetch_related("device").filter( + purchase__edition__game=game + ) + session_count = sessions.count() + session_count_without_manual = ( + Session.objects.without_manual().filter(purchase__edition__game=game).count() + ) + + if sessions: + playrange_start = sessions.earliest().timestamp_start.strftime("%b %Y") + latest_session = sessions.latest() + playrange_end = latest_session.timestamp_start.strftime("%b %Y") + + playrange = ( + playrange_start + if playrange_start == playrange_end + else f"{playrange_start} — {playrange_end}" + ) + else: + playrange = "N/A" + latest_session = None + + total_hours = float(format_duration(sessions.total_duration_unformatted(), "%2.1H")) + total_hours_without_manual = float( + format_duration(sessions.calculated_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_without_manual": round( + safe_division( + total_hours_without_manual, int(session_count_without_manual) + ), + 1, + ), + "session_count": session_count, + "sessions_with_notes_count": sessions.exclude(note="").count(), + "sessions": sessions.order_by("-timestamp_start"), + "title": f"Game Overview - {game.name}", + "hours_sum": total_hours, + "latest_session_id": safe_getattr(latest_session, "pk"), + } + + request.session["return_path"] = request.path + return render(request, "view_game.html", context) diff --git a/games/platformviews.py b/games/platformviews.py index 6ccc1de..09a1287 100644 --- a/games/platformviews.py +++ b/games/platformviews.py @@ -7,8 +7,9 @@ from django.shortcuts import get_object_or_404, redirect, render from django.template.loader import render_to_string from django.urls import reverse +from games.forms import PlatformForm from games.models import Platform -from games.views import dateformat +from games.views import dateformat, use_custom_redirect @login_required @@ -78,3 +79,30 @@ def delete_platform(request: HttpRequest, platform_id: int) -> HttpResponse: platform = get_object_or_404(Platform, id=platform_id) platform.delete() return redirect("list_platforms") + + +@login_required +@use_custom_redirect +def edit_platform(request: HttpRequest, platform_id: int) -> HttpResponse: + context = {} + platform = get_object_or_404(Platform, id=platform_id) + form = PlatformForm(request.POST or None, instance=platform) + if form.is_valid(): + form.save() + return redirect("list_platforms") + context["title"] = "Edit Platform" + context["form"] = form + return render(request, "add.html", context) + + +@login_required +def add_platform(request: HttpRequest) -> HttpResponse: + context: dict[str, Any] = {} + form = PlatformForm(request.POST or None) + if form.is_valid(): + form.save() + return redirect("index") + + context["form"] = form + context["title"] = "Add New Platform" + return render(request, "add.html", context) diff --git a/games/purchaseviews.py b/games/purchaseviews.py index f516dee..555d61a 100644 --- a/games/purchaseviews.py +++ b/games/purchaseviews.py @@ -2,14 +2,21 @@ from typing import Any from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator -from django.http import HttpRequest, HttpResponse -from django.shortcuts import render +from django.http import ( + HttpRequest, + HttpResponse, + HttpResponseBadRequest, + 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 django.utils import timezone from common.utils import truncate_with_popover -from games.models import Purchase -from games.views import dateformat +from games.forms import PurchaseForm +from games.models import Edition, Purchase +from games.views import dateformat, use_custom_redirect @login_required @@ -98,3 +105,73 @@ def list_purchases(request: HttpRequest) -> HttpResponse: }, } return render(request, "list_purchases.html", context) + + +@login_required +def add_purchase(request: HttpRequest, edition_id: int = 0) -> HttpResponse: + context: dict[str, Any] = {} + initial = {"date_purchased": timezone.now()} + + if request.method == "POST": + form = PurchaseForm(request.POST or None, initial=initial) + if form.is_valid(): + purchase = form.save() + if "submit_and_redirect" in request.POST: + return HttpResponseRedirect( + reverse( + "add_session_for_purchase", kwargs={"purchase_id": purchase.id} + ) + ) + else: + return redirect("list_purchases") + else: + if edition_id: + edition = Edition.objects.get(id=edition_id) + form = PurchaseForm( + initial={ + **initial, + "edition": edition, + "platform": edition.platform, + } + ) + else: + form = PurchaseForm(initial=initial) + + context["form"] = form + context["title"] = "Add New Purchase" + context["script_name"] = "add_purchase.js" + return render(request, "add_purchase.html", context) + + +@login_required +@use_custom_redirect +def edit_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse: + context = {} + purchase = get_object_or_404(Purchase, id=purchase_id) + form = PurchaseForm(request.POST or None, instance=purchase) + if form.is_valid(): + form.save() + return redirect("list_sessions") + context["title"] = "Edit Purchase" + context["form"] = form + context["purchase_id"] = str(purchase_id) + context["script_name"] = "add_purchase.js" + return render(request, "add_purchase.html", context) + + +@login_required +def delete_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse: + purchase = get_object_or_404(Purchase, id=purchase_id) + purchase.delete() + return redirect("list_sessions") + + +def related_purchase_by_edition(request: HttpRequest) -> HttpResponse: + edition_id = request.GET.get("edition") + if not edition_id: + return HttpResponseBadRequest("Invalid edition_id") + form = PurchaseForm() + form.fields["related_purchase"].queryset = Purchase.objects.filter( + edition_id=edition_id, type=Purchase.GAME + ).order_by("edition__sort_name") + return render(request, "partials/related_purchase_field.html", {"form": form}) diff --git a/games/sessionviews.py b/games/sessionviews.py index 283e3bc..135d39f 100644 --- a/games/sessionviews.py +++ b/games/sessionviews.py @@ -3,19 +3,22 @@ from typing import Any from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from django.http import HttpRequest, HttpResponse -from django.shortcuts import render +from django.shortcuts import get_object_or_404, redirect, render from django.template.loader import render_to_string from django.urls import reverse +from django.utils import timezone from common.time import format_duration from common.utils import truncate_with_popover -from games.models import Session +from games.forms import SessionForm +from games.models import Purchase, Session from games.views import ( dateformat, datetimeformat, durationformat, durationformat_manual, timeformat, + use_custom_redirect, ) @@ -91,3 +94,98 @@ def list_sessions(request: HttpRequest) -> HttpResponse: }, } return render(request, "list_purchases.html", context) + + +@login_required +def add_session(request: HttpRequest, purchase_id: int = 0) -> HttpResponse: + context = {} + initial: dict[str, Any] = {"timestamp_start": timezone.now()} + + last = Session.objects.last() + if last != None: + initial["purchase"] = last.purchase + + if request.method == "POST": + form = SessionForm(request.POST or None, initial=initial) + if form.is_valid(): + form.save() + return redirect("list_sessions") + else: + if purchase_id: + purchase = Purchase.objects.get(id=purchase_id) + form = SessionForm( + initial={ + **initial, + "purchase": purchase, + } + ) + else: + form = SessionForm(initial=initial) + + context["title"] = "Add New Session" + context["form"] = form + return render(request, "add_session.html", context) + + +@login_required +@use_custom_redirect +def edit_session(request: HttpRequest, session_id: int) -> HttpResponse: + context = {} + session = get_object_or_404(Session, id=session_id) + form = SessionForm(request.POST or None, instance=session) + if form.is_valid(): + form.save() + return redirect("list_sessions") + context["title"] = "Edit Session" + context["form"] = form + return render(request, "add_session.html", context) + + +def clone_session_by_id(session_id: int) -> Session: + session = get_object_or_404(Session, id=session_id) + clone = session + clone.pk = None + clone.timestamp_start = timezone.now() + clone.timestamp_end = None + clone.note = "" + clone.save() + return clone + + +@login_required +@use_custom_redirect +def new_session_from_existing_session( + request: HttpRequest, session_id: int, template: str = "" +) -> HttpResponse: + session = clone_session_by_id(session_id) + if request.htmx: + context = { + "session": session, + "session_count": int(request.GET.get("session_count", 0)) + 1, + } + return render(request, template, context) + return redirect("list_sessions") + + +@login_required +@use_custom_redirect +def end_session( + request: HttpRequest, session_id: int, template: str = "" +) -> HttpResponse: + session = get_object_or_404(Session, id=session_id) + session.timestamp_end = timezone.now() + session.save() + if request.htmx: + context = { + "session": session, + "session_count": request.GET.get("session_count", 0), + } + return render(request, template, context) + return redirect("list_sessions") + + +@login_required +def delete_session(request: HttpRequest, session_id: int = 0) -> HttpResponse: + session = get_object_or_404(Session, id=session_id) + session.delete() + return redirect("list_sessions") diff --git a/games/urls.py b/games/urls.py index e2b72c6..8a942c3 100644 --- a/games/urls.py +++ b/games/urls.py @@ -12,43 +12,53 @@ from games import ( urlpatterns = [ path("", views.index, name="index"), - path("device/add", views.add_device, name="add_device"), + path("device/add", deviceviews.add_device, name="add_device"), path( "device/delete/", deviceviews.delete_device, name="delete_device" ), path("device/edit/", deviceviews.edit_device, name="edit_device"), path("device/list", deviceviews.list_devices, name="list_devices"), - path("edition/add", views.add_edition, name="add_edition"), + path("edition/add", editionviews.add_edition, name="add_edition"), path( "edition/add/for-game/", - views.add_edition, + editionviews.add_edition, name="add_edition_for_game", ), - path("edition//edit", views.edit_edition, name="edit_edition"), + path( + "edition//edit", editionviews.edit_edition, name="edit_edition" + ), path("edition/list", editionviews.list_editions, name="list_editions"), path( "edition//delete", editionviews.delete_edition, name="delete_edition", ), - path("game/add", views.add_game, name="add_game"), - path("game//edit", views.edit_game, name="edit_game"), - path("game//view", views.view_game, name="view_game"), - path("game//delete", views.delete_game, name="delete_game"), + path("game/add", gameviews.add_game, name="add_game"), + path("game//edit", gameviews.edit_game, name="edit_game"), + path("game//view", gameviews.view_game, name="view_game"), + path("game//delete", gameviews.delete_game, name="delete_game"), path("game/list", gameviews.list_games, name="list_games"), - path("platform/add", views.add_platform, name="add_platform"), - path("platform//edit", views.edit_platform, name="edit_platform"), + path("platform/add", platformviews.add_platform, name="add_platform"), + path( + "platform//edit", + platformviews.edit_platform, + name="edit_platform", + ), path( "platform//delete", platformviews.delete_platform, name="delete_platform", ), path("platform/list", platformviews.list_platforms, name="list_platforms"), - path("purchase/add", views.add_purchase, name="add_purchase"), - path("purchase//edit", views.edit_purchase, name="edit_purchase"), + path("purchase/add", purchaseviews.add_purchase, name="add_purchase"), + path( + "purchase//edit", + purchaseviews.edit_purchase, + name="edit_purchase", + ), path( "purchase//delete", - views.delete_purchase, + purchaseviews.delete_purchase, name="delete_purchase", ), path( @@ -58,78 +68,80 @@ urlpatterns = [ ), path( "purchase/related-purchase-by-edition", - views.related_purchase_by_edition, + purchaseviews.related_purchase_by_edition, name="related_purchase_by_edition", ), path( "purchase/add/for-edition/", - views.add_purchase, + purchaseviews.add_purchase, name="add_purchase_for_edition", ), - path("session/add", views.add_session, name="add_session"), + path("session/add", sessionviews.add_session, name="add_session"), path( "session/add/for-purchase/", - views.add_session, + sessionviews.add_session, name="add_session_for_purchase", ), path( "session/add/from-game/", - views.new_session_from_existing_session, + sessionviews.new_session_from_existing_session, {"template": "view_game.html#session-info"}, name="view_game_start_session_from_session", ), path( "session/add/from-list/", - views.new_session_from_existing_session, + sessionviews.new_session_from_existing_session, {"template": "list_sessions.html#session-row"}, name="list_sessions_start_session_from_session", ), - path("session//edit", views.edit_session, name="edit_session"), + path( + "session//edit", sessionviews.edit_session, name="edit_session" + ), path( "session//delete", - views.delete_session, + sessionviews.delete_session, name="delete_session", ), path( "session/end/from-game/", - views.end_session, + sessionviews.end_session, {"template": "view_game.html#session-info"}, name="view_game_end_session", ), path( "session/end/from-list/", - views.end_session, + sessionviews.end_session, {"template": "list_sessions.html#session-row"}, name="list_sessions_end_session", ), path("session/list", sessionviews.list_sessions, name="list_sessions"), path( "session/list/by-purchase/", - views.list_sessions, + sessionviews.list_sessions, {"filter": "purchase"}, name="list_sessions_by_purchase", ), path( "session/list/by-platform/", - views.list_sessions, + sessionviews.list_sessions, {"filter": "platform"}, name="list_sessions_by_platform", ), path( "session/list/by-game/", - views.list_sessions, + sessionviews.list_sessions, {"filter": "game"}, name="list_sessions_by_game", ), path( "session/list/by-edition/", - views.list_sessions, + sessionviews.list_sessions, {"filter": "edition"}, name="list_sessions_by_edition", ), path( "session/list/by-ownership/", - views.list_sessions, + sessionviews.list_sessions, {"filter": "ownership_type"}, name="list_sessions_by_ownership_type", ), diff --git a/games/views.py b/games/views.py index 8e98740..3a567b2 100644 --- a/games/views.py +++ b/games/views.py @@ -1,30 +1,17 @@ from typing import Any, Callable from django.contrib.auth.decorators import login_required -from django.db.models import Avg, Count, ExpressionWrapper, F, Prefetch, Q, Sum, fields +from django.db.models import Avg, Count, ExpressionWrapper, F, Q, Sum, fields from django.db.models.functions import TruncDate, TruncMonth from django.db.models.manager import BaseManager -from django.http import ( - HttpRequest, - HttpResponse, - HttpResponseBadRequest, - HttpResponseRedirect, -) -from django.shortcuts import get_object_or_404, redirect, render +from django.http import HttpRequest, HttpResponse, HttpResponseRedirect +from django.shortcuts import redirect, render from django.urls import reverse from django.utils import timezone from common.time import format_duration -from common.utils import safe_division, safe_getattr +from common.utils import safe_division -from .forms import ( - DeviceForm, - EditionForm, - GameForm, - PlatformForm, - PurchaseForm, - SessionForm, -) from .models import Edition, Game, Platform, Purchase, Session dateformat: str = "%d/%m/%Y" @@ -49,37 +36,6 @@ def stats_dropdown_year_range(request: HttpRequest) -> dict[str, range]: return result -@login_required -def add_session(request: HttpRequest, purchase_id: int = 0) -> HttpResponse: - context = {} - initial: dict[str, Any] = {"timestamp_start": timezone.now()} - - last = Session.objects.last() - if last != None: - initial["purchase"] = last.purchase - - if request.method == "POST": - form = SessionForm(request.POST or None, initial=initial) - if form.is_valid(): - form.save() - return redirect("list_sessions") - else: - if purchase_id: - purchase = Purchase.objects.get(id=purchase_id) - form = SessionForm( - initial={ - **initial, - "purchase": purchase, - } - ) - else: - form = SessionForm(initial=initial) - - context["title"] = "Add New Session" - context["form"] = form - return render(request, "add_session.html", context) - - def use_custom_redirect( func: Callable[..., HttpResponse], ) -> Callable[..., HttpResponse]: @@ -98,265 +54,6 @@ def use_custom_redirect( return wrapper -@login_required -@use_custom_redirect -def edit_session(request: HttpRequest, session_id: int) -> HttpResponse: - context = {} - session = get_object_or_404(Session, id=session_id) - form = SessionForm(request.POST or None, instance=session) - if form.is_valid(): - form.save() - return redirect("list_sessions") - context["title"] = "Edit Session" - context["form"] = form - return render(request, "add_session.html", context) - - -@login_required -@use_custom_redirect -def edit_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse: - context = {} - purchase = get_object_or_404(Purchase, id=purchase_id) - form = PurchaseForm(request.POST or None, instance=purchase) - if form.is_valid(): - form.save() - return redirect("list_sessions") - context["title"] = "Edit Purchase" - context["form"] = form - context["purchase_id"] = str(purchase_id) - context["script_name"] = "add_purchase.js" - return render(request, "add_purchase.html", context) - - -@login_required -@use_custom_redirect -def edit_game(request: HttpRequest, game_id: int) -> HttpResponse: - context = {} - purchase = get_object_or_404(Game, id=game_id) - form = GameForm(request.POST or None, instance=purchase) - if form.is_valid(): - form.save() - return redirect("list_sessions") - context["title"] = "Edit Game" - context["form"] = form - return render(request, "add.html", context) - - -@login_required -def delete_game(request: HttpRequest, game_id: int) -> HttpResponse: - game = get_object_or_404(Game, id=game_id) - game.delete() - return redirect("list_sessions") - - -@login_required -def view_game(request: HttpRequest, game_id: int) -> HttpResponse: - game = Game.objects.get(id=game_id) - nongame_related_purchases_prefetch: Prefetch[Purchase] = Prefetch( - "related_purchases", - queryset=Purchase.objects.exclude(type=Purchase.GAME).order_by( - "date_purchased" - ), - to_attr="nongame_related_purchases", - ) - game_purchases_prefetch: Prefetch[Purchase] = 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") - ) - - sessions = Session.objects.prefetch_related("device").filter( - purchase__edition__game=game - ) - session_count = sessions.count() - session_count_without_manual = ( - Session.objects.without_manual().filter(purchase__edition__game=game).count() - ) - - if sessions: - playrange_start = sessions.earliest().timestamp_start.strftime("%b %Y") - latest_session = sessions.latest() - playrange_end = latest_session.timestamp_start.strftime("%b %Y") - - playrange = ( - playrange_start - if playrange_start == playrange_end - else f"{playrange_start} — {playrange_end}" - ) - else: - playrange = "N/A" - latest_session = None - - total_hours = float(format_duration(sessions.total_duration_unformatted(), "%2.1H")) - total_hours_without_manual = float( - format_duration(sessions.calculated_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_without_manual": round( - safe_division( - total_hours_without_manual, int(session_count_without_manual) - ), - 1, - ), - "session_count": session_count, - "sessions_with_notes_count": sessions.exclude(note="").count(), - "sessions": sessions.order_by("-timestamp_start"), - "title": f"Game Overview - {game.name}", - "hours_sum": total_hours, - "latest_session_id": safe_getattr(latest_session, "pk"), - } - - request.session["return_path"] = request.path - return render(request, "view_game.html", context) - - -@login_required -@use_custom_redirect -def edit_platform(request: HttpRequest, platform_id: int) -> HttpResponse: - context = {} - platform = get_object_or_404(Platform, id=platform_id) - form = PlatformForm(request.POST or None, instance=platform) - if form.is_valid(): - form.save() - return redirect("list_platforms") - context["title"] = "Edit Platform" - context["form"] = form - return render(request, "add.html", context) - - -@login_required -@use_custom_redirect -def edit_edition(request: HttpRequest, edition_id: int) -> HttpResponse: - context = {} - 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_sessions") - context["title"] = "Edit Edition" - context["form"] = form - return render(request, "add.html", context) - - -def related_purchase_by_edition(request: HttpRequest) -> HttpResponse: - edition_id = request.GET.get("edition") - if not edition_id: - return HttpResponseBadRequest("Invalid edition_id") - form = PurchaseForm() - form.fields["related_purchase"].queryset = Purchase.objects.filter( - edition_id=edition_id, type=Purchase.GAME - ).order_by("edition__sort_name") - return render(request, "partials/related_purchase_field.html", {"form": form}) - - -def clone_session_by_id(session_id: int) -> Session: - session = get_object_or_404(Session, id=session_id) - clone = session - clone.pk = None - clone.timestamp_start = timezone.now() - clone.timestamp_end = None - clone.note = "" - clone.save() - return clone - - -@login_required -@use_custom_redirect -def new_session_from_existing_session( - request: HttpRequest, session_id: int, template: str = "" -) -> HttpResponse: - session = clone_session_by_id(session_id) - if request.htmx: - context = { - "session": session, - "session_count": int(request.GET.get("session_count", 0)) + 1, - } - return render(request, template, context) - return redirect("list_sessions") - - -@login_required -@use_custom_redirect -def end_session( - request: HttpRequest, session_id: int, template: str = "" -) -> HttpResponse: - session = get_object_or_404(Session, id=session_id) - session.timestamp_end = timezone.now() - session.save() - if request.htmx: - context = { - "session": session, - "session_count": request.GET.get("session_count", 0), - } - return render(request, template, context) - return redirect("list_sessions") - - -@login_required -def delete_session(request: HttpRequest, session_id: int = 0) -> HttpResponse: - session = get_object_or_404(Session, id=session_id) - session.delete() - return redirect("list_sessions") - - -@login_required -def list_sessions( - request: HttpRequest, - filter: str = "", - purchase_id: int = 0, - platform_id: int = 0, - game_id: int = 0, - edition_id: int = 0, - ownership_type: str = "", -) -> HttpResponse: - context = {} - context["title"] = "Sessions" - - all_sessions = Session.objects.prefetch_related( - "purchase", "purchase__edition", "purchase__edition__game" - ).order_by("-timestamp_start") - - if filter == "purchase": - dataset = all_sessions.filter(purchase=purchase_id) - context["purchase"] = Purchase.objects.get(id=purchase_id) - elif filter == "platform": - dataset = all_sessions.filter(purchase__platform=platform_id) - context["platform"] = Platform.objects.get(id=platform_id) - elif filter == "edition": - dataset = all_sessions.filter(purchase__edition=edition_id) - context["edition"] = Edition.objects.get(id=edition_id) - elif filter == "game": - dataset = all_sessions.filter(purchase__edition__game=game_id) - context["game"] = Game.objects.get(id=game_id) - elif filter == "ownership_type": - dataset = all_sessions.filter(purchase__ownership_type=ownership_type) - context["ownership_type"] = dict(Purchase.OWNERSHIP_TYPES)[ownership_type] - context["title"] = "This year" - else: - dataset = all_sessions - - context = { - **context, - "dataset": dataset, - "dataset_count": dataset.count(), - "last": Session.objects.prefetch_related("purchase__platform").latest(), - } - - return render(request, "list_sessions.html", context) - - @login_required def stats_alltime(request: HttpRequest) -> HttpResponse: year = "Alltime" @@ -815,129 +512,6 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse: return render(request, "stats.html", context) -@login_required -def delete_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse: - purchase = get_object_or_404(Purchase, id=purchase_id) - purchase.delete() - return redirect("list_sessions") - - -@login_required -def add_purchase(request: HttpRequest, edition_id: int = 0) -> HttpResponse: - context: dict[str, Any] = {} - initial = {"date_purchased": timezone.now()} - - if request.method == "POST": - form = PurchaseForm(request.POST or None, initial=initial) - if form.is_valid(): - purchase = form.save() - if "submit_and_redirect" in request.POST: - return HttpResponseRedirect( - reverse( - "add_session_for_purchase", kwargs={"purchase_id": purchase.id} - ) - ) - else: - return redirect("index") - else: - if edition_id: - edition = Edition.objects.get(id=edition_id) - form = PurchaseForm( - initial={ - **initial, - "edition": edition, - "platform": edition.platform, - } - ) - else: - form = PurchaseForm(initial=initial) - - context["form"] = form - context["title"] = "Add New Purchase" - context["script_name"] = "add_purchase.js" - return render(request, "add_purchase.html", context) - - -@login_required -def add_game(request: HttpRequest) -> HttpResponse: - context: dict[str, Any] = {} - form = GameForm(request.POST or None) - if form.is_valid(): - game = form.save() - if "submit_and_redirect" in request.POST: - return HttpResponseRedirect( - reverse("add_edition_for_game", kwargs={"game_id": game.id}) - ) - else: - return redirect("index") - - context["form"] = form - context["title"] = "Add New Game" - context["script_name"] = "add_game.js" - return render(request, "add_game.html", context) - - -@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) - - -@login_required -def add_platform(request: HttpRequest) -> HttpResponse: - context: dict[str, Any] = {} - form = PlatformForm(request.POST or None) - if form.is_valid(): - form.save() - return redirect("index") - - context["form"] = form - context["title"] = "Add New Platform" - return render(request, "add.html", context) - - -@login_required -def add_device(request: HttpRequest) -> HttpResponse: - context: dict[str, Any] = {} - form = DeviceForm(request.POST or None) - if form.is_valid(): - form.save() - return redirect("index") - - context["form"] = form - context["title"] = "Add New Device" - return render(request, "add.html", context) - - @login_required def index(request: HttpRequest) -> HttpResponse: return redirect("list_sessions")