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 .models import Category, Tag, Game
from .models import Category, Tag, Game, GameComment
admin.site.register(Category)
admin.site.register(Tag)
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.urls import reverse
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from autoslug import AutoSlugField
@ -8,6 +10,7 @@ class Category(models.Model):
slug = AutoSlugField(populate_from="name", unique=True)
class Meta:
ordering = ["name"]
verbose_name = "catégorie"
def __str__(self):
@ -21,6 +24,9 @@ class Tag(models.Model):
name = models.CharField(max_length=256, verbose_name="nom")
slug = AutoSlugField(populate_from="name", unique=True)
class Meta:
ordering = ["name"]
def __str__(self):
return self.name
@ -31,27 +37,83 @@ class Tag(models.Model):
class Game(models.Model):
title = models.CharField(verbose_name="titre", max_length=256)
slug = AutoSlugField(populate_from="title", unique=True)
player_range = models.CharField(
max_length=256, verbose_name="nombre de joueur·se·s"
nb_player_min = models.PositiveSmallIntegerField(
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(
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")
category = models.ForeignKey(
Category, on_delete=models.RESTRICT, verbose_name="catégorie"
)
tags = models.ManyToManyField(Tag, blank=True, verbose_name="tags")
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:
ordering = ["title"]
verbose_name = "jeu"
verbose_name_plural = "jeux"
def __str__(self):
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):
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 :
<ul>
{% 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 %}
</ul>
{% endblock %}

View file

@ -12,8 +12,8 @@
<div id="details">
<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.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-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)" }}
<hr/>
<p><i class="fa fa-fw fa-tags" aria-hidden="true"></i>
{% for tag in game.tags.all %}
@ -23,10 +23,56 @@
{% endfor %}
</p>
<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>
</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 %}

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>
<a href="{{ game.get_absolute_url }}">
{{game.title}}
<span class="title">{{ game.title }}</span>
<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-bookmark"></i> {{ game.category }}</p>
{% for tag in game.tags.all %}
<p><i class="fa fa-fw fa-tag" aria-hidden="true"></i> {{ tag }}</p>
{% endfor %}
{% 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 %}
{% if game.editor %}
<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" %}
<h1>Recherche</h1>
<form class="search" method="get" action=".">
<form class="search" method="get">
{{ form.q }}
<button type="submit"><i class="fa fa-fw fa-search" aria-hidden="true"></i></button>
</form>
@ -15,25 +15,15 @@
{% if result.model_name == "game" %}
{% include "./partials/game_item.html" with game=result.object %}
{% elif result.model_name == "category" %}
<li>
<a href="{{ result.object.get_absolute_url }}"><i class="fa fa-bookmark" aria-hidden="true"></i> {{ result.object.name }}</a>
</li>
{% include "./partials/category_item.html" with category=result.object %}
{% elif result.model_name == "tag" %}
<li>
<a href="{{ result.object.get_absolute_url }}"><i class="fa fa-tag" aria-hidden="true"></i> {{ result.object.name }}</a>
</li>
{% include "./partials/tag_item.html" with tag=result.object %}
{% endif %}
{% empty %}
<li>Aucun résultat trouvé</li>
{% endfor %}
</ul>
{% if page_obj.has_previous or page_obj.has_next %}
<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 %}
{% include "./partials/pagination.html" %}
{% endif %}
{% endblock %}

View file

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

View file

@ -6,7 +6,7 @@
Il y a {{ tag_list|length }} tag{{ tag_list|pluralize }} dans la ludothèque&nbsp;:
<ul>
{% 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 %}
</ul>
{% endblock %}

View file

