Streamline evaluating game status
This commit is contained in:
+16
-2
@@ -4,7 +4,7 @@ from datetime import timedelta
|
|||||||
import requests
|
import requests
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
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.expressions import RawSQL
|
||||||
from django.db.models.fields.generated import GeneratedField
|
from django.db.models.fields.generated import GeneratedField
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
@@ -66,7 +66,8 @@ class Game(models.Model):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def finished(self):
|
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):
|
def abandoned(self):
|
||||||
return self.status == self.Status.ABANDONED
|
return self.status == self.Status.ABANDONED
|
||||||
@@ -120,6 +121,19 @@ class PurchaseQueryset(models.QuerySet):
|
|||||||
def games_only(self):
|
def games_only(self):
|
||||||
return self.filter(type=Purchase.GAME)
|
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):
|
class Purchase(models.Model):
|
||||||
PHYSICAL = "ph"
|
PHYSICAL = "ph"
|
||||||
|
|||||||
+55
-25
@@ -2,9 +2,9 @@ from datetime import datetime, timedelta
|
|||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db.models import Avg, Count, ExpressionWrapper, F, 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.functions import TruncDate, TruncMonth
|
||||||
from django.db.models.manager import BaseManager
|
|
||||||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -90,26 +90,34 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
|
|||||||
|
|
||||||
this_year_purchases = Purchase.objects.all()
|
this_year_purchases = Purchase.objects.all()
|
||||||
this_year_purchases_with_currency = this_year_purchases.select_related("games")
|
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
|
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_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(infinite=False)
|
||||||
.filter(Q(type=Purchase.GAME) | Q(type=Purchase.DLC))
|
.filter(Q(type=Purchase.GAME) | Q(type=Purchase.DLC))
|
||||||
) # do not count battle passes etc.
|
) # do not count battle passes etc.
|
||||||
|
|
||||||
this_year_purchases_unfinished = (
|
this_year_purchases_unfinished = (
|
||||||
this_year_purchases_unfinished_dropped_nondropped.filter(
|
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_dropped = (
|
||||||
this_year_purchases_unfinished_dropped_nondropped.filter(
|
this_year_purchases.filter(
|
||||||
date_dropped__isnull=False
|
~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 = (
|
this_year_purchases_without_refunded_count = (
|
||||||
@@ -124,13 +132,28 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
|
|||||||
* 100
|
* 100
|
||||||
)
|
)
|
||||||
|
|
||||||
purchases_finished_this_year: BaseManager[Purchase] = Purchase.objects.finished()
|
_finished_purchases_qs = Purchase.objects.finished()
|
||||||
purchases_finished_this_year_released_this_year = (
|
_finished_with_date = _finished_purchases_qs.annotate(
|
||||||
purchases_finished_this_year.all().order_by("date_finished")
|
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 = (
|
purchased_this_year_finished_this_year = (
|
||||||
this_year_purchases_without_refunded.all()
|
this_year_purchases_without_refunded.filter(pk__in=_finished_purchases_qs.values("pk"))
|
||||||
).order_by("date_finished")
|
.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(
|
this_year_spendings = this_year_purchases_without_refunded.aggregate(
|
||||||
total_spent=Sum(F("converted_price"))
|
total_spent=Sum(F("converted_price"))
|
||||||
@@ -139,7 +162,9 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
|
|||||||
|
|
||||||
games_with_playtime = Game.objects.filter(
|
games_with_playtime = Game.objects.filter(
|
||||||
sessions__in=this_year_sessions
|
sessions__in=this_year_sessions
|
||||||
).distinct()
|
).distinct().annotate(
|
||||||
|
total_playtime=Sum(F("sessions__duration_total"))
|
||||||
|
).filter(total_playtime__gt=timedelta(0))
|
||||||
month_playtimes = (
|
month_playtimes = (
|
||||||
this_year_sessions.annotate(month=TruncMonth("timestamp_start"))
|
this_year_sessions.annotate(month=TruncMonth("timestamp_start"))
|
||||||
.values("month")
|
.values("month")
|
||||||
@@ -166,7 +191,7 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
backlog_decrease_count = (
|
backlog_decrease_count = (
|
||||||
Purchase.objects.all().intersection(purchases_finished_this_year).count()
|
purchases_finished_this_year.count()
|
||||||
)
|
)
|
||||||
|
|
||||||
first_play_date = "N/A"
|
first_play_date = "N/A"
|
||||||
@@ -310,25 +335,30 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
|
|||||||
# not infinite
|
# not infinite
|
||||||
# only Game and DLC
|
# only Game and DLC
|
||||||
this_year_purchases_unfinished_dropped_nondropped = (
|
this_year_purchases_unfinished_dropped_nondropped = (
|
||||||
this_year_purchases_without_refunded.exclude(
|
this_year_purchases_without_refunded.filter(
|
||||||
games__in=Game.objects.filter(status="f")
|
~Q(games__status="f")
|
||||||
|
& ~Q(games__playevents__ended__year=year)
|
||||||
)
|
)
|
||||||
.filter(infinite=False)
|
.filter(infinite=False)
|
||||||
.filter(Q(type=Purchase.GAME) | Q(type=Purchase.DLC))
|
.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 = (
|
||||||
this_year_purchases_unfinished_dropped_nondropped.exclude(
|
this_year_purchases_unfinished_dropped_nondropped.filter(
|
||||||
games__status__in="ura"
|
~Q(games__status="r")
|
||||||
|
& ~Q(games__status="a")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# abandoned
|
# dropped = abandoned OR retired OR refunded (OR logic for transition)
|
||||||
# retired
|
|
||||||
this_year_purchases_dropped = (
|
this_year_purchases_dropped = (
|
||||||
this_year_purchases_unfinished_dropped_nondropped.exclude(
|
this_year_purchases.filter(
|
||||||
games__in=Game.objects.filter(status="ar")
|
~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 = (
|
this_year_purchases_without_refunded_count = (
|
||||||
@@ -343,7 +373,7 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
|
|||||||
* 100
|
* 100
|
||||||
)
|
)
|
||||||
|
|
||||||
purchases_finished_this_year = Purchase.objects.filter(
|
purchases_finished_this_year = Purchase.objects.finished().filter(
|
||||||
games__playevents__ended__year=year
|
games__playevents__ended__year=year
|
||||||
).annotate(game_name=F("games__name"), date_finished=F("games__playevents__ended"))
|
).annotate(game_name=F("games__name"), date_finished=F("games__playevents__ended"))
|
||||||
purchases_finished_this_year_released_this_year = (
|
purchases_finished_this_year_released_this_year = (
|
||||||
|
|||||||
@@ -190,8 +190,9 @@ def view_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
|
|||||||
@login_required
|
@login_required
|
||||||
def drop_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
|
def drop_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
|
||||||
purchase = get_object_or_404(Purchase, id=purchase_id)
|
purchase = get_object_or_404(Purchase, id=purchase_id)
|
||||||
purchase.date_dropped = timezone.now()
|
for game in purchase.games.all():
|
||||||
purchase.save()
|
game.status = Game.Status.ABANDONED
|
||||||
|
game.save()
|
||||||
return redirect("games:list_purchases")
|
return redirect("games:list_purchases")
|
||||||
|
|
||||||
|
|
||||||
@@ -233,8 +234,9 @@ def refund_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
|
|||||||
@login_required
|
@login_required
|
||||||
def finish_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
|
def finish_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
|
||||||
purchase = get_object_or_404(Purchase, id=purchase_id)
|
purchase = get_object_or_404(Purchase, id=purchase_id)
|
||||||
purchase.date_finished = timezone.now()
|
for game in purchase.games.all():
|
||||||
purchase.save()
|
game.status = Game.Status.FINISHED
|
||||||
|
game.save()
|
||||||
return redirect("games:list_purchases")
|
return redirect("games:list_purchases")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user