# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # ############################################################################## import re import requests from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileAllowed, FileRequired from markupsafe import Markup from sqlalchemy import text from wtforms import ( StringField, SubmitField, TextAreaField, SelectField, HiddenField, SelectMultipleField, DateField, BooleanField, FieldList, FormField, ) from wtforms.validators import ValidationError, DataRequired, Email, Optional from wtforms.widgets import ListWidget, CheckboxInput from app.entreprises.models import ( Entreprise, EntrepriseCorrespondant, EntreprisePreferences, ) from app.models import Identite, Departement from app.auth.models import User CHAMP_REQUIS = "Ce champ est requis" SUBMIT_MARGE = {"style": "margin-bottom: 10px;"} def _build_string_field(label, required=True, render_kw=None): if required: return StringField( label, validators=[DataRequired(message=CHAMP_REQUIS)], render_kw=render_kw, ) else: return StringField(label, validators=[Optional()], render_kw=render_kw) class EntrepriseCreationForm(FlaskForm): siret = _build_string_field( "SIRET (*)", render_kw={"placeholder": "Numéro composé de 14 chiffres"}, ) nom_entreprise = _build_string_field("Nom de l'entreprise (*)") adresse = _build_string_field("Adresse de l'entreprise (*)") codepostal = _build_string_field("Code postal de l'entreprise (*)") ville = _build_string_field("Ville de l'entreprise (*)") pays = _build_string_field("Pays de l'entreprise", required=False) civilite = SelectField( "Civilité du correspondant", choices=[("M", "Monsieur"), ("F", "Madame")], validators=[DataRequired(message=CHAMP_REQUIS)], ) nom_correspondant = _build_string_field("Nom du correspondant", required=False) prenom_correspondant = _build_string_field( "Prénom du correspondant", required=False ) telephone = _build_string_field("Téléphone du correspondant", required=False) mail = StringField( "Mail du correspondant", validators=[Optional(), Email(message="Adresse e-mail invalide")], ) poste = _build_string_field("Poste du correspondant", required=False) service = _build_string_field("Service du correspondant", required=False) origine = _build_string_field("Origine du correspondant", required=False) notes = _build_string_field("Notes sur le correspondant", required=False) submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) def validate(self): validate = True if not FlaskForm.validate(self): validate = False if ( self.nom_correspondant.data.strip() or self.prenom_correspondant.data.strip() or self.telephone.data.strip() or self.mail.data.strip() or self.poste.data.strip() or self.service.data.strip() or self.origine.data.strip() or self.notes.data.strip() ): if not self.nom_correspondant.data.strip(): self.nom_correspondant.errors.append("Ce champ est requis") validate = False if not self.prenom_correspondant.data.strip(): self.prenom_correspondant.errors.append("Ce champ est requis") validate = False if not self.telephone.data.strip() and not self.mail.data.strip(): msg = "Saisir un moyen de contact (mail ou téléphone)" self.telephone.errors.append(msg) self.mail.errors.append(msg) validate = False return validate def validate_siret(self, siret): if EntreprisePreferences.get_check_siret(): siret_data = siret.data.strip().replace(" ", "") self.siret.data = siret_data if re.match("^\d{14}$", siret_data) is None: raise ValidationError("Format incorrect") try: req = requests.get( f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret_data}" ) if req.status_code != 200: raise ValidationError("SIRET inexistant") except requests.ConnectionError: raise ValidationError("Impossible de vérifier l'existance du SIRET") entreprise = Entreprise.query.filter_by(siret=siret_data).first() if entreprise is not None: lien = f'<a href="/ScoDoc/entreprises/fiche_entreprise/{entreprise.id}">ici</a>' raise ValidationError( Markup(f"Entreprise déjà présent, lien vers la fiche : {lien}") ) class EntrepriseModificationForm(FlaskForm): hidden_entreprise_siret = HiddenField() siret = StringField("SIRET (*)") nom = _build_string_field("Nom de l'entreprise (*)") adresse = _build_string_field("Adresse (*)") codepostal = _build_string_field("Code postal (*)") ville = _build_string_field("Ville (*)") pays = _build_string_field("Pays", required=False) submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.siret.render_kw = { "disabled": "", "value": self.hidden_entreprise_siret.data, } class SiteCreationForm(FlaskForm): nom = _build_string_field("Nom du site (*)") adresse = _build_string_field("Adresse (*)") codepostal = _build_string_field("Code postal (*)") ville = _build_string_field("Ville (*)") pays = _build_string_field("Pays", required=False) submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) class MultiCheckboxField(SelectMultipleField): widget = ListWidget(prefix_label=False) option_widget = CheckboxInput() class OffreCreationForm(FlaskForm): hidden_entreprise_id = HiddenField() intitule = _build_string_field("Intitulé (*)") description = TextAreaField( "Description (*)", validators=[DataRequired(message=CHAMP_REQUIS)] ) type_offre = SelectField( "Type de l'offre (*)", choices=[("Stage"), ("Alternance")], validators=[DataRequired(message=CHAMP_REQUIS)], ) missions = TextAreaField( "Missions (*)", validators=[DataRequired(message=CHAMP_REQUIS)] ) duree = _build_string_field("Durée (*)") depts = MultiCheckboxField("Départements (*)", validators=[Optional()], coerce=int) expiration_date = DateField("Date expiration", validators=[Optional()]) correspondant = SelectField("Correspondant à contacté", validators=[Optional()]) fichier = FileField( "Fichier", validators=[ Optional(), FileAllowed(["pdf", "docx"], "Fichier .pdf ou .docx uniquement"), ], ) submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.correspondant.choices = [ (correspondant.id, f"{correspondant.nom} {correspondant.prenom}") for correspondant in EntrepriseCorrespondant.query.filter_by( entreprise_id=self.hidden_entreprise_id.data ) ] self.depts.choices = [ (dept.id, dept.acronym) for dept in Departement.query.all() ] def validate(self): validate = True if not FlaskForm.validate(self): validate = False if len(self.depts.data) < 1: self.depts.errors.append("Choisir au moins un département") validate = False return validate class OffreModificationForm(FlaskForm): hidden_entreprise_id = HiddenField() intitule = _build_string_field("Intitulé (*)") description = TextAreaField( "Description (*)", validators=[DataRequired(message=CHAMP_REQUIS)] ) type_offre = SelectField( "Type de l'offre (*)", choices=[("Stage"), ("Alternance")], validators=[DataRequired(message=CHAMP_REQUIS)], ) missions = TextAreaField( "Missions (*)", validators=[DataRequired(message=CHAMP_REQUIS)] ) duree = _build_string_field("Durée (*)") depts = MultiCheckboxField("Départements (*)", validators=[Optional()], coerce=int) expiration_date = DateField("Date expiration", validators=[Optional()]) correspondant = SelectField("Correspondant à contacté", validators=[Optional()]) submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.correspondant.choices = [ (correspondant.id, f"{correspondant.nom} {correspondant.prenom}") for correspondant in EntrepriseCorrespondant.query.filter_by( entreprise_id=self.hidden_entreprise_id.data ) ] self.depts.choices = [ (dept.id, dept.acronym) for dept in Departement.query.all() ] def validate(self): validate = True if not FlaskForm.validate(self): validate = False if len(self.depts.data) < 1: self.depts.errors.append("Choisir au moins un département") validate = False return validate class CorrespondantCreationForm(FlaskForm): civilite = SelectField( "Civilité (*)", choices=[("H", "Monsieur"), ("F", "Madame")], validators=[DataRequired(message=CHAMP_REQUIS)], render_kw={"class": "form-control"}, ) nom = _build_string_field("Nom (*)", render_kw={"class": "form-control"}) prenom = _build_string_field("Prénom (*)", render_kw={"class": "form-control"}) telephone = _build_string_field( "Téléphone (*)", required=False, render_kw={"class": "form-control"} ) mail = StringField( "Mail (*)", validators=[Optional(), Email(message="Adresse e-mail invalide")], render_kw={"class": "form-control"}, ) poste = _build_string_field( "Poste", required=False, render_kw={"class": "form-control"} ) service = _build_string_field( "Service", required=False, render_kw={"class": "form-control"} ) origine = _build_string_field( "Origine", required=False, render_kw={"class": "form-control"} ) notes = _build_string_field( "Notes", required=False, render_kw={"class": "form-control"} ) def validate(self): validate = True if not FlaskForm.validate(self): validate = False if not self.telephone.data and not self.mail.data: msg = "Saisir un moyen de contact (mail ou téléphone)" self.telephone.errors.append(msg) self.mail.errors.append(msg) validate = False return validate class CorrespondantsCreationForm(FlaskForm): hidden_entreprise_id = HiddenField() correspondants = FieldList(FormField(CorrespondantCreationForm), min_entries=1) submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) def validate(self): validate = True if not FlaskForm.validate(self): validate = False correspondant_list = [] for entry in self.correspondants.entries: if entry.nom.data and entry.prenom.data: if ( entry.nom.data.strip(), entry.prenom.data.strip(), ) in correspondant_list: entry.nom.errors.append( "Vous avez saisi 2 fois le même nom et prenom" ) entry.prenom.errors.append("") validate = False correspondant_list.append( (entry.nom.data.strip(), entry.prenom.data.strip()) ) correspondant = EntrepriseCorrespondant.query.filter_by( entreprise_id=self.hidden_entreprise_id.data, nom=entry.nom.data, prenom=entry.prenom.data, ).first() if correspondant is not None: entry.nom.errors.append( "Ce correspondant existe déjà (même nom et prénom)" ) entry.prenom.errors.append("") validate = False return validate class CorrespondantModificationForm(FlaskForm): hidden_correspondant_id = HiddenField() hidden_entreprise_id = HiddenField() civilite = SelectField( "Civilité (*)", choices=[("H", "Monsieur"), ("F", "Madame")], validators=[DataRequired(message=CHAMP_REQUIS)], ) nom = _build_string_field("Nom (*)") prenom = _build_string_field("Prénom (*)") telephone = _build_string_field("Téléphone (*)", required=False) mail = StringField( "Mail (*)", validators=[Optional(), Email(message="Adresse e-mail invalide")], ) poste = _build_string_field("Poste", required=False) service = _build_string_field("Service", required=False) origine = _build_string_field("Origine", required=False) notes = _build_string_field("Notes", required=False) submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE) def validate(self): validate = True if not FlaskForm.validate(self): validate = False correspondant = EntrepriseCorrespondant.query.filter( EntrepriseCorrespondant.id != self.hidden_correspondant_id.data, EntrepriseCorrespondant.entreprise_id == self.hidden_entreprise_id.data, EntrepriseCorrespondant.nom == self.nom.data, EntrepriseCorrespondant.prenom == self.prenom.data, ).first() if correspondant is not None: self.nom.errors.append("Ce correspondant existe déjà (même nom et prénom)") self.prenom.errors.append("") validate = False if not self.telephone.data and not self.mail.data: msg = "Saisir un moyen de contact (mail ou téléphone)" self.telephone.errors.append(msg) self.mail.errors.append(msg) validate = False return validate class ContactCreationForm(FlaskForm): date = _build_string_field( "Date (*)", render_kw={"type": "datetime-local"}, ) utilisateur = _build_string_field( "Utilisateur (*)", render_kw={"placeholder": "Tapez le nom de l'utilisateur"}, ) notes = TextAreaField("Notes (*)", validators=[DataRequired(message=CHAMP_REQUIS)]) submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) def validate_utilisateur(self, utilisateur): utilisateur_data = self.utilisateur.data.upper().strip() stm = text( "SELECT id, UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')')) FROM \"user\" WHERE UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')'))=:utilisateur_data" ) utilisateur = ( User.query.from_statement(stm) .params(utilisateur_data=utilisateur_data) .first() ) if utilisateur is None: raise ValidationError("Champ incorrect (selectionnez dans la liste)") class ContactModificationForm(FlaskForm): date = _build_string_field( "Date (*)", render_kw={"type": "datetime-local"}, ) utilisateur = _build_string_field( "Utilisateur (*)", render_kw={"placeholder": "Tapez le nom de l'utilisateur"}, ) notes = TextAreaField("Notes (*)", validators=[DataRequired(message=CHAMP_REQUIS)]) submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE) def validate_utilisateur(self, utilisateur): utilisateur_data = self.utilisateur.data.upper().strip() stm = text( "SELECT id, UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')')) FROM \"user\" WHERE UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')'))=:utilisateur_data" ) utilisateur = ( User.query.from_statement(stm) .params(utilisateur_data=utilisateur_data) .first() ) if utilisateur is None: raise ValidationError("Champ incorrect (selectionnez dans la liste)") class StageApprentissageCreationForm(FlaskForm): etudiant = _build_string_field( "Étudiant (*)", render_kw={"placeholder": "Tapez le nom de l'étudiant"}, ) type_offre = SelectField( "Type de l'offre (*)", choices=[("Stage"), ("Alternance")], validators=[DataRequired(message=CHAMP_REQUIS)], ) date_debut = DateField( "Date début (*)", validators=[DataRequired(message=CHAMP_REQUIS)] ) date_fin = DateField( "Date fin (*)", validators=[DataRequired(message=CHAMP_REQUIS)] ) notes = TextAreaField("Notes") submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) def validate(self): validate = True if not FlaskForm.validate(self): validate = False if ( self.date_debut.data and self.date_fin.data and self.date_debut.data > self.date_fin.data ): self.date_debut.errors.append("Les dates sont incompatibles") self.date_fin.errors.append("Les dates sont incompatibles") validate = False return validate def validate_etudiant(self, etudiant): etudiant_data = etudiant.data.upper().strip() stm = text( "SELECT id, CONCAT(nom, ' ', prenom) as nom_prenom FROM Identite WHERE CONCAT(nom, ' ', prenom)=:nom_prenom" ) etudiant = ( Identite.query.from_statement(stm).params(nom_prenom=etudiant_data).first() ) if etudiant is None: raise ValidationError("Champ incorrect (selectionnez dans la liste)") class StageApprentissageModificationForm(FlaskForm): etudiant = _build_string_field( "Étudiant (*)", render_kw={"placeholder": "Tapez le nom de l'étudiant"}, ) type_offre = SelectField( "Type de l'offre (*)", choices=[("Stage"), ("Alternance")], validators=[DataRequired(message=CHAMP_REQUIS)], ) date_debut = DateField( "Date début (*)", validators=[DataRequired(message=CHAMP_REQUIS)] ) date_fin = DateField( "Date fin (*)", validators=[DataRequired(message=CHAMP_REQUIS)] ) notes = TextAreaField("Notes") submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE) def validate(self): validate = True if not FlaskForm.validate(self): validate = False if ( self.date_debut.data and self.date_fin.data and self.date_debut.data > self.date_fin.data ): self.date_debut.errors.append("Les dates sont incompatibles") self.date_fin.errors.append("Les dates sont incompatibles") validate = False return validate def validate_etudiant(self, etudiant): etudiant_data = etudiant.data.upper().strip() stm = text( "SELECT id, CONCAT(nom, ' ', prenom) as nom_prenom FROM Identite WHERE CONCAT(nom, ' ', prenom)=:nom_prenom" ) etudiant = ( Identite.query.from_statement(stm).params(nom_prenom=etudiant_data).first() ) if etudiant is None: raise ValidationError("Champ incorrect (selectionnez dans la liste)") class EnvoiOffreForm(FlaskForm): responsables = FieldList( _build_string_field( "Responsable (*)", render_kw={ "placeholder": "Tapez le nom du responsable de formation", "class": "form-control", }, ), min_entries=1, ) submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE) def validate(self): validate = True if not FlaskForm.validate(self): validate = False for entry in self.responsables.entries: if entry.data: responsable_data = entry.data.upper().strip() stm = text( "SELECT id, UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')')) FROM \"user\" WHERE UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')'))=:responsable_data" ) responsable = ( User.query.from_statement(stm) .params(responsable_data=responsable_data) .first() ) if responsable is None: entry.errors.append("Champ incorrect (selectionnez dans la liste)") validate = False return validate class AjoutFichierForm(FlaskForm): fichier = FileField( "Fichier (*)", validators=[ FileRequired(message=CHAMP_REQUIS), FileAllowed(["pdf", "docx"], "Fichier .pdf ou .docx uniquement"), ], ) submit = SubmitField("Ajouter", render_kw=SUBMIT_MARGE) class SuppressionConfirmationForm(FlaskForm): submit = SubmitField("Supprimer", render_kw=SUBMIT_MARGE) class DesactivationConfirmationForm(FlaskForm): notes_active = TextAreaField("Notes sur la désactivation", validators=[Optional()]) submit = SubmitField("Désactiver", render_kw=SUBMIT_MARGE) class ValidationConfirmationForm(FlaskForm): submit = SubmitField("Valider", render_kw=SUBMIT_MARGE) class ImportForm(FlaskForm): fichier = FileField( "Fichier (*)", validators=[ FileRequired(message=CHAMP_REQUIS), FileAllowed(["xlsx"], "Fichier .xlsx uniquement"), ], ) submit = SubmitField("Importer", render_kw=SUBMIT_MARGE) class PreferencesForm(FlaskForm): mail_entreprise = StringField( "Mail notifications", validators=[Optional(), Email(message="Adresse e-mail invalide")], ) check_siret = BooleanField("Vérification SIRET") submit = SubmitField("Valider", render_kw=SUBMIT_MARGE)