@ -7,6 +7,8 @@ from .views import (
TagView,
GameListView,
GameView,
AddGameCommentView,
ModifyGameCommentView,
InventorySearchView,
)
@ -20,5 +22,13 @@ urlpatterns = [
path("tag/<slug>/", TagView.as_view(), name="tag"),
path("game/", GameListView.as_view(), name="game_list"),
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"),
]

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.forms import SearchForm
from haystack.query import SearchQuerySet
from .models import Category, Tag, Game
from .models import Category, Tag, Game, GameComment
class InventoryView(TemplateView):
@ -45,3 +57,55 @@ class InventorySearchView(SearchView):
def get_queryset(self):
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;
}
input {
input, textarea {
display: block;
width: 100%;
font: inherit;
@ -36,11 +36,13 @@ input {
input[type="text"],
input[type="email"],
input[type="password"], {
input[type="password"],
input[type="search"],
textarea {
background-color: white;
border: solid 1px $help_text_color;
padding: 5px 10px;
border-radius: 3px;
border-radius: 5px;
box-shadow: none;
&: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="radio"] {
width: auto;
@ -99,9 +108,26 @@ select {
form.search {
flex-direction: row;
gap: 10px;
input[type="search"] {
border-radius: 5px 0 0 5px;
border-right: 0;
}
button {
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;
padding: 0 20px;
color: $header_text_color;
font-size: $font_size;
font-size: 1em;
}
nav {
@ -31,12 +31,11 @@ header {
border-radius: 0;
color: $header_text_color;
font-size: $nav_font_size;
text-decoration: none;
&:hover {
background-color: darken($header_bg_color, 10%);
color: $page_link_hover_color;
color: $page_link_color;
}
&.current {
background-color: $header_border_color;
@ -44,11 +43,11 @@ header {
&:focus {
background-color: darken($header_bg_color, 10%);
box-shadow: none;
color: $page_link_hover_color;
color: $page_link_color;
}
}
.username {
font-size: 12pt;
font-size: 0.7em;
}
}

View file

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

View file

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

View file

@ -50,7 +50,7 @@ main {
footer {
background-color: $footer_bg_color;
font-size: $footer_font_size;
font-size: 0.7em;
text-align: center;
padding: 10px;
}
@ -69,13 +69,23 @@ h1 {
font-weight: bold;
}
h2 {
font-size: 1.25em;
font-weight: bold;
}
h3 {
font-size: 1em;
font-weight: bold;
}
a {
text-decoration: underline;
text-decoration: underline lighten($header_border_color, 40%);
color: $page_link_color;
border-radius: 3px;
&:hover {
color: $page_link_hover_color;
text-decoration-color: $page_link_color;
}
}
@ -106,7 +116,7 @@ hr {
button, .btn_row a {
@include button;
margin: 10px 5px;
margin: 10px 0;
p {
margin: 0;
@ -277,25 +287,20 @@ iframe {
}
ul {
padding: 0 20px;
padding: 0;
list-style-type: none;
}
li>a {
display: block;
text-decoration: none;
padding: 15px;
margin: 10px 5px;
border-radius: 10px;
border: 1px solid transparent;
text-decoration: none;
&:hover {
border-color: $header_border_color;
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;
.title {
text-decoration: underline lighten($header_border_color, 40%);
}
.details {
@ -309,5 +314,35 @@ ul {
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;
padding: 0 20px;
color: #f6fbfd;
font-size: 16pt; }
font-size: 1em; }
header nav {
display: flex;
justify-content: left;
@ -24,19 +24,18 @@ header {
padding: 10px 20px;
border-radius: 0;
color: #f6fbfd;
font-size: 18pt;
text-decoration: none; }
header a:hover {
background-color: #48a6b4;
color: #180c23; }
color: #1e464c; }
header a.current {
background-color: #51808c; }
header a:focus {
background-color: #48a6b4;
box-shadow: none;
color: #180c23; }
color: #1e464c; }
header .username {
font-size: 12pt; }
font-size: 0.7em; }
form {
display: flex;
@ -50,9 +49,8 @@ form {
form .errorlist li {
border-radius: 10px;
padding: 10px;
border: 1px solid #ff6798;
background-color: #ffcddd;
color: #250f2d;
border: 1px solid #ff79a3;
background-color: #ffdfe9;
margin-bottom: 10px; }
form p {
margin: 5px 0;
@ -62,7 +60,7 @@ form {
font-size: 0.7em;
color: rgba(37, 15, 45, 0.65); }
input {
input, textarea {
display: block;
width: 100%;
font: inherit;
@ -71,26 +69,40 @@ input {
input[type="text"],
input[type="email"],
input[type="password"] {
input[type="password"],
input[type="search"],
textarea {
background-color: white;
border: solid 1px rgba(37, 15, 45, 0.65);
padding: 5px 10px;
border-radius: 3px;
border-radius: 5px;
box-shadow: none; }
input[type="text"]: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); }
input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus {
input[type="password"]:focus,
input[type="search"]:focus,
textarea:focus {
border-color: #6bb8c4;
box-shadow: 0 0 1.5px 1px #6bb8c4; }
input[type="text"]:-moz-ui-invalid,
input[type="email"]:-moz-ui-invalid,
input[type="password"]:-moz-ui-invalid {
border-color: #ff6798;
box-shadow: 0 0 1.5px 1px #ff6798; }
input[type="password"]:-moz-ui-invalid,
input[type="search"]:-moz-ui-invalid,
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="radio"] {
@ -102,11 +114,11 @@ input[type="submit"] {
text-decoration: none;
text-align: center;
font-size: 100%;
color: #250f2d;
border-radius: 10px;
padding: 10px;
border: 1px solid #51808c;
background-color: #c9dbe0;
color: #250f2d; }
background-color: #c9dbe0; }
input[type="submit"]:hover {
background-color: #a9c6cd; }
input[type="submit"]:focus {
@ -120,11 +132,11 @@ select {
text-decoration: none;
text-align: center;
font-size: 100%;
color: #250f2d;
border-radius: 10px;
padding: 10px;
border: 1px solid #51808c;
background-color: #c9dbe0;
color: #250f2d;
width: 100%;
font-size: 0.9em;
margin: 0;
@ -149,12 +161,25 @@ select {
.error_field {
border-radius: 10px;
background-color: rgba(255, 205, 221, 0.4); }
background-color: rgba(255, 223, 233, 0.4); }
form.search {
flex-direction: row;
gap: 10px; }
flex-direction: row; }
form.search input[type="search"] {
border-radius: 5px 0 0 5px;
border-right: 0; }
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; }
html {
@ -192,7 +217,7 @@ main {
footer {
background-color: #6bb8c4;
font-size: 12pt;
font-size: 0.7em;
text-align: center;
padding: 10px; }
@ -207,16 +232,24 @@ h1 {
font-size: 1.5em;
font-weight: bold; }
h2 {
font-size: 1.25em;
font-weight: bold; }
h3 {
font-size: 1em;
font-weight: bold; }
a {
text-decoration: underline;
color: #2b153f;
text-decoration: underline #c9dbe0;
color: #1e464c;
border-radius: 3px; }
a:hover {
color: #180c23; }
text-decoration-color: #1e464c; }
:focus {
outline: none;
box-shadow: 0 0 1.5px 1px #2b153f; }
box-shadow: 0 0 1.5px 1px #1e464c; }
::-moz-focus-inner {
border: none; }
@ -240,12 +273,12 @@ button, .btn_row a {
text-decoration: none;
text-align: center;
font-size: 100%;
color: #250f2d;
border-radius: 10px;
padding: 10px;
border: 1px solid #51808c;
background-color: #c9dbe0;
color: #250f2d;
margin: 10px 5px; }
margin: 10px 0; }
button:hover, .btn_row a:hover {
background-color: #a9c6cd; }
button:focus, .btn_row a:focus {
@ -265,30 +298,26 @@ button, .btn_row a {
.error {
border-radius: 10px;
padding: 10px;
border: 1px solid #ff6798;
background-color: #ffcddd;
color: #250f2d; }
border: 1px solid #ff79a3;
background-color: #ffdfe9; }
.info {
border-radius: 10px;
padding: 10px;
border: 1px solid #6562ff;
background-color: #c9c8ff;
color: #250f2d; }
border: 1px solid #7d79ff;
background-color: #e0dfff; }
.warning {
border-radius: 10px;
padding: 10px;
border: 1px solid #f6b500;
background-color: #ffd45d;
color: #250f2d; }
border: 1px solid #ffd255;
background-color: #ffedbb; }
.success {
border-radius: 10px;
padding: 10px;
border: 1px solid #00d74c;
background-color: #a4ffc4;
color: #250f2d; }
border: 1px solid #84ff72;
background-color: #ddffd8; }
.tooltip {
position: relative;
@ -408,27 +437,46 @@ iframe {
margin: 1ex; }
ul {
padding: 0 20px;
padding: 0;
list-style-type: none; }
ul li > a {
li > a {
display: block;
text-decoration: none;
padding: 15px;
margin: 10px 5px;
border-radius: 10px;
border: 1px solid transparent; }
ul li > a:hover {
border-color: #51808c;
background-color: white; }
ul li > a:focus {
border-color: #51808c;
background-color: white;
box-shadow: 0 0 1.5px 1px #6bb8c4; }
ul li > a .details {
border: 1px solid transparent;
text-decoration: none; }
li > a .title {
text-decoration: underline #c9dbe0; }
li > a .details {
font-size: 0.7em;
display: flex;
flex-wrap: wrap;
margin: 5px;
gap: 5px 20px; }
ul li > a .details p {
li > a .details p {
margin: 0; }
li > a:hover {
border-color: #51808c;
background-color: white; }
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>
<html lang="fr">
<head>
@ -17,7 +19,7 @@
</main>
{% include "partials/footer.html" %}
{% include "partials/base_js.html" %}
<script src="{% static "js/gestiojeux.js" %}"></script>
{% block "extra_foot" %}{% endblock %}
</body>
</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">
<title>GestioJeux</title>
<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>

View file

@ -13,8 +13,8 @@
{% if request.user.is_authenticated %}
<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>
{% 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 %}
{# <a class="login" href="">Logout</a> #}
{% endwith %}
</header>