Compare commits

...

10 Commits

Author SHA1 Message Date
Lukáš Kucharczyk 3314c9b42b
use unified dateformat more
Django CI/CD / test (push) Successful in 56s Details
Django CI/CD / build-and-push (push) Successful in 2m13s Details
2024-10-14 14:26:48 +02:00
Lukáš Kucharczyk 62a1fab15f
view_game: display timezone-aware time for end timestamp
Django CI/CD / test (push) Successful in 1m8s Details
Django CI/CD / build-and-push (push) Successful in 2m16s Details
2024-10-14 13:02:18 +02:00
Lukáš Kucharczyk 7ec622a38a
make purchase price a float
Django CI/CD / test (push) Successful in 1m7s Details
Django CI/CD / build-and-push (push) Successful in 2m8s Details
2024-10-04 11:36:46 +02:00
Lukáš Kucharczyk 7f5a1889f3
avoid exception on game overview when sessions are 0
Django CI/CD / test (push) Successful in 1m7s Details
Django CI/CD / build-and-push (push) Successful in 2m7s Details
2024-10-04 10:17:38 +02:00
Lukáš Kucharczyk 10e96bbc88
rename icon
Django CI/CD / test (push) Successful in 1m20s Details
Django CI/CD / build-and-push (push) Successful in 2m17s Details
2024-10-04 10:11:33 +02:00
Lukáš Kucharczyk 60d3ba6569
order platforms by name
Django CI/CD / test (push) Successful in 1m4s Details
Django CI/CD / build-and-push (push) Successful in 1m51s Details
2024-09-14 20:19:30 +02:00
Lukáš Kucharczyk bcb845adac
remove css cruft 2024-09-14 20:19:20 +02:00
Lukáš Kucharczyk bd222f253e
add more icons 2024-09-14 20:19:14 +02:00
Lukáš Kucharczyk 45e3cfed00
Remove extraneous statement
Django CI/CD / test (push) Successful in 54s Details
Django CI/CD / build-and-push (push) Successful in 1m58s Details
2024-09-14 11:10:28 +02:00
Lukáš Kucharczyk 36dd5635b2
add icon field to platform, use everywhere
Django CI/CD / test (push) Successful in 52s Details
Django CI/CD / build-and-push (push) Has been skipped Details
2024-09-14 11:07:38 +02:00
21 changed files with 211 additions and 94 deletions

View File

