Add more stats
continuous-integration/drone/push Build is passing Details

* Finished (count)
* Unfinished (count)
* Refunded (count)
This commit is contained in:
Lukáš Kucharczyk 2023-11-08 18:13:48 +01:00
parent 5052ca7dbf
commit e7ed349356
5 changed files with 119 additions and 52 deletions

View File

@ -17,8 +17,11 @@
* All finished games * All finished games
* All finished 2023 games * All finished 2023 games
* All finished games that were purchased this year * All finished games that were purchased this year
* Total sessions * Sessions (count)
* Days played * Days played
* Finished (count)
* Unfinished (count)
* Refunded (count)
### Improved ### Improved
* game overview: simplify playtime range display * game overview: simplify playtime range display

View File

@ -33,6 +33,17 @@ class Edition(models.Model):
return self.name return self.name
class PurchaseQueryset(models.QuerySet):
def refunded(self):
return self.filter(date_refunded__isnull=False)
def not_refunded(self):
return self.filter(date_refunded__isnull=True)
def finished(self):
return self.filter(date_finished__isnull=False)
class Purchase(models.Model): class Purchase(models.Model):
PHYSICAL = "ph" PHYSICAL = "ph"
DIGITAL = "di" DIGITAL = "di"
@ -53,6 +64,8 @@ class Purchase(models.Model):
(PIRATED, "Pirated"), (PIRATED, "Pirated"),
] ]
objects = PurchaseQueryset().as_manager()
edition = models.ForeignKey("Edition", on_delete=models.CASCADE) edition = models.ForeignKey("Edition", on_delete=models.CASCADE)
platform = models.ForeignKey( platform = models.ForeignKey(
"Platform", on_delete=models.CASCADE, default=None, null=True, blank=True "Platform", on_delete=models.CASCADE, default=None, null=True, blank=True

View File

@ -1438,6 +1438,10 @@ th label {
display: block; display: block;
} }
.md\:w-1\/2 {
width: 50%;
}
.md\:w-auto { .md\:w-auto {
width: auto; width: auto;
} }

View File

@ -16,47 +16,62 @@
</select> </select>
</form> </form>
</div> </div>
<table class="responsive-table"> <div class="flex flex-column flex-wrap justify-center">
<thead> <div class="md:w-1/2">
<tr> <h1 class="text-5xl text-center my-6">Playtime</h1>
<th class="px-2 sm:px-4 md:px-6 md:py-2">Statistic</th> <table class="responsive-table">
<th class="px-2 sm:px-4 md:px-6 md:py-2">Value</th> <tbody>
</tr> <tr>
<tbody> <td class="px-2 sm:px-4 md:px-6 md:py-2">Hours</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_hours }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Hours</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_hours }}</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Sessions</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_sessions }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Sessions</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_sessions }}</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Days</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ unique_days }} ({{ unique_days_percent }}%)</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Days Played</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ unique_days }} ({{ unique_days_percent }}%)</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Games</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_games }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Games</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_games }}</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Games ({{ year }})</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_2023_games }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Games ({{ year }})</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_2023_games }}</td> <tr>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2">Finished</td>
<tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_finished_this_year.count }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Purchases</td> </tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_purchased_this_year.count }}</td> </tbody>
</tr> </table>
<tr> </div>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Spendings ({{ total_spent_currency }})</td> <div class="md:w-1/2">
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_spent }}</td> <h1 class="text-5xl text-center my-6">Purchases</h1>
</tr> <table class="responsive-table">
<tr> <tbody>
<td class="px-2 sm:px-4 md:px-6 md:py-2">{{ total_spent_currency }}/game</td> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ spent_per_game }}</td> <td class="px-2 sm:px-4 md:px-6 md:py-2">Total</td>
</tr> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_purchased_this_year.count }}</td>
</tbody> </tr>
</table> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Refunded</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_purchased_refunded_this_year.count }} ({{ refunded_percent }}%)</td>
</tr>
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Unfinished</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchased_unfinished.count }} ({{ unfinished_purchases_percent }}%)</td>
</tr>
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Spendings ({{ total_spent_currency }})</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ total_spent }} ({{ spent_per_game }}/game)</td>
</tr>
</tbody>
</table>
</div>
</div>
<h1 class="text-5xl text-center my-6">Top games by playtime</h1> <h1 class="text-5xl text-center my-6">Top games by playtime</h1>
<table class="responsive-table"> <table class="responsive-table">
<thead> <thead>

