435 lines
18 KiB
Python
435 lines
18 KiB
Python
# -*- encoding: utf-8 -*-
|
|
|
|
import random
|
|
|
|
from django.utils.safestring import mark_safe
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.forms import Form, CharField, HiddenInput
|
|
from django import forms
|
|
from django.conf import settings
|
|
from django.contrib.auth import authenticate
|
|
from django.core.cache import cache
|
|
from django.contrib.auth.models import User
|
|
from django.core.urlresolvers import reverse
|
|
from django.template import Template, Context
|
|
from django.utils.http import urlencode
|
|
from django.core.mail import send_mail, EmailMessage
|
|
from django.contrib.auth.forms import SetPasswordForm
|
|
from crispy_forms.helper import FormHelper
|
|
from crispy_forms.layout import (Layout, ButtonHolder, Submit, Fieldset, Field, HTML)
|
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
|
from django.db import transaction
|
|
|
|
from backends import ProfilRechercheAuthentification
|
|
import models
|
|
from utils import next_url, JETON_CHARS, iter_slice
|
|
|
|
class FormulaireAvecRelaie(Form):
|
|
'''
|
|
Classe de base pour un formulaire qui renvoie dynamiquement sur une URL
|
|
quelconque.
|
|
'''
|
|
next = CharField(widget=HiddenInput, required=False)
|
|
static_next_url = None
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.request = kwargs.pop('request', None)
|
|
if self.request is not None:
|
|
initial = kwargs.setdefault('initial', dict())
|
|
initial['next'] = self.next_url()
|
|
super(FormulaireAvecRelaie, self).__init__(*args, **kwargs)
|
|
|
|
def next_url(self):
|
|
return next_url(self.request, self.static_next_url)
|
|
|
|
class CommonForm(Form):
|
|
def __init__(self, *args, **kwargs):
|
|
super(CommonForm, self).__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_class = 'form-horizontal'
|
|
|
|
class AcceptationCguForm(CommonForm):
|
|
def __init__(self, *args, **kwargs):
|
|
super(AcceptationCguForm, self).__init__(*args, **kwargs)
|
|
self.helper.layout = Layout(
|
|
ButtonHolder(Submit('accepte', _(u"J'accepte")),
|
|
Submit('refuse', _(u"Je refuse"))))
|
|
|
|
class AcceptationCharteQualiteForm(CommonForm):
|
|
def __init__(self, *args, **kwargs):
|
|
super(AcceptationCharteQualiteForm, self).__init__(*args, **kwargs)
|
|
self.helper.layout = Layout(
|
|
ButtonHolder(Submit('accepte', _(u"J'accepte")),
|
|
Submit('refuse', _(u"Je refuse"))))
|
|
|
|
class ProfilRechercheCreationForm(forms.ModelForm):
|
|
identifiant_cas = forms.CharField(max_length=64,
|
|
help_text=_(u"ou identifiant ENT de l'utilisateur"))
|
|
|
|
class Meta:
|
|
model = models.ProfilRecherche
|
|
fields = ()
|
|
|
|
def clean_identifiant_cas(self):
|
|
identifiant_cas = self.cleaned_data.get('identifiant_cas', '').strip()
|
|
backend = ProfilRechercheAuthentification()
|
|
ldap_resultat = backend.recherche_utilisateur(identifiant_cas)
|
|
if not ldap_resultat:
|
|
raise forms.ValidationError(_(u"L'identifiant CAS %s n'existe pas") % identifiant_cas)
|
|
attributes = ldap_resultat[0][1]
|
|
if ProfilRechercheAuthentification.MAIL in attributes:
|
|
self.instance.email = attributes[ProfilRechercheAuthentification.MAIL][0]
|
|
elif ProfilRechercheAuthentification.SUPANN_MAIL_PERSO in attributes:
|
|
self.instance.email = attributes[ProfilRechercheAuthentification.SUPANN_MAIL_PERSO][0]
|
|
else:
|
|
raise forms.ValidationError(_(u"Cet utilisateur n'a pas d'email."))
|
|
return identifiant_cas
|
|
|
|
def save(self, commit=True):
|
|
identifiant_cas = self.cleaned_data['identifiant_cas']
|
|
self.instance = models.ProfilRecherche.new_cas_user(identifiant_cas, self.instance.email,
|
|
settings.CAS_URL)
|
|
return self.instance
|
|
|
|
def save_m2m(self):
|
|
pass
|
|
|
|
class AuthentificationEntHomepageForm(forms.Form):
|
|
helper = FormHelper()
|
|
helper.layout = Layout(
|
|
Submit('compte-ent', _(u"Je me connecte en tant qu'étudiant / chercheur")))
|
|
helper.form_class = 'form-horizontal'
|
|
helper.form_action = '/recherche/connexion/?nopassive=1&next=%2Frecherche%2F'
|
|
|
|
class AuthentificationHomepageForm(forms.Form):
|
|
'''
|
|
Formulaire d'authentification en page d'accueil, il ne traite jamais son
|
|
résultat car celui-ci est redirigé sur la page de connexion du profil offre
|
|
/offre/connexion.
|
|
'''
|
|
identifiant = CharField(max_length=64, label=_(u"Email"))
|
|
mot_de_passe = forms.CharField(widget=forms.PasswordInput, label=_(u"Mot de passe"))
|
|
|
|
helper = FormHelper()
|
|
helper.layout = Layout(
|
|
Fieldset(_(u"Je suis propriétaire, j'accède à mon compte"),
|
|
Field('identifiant'),
|
|
Field('mot_de_passe'),
|
|
HTML(u'''{% load url from future %}{% load i18n %}<p id="inscrivez-vous"><a href="{% url 'inscription' %}?next=/offre">{% trans "Vous êtes propriétaire ? Inscrivez-vous !" %}</a></p>'''),
|
|
HTML(u'''{% load url from future %}{% load i18n %}<p id="perdu-mot-de-passe"><a href="{% url 'mot-de-passe-perdu' '' %}">{% trans "J'ai perdu mon mot de passe..." %}</a></p>'''),
|
|
ButtonHolder(
|
|
Submit('connexion-offre', _(u'Me connecter')))))
|
|
helper.form_class = 'form-horizontal'
|
|
helper.form_action = '/offre/connexion/?next=%2Foffre%2F'
|
|
|
|
class BaseAuthentificationForm(forms.Form):
|
|
identifiant = forms.CharField(label='', max_length=128)
|
|
mot_de_passe = forms.CharField(label='', widget=forms.PasswordInput)
|
|
|
|
error_messages = {
|
|
'identifiants_invalides': _(u"Entrez un email et un mot de passe corrects. "
|
|
u"Ces deux champs sont sensibles à la casse."),
|
|
'pas_de_cookies': _(u"Votre navigateur ne semble pas accepter les cookies. "
|
|
u"Les cookies sont nécessaires pour se connecter."),
|
|
'inactif': _(u"Votre compte est bloqué, veuillez contacter le service du logement."),
|
|
}
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.helper = FormHelper()
|
|
self.helper.form_class = 'form-horizontal'
|
|
self.request = kwargs.pop('request', None)
|
|
self.profil = kwargs.pop('profil', None)
|
|
submit_id = kwargs.pop('submit_id', 'connexion')
|
|
self.user_cache = None
|
|
titre = kwargs.pop('titre', _(u'Connexion pour les étudiants ayant un compte temporaire'))
|
|
self.helper.layout = Layout(
|
|
Fieldset(titre,
|
|
Field('identifiant', placeholder=_(u"Email")),
|
|
Field('mot_de_passe', placeholder=_(u"Mot de passe")),
|
|
ButtonHolder(
|
|
Submit(submit_id, _(u'Me connecter')))))
|
|
super(BaseAuthentificationForm, self).__init__(*args, **kwargs)
|
|
|
|
def clean_identifiant(self):
|
|
identifiant = self.cleaned_data['identifiant']
|
|
return identifiant.lower()
|
|
|
|
def clean(self):
|
|
cleaned_data = super(BaseAuthentificationForm, self).clean()
|
|
identifiant = cleaned_data.get('identifiant', '')
|
|
mot_de_passe = cleaned_data.get('mot_de_passe', '')
|
|
|
|
if identifiant and mot_de_passe:
|
|
self.user_cache = authenticate(email=identifiant,
|
|
password=mot_de_passe,
|
|
profil=self.profil)
|
|
if self.user_cache is None:
|
|
self.request.record('echec-de-connexion',
|
|
_(u'tentative de connexion échouée avec l\'email {email}: identifiants erronées'),
|
|
email=identifiant)
|
|
raise forms.ValidationError(
|
|
self.error_messages['identifiants_invalides'])
|
|
elif not self.user_cache.is_active:
|
|
self.request.record('echec-de-connexion',
|
|
_(u'tentative de connexion échouée avec l\'email {email}: ce compte est bloqué'),
|
|
email=identifiant)
|
|
raise forms.ValidationError(self.error_messages['inactif'])
|
|
return cleaned_data
|
|
|
|
def get_user_id(self):
|
|
if self.user_cache:
|
|
return self.user_cache.id
|
|
return None
|
|
|
|
def get_user(self):
|
|
return self.user_cache
|
|
|
|
class CodeDeConfirmationForm(forms.Form):
|
|
jeton = forms.CharField(label=_(u'Code de confirmation'))
|
|
|
|
helper = FormHelper()
|
|
helper.form_class = 'form-horizontal'
|
|
helper.layout = Layout(
|
|
'jeton',
|
|
ButtonHolder(
|
|
Submit('submit', _(u"Je confirme mon email avec ce code"))))
|
|
|
|
def clean_jeton(self):
|
|
'''Remove extra characters'''
|
|
jeton = self.cleaned_data['jeton']
|
|
jeton = ''.join([letter for letter in jeton if letter in JETON_CHARS])
|
|
return jeton
|
|
|
|
def clean(self):
|
|
cleaned_data = super(CodeDeConfirmationForm, self).clean()
|
|
jeton = 'inscription_%s' % cleaned_data.get('jeton', '').upper()
|
|
self.value = cache.get(jeton)
|
|
if self.value is None:
|
|
raise forms.ValidationError(_(u'Ce jeton est invalide ou expiré,'
|
|
u' veuillez-en demander un nouveau en vous réinscrivant'
|
|
u' ou en refaisant une demande de mot de passe perdu'))
|
|
email = self.value['email']
|
|
if 'change_email' in self.value or 'creation' in self.value:
|
|
if models.ProfilOffre.objects.filter(email=email).exists():
|
|
raise forms.ValidationError(_(u"L'email %s est déjà utilisé "
|
|
u"par un autre compte. Veuillez demander un changement vers une adresse différente.")
|
|
% email)
|
|
|
|
if 'creation' in self.value:
|
|
kwargs = {
|
|
'email': email,
|
|
}
|
|
for key in ('first_name', 'last_name', 'address', 'job', 'phone', 'mobile', 'comment'):
|
|
kwargs[key] = self.value.get(key, '')
|
|
self.user_cache, self.created = models.ProfilOffre.objects.get_or_create(**kwargs)
|
|
self.user_cache.backend = 'appli_project.appli_socle.backends.ProfilOffreMoteurAuthentification'
|
|
if self.created:
|
|
self.user_cache.set_unusable_password()
|
|
else:
|
|
user_id = self.value['user_id']
|
|
try:
|
|
self.user_cache = models.ProfilOffre.objects.get(id=user_id)
|
|
self.user_cache.backend = 'appli_project.appli_socle.backends.ProfilOffreMoteurAuthentification'
|
|
except models.ProfilOffre.DoesNotExist:
|
|
try:
|
|
self.user_cache = User.objects.get(id=user_id)
|
|
self.user_cache.backend = 'django.contrib.auth.backends.ModelBackend'
|
|
except User.DoesNotExist:
|
|
raise forms.ValidationError(_(u'Ce jeton est invalide ou expiré,'
|
|
u' veuillez-en demander un nouveau en vous réinscrivant'
|
|
u' ou en refaisant une demande de mot de passe perdu'))
|
|
self.next_url = self.value['next']
|
|
return cleaned_data
|
|
|
|
@transaction.commit_on_success
|
|
def save(self):
|
|
if 'change_email' in self.value:
|
|
if models.ProfilOffre.objects.filter(email=self.value['email']).exists():
|
|
return
|
|
self.user_cache.email = self.value['email']
|
|
self.user_cache.save()
|
|
|
|
class MotDePassePerduForm(FormulaireAvecRelaie):
|
|
email = forms.EmailField(label=_(u'Votre email'))
|
|
|
|
helper = FormHelper()
|
|
helper.form_class = 'form-horizontal'
|
|
helper.layout = Layout(
|
|
'email',
|
|
'next',
|
|
ButtonHolder(
|
|
Submit('submit', _(u"Récupérer mon mot de passe"))))
|
|
|
|
def clean(self):
|
|
cleaned_data = super(MotDePassePerduForm, self).clean()
|
|
self.users_cache = [user for user in
|
|
User.objects.filter(email=cleaned_data.get('email', u''),
|
|
utilisateur_cas__isnull=True).distinct()]
|
|
return cleaned_data
|
|
|
|
def save(self):
|
|
modele, created = models.EmailModele.objects.get_or_create(nom='Mot de passe perdu')
|
|
if created:
|
|
modele.sujet = u'''Confirmation de votre email pour récupération de votre mot de passe sur {{host}}'''
|
|
modele.texte = u'''Vous avez déclaré avoir perdu le mot de passe pour le compte lié à l'e-mail {{ email }} sur {{ host }}.
|
|
|
|
Il est nécessaire de confirmer cet email pour pouvoir récupérer votre mot de passe.
|
|
Pour cela veuillez entrez le code {{ jeton }} sur la page Web suivante:
|
|
|
|
{{ confirmation_url }}
|
|
|
|
ou bien recopier le lien suivant dans la barre d'adresse de votre navigateur:
|
|
|
|
{{ jeton_url }}'''
|
|
modele.save()
|
|
for user in self.users_cache:
|
|
try:
|
|
user.profiloffre
|
|
url = '/offre'
|
|
except models.ProfilOffre.DoesNotExist:
|
|
url = '/admin'
|
|
url = ('%s?%s' % (reverse('changement-mot-de-passe'),
|
|
urlencode(dict(next=url))))
|
|
jeton = ''.join([random.SystemRandom().choice('123456789ABCDEFGHJKMNPQRSTUVWXYZ')
|
|
for i in range(8)])
|
|
jeton_url = self.request.build_absolute_uri(
|
|
reverse('confirmation-email', kwargs=dict(jeton=jeton)))
|
|
confirmation_url = self.request.build_absolute_uri(
|
|
reverse('confirmation-email', kwargs=dict(jeton='')))
|
|
host = self.request.get_host()
|
|
cache.set('inscription_%s' % jeton, { 'email': user.email, 'next': url,
|
|
'user_id': user.id },
|
|
settings.CHANGE_MAIL_TIMEOUT)
|
|
variables = Context({ 'email' : user.email,
|
|
'jeton': jeton, 'jeton_url': jeton_url, 'host': host,
|
|
'confirmation_url': confirmation_url})
|
|
subject = Template(modele.sujet).render(variables)
|
|
body = Template(modele.texte).render(variables)
|
|
send_mail(subject, body, settings.INSCRIPTION_FROM_EMAIL, [user.email])
|
|
yield user, jeton
|
|
|
|
class ChangementMotDePasseForm(SetPasswordForm, FormulaireAvecRelaie):
|
|
helper = FormHelper()
|
|
helper.form_class = 'form-horizontal'
|
|
helper.layout = Layout(
|
|
'new_password1',
|
|
'new_password2',
|
|
'next',
|
|
ButtonHolder(
|
|
Submit('submit', _(u"Changer mon mot de passe"), css_class="btn-primary btn")))
|
|
|
|
class InvalidationForm(forms.Form):
|
|
raison = forms.CharField(label=u'')
|
|
helper = FormHelper()
|
|
helper.form_class = 'form-inline'
|
|
helper.form_action = 'invalider'
|
|
helper.layout = Layout(
|
|
'raison',
|
|
Submit('submit', _(u'Invalider')))
|
|
|
|
class ValidationForm(forms.Form):
|
|
raison = forms.CharField(label=u'')
|
|
helper = FormHelper()
|
|
helper.form_class = 'form-inline'
|
|
helper.form_action = 'valider'
|
|
helper.layout = Layout(
|
|
Submit('submit', _(u'Valider')))
|
|
|
|
class FormulaireNotifications(forms.Form):
|
|
sujet = forms.CharField(max_length=300, required=True)
|
|
text = forms.CharField(required=True, widget=forms.Textarea)
|
|
|
|
helper = FormHelper()
|
|
helper.form_class = 'form-horizontal'
|
|
helper.layout = Layout(
|
|
Field('sujet', style='width: 50%'),
|
|
Field('text', style='width: 50%'),
|
|
Submit('submit', _(u'Envoyer')))
|
|
|
|
def save(self):
|
|
qs = models.ProfilOffre.objects.all()
|
|
qs = qs.filter(accepte_notif_alertes=True)
|
|
emails = qs.values_list('email', flat=True)
|
|
for emails_slice in iter_slice(emails, 100):
|
|
EmailMessage(subject=self.cleaned_data['sujet'],
|
|
body=self.cleaned_data['text'],
|
|
to=[], bcc=list(emails_slice)).send()
|
|
return qs.count()
|
|
|
|
class InjectionProfilRechercheForm(Form):
|
|
fichier = forms.FileField(label=_(u'Fichier texte contenant la liste des comptes'), required=False)
|
|
type_d_offre = forms.ModelMultipleChoiceField(label='',
|
|
queryset=models.TypeOffre.objects.all(), widget=FilteredSelectMultiple(_(u"Types d'offre"),
|
|
False))
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.request = kwargs.pop('request', None)
|
|
super(InjectionProfilRechercheForm, self).__init__(*args, **kwargs)
|
|
|
|
def clean_fichier(self):
|
|
fichier = self.cleaned_data['fichier']
|
|
if fichier is None:
|
|
raise forms.ValidationError(u"Le fichier est obligatoire.")
|
|
import backends
|
|
comptes = filter(str.strip, fichier.read().splitlines())
|
|
comptes = filter(None, comptes)
|
|
try:
|
|
for ligne, compte in enumerate(comptes, 1):
|
|
unicode(compte, 'ascii')
|
|
except UnicodeDecodeError:
|
|
raise forms.ValidationError(u"Le fichier contient des caractères "
|
|
u"non-ASCII à la ligne %i, veuillez le corriger." % ligne)
|
|
backend = backends.ProfilRechercheAuthentification()
|
|
errors = []
|
|
ldap_resultats = []
|
|
for compte in comptes:
|
|
ldap_resultat = backend.recherche_utilisateur(compte)
|
|
attributes = ldap_resultat[0][1]
|
|
if ProfilRechercheAuthentification.MAIL in attributes:
|
|
pass
|
|
elif ProfilRechercheAuthentification.SUPANN_MAIL_PERSO in attributes:
|
|
pass
|
|
else:
|
|
raise forms.ValidationError(_(u"L'utilisateur %s n'a pas d'email.") % compte)
|
|
if not ldap_resultat:
|
|
errors.append(compte)
|
|
else:
|
|
ldap_resultats.append(ldap_resultat)
|
|
if errors:
|
|
msg = _(u"Les comptes suivants ne sont par présents dans le "
|
|
u'LDAP: %s') % \
|
|
u''.join(map(lambda s: "<p>%s</p>" % s, errors))
|
|
raise forms.ValidationError(mark_safe(msg))
|
|
fichier.seek(0)
|
|
return zip(comptes, ldap_resultats)
|
|
|
|
def clean_type_d_offre(self):
|
|
type_d_offre = self.cleaned_data['type_d_offre']
|
|
type_d_offre = type_d_offre | models.TypeOffre.objects.filter(id=1)
|
|
return type_d_offre
|
|
|
|
def save(self):
|
|
comptes = self.cleaned_data['fichier']
|
|
import backends
|
|
backend = backends.ProfilRechercheAuthentification()
|
|
i = 0
|
|
self.created = []
|
|
for compte, ldap_resultat in comptes:
|
|
try:
|
|
user = models.ProfilRecherche.objects.get(
|
|
utilisateur_cas__identifiant=compte)
|
|
except models.ProfilRecherche.DoesNotExist:
|
|
self.created.append(compte)
|
|
user = backend.creation_du_compte(compte, 'dauphine', ldap_resultat)
|
|
self.request.record('nouveau-compte-recherche-import',
|
|
'nouveau compte CAS {cas_user}',
|
|
cas_user=compte, new_user=user)
|
|
i += 1
|
|
user.type_d_offre = self.cleaned_data['type_d_offre']
|
|
user.save()
|
|
return i, len(comptes)-i
|
|
|
|
|
|
|