Authenticate by email with a custom user model
This avoid the weird double username situation where we want to give the users the choice of their displayed name but at the same time we need them to never change their CAS username. Now CAS matches on email so we do not have problems.
This commit is contained in:
parent
0d838cafee
commit
c836f642fa
25 changed files with 259 additions and 158 deletions
1
accounts/__init__.py
Normal file
1
accounts/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
default_app_config = "accounts.apps.AccountsConfig"
|
38
accounts/admin.py
Normal file
38
accounts/admin.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from .models import User
|
||||
|
||||
|
||||
class UserAdmin(DjangoUserAdmin):
|
||||
fieldsets = (
|
||||
(None, {"fields": ("email", "password")}),
|
||||
(_("Personal info"), {"fields": ("public_name",)}),
|
||||
(
|
||||
_("Permissions"),
|
||||
{
|
||||
"fields": (
|
||||
"is_active",
|
||||
"is_staff",
|
||||
"is_superuser",
|
||||
"groups",
|
||||
"user_permissions",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
add_fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"classes": ("wide",),
|
||||
"fields": ("email", "public_name", "password1", "password2"),
|
||||
},
|
||||
),
|
||||
)
|
||||
list_display = ("public_name", "email", "is_staff")
|
||||
search_fields = ("public_name", "email")
|
||||
ordering = ("email",)
|
||||
|
||||
|
||||
admin.site.register(User, UserAdmin)
|
6
accounts/apps.py
Normal file
6
accounts/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
name = "accounts"
|
||||
verbose_name = "Comptes utilisateur·ice·s"
|
7
accounts/backends.py
Normal file
7
accounts/backends.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from django_cas_ng.backends import CASBackend as DjangoCasNgBackend
|
||||
|
||||
|
||||
class CasBackend(DjangoCasNgBackend):
|
||||
def clean_username(self, username):
|
||||
# We need to build an email out of the CAS username
|
||||
return username.lower() + "@clipper.ens.fr"
|
19
accounts/forms.py
Normal file
19
accounts/forms.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from django.forms import ModelForm, ValidationError
|
||||
from .models import User
|
||||
|
||||
|
||||
class AccountSettingsForm(ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["public_name"]
|
||||
|
||||
def clean_public_name(self):
|
||||
public_name = self.cleaned_data["public_name"]
|
||||
public_name = public_name.strip()
|
||||
if (
|
||||
User.objects.filter(public_name=public_name)
|
||||
.exclude(pk=self.instance.pk)
|
||||
.exists()
|
||||
):
|
||||
raise ValidationError("Un autre compte utilise déjà ce nom ou ce pseudo.")
|
||||
return public_name
|
34
accounts/migrations/0001_initial.py
Normal file
34
accounts/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Generated by Django 3.1.2 on 2020-12-24 00:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('email', models.EmailField(max_length=254, unique=True, verbose_name='adresse email')),
|
||||
('public_name', models.CharField(help_text='Ce nom est utilisé pour toutes les interactions publiques sur GestioJeux. Il doit être unique.', max_length=150, unique=True, verbose_name='nom ou pseudo')),
|
||||
('is_staff', models.BooleanField(default=False, help_text="Précise si l’utilisateur peut se connecter à ce site d'administration.", verbose_name='statut équipe')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Précise si l’utilisateur doit être considéré comme actif. Décochez ceci plutôt que de supprimer le compte.', verbose_name='actif')),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'utilisateur·ice',
|
||||
'verbose_name_plural': 'utilisateur·ice·s',
|
||||
},
|
||||
),
|
||||
]
|
76
accounts/models.py
Normal file
76
accounts/models.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
|
||||
from django.contrib.auth.models import PermissionsMixin
|
||||
from django.core.mail import send_mail
|
||||
|
||||
|
||||
class User(AbstractBaseUser, PermissionsMixin):
|
||||
USERNAME_FIELD = "email"
|
||||
EMAIL_FIELD = "email"
|
||||
REQUIRED_FIELDS = ["public_name"]
|
||||
MAX_NAME_LENGTH = 150
|
||||
|
||||
email = models.EmailField(verbose_name="adresse email", unique=True)
|
||||
public_name = models.CharField(
|
||||
verbose_name="nom ou pseudo",
|
||||
max_length=MAX_NAME_LENGTH,
|
||||
unique=True,
|
||||
help_text="Ce nom est utilisé pour toutes les interactions publiques sur GestioJeux. Il doit être unique.",
|
||||
)
|
||||
is_staff = models.BooleanField(
|
||||
"statut équipe",
|
||||
default=False,
|
||||
help_text="Précise si l’utilisateur peut se connecter à ce site d'administration.",
|
||||
)
|
||||
is_active = models.BooleanField(
|
||||
"actif",
|
||||
default=True,
|
||||
help_text="Précise si l’utilisateur doit être considéré comme actif. Décochez ceci plutôt que de supprimer le compte.",
|
||||
)
|
||||
|
||||
objects = BaseUserManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "utilisateur·ice"
|
||||
verbose_name_plural = "utilisateur·ice·s"
|
||||
|
||||
@classmethod
|
||||
def generate_unique_public_name(cls, base_name):
|
||||
index = 0
|
||||
public_name = base_name
|
||||
while cls.objects.filter(public_name=public_name).exists():
|
||||
index += 1
|
||||
|
||||
# ensure the resulting string is not too long
|
||||
tail_length = len(str(index))
|
||||
combined_length = len(base_name) + tail_length
|
||||
if cls.MAX_NAME_LENGTH < combined_length:
|
||||
base_name = base_name[: cls.MAX_NAME_LENGTH - tail_length]
|
||||
|
||||
public_name = base_name + str(index)
|
||||
|
||||
return public_name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.public_name:
|
||||
# Fill the public name with a generated one from email address
|
||||
base_name = self.email.split("@")[0]
|
||||
self.public_name = User.generate_unique_public_name(base_name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_full_name(self):
|
||||
return self.public_name
|
||||
|
||||
def get_short_name(self):
|
||||
return self.public_name
|
||||
|
||||
def email_user(self, subject, message, from_email=None, **kwargs):
|
||||
"""Send an email to this user."""
|
||||
send_mail(subject, message, from_email, [self.email], **kwargs)
|
||||
|
||||
@classmethod
|
||||
def normalize_username(cls, username):
|
||||
return super().normalize_username(username.lower())
|
||||
|
||||
def __str__(self):
|
||||
return self.public_name
|
|
@ -3,7 +3,7 @@
|
|||
{% block "content" %}
|
||||
<h1>Paramètres du compte</h1>
|
||||
|
||||
<p>Vous êtes connecté en tant que <tt>{{ request.user.username }}</tt></p>
|
||||
<p>Vous êtes connecté en tant que <tt>{{ request.user.email }}</tt></p>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
@ -14,7 +14,7 @@
|
|||
{% if request.user.password and request.user.has_usable_password %}
|
||||
<hr/>
|
||||
|
||||
<a href="{% url "gestiojeux_auth:change_password" %}">Changer mon mot de passe</a>
|
||||
<a href="{% url "accounts:change_password" %}">Changer mon mot de passe</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
@ -3,7 +3,7 @@ import django.contrib.auth.views as dj_auth_views
|
|||
from .views import LoginView, LogoutView, PasswordChangeView, AccountSettingsView
|
||||
import django_cas_ng.views
|
||||
|
||||
app_name = "gestiojeux_auth"
|
||||
app_name = "accounts"
|
||||
|
||||
cas_patterns = [
|
||||
path("login/", django_cas_ng.views.LoginView.as_view(), name="cas_ng_login"),
|
||||
|
@ -21,7 +21,7 @@ accounts_patterns = [
|
|||
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"),
|
||||
path("settings/", AccountSettingsView.as_view(), name="account_settings"),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
|
@ -19,12 +19,6 @@ class LoginView(TemplateView):
|
|||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if request.user.is_authenticated:
|
||||
messages.warning(
|
||||
request,
|
||||
"Vous êtes déjà connecté·e en tant que {}.".format(
|
||||
request.user.username
|
||||
),
|
||||
)
|
||||
return redirect(self.get_next_url() or "/")
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
@ -42,14 +36,14 @@ class LoginView(TemplateView):
|
|||
next_url = self.get_next_url()
|
||||
if next_url:
|
||||
context["pass_url"] = "{}?next={}".format(
|
||||
reverse("gestiojeux_auth:password_login"), urlquote(next_url, safe="")
|
||||
reverse("accounts:password_login"), urlquote(next_url, safe="")
|
||||
)
|
||||
context["cas_url"] = "{}?next={}".format(
|
||||
reverse("gestiojeux_auth:cas_ng_login"), urlquote(next_url, safe="")
|
||||
reverse("accounts:cas_ng_login"), urlquote(next_url, safe="")
|
||||
)
|
||||
else:
|
||||
context["pass_url"] = reverse("gestiojeux_auth:password_login")
|
||||
context["cas_url"] = reverse("gestiojeux_auth:cas_ng_login")
|
||||
context["pass_url"] = reverse("accounts:password_login")
|
||||
context["cas_url"] = reverse("accounts:cas_ng_login")
|
||||
|
||||
return context
|
||||
|
||||
|
@ -58,7 +52,7 @@ class LogoutView(RedirectView):
|
|||
permanent = False
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
CAS_BACKEND_NAME = "django_cas_ng.backends.CASBackend"
|
||||
CAS_BACKEND_NAME = "accounts.backends.CasBackend"
|
||||
if self.request.session["_auth_user_backend"] != CAS_BACKEND_NAME:
|
||||
auth_logout(self.request)
|
||||
if "next" in self.request.GET:
|
||||
|
@ -67,10 +61,10 @@ class LogoutView(RedirectView):
|
|||
|
||||
if "next" in self.request.GET:
|
||||
return "{}?next={}".format(
|
||||
reverse("gestiojeux_auth:cas_ng_logout"),
|
||||
reverse("accounts:cas_ng_logout"),
|
||||
urlquote(self.request.GET["next"], safe=""),
|
||||
)
|
||||
return reverse("gestiojeux_auth:cas_ng_logout")
|
||||
return reverse("accounts:cas_ng_logout")
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
|
@ -93,7 +87,7 @@ class PasswordChangeView(PasswordChangeView):
|
|||
|
||||
def get_success_url(self):
|
||||
messages.info(self.request, "Mot de passe mis à jour")
|
||||
return reverse("gestiojeux_auth:account_settings")
|
||||
return reverse("accounts:account_settings")
|
||||
|
||||
|
||||
class AccountSettingsView(LoginRequiredMixin, UpdateView):
|
|
@ -30,7 +30,7 @@ INSTALLED_APPS = [
|
|||
"mainsite",
|
||||
"inventory",
|
||||
"django_cas_ng",
|
||||
"gestiojeux_auth",
|
||||
"accounts",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -64,9 +64,11 @@ TEMPLATES = [
|
|||
|
||||
WSGI_APPLICATION = "gestiojeux.wsgi.application"
|
||||
|
||||
AUTH_USER_MODEL = "accounts.User"
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
"django_cas_ng.backends.CASBackend",
|
||||
"accounts.backends.CasBackend",
|
||||
)
|
||||
|
||||
# Password validation
|
||||
|
@ -81,7 +83,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
||||
]
|
||||
|
||||
LOGIN_URL = "gestiojeux_auth:login"
|
||||
LOGIN_URL = "accounts:login"
|
||||
|
||||
# Use markdown extensions
|
||||
MARKDOWNX_MARKDOWN_EXTENSIONS = [
|
||||
|
@ -112,7 +114,6 @@ CAS_SERVER_URL = "https://cas.eleves.ens.fr/"
|
|||
CAS_VERIFY_URL = "https://cas.eleves.ens.fr/"
|
||||
CAS_VERSION = "CAS_2_SAML_1_0"
|
||||
CAS_IGNORE_REFERER = True
|
||||
CAS_FORCE_CHANGE_USERNAME_CASE = "lower"
|
||||
CAS_LOGIN_MSG = None
|
||||
CAS_LOGIN_URL_NAME = "gestiojeux_auth:cas_ng_login"
|
||||
CAS_LOGOUT_URL_NAME = "gestiojeux_auth:cas_ng_logout"
|
||||
CAS_LOGIN_URL_NAME = "accounts:cas_ng_login"
|
||||
CAS_LOGOUT_URL_NAME = "accounts:cas_ng_logout"
|
||||
|
|
|
@ -22,7 +22,7 @@ urlpatterns = [
|
|||
path("admin/", admin.site.urls),
|
||||
path("markdownx/", include("markdownx.urls")),
|
||||
path("inventory/", include("inventory.urls")),
|
||||
path("auth/", include("gestiojeux_auth.urls")),
|
||||
path("account/", include("accounts.urls")),
|
||||
path("", include("mainsite.urls")),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class GestiojeuxAuthConfig(AppConfig):
|
||||
name = "gestiojeux_auth"
|
|
@ -1,24 +0,0 @@
|
|||
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
|
|
@ -1,6 +1,7 @@
|
|||
# Generated by Django 3.1.2 on 2020-11-21 18:17
|
||||
# Generated by Django 3.1.2 on 2020-12-21 01:20
|
||||
|
||||
import autoslug.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
@ -10,6 +11,7 @@ class Migration(migrations.Migration):
|
|||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
@ -22,6 +24,31 @@ class Migration(migrations.Migration):
|
|||
],
|
||||
options={
|
||||
'verbose_name': 'catégorie',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Game',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=256, verbose_name='titre')),
|
||||
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True)),
|
||||
('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(blank=True, help_text='Affichage personnalisé pour le nombre de joueur·se·s', max_length=256, verbose_name='nombre de joueur·se·s')),
|
||||
('duration', models.CharField(blank=True, max_length=256, verbose_name='durée de partie')),
|
||||
('game_designer', models.CharField(blank=True, max_length=256, verbose_name='game designer')),
|
||||
('illustrator', models.CharField(blank=True, max_length=256, verbose_name='illustrateur·trice')),
|
||||
('editor', models.CharField(blank=True, max_length=256, verbose_name='éditeur')),
|
||||
('description', models.TextField(blank=True, verbose_name='description')),
|
||||
('image', models.ImageField(blank=True, upload_to='game_img/', verbose_name='image')),
|
||||
('missing_elements', models.TextField(blank=True, verbose_name='pièces manquantes')),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='inventory.category', verbose_name='catégorie')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'jeu',
|
||||
'verbose_name_plural': 'jeux',
|
||||
'ordering': ['title'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
@ -31,25 +58,29 @@ class Migration(migrations.Migration):
|
|||
('name', models.CharField(max_length=256, verbose_name='nom')),
|
||||
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Game',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=256, verbose_name='titre')),
|
||||
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True)),
|
||||
('player_range', models.CharField(max_length=256, verbose_name='nombre de joueur·se·s')),
|
||||
('duration', models.CharField(max_length=256, verbose_name='durée de partie')),
|
||||
('editor', models.CharField(blank=True, max_length=256, verbose_name='éditeur')),
|
||||
('game_designer', models.CharField(blank=True, max_length=256, verbose_name='game designer')),
|
||||
('description', models.TextField(blank=True, verbose_name='description')),
|
||||
('image', models.ImageField(blank=True, upload_to='game_img/', verbose_name='image')),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='inventory.category', verbose_name='catégorie')),
|
||||
('tags', models.ManyToManyField(blank=True, to='inventory.Tag', verbose_name='tags')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'jeu',
|
||||
'verbose_name_plural': 'jeux',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
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'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='game',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, to='inventory.Tag', verbose_name='tags'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
# 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'],
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1,8 +1,8 @@
|
|||
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
|
||||
from accounts.models import User
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
{% if comment == edited_comment %}
|
||||
<form id="edited_comment" class="comment" method="post">
|
||||
<div class="meta">
|
||||
<span class="author">{% include "partials/user.html" with user=comment.author %}</span>
|
||||
<span class="author">{{ 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>
|
||||
|
@ -54,7 +54,7 @@
|
|||
{% else %}
|
||||
<div class="comment">
|
||||
<div class="meta">
|
||||
<span class="author">{% include "partials/user.html" with user=comment.author %}</span>
|
||||
<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" title="Éditer mon commentaire"><i class="fa fa-pencil" aria-hidden="true"></i></a>
|
||||
|
@ -74,7 +74,7 @@
|
|||
<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.
|
||||
<a href="{% url "accounts:login" %}?next={{ request.get_full_path }}">Connectez-vous</a> pour ajouter un commentaire.
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<p>Vous n'avez pas la permission pour consulter cette page.</p>
|
||||
{% if not user.is_authenticated %}
|
||||
<p>Cet accès vous est probablement refusé car vous n'êtes actuellement pas connecté·e.
|
||||
Vous pouvez vous rendre à la page de <a href="{% url "registration:login" %}">connexion</a>.</p>
|
||||
Vous pouvez vous rendre à la page de <a href="{% url "accounts:login" %}?next={{ request.get_full_path }}">connexion</a>.</p>
|
||||
{% endif %}
|
||||
<p>Vous pouvez retourner sur la <a href="{% url "mainsite:home" %}">page d'accueil</a>.</p>
|
||||
{% endblock %}
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
{% endif %}
|
||||
</nav>
|
||||
{% if request.user.is_authenticated %}
|
||||
<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>
|
||||
<a class="username" href="{% url "accounts:account_settings" %}">{{ request.user }}</a>
|
||||
<a class="login" href="{% url "accounts: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>
|
||||
<a class="login{% if url_name == "login" %} current{% endif %}" href="{% url "accounts:login" %}?next={{ request.get_full_path }}">Connexion</a>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</header>
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
{% if user.first_name %}{{ user.first_name }}{% else %}{{ user.username }}{% endif %}
|
Loading…
Reference in a new issue