Add game comments and rework game models

Models are now more structured for number of players, and durations can
be omitted.
This commit is contained in:
Guillaume Bertholon 2020-12-13 00:12:06 +01:00
parent 497e83eca3
commit ccd8ee9cc9
25 changed files with 547 additions and 162 deletions

View file

@ -1,6 +1,7 @@
from django.contrib import admin from django.contrib import admin
from .models import Category, Tag, Game from .models import Category, Tag, Game, GameComment
admin.site.register(Category) admin.site.register(Category)
admin.site.register(Tag) admin.site.register(Tag)
admin.site.register(Game) admin.site.register(Game)
admin.site.register(GameComment)

View file

@ -0,0 +1,76 @@
# Generated by Django 3.1.2 on 2020-12-12 21:33
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('inventory', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='category',
options={'ordering': ['name'], 'verbose_name': 'catégorie'},
),
migrations.AlterModelOptions(
name='game',
options={'ordering': ['title'], 'verbose_name': 'jeu', 'verbose_name_plural': 'jeux'},
),
migrations.AlterModelOptions(
name='tag',
options={'ordering': ['name']},
),
migrations.AddField(
model_name='game',
name='illustrator',
field=models.CharField(blank=True, max_length=256, verbose_name='illustrateur·trice'),
),
migrations.AddField(
model_name='game',
name='missing_elements',
field=models.TextField(blank=True, verbose_name='pièces manquantes'),
),
migrations.AddField(
model_name='game',
name='nb_player_max',
field=models.PositiveSmallIntegerField(default=20, verbose_name='nombre de joueur·se·s maximum'),
preserve_default=False,
),
migrations.AddField(
model_name='game',
name='nb_player_min',
field=models.PositiveSmallIntegerField(default=2, verbose_name='nombre de joueur·se·s minimum'),
preserve_default=False,
),
migrations.AlterField(
model_name='game',
name='duration',
field=models.CharField(blank=True, max_length=256, verbose_name='durée de partie'),
),
migrations.AlterField(
model_name='game',
name='player_range',
field=models.CharField(blank=True, help_text='Affichage personnalisé pour le nombre de joueur·se·s', max_length=256, verbose_name='nombre de joueur·se·s'),
),
migrations.CreateModel(
name='GameComment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField(verbose_name='texte')),
('created_on', models.DateTimeField(auto_now_add=True, verbose_name='date de publication')),
('modified_on', models.DateTimeField(auto_now=True, verbose_name='date de modification')),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='auteur·ice')),
('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='inventory.game', verbose_name='jeu')),
],
options={
'verbose_name': 'commentaire sur un jeu',
'verbose_name_plural': 'commentaires sur des jeux',
'ordering': ['created_on'],
},
),
]

View file

@ -1,5 +1,7 @@
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from autoslug import AutoSlugField from autoslug import AutoSlugField
@ -8,6 +10,7 @@ class Category(models.Model):
slug = AutoSlugField(populate_from="name", unique=True) slug = AutoSlugField(populate_from="name", unique=True)
class Meta: class Meta:
ordering = ["name"]
verbose_name = "catégorie" verbose_name = "catégorie"
def __str__(self): def __str__(self):
@ -21,6 +24,9 @@ class Tag(models.Model):
name = models.CharField(max_length=256, verbose_name="nom") name = models.CharField(max_length=256, verbose_name="nom")
slug = AutoSlugField(populate_from="name", unique=True) slug = AutoSlugField(populate_from="name", unique=True)
class Meta:
ordering = ["name"]
def __str__(self): def __str__(self):
return self.name return self.name
@ -31,27 +37,83 @@ class Tag(models.Model):
class Game(models.Model): class Game(models.Model):
title = models.CharField(verbose_name="titre", max_length=256) title = models.CharField(verbose_name="titre", max_length=256)
slug = AutoSlugField(populate_from="title", unique=True) slug = AutoSlugField(populate_from="title", unique=True)
player_range = models.CharField( nb_player_min = models.PositiveSmallIntegerField(
max_length=256, verbose_name="nombre de joueur·se·s" verbose_name="nombre de joueur·se·s minimum"
)
nb_player_max = models.PositiveSmallIntegerField(
verbose_name="nombre de joueur·se·s maximum"
)
player_range = models.CharField(
max_length=256,
blank=True,
help_text="Affichage personnalisé pour le nombre de joueur·se·s",
verbose_name="nombre de joueur·se·s",
)
duration = models.CharField(
max_length=256, blank=True, verbose_name="durée de partie"
) )
duration = models.CharField(max_length=256, verbose_name="durée de partie")
editor = models.CharField(max_length=256, blank=True, verbose_name="éditeur")
game_designer = models.CharField( game_designer = models.CharField(
max_length=256, blank=True, verbose_name="game designer" max_length=256, blank=True, verbose_name="game designer"
) )
illustrator = models.CharField(
max_length=256, blank=True, verbose_name="illustrateur·trice"
)
editor = models.CharField(max_length=256, blank=True, verbose_name="éditeur")
description = models.TextField(blank=True, verbose_name="description") description = models.TextField(blank=True, verbose_name="description")
category = models.ForeignKey( category = models.ForeignKey(
Category, on_delete=models.RESTRICT, verbose_name="catégorie" Category, on_delete=models.RESTRICT, verbose_name="catégorie"
) )
tags = models.ManyToManyField(Tag, blank=True, verbose_name="tags") tags = models.ManyToManyField(Tag, blank=True, verbose_name="tags")
image = models.ImageField(upload_to="game_img/", blank=True, verbose_name="image") image = models.ImageField(upload_to="game_img/", blank=True, verbose_name="image")
missing_elements = models.TextField(blank=True, verbose_name="pièces manquantes")
class Meta: class Meta:
ordering = ["title"]
verbose_name = "jeu" verbose_name = "jeu"
verbose_name_plural = "jeux" verbose_name_plural = "jeux"
def __str__(self): def __str__(self):
return self.title return self.title
def clean(self):
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 joueurs minimum"
}
)
def get_player_range(self):
if self.player_range:
return self.player_range
elif self.nb_player_min != self.nb_player_max:
return "{} à {} joueur·se·s".format(self.nb_player_min, self.nb_player_max)
else:
return "{} joueur·se·s".format(self.nb_player_min)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("inventory:game", args=(self.slug,)) return reverse("inventory:game", args=(self.slug,))
class GameComment(models.Model):
game = models.ForeignKey(
Game, on_delete=models.CASCADE, related_name="comments", verbose_name="jeu"
)
author = models.ForeignKey(
User, on_delete=models.CASCADE, verbose_name="auteur·ice"
)
text = models.TextField(verbose_name="texte")
created_on = models.DateTimeField(
auto_now_add=True, verbose_name="date de publication"
)
modified_on = models.DateTimeField(
auto_now=True, verbose_name="date de modification"
)
class Meta:
ordering = ["created_on"]
verbose_name = "commentaire sur un jeu"
verbose_name_plural = "commentaires sur des jeux"
def __str__(self):
return "({}) {}: {}".format(self.game, self.author, self.text)

