Compare commits

...

4 Commits

Author SHA1 Message Date
Lukáš Kucharczyk 2fd44c1f53
separate views out 2/2
Django CI/CD / test (push) Successful in 57s Details
Django CI/CD / build-and-push (push) Has been skipped Details
2024-08-12 21:52:26 +02:00
Lukáš Kucharczyk c3f99d124c
update base.css 2024-08-12 21:42:56 +02:00
Lukáš Kucharczyk 51f5b9fceb
update ruff path 2024-08-12 21:42:47 +02:00
Lukáš Kucharczyk 973f4416de
separate views out 1/2 2024-08-12 21:42:34 +02:00
13 changed files with 509 additions and 613 deletions

View File

@ -13,7 +13,7 @@
"source.organizeImports": "explicit" "source.organizeImports": "explicit"
}, },
}, },
"ruff.path": ["/nix/store/s3q6qc2954x62bkcs9dwaxyiqchl7j01-ruff-0.5.6/bin/ruff"], "ruff.path": ["/nix/store/jaibb3v0rrnlw5ib54qqq3452yhp1xcb-ruff-0.5.7/bin/ruff"],
"tailwind-fold.supportedLanguages": [ "tailwind-fold.supportedLanguages": [
"html", "html",
"typescriptreact", "typescriptreact",

View File

@ -1,78 +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
from django.shortcuts import 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
@login_required
def list_games(request: HttpRequest) -> HttpResponse:
context: dict[Any, Any] = {}
page_number = request.GET.get("page", 1)
limit = request.GET.get("limit", 10)
games = Game.objects.order_by("-created_at")
page_obj = None
if int(limit) != 0:
paginator = Paginator(games, limit)
page_obj = paginator.get_page(page_number)
games = page_obj.object_list
context = {
"title": "Manage games",
"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": {
"columns": [
"Name",
"Sort Name",
"Year",
"Wikidata",
"Created",
"Actions",
],
"rows": [
[
truncate_with_popover(game.name),
truncate_with_popover(
game.sort_name
if game.sort_name is not None and game.name != game.sort_name
else "(identical)"
),
game.year_released,
game.wikidata,
game.created_at.strftime(dateformat),
render_to_string(
"components/button_group_sm.html",
{
"buttons": [
{
"href": reverse("edit_game", args=[game.pk]),
"text": "Edit",
},
{
"href": reverse("delete_game", args=[game.pk]),
"text": "Delete",
"color": "red",
},
]
},
),
]
for game in games
],
},
}
return render(request, "list_purchases.html", context)

View File

@ -2207,10 +2207,6 @@ input:checked + .toggle-bg {
max-width: 20ch; max-width: 20ch;
} }
.min-w-30char {
min-width: 30ch;
}
.\[a-zA-Z\:\\-\] { .\[a-zA-Z\:\\-\] {
a-z-a--z: \-; a-z-a--z: \-;
} }

View File

@ -1,142 +1,110 @@
from django.urls import path from django.urls import path
from games import ( from games.views import device, edition, game, general, platform, purchase, session
deviceviews,
editionviews,
gameviews,
platformviews,
purchaseviews,
sessionviews,
views,
)
urlpatterns = [ urlpatterns = [
path("", views.index, name="index"), path("", general.index, name="index"),
path("device/add", views.add_device, name="add_device"), path("device/add", device.add_device, name="add_device"),
path( path("device/delete/<int:device_id>", device.delete_device, name="delete_device"),
"device/delete/<int:device_id>", deviceviews.delete_device, name="delete_device" path("device/edit/<int:device_id>", device.edit_device, name="edit_device"),
), path("device/list", device.list_devices, name="list_devices"),
path("device/edit/<int:device_id>", deviceviews.edit_device, name="edit_device"), path("edition/add", edition.add_edition, name="add_edition"),
path("device/list", deviceviews.list_devices, name="list_devices"),
path("edition/add", views.add_edition, name="add_edition"),
path( path(
"edition/add/for-game/<int:game_id>", "edition/add/for-game/<int:game_id>",
views.add_edition, edition.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", edition.edit_edition, name="edit_edition"),
path("edition/list", editionviews.list_editions, name="list_editions"), path("edition/list", edition.list_editions, name="list_editions"),
path( path(
"edition/<int:edition_id>/delete", "edition/<int:edition_id>/delete",
editionviews.delete_edition, edition.delete_edition,
name="delete_edition", name="delete_edition",
), ),
path("game/add", views.add_game, name="add_game"), path("game/add", game.add_game, name="add_game"),
path("game/<int:game_id>/edit", views.edit_game, name="edit_game"), path("game/<int:game_id>/edit", game.edit_game, name="edit_game"),
path("game/<int:game_id>/view", views.view_game, name="view_game"), path("game/<int:game_id>/view", game.view_game, name="view_game"),
path("game/<int:game_id>/delete", views.delete_game, name="delete_game"), path("game/<int:game_id>/delete", game.delete_game, name="delete_game"),
path("game/list", gameviews.list_games, name="list_games"), path("game/list", game.list_games, name="list_games"),
path("platform/add", views.add_platform, name="add_platform"), path("platform/add", platform.add_platform, name="add_platform"),
path("platform/<int:platform_id>/edit", views.edit_platform, name="edit_platform"), path(
"platform/<int:platform_id>/edit",
platform.edit_platform,
name="edit_platform",
),
path( path(
"platform/<int:platform_id>/delete", "platform/<int:platform_id>/delete",
platformviews.delete_platform, platform.delete_platform,
name="delete_platform", name="delete_platform",
), ),
path("platform/list", platformviews.list_platforms, name="list_platforms"), path("platform/list", platform.list_platforms, name="list_platforms"),
path("purchase/add", views.add_purchase, name="add_purchase"), path("purchase/add", purchase.add_purchase, name="add_purchase"),
path("purchase/<int:purchase_id>/edit", views.edit_purchase, name="edit_purchase"), path(
"purchase/<int:purchase_id>/edit",
purchase.edit_purchase,
name="edit_purchase",
),
path( path(
"purchase/<int:purchase_id>/delete", "purchase/<int:purchase_id>/delete",
views.delete_purchase, purchase.delete_purchase,
name="delete_purchase", name="delete_purchase",
), ),
path( path(
"purchase/list", "purchase/list",
purchaseviews.list_purchases, purchase.list_purchases,
name="list_purchases", name="list_purchases",
), ),
path( path(
"purchase/related-purchase-by-edition", "purchase/related-purchase-by-edition",
views.related_purchase_by_edition, purchase.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, purchase.add_purchase,
name="add_purchase_for_edition", name="add_purchase_for_edition",
), ),
path("session/add", views.add_session, name="add_session"), path("session/add", session.add_session, name="add_session"),
path( path(
"session/add/for-purchase/<int:purchase_id>", "session/add/for-purchase/<int:purchase_id>",
views.add_session, session.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, session.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, session.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", session.edit_session, name="edit_session"),
path( path(
"session/<int:session_id>/delete", "session/<int:session_id>/delete",
views.delete_session, session.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, session.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, session.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", session.list_sessions, name="list_sessions"),
path( path("stats/", general.stats_alltime, name="stats_alltime"),
"session/list/by-purchase/<int:purchase_id>",
views.list_sessions,
{"filter": "purchase"},
name="list_sessions_by_purchase",
),
path(
"session/list/by-platform/<int:platform_id>",
views.list_sessions,
{"filter": "platform"},
name="list_sessions_by_platform",
),
path(
"session/list/by-game/<int:game_id>",
views.list_sessions,
{"filter": "game"},
name="list_sessions_by_game",
),
path(
"session/list/by-edition/<int:edition_id>",
views.list_sessions,
{"filter": "edition"},
name="list_sessions_by_edition",
),
path(
"session/list/by-ownership/<str:ownership_type>",
views.list_sessions,
{"filter": "ownership_type"},
name="list_sessions_by_ownership_type",
),
path("stats/", views.stats_alltime, name="stats_alltime"),
path( path(
"stats/<int:year>", "stats/<int:year>",
views.stats, general.stats,
name="stats_by_year", name="stats_by_year",
), ),
] ]

0
games/views/__init__.py Normal file
View File

View File

@ -9,7 +9,7 @@ from django.urls import reverse
from games.forms import DeviceForm from games.forms import DeviceForm
from games.models import Device from games.models import Device
from games.views import dateformat from games.views.general import dateformat
@login_required @login_required
@ -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,15 +2,15 @@ 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.general import dateformat
@login_required @login_required
@ -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)

193
games/views/game.py Normal file
View File

@ -0,0 +1,193 @@
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.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.general import dateformat, use_custom_redirect
@login_required
def list_games(request: HttpRequest) -> HttpResponse:
context: dict[Any, Any] = {}
page_number = request.GET.get("page", 1)
limit = request.GET.get("limit", 10)
games = Game.objects.order_by("-created_at")
page_obj = None
if int(limit) != 0:
paginator = Paginator(games, limit)
page_obj = paginator.get_page(page_number)
games = page_obj.object_list
context = {
"title": "Manage games",
"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": {
"columns": [
"Name",
"Sort Name",
"Year",
"Wikidata",
"Created",
"Actions",
],
"rows": [
[
truncate_with_popover(game.name),
truncate_with_popover(
game.sort_name
if game.sort_name is not None and game.name != game.sort_name
else "(identical)"
),
game.year_released,
game.wikidata,
game.created_at.strftime(dateformat),
render_to_string(
"components/button_group_sm.html",
{
"buttons": [
{
"href": reverse("edit_game", args=[game.pk]),
"text": "Edit",
},
{
"href": reverse("delete_game", args=[game.pk]),
"text": "Delete",
"color": "red",
},
]
},
),
]
for game in games
],
},
}
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

@ -1,31 +1,16 @@
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 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 games.models import Edition, Game, Platform, Purchase, Session
from .forms import (
DeviceForm,
EditionForm,
GameForm,
PlatformForm,
PurchaseForm,
SessionForm,
)
from .models import Edition, Game, Platform, Purchase, Session
dateformat: str = "%d/%m/%Y" dateformat: str = "%d/%m/%Y"
datetimeformat: str = "%d/%m/%Y %H:%M" datetimeformat: str = "%d/%m/%Y %H:%M"
@ -44,42 +29,6 @@ def model_counts(request: HttpRequest) -> dict[str, bool]:
} }
def stats_dropdown_year_range(request: HttpRequest) -> dict[str, range]:
result = {"stats_dropdown_year_range": range(timezone.now().year, 1999, -1)}
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 +47,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 +505,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")

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.general 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.general 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.views import ( from games.models import Purchase, Session
from games.views.general 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

@ -83,8 +83,7 @@ TEMPLATES = [
"django.template.context_processors.request", "django.template.context_processors.request",
"django.contrib.auth.context_processors.auth", "django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
"games.views.model_counts", "games.views.general.model_counts",
"games.views.stats_dropdown_year_range",
], ],
"builtins": [ "builtins": [
"template_partials.templatetags.partials", "template_partials.templatetags.partials",