Add status, mastered to Game

This commit is contained in:
Lukáš Kucharczyk 2025-02-04 20:09:05 +01:00
parent b2b69339b3
commit b22e185d47
Signed by: lukas
SSH Key Fingerprint: SHA256:vMuSwvwAvcT6htVAioMP7rzzwMQNi3roESyhv+nAxeg
7 changed files with 193 additions and 1 deletions

View File

@ -24,6 +24,12 @@ class SessionForm(forms.ModelForm):
device = forms.ModelChoiceField(queryset=Device.objects.order_by("name")) device = forms.ModelChoiceField(queryset=Device.objects.order_by("name"))
mark_as_played = forms.BooleanField(
required=False,
initial={"mark_as_played": True},
label="Set game status to Played if Unplayed",
)
class Meta: class Meta:
widgets = { widgets = {
"timestamp_start": custom_datetime_widget, "timestamp_start": custom_datetime_widget,
@ -38,8 +44,21 @@ class SessionForm(forms.ModelForm):
"emulated", "emulated",
"device", "device",
"note", "note",
"mark_as_played",
] ]
def save(self, commit=True):
session = super().save(commit=False)
if self.cleaned_data.get("mark_as_played"):
game_instance = session.game
if game_instance.status == "u":
game_instance.status = "p"
if commit:
game_instance.save()
if commit:
session.save()
return session
class IncludePlatformSelect(forms.SelectMultiple): class IncludePlatformSelect(forms.SelectMultiple):
def create_option(self, name, value, *args, **kwargs): def create_option(self, name, value, *args, **kwargs):
@ -143,7 +162,14 @@ class GameForm(forms.ModelForm):
class Meta: class Meta:
model = Game model = Game
fields = ["name", "sort_name", "platform", "year_released", "wikidata"] fields = [
"name",
"sort_name",
"platform",
"year_released",
"status",
"wikidata",
]
widgets = {"name": autofocus_input_widget} widgets = {"name": autofocus_input_widget}

View File

@ -0,0 +1,38 @@
# Generated by Django 5.1.5 on 2025-02-01 19:18
from django.db import migrations, models
def set_finished_status(apps, schema_editor):
Game = apps.get_model("games", "Game")
Game.objects.filter(purchases__date_finished__isnull=False).update(status="f")
class Migration(migrations.Migration):
dependencies = [
("games", "0004_purchase_num_purchases"),
]
operations = [
migrations.AddField(
model_name="game",
name="mastered",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="game",
name="status",
field=models.CharField(
choices=[
("u", "Unplayed"),
("p", "Played"),
("f", "Finished"),
("r", "Retired"),
("a", "Abandoned"),
],
default="u",
max_length=1,
),
),
migrations.RunPython(set_finished_status),
]

View File

@ -23,6 +23,31 @@ class Game(models.Model):
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
class Status(models.TextChoices):
UNPLAYED = (
"u",
"Unplayed",
)
PLAYED = (
"p",
"Played",
)
FINISHED = (
"f",
"Finished",
)
RETIRED = (
"r",
"Retired",
)
ABANDONED = (
"a",
"Abandoned",
)
status = models.CharField(max_length=1, choices=Status, default=Status.UNPLAYED)
mastered = models.BooleanField(default=False)
session_average: float | int | timedelta | None session_average: float | int | timedelta | None
session_count: int | None session_count: int | None

View File

