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.*