separate views out 1/2

This commit is contained in:
Lukáš Kucharczyk 2024-08-12 21:42:34 +02:00
parent a84209eb81
commit 973f4416de
Signed by: lukas
SSH Key Fingerprint: SHA256:vMuSwvwAvcT6htVAioMP7rzzwMQNi3roESyhv+nAxeg
8 changed files with 425 additions and 473 deletions

View File

@ -87,3 +87,16 @@ def delete_device(request: HttpRequest, device_id: int) -> HttpResponse:
device = get_object_or_404(Device, id=device_id) device = get_object_or_404(Device, id=device_id)
device.delete() device.delete()
return redirect("list_sessions") 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)

View File

@ -2,14 +2,14 @@ from typing import Any
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator 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.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
from common.utils import truncate_with_popover from common.utils import truncate_with_popover
from games.forms import EditionForm from games.forms import EditionForm
from games.models import Edition from games.models import Edition, Game
from games.views import dateformat from games.views import dateformat
@ -91,7 +91,7 @@ def list_editions(request: HttpRequest) -> HttpResponse:
@login_required @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) edition = get_object_or_404(Edition, id=edition_id)
form = EditionForm(request.POST or None, instance=edition) form = EditionForm(request.POST or None, instance=edition)
if form.is_valid(): 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 = get_object_or_404(Edition, id=edition_id)
edition.delete() edition.delete()
return redirect("list_editions") 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

@ -2,14 +2,16 @@ from typing import Any
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
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.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
from common.utils import truncate_with_popover from common.time import format_duration
from games.models import Game from common.utils import safe_division, safe_getattr, truncate_with_popover
from games.views import dateformat from games.forms import GameForm
from games.models import Game, Purchase, Session
from games.views import dateformat, use_custom_redirect
@login_required @login_required
@ -76,3 +78,116 @@ def list_games(request: HttpRequest) -> HttpResponse:
}, },
} }
return render(request, "list_purchases.html", context) 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)

View File

@ -7,8 +7,9 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
from games.forms import PlatformForm
from games.models import Platform from games.models import Platform
from games.views import dateformat from games.views import dateformat, use_custom_redirect
@login_required @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 = get_object_or_404(Platform, id=platform_id)
platform.delete() platform.delete()
return redirect("list_platforms") 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)

View File

@ -2,14 +2,21 @@ from typing import Any
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.http import HttpRequest, HttpResponse from django.http import (
from django.shortcuts import render HttpRequest,
HttpResponse,
HttpResponseBadRequest,
HttpResponseRedirect,
)
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from common.utils import truncate_with_popover from common.utils import truncate_with_popover
from games.models import Purchase from games.forms import PurchaseForm
from games.views import dateformat from games.models import Edition, Purchase
from games.views import dateformat, use_custom_redirect
@login_required @login_required
@ -98,3 +105,73 @@ def list_purchases(request: HttpRequest) -> HttpResponse:
}, },
} }
return render(request, "list_purchases.html", context) 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})

View File

@ -3,19 +3,22 @@ from typing import Any
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.http import HttpRequest, HttpResponse 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.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from common.time import format_duration from common.time import format_duration
from common.utils import truncate_with_popover 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 ( from games.views import (
dateformat, dateformat,
datetimeformat, datetimeformat,
durationformat, durationformat,
durationformat_manual, durationformat_manual,
timeformat, timeformat,
use_custom_redirect,
) )
@ -91,3 +94,98 @@ def list_sessions(request: HttpRequest) -> HttpResponse:
}, },
} }
return render(request, "list_purchases.html", context) 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")

View File

