Use min/max/precisions fields for game duration

There is an data migration for Game items but not for Suggestion items.
It assumes that all duration fields are filled with a single integer or
a range separated by '-' or 'à'.
This commit is contained in:
Guillaume Bertholon 2021-01-29 12:07:46 +01:00
parent d5d239c3df
commit 1fea637d7a
9 changed files with 206 additions and 37 deletions

View file

@ -0,0 +1,77 @@
# Generated by Django 3.1.5 on 2021-01-28 23:15
from django.db import migrations, models
def extract_duration_range(apps, schema_editor):
Game = apps.get_model("inventory", "Game")
for game in Game.objects.all():
sep = None
if game.duration.count("-") == 1:
sep = "-"
elif game.duration.count("à") == 1:
sep = "à"
if sep:
duration_split = game.duration.split(sep)
game.duration_min = int(duration_split[0])
game.duration_max = int(duration_split[1])
else:
single_duration = int(game.duration)
game.duration_min = single_duration
game.duration_max = single_duration
game.duration = ""
game.save()
class Migration(migrations.Migration):
dependencies = [
("inventory", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="game",
name="duration_max",
field=models.PositiveSmallIntegerField(
null=True,
),
),
migrations.AddField(
model_name="game",
name="duration_min",
field=models.PositiveSmallIntegerField(
null=True,
),
),
migrations.AlterField(
model_name="game",
name="duration",
field=models.CharField(
blank=True,
help_text="Affichage personnalisé pour la durée de la partie",
max_length=256,
verbose_name="durée de partie",
),
),
migrations.RunPython(extract_duration_range),
migrations.AlterField(
model_name="game",
name="duration_max",
field=models.PositiveSmallIntegerField(
blank=True,
help_text="En minutes, telle qu'indiquée par l'éditeur, identique à la durée minimale si laissée vide",
verbose_name="durée de partie maximale",
),
),
migrations.AlterField(
model_name="game",
name="duration_min",
field=models.PositiveSmallIntegerField(
help_text="En minutes, telle qu'indiquée par l'éditeur",
verbose_name="durée de partie minimale",
),
),
]

View file

