Improve time-related stuff
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
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:
parent
3f037b4c7c
commit
c44d8bf427
|
@ -1,12 +1,5 @@
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
from zoneinfo import ZoneInfo
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
def now() -> datetime:
|
|
||||||
return datetime.now(ZoneInfo(settings.TIME_ZONE))
|
|
||||||
|
|
||||||
|
|
||||||
def _safe_timedelta(duration: timedelta | int | None):
|
def _safe_timedelta(duration: timedelta | int | None):
|
||||||
|
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,10 +1,9 @@
|
||||||
from common.time import format_duration
|
from common.time import format_duration
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
from django.conf import settings
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils import timezone
|
||||||
from django.db.models import F, Manager, Sum
|
from django.db.models import F, Manager, Sum
|
||||||
from zoneinfo import ZoneInfo
|
|
||||||
|
|
||||||
|
|
||||||
class Game(models.Model):
|
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)
|
sort_name = models.CharField(max_length=255, null=True, blank=True, default=None)
|
||||||
year_released = models.IntegerField(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)
|
wikidata = models.CharField(max_length=50, null=True, blank=True, default=None)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -42,6 +42,7 @@ class Edition(models.Model):
|
||||||
)
|
)
|
||||||
year_released = models.IntegerField(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)
|
wikidata = models.CharField(max_length=50, null=True, blank=True, default=None)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.sort_name
|
return self.sort_name
|
||||||
|
@ -128,6 +129,7 @@ class Purchase(models.Model):
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="related_purchases",
|
related_name="related_purchases",
|
||||||
)
|
)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
additional_info = [
|
additional_info = [
|
||||||
|
@ -156,6 +158,7 @@ class Purchase(models.Model):
|
||||||
class Platform(models.Model):
|
class Platform(models.Model):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
group = models.CharField(max_length=255, null=True, blank=True, default=None)
|
group = models.CharField(max_length=255, null=True, blank=True, default=None)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -173,6 +176,9 @@ class SessionQuerySet(models.QuerySet):
|
||||||
|
|
||||||
|
|
||||||
class Session(models.Model):
|
class Session(models.Model):
|
||||||
|
class Meta:
|
||||||
|
get_latest_by = "timestamp_start"
|
||||||
|
|
||||||
purchase = models.ForeignKey("Purchase", on_delete=models.CASCADE)
|
purchase = models.ForeignKey("Purchase", on_delete=models.CASCADE)
|
||||||
timestamp_start = models.DateTimeField()
|
timestamp_start = models.DateTimeField()
|
||||||
timestamp_end = models.DateTimeField(blank=True, null=True)
|
timestamp_end = models.DateTimeField(blank=True, null=True)
|
||||||
|
@ -186,6 +192,8 @@ class Session(models.Model):
|
||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
note = models.TextField(blank=True, null=True)
|
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()
|
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})"
|
return f"{str(self.purchase)} {str(self.timestamp_start.date())} ({self.duration_formatted()}{mark})"
|
||||||
|
|
||||||
def finish_now(self):
|
def finish_now(self):
|
||||||
self.timestamp_end = datetime.now(ZoneInfo(settings.TIME_ZONE))
|
self.timestamp_end = timezone.now()
|
||||||
|
|
||||||
def start_now():
|
def start_now():
|
||||||
self.timestamp_start = datetime.now(ZoneInfo(settings.TIME_ZONE))
|
self.timestamp_start = timezone.now()
|
||||||
|
|
||||||
def duration_seconds(self) -> timedelta:
|
def duration_seconds(self) -> timedelta:
|
||||||
manual = timedelta(0)
|
manual = timedelta(0)
|
||||||
|
@ -250,6 +258,7 @@ class Device(models.Model):
|
||||||
]
|
]
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
type = models.CharField(max_length=3, choices=DEVICE_TYPES, default=UNKNOWN)
|
type = models.CharField(max_length=3, choices=DEVICE_TYPES, default=UNKNOWN)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name} ({self.get_type_display()})"
|
return f"{self.name} ({self.get_type_display()})"
|
||||||
|
|
|
@ -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 common.utils import safe_division
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from django.conf import settings
|
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.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
|
||||||
|
from django.utils import timezone
|
||||||
from typing import Callable, Any
|
from typing import Callable, Any
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
@ -32,19 +33,15 @@ def model_counts(request):
|
||||||
|
|
||||||
|
|
||||||
def stats_dropdown_year_range(request):
|
def stats_dropdown_year_range(request):
|
||||||
result = {
|
result = {"stats_dropdown_year_range": range(timezone.now().year, 1999, -1)}
|
||||||
"stats_dropdown_year_range": range(
|
|
||||||
datetime.now(ZoneInfo(settings.TIME_ZONE)).year, 1999, -1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def add_session(request, purchase_id=None):
|
def add_session(request, purchase_id=None):
|
||||||
context = {}
|
context = {}
|
||||||
initial = {"timestamp_start": now_with_tz()}
|
initial = {"timestamp_start": timezone.now()}
|
||||||
|
|
||||||
last = Session.objects.all().last()
|
last = Session.objects.last()
|
||||||
if last != None:
|
if last != None:
|
||||||
initial["purchase"] = last.purchase
|
initial["purchase"] = last.purchase
|
||||||
|
|
||||||
|
@ -155,13 +152,11 @@ def view_game(request, game_id=None):
|
||||||
.order_by("year_released")
|
.order_by("year_released")
|
||||||
)
|
)
|
||||||
|
|
||||||
sessions = Session.objects.filter(purchase__edition__game=game).order_by(
|
sessions = Session.objects.filter(purchase__edition__game=game)
|
||||||
"timestamp_start"
|
|
||||||
)
|
|
||||||
session_count = sessions.count()
|
session_count = sessions.count()
|
||||||
|
|
||||||
playrange_start = sessions.first().timestamp_start.strftime("%b %Y")
|
playrange_start = sessions.earliest().timestamp_start.strftime("%b %Y")
|
||||||
playrange_end = sessions.last().timestamp_start.strftime("%b %Y")
|
playrange_end = sessions.latest().timestamp_start.strftime("%b %Y")
|
||||||
|
|
||||||
playrange = (
|
playrange = (
|
||||||
playrange_start
|
playrange_start
|
||||||
|
@ -225,15 +220,11 @@ def related_purchase_by_edition(request):
|
||||||
|
|
||||||
@use_custom_redirect
|
@use_custom_redirect
|
||||||
def start_game_session(request, game_id: int):
|
def start_game_session(request, game_id: int):
|
||||||
last_session = (
|
last_session = Session.objects.filter(purchase__edition__game_id=game_id).latest()
|
||||||
Session.objects.filter(purchase__edition__game_id=game_id)
|
|
||||||
.order_by("-timestamp_start")
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
session = SessionForm(
|
session = SessionForm(
|
||||||
{
|
{
|
||||||
"purchase": last_session.purchase.id,
|
"purchase": last_session.purchase.id,
|
||||||
"timestamp_start": now_with_tz(),
|
"timestamp_start": timezone.now(),
|
||||||
"device": last_session.device,
|
"device": last_session.device,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -246,7 +237,7 @@ def start_session_same_as_last(request, last_session_id: int):
|
||||||
session = SessionForm(
|
session = SessionForm(
|
||||||
{
|
{
|
||||||
"purchase": last_session.purchase.id,
|
"purchase": last_session.purchase.id,
|
||||||
"timestamp_start": now_with_tz(),
|
"timestamp_start": timezone.now(),
|
||||||
"device": last_session.device,
|
"device": last_session.device,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -296,19 +287,18 @@ def list_sessions(
|
||||||
context["title"] = "This year"
|
context["title"] = "This year"
|
||||||
else:
|
else:
|
||||||
# by default, sort from newest to oldest
|
# 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:
|
for session in dataset:
|
||||||
if session.timestamp_end == None and session.duration_manual == timedelta(
|
if session.timestamp_end == None and session.duration_manual == timedelta(
|
||||||
seconds=0
|
seconds=0
|
||||||
):
|
):
|
||||||
session.timestamp_end = datetime.now(ZoneInfo(settings.TIME_ZONE))
|
session.timestamp_end = timezone.now()
|
||||||
session.unfinished = True
|
session.unfinished = True
|
||||||
|
|
||||||
context["total_duration"] = dataset.total_duration_formatted()
|
context["total_duration"] = dataset.total_duration_formatted()
|
||||||
context["dataset"] = dataset
|
context["dataset"] = dataset
|
||||||
# cannot use dataset[0] here because that might be only partial QuerySet
|
context["last"] = Session.objects.latest()
|
||||||
context["last"] = Session.objects.all().order_by("timestamp_start").last()
|
|
||||||
|
|
||||||
return render(request, "list_sessions.html", context)
|
return render(request, "list_sessions.html", context)
|
||||||
|
|
||||||
|
@ -318,7 +308,7 @@ def stats(request, year: int = 0):
|
||||||
if selected_year:
|
if selected_year:
|
||||||
return HttpResponseRedirect(reverse("stats_by_year", args=[selected_year]))
|
return HttpResponseRedirect(reverse("stats_by_year", args=[selected_year]))
|
||||||
if year == 0:
|
if year == 0:
|
||||||
year = now_with_tz().year
|
year = timezone.now().year
|
||||||
this_year_sessions = Session.objects.filter(timestamp_start__year=year)
|
this_year_sessions = Session.objects.filter(timestamp_start__year=year)
|
||||||
selected_currency = "CZK"
|
selected_currency = "CZK"
|
||||||
unique_days = (
|
unique_days = (
|
||||||
|
@ -451,7 +441,7 @@ def stats(request, year: int = 0):
|
||||||
|
|
||||||
def add_purchase(request, edition_id=None):
|
def add_purchase(request, edition_id=None):
|
||||||
context = {}
|
context = {}
|
||||||
initial = {"date_purchased": now_with_tz()}
|
initial = {"date_purchased": timezone.now()}
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = PurchaseForm(request.POST or None, initial=initial)
|
form = PurchaseForm(request.POST or None, initial=initial)
|
||||||
|
|
Loading…
Reference in New Issue