View file

@ -6,7 +6,7 @@
Il y a {{ category_list|length }} catégorie{{ category_list|pluralize }} de jeux : Il y a {{ category_list|length }} catégorie{{ category_list|pluralize }} de jeux :
<ul> <ul>
{% for category in category_list %} {% for category in category_list %}
<li><a href="{{ category.get_absolute_url }}"><i class="fa fa-bookmark" aria-hidden="true"></i> {{ category.name }}</a></li> {% include "./partials/category_item.html" %}
{% endfor %} {% endfor %}
</ul> </ul>
{% endblock %} {% endblock %}

View file

@ -12,8 +12,8 @@
<div id="details"> <div id="details">
<p><i class="fa fa-fw fa-bookmark"></i> <a href="{{ game.category.get_absolute_url }}">{{ game.category }}</a></p> <p><i class="fa fa-fw fa-bookmark"></i> <a href="{{ game.category.get_absolute_url }}">{{ game.category }}</a></p>
<hr/> <hr/>
<p><i class="fa fa-fw fa-users" aria-hidden="true"></i> {{ game.player_range }}</p> <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 }} <p><i class="fa fa-fw fa-clock-o" aria-hidden="true"></i> {{ game.duration|default:"(Durée de jeu inconnue)" }}
<hr/> <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 %} {% for tag in game.tags.all %}
@ -23,10 +23,56 @@
{% endfor %} {% endfor %}
</p> </p>
<hr/> <hr/>
<p><i class="fa fa-fw fa-pencil" aria-hidden="true"></i> {{ game.game_designer|default:"(Game designer inconnu)" }}</li> <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-cogs" aria-hidden="true"></i> {{ game.editor|default:"(Éditeur inconnu)" }}</p> <p><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> {{ game.editor|default:"(Éditeur inconnu)" }}</p>
</div> </div>
</div> </div>
<p id="description">{{ object.description }}</p> <h2 id="description">Description</h2>
{{ object.description|linebreaks }}
{% if game.missing_elements %}
<h2>Pièces manquantes</h2>
<p class="warning">{{ game.missing_elements|linebreaksbr }}</p>
{% endif %}
<h2>Commentaires et propositions de variantes</h2>
<ul>
{% for comment in game.comments.all %}
{% if comment == edited_comment %}
<li id="edited_comment">
<form class="comment" method="post">
{% csrf_token %}
<textarea name="comment_text" required>{{ comment.text }}</textarea>
<button type="submit"><i class="fa fa-pencil" aria-hidden="true"></i> Modifier mon commentaire</button>
</form>
</li>
{% else %}
<li class="comment">
<div class="meta">
<span class="author">{{ comment.author }}</span>
<span class="date">posté le {{ comment.created_on|date }} à {{ comment.created_on|time }}{% if comment.created_on|date:"YmdHi" != comment.modified_on|date:"YmdHi" %}, dernière modification le {{ comment.modified_on|date }} à {{ comment.modified_on|time }}{% endif %}</span>
{% if comment.author == request.user %}
<a href="{% url "inventory:modify_game_comment" game.slug comment.id %}#edited_comment"><i class="fa fa-pencil" aria-hidden="true"></i></a>
{% endif %}
</div>
{{ comment.text|linebreaks }}
</li>
{% endif %}
{% empty %}
<li>(Aucun commentaire sur ce jeu)</li>
{% endfor %}
</ul>
{% if not edited_comment %}
{% if request.user.is_authenticated %}
<form class="comment" method="post" action="{% url "inventory:add_game_comment" game.slug %}">
{% csrf_token %}
<textarea name="comment_text" placeholder="Ajouter un commentaire à propos de {{ game.title }}" required></textarea>
<button type="submit"><i class="fa fa-paper-plane" aria-hidden="true"></i> Envoyer le commentaire</button>
</form>
{% else %}
<a href="{% url "gestiojeux_auth:login" %}?next={{ request.get_full_path }}">Connectez-vous</a> pour ajouter un commentaire.
{% endif %}
{% endif %}
{% endblock %} {% endblock %}

