Add status, mastered to Game
This commit is contained in:
parent
b2b69339b3
commit
b22e185d47
@ -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}
|
||||||
|
|
||||||
|
|
||||||
|
38
games/migrations/0005_game_mastered_game_status.py
Normal file
38
games/migrations/0005_game_mastered_game_status.py
Normal 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),
|
||||||
|
]
|
@ -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
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
16
games/templates/cotton/gamestatus.html
Normal file
16
games/templates/cotton/gamestatus.html
Normal 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 %}
|
||||||
|
"> </span>
|
||||||
|
{{ slot }}
|
||||||
|
</div>
|
@ -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"
|
||||||
|
@ -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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user