Add public name customization and small style adjustments
This commit is contained in:
parent
f401c66f97
commit
0d838cafee
20 changed files with 176 additions and 162 deletions
|
@ -81,6 +81,8 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
||||
]
|
||||
|
||||
LOGIN_URL = "gestiojeux_auth:login"
|
||||
|
||||
# Use markdown extensions
|
||||
MARKDOWNX_MARKDOWN_EXTENSIONS = [
|
||||
"markdown.extensions.extra",
|
||||
|
|
24
gestiojeux_auth/forms.py
Normal file
24
gestiojeux_auth/forms.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from django.forms import ModelForm, ValidationError
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class AccountSettingsForm(ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["first_name"]
|
||||
labels = {"first_name": "Nom ou pseudo"}
|
||||
help_texts = {
|
||||
"first_name": "Ce nom sera utilisé pour toutes vos interactions publiques sur GestioJeux. Si laissé vide, votre login sera utilisé à la place."
|
||||
}
|
||||
|
||||
def clean_first_name(self):
|
||||
""" Check there is no conflict that could lead to imprersonation """
|
||||
public_name = self.cleaned_data["first_name"]
|
||||
public_name = public_name.strip()
|
||||
if public_name == self.instance.first_name or public_name == "":
|
||||
return public_name
|
||||
if User.objects.filter(first_name=public_name).count() > 0:
|
||||
raise ValidationError("Un autre compte utilise déjà ce nom ou ce pseudo.")
|
||||
if User.objects.filter(username=public_name).count() > 0:
|
||||
raise ValidationError("Ce nom est déjà le login de quelqu'un.")
|
||||
return public_name
|
20
gestiojeux_auth/templates/registration/account_settings.html
Normal file
20
gestiojeux_auth/templates/registration/account_settings.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends "small_page.html" %}
|
||||
|
||||
{% block "content" %}
|
||||
<h1>Paramètres du compte</h1>
|
||||
|
||||
<p>Vous êtes connecté en tant que <tt>{{ request.user.username }}</tt></p>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Enregistrer</button>
|
||||
</form>
|
||||
|
||||
{% if request.user.password and request.user.has_usable_password %}
|
||||
<hr/>
|
||||
|
||||
<a href="{% url "gestiojeux_auth:change_password" %}">Changer mon mot de passe</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
12
gestiojeux_auth/templates/registration/change_password.html
Normal file
12
gestiojeux_auth/templates/registration/change_password.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends "small_page.html" %}
|
||||
|
||||
{% block "content" %}
|
||||
<h1>Changement de mot de passe</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Changer de mot de passe</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
{% block "content" %}
|
||||
<h1>Connexion par mot de passe</h1>
|
||||
|
||||
<form method="post" action="{% url "gestiojeux_auth:password_login" %}?next={{ next|urlencode }}">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Connexion</button>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.urls import include, path
|
||||
import django.contrib.auth.views as dj_auth_views
|
||||
from .views import LoginView, LogoutView
|
||||
from .views import LoginView, LogoutView, PasswordChangeView, AccountSettingsView
|
||||
import django_cas_ng.views
|
||||
|
||||
app_name = "gestiojeux_auth"
|
||||
|
@ -20,6 +20,8 @@ accounts_patterns = [
|
|||
path("login/", LoginView.as_view(), name="login"),
|
||||
path("logout/", LogoutView.as_view(), name="logout"),
|
||||
path("password_login/", dj_auth_views.LoginView.as_view(), name="password_login"),
|
||||
path("change_password/", PasswordChangeView.as_view(), name="change_password"),
|
||||
path("account_settings/", AccountSettingsView.as_view(), name="account_settings"),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
from django.views.generic import TemplateView, RedirectView
|
||||
from django.views.generic.edit import UpdateView
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.dispatch import receiver
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
from django.contrib.auth import user_logged_in, user_logged_out, user_login_failed
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.views import PasswordChangeView
|
||||
from django.contrib import messages
|
||||
|
||||
from urllib.parse import quote as urlquote
|
||||
|
||||
from .forms import AccountSettingsForm
|
||||
|
||||
|
||||
class LoginView(TemplateView):
|
||||
template_name = "registration/login_switch.html"
|
||||
|
@ -81,3 +86,26 @@ def on_logout(request, **kwargs):
|
|||
@receiver(user_login_failed)
|
||||
def on_login_failed(request, **kwargs):
|
||||
messages.error(request, "Connexion échouée.")
|
||||
|
||||
|
||||
class PasswordChangeView(PasswordChangeView):
|
||||
template_name = "registration/change_password.html"
|
||||
|
||||
def get_success_url(self):
|
||||
messages.info(self.request, "Mot de passe mis à jour")
|
||||
return reverse("gestiojeux_auth:account_settings")
|
||||
|
||||
|
||||
class AccountSettingsView(LoginRequiredMixin, UpdateView):
|
||||
template_name = "registration/account_settings.html"
|
||||
form_class = AccountSettingsForm
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.get_full_path()
|
||||
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, "Paramètres du compte mis à jour")
|
||||
return super().form_valid(form)
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
<h1><i class="fa fa-bookmark" aria-hidden="true"></i> {{ category.name }}</h1>
|
||||
|
||||
{% with game_list=category.game_set.all %}
|
||||
Il y a {{ game_list|length }} jeu{{ game_list|pluralize:"x" }} dans cette catégorie :
|
||||
<ul>
|
||||
{% for game in game_list %}
|
||||
<li>{% include "./partials/game_item.html" %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p>Il y a {{ game_list|length }} jeu{{ game_list|pluralize:"x" }} dans cette catégorie :</p>
|
||||
{% for game in game_list %}
|
||||
{% include "./partials/game_item.html" %}
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
{% block "content" %}
|
||||
<h1>Liste des catégories</h1>
|
||||
|
||||
Il y a {{ category_list|length }} catégorie{{ category_list|pluralize }} de jeux :
|
||||
<ul>
|
||||
{% for category in category_list %}
|
||||
<li>{% include "./partials/category_item.html" %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p>Il y a {{ category_list|length }} catégorie{{ category_list|pluralize }} de jeux :</p>
|
||||
{% for category in category_list %}
|
||||
{% include "./partials/category_item.html" %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -38,32 +38,34 @@
|
|||
{% 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">
|
||||
<form id="edited_comment" class="comment" method="post">
|
||||
<div class="meta">
|
||||
<span class="author">{{ comment.author }}</span>
|
||||
<span class="author">{% include "partials/user.html" with user=comment.author %}</span>
|
||||
<span class="date">posté le {{ comment.created_on|date }} à {{ comment.created_on|time }}</span>
|
||||
<a href="{% url "inventory:game" game.slug %}" title="Annuler la modification"><i class="fa fa-ban" aria-hidden="true"></i></a>
|
||||
</div>
|
||||
|
||||
{% 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>
|
||||
{% else %}
|
||||
<div class="comment">
|
||||
<div class="meta">
|
||||
<span class="author">{% include "partials/user.html" with user=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>
|
||||
<a href="{% url "inventory:modify_game_comment" game.slug comment.id %}#edited_comment" title="Éditer mon commentaire"><i class="fa fa-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ comment.text|linebreaks }}
|
||||
</li>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<li>(Aucun commentaire sur ce jeu)</li>
|
||||
(Aucun commentaire sur ce jeu)
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if not edited_comment %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<form class="comment" method="post" action="{% url "inventory:add_game_comment" game.slug %}">
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
{% block "content" %}
|
||||
<h1>Liste des jeux</h1>
|
||||
|
||||
Il y a {{ game_list|length }} jeu{{ game_list|pluralize:"x" }} en salle jeux :
|
||||
<ul>
|
||||
{% for game in game_list %}
|
||||
<li>{% include "./partials/game_item.html" %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p>Il y a {{ game_list|length }} jeu{{ game_list|pluralize:"x" }} en salle jeux :</p>
|
||||
{% for game in game_list %}
|
||||
{% include "./partials/game_item.html" %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
<hr/>
|
||||
|
||||
<div class="btn_row">
|
||||
|
||||
<a href="{% url "inventory:category_list" %}">
|
||||
Liste des catégories
|
||||
<p class="helptext">
|
||||
|
|
|
@ -10,9 +10,7 @@
|
|||
{% if query %}
|
||||
<hr/>
|
||||
|
||||
<ul>
|
||||
{% for result in page_obj.object_list %}
|
||||
<li>
|
||||
{% if result.model_name == "game" %}
|
||||
{% include "./partials/game_item.html" with game=result.object %}
|
||||
{% elif result.model_name == "category" %}
|
||||
|
@ -20,11 +18,9 @@
|
|||
{% elif result.model_name == "tag" %}
|
||||
{% include "./partials/tag_item.html" with tag=result.object %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% empty %}
|
||||
<li>Aucun résultat trouvé</li>
|
||||
Aucun résultat trouvé
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% include "./partials/pagination.html" %}
|
||||
{% endif %}
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
<h1><i class="fa fa-tag" aria-hidden="true"></i> {{ tag.name }}</h1>
|
||||
|
||||
{% with game_list=tag.game_set.all %}
|
||||
Il y a {{ game_list|length }} jeu{{ game_list|pluralize:"x" }} marqué{{ game_list|pluralize }} avec ce tag :
|
||||
<ul>
|
||||
{% for game in game_list %}
|
||||
<li>{% include "./partials/game_item.html" %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p>Il y a {{ game_list|length }} jeu{{ game_list|pluralize:"x" }} marqué{{ game_list|pluralize }} avec ce tag :</p>
|
||||
{% for game in game_list %}
|
||||
{% include "./partials/game_item.html" %}
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
{% block "content" %}
|
||||
<h1>Liste des tags</h1>
|
||||
|
||||
Il y a {{ tag_list|length }} tag{{ tag_list|pluralize }} dans la ludothèque :
|
||||
<ul>
|
||||
{% for tag in tag_list %}
|
||||
<li>{% include "./partials/tag_item.html" %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p>Il y a {{ tag_list|length }} tag{{ tag_list|pluralize }} dans la ludothèque :</p>
|
||||
{% for tag in tag_list %}
|
||||
{% include "./partials/tag_item.html" %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -22,6 +22,7 @@ form {
|
|||
}
|
||||
|
||||
.helptext {
|
||||
display: block;
|
||||
font-size: 0.7em;
|
||||
color: $help_text_color;
|
||||
}
|
||||
|
@ -120,7 +121,9 @@ form.search {
|
|||
}
|
||||
|
||||
form.comment {
|
||||
font-size: 0.7em;
|
||||
padding: 0;
|
||||
border: none;
|
||||
|
||||
textarea {
|
||||
border-radius: 10px 10px 0 0;
|
||||
border-bottom: none;
|
||||
|
@ -130,4 +133,19 @@ form.comment {
|
|||
border-radius: 0 0 10px 10px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
form#edited_comment {
|
||||
.meta {
|
||||
padding: 10px;
|
||||
padding-bottom: 5px;
|
||||
border-radius: 10px 10px 0 0;
|
||||
border: 1px solid $indexbar_bg_color_1;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,15 +55,6 @@ footer {
|
|||
padding: 10px;
|
||||
}
|
||||
|
||||
.help_bubble {
|
||||
@media (min-width: 700px) {
|
||||
font-size: 0.7em;
|
||||
position: relative;
|
||||
bottom: 0.3ex;
|
||||
left: 0.2ex;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
|
@ -107,6 +98,11 @@ hr {
|
|||
margin: 30px 60px;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding: 0;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
.btn_row {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
@ -139,49 +135,6 @@ button, .btn_row a {
|
|||
.warning { @include warning_box; }
|
||||
.success { @include success_box; }
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
opacity: 0.75;
|
||||
border-radius: 3px;
|
||||
|
||||
.tooltiptext {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
background-color: black;
|
||||
color: rgba(white, 0.80);
|
||||
text-align: justify;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.8em;
|
||||
width: 250px;
|
||||
|
||||
@media (max-width: 400px) {
|
||||
width: 150px;
|
||||
position: absolute;
|
||||
left: -75px;
|
||||
}
|
||||
|
||||
/* Position the tooltip text - see examples below! */
|
||||
position: absolute;
|
||||
left: -75px;
|
||||
z-index: 1;
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding-left: 15px;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
opacity: 1;
|
||||
.tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.antispam {
|
||||
unicode-bidi: bidi-override;
|
||||
direction: rtl;
|
||||
|
@ -286,11 +239,6 @@ iframe {
|
|||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
a.inventory_item {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
|
@ -326,7 +274,7 @@ a.inventory_item {
|
|||
}
|
||||
}
|
||||
|
||||
li.comment {
|
||||
.comment {
|
||||
@include box(white, $indexbar_bg_color_1);
|
||||
margin: 1em 0;
|
||||
font-size: 0.7em;
|
||||
|
|
|
@ -57,6 +57,7 @@ form {
|
|||
width: 100%; }
|
||||
|
||||
.helptext {
|
||||
display: block;
|
||||
font-size: 0.7em;
|
||||
color: rgba(37, 15, 45, 0.65); }
|
||||
|
||||
|
@ -173,7 +174,8 @@ form.search {
|
|||
border-radius: 0 10px 10px 0; }
|
||||
|
||||
form.comment {
|
||||
font-size: 0.7em; }
|
||||
padding: 0;
|
||||
border: none; }
|
||||
form.comment textarea {
|
||||
border-radius: 10px 10px 0 0;
|
||||
border-bottom: none;
|
||||
|
@ -182,6 +184,16 @@ form.comment {
|
|||
border-radius: 0 0 10px 10px;
|
||||
margin: 0; }
|
||||
|
||||
form#edited_comment .meta {
|
||||
padding: 10px;
|
||||
padding-bottom: 5px;
|
||||
border-radius: 10px 10px 0 0;
|
||||
border: 1px solid rgba(107, 184, 196, 0.75);
|
||||
border-bottom: none; }
|
||||
|
||||
form#edited_comment textarea {
|
||||
border-radius: 0; }
|
||||
|
||||
html {
|
||||
box-sizing: border-box; }
|
||||
|
||||
|
@ -221,13 +233,6 @@ footer {
|
|||
text-align: center;
|
||||
padding: 10px; }
|
||||
|
||||
@media (min-width: 700px) {
|
||||
.help_bubble {
|
||||
font-size: 0.7em;
|
||||
position: relative;
|
||||
bottom: 0.3ex;
|
||||
left: 0.2ex; } }
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold; }
|
||||
|
@ -262,6 +267,10 @@ hr {
|
|||
border: 1px solid #c9dbe0;
|
||||
margin: 30px 60px; }
|
||||
|
||||
ul, ol {
|
||||
padding: 0;
|
||||
list-style-position: inside; }
|
||||
|
||||
.btn_row {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
@ -319,39 +328,6 @@ button, .btn_row a {
|
|||
border: 1px solid #84ff72;
|
||||
background-color: #ddffd8; }
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
opacity: 0.75;
|
||||
border-radius: 3px; }
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
background-color: black;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-align: justify;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.8em;
|
||||
width: 250px;
|
||||
/* Position the tooltip text - see examples below! */
|
||||
position: absolute;
|
||||
left: -75px;
|
||||
z-index: 1; }
|
||||
@media (max-width: 400px) {
|
||||
.tooltip .tooltiptext {
|
||||
width: 150px;
|
||||
position: absolute;
|
||||
left: -75px; } }
|
||||
.tooltip .tooltiptext ul {
|
||||
margin: 0;
|
||||
padding-left: 15px;
|
||||
color: inherit; }
|
||||
.tooltip:hover, .tooltip:focus {
|
||||
opacity: 1; }
|
||||
.tooltip:hover .tooltiptext, .tooltip:focus .tooltiptext {
|
||||
visibility: visible; }
|
||||
|
||||
.antispam {
|
||||
unicode-bidi: bidi-override;
|
||||
direction: rtl; }
|
||||
|
@ -436,10 +412,6 @@ iframe {
|
|||
#game_infos #details hr {
|
||||
margin: 1ex; }
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
list-style-type: none; }
|
||||
|
||||
a.inventory_item {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
|
@ -465,16 +437,16 @@ a.inventory_item {
|
|||
background-color: white;
|
||||
box-shadow: 0 0 1.5px 1px #6bb8c4; }
|
||||
|
||||
li.comment {
|
||||
.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 {
|
||||
.comment .author {
|
||||
font-weight: bold; }
|
||||
li.comment .date {
|
||||
.comment .date {
|
||||
font-size: 0.7em; }
|
||||
li.comment p {
|
||||
.comment p {
|
||||
margin: 0.5em; }
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
{% endif %}
|
||||
</nav>
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="username">{{ request.user.username }}</div>
|
||||
<a class="username" href="{% url "gestiojeux_auth:account_settings" %}">{% include "./user.html" with user=request.user %}</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{% if url_name == "login" %} current{% endif %}" href="{% url "gestiojeux_auth:login" %}?next={{ request.get_full_path }}">Connexion</a>
|
||||
|
|
1
mainsite/templates/partials/user.html
Normal file
1
mainsite/templates/partials/user.html
Normal file
|
@ -0,0 +1 @@
|
|||
{% if user.first_name %}{{ user.first_name }}{% else %}{{ user.username }}{% endif %}
|
Loading…
Reference in a new issue