@ -12,43 +12,53 @@ from games import (
urlpatterns = [ urlpatterns = [
path("", views.index, name="index"), path("", views.index, name="index"),
path("device/add", views.add_device, name="add_device"), path("device/add", deviceviews.add_device, name="add_device"),
path( path(
"device/delete/<int:device_id>", deviceviews.delete_device, name="delete_device" "device/delete/<int:device_id>", deviceviews.delete_device, name="delete_device"
), ),
path("device/edit/<int:device_id>", deviceviews.edit_device, name="edit_device"), path("device/edit/<int:device_id>", deviceviews.edit_device, name="edit_device"),
path("device/list", deviceviews.list_devices, name="list_devices"), 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( path(
"edition/add/for-game/<int:game_id>", "edition/add/for-game/<int:game_id>",
views.add_edition, editionviews.add_edition,
name="add_edition_for_game", name="add_edition_for_game",
), ),
path("edition/<int:edition_id>/edit", views.edit_edition, name="edit_edition"), path(
"edition/<int:edition_id>/edit", editionviews.edit_edition, name="edit_edition"
),
path("edition/list", editionviews.list_editions, name="list_editions"), path("edition/list", editionviews.list_editions, name="list_editions"),
path( path(
"edition/<int:edition_id>/delete", "edition/<int:edition_id>/delete",
editionviews.delete_edition, editionviews.delete_edition,
name="delete_edition", name="delete_edition",
), ),
path("game/add", views.add_game, name="add_game"), path("game/add", gameviews.add_game, name="add_game"),
path("game/<int:game_id>/edit", views.edit_game, name="edit_game"), path("game/<int:game_id>/edit", gameviews.edit_game, name="edit_game"),
path("game/<int:game_id>/view", views.view_game, name="view_game"), path("game/<int:game_id>/view", gameviews.view_game, name="view_game"),
path("game/<int:game_id>/delete", views.delete_game, name="delete_game"), path("game/<int:game_id>/delete", gameviews.delete_game, name="delete_game"),
path("game/list", gameviews.list_games, name="list_games"), path("game/list", gameviews.list_games, name="list_games"),
path("platform/add", views.add_platform, name="add_platform"), path("platform/add", platformviews.add_platform, name="add_platform"),
path("platform/<int:platform_id>/edit", views.edit_platform, name="edit_platform"), path(
"platform/<int:platform_id>/edit",
platformviews.edit_platform,
name="edit_platform",
),
path( path(
"platform/<int:platform_id>/delete", "platform/<int:platform_id>/delete",
platformviews.delete_platform, platformviews.delete_platform,
name="delete_platform", name="delete_platform",
), ),
path("platform/list", platformviews.list_platforms, name="list_platforms"), path("platform/list", platformviews.list_platforms, name="list_platforms"),
path("purchase/add", views.add_purchase, name="add_purchase"), path("purchase/add", purchaseviews.add_purchase, name="add_purchase"),
path("purchase/<int:purchase_id>/edit", views.edit_purchase, name="edit_purchase"), path(
"purchase/<int:purchase_id>/edit",
purchaseviews.edit_purchase,
name="edit_purchase",
),
path( path(
"purchase/<int:purchase_id>/delete", "purchase/<int:purchase_id>/delete",
views.delete_purchase, purchaseviews.delete_purchase,
name="delete_purchase", name="delete_purchase",
), ),
path( path(
@ -58,78 +68,80 @@ urlpatterns = [
), ),
path( path(
"purchase/related-purchase-by-edition", "purchase/related-purchase-by-edition",
views.related_purchase_by_edition, purchaseviews.related_purchase_by_edition,
name="related_purchase_by_edition", name="related_purchase_by_edition",
), ),
path( path(
"purchase/add/for-edition/<int:edition_id>", "purchase/add/for-edition/<int:edition_id>",
views.add_purchase, purchaseviews.add_purchase,
name="add_purchase_for_edition", name="add_purchase_for_edition",
), ),
path("session/add", views.add_session, name="add_session"), path("session/add", sessionviews.add_session, name="add_session"),
path( path(
"session/add/for-purchase/<int:purchase_id>", "session/add/for-purchase/<int:purchase_id>",
views.add_session, sessionviews.add_session,
name="add_session_for_purchase", name="add_session_for_purchase",
), ),
path( path(
"session/add/from-game/<int:session_id>", "session/add/from-game/<int:session_id>",
views.new_session_from_existing_session, sessionviews.new_session_from_existing_session,
{"template": "view_game.html#session-info"}, {"template": "view_game.html#session-info"},
name="view_game_start_session_from_session", name="view_game_start_session_from_session",
), ),
path( path(
"session/add/from-list/<int:session_id>", "session/add/from-list/<int:session_id>",
views.new_session_from_existing_session, sessionviews.new_session_from_existing_session,
{"template": "list_sessions.html#session-row"}, {"template": "list_sessions.html#session-row"},
name="list_sessions_start_session_from_session", name="list_sessions_start_session_from_session",
), ),
path("session/<int:session_id>/edit", views.edit_session, name="edit_session"), path(
"session/<int:session_id>/edit", sessionviews.edit_session, name="edit_session"
),
path( path(
"session/<int:session_id>/delete", "session/<int:session_id>/delete",
views.delete_session, sessionviews.delete_session,
name="delete_session", name="delete_session",
), ),
path( path(
"session/end/from-game/<int:session_id>", "session/end/from-game/<int:session_id>",
views.end_session, sessionviews.end_session,
{"template": "view_game.html#session-info"}, {"template": "view_game.html#session-info"},
name="view_game_end_session", name="view_game_end_session",
), ),
path( path(
"session/end/from-list/<int:session_id>", "session/end/from-list/<int:session_id>",
views.end_session, sessionviews.end_session,
{"template": "list_sessions.html#session-row"}, {"template": "list_sessions.html#session-row"},
name="list_sessions_end_session", name="list_sessions_end_session",
), ),
path("session/list", sessionviews.list_sessions, name="list_sessions"), path("session/list", sessionviews.list_sessions, name="list_sessions"),
path( path(
"session/list/by-purchase/<int:purchase_id>", "session/list/by-purchase/<int:purchase_id>",
views.list_sessions, sessionviews.list_sessions,
{"filter": "purchase"}, {"filter": "purchase"},
name="list_sessions_by_purchase", name="list_sessions_by_purchase",
), ),
path( path(
"session/list/by-platform/<int:platform_id>", "session/list/by-platform/<int:platform_id>",
views.list_sessions, sessionviews.list_sessions,
{"filter": "platform"}, {"filter": "platform"},
name="list_sessions_by_platform", name="list_sessions_by_platform",
), ),
path( path(
"session/list/by-game/<int:game_id>", "session/list/by-game/<int:game_id>",
views.list_sessions, sessionviews.list_sessions,
{"filter": "game"}, {"filter": "game"},
name="list_sessions_by_game", name="list_sessions_by_game",
), ),
path( path(
"session/list/by-edition/<int:edition_id>", "session/list/by-edition/<int:edition_id>",
views.list_sessions, sessionviews.list_sessions,
{"filter": "edition"}, {"filter": "edition"},
name="list_sessions_by_edition", name="list_sessions_by_edition",
), ),
path( path(
"session/list/by-ownership/<str:ownership_type>", "session/list/by-ownership/<str:ownership_type>",
views.list_sessions, sessionviews.list_sessions,
{"filter": "ownership_type"}, {"filter": "ownership_type"},
name="list_sessions_by_ownership_type", name="list_sessions_by_ownership_type",
), ),

View File

@ -1,30 +1,17 @@
from typing import Any, Callable from typing import Any, Callable
from django.contrib.auth.decorators import login_required 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.functions import TruncDate, TruncMonth
from django.db.models.manager import BaseManager from django.db.models.manager import BaseManager
from django.http import ( from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
HttpRequest, from django.shortcuts import redirect, render
HttpResponse,
HttpResponseBadRequest,
HttpResponseRedirect,
)
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from common.time import format_duration 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 from .models import Edition, Game, Platform, Purchase, Session
dateformat: str = "%d/%m/%Y" dateformat: str = "%d/%m/%Y"
@ -49,37 +36,6 @@ def stats_dropdown_year_range(request: HttpRequest) -> dict[str, range]:
return result 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( def use_custom_redirect(
func: Callable[..., HttpResponse], func: Callable[..., HttpResponse],
) -> Callable[..., HttpResponse]: ) -> Callable[..., HttpResponse]:
@ -98,265 +54,6 @@ def use_custom_redirect(
return wrapper 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 @login_required
def stats_alltime(request: HttpRequest) -> HttpResponse: def stats_alltime(request: HttpRequest) -> HttpResponse:
year = "Alltime" year = "Alltime"
@ -815,129 +512,6 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
return render(request, "stats.html", context) 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 @login_required
def index(request: HttpRequest) -> HttpResponse: def index(request: HttpRequest) -> HttpResponse:
return redirect("list_sessions") return redirect("list_sessions")