Compare commits
5 Commits
purchase_s
...
242833f886
Author | SHA1 | Date | |
---|---|---|---|
242833f886
|
|||
0cdfd3c298
|
|||
a98b4839dd
|
|||
1999f13cf2
|
|||
8466f67c86
|
@ -83,6 +83,7 @@ class PurchaseForm(forms.ModelForm):
|
||||
"date_purchased": custom_date_widget,
|
||||
"date_refunded": custom_date_widget,
|
||||
"date_finished": custom_date_widget,
|
||||
"date_dropped": custom_date_widget,
|
||||
}
|
||||
model = Purchase
|
||||
fields = [
|
||||
@ -91,6 +92,8 @@ class PurchaseForm(forms.ModelForm):
|
||||
"date_purchased",
|
||||
"date_refunded",
|
||||
"date_finished",
|
||||
"date_dropped",
|
||||
"infinite",
|
||||
"price",
|
||||
"price_currency",
|
||||
"ownership_type",
|
||||
|
@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.2.7 on 2024-01-03 21:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("games", "0033_alter_edition_unique_together"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="purchase",
|
||||
name="date_dropped",
|
||||
field=models.DateField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="purchase",
|
||||
name="infinite",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
@ -116,6 +116,8 @@ class Purchase(models.Model):
|
||||
date_purchased = models.DateField()
|
||||
date_refunded = models.DateField(blank=True, null=True)
|
||||
date_finished = models.DateField(blank=True, null=True)
|
||||
date_dropped = models.DateField(blank=True, null=True)
|
||||
infinite = models.BooleanField(default=False)
|
||||
price = models.IntegerField(default=0)
|
||||
price_currency = models.CharField(max_length=3, default="USD")
|
||||
ownership_type = models.CharField(
|
||||
|
@ -43,11 +43,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2">Finished</td>
|
||||
<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 font-mono">{{ all_finished_this_year_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2">Finished ({{ year }})</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ this_year_finished_this_year.count }}</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ this_year_finished_this_year_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2">Longest session</td>
|
||||
@ -63,6 +63,14 @@
|
||||
{{ highest_session_average }} ({{ highest_session_average_game }})
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2">First play</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ first_play_name }} ({{ first_play_date }})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2">Last play</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ last_play_name }} ({{ last_play_date }})</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h1 class="text-5xl text-center my-6">Purchases</h1>
|
||||
@ -70,18 +78,18 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2">Total</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_purchased_this_year.count }}</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_purchased_this_year_count }}</td>
|
||||
</tr>
|
||||
<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 }}%)
|
||||
{{ 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 }}%)
|
||||
{{ purchased_unfinished_count }} ({{ unfinished_purchases_percent }}%)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -197,6 +205,33 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h1 class="text-5xl text-center my-6">Unfinished Purchases</h1>
|
||||
<table class="responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2 purchase-name truncate max-w-20char">Name</th>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Price ({{ total_spent_currency }})</th>
|
||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for purchase in purchased_unfinished %}
|
||||
<tr>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
|
||||
<a class="underline decoration-slate-500 sm:decoration-2"
|
||||
href="{% url 'edit_purchase' purchase.id %}">
|
||||
{{ purchase.edition.name }}
|
||||
{% if purchase.type == "dlc" %}({{ purchase.name }}, {{ purchase.get_type_display }}){% endif %}
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.price }}</td>
|
||||
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_purchased | date:"d/m/Y" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h1 class="text-5xl text-center my-6">All Purchases</h1>
|
||||
<table class="responsive-table">
|
||||
<thead>
|
||||
|
101
games/views.py
101
games/views.py
@ -320,7 +320,9 @@ def stats(request, year: int = 0):
|
||||
return HttpResponseRedirect(reverse("stats_by_year", args=[selected_year]))
|
||||
if year == 0:
|
||||
year = timezone.now().year
|
||||
this_year_sessions = Session.objects.filter(timestamp_start__year=year)
|
||||
this_year_sessions = Session.objects.filter(
|
||||
timestamp_start__year=year
|
||||
).select_related("purchase__edition")
|
||||
this_year_sessions_with_durations = this_year_sessions.annotate(
|
||||
duration=ExpressionWrapper(
|
||||
F("timestamp_end") - F("timestamp_start"),
|
||||
@ -328,7 +330,10 @@ def stats(request, year: int = 0):
|
||||
)
|
||||
)
|
||||
longest_session = this_year_sessions_with_durations.order_by("-duration").first()
|
||||
this_year_games_with_session_counts = Game.objects.annotate(
|
||||
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",
|
||||
filter=Q(edition__purchase__session__timestamp_start__year=year),
|
||||
@ -349,23 +354,29 @@ def stats(request, year: int = 0):
|
||||
).distinct()
|
||||
|
||||
this_year_purchases = Purchase.objects.filter(date_purchased__year=year)
|
||||
this_year_purchases_with_currency = this_year_purchases.filter(
|
||||
price_currency__exact=selected_currency
|
||||
)
|
||||
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 = this_year_purchases_without_refunded.filter(
|
||||
date_finished__isnull=True
|
||||
).filter(
|
||||
Q(type=Purchase.GAME) | Q(type=Purchase.DLC)
|
||||
this_year_purchases_unfinished = (
|
||||
this_year_purchases_without_refunded.filter(date_finished__isnull=True)
|
||||
.filter(date_dropped__isnull=True)
|
||||
.filter(infinite=False)
|
||||
.filter(Q(type=Purchase.GAME) | Q(type=Purchase.DLC))
|
||||
) # do not count battle passes etc.
|
||||
|
||||
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_refunded.count()
|
||||
this_year_purchases_unfinished_count,
|
||||
this_year_purchases_without_refunded_count,
|
||||
)
|
||||
* 100
|
||||
)
|
||||
@ -377,10 +388,8 @@ def stats(request, year: int = 0):
|
||||
)
|
||||
)
|
||||
purchased_this_year_finished_this_year = (
|
||||
this_year_purchases_without_refunded.intersection(
|
||||
purchases_finished_this_year
|
||||
this_year_purchases_without_refunded.filter(date_finished__year=year)
|
||||
).order_by("date_finished")
|
||||
)
|
||||
|
||||
this_year_spendings = this_year_purchases_without_refunded.aggregate(
|
||||
total_spent=Sum(F("price"))
|
||||
@ -425,6 +434,20 @@ def stats(request, year: int = 0):
|
||||
.count()
|
||||
)
|
||||
|
||||
first_play_name = "N/A"
|
||||
first_play_date = "N/A"
|
||||
last_play_name = "N/A"
|
||||
last_play_date = "N/A"
|
||||
if this_year_sessions:
|
||||
first_session = this_year_sessions.earliest()
|
||||
first_play_name = first_session.purchase.edition.name
|
||||
first_play_date = first_session.timestamp_start.strftime("%x")
|
||||
last_session = this_year_sessions.latest()
|
||||
last_play_name = last_session.purchase.edition.name
|
||||
last_play_date = last_session.timestamp_start.strftime("%x")
|
||||
|
||||
all_purchased_this_year_count = this_year_purchases_with_currency.count()
|
||||
all_purchased_refunded_this_year_count = this_year_purchases_refunded.count()
|
||||
context = {
|
||||
"total_hours": format_duration(
|
||||
this_year_sessions.total_duration_unformatted(), "%2.0H"
|
||||
@ -440,45 +463,67 @@ def stats(request, year: int = 0):
|
||||
"total_spent_currency": selected_currency,
|
||||
"all_purchased_this_year": this_year_purchases_without_refunded,
|
||||
"spent_per_game": int(
|
||||
safe_division(total_spent, this_year_purchases_without_refunded.count())
|
||||
safe_division(total_spent, this_year_purchases_without_refunded_count)
|
||||
),
|
||||
"all_finished_this_year": purchases_finished_this_year.order_by(
|
||||
"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"
|
||||
).order_by(
|
||||
"date_finished"
|
||||
),
|
||||
"this_year_finished_this_year": purchases_finished_this_year_released_this_year.order_by(
|
||||
"date_finished"
|
||||
),
|
||||
"purchased_this_year_finished_this_year": purchased_this_year_finished_this_year.order_by(
|
||||
"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"
|
||||
).order_by(
|
||||
"date_finished"
|
||||
),
|
||||
"total_sessions": this_year_sessions.count(),
|
||||
"unique_days": unique_days["dates"],
|
||||
"unique_days_percent": int(unique_days["dates"] / 365 * 100),
|
||||
"purchased_unfinished": this_year_purchases_unfinished,
|
||||
"purchased_unfinished_count": this_year_purchases_unfinished_count,
|
||||
"unfinished_purchases_percent": this_year_purchases_unfinished_percent,
|
||||
"refunded_percent": int(
|
||||
safe_division(
|
||||
this_year_purchases_refunded.count(),
|
||||
this_year_purchases_with_currency.count(),
|
||||
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": this_year_purchases_with_currency.order_by(
|
||||
"date_purchased"
|
||||
),
|
||||
"all_purchased_this_year_count": all_purchased_this_year_count,
|
||||
"backlog_decrease_count": backlog_decrease_count,
|
||||
"longest_session_time": format_duration(
|
||||
longest_session.duration if longest_session else timedelta(0),
|
||||
"%2.0Hh %2.0mm",
|
||||
),
|
||||
"longest_session_game": longest_session.purchase.edition.name,
|
||||
"highest_session_count": game_highest_session_count.session_count,
|
||||
"highest_session_count_game": game_highest_session_count.name,
|
||||
longest_session.duration, "%2.0Hh %2.0mm"
|
||||
)
|
||||
if longest_session
|
||||
else 0,
|
||||
"longest_session_game": longest_session.purchase.edition.name
|
||||
if longest_session
|
||||
else "N/A",
|
||||
"highest_session_count": game_highest_session_count.session_count
|
||||
if game_highest_session_count
|
||||
else 0,
|
||||
"highest_session_count_game": game_highest_session_count.name
|
||||
if game_highest_session_count
|
||||
else "N/A",
|
||||
"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_name": first_play_name,
|
||||
"first_play_date": first_play_date,
|
||||
"last_play_name": last_play_name,
|
||||
"last_play_date": last_play_date,
|
||||
"title": f"{year} Stats",
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user