7 Commits

Author SHA1 Message Date
5cc1652002 Always set game status change timestamp to now instead of game's last updated_at
All checks were successful
Django CI/CD / test (push) Successful in 1m16s
Django CI/CD / build-and-push (push) Successful in 2m15s
2025-06-07 20:28:14 +02:00
7cf2180192 Allow setting game to Finished when creating PlayEvent
All checks were successful
Django CI/CD / test (push) Successful in 1m17s
Django CI/CD / build-and-push (push) Successful in 2m21s
2025-06-07 20:14:14 +02:00
ad0641f95b Fix playtime stats per year
All checks were successful
Django CI/CD / test (push) Successful in 58s
Django CI/CD / build-and-push (push) Successful in 2m39s
2025-04-17 16:15:38 +02:00
abdcfdfe64 Redirect to previous page after editing a session
All checks were successful
Django CI/CD / test (push) Successful in 1m9s
Django CI/CD / build-and-push (push) Successful in 2m17s
2025-04-01 15:36:40 +02:00
31daf2efe0 Make game overview playthrough dropdown close when clicked outside
All checks were successful
Django CI/CD / test (push) Successful in 1m6s
Django CI/CD / build-and-push (push) Successful in 2m17s
2025-03-28 13:43:46 +01:00
6d53fca910 Always return timedelta in update_game_playtime
All checks were successful
Django CI/CD / test (push) Successful in 1m3s
Django CI/CD / build-and-push (push) Successful in 2m19s
2025-03-26 12:05:10 +01:00
f7e426e030 Make it easier to create a play event 2025-03-26 12:04:46 +01:00
7 changed files with 48 additions and 22 deletions

View File

@ -1,4 +1,5 @@
from django import forms
from django.db import transaction
from django.urls import reverse
from common.utils import safe_getattr
@ -227,19 +228,30 @@ class PlayEventForm(forms.ModelForm):
widget=forms.Select(attrs={"autofocus": "autofocus"}),
)
mark_as_finished = forms.BooleanField(
required=False,
initial={"mark_as_finished": True},
label="Set game status to Finished",
)
class Meta:
model = PlayEvent
fields = [
"game",
"started",
"ended",
"note",
]
fields = ["game", "started", "ended", "note", "mark_as_finished"]
widgets = {
"started": custom_date_widget,
"ended": custom_date_widget,
}
def save(self, commit=True):
with transaction.atomic():
session = super().save(commit=False)
if self.cleaned_data.get("mark_as_finished"):
game_instance = session.game
game_instance.status = "f"
game_instance.save()
session.save()
return session
class GameStatusChangeForm(forms.ModelForm):
class Meta:

View File

@ -1,3 +1,6 @@
import logging
from datetime import timedelta
from django.db.models import F, Sum
from django.db.models.signals import m2m_changed, post_delete, post_save, pre_save
from django.dispatch import receiver
@ -5,6 +8,8 @@ from django.utils.timezone import now
from games.models import Game, GameStatusChange, Purchase, Session
logger = logging.getLogger("games")
@receiver(m2m_changed, sender=Purchase.games.through)
def update_num_purchases(sender, instance, **kwargs):
@ -19,7 +24,7 @@ def update_game_playtime(sender, instance, **kwargs):
total_playtime = game.sessions.aggregate(
total_playtime=Sum(F("duration_calculated") + F("duration_manual"))
)["total_playtime"]
game.playtime = total_playtime if total_playtime else 0
game.playtime = total_playtime if total_playtime else timedelta(0)
game.save(update_fields=["playtime"])
@ -31,21 +36,23 @@ def game_status_changed(sender, instance, **kwargs):
try:
old_instance = sender.objects.get(pk=instance.pk)
old_status = old_instance.status
print("Got old instance")
logger.info("[game_status_changed]: Previous status exists.")
except sender.DoesNotExist:
# Handle the case where the instance was deleted before the signal was sent
print("Instance does not exist")
logger.info("[game_status_changed]: Previous status does not exist.")
return
if old_status != instance.status:
print("Status changed")
logger.info(
"[game_status_changed]: Status changed from {} to {}".format(
old_status, instance.status
)
)
GameStatusChange.objects.create(
game=instance,
old_status=old_status,
new_status=instance.status,
timestamp=instance.updated_at,
timestamp=now(),
)
else:
print("Status not changed")
print(f"{old_instance.status}")
print(f"{instance.status}")
logger.info("[game_status_changed]: Status has not changed")

View File

@ -167,7 +167,7 @@
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<c-gamelink :game_id=game.id :name=game.name />
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ game.playtime | format_duration }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ game.total_playtime | format_duration }}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -74,7 +74,7 @@
<span x-text="played"></span> times
</button>
</a>
<button type="button" x-on:click="open = !open" class="relative px-4 py-2 text-sm font-medium text-gray-900 bg-white border-e border-b border-t border-gray-200 rounded-e-lg hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:border-gray-700 dark:text-white dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-blue-500 dark:focus:text-white align-middle">
<button type="button" x-on:click="open = !open" @click.outside="open = false" class="relative px-4 py-2 text-sm font-medium text-gray-900 bg-white border-e border-b border-t border-gray-200 rounded-e-lg hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:border-gray-700 dark:text-white dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-blue-500 dark:focus:text-white align-middle">
<svg class="text-white w-3" viewBox="5 8 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9L12 15L18 9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

View File

@ -364,9 +364,16 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
)
total_spent = this_year_spendings["total_spent"] or 0
games_with_playtime = Game.objects.filter(
sessions__in=this_year_sessions
).distinct()
games_with_playtime = (
Game.objects.filter(sessions__timestamp_start__year=year)
.annotate(
total_playtime=Sum(
F("sessions__duration_calculated"),
)
)
.filter(total_playtime__gt=timedelta(0))
)
month_playtimes = (
this_year_sessions.annotate(month=TruncMonth("timestamp_start"))
.values("month")
@ -380,7 +387,7 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
.order_by("-session_average")
.first()
)
top_10_games_by_playtime = games_with_playtime.order_by("-playtime")
top_10_games_by_playtime = games_with_playtime.order_by("-total_playtime")
total_playtime_per_platform = (
this_year_sessions.values("game__platform__name")

View File

@ -115,6 +115,8 @@ def add_playevent(request: HttpRequest, game_id: int = 0) -> HttpResponse:
# coming from add_playevent_for_game url path
game = get_object_or_404(Game, id=game_id)
initial["game"] = game
initial["started"] = game.sessions.earliest().timestamp_start
initial["ended"] = game.sessions.latest().timestamp_start
form = PlayEventForm(request.POST or None, initial=initial)
if form.is_valid():
form.save()

View File

@ -26,7 +26,6 @@ from common.time import (
from common.utils import truncate
from games.forms import SessionForm
from games.models import Game, Session
from games.views.general import use_custom_redirect
@login_required
@ -215,7 +214,6 @@ def add_session(request: HttpRequest, game_id: int = 0) -> HttpResponse:
@login_required
@use_custom_redirect
def edit_session(request: HttpRequest, session_id: int) -> HttpResponse:
context = {}
session = get_object_or_404(Session, id=session_id)