Move the auth stuff to gestion/

- The login views are in `gestion/`
- The templates are under `gestion/templates/gestion/`
- `cof/shared.py` moves to `gestion/` and is splitted into 3 files:
    - The auth backends are in `backends.py`.
    - The context_processor is in `context_processor.py`
    - The LOCK/UNLOCK functions remain in `shared.py`
This commit is contained in:
Martin Pépin 2017-02-12 14:14:08 +01:00
parent 50b667993f
commit a28c00e474
16 changed files with 156 additions and 147 deletions

View file

@ -15,9 +15,9 @@ from django.core.validators import MinLengthValidator
from .models import CofProfile, EventCommentValue, \ from .models import CofProfile, EventCommentValue, \
CalendarSubscription, Club CalendarSubscription, Club
from .widgets import TriStateCheckbox from .widgets import TriStateCheckbox
from .shared import lock_table, unlock_table
from gestion.models import Profile from gestion.models import Profile
from gestion.shared import lock_table, unlock_table
from bda.models import Spectacle from bda.models import Spectacle

View file

@ -18,9 +18,10 @@ from .petits_cours_models import (
PetitCoursAbility, PetitCoursSubject PetitCoursAbility, PetitCoursSubject
) )
from .decorators import buro_required from .decorators import buro_required
from .shared import lock_table, unlock_tables
from .petits_cours_forms import DemandeForm, MatieresFormSet from .petits_cours_forms import DemandeForm, MatieresFormSet
from gestion.shared import lock_table, unlock_tables
class DemandeListView(ListView): class DemandeListView(ListView):
model = PetitCoursDemande model = PetitCoursDemande

View file

@ -10,8 +10,10 @@
{% endblock %} {% endblock %}
</a> </a>
<div class="secondary"> <div class="secondary">
<span class="hidden-xxs">&nbsp;&nbsp;|&nbsp; </span> <span class="hidden-xxs">&nbsp;&nbsp;|&nbsp; </span>
<span><a href="{% url "cof.views.logout" %}">Se déconnecter&nbsp;<span class="glyphicon glyphicon-log-out"></span></a></span> <span><a href="{% url "gestion:logout" %}">
Se déconnecter&nbsp;<span class="glyphicon glyphicon-log-out"></span>
</a></span>
</div> </div>
<h2 class="member-status">{% if user.first_name %}{{ user.first_name }}{% else %}<tt>{{ user.username }}</tt>{% endif %}, {% if user.profile.is_cof %}<tt class="user-is-cof">au COF{% else %}<tt class="user-is-not-cof">non-COF{% endif %}</tt></h2> <h2 class="member-status">{% if user.first_name %}{{ user.first_name }}{% else %}<tt>{{ user.username }}</tt>{% endif %}, {% if user.profile.is_cof %}<tt class="user-is-cof">au COF{% else %}<tt class="user-is-not-cof">non-COF{% endif %}</tt></h2>
</div><!-- /.container --> </div><!-- /.container -->

View file

@ -1,14 +0,0 @@
{% extends "base_title.html" %}
{% block realcontent %}
{% if error_type == "use_clipper_login" %}
<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>
{% elif error_type == "no_password" %}
<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>
{% else %}
<h1><strong>{{ error_title }}</strong></h1>
<p>{{ error_description }}</p>
{% endif %}
{% endblock %}

View file

@ -9,7 +9,6 @@ from custommail.shortcuts import send_custom_mail
from django.shortcuts import redirect, get_object_or_404, render from django.shortcuts import redirect, get_object_or_404, render
from django.http import Http404, HttpResponse, HttpResponseForbidden from django.http import Http404, HttpResponse, HttpResponseForbidden
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import login as django_login_view
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.utils import timezone from django.utils import timezone
@ -50,50 +49,6 @@ def home(request):
return render(request, "home.html", data) return render(request, "home.html", data)
def login(request):
if request.user.is_authenticated():
return redirect("cof.views.home")
context = {}
if request.method == "GET" and 'next' in request.GET:
context['next'] = request.GET['next']
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)
@login_required
def logout(request):
try:
profile = request.user.profile
except CofProfile.DoesNotExist:
profile, created = CofProfile.objects.get_or_create(user=request.user)
if profile.login_clipper:
return redirect("django_cas_ng.views.logout")
else:
return redirect("django.contrib.auth.views.logout")
@login_required @login_required
def survey(request, survey_id): def survey(request, survey_id):
survey = get_object_or_404(Survey, id=survey_id) survey = get_object_or_404(Survey, id=survey_id)