@ -51,8 +51,21 @@ class Game(models.Model):
help_text="Affichage personnalisé pour le nombre de joueur·se·s",
verbose_name="nombre de joueur·se·s",
)
duration_min = models.PositiveSmallIntegerField(
verbose_name="durée de partie minimale",
help_text="En minutes, telle qu'indiquée par l'éditeur",
)
duration_max = models.PositiveSmallIntegerField(
verbose_name="durée de partie maximale",
help_text="En minutes, telle qu'indiquée par l'éditeur, identique à la durée minimale si laissée vide",
blank=True,
)
duration = models.CharField(
max_length=256, blank=True, verbose_name="durée de partie"
max_length=256,
blank=True,
help_text="Affichage personnalisé pour la durée de la partie",
verbose_name="durée de partie",
)
game_designer = models.CharField(
@ -88,16 +101,22 @@ class Game(models.Model):
return self.title
def clean(self):
if (
self.nb_player_min is not None
and self.nb_player_max is not None
and self.nb_player_min > self.nb_player_max
):
if not self.nb_player_min or not self.nb_player_max or not self.duration_min:
return
if self.nb_player_min > self.nb_player_max:
raise ValidationError(
{
"nb_player_max": "Le nombre de joueur·se·s maximum doit être supérieur au nombre de joueur·se·s minimum"
}
)
if self.duration_max is None:
self.duration_max = self.duration_min
if self.duration_min > self.duration_max:
raise ValidationError(
{
"duration_max": "La durée maximale doit être supérieure à la durée minimale"
}
)
def get_player_range(self):
if self.player_range:
@ -107,6 +126,14 @@ class Game(models.Model):
else:
return "{} joueur·se·s".format(self.nb_player_min)
def get_duration_range(self):
if self.duration:
return self.duration
elif self.duration_min != self.duration_max:
return "{} à {} min".format(self.duration_min, self.duration_max)
else:
return "{} min".format(self.duration_min)
def get_absolute_url(self):
return reverse("inventory:game", args=(self.slug,))

View file

@ -13,9 +13,9 @@
<p><i class="fa fa-fw fa-bookmark"></i> <a href="{{ game.category.get_absolute_url }}">{{ game.category }}</a></p>
<hr/>
<p><i class="fa fa-fw fa-users" aria-hidden="true"></i> {{ game.get_player_range }}</p>
<p><i class="fa fa-fw fa-clock-o" aria-hidden="true"></i> {{ game.duration|default:"(Durée de jeu inconnue)" }}
<p><i class="fa fa-fw fa-clock-o" aria-hidden="true"></i> {{ game.get_duration_range }}</p>
<hr/>
<p><i class="fa fa-fw fa-tags" aria-hidden="true"></i>
<p><i class="fa fa-fw fa-tags" aria-hidden="true"></i>
{% for tag in game.tags.all %}
<a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %},{% endif %}
{% empty %}
@ -23,8 +23,8 @@
{% endfor %}
</p>
<hr/>
<p><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> {{ game.game_designer|default:"(Game designer inconnu·e)" }}</li>
<p><i class="fa fa-fw fa-paint-brush" aria-hidden="true"></i> {{ game.illustrator|default:"(Illustrateur·trice inconnu·e)" }}</li>
<p><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> {{ game.game_designer|default:"(Game designer inconnu·e)" }}</p>
<p><i class="fa fa-fw fa-paint-brush" aria-hidden="true"></i> {{ game.illustrator|default:"(Illustrateur·trice inconnu·e)" }}</p>
<p><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> {{ game.editor|default:"(Éditeur inconnu)" }}</p>
</div>
</div>

View file

@ -1,22 +1,20 @@
<a class="inventory_item game" href="{{ game.get_absolute_url }}">
<span class="title">{{ game.title }}</span>
<span class="details">
<span><i class="fa fa-fw fa-users" aria-hidden="true"></i> {{ game.get_player_range }}</span>
{% if game.duration %}
<span><i class="fa fa-fw fa-clock-o" aria-hidden="true"></i> {{ game.duration }}</span>
{% endif %}
<span><i class="fa fa-fw fa-bookmark"></i> {{ game.category }}</span>
<span><i class="fa fa-users" aria-hidden="true"></i> {{ game.get_player_range }}</span>
<span><i class="fa fa-clock-o" aria-hidden="true"></i> {{ game.get_duration_range }}</span>
<span><i class="fa fa-bookmark"></i> {{ game.category }}</span>
{% for tag in game.tags.all %}
<span><i class="fa fa-fw fa-tag" aria-hidden="true"></i> {{ tag }}</span>
<span><i class="fa fa-tag" aria-hidden="true"></i> {{ tag }}</span>
{% endfor %}
{% if game.game_designer %}
<span><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> {{ game.game_designer }}</span>
<span><i class="fa fa-wrench" aria-hidden="true"></i> {{ game.game_designer }}</span>
{% endif %}
{% if game.illustrator %}
<span><i class="fa fa-fw fa-paint-brush" aria-hidden="true"></i> {{ game.illustrator }}</span>
<span><i class="fa fa-paint-brush" aria-hidden="true"></i> {{ game.illustrator }}</span>
{% endif %}
{% if game.editor %}
<span><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> {{ game.editor }}</span>
<span><i class="fa fa-cogs" aria-hidden="true"></i> {{ game.editor }}</span>
{% endif %}
</span>
</a>

View file

@ -15,7 +15,9 @@ class SuggestionForm(forms.ModelForm):
"nb_player_min",
"nb_player_max",
"player_range_precisions",
"duration",
"duration_min",
"duration_max",
"duration_precisions",
"category",
"tags",
"game_designer",

View file

