Improve time-related stuff

Add created_at to all models
Add modified_at to Session
Get rid of custom now() function
Make sure aware datetime is used everywhere
This commit is contained in:
Lukáš Kucharczyk 2023-11-15 17:47:51 +01:00
parent a7dd0c5556
commit 51ba5cfa20
5 changed files with 127 additions and 39 deletions

View File

@ -1,12 +1,5 @@
import re
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from django.conf import settings
def now() -> datetime:
return datetime.now(ZoneInfo(settings.TIME_ZONE))
from datetime import timedelta
def _safe_timedelta(duration: timedelta | int | None):

View File

@ -0,0 +1,44 @@
# Generated by Django 4.1.5 on 2023-11-15 13:51
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
("games", "0030_alter_purchase_name"),
]
operations = [
migrations.AddField(
model_name="device",
name="created_at",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name="edition",
name="created_at",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name="game",
name="created_at",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name="platform",
name="created_at",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name="purchase",
name="created_at",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name="session",
name="created_at",
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

View File

@ -0,0 +1,52 @@
# Generated by Django 4.1.5 on 2023-11-15 18:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("games", "0031_device_created_at_edition_created_at_game_created_at_and_more"),
]
operations = [
migrations.AlterModelOptions(
name="session",
options={"get_latest_by": "timestamp_start"},
),
migrations.AddField(
model_name="session",
name="modified_at",
field=models.DateTimeField(auto_now=True),
),
migrations.AlterField(
model_name="device",
name="created_at",
field=models.DateTimeField(auto_now_add=True),
),
migrations.AlterField(
model_name="edition",
name="created_at",
field=models.DateTimeField(auto_now_add=True),
),
migrations.AlterField(
model_name="game",
name="created_at",
field=models.DateTimeField(auto_now_add=True),
),
migrations.AlterField(
model_name="platform",
name="created_at",
field=models.DateTimeField(auto_now_add=True),
),
migrations.AlterField(
model_name="purchase",
name="created_at",
field=models.DateTimeField(auto_now_add=True),
),
migrations.AlterField(
model_name="session",
name="created_at",
field=models.DateTimeField(auto_now_add=True),
),
]

View File

@ -1,10 +1,9 @@
from common.time import format_duration
from datetime import datetime, timedelta
from django.conf import settings
from datetime import timedelta
from django.db import models
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.db.models import F, Manager, Sum
from zoneinfo import ZoneInfo
class Game(models.Model):
@ -12,6 +11,7 @@ class Game(models.Model):
sort_name = models.CharField(max_length=255, null=True, blank=True, default=None)
year_released = models.IntegerField(null=True, blank=True, default=None)
wikidata = models.CharField(max_length=50, null=True, blank=True, default=None)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
@ -42,6 +42,7 @@ class Edition(models.Model):
)
year_released = models.IntegerField(null=True, blank=True, default=None)
wikidata = models.CharField(max_length=50, null=True, blank=True, default=None)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.sort_name
@ -128,6 +129,7 @@ class Purchase(models.Model):
blank=True,
related_name="related_purchases",
)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
additional_info = [
@ -156,6 +158,7 @@ class Purchase(models.Model):
class Platform(models.Model):
name = models.CharField(max_length=255)
group = models.CharField(max_length=255, null=True, blank=True, default=None)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
@ -173,6 +176,9 @@ class SessionQuerySet(models.QuerySet):
class Session(models.Model):
class Meta:
get_latest_by = "timestamp_start"
purchase = models.ForeignKey("Purchase", on_delete=models.CASCADE)
timestamp_start = models.DateTimeField()
timestamp_end = models.DateTimeField(blank=True, null=True)
@ -186,6 +192,8 @@ class Session(models.Model):
default=None,
)
note = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
objects = SessionQuerySet.as_manager()
@ -194,10 +202,10 @@ class Session(models.Model):
return f"{str(self.purchase)} {str(self.timestamp_start.date())} ({self.duration_formatted()}{mark})"
def finish_now(self):
self.timestamp_end = datetime.now(ZoneInfo(settings.TIME_ZONE))
self.timestamp_end = timezone.now()
def start_now():
self.timestamp_start = datetime.now(ZoneInfo(settings.TIME_ZONE))
self.timestamp_start = timezone.now()
def duration_seconds(self) -> timedelta:
manual = timedelta(0)
@ -250,6 +258,7 @@ class Device(models.Model):
]
name = models.CharField(max_length=255)
type = models.CharField(max_length=3, choices=DEVICE_TYPES, default=UNKNOWN)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.name} ({self.get_type_display()})"

