From a1ead1bfc891754bf0c248d587b906e6344a9fe0 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 25 Mar 2019 23:05:47 +0100 Subject: [PATCH] =?UTF-8?q?Pr=C3=A9paration=20Django2=20:=20vues=20de=20lo?= =?UTF-8?q?gin/logout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit À 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. --- cof/urls.py | 12 +++-- gestioncof/forms.py | 32 ++++++++++++ .../{error.html => login_error.html} | 7 +-- gestioncof/templates/logout.html | 7 +++ gestioncof/views.py | 52 +++++++++---------- requirements.txt | 4 +- 6 files changed, 78 insertions(+), 36 deletions(-) rename gestioncof/templates/{error.html => login_error.html} (73%) create mode 100644 gestioncof/templates/logout.html diff --git a/cof/urls.py b/cof/urls.py index aab91130..dddf88a3 100644 --- a/cof/urls.py +++ b/cof/urls.py @@ -49,10 +49,14 @@ urlpatterns = [ TemplateView.as_view(template_name="cof-denied.html"), name="cof-denied", ), - url(r"^cas/login$", django_cas_views.login, name="cas_login_view"), - url(r"^cas/logout$", django_cas_views.logout), - url(r"^outsider/login$", gestioncof_views.login_ext, name="ext_login_view"), - url(r"^outsider/logout$", django_views.logout, {"next_page": "home"}), + url(r"^cas/login$", django_cas_views.LoginView.as_view(), name="cas_login_view"), + url(r"^cas/logout$", django_cas_views.LogoutView.as_view()), + url( + 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"^logout$", gestioncof_views.logout, name="cof-logout"), # Infos persos diff --git a/gestioncof/forms.py b/gestioncof/forms.py index aec5ce24..b29b9aff 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.models import User from django.forms.formsets import BaseFormSet, formset_factory from django.forms.widgets import CheckboxSelectMultiple, RadioSelect @@ -10,6 +11,37 @@ from gestioncof.models import CalendarSubscription, Club, CofProfile, EventComme 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): def __init__(self, *args, **kwargs): event = kwargs.pop("event") diff --git a/gestioncof/templates/error.html b/gestioncof/templates/login_error.html similarity index 73% rename from gestioncof/templates/error.html rename to gestioncof/templates/login_error.html index 082abcf0..fc736afc 100644 --- a/gestioncof/templates/error.html +++ b/gestioncof/templates/login_error.html @@ -1,14 +1,11 @@ {% extends "base_title.html" %} {% block realcontent %} - {% if error_type == "use_clipper_login" %} + {% if error_code == "has_clipper" %}

Votre identifiant est lié à un compte clipper

Veuillez vous connecter à l'aide de votre compte clipper

- {% elif error_type == "no_password" %} + {% elif error_code == "no_password" %}

Votre compte n'a pas de mot de passe associé

Veuillez nous contacter pour que nous en définissions un et que nous vous le transmettions !

- {% else %} -

{{ error_title }}

-

{{ error_description }}

{% endif %} {% endblock %} diff --git a/gestioncof/templates/logout.html b/gestioncof/templates/logout.html new file mode 100644 index 00000000..ebda0ac7 --- /dev/null +++ b/gestioncof/templates/logout.html @@ -0,0 +1,7 @@ +{% extends "base_title.html" %} + +{% block realcontent %} +

+ Déconnexion réussie. À bientôt ! +

+{% endblock %} \ No newline at end of file diff --git a/gestioncof/views.py b/gestioncof/views.py index 4e6daa89..805c5576 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -7,8 +7,8 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User from django.contrib.auth.views import ( - login as django_login_view, - logout as django_logout_view, + LoginView as DjangoLoginView, + LogoutView as DjangoLogoutView, redirect_to_login, ) 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.translation import ugettext_lazy as _ 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 bda.models import Spectacle, Tirage @@ -29,6 +29,7 @@ from gestioncof.forms import ( EventForm, EventFormset, EventStatusFilterForm, + ExteAuthenticationForm, GestioncofConfigForm, ProfileForm, RegistrationPassUserForm, @@ -81,26 +82,23 @@ def login(request): return render(request, "login_switch.html", context) -def login_ext(request): - if request.method == "POST" and "username" in request.POST: - try: - user = User.objects.get(username=request.POST["username"]) - if not user.has_usable_password() or user.password in ("", "!"): - profile, created = CofProfile.objects.get_or_create(user=user) - if profile.login_clipper: - return render( - request, "error.html", {"error_type": "use_clipper_login"} - ) - else: - return render(request, "error.html", {"error_type": "no_password"}) - except User.DoesNotExist: - pass - context = {} - if request.method == "GET" and "next" in request.GET: - context["next"] = request.GET["next"] - 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) +class LoginExtView(DjangoLoginView): + template_name = "login.html" + form_class = ExteAuthenticationForm + + def form_invalid(self, form): + # forms.non_field_errors() returns strings for some reason + non_field_errors = form.errors["__all__"].as_data() + exte_login_error = next( + (e for e in non_field_errors if e.code in ["has_clipper", "no_password"]), + None, + ) + + if exte_login_error is not None: + return render( + self.request, "login_error.html", {"error_code": exte_login_error.code} + ) + return super().form_invalid(form) @login_required @@ -112,13 +110,15 @@ def logout(request, next_page=None): if profile and profile.login_clipper: msg = _("Déconnexion de GestioCOF et CAS réussie. À bientôt {}.") - logout_view = cas_logout_view + logout_view = CasLogoutView.as_view() else: 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())) - return logout_view(request, next_page=next_page) + return logout_view(request) @login_required diff --git a/requirements.txt b/requirements.txt index 6386229b..87725d39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ configparser==3.5.0 +# TODO: change to 2.2 when out Django==1.11.* django-autocomplete-light==3.3.* django-autoslug==1.9.3 -django-cas-ng==3.5.* +django-cas-ng==3.6.* django-djconfig==0.8.0 django-recaptcha==1.4.0 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 channels==1.1.5 python-dateutil +# TODO: change to 2.5 when out (2.4 is not explicitly compatible with Django 2.2) wagtail==2.3.* wagtailmenus==2.12.* wagtail-modeltranslation==0.10.*