View file

@ -0,0 +1,3 @@
<li>
<a href="{{ category.get_absolute_url }}"><i class="fa fa-bookmark" aria-hidden="true"></i> <span class="title">{{ category.name }}</span></a>
</li>

View file

@ -1,15 +1,18 @@
<li> <li>
<a href="{{ game.get_absolute_url }}"> <a href="{{ game.get_absolute_url }}">
{{game.title}} <span class="title">{{ game.title }}</span>
<div class="details"> <div class="details">
<p><i class="fa fa-fw fa-users" aria-hidden="true"></i> {{ game.player_range }}</p> <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 }} <p><i class="fa fa-fw fa-clock-o" aria-hidden="true"></i> {{ game.duration }}
<p><i class="fa fa-fw fa-bookmark"></i> {{ game.category }}</p> <p><i class="fa fa-fw fa-bookmark"></i> {{ game.category }}</p>
{% for tag in game.tags.all %} {% for tag in game.tags.all %}
<p><i class="fa fa-fw fa-tag" aria-hidden="true"></i> {{ tag }}</p> <p><i class="fa fa-fw fa-tag" aria-hidden="true"></i> {{ tag }}</p>
{% endfor %} {% endfor %}
{% if game.game_designer %} {% if game.game_designer %}
<p><i class="fa fa-fw fa-pencil" aria-hidden="true"></i> {{ game.game_designer }} <p><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> {{ game.game_designer }}</p>
{% endif %}
{% if game.illustrator %}
<p><i class="fa fa-fw fa-paint-brush" aria-hidden="true"></i> {{ game.illustrator }}</p>
{% endif %} {% endif %}
{% if game.editor %} {% if game.editor %}
<p><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> {{ game.editor }}</p> <p><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> {{ game.editor }}</p>

View file

@ -0,0 +1,8 @@
{% if page_obj.has_other_pages %}
<div id="pagination">
{% if page_obj.has_previous %}<a href="?q={{ query }}&amp;page={{ page_obj.previous_page_number }}">{% endif %}<i class="fa fa-arrow-left" aria-hidden="true"></i> Précédent{% if page_obj.has_previous %}</a>{% endif %}
| Page {{ page_obj.number }} sur {{ paginator.num_pages }} |
{% if page_obj.has_next %}<a href="?q={{ query }}&amp;page={{ page_obj.next_page_number }}">{% endif %}Suivant <i class="fa fa-arrow-right" aria-hidden="true"></i>{% if page_obj.has_next %}</a>{% endif %}
</div>
{% endif %}

View file

@ -0,0 +1,3 @@
<li>
<a href="{{ tag.get_absolute_url }}"><i class="fa fa-tag" aria-hidden="true"></i> <span class="title">{{ tag.name }}</span></a>
</li>

View file