View File

@ -291,19 +291,40 @@ def stats(request, year: int = 0):
all_purchased_this_year = ( all_purchased_this_year = (
Purchase.objects.filter(date_purchased__year=year) Purchase.objects.filter(date_purchased__year=year)
.filter(price_currency__exact=selected_currency) .filter(price_currency__exact=selected_currency)
.filter(date_refunded__exact=None) .order_by("date_purchased")
)
all_purchased_without_refunded_this_year = all_purchased_this_year.not_refunded()
all_purchased_refunded_this_year = (
Purchase.objects.filter(date_purchased__year=year)
.filter(price_currency__exact=selected_currency)
.refunded()
.order_by("date_purchased") .order_by("date_purchased")
) )
all_finished_this_year = Purchase.objects.filter(date_finished__year=year) purchased_unfinished = all_purchased_without_refunded_this_year.filter(
this_year_finished_this_year = Purchase.objects.filter( date_finished__isnull=True
date_finished__year=year )
).filter(edition__year_released=year) unfinished_purchases_percent = int(
purchased_this_year_finished_this_year = all_purchased_this_year.filter( purchased_unfinished.count() / all_purchased_refunded_this_year.count() * 100
date_finished__year=year
) )
this_year_spendings = all_purchased_this_year.aggregate(total_spent=Sum(F("price"))) all_finished_this_year = Purchase.objects.filter(date_finished__year=year).order_by(
"date_finished"
)
this_year_finished_this_year = (
Purchase.objects.filter(date_finished__year=year)
.filter(edition__year_released=year)
.order_by("date_finished")
)
purchased_this_year_finished_this_year = (
all_purchased_without_refunded_this_year.filter(
date_finished__year=year
).order_by("date_finished")
)
this_year_spendings = all_purchased_without_refunded_this_year.aggregate(
total_spent=Sum(F("price"))
)
total_spent = this_year_spendings["total_spent"] total_spent = this_year_spendings["total_spent"]
games_with_playtime = ( games_with_playtime = (
@ -343,14 +364,25 @@ def stats(request, year: int = 0):
"total_playtime_per_platform": total_playtime_per_platform, "total_playtime_per_platform": total_playtime_per_platform,
"total_spent": total_spent, "total_spent": total_spent,
"total_spent_currency": selected_currency, "total_spent_currency": selected_currency,
"all_purchased_this_year": all_purchased_this_year, "all_purchased_this_year": all_purchased_without_refunded_this_year,
"spent_per_game": int(total_spent / all_purchased_this_year.count()), "spent_per_game": int(
total_spent / all_purchased_without_refunded_this_year.count()
),
"all_finished_this_year": all_finished_this_year, "all_finished_this_year": all_finished_this_year,
"this_year_finished_this_year": this_year_finished_this_year, "this_year_finished_this_year": this_year_finished_this_year,
"purchased_this_year_finished_this_year": purchased_this_year_finished_this_year, "purchased_this_year_finished_this_year": purchased_this_year_finished_this_year,
"total_sessions": year_sessions.count(), "total_sessions": year_sessions.count(),
"unique_days": unique_days["dates"], "unique_days": unique_days["dates"],
"unique_days_percent": int(unique_days["dates"] / 365 * 100), "unique_days_percent": int(unique_days["dates"] / 365 * 100),
"purchased_unfinished": purchased_unfinished,
"unfinished_purchases_percent": unfinished_purchases_percent,
"refunded_percent": int(
all_purchased_refunded_this_year.count()
/ all_purchased_this_year.count()
* 100
),
"all_purchased_refunded_this_year": all_purchased_refunded_this_year,
"all_purchased_this_year": all_purchased_this_year,
} }
request.session["return_path"] = request.path request.session["return_path"] = request.path