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