Préparation Django2 : vues de login/logout

À partir de Django 2.1, les vues de login et logout sont class-based
uniquement. On passe donc à django-cas-ng 2.6 pour harmoniser.
On cleanup un peu le processus de login avec une classe un peu propre +
un vrai formulaire/des vrais templates.
This commit is contained in:
Ludovic Stephan 2019-03-25 23:05:47 +01:00
parent cef75e56d7
commit a1ead1bfc8
6 changed files with 78 additions and 36 deletions

View file

@ -49,10 +49,14 @@ urlpatterns = [
TemplateView.as_view(template_name="cof-denied.html"), TemplateView.as_view(template_name="cof-denied.html"),
name="cof-denied", name="cof-denied",
), ),
url(r"^cas/login$", django_cas_views.login, name="cas_login_view"), url(r"^cas/login$", django_cas_views.LoginView.as_view(), name="cas_login_view"),
url(r"^cas/logout$", django_cas_views.logout), url(r"^cas/logout$", django_cas_views.LogoutView.as_view()),
url(r"^outsider/login$", gestioncof_views.login_ext, name="ext_login_view"), url(
url(r"^outsider/logout$", django_views.logout, {"next_page": "home"}), r"^outsider/login$",
gestioncof_views.LoginExtView.as_view(),
name="ext_login_view",
),
url(r"^outsider/logout$", django_views.LogoutView.as_view(), {"next_page": "home"}),
url(r"^login$", gestioncof_views.login, name="cof-login"), url(r"^login$", gestioncof_views.login, name="cof-login"),
url(r"^logout$", gestioncof_views.logout, name="cof-logout"), url(r"^logout$", gestioncof_views.logout, name="cof-logout"),
# Infos persos # Infos persos

View file

@ -1,4 +1,5 @@
from django import forms from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.forms.formsets import BaseFormSet, formset_factory from django.forms.formsets import BaseFormSet, formset_factory
from django.forms.widgets import CheckboxSelectMultiple, RadioSelect from django.forms.widgets import CheckboxSelectMultiple, RadioSelect
@ -10,6 +11,37 @@ from gestioncof.models import CalendarSubscription, Club, CofProfile, EventComme
from gestioncof.widgets import TriStateCheckbox from gestioncof.widgets import TriStateCheckbox
class ExteAuthenticationForm(AuthenticationForm):
"""
Formulaire pour l'authentification des extés : renvoie une erreur si la personne
qui essaie de s'authentifier n'a pas de mot de passe. L'erreur dépend de si la
personne a un login clipper ou non.
"""
def clean(self):
username = self.cleaned_data.get("username")
if username is not None:
try:
user = User.objects.get(username=username)
if not user.has_usable_password() or user.password in ("", "!"):
profile, created = CofProfile.objects.get_or_create(user=user)
if profile.login_clipper:
raise forms.ValidationError(
_("L'utilisateur·ice a un login clipper !"),
code="has_clipper",
)
else:
raise forms.ValidationError(
_("L'utilisateur·ice n'a pas de mot de passe"),
code="no_password",
)
except User.DoesNotExist:
pass
return super().clean()
class EventForm(forms.Form): class EventForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
event = kwargs.pop("event") event = kwargs.pop("event")

View file

@ -1,14 +1,11 @@
{% extends "base_title.html" %} {% extends "base_title.html" %}
{% block realcontent %} {% block realcontent %}
{% if error_type == "use_clipper_login" %} {% if error_code == "has_clipper" %}
<h2><strong>Votre identifiant est lié à un compte <tt>clipper</tt></strong></h2> <h2><strong>Votre identifiant est lié à un compte <tt>clipper</tt></strong></h2>
<p>Veuillez vous connecter à l'aide de votre <a href="{% url 'cas_login_view' %}">compte <tt>clipper</tt></a></p> <p>Veuillez vous connecter à l'aide de votre <a href="{% url 'cas_login_view' %}">compte <tt>clipper</tt></a></p>
{% elif error_type == "no_password" %} {% elif error_code == "no_password" %}
<h2><strong>Votre compte n'a pas de mot de passe associé</strong></h2> <h2><strong>Votre compte n'a pas de mot de passe associé</strong></h2>
<p>Veuillez <a href="mailto:cof@clipper.ens.fr">nous contacter</a> pour que nous en définissions un et que nous vous le transmettions !</p> <p>Veuillez <a href="mailto:cof@clipper.ens.fr">nous contacter</a> pour que nous en définissions un et que nous vous le transmettions !</p>
{% else %}
<h1><strong>{{ error_title }}</strong></h1>
<p>{{ error_description }}</p>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -0,0 +1,7 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>
Déconnexion réussie. À bientôt !
</h2>
{% endblock %}