@ -2,7 +2,7 @@
{% block "content" %} {% block "content" %}
<h1>Recherche</h1> <h1>Recherche</h1>
<form class="search" method="get" action="."> <form class="search" method="get">
{{ form.q }} {{ form.q }}
<button type="submit"><i class="fa fa-fw fa-search" aria-hidden="true"></i></button> <button type="submit"><i class="fa fa-fw fa-search" aria-hidden="true"></i></button>
</form> </form>
@ -15,25 +15,15 @@
{% if result.model_name == "game" %} {% if result.model_name == "game" %}
{% include "./partials/game_item.html" with game=result.object %} {% include "./partials/game_item.html" with game=result.object %}
{% elif result.model_name == "category" %} {% elif result.model_name == "category" %}
<li> {% include "./partials/category_item.html" with category=result.object %}
<a href="{{ result.object.get_absolute_url }}"><i class="fa fa-bookmark" aria-hidden="true"></i> {{ result.object.name }}</a>
</li>
{% elif result.model_name == "tag" %} {% elif result.model_name == "tag" %}
<li> {% include "./partials/tag_item.html" with tag=result.object %}
<a href="{{ result.object.get_absolute_url }}"><i class="fa fa-tag" aria-hidden="true"></i> {{ result.object.name }}</a>
</li>
{% endif %} {% endif %}
{% empty %} {% empty %}
<li>Aucun résultat trouvé</li> <li>Aucun résultat trouvé</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% if page_obj.has_previous or page_obj.has_next %} {% include "./partials/pagination.html" %}
<div>
{% if page_obj.has_previous %}<a href="?q={{ query }}&amp;page={{ page_obj.previous_page_number }}">{% endif %}&laquo; Précédent{% if page_obj.has_previous %}</a>{% endif %}
|
{% if page_obj.has_next %}<a href="?q={{ query }}&amp;page={{ page_obj.next_page_number }}">{% endif %}Suivant &raquo;{% if page_obj.has_next %}</a>{% endif %}
</div>
{% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -1,4 +1,5 @@
{{ object.title }} {{ object.title }}
{{ object.editor }}
{{ object.game_designer }} {{ object.game_designer }}
{{ object.illustrator }}
{{ object.editor }}
{{ object.description }} {{ object.description }}

View file

@ -6,7 +6,7 @@
Il y a {{ tag_list|length }} tag{{ tag_list|pluralize }} dans la ludothèque&nbsp;: Il y a {{ tag_list|length }} tag{{ tag_list|pluralize }} dans la ludothèque&nbsp;:
<ul> <ul>
{% for tag in tag_list %} {% for tag in tag_list %}
<li><a href="{{ tag.get_absolute_url }}"><i class="fa fa-tag" aria-hidden="true"></i> {{ tag.name }}</a></li> {% include "./partials/tag_item.html" %}
{% endfor %} {% endfor %}
</ul> </ul>
{% endblock %} {% endblock %}

View file

@ -7,6 +7,8 @@ from .views import (
TagView, TagView,
GameListView, GameListView,
GameView, GameView,
AddGameCommentView,
ModifyGameCommentView,
InventorySearchView, InventorySearchView,
) )
@ -20,5 +22,13 @@ urlpatterns = [
path("tag/<slug>/", TagView.as_view(), name="tag"), path("tag/<slug>/", TagView.as_view(), name="tag"),
path("game/", GameListView.as_view(), name="game_list"), path("game/", GameListView.as_view(), name="game_list"),
path("game/<slug>/", GameView.as_view(), name="game"), path("game/<slug>/", GameView.as_view(), name="game"),
path(
"game/<slug>/add_comment", AddGameCommentView.as_view(), name="add_game_comment"
),
path(
"game/<slug>/modify_comment/<int:comment_id>",
ModifyGameCommentView.as_view(),
name="modify_game_comment",
),
path("search/", InventorySearchView.as_view(), name="search"), path("search/", InventorySearchView.as_view(), name="search"),
] ]

View file

@ -1,8 +1,20 @@
from django.views.generic import TemplateView, ListView, DetailView from django.views.generic import (
TemplateView,
ListView,
DetailView,
RedirectView,
)
from django.views.generic.detail import SingleObjectMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse
from django.shortcuts import redirect, get_object_or_404
from django.contrib import messages
from django.http import Http404
from django.core.exceptions import PermissionDenied
from haystack.generic_views import SearchView from haystack.generic_views import SearchView
from haystack.forms import SearchForm from haystack.forms import SearchForm
from haystack.query import SearchQuerySet from haystack.query import SearchQuerySet
from .models import Category, Tag, Game from .models import Category, Tag, Game, GameComment
class InventoryView(TemplateView): class InventoryView(TemplateView):
@ -45,3 +57,55 @@ class InventorySearchView(SearchView):
def get_queryset(self): def get_queryset(self):
return SearchQuerySet().models(Category, Tag, Game) return SearchQuerySet().models(Category, Tag, Game)
class AddGameCommentView(LoginRequiredMixin, SingleObjectMixin, RedirectView):
model = Game
permanent = False
def get_redirect_url(self, *args, **kwargs):
req_dict = self.request.POST
if "comment_text" in req_dict:
comment_text = req_dict["comment_text"]
game = self.get_object()
game.comments.create(author=self.request.user, text=comment_text)
messages.success(self.request, "Commentaire ajouté")
else:
messages.error(
self.request, "Pas de texte pour le commentaire dans la requête"
)
return reverse("inventory:game", kwargs=kwargs)
class ModifyGameCommentView(LoginRequiredMixin, SingleObjectMixin, TemplateView):
model = Game
template_name = "inventory/game.html"
def dispatch(self, *args, **kwargs):
comment_id = kwargs["comment_id"]
self.object = self.get_object()
self.comment = get_object_or_404(GameComment, id=comment_id)
if self.comment.game != self.object:
raise Http404()
if self.comment.author != self.request.user:
raise PermissionDenied()
return super().dispatch(*args, **kwargs)
def post(self, *args, **kwargs):
req_dict = self.request.POST
if "comment_text" in req_dict:
self.comment.text = req_dict["comment_text"]
self.comment.save()
messages.success(self.request, "Commentaire modifié")
else:
messages.error(
self.request, "Pas de texte pour le commentaire dans la requête"
)
return redirect("inventory:game", slug=kwargs["slug"])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["edited_comment"] = self.comment
return context

View file

