This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
dauphine-logement/appli_project/appli_socle/forms.py

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