@ -1299,6 +1299,10 @@ input:checked + .toggle-bg {
bottom: 0px; bottom: 0px;
} }
.-left-3 {
left: -0.75rem;
}
.bottom-0 { .bottom-0 {
bottom: 0px; bottom: 0px;
} }
@ -1335,6 +1339,10 @@ input:checked + .toggle-bg {
top: 0px; top: 0px;
} }
.top-2 {
top: 0.5rem;
}
.top-3 { .top-3 {
top: 0.75rem; top: 0.75rem;
} }
@ -1415,6 +1423,10 @@ input:checked + .toggle-bg {
margin-inline-end: 0.5rem; margin-inline-end: 0.5rem;
} }
.ml-3 {
margin-left: 0.75rem;
}
.mr-4 { .mr-4 {
margin-right: 1rem; margin-right: 1rem;
} }
@ -1488,6 +1500,10 @@ input:checked + .toggle-bg {
height: 3rem; height: 3rem;
} }
.h-2 {
height: 0.5rem;
}
.h-2\.5 { .h-2\.5 {
height: 0.625rem; height: 0.625rem;
} }
@ -1524,6 +1540,10 @@ input:checked + .toggle-bg {
width: 2.5rem; width: 2.5rem;
} }
.w-2 {
width: 0.5rem;
}
.w-2\.5 { .w-2\.5 {
width: 0.625rem; width: 0.625rem;
} }
@ -1668,6 +1688,10 @@ input:checked + .toggle-bg {
grid-template-columns: repeat(7, minmax(0, 1fr)); grid-template-columns: repeat(7, minmax(0, 1fr));
} }
.flex-row {
flex-direction: row;
}
.flex-col { .flex-col {
flex-direction: column; flex-direction: column;
} }
@ -1716,6 +1740,14 @@ input:checked + .toggle-bg {
gap: 1.25rem; gap: 1.25rem;
} }
.gap-3 {
gap: 0.75rem;
}
.gap-6 {
gap: 1.5rem;
}
.-space-x-px > :not([hidden]) ~ :not([hidden]) { .-space-x-px > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0; --tw-space-x-reverse: 0;
margin-right: calc(-1px * var(--tw-space-x-reverse)); margin-right: calc(-1px * var(--tw-space-x-reverse));
@ -1787,6 +1819,10 @@ input:checked + .toggle-bg {
border-radius: 0.125rem; border-radius: 0.125rem;
} }
.rounded-xl {
border-radius: 0.75rem;
}
.rounded-e-lg { .rounded-e-lg {
border-start-end-radius: 0.5rem; border-start-end-radius: 0.5rem;
border-end-end-radius: 0.5rem; border-end-end-radius: 0.5rem;
@ -1880,6 +1916,11 @@ input:checked + .toggle-bg {
background-color: rgb(249 250 251 / var(--tw-bg-opacity)); background-color: rgb(249 250 251 / var(--tw-bg-opacity));
} }
.bg-gray-500 {
--tw-bg-opacity: 1;
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
}
.bg-gray-800 { .bg-gray-800 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(31 41 55 / var(--tw-bg-opacity)); background-color: rgb(31 41 55 / var(--tw-bg-opacity));
@ -1889,6 +1930,11 @@ input:checked + .toggle-bg {
background-color: rgb(17 24 39 / 0.5); background-color: rgb(17 24 39 / 0.5);
} }
.bg-green-500 {
--tw-bg-opacity: 1;
background-color: rgb(14 159 110 / var(--tw-bg-opacity));
}
.bg-green-600 { .bg-green-600 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(5 122 85 / var(--tw-bg-opacity)); background-color: rgb(5 122 85 / var(--tw-bg-opacity));
@ -1899,6 +1945,21 @@ input:checked + .toggle-bg {
background-color: rgb(4 108 78 / var(--tw-bg-opacity)); background-color: rgb(4 108 78 / var(--tw-bg-opacity));
} }
.bg-orange-400 {
--tw-bg-opacity: 1;
background-color: rgb(255 138 76 / var(--tw-bg-opacity));
}
.bg-purple-500 {
--tw-bg-opacity: 1;
background-color: rgb(144 97 249 / var(--tw-bg-opacity));
}
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(240 82 82 / var(--tw-bg-opacity));
}
.bg-red-700 { .bg-red-700 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(200 30 30 / var(--tw-bg-opacity)); background-color: rgb(200 30 30 / var(--tw-bg-opacity));
@ -2030,6 +2091,10 @@ input:checked + .toggle-bg {
vertical-align: top; vertical-align: top;
} }
.align-middle {
vertical-align: middle;
}
.font-mono { .font-mono {
font-family: IBM Plex Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-family: IBM Plex Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
} }
@ -2192,6 +2257,11 @@ input:checked + .toggle-bg {
color: rgb(250 202 21 / var(--tw-text-opacity)); color: rgb(250 202 21 / var(--tw-text-opacity));
} }
.text-slate-400 {
--tw-text-opacity: 1;
color: rgb(148 163 184 / var(--tw-text-opacity));
}
.underline { .underline {
text-decoration-line: underline; text-decoration-line: underline;
} }

View File

@ -0,0 +1,16 @@
<div class="relative ml-3">
<span class="rounded-xl w-2 h-2 absolute -left-3 top-2
{% if status == "u" %}
bg-gray-500
{% elif status == "p" %}
bg-orange-400
{% elif status == "f" %}
bg-green-500
{% elif status == "a" %}
bg-red-500
{% elif status == "r" %}
bg-purple-500
{% endif %}
">&nbsp;</span>
{{ slot }}
</div>

View File

@ -52,6 +52,18 @@
{{ playrange }} {{ playrange }}
</c-popover> </c-popover>
</div> </div>
<div class="mb-6 text-slate-400">
<div class="flex gap-2 items-center">
<span class="uppercase font-bold text-slate-300">Status</span>
<c-gamestatus :status="game.status">
{{ game.get_status_display }}
</c-gamestatus>
</div>
<div class="flex gap-2 items-center">
<span class="uppercase font-bold text-slate-300">Platform</span>
<span>{{ game.platform }}</span>
</div>
</div>
<div class="inline-flex rounded-md shadow-sm mb-3" role="group"> <div class="inline-flex rounded-md shadow-sm mb-3" role="group">
<a href="{% url 'edit_game' game.id %}"> <a href="{% url 'edit_game' game.id %}">
<button type="button" <button type="button"

View File

@ -61,6 +61,7 @@ def list_games(request: HttpRequest) -> HttpResponse:
"Name", "Name",
"Sort Name", "Sort Name",
"Year", "Year",
"Status",
"Wikidata", "Wikidata",
"Created", "Created",
"Actions", "Actions",
@ -74,6 +75,10 @@ def list_games(request: HttpRequest) -> HttpResponse:
else "(identical)" else "(identical)"
), ),
game.year_released, game.year_released,
render_to_string(
"cotton/gamestatus.html",
{"status": game.status, "slot": game.get_status_display()},
),
game.wikidata, game.wikidata,
local_strftime(game.created_at, dateformat), local_strftime(game.created_at, dateformat),
render_to_string( render_to_string(