@ -26,7 +26,7 @@ form {
color: $help_text_color; color: $help_text_color;
} }
input { input, textarea {
display: block; display: block;
width: 100%; width: 100%;
font: inherit; font: inherit;
@ -36,11 +36,13 @@ input {
input[type="text"], input[type="text"],
input[type="email"], input[type="email"],
input[type="password"], { input[type="password"],
input[type="search"],
textarea {
background-color: white; background-color: white;
border: solid 1px $help_text_color; border: solid 1px $help_text_color;
padding: 5px 10px; padding: 5px 10px;
border-radius: 3px; border-radius: 5px;
box-shadow: none; box-shadow: none;
&:optional { &:optional {
@ -56,6 +58,13 @@ input[type="password"], {
} }
} }
textarea {
font-family: $font_family;
font-size: 1em;
resize: vertical;
min-height: 120px;
}
input[type="checkbox"], input[type="checkbox"],
input[type="radio"] { input[type="radio"] {
width: auto; width: auto;
@ -99,9 +108,26 @@ select {
form.search { form.search {
flex-direction: row; flex-direction: row;
gap: 10px;
input[type="search"] {
border-radius: 5px 0 0 5px;
border-right: 0;
}
button { button {
margin: 0; margin: 0;
border-radius: 0 10px 10px 0;
}
}
form.comment {
font-size: 0.7em;
textarea {
border-radius: 10px 10px 0 0;
border-bottom: none;
margin: 0;
}
button {
border-radius: 0 0 10px 10px;
margin: 0;
} }
} }

View file

@ -14,7 +14,7 @@ header {
margin: 0; margin: 0;
padding: 0 20px; padding: 0 20px;
color: $header_text_color; color: $header_text_color;
font-size: $font_size; font-size: 1em;
} }
nav { nav {
@ -31,12 +31,11 @@ header {
border-radius: 0; border-radius: 0;
color: $header_text_color; color: $header_text_color;
font-size: $nav_font_size;
text-decoration: none; text-decoration: none;
&:hover { &:hover {
background-color: darken($header_bg_color, 10%); background-color: darken($header_bg_color, 10%);
color: $page_link_hover_color; color: $page_link_color;
} }
&.current { &.current {
background-color: $header_border_color; background-color: $header_border_color;
@ -44,11 +43,11 @@ header {
&:focus { &:focus {
background-color: darken($header_bg_color, 10%); background-color: darken($header_bg_color, 10%);
box-shadow: none; box-shadow: none;
color: $page_link_hover_color; color: $page_link_color;
} }
} }
.username { .username {
font-size: 12pt; font-size: 0.7em;
} }
} }

View file

@ -3,7 +3,6 @@
padding: 10px; padding: 10px;
border: 1px solid $border_color; border: 1px solid $border_color;
background-color: $bg_color; background-color: $bg_color;
color: $page_text_color;
} }
@mixin error_box { @mixin error_box {
@include box($error_box_color, $error_box_border_color); @include box($error_box_color, $error_box_border_color);
@ -23,6 +22,7 @@
text-decoration: none; text-decoration: none;
text-align: center; text-align: center;
font-size: 100%; font-size: 100%;
color: $page_text_color;
@include box(lighten($header_border_color, 40%), $header_border_color); @include box(lighten($header_border_color, 40%), $header_border_color);

View file

@ -1,7 +1,6 @@
$page_bg_color: #f6fbfd; $page_bg_color: #f6fbfd;
$page_text_color: #250f2d; $page_text_color: #250f2d;
$page_link_color: #2b153f; $page_link_color: #1e464c;
$page_link_hover_color: #180c23;
$page_width: 800px; $page_width: 800px;
$small_page_width: 500px; $small_page_width: 500px;
@ -16,20 +15,18 @@ $header_bg_color: #6bb8c4;
$header_text_color: #f6fbfd; $header_text_color: #f6fbfd;
$header_infos_font_family: "Kalam"; $header_infos_font_family: "Kalam";
$header_border_color: #51808c; $header_border_color: #51808c;
$nav_font_size: 18pt;
$footer_bg_color: $header_bg_color; $footer_bg_color: $header_bg_color;
$footer_font_size: 12pt;
$indexbar_bg_color_1: rgba($header_bg_color, 0.75); $indexbar_bg_color_1: rgba($header_bg_color, 0.75);
$indexbar_bg_color_2: rgba($header_bg_color, 0.6); $indexbar_bg_color_2: rgba($header_bg_color, 0.6);
$indexbar_text_color: darken($page_link_color, 15); $indexbar_text_color: darken($page_link_color, 15);
$info_box_color: #c9c8ff; $info_box_color: #e0dfff;
$info_box_border_color: darken($info_box_color, 20%); $info_box_border_color: darken($info_box_color, 20%);
$error_box_color: #ffcddd; $error_box_color: #ffdfe9;
$error_box_border_color: darken($error_box_color, 20%); $error_box_border_color: darken($error_box_color, 20%);
$success_box_color: #a4ffc4; $success_box_color: #ddffd8;
$success_box_border_color: darken($success_box_color, 40%); $success_box_border_color: darken($success_box_color, 20%);
$warning_box_color: #ffd45d; $warning_box_color: #ffedbb;
$warning_box_border_color: darken($warning_box_color, 20%); $warning_box_border_color: darken($warning_box_color, 20%);

View file

@ -50,7 +50,7 @@ main {
footer { footer {
background-color: $footer_bg_color; background-color: $footer_bg_color;
font-size: $footer_font_size; font-size: 0.7em;
text-align: center; text-align: center;
padding: 10px; padding: 10px;
} }
@ -69,13 +69,23 @@ h1 {
font-weight: bold; font-weight: bold;
} }
h2 {
font-size: 1.25em;
font-weight: bold;
}
h3 {
font-size: 1em;
font-weight: bold;
}
a { a {
text-decoration: underline; text-decoration: underline lighten($header_border_color, 40%);
color: $page_link_color; color: $page_link_color;
border-radius: 3px; border-radius: 3px;
&:hover { &:hover {
color: $page_link_hover_color; text-decoration-color: $page_link_color;
} }
} }
@ -106,7 +116,7 @@ hr {
button, .btn_row a { button, .btn_row a {
@include button; @include button;
margin: 10px 5px; margin: 10px 0;
p { p {
margin: 0; margin: 0;
@ -277,37 +287,62 @@ iframe {
} }
ul { ul {
padding: 0 20px; padding: 0;
list-style-type: none; list-style-type: none;
}
li>a { li>a {
display: block; display: block;
text-decoration: none; padding: 15px;
padding: 15px; margin: 10px 5px;
margin: 10px 5px; border-radius: 10px;
border-radius: 10px; border: 1px solid transparent;
border: 1px solid transparent; text-decoration: none;
&:hover { .title {
border-color: $header_border_color; text-decoration: underline lighten($header_border_color, 40%);
background-color: lighten($header_border_color, 70%); }
}
&:focus {
border-color: $header_border_color;
background-color: lighten($header_border_color, 70%);
box-shadow: 0 0 1.5px 1px $header_bg_color;
}
.details { .details {
font-size: 0.7em; font-size: 0.7em;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
margin: 5px; margin: 5px;
gap: 5px 20px; gap: 5px 20px;
p { p {
margin: 0; margin: 0;
}
} }
} }
&:hover {
border-color: $header_border_color;
background-color: white;
.title {
text-decoration-color: currentColor;
}
}
&:focus {
border-color: $header_border_color;
background-color: white;
box-shadow: 0 0 1.5px 1px $header_bg_color;
}
}
li.comment {
@include box(white, $indexbar_bg_color_1);
margin: 1em 0;
font-size: 0.7em;
.author {
font-weight: bold;
}
.date {
font-size: 0.7em;
}
p {
margin: 0.5em;
}
} }

View file

@ -12,7 +12,7 @@ header {
margin: 0; margin: 0;
padding: 0 20px; padding: 0 20px;
color: #f6fbfd; color: #f6fbfd;
font-size: 16pt; } font-size: 1em; }
header nav { header nav {
display: flex; display: flex;
justify-content: left; justify-content: left;
@ -24,19 +24,18 @@ header {
padding: 10px 20px; padding: 10px 20px;
border-radius: 0; border-radius: 0;
color: #f6fbfd; color: #f6fbfd;
font-size: 18pt;
text-decoration: none; } text-decoration: none; }
header a:hover { header a:hover {
background-color: #48a6b4; background-color: #48a6b4;
color: #180c23; } color: #1e464c; }
header a.current { header a.current {
background-color: #51808c; } background-color: #51808c; }
header a:focus { header a:focus {
background-color: #48a6b4; background-color: #48a6b4;
box-shadow: none; box-shadow: none;
color: #180c23; } color: #1e464c; }
header .username { header .username {
font-size: 12pt; } font-size: 0.7em; }
form { form {
display: flex; display: flex;
@ -50,9 +49,8 @@ form {
form .errorlist li { form .errorlist li {
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
border: 1px solid #ff6798; border: 1px solid #ff79a3;
background-color: #ffcddd; background-color: #ffdfe9;
color: #250f2d;
margin-bottom: 10px; } margin-bottom: 10px; }
form p { form p {
margin: 5px 0; margin: 5px 0;
@ -62,7 +60,7 @@ form {
font-size: 0.7em; font-size: 0.7em;
color: rgba(37, 15, 45, 0.65); } color: rgba(37, 15, 45, 0.65); }
input { input, textarea {
display: block; display: block;
width: 100%; width: 100%;
font: inherit; font: inherit;
@ -71,26 +69,40 @@ input {
input[type="text"], input[type="text"],
input[type="email"], input[type="email"],
input[type="password"] { input[type="password"],
input[type="search"],
textarea {
background-color: white; background-color: white;
border: solid 1px rgba(37, 15, 45, 0.65); border: solid 1px rgba(37, 15, 45, 0.65);
padding: 5px 10px; padding: 5px 10px;
border-radius: 3px; border-radius: 5px;
box-shadow: none; } box-shadow: none; }
input[type="text"]:optional, input[type="text"]:optional,
input[type="email"]:optional, input[type="email"]:optional,
input[type="password"]:optional { input[type="password"]:optional,
input[type="search"]:optional,
textarea:optional {
border-color: rgba(37, 15, 45, 0.4); } border-color: rgba(37, 15, 45, 0.4); }
input[type="text"]:focus, input[type="text"]:focus,
input[type="email"]:focus, input[type="email"]:focus,
input[type="password"]:focus { input[type="password"]:focus,
input[type="search"]:focus,
textarea:focus {
border-color: #6bb8c4; border-color: #6bb8c4;
box-shadow: 0 0 1.5px 1px #6bb8c4; } box-shadow: 0 0 1.5px 1px #6bb8c4; }
input[type="text"]:-moz-ui-invalid, input[type="text"]:-moz-ui-invalid,
input[type="email"]:-moz-ui-invalid, input[type="email"]:-moz-ui-invalid,
input[type="password"]:-moz-ui-invalid { input[type="password"]:-moz-ui-invalid,
border-color: #ff6798; input[type="search"]:-moz-ui-invalid,
box-shadow: 0 0 1.5px 1px #ff6798; } textarea:-moz-ui-invalid {
border-color: #ff79a3;
box-shadow: 0 0 1.5px 1px #ff79a3; }
textarea {
font-family: "Open Sans";
font-size: 1em;
resize: vertical;
min-height: 120px; }
input[type="checkbox"], input[type="checkbox"],
input[type="radio"] { input[type="radio"] {
@ -102,11 +114,11 @@ input[type="submit"] {
text-decoration: none; text-decoration: none;
text-align: center; text-align: center;
font-size: 100%; font-size: 100%;
color: #250f2d;
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
border: 1px solid #51808c; border: 1px solid #51808c;
background-color: #c9dbe0; background-color: #c9dbe0; }
color: #250f2d; }
input[type="submit"]:hover { input[type="submit"]:hover {
background-color: #a9c6cd; } background-color: #a9c6cd; }
input[type="submit"]:focus { input[type="submit"]:focus {
@ -120,11 +132,11 @@ select {
text-decoration: none; text-decoration: none;
text-align: center; text-align: center;
font-size: 100%; font-size: 100%;
color: #250f2d;
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
border: 1px solid #51808c; border: 1px solid #51808c;
background-color: #c9dbe0; background-color: #c9dbe0;
color: #250f2d;
width: 100%; width: 100%;
font-size: 0.9em; font-size: 0.9em;
margin: 0; margin: 0;
@ -149,12 +161,25 @@ select {
.error_field { .error_field {
border-radius: 10px; border-radius: 10px;
background-color: rgba(255, 205, 221, 0.4); } background-color: rgba(255, 223, 233, 0.4); }
form.search { form.search {
flex-direction: row; flex-direction: row; }
gap: 10px; } form.search input[type="search"] {
border-radius: 5px 0 0 5px;
border-right: 0; }
form.search button { form.search button {
margin: 0;
border-radius: 0 10px 10px 0; }
form.comment {
font-size: 0.7em; }
form.comment textarea {
border-radius: 10px 10px 0 0;
border-bottom: none;
margin: 0; }
form.comment button {
border-radius: 0 0 10px 10px;
margin: 0; } margin: 0; }
html { html {
@ -192,7 +217,7 @@ main {
footer { footer {
background-color: #6bb8c4; background-color: #6bb8c4;
font-size: 12pt; font-size: 0.7em;
text-align: center; text-align: center;
padding: 10px; } padding: 10px; }
@ -207,16 +232,24 @@ h1 {
font-size: 1.5em; font-size: 1.5em;
font-weight: bold; } font-weight: bold; }
h2 {
font-size: 1.25em;
font-weight: bold; }
h3 {
font-size: 1em;
font-weight: bold; }
a { a {
text-decoration: underline; text-decoration: underline #c9dbe0;
color: #2b153f; color: #1e464c;
border-radius: 3px; } border-radius: 3px; }
a:hover { a:hover {
color: #180c23; } text-decoration-color: #1e464c; }
:focus { :focus {
outline: none; outline: none;
box-shadow: 0 0 1.5px 1px #2b153f; } box-shadow: 0 0 1.5px 1px #1e464c; }
::-moz-focus-inner { ::-moz-focus-inner {
border: none; } border: none; }
@ -240,12 +273,12 @@ button, .btn_row a {
text-decoration: none; text-decoration: none;
text-align: center; text-align: center;
font-size: 100%; font-size: 100%;
color: #250f2d;
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
border: 1px solid #51808c; border: 1px solid #51808c;
background-color: #c9dbe0; background-color: #c9dbe0;
color: #250f2d; margin: 10px 0; }
margin: 10px 5px; }
button:hover, .btn_row a:hover { button:hover, .btn_row a:hover {
background-color: #a9c6cd; } background-color: #a9c6cd; }
button:focus, .btn_row a:focus { button:focus, .btn_row a:focus {
@ -265,30 +298,26 @@ button, .btn_row a {
.error { .error {
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
border: 1px solid #ff6798; border: 1px solid #ff79a3;
background-color: #ffcddd; background-color: #ffdfe9; }
color: #250f2d; }
.info { .info {
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
border: 1px solid #6562ff; border: 1px solid #7d79ff;
background-color: #c9c8ff; background-color: #e0dfff; }
color: #250f2d; }
.warning { .warning {
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
border: 1px solid #f6b500; border: 1px solid #ffd255;
background-color: #ffd45d; background-color: #ffedbb; }
color: #250f2d; }
.success { .success {
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
border: 1px solid #00d74c; border: 1px solid #84ff72;
background-color: #a4ffc4; background-color: #ddffd8; }
color: #250f2d; }
.tooltip { .tooltip {
position: relative; position: relative;
@ -408,27 +437,46 @@ iframe {
margin: 1ex; } margin: 1ex; }
ul { ul {
padding: 0 20px; padding: 0;
list-style-type: none; } list-style-type: none; }
ul li > a {
display: block; li > a {
text-decoration: none; display: block;
padding: 15px; padding: 15px;
margin: 10px 5px; margin: 10px 5px;
border-radius: 10px; border-radius: 10px;
border: 1px solid transparent; } border: 1px solid transparent;
ul li > a:hover { text-decoration: none; }
border-color: #51808c; li > a .title {
background-color: white; } text-decoration: underline #c9dbe0; }
ul li > a:focus { li > a .details {
border-color: #51808c; font-size: 0.7em;
background-color: white; display: flex;
box-shadow: 0 0 1.5px 1px #6bb8c4; } flex-wrap: wrap;
ul li > a .details { margin: 5px;
font-size: 0.7em; gap: 5px 20px; }
display: flex; li > a .details p {
flex-wrap: wrap; margin: 0; }
margin: 5px; li > a:hover {
gap: 5px 20px; } border-color: #51808c;
ul li > a .details p { background-color: white; }
margin: 0; } li > a:hover .title {
text-decoration-color: currentColor; }
li > a:focus {
border-color: #51808c;
background-color: white;
box-shadow: 0 0 1.5px 1px #6bb8c4; }
li.comment {
border-radius: 10px;
padding: 10px;
border: 1px solid rgba(107, 184, 196, 0.75);
background-color: white;
margin: 1em 0;
font-size: 0.7em; }
li.comment .author {
font-weight: bold; }
li.comment .date {
font-size: 0.7em; }
li.comment p {
margin: 0.5em; }

View file

@ -0,0 +1,25 @@
const MOBILE_NAV_PAGE_SIZE = 475; // px
const MOBILE_NAV_TOGGLE_TIME = 300; // msec
const TEXTAREA_MIN_SIZE = 120; // px
function mobile_nav_toggle() {
$("nav > a").slideToggle(MOBILE_NAV_TOGGLE_TIME);
}
$(window).resize(function () {
if(window.innerWidth > MOBILE_NAV_PAGE_SIZE) {
$("nav > a").show(0);
}
else {
$("nav > a").hide(0);
}
});
$('textarea').each(function () {
this.style.height = Math.max(this.scrollHeight, TEXTAREA_MIN_SIZE) + 'px';
this.style.resize = 'none';
this.style.overflowY = 'hidden';
}).on('input', function () {
this.style.height = 'auto';
this.style.height = Math.max(this.scrollHeight, TEXTAREA_MIN_SIZE) + 'px';
});

View file

@ -1,3 +1,5 @@
{% load static %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
<head> <head>
@ -17,7 +19,7 @@
</main> </main>
{% include "partials/footer.html" %} {% include "partials/footer.html" %}
{% include "partials/base_js.html" %} <script src="{% static "js/gestiojeux.js" %}"></script>
{% block "extra_foot" %}{% endblock %} {% block "extra_foot" %}{% endblock %}
</body> </body>
</html> </html>

View file

@ -1,14 +0,0 @@
<script>
function mobile_nav_toggle() {
$("nav > a").slideToggle(300);
}
$(window).resize(function () {
if(window.innerWidth > 475) {
$("nav > a").show(0);
}
else {
$("nav > a").hide(0);
}
});
</script>

View file

@ -4,5 +4,5 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GestioJeux</title> <title>GestioJeux</title>
<link type="text/css" rel="stylesheet" href="{% static "css/style.css" %}" /> <link type="text/css" rel="stylesheet" href="{% static "css/style.css" %}" />
<link rel="shortcut icon" type="image/png" href="{% static "img/favicon.png" %}"/> <link type="image/png" rel="shortcut icon" href="{% static "img/favicon.png" %}"/>
<script src="{% static "js/jquery-3.4.1.min.js" %}"></script> <script src="{% static "js/jquery-3.4.1.min.js" %}"></script>

View file

@ -13,8 +13,8 @@
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<div class="username">{{ request.user.username }}</div> <div class="username">{{ request.user.username }}</div>
<a class="login" href="{% url "gestiojeux_auth:logout" %}?next={{ request.get_full_path }}"><i class="fa fa-sign-out" aria-hidden="true"></i></a> <a class="login" href="{% url "gestiojeux_auth:logout" %}?next={{ request.get_full_path }}"><i class="fa fa-sign-out" aria-hidden="true"></i></a>
{% else %} <a class="login" href="{% url "gestiojeux_auth:login" %}?next={{ request.get_full_path }}">Connexion</a> {% else %}
<a class="login{% if url_name == "login" %} current{% endif %}" href="{% url "gestiojeux_auth:login" %}?next={{ request.get_full_path }}">Connexion</a>
{% endif %} {% endif %}
{# <a class="login" href="">Logout</a> #}
{% endwith %} {% endwith %}
</header> </header>