@ -0,0 +1,34 @@
# Generated by Django 3.1.5 on 2021-01-29 00:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('suggestions', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='suggestion',
name='duration',
),
migrations.AddField(
model_name='suggestion',
name='duration_max',
field=models.PositiveSmallIntegerField(blank=True, default=60, help_text="En minutes, telle qu'indiquée par l'éditeur, identique à la durée minimale si laissée vide", verbose_name='durée de partie maximale'),
preserve_default=False,
),
migrations.AddField(
model_name='suggestion',
name='duration_min',
field=models.PositiveSmallIntegerField(default=60, help_text="En minutes, telle qu'indiquée par l'éditeur", verbose_name='durée de partie minimale'),
preserve_default=False,
),
migrations.AddField(
model_name='suggestion',
name='duration_precisions',
field=models.CharField(blank=True, help_text='Pour indiquer des informations complémentaires sur la durée de la partie (ex. évolution en fonction du nombre de joueur·se·s)', max_length=256, verbose_name='précisions sur la durée de partie'),
),
]

View file

@ -34,7 +34,21 @@ class Suggestion(models.Model):
help_text="Pour indiquer une éventuelle contrainte (ex. parité) ou information sur le nombre de joueur·se·s",
)
duration = models.CharField(max_length=256, verbose_name="durée de partie")
duration_min = models.PositiveSmallIntegerField(
verbose_name="durée de partie minimale",
help_text="En minutes, telle qu'indiquée par l'éditeur",
)
duration_max = models.PositiveSmallIntegerField(
verbose_name="durée de partie maximale",
help_text="En minutes, telle qu'indiquée par l'éditeur, identique à la durée minimale si laissée vide",
blank=True,
)
duration_precisions = models.CharField(
max_length=256,
blank=True,
verbose_name="précisions sur la durée de partie",
help_text="Pour indiquer des informations complémentaires sur la durée de la partie (ex. évolution en fonction du nombre de joueur·se·s)",
)
game_designer = models.CharField(
max_length=256, blank=True, verbose_name="game designer"
@ -89,7 +103,7 @@ class Suggestion(models.Model):
return self.title
def clean(self):
if not self.nb_player_min or not self.nb_player_max:
if not self.nb_player_min or not self.nb_player_max or not self.duration_min:
return
if self.nb_player_min > self.nb_player_max:
raise ValidationError(
@ -97,6 +111,14 @@ class Suggestion(models.Model):
"nb_player_max": "Le nombre de joueur·se·s maximum doit être supérieur au nombre de joueur·se·s minimum"
}
)
if self.duration_max is None:
self.duration_max = self.duration_min
if self.duration_min > self.duration_max:
raise ValidationError(
{
"duration_max": "La durée maximale doit être supérieure à la durée minimale"
}
)
def get_player_range(self):
precisions = ""
@ -109,6 +131,17 @@ class Suggestion(models.Model):
else:
return "{} joueur·se·s{}".format(self.nb_player_min, precisions)
def get_duration_range(self):
precisions = ""
if self.duration_precisions:
precisions = " ({})".format(self.duration_precisions)
elif self.duration_min != self.duration_max:
return "{} à {} min{}".format(
self.duration_min, self.duration_max, precisions
)
else:
return "{} min{}".format(self.duration_min, precisions)
def get_absolute_url(self):
return reverse("suggestions:suggestion", args=(self.slug,))

View file