View file

@ -7,8 +7,8 @@ from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.views import ( from django.contrib.auth.views import (
login as django_login_view, LoginView as DjangoLoginView,
logout as django_logout_view, LogoutView as DjangoLogoutView,
redirect_to_login, redirect_to_login,
) )
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
@ -18,7 +18,7 @@ from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView from django.views.generic import FormView
from django_cas_ng.views import logout as cas_logout_view from django_cas_ng.views import LogoutView as CasLogoutView
from icalendar import Calendar, Event as Vevent from icalendar import Calendar, Event as Vevent
from bda.models import Spectacle, Tirage from bda.models import Spectacle, Tirage
@ -29,6 +29,7 @@ from gestioncof.forms import (
EventForm, EventForm,
EventFormset, EventFormset,
EventStatusFilterForm, EventStatusFilterForm,
ExteAuthenticationForm,
GestioncofConfigForm, GestioncofConfigForm,
ProfileForm, ProfileForm,
RegistrationPassUserForm, RegistrationPassUserForm,
@ -81,26 +82,23 @@ def login(request):
return render(request, "login_switch.html", context) return render(request, "login_switch.html", context)
def login_ext(request): class LoginExtView(DjangoLoginView):
if request.method == "POST" and "username" in request.POST: template_name = "login.html"
try: form_class = ExteAuthenticationForm
user = User.objects.get(username=request.POST["username"])
if not user.has_usable_password() or user.password in ("", "!"): def form_invalid(self, form):
profile, created = CofProfile.objects.get_or_create(user=user) # forms.non_field_errors() returns strings for some reason
if profile.login_clipper: non_field_errors = form.errors["__all__"].as_data()
return render( exte_login_error = next(
request, "error.html", {"error_type": "use_clipper_login"} (e for e in non_field_errors if e.code in ["has_clipper", "no_password"]),
) None,
else: )
return render(request, "error.html", {"error_type": "no_password"})
except User.DoesNotExist: if exte_login_error is not None:
pass return render(
context = {} self.request, "login_error.html", {"error_code": exte_login_error.code}
if request.method == "GET" and "next" in request.GET: )
context["next"] = request.GET["next"] return super().form_invalid(form)
if request.method == "POST" and "next" in request.POST:
context["next"] = request.POST["next"]
return django_login_view(request, template_name="login.html", extra_context=context)
@login_required @login_required
@ -112,13 +110,15 @@ def logout(request, next_page=None):
if profile and profile.login_clipper: if profile and profile.login_clipper:
msg = _("Déconnexion de GestioCOF et CAS réussie. À bientôt {}.") msg = _("Déconnexion de GestioCOF et CAS réussie. À bientôt {}.")
logout_view = cas_logout_view logout_view = CasLogoutView.as_view()
else: else:
msg = _("Déconnexion de GestioCOF réussie. À bientôt {}.") msg = _("Déconnexion de GestioCOF réussie. À bientôt {}.")
logout_view = django_logout_view logout_view = DjangoLogoutView.as_view(
next_page=next_page, template_name="logout.html"
)
messages.success(request, msg.format(request.user.get_short_name())) messages.success(request, msg.format(request.user.get_short_name()))
return logout_view(request, next_page=next_page) return logout_view(request)
@login_required @login_required

View file

@ -1,8 +1,9 @@
configparser==3.5.0 configparser==3.5.0
# TODO: change to 2.2 when out
Django==1.11.* Django==1.11.*
django-autocomplete-light==3.3.* django-autocomplete-light==3.3.*
django-autoslug==1.9.3 django-autoslug==1.9.3
django-cas-ng==3.5.* django-cas-ng==3.6.*
django-djconfig==0.8.0 django-djconfig==0.8.0
django-recaptcha==1.4.0 django-recaptcha==1.4.0
django-redis-cache==1.8.1 django-redis-cache==1.8.1
@ -20,6 +21,7 @@ git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_customma
ldap3 ldap3
channels==1.1.5 channels==1.1.5
python-dateutil python-dateutil
# TODO: change to 2.5 when out (2.4 is not explicitly compatible with Django 2.2)
wagtail==2.3.* wagtail==2.3.*
wagtailmenus==2.12.* wagtailmenus==2.12.*
wagtail-modeltranslation==0.10.* wagtail-modeltranslation==0.10.*