Compare commits
No commits in common. "2fd44c1f530167c16359dfb5537100c71c8e1132" and "a84209eb81d5b0881743eda15fd807acdd2cb348" have entirely different histories.
2fd44c1f53
...
a84209eb81
|
@ -13,7 +13,7 @@
|
||||||
"source.organizeImports": "explicit"
|
"source.organizeImports": "explicit"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"ruff.path": ["/nix/store/jaibb3v0rrnlw5ib54qqq3452yhp1xcb-ruff-0.5.7/bin/ruff"],
|
"ruff.path": ["/nix/store/s3q6qc2954x62bkcs9dwaxyiqchl7j01-ruff-0.5.6/bin/ruff"],
|
||||||
"tailwind-fold.supportedLanguages": [
|
"tailwind-fold.supportedLanguages": [
|
||||||
"html",
|
"html",
|
||||||
"typescriptreact",
|
"typescriptreact",
|
||||||
|
|
|
@ -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.general import dateformat
|
from games.views import dateformat
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -87,16 +87,3 @@ 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)
|
|
|
@ -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, HttpResponseRedirect
|
from django.http import HttpRequest, HttpResponse
|
||||||
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, Game
|
from games.models import Edition
|
||||||
from games.views.general import dateformat
|
from games.views import dateformat
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -91,7 +91,7 @@ def list_editions(request: HttpRequest) -> HttpResponse:
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def edit_edition(request: HttpRequest, edition_id: int = 0) -> HttpResponse:
|
def edit_device(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,38 +107,3 @@ 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)
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
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)
|
|
@ -7,9 +7,8 @@ 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.general import dateformat, use_custom_redirect
|
from games.views import dateformat
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -79,30 +78,3 @@ 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)
|
|
|
@ -2,21 +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 (
|
from django.http import HttpRequest, HttpResponse
|
||||||
HttpRequest,
|
from django.shortcuts import render
|
||||||
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.forms import PurchaseForm
|
from games.models import Purchase
|
||||||
from games.models import Edition, Purchase
|
from games.views import dateformat
|
||||||
from games.views.general import dateformat, use_custom_redirect
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -105,73 +98,3 @@ 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})
|
|
|
@ -3,22 +3,19 @@ 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 get_object_or_404, redirect, render
|
from django.shortcuts import 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.forms import SessionForm
|
from games.models import Session
|
||||||
from games.models import Purchase, Session
|
from games.views import (
|
||||||
from games.views.general import (
|
|
||||||
dateformat,
|
dateformat,
|
||||||
datetimeformat,
|
datetimeformat,
|
||||||
durationformat,
|
durationformat,
|
||||||
durationformat_manual,
|
durationformat_manual,
|
||||||
timeformat,
|
timeformat,
|
||||||
use_custom_redirect,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,98 +91,3 @@ 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")
|
|
|
@ -2207,6 +2207,10 @@ 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: \-;
|
||||||
}
|
}
|
||||||
|
|
122
games/urls.py
122
games/urls.py
|
@ -1,110 +1,142 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from games.views import device, edition, game, general, platform, purchase, session
|
from games import (
|
||||||
|
deviceviews,
|
||||||
|
editionviews,
|
||||||
|
gameviews,
|
||||||
|
platformviews,
|
||||||
|
purchaseviews,
|
||||||
|
sessionviews,
|
||||||
|
views,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", general.index, name="index"),
|
path("", views.index, name="index"),
|
||||||
path("device/add", device.add_device, name="add_device"),
|
path("device/add", views.add_device, name="add_device"),
|
||||||
path("device/delete/<int:device_id>", device.delete_device, name="delete_device"),
|
path(
|
||||||
path("device/edit/<int:device_id>", device.edit_device, name="edit_device"),
|
"device/delete/<int:device_id>", deviceviews.delete_device, name="delete_device"
|
||||||
path("device/list", device.list_devices, name="list_devices"),
|
),
|
||||||
path("edition/add", edition.add_edition, name="add_edition"),
|
path("device/edit/<int:device_id>", 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(
|
path(
|
||||||
"edition/add/for-game/<int:game_id>",
|
"edition/add/for-game/<int:game_id>",
|
||||||
edition.add_edition,
|
views.add_edition,
|
||||||
name="add_edition_for_game",
|
name="add_edition_for_game",
|
||||||
),
|
),
|
||||||
path("edition/<int:edition_id>/edit", edition.edit_edition, name="edit_edition"),
|
path("edition/<int:edition_id>/edit", views.edit_edition, name="edit_edition"),
|
||||||
path("edition/list", edition.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",
|
||||||
edition.delete_edition,
|
editionviews.delete_edition,
|
||||||
name="delete_edition",
|
name="delete_edition",
|
||||||
),
|
),
|
||||||
path("game/add", game.add_game, name="add_game"),
|
path("game/add", views.add_game, name="add_game"),
|
||||||
path("game/<int:game_id>/edit", game.edit_game, name="edit_game"),
|
path("game/<int:game_id>/edit", views.edit_game, name="edit_game"),
|
||||||
path("game/<int:game_id>/view", game.view_game, name="view_game"),
|
path("game/<int:game_id>/view", views.view_game, name="view_game"),
|
||||||
path("game/<int:game_id>/delete", game.delete_game, name="delete_game"),
|
path("game/<int:game_id>/delete", views.delete_game, name="delete_game"),
|
||||||
path("game/list", game.list_games, name="list_games"),
|
path("game/list", gameviews.list_games, name="list_games"),
|
||||||
path("platform/add", platform.add_platform, name="add_platform"),
|
path("platform/add", views.add_platform, name="add_platform"),
|
||||||
path(
|
path("platform/<int:platform_id>/edit", views.edit_platform, name="edit_platform"),
|
||||||
"platform/<int:platform_id>/edit",
|
|
||||||
platform.edit_platform,
|
|
||||||
name="edit_platform",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"platform/<int:platform_id>/delete",
|
"platform/<int:platform_id>/delete",
|
||||||
platform.delete_platform,
|
platformviews.delete_platform,
|
||||||
name="delete_platform",
|
name="delete_platform",
|
||||||
),
|
),
|
||||||
path("platform/list", platform.list_platforms, name="list_platforms"),
|
path("platform/list", platformviews.list_platforms, name="list_platforms"),
|
||||||
path("purchase/add", purchase.add_purchase, name="add_purchase"),
|
path("purchase/add", views.add_purchase, name="add_purchase"),
|
||||||
path(
|
path("purchase/<int:purchase_id>/edit", views.edit_purchase, name="edit_purchase"),
|
||||||
"purchase/<int:purchase_id>/edit",
|
|
||||||
purchase.edit_purchase,
|
|
||||||
name="edit_purchase",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"purchase/<int:purchase_id>/delete",
|
"purchase/<int:purchase_id>/delete",
|
||||||
purchase.delete_purchase,
|
views.delete_purchase,
|
||||||
name="delete_purchase",
|
name="delete_purchase",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"purchase/list",
|
"purchase/list",
|
||||||
purchase.list_purchases,
|
purchaseviews.list_purchases,
|
||||||
name="list_purchases",
|
name="list_purchases",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"purchase/related-purchase-by-edition",
|
"purchase/related-purchase-by-edition",
|
||||||
purchase.related_purchase_by_edition,
|
views.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>",
|
||||||
purchase.add_purchase,
|
views.add_purchase,
|
||||||
name="add_purchase_for_edition",
|
name="add_purchase_for_edition",
|
||||||
),
|
),
|
||||||
path("session/add", session.add_session, name="add_session"),
|
path("session/add", views.add_session, name="add_session"),
|
||||||
path(
|
path(
|
||||||
"session/add/for-purchase/<int:purchase_id>",
|
"session/add/for-purchase/<int:purchase_id>",
|
||||||
session.add_session,
|
views.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>",
|
||||||
session.new_session_from_existing_session,
|
views.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>",
|
||||||
session.new_session_from_existing_session,
|
views.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", session.edit_session, name="edit_session"),
|
path("session/<int:session_id>/edit", views.edit_session, name="edit_session"),
|
||||||
path(
|
path(
|
||||||
"session/<int:session_id>/delete",
|
"session/<int:session_id>/delete",
|
||||||
session.delete_session,
|
views.delete_session,
|
||||||
name="delete_session",
|
name="delete_session",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"session/end/from-game/<int:session_id>",
|
"session/end/from-game/<int:session_id>",
|
||||||
session.end_session,
|
views.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>",
|
||||||
session.end_session,
|
views.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", session.list_sessions, name="list_sessions"),
|
path("session/list", sessionviews.list_sessions, name="list_sessions"),
|
||||||
path("stats/", general.stats_alltime, name="stats_alltime"),
|
path(
|
||||||
|
"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>",
|
||||||
general.stats,
|
views.stats,
|
||||||
name="stats_by_year",
|
name="stats_by_year",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,16 +1,31 @@
|
||||||
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, Q, Sum, fields
|
from django.db.models import Avg, Count, ExpressionWrapper, F, Prefetch, 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 HttpRequest, HttpResponse, HttpResponseRedirect
|
from django.http import (
|
||||||
from django.shortcuts import redirect, render
|
HttpRequest,
|
||||||
|
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
|
from common.utils import safe_division, safe_getattr
|
||||||
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"
|
||||||
|
@ -29,6 +44,42 @@ 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]:
|
||||||
|
@ -47,6 +98,265 @@ 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"
|
||||||
|
@ -505,6 +815,129 @@ 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")
|
|
@ -1,193 +0,0 @@
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.core.paginator import Paginator
|
|
||||||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from common.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)
|
|
|
@ -83,7 +83,8 @@ 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.general.model_counts",
|
"games.views.model_counts",
|
||||||
|
"games.views.stats_dropdown_year_range",
|
||||||
],
|
],
|
||||||
"builtins": [
|
"builtins": [
|
||||||
"template_partials.templatetags.partials",
|
"template_partials.templatetags.partials",
|
||||||
|
|
Loading…
Reference in New Issue