@ -1,26 +1,24 @@
<a class="inventory_item suggestion" href="{{ suggestion.get_absolute_url }}">
<span class="title">{{ suggestion.title }}</span>
<span class="details">
<span><i class="fa fa-fw fa-thumbs-up" aria-hidden="true"></i> {{ suggestion.upvoting_users.count }}</span>
<span><i class="fa fa-fw fa-money" aria-hidden="true"></i> {{ suggestion.price }} €</span>
<span><i class="fa fa-fw fa-users" aria-hidden="true"></i> {{ suggestion.get_player_range }}</span>
{% if suggestion.duration %}
<span><i class="fa fa-fw fa-clock-o" aria-hidden="true"></i> {{ suggestion.duration }}</span>
{% endif %}
<span><i class="fa fa-thumbs-up" aria-hidden="true"></i> {{ suggestion.upvoting_users.count }}</span>
<span><i class="fa fa-money" aria-hidden="true"></i> {{ suggestion.price }} €</span>
<span><i class="fa fa-users" aria-hidden="true"></i> {{ suggestion.get_player_range }}</span>
<span><i class="fa fa-clock-o" aria-hidden="true"></i> {{ suggestion.get_duration_range }}</span>
{% if suggestion.category %}
<span><i class="fa fa-fw fa-bookmark"></i> {{ suggestion.category }}</span>
<span><i class="fa fa-bookmark"></i> {{ suggestion.category }}</span>
{% endif %}
{% for tag in suggestion.tags.all %}
<span><i class="fa fa-fw fa-tag" aria-hidden="true"></i> {{ tag }}</span>
<span><i class="fa fa-tag" aria-hidden="true"></i> {{ tag }}</span>
{% endfor %}
{% if suggestion.game_designer %}
<span><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> {{ suggestion.game_designer }}</span>
<span><i class="fa fa-wrench" aria-hidden="true"></i> {{ suggestion.game_designer }}</span>
{% endif %}
{% if suggestion.illustrator %}
<span><i class="fa fa-fw fa-paint-brush" aria-hidden="true"></i> {{ suggestion.illustrator }}</span>
<span><i class="fa fa-paint-brush" aria-hidden="true"></i> {{ suggestion.illustrator }}</span>
{% endif %}
{% if suggestion.editor %}
<span><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> {{ suggestion.editor }}</span>
<span><i class="fa fa-cogs" aria-hidden="true"></i> {{ suggestion.editor }}</span>
{% endif %}
</span>
</a>

View file

@ -13,10 +13,10 @@
<p><i class="fa fa-fw fa-money" aria-hidden="true"></i> <a href="{{ suggestion.buy_link }}" rel="nofollow">{{ suggestion.price }} €</a></p>
<hr/>
<p><i class="fa fa-fw fa-users" aria-hidden="true"></i> {{ suggestion.get_player_range }}</p>
<p><i class="fa fa-fw fa-clock-o" aria-hidden="true"></i> {{ suggestion.duration|default:"(Durée de jeu inconnue)" }}
<p><i class="fa fa-fw fa-clock-o" aria-hidden="true"></i> {{ suggestion.get_duration_range }}</p>
<hr/>
<p><i class="fa fa-fw fa-bookmark" aria-hidden="true"></i> {% if suggestion.category %}<a href="{{ suggestion.category.get_absolute_url }}">{{ suggestion.category }}</a>{% else %}(Pas de catégorie){% endif %}</p>
<p><i class="fa fa-fw fa-tags" aria-hidden="true"></i>
<p><i class="fa fa-fw fa-tags" aria-hidden="true"></i>
{% for tag in suggestion.tags.all %}
<a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %},{% endif %}
{% empty %}
@ -24,8 +24,8 @@
{% endfor %}
</p>
<hr/>
<p><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> {{ suggestion.game_designer|default:"(Game designer inconnu·e)" }}</li>
<p><i class="fa fa-fw fa-paint-brush" aria-hidden="true"></i> {{ suggestion.illustrator|default:"(Illustrateur·trice inconnu·e)" }}</li>
<p><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> {{ suggestion.game_designer|default:"(Game designer inconnu·e)" }}</p>
<p><i class="fa fa-fw fa-paint-brush" aria-hidden="true"></i> {{ suggestion.illustrator|default:"(Illustrateur·trice inconnu·e)" }}</p>
<p><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> {{ suggestion.editor|default:"(Éditeur inconnu)" }}</p>
</div>
</div>