Streamline evaluating game status

This commit is contained in:
2026-05-12 12:44:06 +02:00
parent 913c7d3a98
commit bf2b86ba1f
3 changed files with 77 additions and 31 deletions
+55 -25
View File
@@ -2,9 +2,9 @@ from datetime import datetime, timedelta
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, Max, OuterRef, Prefetch, Q, Subquery, Sum, fields
from django.db.models.functions import TruncDate, TruncMonth
from django.db.models.manager import BaseManager
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect, render
from django.urls import reverse
@@ -90,26 +90,34 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
this_year_purchases = Purchase.objects.all()
this_year_purchases_with_currency = this_year_purchases.select_related("games")
this_year_purchases_without_refunded = this_year_purchases_with_currency.filter(
this_year_purchases_without_refunded = Purchase.objects.filter(
date_refunded=None
)
this_year_purchases_refunded = this_year_purchases_with_currency.refunded()
this_year_purchases_refunded = Purchase.objects.refunded()
this_year_purchases_unfinished_dropped_nondropped = (
this_year_purchases_without_refunded.filter(date_finished__isnull=True)
this_year_purchases_without_refunded.filter(
~Q(games__status="f")
& ~Q(games__playevents__ended__isnull=False)
)
.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
~Q(games__status="r")
& ~Q(games__status="a")
)
)
this_year_purchases_dropped = (
this_year_purchases_unfinished_dropped_nondropped.filter(
date_dropped__isnull=False
this_year_purchases.filter(
~Q(games__status="f")
& ~Q(games__playevents__ended__isnull=False)
)
.filter(Q(games__status="a") | Q(date_refunded__isnull=False))
.filter(infinite=False)
.filter(Q(type=Purchase.GAME) | Q(type=Purchase.DLC))
)
this_year_purchases_without_refunded_count = (
@@ -124,13 +132,28 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
* 100
)
purchases_finished_this_year: BaseManager[Purchase] = Purchase.objects.finished()
purchases_finished_this_year_released_this_year = (
purchases_finished_this_year.all().order_by("date_finished")
_finished_purchases_qs = Purchase.objects.finished()
_finished_with_date = _finished_purchases_qs.annotate(
date_finished=Subquery(
Purchase.objects.filter(pk=OuterRef("pk"))
.annotate(max_ended=Max("games__playevents__ended"))
.values("max_ended")[:1]
)
)
purchases_finished_this_year = _finished_with_date
purchases_finished_this_year_released_this_year = _finished_with_date.order_by(
"-date_finished"
)
purchased_this_year_finished_this_year = (
this_year_purchases_without_refunded.all()
).order_by("date_finished")
this_year_purchases_without_refunded.filter(pk__in=_finished_purchases_qs.values("pk"))
.annotate(
date_finished=Subquery(
Purchase.objects.filter(pk=OuterRef("pk"))
.annotate(max_ended=Max("games__playevents__ended"))
.values("max_ended")[:1]
)
)
).order_by("-date_finished")
this_year_spendings = this_year_purchases_without_refunded.aggregate(
total_spent=Sum(F("converted_price"))
@@ -139,7 +162,9 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
games_with_playtime = Game.objects.filter(
sessions__in=this_year_sessions
).distinct()
).distinct().annotate(
total_playtime=Sum(F("sessions__duration_total"))
).filter(total_playtime__gt=timedelta(0))
month_playtimes = (
this_year_sessions.annotate(month=TruncMonth("timestamp_start"))
.values("month")
@@ -166,7 +191,7 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
)
backlog_decrease_count = (
Purchase.objects.all().intersection(purchases_finished_this_year).count()
purchases_finished_this_year.count()
)
first_play_date = "N/A"
@@ -310,25 +335,30 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
# not infinite
# only Game and DLC
this_year_purchases_unfinished_dropped_nondropped = (
this_year_purchases_without_refunded.exclude(
games__in=Game.objects.filter(status="f")
this_year_purchases_without_refunded.filter(
~Q(games__status="f")
& ~Q(games__playevents__ended__year=year)
)
.filter(infinite=False)
.filter(Q(type=Purchase.GAME) | Q(type=Purchase.DLC))
)
# not finished
# unfinished = not finished AND not dropped
this_year_purchases_unfinished = (
this_year_purchases_unfinished_dropped_nondropped.exclude(
games__status__in="ura"
this_year_purchases_unfinished_dropped_nondropped.filter(
~Q(games__status="r")
& ~Q(games__status="a")
)
)
# abandoned
# retired
# dropped = abandoned OR retired OR refunded (OR logic for transition)
this_year_purchases_dropped = (
this_year_purchases_unfinished_dropped_nondropped.exclude(
games__in=Game.objects.filter(status="ar")
this_year_purchases.filter(
~Q(games__status="f")
& ~Q(games__playevents__ended__year=year)
)
.filter(Q(games__status="a") | Q(date_refunded__isnull=False))
.filter(infinite=False)
.filter(Q(type=Purchase.GAME) | Q(type=Purchase.DLC))
)
this_year_purchases_without_refunded_count = (
@@ -343,7 +373,7 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
* 100
)
purchases_finished_this_year = Purchase.objects.filter(
purchases_finished_this_year = Purchase.objects.finished().filter(
games__playevents__ended__year=year
).annotate(game_name=F("games__name"), date_finished=F("games__playevents__ended"))
purchases_finished_this_year_released_this_year = (
+6 -4
View File
@@ -190,8 +190,9 @@ def view_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
@login_required
def drop_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
purchase = get_object_or_404(Purchase, id=purchase_id)
purchase.date_dropped = timezone.now()
purchase.save()
for game in purchase.games.all():
game.status = Game.Status.ABANDONED
game.save()
return redirect("games:list_purchases")
@@ -233,8 +234,9 @@ def refund_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
@login_required
def finish_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
purchase = get_object_or_404(Purchase, id=purchase_id)
purchase.date_finished = timezone.now()
purchase.save()
for game in purchase.games.all():
game.status = Game.Status.FINISHED
game.save()
return redirect("games:list_purchases")