2024-08-11 15:23:28 +00:00
|
|
|
from typing import Any, Callable
|
2024-02-09 21:03:18 +00:00
|
|
|
|
|
|
|
from django.contrib.auth.decorators import login_required
|
2024-08-08 13:02:39 +00:00
|
|
|
from django.db.models import Avg, Count, ExpressionWrapper, F, Prefetch, Q, Sum, fields
|
2024-08-08 07:47:06 +00:00
|
|
|
from django.db.models.functions import TruncDate, TruncMonth
|
2024-08-08 19:19:43 +00:00
|
|
|
from django.db.models.manager import BaseManager
|
2023-11-17 08:08:30 +00:00
|
|
|
from django.http import (
|
|
|
|
HttpRequest,
|
|
|
|
HttpResponse,
|
|
|
|
HttpResponseBadRequest,
|
|
|
|
HttpResponseRedirect,
|
|
|
|
)
|
2024-08-08 13:02:39 +00:00
|
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
2023-11-02 08:20:09 +00:00
|
|
|
from django.urls import reverse
|
2023-11-15 16:47:51 +00:00
|
|
|
from django.utils import timezone
|
2023-01-15 22:39:52 +00:00
|
|
|
|
2023-11-15 18:36:22 +00:00
|
|
|
from common.time import format_duration
|
2024-07-09 05:32:49 +00:00
|
|
|
from common.utils import safe_division, safe_getattr
|
2023-11-15 18:36:22 +00:00
|
|
|
|
2023-11-16 15:27:33 +00:00
|
|
|
from .forms import (
|
|
|
|
DeviceForm,
|
|
|
|
EditionForm,
|
|
|
|
GameForm,
|
|
|
|
PlatformForm,
|
|
|
|
PurchaseForm,
|
|
|
|
SessionForm,
|
|
|
|
)
|
2024-08-11 15:23:28 +00:00
|
|
|
from .models import Edition, Game, Platform, Purchase, Session
|
2023-01-04 16:27:54 +00:00
|
|
|
|
2024-08-08 18:17:43 +00:00
|
|
|
dateformat: str = "%d/%m/%Y"
|
|
|
|
datetimeformat: str = "%d/%m/%Y %H:%M"
|
2024-08-11 18:29:47 +00:00
|
|
|
timeformat: str = "%H:%M"
|
|
|
|
durationformat: str = "%2.1H hours"
|
|
|
|
durationformat_manual: str = "%H hours"
|
2024-08-08 18:17:43 +00:00
|
|
|
|
2023-01-04 16:27:54 +00:00
|
|
|
|
2024-08-08 19:19:43 +00:00
|
|
|
def model_counts(request: HttpRequest) -> dict[str, bool]:
|
2023-01-04 16:19:40 +00:00
|
|
|
return {
|
2023-11-21 16:42:57 +00:00
|
|
|
"game_available": Game.objects.exists(),
|
|
|
|
"edition_available": Edition.objects.exists(),
|
|
|
|
"platform_available": Platform.objects.exists(),
|
|
|
|
"purchase_available": Purchase.objects.exists(),
|
|
|
|
"session_count": Session.objects.exists(),
|
2023-01-04 16:19:40 +00:00
|
|
|
}
|
2022-12-31 13:18:27 +00:00
|
|
|
|
|
|
|
|
2024-08-08 19:19:43 +00:00
|
|
|
def stats_dropdown_year_range(request: HttpRequest) -> dict[str, range]:
|
2023-11-15 16:47:51 +00:00
|
|
|
result = {"stats_dropdown_year_range": range(timezone.now().year, 1999, -1)}
|
2023-11-12 06:50:12 +00:00
|
|
|
return result
|
2023-11-02 08:52:42 +00:00
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2024-08-09 11:12:47 +00:00
|
|
|
def add_session(request: HttpRequest, purchase_id: int = 0) -> HttpResponse:
|
2022-12-31 13:18:27 +00:00
|
|
|
context = {}
|
2024-08-08 19:19:43 +00:00
|
|
|
initial: dict[str, Any] = {"timestamp_start": timezone.now()}
|
2023-01-19 19:35:25 +00:00
|
|
|
|
2023-11-15 16:47:51 +00:00
|
|
|
last = Session.objects.last()
|
2023-01-19 19:35:25 +00:00
|
|
|
if last != None:
|
|
|
|
initial["purchase"] = last.purchase
|
|
|
|
|
2023-11-09 20:01:01 +00:00
|
|
|
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)
|
2022-12-31 13:18:27 +00:00
|
|
|
|
2023-01-05 21:09:21 +00:00
|
|
|
context["title"] = "Add New Session"
|
2022-12-31 13:18:27 +00:00
|
|
|
context["form"] = form
|
2023-02-21 22:49:57 +00:00
|
|
|
return render(request, "add_session.html", context)
|
2022-12-31 13:18:27 +00:00
|
|
|
|
|
|
|
|
2023-11-05 14:09:51 +00:00
|
|
|
def use_custom_redirect(
|
2024-08-11 15:23:28 +00:00
|
|
|
func: Callable[..., HttpResponse],
|
2023-11-05 14:09:51 +00:00
|
|
|
) -> Callable[..., HttpResponse]:
|
|
|
|
"""
|
|
|
|
Will redirect to "return_path" session variable if set.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
|
|
|
|
response = func(request, *args, **kwargs)
|
|
|
|
if isinstance(response, HttpResponseRedirect) and (
|
|
|
|
next_url := request.session.get("return_path")
|
|
|
|
):
|
|
|
|
return HttpResponseRedirect(next_url)
|
|
|
|
return response
|
|
|
|
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2023-11-05 14:09:51 +00:00
|
|
|
@use_custom_redirect
|
2024-08-08 19:19:43 +00:00
|
|
|
def edit_session(request: HttpRequest, session_id: int) -> HttpResponse:
|
2023-01-30 16:38:44 +00:00
|
|
|
context = {}
|
2024-08-08 19:19:43 +00:00
|
|
|
session = get_object_or_404(Session, id=session_id)
|
2023-01-30 16:38:44 +00:00
|
|
|
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
|
2023-09-18 18:21:05 +00:00
|
|
|
return render(request, "add_session.html", context)
|
2023-01-30 16:38:44 +00:00
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2023-11-05 14:09:51 +00:00
|
|
|
@use_custom_redirect
|
2024-08-08 19:19:43 +00:00
|
|
|
def edit_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
|
2023-02-18 20:44:19 +00:00
|
|
|
context = {}
|
2024-08-08 19:19:43 +00:00
|
|
|
purchase = get_object_or_404(Purchase, id=purchase_id)
|
2023-02-18 20:44:19 +00:00
|
|
|
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
|
2024-08-08 19:19:43 +00:00
|
|
|
context["purchase_id"] = str(purchase_id)
|
2023-11-14 18:27:00 +00:00
|
|
|
context["script_name"] = "add_purchase.js"
|
|
|
|
return render(request, "add_purchase.html", context)
|
2023-02-18 20:44:19 +00:00
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2023-11-05 14:09:51 +00:00
|
|
|
@use_custom_redirect
|
2024-08-08 19:19:43 +00:00
|
|
|
def edit_game(request: HttpRequest, game_id: int) -> HttpResponse:
|
2023-02-20 16:16:19 +00:00
|
|
|
context = {}
|
2024-08-08 19:19:43 +00:00
|
|
|
purchase = get_object_or_404(Game, id=game_id)
|
2023-02-20 16:16:19 +00:00
|
|
|
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)
|
2023-10-01 19:28:02 +00:00
|
|
|
|
|
|
|
|
2024-07-09 21:03:03 +00:00
|
|
|
@login_required
|
2024-08-08 19:19:43 +00:00
|
|
|
def delete_game(request: HttpRequest, game_id: int) -> HttpResponse:
|
2024-07-09 21:03:03 +00:00
|
|
|
game = get_object_or_404(Game, id=game_id)
|
|
|
|
game.delete()
|
|
|
|
return redirect("list_sessions")
|
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2024-08-08 19:19:43 +00:00
|
|
|
def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
|
2023-10-01 19:28:02 +00:00
|
|
|
game = Game.objects.get(id=game_id)
|
2024-08-08 19:19:43 +00:00
|
|
|
nongame_related_purchases_prefetch: Prefetch[Purchase] = Prefetch(
|
2023-11-14 22:45:41 +00:00
|
|
|
"related_purchases",
|
2024-02-18 11:31:03 +00:00
|
|
|
queryset=Purchase.objects.exclude(type=Purchase.GAME).order_by(
|
|
|
|
"date_purchased"
|
|
|
|
),
|
2023-11-14 22:45:41 +00:00
|
|
|
to_attr="nongame_related_purchases",
|
2023-11-14 18:27:00 +00:00
|
|
|
)
|
2024-08-08 19:19:43 +00:00
|
|
|
game_purchases_prefetch: Prefetch[Purchase] = Prefetch(
|
2023-11-14 22:45:41 +00:00
|
|
|
"purchase_set",
|
|
|
|
queryset=Purchase.objects.filter(type=Purchase.GAME).prefetch_related(
|
|
|
|
nongame_related_purchases_prefetch
|
|
|
|
),
|
|
|
|
to_attr="game_purchases",
|
2023-10-08 22:00:45 +00:00
|
|
|
)
|
2023-11-14 22:45:41 +00:00
|
|
|
editions = (
|
|
|
|
Edition.objects.filter(game=game)
|
|
|
|
.prefetch_related(game_purchases_prefetch)
|
2024-02-18 11:31:03 +00:00
|
|
|
.order_by("year_released")
|
2023-10-08 22:00:45 +00:00
|
|
|
)
|
2023-11-06 11:05:39 +00:00
|
|
|
|
2024-01-14 16:04:06 +00:00
|
|
|
sessions = Session.objects.prefetch_related("device").filter(
|
|
|
|
purchase__edition__game=game
|
|
|
|
)
|
2023-11-14 22:45:41 +00:00
|
|
|
session_count = sessions.count()
|
2024-07-02 15:14:56 +00:00
|
|
|
session_count_without_manual = (
|
|
|
|
Session.objects.without_manual().filter(purchase__edition__game=game).count()
|
|
|
|
)
|
2023-11-14 22:45:41 +00:00
|
|
|
|
2024-07-09 05:32:49 +00:00
|
|
|
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
|
2023-11-14 22:45:41 +00:00
|
|
|
|
|
|
|
total_hours = float(format_duration(sessions.total_duration_unformatted(), "%2.1H"))
|
2024-07-02 15:14:56 +00:00
|
|
|
total_hours_without_manual = float(
|
|
|
|
format_duration(sessions.calculated_duration_unformatted(), "%2.1H")
|
|
|
|
)
|
2023-11-14 22:45:41 +00:00
|
|
|
context = {
|
|
|
|
"edition_count": editions.count(),
|
|
|
|
"editions": editions,
|
|
|
|
"game": game,
|
|
|
|
"playrange": playrange,
|
|
|
|
"purchase_count": Purchase.objects.filter(edition__game=game).count(),
|
2024-07-02 15:14:56 +00:00
|
|
|
"session_average_without_manual": round(
|
|
|
|
safe_division(
|
|
|
|
total_hours_without_manual, int(session_count_without_manual)
|
|
|
|
),
|
|
|
|
1,
|
|
|
|
),
|
2023-11-14 22:45:41 +00:00
|
|
|
"session_count": session_count,
|
2023-11-17 20:20:33 +00:00
|
|
|
"sessions_with_notes_count": sessions.exclude(note="").count(),
|
2023-11-14 22:45:41 +00:00
|
|
|
"sessions": sessions.order_by("-timestamp_start"),
|
|
|
|
"title": f"Game Overview - {game.name}",
|
|
|
|
"hours_sum": total_hours,
|
2024-07-09 05:32:49 +00:00
|
|
|
"latest_session_id": safe_getattr(latest_session, "pk"),
|
2023-11-14 22:45:41 +00:00
|
|
|
}
|
2023-11-06 11:05:39 +00:00
|
|
|
|
2023-11-05 14:09:51 +00:00
|
|
|
request.session["return_path"] = request.path
|
2023-10-01 19:28:02 +00:00
|
|
|
return render(request, "view_game.html", context)
|
2023-02-20 16:16:19 +00:00
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2023-11-05 14:09:51 +00:00
|
|
|
@use_custom_redirect
|
2024-08-08 19:19:43 +00:00
|
|
|
def edit_platform(request: HttpRequest, platform_id: int) -> HttpResponse:
|
2023-02-20 16:16:19 +00:00
|
|
|
context = {}
|
2024-08-11 16:34:50 +00:00
|
|
|
platform = get_object_or_404(Platform, id=platform_id)
|
|
|
|
form = PlatformForm(request.POST or None, instance=platform)
|
2023-02-20 16:16:19 +00:00
|
|
|
if form.is_valid():
|
|
|
|
form.save()
|
2024-08-11 16:34:50 +00:00
|
|
|
return redirect("list_platforms")
|
2023-02-20 16:16:19 +00:00
|
|
|
context["title"] = "Edit Platform"
|
|
|
|
context["form"] = form
|
|
|
|
return render(request, "add.html", context)
|
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2023-11-05 14:09:51 +00:00
|
|
|
@use_custom_redirect
|
2024-08-08 19:19:43 +00:00
|
|
|
def edit_edition(request: HttpRequest, edition_id: int) -> HttpResponse:
|
2023-02-18 20:47:25 +00:00
|
|
|
context = {}
|
2024-08-08 19:19:43 +00:00
|
|
|
edition = get_object_or_404(Edition, id=edition_id)
|
2023-02-18 20:47:25 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2024-08-08 19:19:43 +00:00
|
|
|
def related_purchase_by_edition(request: HttpRequest) -> HttpResponse:
|
2023-11-15 13:25:42 +00:00
|
|
|
edition_id = request.GET.get("edition")
|
2023-11-17 08:08:30 +00:00
|
|
|
if not edition_id:
|
|
|
|
return HttpResponseBadRequest("Invalid edition_id")
|
2023-11-15 13:25:42 +00:00
|
|
|
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})
|
|
|
|
|
|
|
|
|
2024-01-14 20:27:14 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2023-11-05 14:09:51 +00:00
|
|
|
@use_custom_redirect
|
2024-08-08 19:19:43 +00:00
|
|
|
def new_session_from_existing_session(
|
|
|
|
request: HttpRequest, session_id: int, template: str = ""
|
|
|
|
) -> HttpResponse:
|
2024-01-14 20:27:14 +00:00
|
|
|
session = clone_session_by_id(session_id)
|
|
|
|
if request.htmx:
|
2024-01-15 20:41:25 +00:00
|
|
|
context = {
|
|
|
|
"session": session,
|
|
|
|
"session_count": int(request.GET.get("session_count", 0)) + 1,
|
|
|
|
}
|
2024-01-14 20:27:14 +00:00
|
|
|
return render(request, template, context)
|
2023-10-13 17:22:43 +00:00
|
|
|
return redirect("list_sessions")
|
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2024-01-14 20:27:14 +00:00
|
|
|
@use_custom_redirect
|
2024-08-08 19:19:43 +00:00
|
|
|
def end_session(
|
|
|
|
request: HttpRequest, session_id: int, template: str = ""
|
|
|
|
) -> HttpResponse:
|
2024-01-14 20:27:14 +00:00
|
|
|
session = get_object_or_404(Session, id=session_id)
|
|
|
|
session.timestamp_end = timezone.now()
|
2023-01-13 15:54:24 +00:00
|
|
|
session.save()
|
2024-01-10 14:53:15 +00:00
|
|
|
if request.htmx:
|
2024-01-15 20:41:25 +00:00
|
|
|
context = {
|
|
|
|
"session": session,
|
|
|
|
"session_count": request.GET.get("session_count", 0),
|
|
|
|
}
|
2024-01-14 20:27:14 +00:00
|
|
|
return render(request, template, context)
|
2023-01-13 15:54:24 +00:00
|
|
|
return redirect("list_sessions")
|
|
|
|
|
2024-06-03 16:19:11 +00:00
|
|
|
|
2024-06-03 16:07:10 +00:00
|
|
|
@login_required
|
2024-08-08 19:19:43 +00:00
|
|
|
def delete_session(request: HttpRequest, session_id: int = 0) -> HttpResponse:
|
2024-06-03 16:07:10 +00:00
|
|
|
session = get_object_or_404(Session, id=session_id)
|
|
|
|
session.delete()
|
|
|
|
return redirect("list_sessions")
|
2023-01-04 19:28:07 +00:00
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2023-02-19 13:30:26 +00:00
|
|
|
def list_sessions(
|
2024-08-08 19:19:43 +00:00
|
|
|
request: HttpRequest,
|
|
|
|
filter: str = "",
|
|
|
|
purchase_id: int = 0,
|
|
|
|
platform_id: int = 0,
|
|
|
|
game_id: int = 0,
|
|
|
|
edition_id: int = 0,
|
2023-02-19 13:30:26 +00:00
|
|
|
ownership_type: str = "",
|
2024-08-08 19:19:43 +00:00
|
|
|
) -> HttpResponse:
|
2022-12-31 13:18:27 +00:00
|
|
|
context = {}
|
2023-01-30 21:16:28 +00:00
|
|
|
context["title"] = "Sessions"
|
2023-01-03 18:03:30 +00:00
|
|
|
|
2024-01-10 16:13:59 +00:00
|
|
|
all_sessions = Session.objects.prefetch_related(
|
2024-01-10 15:57:01 +00:00
|
|
|
"purchase", "purchase__edition", "purchase__edition__game"
|
|
|
|
).order_by("-timestamp_start")
|
|
|
|
|
2023-01-15 22:14:28 +00:00
|
|
|
if filter == "purchase":
|
2024-01-10 16:13:59 +00:00
|
|
|
dataset = all_sessions.filter(purchase=purchase_id)
|
2023-01-03 18:03:30 +00:00
|
|
|
context["purchase"] = Purchase.objects.get(id=purchase_id)
|
2023-01-15 22:14:28 +00:00
|
|
|
elif filter == "platform":
|
2024-01-10 16:13:59 +00:00
|
|
|
dataset = all_sessions.filter(purchase__platform=platform_id)
|
2023-01-15 22:14:28 +00:00
|
|
|
context["platform"] = Platform.objects.get(id=platform_id)
|
2023-02-18 19:49:46 +00:00
|
|
|
elif filter == "edition":
|
2024-01-10 16:13:59 +00:00
|
|
|
dataset = all_sessions.filter(purchase__edition=edition_id)
|
2023-02-18 19:49:46 +00:00
|
|
|
context["edition"] = Edition.objects.get(id=edition_id)
|
2023-02-19 13:30:26 +00:00
|
|
|
elif filter == "game":
|
2024-01-10 16:13:59 +00:00
|
|
|
dataset = all_sessions.filter(purchase__edition__game=game_id)
|
2023-02-19 13:30:26 +00:00
|
|
|
context["game"] = Game.objects.get(id=game_id)
|
|
|
|
elif filter == "ownership_type":
|
2024-01-10 16:13:59 +00:00
|
|
|
dataset = all_sessions.filter(purchase__ownership_type=ownership_type)
|
2023-02-19 13:30:26 +00:00
|
|
|
context["ownership_type"] = dict(Purchase.OWNERSHIP_TYPES)[ownership_type]
|
2023-09-14 16:49:16 +00:00
|
|
|
context["title"] = "This year"
|
2024-01-10 16:13:59 +00:00
|
|
|
else:
|
|
|
|
dataset = all_sessions
|
2023-01-03 18:03:30 +00:00
|
|
|
|
2024-01-10 16:13:59 +00:00
|
|
|
context = {
|
2024-01-14 20:40:15 +00:00
|
|
|
**context,
|
2024-01-10 16:13:59 +00:00
|
|
|
"dataset": dataset,
|
|
|
|
"dataset_count": dataset.count(),
|
|
|
|
"last": Session.objects.prefetch_related("purchase__platform").latest(),
|
|
|
|
}
|
2022-12-31 13:18:27 +00:00
|
|
|
|
|
|
|
return render(request, "list_sessions.html", context)
|
|
|
|
|
|
|
|
|
2024-08-04 20:40:37 +00:00
|
|
|
@login_required
|
2024-08-08 19:19:43 +00:00
|
|
|
def stats_alltime(request: HttpRequest) -> HttpResponse:
|
2024-08-04 20:40:37 +00:00
|
|
|
year = "Alltime"
|
|
|
|
this_year_sessions = Session.objects.all().select_related("purchase__edition")
|
|
|
|
this_year_sessions_with_durations = this_year_sessions.annotate(
|
|
|
|
duration=ExpressionWrapper(
|
|
|
|
F("timestamp_end") - F("timestamp_start"),
|
|
|
|
output_field=fields.DurationField(),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
longest_session = this_year_sessions_with_durations.order_by("-duration").first()
|
|
|
|
this_year_games = Game.objects.filter(
|
|
|
|
edition__purchase__session__in=this_year_sessions
|
|
|
|
).distinct()
|
|
|
|
this_year_games_with_session_counts = this_year_games.annotate(
|
|
|
|
session_count=Count("edition__purchase__session"),
|
|
|
|
)
|
|
|
|
game_highest_session_count = this_year_games_with_session_counts.order_by(
|
|
|
|
"-session_count"
|
|
|
|
).first()
|
|
|
|
selected_currency = "CZK"
|
|
|
|
unique_days = (
|
|
|
|
this_year_sessions.annotate(date=TruncDate("timestamp_start"))
|
|
|
|
.values("date")
|
|
|
|
.distinct()
|
|
|
|
.aggregate(dates=Count("date"))
|
|
|
|
)
|
|
|
|
this_year_played_purchases = Purchase.objects.filter(
|
|
|
|
session__in=this_year_sessions
|
|
|
|
).distinct()
|
|
|
|
|
|
|
|
this_year_purchases = Purchase.objects.all()
|
|
|
|
this_year_purchases_with_currency = this_year_purchases.select_related(
|
|
|
|
"edition"
|
|
|
|
).filter(price_currency__exact=selected_currency)
|
|
|
|
this_year_purchases_without_refunded = this_year_purchases_with_currency.filter(
|
|
|
|
date_refunded=None
|
|
|
|
)
|
|
|
|
this_year_purchases_refunded = this_year_purchases_with_currency.refunded()
|
|
|
|
|
|
|
|
this_year_purchases_unfinished_dropped_nondropped = (
|
|
|
|
this_year_purchases_without_refunded.filter(date_finished__isnull=True)
|
|
|
|
.filter(infinite=False)
|
|
|
|
.filter(Q(type=Purchase.GAME) | Q(type=Purchase.DLC))
|
|
|
|
) # do not count battle passes etc.
|
|
|
|
|
|
|
|
this_year_purchases_unfinished = (
|
|
|
|
this_year_purchases_unfinished_dropped_nondropped.filter(
|
|
|
|
date_dropped__isnull=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
this_year_purchases_dropped = (
|
|
|
|
this_year_purchases_unfinished_dropped_nondropped.filter(
|
|
|
|
date_dropped__isnull=False
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
this_year_purchases_without_refunded_count = (
|
|
|
|
this_year_purchases_without_refunded.count()
|
|
|
|
)
|
|
|
|
this_year_purchases_unfinished_count = this_year_purchases_unfinished.count()
|
|
|
|
this_year_purchases_unfinished_percent = int(
|
|
|
|
safe_division(
|
|
|
|
this_year_purchases_unfinished_count,
|
|
|
|
this_year_purchases_without_refunded_count,
|
|
|
|
)
|
|
|
|
* 100
|
|
|
|
)
|
|
|
|
|
2024-08-08 19:19:43 +00:00
|
|
|
purchases_finished_this_year: BaseManager[Purchase] = Purchase.objects.finished()
|
2024-08-04 20:40:37 +00:00
|
|
|
purchases_finished_this_year_released_this_year = (
|
|
|
|
purchases_finished_this_year.all().order_by("date_finished")
|
|
|
|
)
|
|
|
|
purchased_this_year_finished_this_year = (
|
|
|
|
this_year_purchases_without_refunded.all()
|
|
|
|
).order_by("date_finished")
|
|
|
|
|
|
|
|
this_year_spendings = this_year_purchases_without_refunded.aggregate(
|
|
|
|
total_spent=Sum(F("price"))
|
|
|
|
)
|
|
|
|
total_spent = this_year_spendings["total_spent"] or 0
|
|
|
|
|
|
|
|
games_with_playtime = (
|
|
|
|
Game.objects.filter(edition__purchase__session__in=this_year_sessions)
|
|
|
|
.annotate(
|
|
|
|
total_playtime=Sum(
|
|
|
|
F("edition__purchase__session__duration_calculated")
|
|
|
|
+ F("edition__purchase__session__duration_manual")
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.values("id", "name", "total_playtime")
|
|
|
|
)
|
|
|
|
month_playtimes = (
|
|
|
|
this_year_sessions.annotate(month=TruncMonth("timestamp_start"))
|
|
|
|
.values("month")
|
|
|
|
.annotate(playtime=Sum("duration_calculated"))
|
|
|
|
.order_by("month")
|
|
|
|
)
|
|
|
|
for month in month_playtimes:
|
|
|
|
month["playtime"] = format_duration(month["playtime"], "%2.0H")
|
|
|
|
|
|
|
|
highest_session_average_game = (
|
|
|
|
Game.objects.filter(edition__purchase__session__in=this_year_sessions)
|
|
|
|
.annotate(
|
|
|
|
session_average=Avg("edition__purchase__session__duration_calculated")
|
|
|
|
)
|
|
|
|
.order_by("-session_average")
|
|
|
|
.first()
|
|
|
|
)
|
|
|
|
top_10_games_by_playtime = games_with_playtime.order_by("-total_playtime")[:10]
|
|
|
|
for game in top_10_games_by_playtime:
|
|
|
|
game["formatted_playtime"] = format_duration(game["total_playtime"], "%2.0H")
|
|
|
|
|
|
|
|
total_playtime_per_platform = (
|
|
|
|
this_year_sessions.values("purchase__platform__name")
|
|
|
|
.annotate(total_playtime=Sum(F("duration_calculated") + F("duration_manual")))
|
|
|
|
.annotate(platform_name=F("purchase__platform__name"))
|
|
|
|
.values("platform_name", "total_playtime")
|
|
|
|
.order_by("-total_playtime")
|
|
|
|
)
|
|
|
|
for item in total_playtime_per_platform:
|
|
|
|
item["formatted_playtime"] = format_duration(item["total_playtime"], "%2.0H")
|
|
|
|
|
|
|
|
backlog_decrease_count = (
|
|
|
|
Purchase.objects.all().intersection(purchases_finished_this_year).count()
|
|
|
|
)
|
|
|
|
|
|
|
|
first_play_date = "N/A"
|
|
|
|
last_play_date = "N/A"
|
|
|
|
if this_year_sessions:
|
|
|
|
first_session = this_year_sessions.earliest()
|
|
|
|
first_play_game = first_session.purchase.edition.game
|
|
|
|
first_play_date = first_session.timestamp_start.strftime("%x")
|
|
|
|
last_session = this_year_sessions.latest()
|
|
|
|
last_play_game = last_session.purchase.edition.game
|
|
|
|
last_play_date = last_session.timestamp_start.strftime("%x")
|
|
|
|
|
|
|
|
all_purchased_this_year_count = this_year_purchases_with_currency.count()
|
2024-08-08 19:19:43 +00:00
|
|
|
all_purchased_refunded_this_year_count: int = this_year_purchases_refunded.count()
|
2024-08-04 20:40:37 +00:00
|
|
|
|
|
|
|
this_year_purchases_dropped_count = this_year_purchases_dropped.count()
|
|
|
|
this_year_purchases_dropped_percentage = int(
|
|
|
|
safe_division(this_year_purchases_dropped_count, all_purchased_this_year_count)
|
|
|
|
* 100
|
|
|
|
)
|
|
|
|
context = {
|
|
|
|
"total_hours": format_duration(
|
|
|
|
this_year_sessions.total_duration_unformatted(), "%2.0H"
|
|
|
|
),
|
|
|
|
"total_2023_games": this_year_played_purchases.all().count(),
|
|
|
|
"top_10_games_by_playtime": top_10_games_by_playtime,
|
|
|
|
"year": year,
|
|
|
|
"total_playtime_per_platform": total_playtime_per_platform,
|
|
|
|
"total_spent": total_spent,
|
|
|
|
"total_spent_currency": selected_currency,
|
|
|
|
"spent_per_game": int(
|
|
|
|
safe_division(total_spent, this_year_purchases_without_refunded_count)
|
|
|
|
),
|
|
|
|
"this_year_finished_this_year_count": purchases_finished_this_year_released_this_year.count(),
|
|
|
|
"total_sessions": this_year_sessions.count(),
|
|
|
|
"unique_days": unique_days["dates"],
|
|
|
|
"unique_days_percent": int(unique_days["dates"] / 365 * 100),
|
|
|
|
"purchased_unfinished_count": this_year_purchases_unfinished_count,
|
|
|
|
"unfinished_purchases_percent": this_year_purchases_unfinished_percent,
|
|
|
|
"dropped_count": this_year_purchases_dropped_count,
|
|
|
|
"dropped_percentage": this_year_purchases_dropped_percentage,
|
|
|
|
"refunded_percent": int(
|
|
|
|
safe_division(
|
|
|
|
all_purchased_refunded_this_year_count,
|
|
|
|
all_purchased_this_year_count,
|
|
|
|
)
|
|
|
|
* 100
|
|
|
|
),
|
|
|
|
"all_purchased_refunded_this_year": this_year_purchases_refunded,
|
|
|
|
"all_purchased_refunded_this_year_count": all_purchased_refunded_this_year_count,
|
|
|
|
"all_purchased_this_year_count": all_purchased_this_year_count,
|
|
|
|
"backlog_decrease_count": backlog_decrease_count,
|
|
|
|
"longest_session_time": (
|
|
|
|
format_duration(longest_session.duration, "%2.0Hh %2.0mm")
|
|
|
|
if longest_session
|
|
|
|
else 0
|
|
|
|
),
|
|
|
|
"longest_session_game": (
|
|
|
|
longest_session.purchase.edition.game if longest_session else None
|
|
|
|
),
|
|
|
|
"highest_session_count": (
|
|
|
|
game_highest_session_count.session_count
|
|
|
|
if game_highest_session_count
|
|
|
|
else 0
|
|
|
|
),
|
|
|
|
"highest_session_count_game": (
|
|
|
|
game_highest_session_count if game_highest_session_count else None
|
|
|
|
),
|
|
|
|
"highest_session_average": (
|
|
|
|
format_duration(
|
|
|
|
highest_session_average_game.session_average, "%2.0Hh %2.0mm"
|
|
|
|
)
|
|
|
|
if highest_session_average_game
|
|
|
|
else 0
|
|
|
|
),
|
|
|
|
"highest_session_average_game": highest_session_average_game,
|
|
|
|
"first_play_game": first_play_game,
|
|
|
|
"first_play_date": first_play_date,
|
|
|
|
"last_play_game": last_play_game,
|
|
|
|
"last_play_date": last_play_date,
|
|
|
|
"title": f"{year} Stats",
|
|
|
|
}
|
|
|
|
|
|
|
|
request.session["return_path"] = request.path
|
|
|
|
return render(request, "stats.html", context)
|
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2024-08-08 19:19:43 +00:00
|
|
|
def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
|
2023-11-02 08:20:09 +00:00
|
|
|
selected_year = request.GET.get("year")
|
|
|
|
if selected_year:
|
|
|
|
return HttpResponseRedirect(reverse("stats_by_year", args=[selected_year]))
|
|
|
|
if year == 0:
|
2024-08-04 20:40:37 +00:00
|
|
|
return HttpResponseRedirect(reverse("stats_alltime"))
|
2024-01-03 20:35:47 +00:00
|
|
|
this_year_sessions = Session.objects.filter(
|
|
|
|
timestamp_start__year=year
|
|
|
|
).select_related("purchase__edition")
|
2023-11-21 16:02:44 +00:00
|
|
|
this_year_sessions_with_durations = this_year_sessions.annotate(
|
|
|
|
duration=ExpressionWrapper(
|
|
|
|
F("timestamp_end") - F("timestamp_start"),
|
|
|
|
output_field=fields.DurationField(),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
longest_session = this_year_sessions_with_durations.order_by("-duration").first()
|
2024-01-01 17:21:50 +00:00
|
|
|
this_year_games = Game.objects.filter(
|
|
|
|
edition__purchase__session__in=this_year_sessions
|
|
|
|
).distinct()
|
|
|
|
this_year_games_with_session_counts = this_year_games.annotate(
|
2023-11-21 16:02:44 +00:00
|
|
|
session_count=Count(
|
|
|
|
"edition__purchase__session",
|
|
|
|
filter=Q(edition__purchase__session__timestamp_start__year=year),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
game_highest_session_count = this_year_games_with_session_counts.order_by(
|
|
|
|
"-session_count"
|
|
|
|
).first()
|
2023-11-09 18:35:57 +00:00
|
|
|
selected_currency = "CZK"
|
2023-11-08 15:24:22 +00:00
|
|
|
unique_days = (
|
2023-11-09 18:35:57 +00:00
|
|
|
this_year_sessions.annotate(date=TruncDate("timestamp_start"))
|
2023-11-08 15:24:22 +00:00
|
|
|
.values("date")
|
|
|
|
.distinct()
|
|
|
|
.aggregate(dates=Count("date"))
|
|
|
|
)
|
2023-11-09 18:35:57 +00:00
|
|
|
this_year_played_purchases = Purchase.objects.filter(
|
|
|
|
session__in=this_year_sessions
|
2023-11-02 19:12:32 +00:00
|
|
|
).distinct()
|
|
|
|
|
2023-11-09 18:35:57 +00:00
|
|
|
this_year_purchases = Purchase.objects.filter(date_purchased__year=year)
|
2024-01-03 20:35:47 +00:00
|
|
|
this_year_purchases_with_currency = this_year_purchases.select_related(
|
|
|
|
"edition"
|
|
|
|
).filter(price_currency__exact=selected_currency)
|
2023-11-09 18:35:57 +00:00
|
|
|
this_year_purchases_without_refunded = this_year_purchases_with_currency.filter(
|
|
|
|
date_refunded=None
|
2023-11-08 17:13:48 +00:00
|
|
|
)
|
2023-11-09 18:35:57 +00:00
|
|
|
this_year_purchases_refunded = this_year_purchases_with_currency.refunded()
|
2023-11-08 17:13:48 +00:00
|
|
|
|
2024-03-10 21:48:46 +00:00
|
|
|
this_year_purchases_unfinished_dropped_nondropped = (
|
2024-01-03 21:35:39 +00:00
|
|
|
this_year_purchases_without_refunded.filter(date_finished__isnull=True)
|
|
|
|
.filter(infinite=False)
|
|
|
|
.filter(Q(type=Purchase.GAME) | Q(type=Purchase.DLC))
|
2023-11-20 20:56:16 +00:00
|
|
|
) # do not count battle passes etc.
|
2023-11-09 09:06:14 +00:00
|
|
|
|
2024-03-10 21:48:46 +00:00
|
|
|
this_year_purchases_unfinished = (
|
|
|
|
this_year_purchases_unfinished_dropped_nondropped.filter(
|
|
|
|
date_dropped__isnull=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
this_year_purchases_dropped = (
|
|
|
|
this_year_purchases_unfinished_dropped_nondropped.filter(
|
|
|
|
date_dropped__isnull=False
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2024-01-03 20:35:47 +00:00
|
|
|
this_year_purchases_without_refunded_count = (
|
|
|
|
this_year_purchases_without_refunded.count()
|
|
|
|
)
|
|
|
|
this_year_purchases_unfinished_count = this_year_purchases_unfinished.count()
|
2023-11-09 18:35:57 +00:00
|
|
|
this_year_purchases_unfinished_percent = int(
|
2023-11-09 09:06:14 +00:00
|
|
|
safe_division(
|
2024-01-03 20:35:47 +00:00
|
|
|
this_year_purchases_unfinished_count,
|
|
|
|
this_year_purchases_without_refunded_count,
|
2023-11-09 08:18:49 +00:00
|
|
|
)
|
2023-11-09 09:06:14 +00:00
|
|
|
* 100
|
|
|
|
)
|
2023-11-02 19:12:32 +00:00
|
|
|
|
2023-11-09 18:35:57 +00:00
|
|
|
purchases_finished_this_year = Purchase.objects.filter(date_finished__year=year)
|
|
|
|
purchases_finished_this_year_released_this_year = (
|
|
|
|
purchases_finished_this_year.filter(edition__year_released=year).order_by(
|
|
|
|
"date_finished"
|
|
|
|
)
|
2023-11-08 17:13:48 +00:00
|
|
|
)
|
|
|
|
purchased_this_year_finished_this_year = (
|
2024-01-03 20:35:47 +00:00
|
|
|
this_year_purchases_without_refunded.filter(date_finished__year=year)
|
|
|
|
).order_by("date_finished")
|
2023-11-08 15:24:22 +00:00
|
|
|
|
2023-11-09 18:35:57 +00:00
|
|
|
this_year_spendings = this_year_purchases_without_refunded.aggregate(
|
2023-11-08 17:13:48 +00:00
|
|
|
total_spent=Sum(F("price"))
|
|
|
|
)
|
2023-11-12 07:01:12 +00:00
|
|
|
total_spent = this_year_spendings["total_spent"] or 0
|
2023-11-02 14:08:11 +00:00
|
|
|
|
|
|
|
games_with_playtime = (
|
2023-11-09 18:35:57 +00:00
|
|
|
Game.objects.filter(edition__purchase__session__in=this_year_sessions)
|
2023-11-02 14:08:11 +00:00
|
|
|
.annotate(
|
|
|
|
total_playtime=Sum(
|
|
|
|
F("edition__purchase__session__duration_calculated")
|
|
|
|
+ F("edition__purchase__session__duration_manual")
|
|
|
|
)
|
2023-11-01 19:18:39 +00:00
|
|
|
)
|
2023-11-02 14:08:11 +00:00
|
|
|
.values("id", "name", "total_playtime")
|
2023-11-01 19:18:39 +00:00
|
|
|
)
|
2024-04-02 06:18:58 +00:00
|
|
|
month_playtimes = (
|
|
|
|
this_year_sessions.annotate(month=TruncMonth("timestamp_start"))
|
|
|
|
.values("month")
|
|
|
|
.annotate(playtime=Sum("duration_calculated"))
|
|
|
|
.order_by("month")
|
|
|
|
)
|
|
|
|
for month in month_playtimes:
|
|
|
|
month["playtime"] = format_duration(month["playtime"], "%2.0H")
|
|
|
|
|
2023-11-21 20:57:17 +00:00
|
|
|
highest_session_average_game = (
|
|
|
|
Game.objects.filter(edition__purchase__session__in=this_year_sessions)
|
|
|
|
.annotate(
|
|
|
|
session_average=Avg("edition__purchase__session__duration_calculated")
|
|
|
|
)
|
|
|
|
.order_by("-session_average")
|
|
|
|
.first()
|
|
|
|
)
|
2023-11-02 14:08:11 +00:00
|
|
|
top_10_games_by_playtime = games_with_playtime.order_by("-total_playtime")[:10]
|
|
|
|
for game in top_10_games_by_playtime:
|
|
|
|
game["formatted_playtime"] = format_duration(game["total_playtime"], "%2.0H")
|
2023-11-01 19:18:39 +00:00
|
|
|
|
|
|
|
total_playtime_per_platform = (
|
2023-11-09 18:35:57 +00:00
|
|
|
this_year_sessions.values("purchase__platform__name")
|
2023-11-02 14:09:31 +00:00
|
|
|
.annotate(total_playtime=Sum(F("duration_calculated") + F("duration_manual")))
|
|
|
|
.annotate(platform_name=F("purchase__platform__name"))
|
|
|
|
.values("platform_name", "total_playtime")
|
|
|
|
.order_by("-total_playtime")
|
2023-11-01 19:18:39 +00:00
|
|
|
)
|
|
|
|
for item in total_playtime_per_platform:
|
|
|
|
item["formatted_playtime"] = format_duration(item["total_playtime"], "%2.0H")
|
|
|
|
|
2023-11-09 18:15:49 +00:00
|
|
|
backlog_decrease_count = (
|
|
|
|
Purchase.objects.filter(date_purchased__year__lt=year)
|
2023-11-09 18:35:57 +00:00
|
|
|
.intersection(purchases_finished_this_year)
|
2023-11-09 18:15:49 +00:00
|
|
|
.count()
|
|
|
|
)
|
|
|
|
|
2024-01-01 17:42:14 +00:00
|
|
|
first_play_date = "N/A"
|
|
|
|
last_play_date = "N/A"
|
2024-08-08 19:19:43 +00:00
|
|
|
first_play_game = None
|
|
|
|
last_play_game = None
|
2024-01-01 17:42:14 +00:00
|
|
|
if this_year_sessions:
|
|
|
|
first_session = this_year_sessions.earliest()
|
2024-07-09 17:40:47 +00:00
|
|
|
first_play_game = first_session.purchase.edition.game
|
2024-01-01 17:42:14 +00:00
|
|
|
first_play_date = first_session.timestamp_start.strftime("%x")
|
|
|
|
last_session = this_year_sessions.latest()
|
2024-07-09 17:40:47 +00:00
|
|
|
last_play_game = last_session.purchase.edition.game
|
2024-01-01 17:42:14 +00:00
|
|
|
last_play_date = last_session.timestamp_start.strftime("%x")
|
|
|
|
|
2024-01-03 20:35:47 +00:00
|
|
|
all_purchased_this_year_count = this_year_purchases_with_currency.count()
|
|
|
|
all_purchased_refunded_this_year_count = this_year_purchases_refunded.count()
|
2024-03-10 21:48:46 +00:00
|
|
|
|
|
|
|
this_year_purchases_dropped_count = this_year_purchases_dropped.count()
|
|
|
|
this_year_purchases_dropped_percentage = int(
|
|
|
|
safe_division(this_year_purchases_dropped_count, all_purchased_this_year_count)
|
|
|
|
* 100
|
|
|
|
)
|
2023-11-01 19:18:39 +00:00
|
|
|
context = {
|
|
|
|
"total_hours": format_duration(
|
2023-11-09 18:35:57 +00:00
|
|
|
this_year_sessions.total_duration_unformatted(), "%2.0H"
|
2023-11-01 19:18:39 +00:00
|
|
|
),
|
2023-11-09 18:35:57 +00:00
|
|
|
"total_games": this_year_played_purchases.count(),
|
|
|
|
"total_2023_games": this_year_played_purchases.filter(
|
2023-11-02 19:12:32 +00:00
|
|
|
edition__year_released=year
|
|
|
|
).count(),
|
2023-11-02 14:08:11 +00:00
|
|
|
"top_10_games_by_playtime": top_10_games_by_playtime,
|
2023-11-01 19:18:39 +00:00
|
|
|
"year": year,
|
|
|
|
"total_playtime_per_platform": total_playtime_per_platform,
|
2023-11-02 19:12:32 +00:00
|
|
|
"total_spent": total_spent,
|
|
|
|
"total_spent_currency": selected_currency,
|
2023-11-09 18:35:57 +00:00
|
|
|
"all_purchased_this_year": this_year_purchases_without_refunded,
|
2023-11-08 17:13:48 +00:00
|
|
|
"spent_per_game": int(
|
2024-01-03 20:35:47 +00:00
|
|
|
safe_division(total_spent, this_year_purchases_without_refunded_count)
|
2023-11-09 20:43:17 +00:00
|
|
|
),
|
2024-01-03 20:35:47 +00:00
|
|
|
"all_finished_this_year": purchases_finished_this_year.select_related(
|
|
|
|
"edition"
|
|
|
|
).order_by("date_finished"),
|
|
|
|
"all_finished_this_year_count": purchases_finished_this_year.count(),
|
|
|
|
"this_year_finished_this_year": purchases_finished_this_year_released_this_year.select_related(
|
|
|
|
"edition"
|
2024-08-11 16:34:50 +00:00
|
|
|
).order_by("date_finished"),
|
2024-01-03 20:35:47 +00:00
|
|
|
"this_year_finished_this_year_count": purchases_finished_this_year_released_this_year.count(),
|
|
|
|
"purchased_this_year_finished_this_year": purchased_this_year_finished_this_year.select_related(
|
|
|
|
"edition"
|
2024-08-11 16:34:50 +00:00
|
|
|
).order_by("date_finished"),
|
2023-11-09 18:35:57 +00:00
|
|
|
"total_sessions": this_year_sessions.count(),
|
2023-11-08 15:24:22 +00:00
|
|
|
"unique_days": unique_days["dates"],
|
|
|
|
"unique_days_percent": int(unique_days["dates"] / 365 * 100),
|
2023-11-09 18:35:57 +00:00
|
|
|
"purchased_unfinished": this_year_purchases_unfinished,
|
2024-01-03 20:35:47 +00:00
|
|
|
"purchased_unfinished_count": this_year_purchases_unfinished_count,
|
2023-11-09 18:35:57 +00:00
|
|
|
"unfinished_purchases_percent": this_year_purchases_unfinished_percent,
|
2024-03-10 21:48:46 +00:00
|
|
|
"dropped_count": this_year_purchases_dropped_count,
|
|
|
|
"dropped_percentage": this_year_purchases_dropped_percentage,
|
2023-11-08 17:13:48 +00:00
|
|
|
"refunded_percent": int(
|
2023-11-09 09:06:14 +00:00
|
|
|
safe_division(
|
2024-01-03 20:35:47 +00:00
|
|
|
all_purchased_refunded_this_year_count,
|
|
|
|
all_purchased_this_year_count,
|
2023-11-09 09:06:14 +00:00
|
|
|
)
|
2023-11-08 17:13:48 +00:00
|
|
|
* 100
|
|
|
|
),
|
2023-11-09 18:35:57 +00:00
|
|
|
"all_purchased_refunded_this_year": this_year_purchases_refunded,
|
2024-01-03 20:35:47 +00:00
|
|
|
"all_purchased_refunded_this_year_count": all_purchased_refunded_this_year_count,
|
2023-11-09 18:35:57 +00:00
|
|
|
"all_purchased_this_year": this_year_purchases_with_currency.order_by(
|
|
|
|
"date_purchased"
|
|
|
|
),
|
2024-01-03 20:35:47 +00:00
|
|
|
"all_purchased_this_year_count": all_purchased_this_year_count,
|
2023-11-09 18:15:49 +00:00
|
|
|
"backlog_decrease_count": backlog_decrease_count,
|
2024-02-18 08:03:35 +00:00
|
|
|
"longest_session_time": (
|
|
|
|
format_duration(longest_session.duration, "%2.0Hh %2.0mm")
|
|
|
|
if longest_session
|
|
|
|
else 0
|
|
|
|
),
|
|
|
|
"longest_session_game": (
|
2024-07-09 17:40:47 +00:00
|
|
|
longest_session.purchase.edition.game if longest_session else None
|
2024-02-18 08:03:35 +00:00
|
|
|
),
|
|
|
|
"highest_session_count": (
|
|
|
|
game_highest_session_count.session_count
|
|
|
|
if game_highest_session_count
|
|
|
|
else 0
|
|
|
|
),
|
|
|
|
"highest_session_count_game": (
|
2024-07-09 17:40:47 +00:00
|
|
|
game_highest_session_count if game_highest_session_count else None
|
2024-02-18 08:03:35 +00:00
|
|
|
),
|
|
|
|
"highest_session_average": (
|
|
|
|
format_duration(
|
|
|
|
highest_session_average_game.session_average, "%2.0Hh %2.0mm"
|
|
|
|
)
|
|
|
|
if highest_session_average_game
|
|
|
|
else 0
|
|
|
|
),
|
2023-11-21 20:57:17 +00:00
|
|
|
"highest_session_average_game": highest_session_average_game,
|
2024-07-09 17:40:47 +00:00
|
|
|
"first_play_game": first_play_game,
|
2024-01-01 17:42:14 +00:00
|
|
|
"first_play_date": first_play_date,
|
2024-07-09 17:40:47 +00:00
|
|
|
"last_play_game": last_play_game,
|
2024-01-01 17:42:14 +00:00
|
|
|
"last_play_date": last_play_date,
|
2023-12-15 09:58:15 +00:00
|
|
|
"title": f"{year} Stats",
|
2024-04-02 06:18:58 +00:00
|
|
|
"month_playtimes": month_playtimes,
|
2023-11-01 19:18:39 +00:00
|
|
|
}
|
|
|
|
|
2023-11-05 14:09:51 +00:00
|
|
|
request.session["return_path"] = request.path
|
2023-11-01 19:18:39 +00:00
|
|
|
return render(request, "stats.html", context)
|
|
|
|
|
2024-06-03 16:19:11 +00:00
|
|
|
|
2024-04-29 14:20:44 +00:00
|
|
|
@login_required
|
2024-08-08 19:19:43 +00:00
|
|
|
def delete_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
|
2024-04-29 14:20:44 +00:00
|
|
|
purchase = get_object_or_404(Purchase, id=purchase_id)
|
|
|
|
purchase.delete()
|
|
|
|
return redirect("list_sessions")
|
2023-11-01 19:18:39 +00:00
|
|
|
|
2024-06-03 16:19:11 +00:00
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2024-08-09 11:12:47 +00:00
|
|
|
def add_purchase(request: HttpRequest, edition_id: int = 0) -> HttpResponse:
|
2024-08-08 19:19:43 +00:00
|
|
|
context: dict[str, Any] = {}
|
2023-11-15 16:47:51 +00:00
|
|
|
initial = {"date_purchased": timezone.now()}
|
2023-11-09 20:01:01 +00:00
|
|
|
|
|
|
|
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)
|
2022-12-31 13:18:27 +00:00
|
|
|
|
|
|
|
context["form"] = form
|
2023-01-03 21:04:36 +00:00
|
|
|
context["title"] = "Add New Purchase"
|
2023-11-09 13:49:00 +00:00
|
|
|
context["script_name"] = "add_purchase.js"
|
2023-11-09 20:01:01 +00:00
|
|
|
return render(request, "add_purchase.html", context)
|
2022-12-31 13:18:27 +00:00
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2024-08-08 19:19:43 +00:00
|
|
|
def add_game(request: HttpRequest) -> HttpResponse:
|
|
|
|
context: dict[str, Any] = {}
|
2022-12-31 13:18:27 +00:00
|
|
|
form = GameForm(request.POST or None)
|
|
|
|
if form.is_valid():
|
2023-11-09 20:01:01 +00:00
|
|
|
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")
|
2022-12-31 13:18:27 +00:00
|
|
|
|
|
|
|
context["form"] = form
|
|
|
|
context["title"] = "Add New Game"
|
2023-11-09 13:49:00 +00:00
|
|
|
context["script_name"] = "add_game.js"
|
2023-11-09 20:01:01 +00:00
|
|
|
return render(request, "add_game.html", context)
|
2023-01-04 16:22:36 +00:00
|
|
|
|
2023-01-04 16:23:34 +00:00
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2024-08-09 11:12:47 +00:00
|
|
|
def add_edition(request: HttpRequest, game_id: int = 0) -> HttpResponse:
|
2024-08-08 19:19:43 +00:00
|
|
|
context: dict[str, Any] = {}
|
2023-11-09 20:01:01 +00:00
|
|
|
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:
|
2024-08-08 19:19:43 +00:00
|
|
|
game = get_object_or_404(Game, id=game_id)
|
2023-11-09 20:01:01 +00:00
|
|
|
form = EditionForm(
|
2023-11-09 20:20:12 +00:00
|
|
|
initial={
|
|
|
|
"game": game,
|
|
|
|
"name": game.name,
|
|
|
|
"sort_name": game.sort_name,
|
|
|
|
"year_released": game.year_released,
|
|
|
|
}
|
2023-11-09 20:01:01 +00:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
form = EditionForm()
|
2023-02-18 19:49:46 +00:00
|
|
|
|
|
|
|
context["form"] = form
|
|
|
|
context["title"] = "Add New Edition"
|
2023-11-09 13:49:00 +00:00
|
|
|
context["script_name"] = "add_edition.js"
|
2023-11-09 20:01:01 +00:00
|
|
|
return render(request, "add_edition.html", context)
|
2023-02-18 19:49:46 +00:00
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2024-08-08 19:19:43 +00:00
|
|
|
def add_platform(request: HttpRequest) -> HttpResponse:
|
|
|
|
context: dict[str, Any] = {}
|
2023-01-04 16:23:34 +00:00
|
|
|
form = PlatformForm(request.POST or None)
|
|
|
|
if form.is_valid():
|
|
|
|
form.save()
|
2023-02-18 19:50:36 +00:00
|
|
|
return redirect("index")
|
2023-01-04 16:23:34 +00:00
|
|
|
|
|
|
|
context["form"] = form
|
|
|
|
context["title"] = "Add New Platform"
|
|
|
|
return render(request, "add.html", context)
|
2023-02-18 19:50:36 +00:00
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2024-08-08 19:19:43 +00:00
|
|
|
def add_device(request: HttpRequest) -> HttpResponse:
|
|
|
|
context: dict[str, Any] = {}
|
2023-02-18 20:12:18 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2024-02-09 21:03:18 +00:00
|
|
|
@login_required
|
2024-08-08 19:19:43 +00:00
|
|
|
def index(request: HttpRequest) -> HttpResponse:
|
2024-08-11 15:58:08 +00:00
|
|
|
return redirect("list_sessions")
|