View File

@ -1,4 +1,4 @@
from common.time import format_duration, now as now_with_tz
from common.time import format_duration
from common.utils import safe_division
from datetime import datetime, timedelta
from django.conf import settings
@ -7,6 +7,7 @@ from django.db.models.functions import TruncDate
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils import timezone
from typing import Callable, Any
from zoneinfo import ZoneInfo
@ -32,19 +33,15 @@ def model_counts(request):
def stats_dropdown_year_range(request):
result = {
"stats_dropdown_year_range": range(
datetime.now(ZoneInfo(settings.TIME_ZONE)).year, 1999, -1
)
}
result = {"stats_dropdown_year_range": range(timezone.now().year, 1999, -1)}
return result
def add_session(request, purchase_id=None):
context = {}
initial = {"timestamp_start": now_with_tz()}
initial = {"timestamp_start": timezone.now()}
last = Session.objects.all().last()
last = Session.objects.last()
if last != None:
initial["purchase"] = last.purchase
@ -155,13 +152,11 @@ def view_game(request, game_id=None):
.order_by("year_released")
)
sessions = Session.objects.filter(purchase__edition__game=game).order_by(
"timestamp_start"
)
sessions = Session.objects.filter(purchase__edition__game=game)
session_count = sessions.count()
playrange_start = sessions.first().timestamp_start.strftime("%b %Y")
playrange_end = sessions.last().timestamp_start.strftime("%b %Y")
playrange_start = sessions.earliest().timestamp_start.strftime("%b %Y")
playrange_end = sessions.latest().timestamp_start.strftime("%b %Y")
playrange = (
playrange_start
@ -225,15 +220,11 @@ def related_purchase_by_edition(request):
@use_custom_redirect
def start_game_session(request, game_id: int):
last_session = (
Session.objects.filter(purchase__edition__game_id=game_id)
.order_by("-timestamp_start")
.first()
)
last_session = Session.objects.filter(purchase__edition__game_id=game_id).latest()
session = SessionForm(
{
"purchase": last_session.purchase.id,
"timestamp_start": now_with_tz(),
"timestamp_start": timezone.now(),
"device": last_session.device,
}
)
@ -246,7 +237,7 @@ def start_session_same_as_last(request, last_session_id: int):
session = SessionForm(
{
"purchase": last_session.purchase.id,
"timestamp_start": now_with_tz(),
"timestamp_start": timezone.now(),
"device": last_session.device,
}
)
@ -296,19 +287,18 @@ def list_sessions(
context["title"] = "This year"
else:
# by default, sort from newest to oldest
dataset = Session.objects.all().order_by("-timestamp_start")
dataset = Session.objects.order_by("-timestamp_start")
for session in dataset:
if session.timestamp_end == None and session.duration_manual == timedelta(
seconds=0
):
session.timestamp_end = datetime.now(ZoneInfo(settings.TIME_ZONE))
session.timestamp_end = timezone.now()
session.unfinished = True
context["total_duration"] = dataset.total_duration_formatted()
context["dataset"] = dataset
# cannot use dataset[0] here because that might be only partial QuerySet
context["last"] = Session.objects.all().order_by("timestamp_start").last()
context["last"] = Session.objects.latest()
return render(request, "list_sessions.html", context)
@ -318,7 +308,7 @@ def stats(request, year: int = 0):
if selected_year:
return HttpResponseRedirect(reverse("stats_by_year", args=[selected_year]))
if year == 0:
year = now_with_tz().year
year = timezone.now().year
this_year_sessions = Session.objects.filter(timestamp_start__year=year)
selected_currency = "CZK"
unique_days = (
@ -451,7 +441,7 @@ def stats(request, year: int = 0):
def add_purchase(request, edition_id=None):
context = {}
initial = {"date_purchased": now_with_tz()}
initial = {"date_purchased": timezone.now()}
if request.method == "POST":
form = PurchaseForm(request.POST or None, initial=initial)