Streamline evaluating game status
This commit is contained in:
+16
-2
@@ -4,7 +4,7 @@ from datetime import timedelta
|
||||
import requests
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models import F, Sum
|
||||
from django.db.models import F, Q, Sum
|
||||
from django.db.models.expressions import RawSQL
|
||||
from django.db.models.fields.generated import GeneratedField
|
||||
from django.db.models.functions import Coalesce
|
||||
@@ -66,7 +66,8 @@ class Game(models.Model):
|
||||
return self.name
|
||||
|
||||
def finished(self):
|
||||
return self.status == self.Status.FINISHED
|
||||
return (self.status == self.Status.FINISHED or
|
||||
self.playevents.filter(ended__isnull=False).exists())
|
||||
|
||||
def abandoned(self):
|
||||
return self.status == self.Status.ABANDONED
|
||||
@@ -120,6 +121,19 @@ class PurchaseQueryset(models.QuerySet):
|
||||
def games_only(self):
|
||||
return self.filter(type=Purchase.GAME)
|
||||
|
||||
def finished(self):
|
||||
return self.filter(
|
||||
Q(games__status="f") | Q(games__playevents__ended__isnull=False)
|
||||
).distinct()
|
||||
|
||||
def abandoned(self):
|
||||
return self.filter(games__status="a").distinct()
|
||||
|
||||
def dropped(self):
|
||||
return self.filter(
|
||||
Q(games__status="a") | Q(date_refunded__isnull=False)
|
||||
).distinct()
|
||||
|
||||
|
||||
class Purchase(models.Model):
|
||||
PHYSICAL = "ph"
|
||||
|
||||
+55
-25
@@ -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 = (
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user