@ -4,7 +4,9 @@ from typing import Any, Callable
from django.template.loader import render_to_string
from django.urls import NoReverseMatch, reverse
from django.utils.safestring import mark_safe
from django.utils.safestring import SafeText, mark_safe
from common.utils import truncate
HTMLAttribute = tuple[str, str | int | bool]
HTMLTag = str
@ -65,6 +67,13 @@ def Popover(
)
def PopoverTruncated(input_string: str) -> str:
if (truncated := truncate(input_string)) != input_string:
return Popover(wrapped_content=truncated, popover_content=input_string)
else:
return input_string
def A(
attributes: list[HTMLAttribute] = [],
children: list[HTMLTag] | HTMLTag = [],
@ -120,3 +129,39 @@ def Icon(
attributes: list[HTMLAttribute] = [],
):
return Component(template=f"cotton/icon/{name}.html", attributes=attributes)
def LinkedNameWithPlatformIcon(name: str, game_id: int, platform: str) -> SafeText:
link = reverse("view_game", args=[int(game_id)])
a_content = Div(
[("class", "inline-flex gap-2 items-center")],
[
Icon(
platform.icon,
[("title", platform.name)],
),
PopoverTruncated(name),
],
)
return mark_safe(
A(
url=link,
children=[a_content],
),
)
def NameWithPlatformIcon(name: str, platform: str) -> SafeText:
content = Div(
[("class", "inline-flex gap-2 items-center")],
[
Icon(
platform.icon,
[("title", platform.name)],
),
PopoverTruncated(name),
],
)
return mark_safe(content)

View File

@ -1,8 +1,6 @@
from datetime import date
from typing import Any, Generator, TypeVar
from common.components import Popover
def safe_division(numerator: int | float, denominator: int | float) -> int | float:
"""
@ -44,13 +42,6 @@ def truncate(input_string: str, length: int = 30, ellipsis: str = "…") -> str:
)
def truncate_with_popover(input_string: str) -> str:
if (truncated := truncate(input_string)) != input_string:
return Popover(wrapped_content=truncated, popover_content=input_string)
else:
return input_string
T = TypeVar("T", str, int, date)
@ -69,3 +60,7 @@ def generate_split_ranges(
except IndexError:
end = len(value_list)
yield (value_list[start], value_list[end - 1])
def format_float_or_int(number: int | float):
return int(number) if float(number).is_integer() else f"{number:03.2f}"

View File

@ -164,7 +164,11 @@ class GameForm(forms.ModelForm):
class PlatformForm(forms.ModelForm):
class Meta:
model = Platform
fields = ["name", "group"]
fields = [
"name",
"icon",
"group",
]
widgets = {"name": autofocus_input_widget}

View File

@ -0,0 +1,26 @@
# Generated by Django 5.1.1 on 2024-09-14 07:05
from django.db import migrations, models
from django.utils.text import slugify
def update_empty_icons(apps, schema_editor):
Platform = apps.get_model("games", "Platform")
for platform in Platform.objects.filter(icon=""):
platform.icon = slugify(platform.name)
platform.save()
class Migration(migrations.Migration):
dependencies = [
("games", "0036_alter_edition_platform"),
]
operations = [
migrations.AddField(
model_name="platform",
name="icon",
field=models.SlugField(blank=True),
),
migrations.RunPython(update_empty_icons),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.1 on 2024-10-04 09:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('games', '0037_platform_icon'),
]
operations = [
migrations.AlterField(
model_name='purchase',
name='price',
field=models.FloatField(default=0),
),
]

View File

@ -3,6 +3,7 @@ from datetime import timedelta
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import F, Sum
from django.template.defaultfilters import slugify
from django.utils import timezone
from common.time import format_duration
@ -25,11 +26,17 @@ class Game(models.Model):
class Platform(models.Model):
name = models.CharField(max_length=255)
group = models.CharField(max_length=255, null=True, blank=True, default=None)
icon = models.SlugField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.icon:
self.icon = slugify(self.name)
super().save(*args, **kwargs)
class Edition(models.Model):
class Meta:
@ -104,7 +111,7 @@ class Purchase(models.Model):
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 = models.FloatField(default=0)
price_currency = models.CharField(max_length=3, default="USD")
ownership_type = models.CharField(
max_length=2, choices=OWNERSHIP_TYPES, default=DIGITAL

View File

@ -3188,10 +3188,6 @@ textarea:disabled:is(.dark *) {
border-end-end-radius: 0.5rem;
}
.\[\&_\:last-child\]\:text-right :last-child {
text-align: right;
}
.\[\&_a\]\:underline a {
text-decoration-line: underline;
}
@ -3207,3 +3203,7 @@ textarea:disabled:is(.dark *) {
.\[\&_h1\]\:mb-2 h1 {
margin-bottom: 0.5rem;
}
.\[\&_td\:last-child\]\:text-right td:last-child {
text-align: right;
}

View File

@ -1,4 +1,5 @@
<c-svg title="Epic Games Store" viewbox="0 0 50 50">
<c-vars title="Epic Games Store" />
<c-svg :title=title viewbox="0 0 50 50">
<c-slot name="path">
M 10 3 C 6.69 3 4 5.69 4 9 L 4 41.240234 L 25 47.539062 L 46 41.240234 L 46 9 C 46 5.69 43.31 3 40 3 L 10 3 z M 11 8 L 15 8 L 15 11 L 11 11 L 11 18 L 14 18 L 14 21 L 11 21 L 11 28 L 15 28 L 15 31 L 11 31 C 9.34 31 8 29.66 8 28 L 8 11 C 8 9.34 9.34 8 11 8 z M 17 8 L 23 8 C 24.66 8 26 9.34 26 11 L 26 18 C 26 19.66 24.66 21 23 21 L 20 21 L 20 31 L 17 31 L 17 8 z M 28 8 L 31 8 L 31 31 L 28 31 L 28 8 z M 36 8 L 39 8 C 40.66 8 42 9.34 42 11 L 42 15 L 39 15 L 39 11 L 36 11 L 36 28 L 39 28 L 39 24 L 42 24 L 42 28 C 42 29.66 40.66 31 39 31 L 36 31 C 34.34 31 33 29.66 33 28 L 33 11 C 33 9.34 34.34 8 36 8 z M 20 11 L 20 18 L 23 18 L 23 11 L 20 11 z M 9 34 L 13 34 C 13.55 34 14 34.45 14 35 L 14 36 L 13 36 L 13 35.25 C 13 35.11 12.89 35 12.75 35 L 9.25 35 C 9.11 35 9 35.11 9 35.25 L 9 38.75 C 9 38.89 9.11 39 9.25 39 L 12.75 39 C 12.89 39 13 38.89 13 38.75 L 13 38 L 12 38 L 12 37 L 14 37 L 14 39 C 14 39.55 13.55 40 13 40 L 9 40 C 8.45 40 8 39.55 8 39 L 8 35 C 8 34.45 8.45 34 9 34 z M 18 34 L 19 34 L 22 40 L 21 40 L 20.5 39 L 16.5 39 L 16 40 L 15 40 L 18 34 z M 23 34 L 24 34 L 26 38 L 28 34 L 29 34 L 29 40 L 28 40 L 28 36 L 26.5 39 L 25.5 39 L 24 36 L 24 40 L 23 40 L 23 34 z M 30 34 L 35 34 L 35 35 L 31 35 L 31 36.5 L 33 36.5 L 33 37.5 L 31 37.5 L 31 39 L 35 39 L 35 40 L 30 40 L 30 34 z M 37 34 L 41 34 C 41.55 34 42 34.45 42 35 L 42 35.5 L 41 35.5 L 41 35.25 C 41 35.11 40.89 35 40.75 35 L 37.25 35 C 37.11 35 37 35.11 37 35.25 L 37 36.25 C 37 36.39 37.11 36.5 37.25 36.5 L 41 36.5 C 41.55 36.5 42 36.95 42 37.5 L 42 39 C 42 39.55 41.55 40 41 40 L 37 40 C 36.45 40 36 39.55 36 39 L 36 38.5 L 37 38.5 L 37 38.75 C 37 38.89 37.11 39 37.25 39 L 40.75 39 C 40.89 39 41 38.89 41 38.75 L 41 37.75 C 41 37.61 40.89 37.5 40.75 37.5 L 37 37.5 C 36.45 37.5 36 37.05 36 36.5 L 36 35 C 36 34.45 36.45 34 37 34 z M 18.5 35 L 17 38 L 20 38 L 18.5 35 z
</c-slot>

View File

@ -0,0 +1 @@
<c-icon.nintendo />

View File

@ -0,0 +1,6 @@
<c-vars title="Nintendo" />
<c-svg viewBox="0 0 24 24">
<c-slot name="path">
M0 .6h7.1l9.85 15.9V.6H24v22.8h-7.04L7.06 7.5v15.9H0V.6
</c-slot>
</c-svg>

View File

@ -0,0 +1,5 @@
<c-svg viewbox="0 0 512 512">
<title>Physical Media</title>
<path fill="currentColor" d="M277.333,256c0-11.755-9.557-21.333-21.333-21.333s-21.333,9.579-21.333,21.333c0,11.755,9.557,21.333,21.333,21.333 S277.333,267.755,277.333,256z" />
<path fill="currentColor" d="M256,0C114.837,0,0,114.837,0,256s114.837,256,256,256s256-114.837,256-256S397.163,0,256,0z M128,256 c0,11.776-9.536,21.333-21.333,21.333c-11.797,0-21.333-9.557-21.333-21.333c0-94.101,76.565-170.667,170.667-170.667 c11.797,0,21.333,9.557,21.333,21.333S267.797,128,256,128C185.408,128,128,185.408,128,256z M192,256c0-35.285,28.715-64,64-64 s64,28.715,64,64s-28.715,64-64,64S192,291.285,192,256z M256,426.667c-11.797,0-21.333-9.557-21.333-21.333S244.203,384,256,384 c70.592,0,128-57.408,128-128c0-11.776,9.536-21.333,21.333-21.333s21.333,9.557,21.333,21.333 C426.667,350.101,350.101,426.667,256,426.667z" />
</c-svg>

View File

@ -0,0 +1,6 @@
<c-vars title="Playstation 1" />
<c-svg viewBox="0 0 50 50">
<c-slot name="path">
M 19.3125 4 C 19.011719 4 18.707031 3.988281 18.40625 4.1875 C 18.105469 4.386719 18 4.699219 18 5 L 18 41.59375 C 18 41.992188 18.289063 42.394531 18.6875 42.59375 L 26.6875 45 L 27 45 C 27.199219 45 27.394531 44.914063 27.59375 44.8125 C 27.894531 44.613281 28 44.300781 28 44 L 28 13.40625 C 28.601563 13.707031 29 14.300781 29 15 L 29 26.09375 C 29 26.394531 29.199219 26.804688 29.5 26.90625 C 29.699219 27.007813 31.199219 27.90625 34 27.90625 C 36.699219 27.90625 40 26.414063 40 19.3125 C 40 13.613281 36.8125 9.292969 31.3125 7.59375 Z M 17 26.40625 L 5.90625 30.40625 L 4.3125 31 C 1.613281 32.101563 0 33.886719 0 35.6875 C 0 39.488281 2.699219 41.6875 7.5 41.6875 C 10.101563 41.6875 13.300781 41.113281 17 39.8125 L 17 36 C 16.101563 36.300781 15.113281 36.699219 14.3125 37 C 12.710938 37.601563 11.5 37.8125 10.5 37.8125 C 9 37.8125 8.300781 37.300781 8 37 C 7.601563 36.699219 7.398438 36.3125 7.5 35.8125 C 7.601563 34.8125 8.800781 33.894531 11 33.09375 C 11.5 32.894531 14.898438 31.699219 17 31 Z M 36.5 28.90625 C 34.101563 29.007813 31.601563 29.394531 29 30.09375 L 29 34.6875 C 30.101563 34.289063 31.585938 33.800781 33.6875 33 C 38.488281 31.300781 40.492188 31.488281 41.09375 31.6875 C 42.292969 31.789063 42.800781 32.5 43 33 C 43.5 34.5 41.613281 35.1875 38.8125 36.1875 C 37.511719 36.6875 31.898438 38.6875 29 39.6875 L 29 44.3125 L 44.5 38.8125 L 45.6875 38.3125 C 47.6875 37.613281 50.199219 36.300781 50 34 C 49.898438 31.800781 47.210938 30.695313 45.3125 30.09375 C 42.511719 29.195313 39.5 28.804688 36.5 28.90625 Z
</c-slot>
</c-svg>

View File

@ -0,0 +1 @@
<c-icon.playstation />

View File

@ -1,4 +1,5 @@
<c-svg title="Playstation 4" viewbox="0 0 50 50">
<c-vars title="Playstation 4" />
<c-svg :title=title viewbox="0 0 50 50">
<c-slot name="path">
M 1 19 A 1.0001 1.0001 0 1 0 1 21 L 12.5 21 C 13.340812 21 14 21.659188 14 22.5 C 14 23.340812 13.340812 24 12.5 24 L 3 24 C 1.3550302 24 0 25.35503 0 27 L 0 30 A 1.0001 1.0001 0 1 0 2 30 L 2 27 C 2 26.43497 2.4349698 26 3 26 L 12.5 26 C 14.28508 26 15.719786 24.619005 15.921875 22.884766 A 1.0001 1.0001 0 0 0 16 22.5 C 16 20.578812 14.421188 19 12.5 19 L 1 19 z M 26 19 C 24.35503 19 23 20.35503 23 22 L 23 28 C 23 28.56503 22.56503 29 22 29 L 16 29 A 1.0001 1.0001 0 1 0 16 31 L 22 31 C 23.64497 31 25 29.64497 25 28 L 25 22 C 25 21.43497 25.43497 21 26 21 L 32 21 A 1.0001 1.0001 0 1 0 32 19 L 26 19 z M 46.970703 19 A 1.0001 1.0001 0 0 0 46.503906 19.130859 L 32.503906 27.130859 A 1.0001 1.0001 0 0 0 33 29 L 46 29 L 46 30 A 1.0001 1.0001 0 1 0 48 30 L 48 29 L 49 29 A 1.0001 1.0001 0 1 0 49 27 L 48 27 L 48 20 A 1.0001 1.0001 0 0 0 46.970703 19 z M 46 21.724609 L 46 27 L 36.767578 27 L 46 21.724609 z
</c-slot>

View File

@ -1,4 +1,4 @@
<tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 [&_a]:underline [&_a]:underline-offset-4 [&_a]:decoration-2 [&_:last-child]:text-right">
<tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 [&_a]:underline [&_a]:underline-offset-4 [&_a]:decoration-2 [&_td:last-child]:text-right">
{% if slot %}
{{ slot }}
{% else %}

View File

@ -7,9 +7,14 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.urls import reverse
from common.components import A, Button, Icon
from common.components import (
A,
Button,
Icon,
LinkedNameWithPlatformIcon,
PopoverTruncated,
)
from common.time import dateformat, local_strftime
from common.utils import truncate_with_popover
from games.forms import EditionForm
from games.models import Edition, Game
@ -50,30 +55,22 @@ def list_editions(request: HttpRequest) -> HttpResponse:
],
"rows": [
[
A(
[
(
"href",
reverse(
"view_game",
args=[edition.game.pk],
LinkedNameWithPlatformIcon(
name=edition.name,
game_id=edition.game.id,
platform=edition.platform,
),
)
],
truncate_with_popover(edition.game.name),
),
truncate_with_popover(
PopoverTruncated(
edition.name
if edition.game.name != edition.name
else "(identical)"
),
truncate_with_popover(
PopoverTruncated(
edition.sort_name
if edition.sort_name is not None
and edition.game.name != edition.sort_name
else "(identical)"
),
truncate_with_popover(str(edition.platform)),
edition.year_released,
edition.wikidata,
local_strftime(edition.created_at, dateformat),

View File

@ -8,7 +8,15 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.urls import reverse
from common.components import A, Button, Div, Icon, Popover
from common.components import (
A,
Button,
Div,
Icon,
NameWithPlatformIcon,
Popover,
PopoverTruncated,
)
from common.time import (
dateformat,
durationformat,
@ -17,7 +25,7 @@ from common.time import (
local_strftime,
timeformat,
)
from common.utils import safe_division, truncate, truncate_with_popover
from common.utils import format_float_or_int, safe_division, truncate
from games.forms import GameForm
from games.models import Edition, Game, Purchase, Session
from games.views.general import use_custom_redirect
@ -67,9 +75,9 @@ def list_games(request: HttpRequest) -> HttpResponse:
),
)
],
truncate_with_popover(game.name),
PopoverTruncated(game.name),
),
truncate_with_popover(
PopoverTruncated(
game.sort_name
if game.sort_name is not None and game.name != game.sort_name
else "(identical)"
@ -197,14 +205,15 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
edition_data: dict[str, Any] = {
"columns": [
"Name",
"Platform",
"Year Released",
"Actions",
],
"rows": [
[
edition.name,
Icon(str(edition.platform).lower().replace(".", "")),
NameWithPlatformIcon(
name=edition.name,
platform=edition.platform,
),
edition.year_released,
render_to_string(
"cotton/button_group.html",
@ -232,10 +241,13 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
"columns": ["Name", "Type", "Date", "Price", "Actions"],
"rows": [
[
purchase.name if purchase.name else purchase.edition.name,
NameWithPlatformIcon(
name=purchase.name if purchase.name else purchase.edition.name,
platform=purchase.platform,
),
purchase.get_type_display(),
purchase.date_purchased.strftime(dateformat),
f"{purchase.price} {purchase.price_currency}",
f"{format_float_or_int(purchase.price)} {purchase.price_currency}",
render_to_string(
"cotton/button_group.html",
{
@ -261,6 +273,8 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
sessions_all = Session.objects.filter(purchase__edition__game=game).order_by(
"-timestamp_start"
)
last_session = None
if sessions_all.exists():
last_session = sessions_all.latest()
session_count = sessions_all.count()
session_paginator = Paginator(sessions_all, 5)
@ -298,13 +312,21 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
)
],
),
),
)
if last_session
else "",
],
),
"columns": ["Date", "Duration", "Actions"],
"columns": ["Edition", "Date", "Duration", "Actions"],
"rows": [
[
f"{local_strftime(session.timestamp_start)}{f"{session.timestamp_end.strftime(timeformat)}" if session.timestamp_end else ""}",
NameWithPlatformIcon(
name=session.purchase.name
if session.purchase.name
else session.purchase.edition.name,
platform=session.purchase.platform,
),
f"{local_strftime(session.timestamp_start)}{f"{local_strftime(session.timestamp_end, timeformat)}" if session.timestamp_end else ""}",
(
format_duration(session.duration_calculated, durationformat)
if session.duration_calculated

View File

@ -8,7 +8,7 @@ from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect, render
from django.urls import reverse
from common.time import format_duration
from common.time import dateformat, format_duration
from common.utils import safe_division
from games.models import Edition, Game, Platform, Purchase, Session
@ -173,10 +173,10 @@ def stats_alltime(request: HttpRequest) -> HttpResponse:
if this_year_sessions:
first_session = this_year_sessions.earliest()
first_play_game = first_session.purchase.edition.game
first_play_date = first_session.timestamp_start.strftime("%x")
first_play_date = first_session.timestamp_start.strftime(dateformat)
last_session = this_year_sessions.latest()
last_play_game = last_session.purchase.edition.game
last_play_date = last_session.timestamp_start.strftime("%x")
last_play_date = last_session.timestamp_start.strftime(dateformat)
all_purchased_this_year_count = this_year_purchases_with_currency.count()
all_purchased_refunded_this_year_count: int = this_year_purchases_refunded.count()
@ -400,10 +400,10 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
if this_year_sessions:
first_session = this_year_sessions.earliest()
first_play_game = first_session.purchase.edition.game
first_play_date = first_session.timestamp_start.strftime("%x")
first_play_date = first_session.timestamp_start.strftime(dateformat)
last_session = this_year_sessions.latest()
last_play_game = last_session.purchase.edition.game
last_play_date = last_session.timestamp_start.strftime("%x")
last_play_date = last_session.timestamp_start.strftime(dateformat)
all_purchased_this_year_count = this_year_purchases_with_currency.count()
all_purchased_refunded_this_year_count = this_year_purchases_refunded.count()

View File

@ -19,7 +19,7 @@ def list_platforms(request: HttpRequest) -> HttpResponse:
context: dict[Any, Any] = {}
page_number = request.GET.get("page", 1)
limit = request.GET.get("limit", 10)
platforms = Platform.objects.order_by("-created_at")
platforms = Platform.objects.order_by("name")
page_obj = None
if int(limit) != 0:
paginator = Paginator(platforms, limit)
@ -40,6 +40,7 @@ def list_platforms(request: HttpRequest) -> HttpResponse:
"header_action": A([], Button([], "Add platform"), url="add_platform"),
"columns": [
"Name",
"Icon",
"Group",
"Created",
"Actions",
@ -47,6 +48,7 @@ def list_platforms(request: HttpRequest) -> HttpResponse:
"rows": [
[
platform.name,
Icon(platform.icon),
platform.group,
local_strftime(platform.created_at, dateformat),
render_to_string(

View File

@ -13,9 +13,9 @@ from django.template.loader import render_to_string
from django.urls import reverse
from django.utils import timezone
from common.components import A, Button, Div, Icon
from common.components import A, Button, Icon, LinkedNameWithPlatformIcon
from common.time import dateformat
from common.utils import truncate_with_popover
from common.utils import format_float_or_int
from games.forms import PurchaseForm
from games.models import Edition, Purchase
from games.views.general import use_custom_redirect
@ -60,38 +60,13 @@ def list_purchases(request: HttpRequest) -> HttpResponse:
],
"rows": [
[
A(
[
(
"href",
reverse(
"view_game",
args=[purchase.edition.game.pk],
),
),
],
Div(
attributes=[("class", "inline-flex gap-2 items-center")],
children=[
Icon(
str(purchase.platform)
.lower()
.translate(
str(purchase.platform)
.lower()
.maketrans("", "", ". /()")
)
),
truncate_with_popover(
purchase.edition.game.name
if purchase.type == "game"
else f"{purchase.edition.game.name} ({purchase.name})"
),
],
),
LinkedNameWithPlatformIcon(
name=purchase.edition.name,
game_id=purchase.edition.game.pk,
platform=purchase.platform,
),
purchase.get_type_display(),
purchase.price,
format_float_or_int(purchase.price),
purchase.price_currency,
purchase.infinite,
purchase.date_purchased.strftime(dateformat),

View File

@ -8,7 +8,7 @@ from django.template.loader import render_to_string
from django.urls import reverse
from django.utils import timezone
from common.components import A, Button, Div, Icon, Popover
from common.components import A, Button, Div, Icon, LinkedNameWithPlatformIcon, Popover
from common.time import (
dateformat,
durationformat,
@ -17,7 +17,7 @@ from common.time import (
local_strftime,
timeformat,
)
from common.utils import truncate, truncate_with_popover
from common.utils import truncate
from games.forms import SessionForm
from games.models import Purchase, Session
from games.views.general import use_custom_redirect
@ -91,12 +91,10 @@ def list_sessions(request: HttpRequest) -> HttpResponse:
],
"rows": [
[
A(
children=truncate_with_popover(session.purchase.edition.name),
url=reverse(
"view_game",
args=[session.purchase.edition.game.pk],
),
LinkedNameWithPlatformIcon(
name=session.purchase.edition.name,
game_id=session.purchase.edition.game.pk,
platform=session.purchase.platform,
),
f"{local_strftime(session.timestamp_start)}{f"{local_strftime(session.timestamp_end, timeformat)}" if session.timestamp_end else ""}",
(
@ -243,3 +241,10 @@ def delete_session(request: HttpRequest, session_id: int = 0) -> HttpResponse:
session = get_object_or_404(Session, id=session_id)
session.delete()
return redirect("list_sessions")
@login_required
def delete_session(request: HttpRequest, session_id: int = 0) -> HttpResponse:
session = get_object_or_404(Session, id=session_id)
session.delete()
return redirect("list_sessions")