View file

@ -33,7 +33,6 @@ INSTALLED_APPS = (
'django.contrib.sites', 'django.contrib.sites',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'grappelli',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.admindocs', 'django.contrib.admindocs',
'autocomplete_light', 'autocomplete_light',
@ -80,7 +79,7 @@ TEMPLATES = [
'django.core.context_processors.i18n', 'django.core.context_processors.i18n',
'django.core.context_processors.media', 'django.core.context_processors.media',
'django.core.context_processors.static', 'django.core.context_processors.static',
'cof.shared.context_processor', 'gestion.context_processors.context_processor',
'kfet.context_processors.auth', 'kfet.context_processors.auth',
], ],
}, },
@ -130,9 +129,6 @@ MEDIA_URL = '/media/'
# Various additional settings # Various additional settings
SITE_ID = 1 SITE_ID = 1
GRAPPELLI_ADMIN_HEADLINE = "GestioCOF"
GRAPPELLI_ADMIN_TITLE = "<a href=\"/\">GestioCOF</a>"
MAIL_DATA = { MAIL_DATA = {
'petits_cours': { 'petits_cours': {
'FROM': "Le COF <cof@ens.fr>", 'FROM': "Le COF <cof@ens.fr>",
@ -146,7 +142,7 @@ MAIL_DATA = {
'REPLYTO': 'BdA-Revente <bda-revente@ens.fr>'}, 'REPLYTO': 'BdA-Revente <bda-revente@ens.fr>'},
} }
LOGIN_URL = "cof-login" LOGIN_URL = "gestion:login"
LOGIN_REDIRECT_URL = "home" LOGIN_REDIRECT_URL = "home"
CAS_SERVER_URL = 'https://cas.eleves.ens.fr/' CAS_SERVER_URL = 'https://cas.eleves.ens.fr/'
@ -155,7 +151,7 @@ CAS_REDIRECT_URL = '/'
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr" CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', 'django.contrib.auth.backends.ModelBackend',
'cof.shared.COFCASBackend', 'gestion.backends.COFCASBackend',
'kfet.backends.GenericTeamBackend', 'kfet.backends.GenericTeamBackend',
) )

View file

@ -10,16 +10,16 @@ from django.conf import settings
from django.conf.urls import include, url from django.conf.urls import include, url
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.views.generic.base import TemplateView
from django.contrib.auth import views as django_views from django.contrib.auth import views as django_views
from django_cas_ng import views as django_cas_views
from cof import views as cof_views, csv_views from cof import views as cof_views
from cof.urls import export_patterns, petitcours_patterns, \ from cof.urls import export_patterns, petitcours_patterns, \
surveys_patterns, events_patterns, calendar_patterns, \ surveys_patterns, events_patterns, calendar_patterns, \
clubs_patterns clubs_patterns
from cof.autocomplete import autocomplete from cof.autocomplete import autocomplete
from gestion import views as gestion_views
autocomplete_light.autodiscover() autocomplete_light.autodiscover()
admin.autodiscover() admin.autodiscover()
@ -28,6 +28,10 @@ urlpatterns = [
url(r'^$', cof_views.home, name='home'), url(r'^$', cof_views.home, name='home'),
# The common views # The common views
url(r"^", include("gestion.urls", namespace='gestion')), url(r"^", include("gestion.urls", namespace='gestion')),
# Admin urls
url(r'^admin/logout/', gestion_views.logout),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)),
# Le BdA # Le BdA
url(r'^bda/', include('bda.urls')), url(r'^bda/', include('bda.urls')),
# Les exports # Les exports
@ -42,15 +46,6 @@ urlpatterns = [
url(r'^calendar/', include(calendar_patterns)), url(r'^calendar/', include(calendar_patterns)),
# Clubs # Clubs
url(r'^clubs/', include(clubs_patterns)), url(r'^clubs/', include(clubs_patterns)),
# Authentification
url(r'^cof/denied$', 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$', cof_views.login_ext),
url(r'^outsider/logout$', django_views.logout, {'next_page': 'home'}),
url(r'^login$', cof_views.login, name="cof-login"),
url(r'^logout$', cof_views.logout),
# Infos persos # Infos persos
url(r'^outsider/password-change$', django_views.password_change), url(r'^outsider/password-change$', django_views.password_change),
url(r'^outsider/password-change-done$', url(r'^outsider/password-change-done$',
@ -67,14 +62,6 @@ urlpatterns = [
# Autocompletion # Autocompletion
url(r'^autocomplete/registration$', autocomplete), url(r'^autocomplete/registration$', autocomplete),
url(r'^autocomplete/', include('autocomplete_light.urls')), url(r'^autocomplete/', include('autocomplete_light.urls')),
# Interface admin
url(r'^admin/logout/', cof_views.logout),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/',
csv_views.admin_list_export,
{'fields': ['username', ]}),
url(r'^admin/', include(admin.site.urls)),
url(r'^grappelli/', include('grappelli.urls')),
# Liens utiles du COF et du BdA # Liens utiles du COF et du BdA
url(r'^utile_cof$', cof_views.utile_cof), url(r'^utile_cof$', cof_views.utile_cof),
url(r'^utile_bda$', cof_views.utile_bda), url(r'^utile_bda$', cof_views.utile_bda),

View file

@ -1,17 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from django.contrib.sites.models import Site
from django.conf import settings from django.conf import settings
from django_cas_ng.backends import CASBackend from django_cas_ng.backends import CASBackend
from django_cas_ng.utils import get_cas_client from django_cas_ng.utils import get_cas_client
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db import connection
from .models import CofProfile from gestion.models import Profile
User = get_user_model() User = get_user_model()
@ -32,9 +26,12 @@ class COFCASBackend(CASBackend):
# éviter les doublons. # éviter les doublons.
username = username.strip().lower() username = username.strip().lower()
profiles = CofProfile.objects.filter(login_clipper=username) profiles = Profile.objects.filter(login_clipper=username)
if len(profiles) > 0: if len(profiles) > 0:
profile = profiles.order_by('-is_cof')[0] # XXX. We have to deal with multiple profiles, this should not
# happen
# profile = profiles.order_by('-is_cof')[0]
profile = profiles.first()
user = profile.user user = profile.user
return user return user
try: try:
@ -50,49 +47,11 @@ class COFCASBackend(CASBackend):
user = self.authenticate_cas(ticket, service, request) user = self.authenticate_cas(ticket, service, request)
if user is None: if user is None:
return user return user
try: profile = user.profile
profile = user.profile
except CofProfile.DoesNotExist:
profile, created = CofProfile.objects.get_or_create(user=user)
profile.save()
if not profile.login_clipper: if not profile.login_clipper:
profile.login_clipper = user.username profile.login_clipper = user.username
profile.save() profile.save()
if not user.email: if not user.email:
user.email = settings.CAS_EMAIL_FORMAT % profile.login_clipper user.email = settings.CAS_EMAIL_FORMAT % profile.login_clipper
user.save() user.save()
if profile.is_buro and not user.is_staff:
user.is_staff = True
user.save()
return user return user
def context_processor(request):
'''Append extra data to the context of the given request'''
data = {
"user": request.user,
"site": Site.objects.get_current(),
}
return data
def lock_table(*models):
query = "LOCK TABLES "
for i, model in enumerate(models):
table = model._meta.db_table
if i > 0:
query += ", "
query += "%s WRITE" % table
cursor = connection.cursor()
cursor.execute(query)
row = cursor.fetchone()
return row
def unlock_tables(*models):
cursor = connection.cursor()
cursor.execute("UNLOCK TABLES")
row = cursor.fetchone()
return row
unlock_table = unlock_tables

View file

@ -0,0 +1,10 @@
from django.contrib.sites.models import Site
def context_processor(request):
'''Append extra data to the context of the given request'''
data = {
"user": request.user,
"site": Site.objects.get_current(),
}
return data

27
gestion/shared.py Normal file
View file

@ -0,0 +1,27 @@
"""
Locking/unlocking tools to prevent tables to be corrupted
"""
from django.db import connection
def lock_table(*models):
query = "LOCK TABLES "
for i, model in enumerate(models):
table = model._meta.db_table
if i > 0:
query += ", "
query += "%s WRITE" % table
cursor = connection.cursor()
cursor.execute(query)
row = cursor.fetchone()
return row
def unlock_tables(*models):
cursor = connection.cursor()
cursor.execute("UNLOCK TABLES")
row = cursor.fetchone()
return row
unlock_table = unlock_tables

View file

@ -0,0 +1,20 @@
{% extends "base_title.html" %}
{% block realcontent %}
{% if error_type == "use_clipper_login" %}
<h2><strong>Votre identifiant est lié à un compte <tt>clipper</tt></strong></h2>
<p>
Veuillez vous connecter à l'aide de votre
<a href="{% url 'gestion:cas_login' %}">compte <tt>clipper</tt></a>
</p>
{% elif error_type == "no_password" %}
<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>
{% else %}
<h1><strong>{{ error_title }}</strong></h1>
<p>{{ error_description }}</p>
{% endif %}
{% endblock %}

View file

@ -15,7 +15,7 @@
<p class="error">Identifiants incorrects.</p> <p class="error">Identifiants incorrects.</p>
{% endif %} {% endif %}
<form class="form-horizontal" method="post" <form class="form-horizontal" method="post"
action="{% url 'cof.views.login_ext' %}?next={{ next|urlencode }}"> action="{% url 'gestion:login_ext' %}?next={{ next|urlencode }}">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<input class="form-control" id="id_username" maxlength="254" name="username" type="text" placeholder="Nom d'utilisateur"> <input class="form-control" id="id_username" maxlength="254" name="username" type="text" placeholder="Nom d'utilisateur">

View file

@ -12,13 +12,13 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row" style="margin:0;"> <div class="row" style="margin:0;">
<a aria-label="Compte clipper" <a aria-label="Compte clipper"
href="{% url 'django_cas_ng.views.login' %}?next={{ next|urlencode }}"> href="{% url 'gestion:cas_login' %}?next={{ next|urlencode }}">
<div class="col-xs-12 col-sm-6" id="login_clipper"> <div class="col-xs-12 col-sm-6" id="login_clipper">
Compte clipper Compte clipper
</div> </div>
</a> </a>
<a aria-label="Extérieur" <a aria-label="Extérieur"
href="{% url 'cof.views.login_ext' %}?next={{ next|urlencode }}"> href="{% url 'gestion:login_ext' %}?next={{ next|urlencode }}">
<div class="col-xs-12 col-sm-6" id="login_outsider"> <div class="col-xs-12 col-sm-6" id="login_outsider">
Extérieur Extérieur
</div> </div>

View file

@ -1,8 +1,24 @@
from django.conf.urls import url from django.conf.urls import url, include
from django.views.generic.base import TemplateView
from django.contrib.auth import views as django_views
from django.contrib import admin
from django_cas_ng import views as django_cas_views
from . import views from . import views
urlpatterns = [ urlpatterns = [
# Profile edition
url(r"^profile/?$", views.profile, name="profile"), url(r"^profile/?$", views.profile, name="profile"),
# Authentication
url(r'^cof/denied$',
TemplateView.as_view(template_name='cof-denied.html'),
name="denied"),
url(r'^cas/login$', django_cas_views.login, name="cas_login"),
url(r'^cas/logout$', django_cas_views.logout, name="cas_logout"),
url(r'^outsider/login$', views.login_ext, name="login_ext"),
url(r'^outsider/logout$', django_views.logout, {'next_page': 'home'}),
url(r'^login$', views.login, name="login"),
url(r'^logout$', views.logout, name="logout"),
] ]

View file

@ -1,9 +1,59 @@
from django.shortcuts import render """
The common views of the different organisations.
- Authentication
- Profile edition
"""
from django.shortcuts import render, redirect
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.views import (
login as django_login, logout as django_logout
)
from .forms import ProfileForm, UserForm from .forms import ProfileForm, UserForm
def login(request):
if request.user.is_authenticated():
return redirect("cof.views.home")
context = {}
# Fetch the next page from the request data
if request.method == "GET" and 'next' in request.GET:
context['next'] = request.GET['next']
return render(request, "gestion/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 user.profile.login_clipper:
return render(request, "gestion/error.html",
{"error_type": "use_clipper_login"})
if not user.has_usable_password() or user.password in ("", "!"):
return render(request, "gestion/error.html",
{"error_type": "no_password"})
except User.DoesNotExist:
pass
context = {}
# Fetch the next page from the request data
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(request, template_name='gestion/login.html',
extra_context=context)
@login_required
def logout(request):
if request.user.profile.login_clipper:
return redirect("gestion:cas_logout")
else:
return django_logout(request)
@login_required @login_required
def profile(request): def profile(request):
success = False success = False

View file

@ -51,7 +51,7 @@
</li> </li>
{% endif %} {% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li><a href="{% url 'cof.views.logout' %}?next=/k-fet/" title="Déconnexion"><span class="glyphicon glyphicon-log-out"></span></a></li> <li><a href="{% url 'gestion:logout' %}?next=/k-fet/" title="Déconnexion"><span class="glyphicon glyphicon-log-out"></span></a></li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>