forked from ScoDoc/ScoDoc
- corrige saisi stage sur entreprise (fix #642)
- clé étrangère sur Identite dans EntrepriseStageApprentissage - nouveau mécanisme pour le choix d'étudiant via auto-completion (ajout de autoComplete.js-10.2.7) - nouveau point d'API: /etudiants/name/<string:start> (et son test unitaire)
This commit is contained in:
parent
111c400333
commit
8e1cb055f6
@ -8,16 +8,17 @@
|
|||||||
API : accès aux étudiants
|
API : accès aux étudiants
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from operator import attrgetter
|
||||||
|
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
from sqlalchemy import desc, or_
|
from sqlalchemy import desc, func, or_
|
||||||
|
from sqlalchemy.dialects.postgresql import VARCHAR
|
||||||
|
|
||||||
import app
|
import app
|
||||||
from app.api import api_bp as bp, api_web_bp
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.scodoc.sco_utils import json_error
|
|
||||||
from app.api import tools
|
from app.api import tools
|
||||||
from app.decorators import scodoc, permission_required
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import (
|
from app.models import (
|
||||||
@ -31,6 +32,8 @@ from app.scodoc import sco_bulletins
|
|||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud
|
from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
from app.scodoc.sco_utils import json_error, suppress_accents
|
||||||
|
|
||||||
|
|
||||||
# Un exemple:
|
# Un exemple:
|
||||||
# @bp.route("/api_function/<int:arg>")
|
# @bp.route("/api_function/<int:arg>")
|
||||||
@ -164,12 +167,39 @@ def etudiants(etudid: int = None, nip: str = None, ine: str = None):
|
|||||||
)
|
)
|
||||||
if not None in allowed_depts:
|
if not None in allowed_depts:
|
||||||
# restreint aux départements autorisés:
|
# restreint aux départements autorisés:
|
||||||
etuds = etuds.join(Departement).filter(
|
query = query.join(Departement).filter(
|
||||||
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
||||||
)
|
)
|
||||||
return [etud.to_dict_api() for etud in query]
|
return [etud.to_dict_api() for etud in query]
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/etudiants/name/<string:start>")
|
||||||
|
@api_web_bp.route("/etudiants/name/<string:start>")
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
@as_json
|
||||||
|
def etudiants_by_name(start: str = "", min_len=3, limit=32):
|
||||||
|
"""Liste des étudiants dont le nom débute par start.
|
||||||
|
Si start fait moins de min_len=3 caractères, liste vide.
|
||||||
|
La casse et les accents sont ignorés.
|
||||||
|
"""
|
||||||
|
if len(start) < min_len:
|
||||||
|
return []
|
||||||
|
start = suppress_accents(start).lower()
|
||||||
|
query = Identite.query.filter(
|
||||||
|
func.lower(func.unaccent(Identite.nom, type_=VARCHAR)).ilike(start + "%")
|
||||||
|
)
|
||||||
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
||||||
|
if not None in allowed_depts:
|
||||||
|
# restreint aux départements autorisés:
|
||||||
|
query = query.join(Departement).filter(
|
||||||
|
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
||||||
|
)
|
||||||
|
etuds = query.order_by(Identite.nom, Identite.prenom).limit(limit)
|
||||||
|
# Note: on raffine le tri pour les caractères spéciaux et nom usuel ici:
|
||||||
|
return [etud.to_dict_api() for etud in sorted(etuds, key=attrgetter("sort_key"))]
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
||||||
@bp.route("/etudiant/nip/<string:nip>/formsemestres")
|
@bp.route("/etudiant/nip/<string:nip>/formsemestres")
|
||||||
@bp.route("/etudiant/ine/<string:ine>/formsemestres")
|
@bp.route("/etudiant/ine/<string:ine>/formsemestres")
|
||||||
|
@ -122,7 +122,7 @@ class EntrepriseCreationForm(FlaskForm):
|
|||||||
origine = _build_string_field("Origine du correspondant", required=False)
|
origine = _build_string_field("Origine du correspondant", required=False)
|
||||||
notes = _build_string_field("Notes sur le correspondant", required=False)
|
notes = _build_string_field("Notes sur le correspondant", required=False)
|
||||||
|
|
||||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@ -248,7 +248,7 @@ class SiteCreationForm(FlaskForm):
|
|||||||
codepostal = _build_string_field("Code postal (*)")
|
codepostal = _build_string_field("Code postal (*)")
|
||||||
ville = _build_string_field("Ville (*)")
|
ville = _build_string_field("Ville (*)")
|
||||||
pays = _build_string_field("Pays", required=False)
|
pays = _build_string_field("Pays", required=False)
|
||||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@ -326,7 +326,7 @@ class OffreCreationForm(FlaskForm):
|
|||||||
FileAllowed(["pdf", "docx"], "Fichier .pdf ou .docx uniquement"),
|
FileAllowed(["pdf", "docx"], "Fichier .pdf ou .docx uniquement"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -458,7 +458,7 @@ class CorrespondantCreationForm(FlaskForm):
|
|||||||
class CorrespondantsCreationForm(FlaskForm):
|
class CorrespondantsCreationForm(FlaskForm):
|
||||||
hidden_site_id = HiddenField()
|
hidden_site_id = HiddenField()
|
||||||
correspondants = FieldList(FormField(CorrespondantCreationForm), min_entries=1)
|
correspondants = FieldList(FormField(CorrespondantCreationForm), min_entries=1)
|
||||||
submit = SubmitField("Envoyer")
|
submit = SubmitField("Enregistrer")
|
||||||
cancel = SubmitField("Annuler")
|
cancel = SubmitField("Annuler")
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@ -566,7 +566,7 @@ class ContactCreationForm(FlaskForm):
|
|||||||
render_kw={"placeholder": "Tapez le nom de l'utilisateur"},
|
render_kw={"placeholder": "Tapez le nom de l'utilisateur"},
|
||||||
)
|
)
|
||||||
notes = TextAreaField("Notes (*)", validators=[DataRequired(message=CHAMP_REQUIS)])
|
notes = TextAreaField("Notes (*)", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||||
|
|
||||||
def validate_utilisateur(self, utilisateur):
|
def validate_utilisateur(self, utilisateur):
|
||||||
@ -613,8 +613,9 @@ class ContactModificationForm(FlaskForm):
|
|||||||
class StageApprentissageCreationForm(FlaskForm):
|
class StageApprentissageCreationForm(FlaskForm):
|
||||||
etudiant = _build_string_field(
|
etudiant = _build_string_field(
|
||||||
"Étudiant (*)",
|
"Étudiant (*)",
|
||||||
render_kw={"placeholder": "Tapez le nom de l'étudiant"},
|
render_kw={"placeholder": "Tapez le nom de l'étudiant", "autocomplete": "off"},
|
||||||
)
|
)
|
||||||
|
etudid = HiddenField()
|
||||||
type_offre = SelectField(
|
type_offre = SelectField(
|
||||||
"Type de l'offre (*)",
|
"Type de l'offre (*)",
|
||||||
choices=[("Stage"), ("Alternance")],
|
choices=[("Stage"), ("Alternance")],
|
||||||
@ -627,12 +628,12 @@ class StageApprentissageCreationForm(FlaskForm):
|
|||||||
"Date fin (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
|
"Date fin (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||||
)
|
)
|
||||||
notes = TextAreaField("Notes")
|
notes = TextAreaField("Notes")
|
||||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self, extra_validators=None):
|
||||||
validate = True
|
validate = True
|
||||||
if not FlaskForm.validate(self):
|
if not super().validate(extra_validators):
|
||||||
validate = False
|
validate = False
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -646,64 +647,12 @@ class StageApprentissageCreationForm(FlaskForm):
|
|||||||
|
|
||||||
return validate
|
return validate
|
||||||
|
|
||||||
def validate_etudiant(self, etudiant):
|
def validate_etudid(self, field):
|
||||||
etudiant_data = etudiant.data.upper().strip()
|
"L'etudid doit avoit été placé par le JS"
|
||||||
stm = text(
|
etudid = int(field.data) if field.data else None
|
||||||
"SELECT id, CONCAT(nom, ' ', prenom) as nom_prenom FROM Identite WHERE CONCAT(nom, ' ', prenom)=:nom_prenom"
|
etudiant = Identite.query.get(etudid) if etudid is not None else None
|
||||||
)
|
|
||||||
etudiant = (
|
|
||||||
Identite.query.from_statement(stm).params(nom_prenom=etudiant_data).first()
|
|
||||||
)
|
|
||||||
if etudiant is None:
|
if etudiant is None:
|
||||||
raise ValidationError("Champ incorrect (selectionnez dans la liste)")
|
raise ValidationError("Étudiant introuvable (sélectionnez 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)
|
|
||||||
cancel = SubmitField("Annuler", 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 TaxeApprentissageForm(FlaskForm):
|
class TaxeApprentissageForm(FlaskForm):
|
||||||
@ -732,7 +681,7 @@ class TaxeApprentissageForm(FlaskForm):
|
|||||||
default=1,
|
default=1,
|
||||||
)
|
)
|
||||||
notes = TextAreaField("Notes")
|
notes = TextAreaField("Notes")
|
||||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
submit = SubmitField("Enregistrer", render_kw=SUBMIT_MARGE)
|
||||||
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
cancel = SubmitField("Annuler", render_kw=SUBMIT_MARGE)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
@ -164,7 +164,10 @@ class EntrepriseStageApprentissage(db.Model):
|
|||||||
entreprise_id = db.Column(
|
entreprise_id = db.Column(
|
||||||
db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
|
db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
|
||||||
)
|
)
|
||||||
etudid = db.Column(db.Integer)
|
etudid = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("identite.id", ondelete="CASCADE"),
|
||||||
|
)
|
||||||
type_offre = db.Column(db.Text)
|
type_offre = db.Column(db.Text)
|
||||||
date_debut = db.Column(db.Date)
|
date_debut = db.Column(db.Date)
|
||||||
date_fin = db.Column(db.Date)
|
date_fin = db.Column(db.Date)
|
||||||
|
@ -28,7 +28,6 @@ from app.entreprises.forms import (
|
|||||||
ContactCreationForm,
|
ContactCreationForm,
|
||||||
ContactModificationForm,
|
ContactModificationForm,
|
||||||
StageApprentissageCreationForm,
|
StageApprentissageCreationForm,
|
||||||
StageApprentissageModificationForm,
|
|
||||||
EnvoiOffreForm,
|
EnvoiOffreForm,
|
||||||
AjoutFichierForm,
|
AjoutFichierForm,
|
||||||
TaxeApprentissageForm,
|
TaxeApprentissageForm,
|
||||||
@ -1473,7 +1472,8 @@ def delete_contact(entreprise_id, contact_id):
|
|||||||
@permission_required(Permission.RelationsEntreprisesChange)
|
@permission_required(Permission.RelationsEntreprisesChange)
|
||||||
def add_stage_apprentissage(entreprise_id):
|
def add_stage_apprentissage(entreprise_id):
|
||||||
"""
|
"""
|
||||||
Permet d'ajouter un étudiant ayant réalisé un stage ou une alternance sur la fiche entreprise de l'entreprise
|
Permet d'ajouter un étudiant ayant réalisé un stage ou alternance
|
||||||
|
sur la fiche de l'entreprise
|
||||||
"""
|
"""
|
||||||
entreprise = Entreprise.query.filter_by(
|
entreprise = Entreprise.query.filter_by(
|
||||||
id=entreprise_id, visible=True
|
id=entreprise_id, visible=True
|
||||||
@ -1484,15 +1484,8 @@ def add_stage_apprentissage(entreprise_id):
|
|||||||
url_for("entreprises.fiche_entreprise", entreprise_id=entreprise_id)
|
url_for("entreprises.fiche_entreprise", entreprise_id=entreprise_id)
|
||||||
)
|
)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
etudiant_nomcomplet = form.etudiant.data.upper().strip()
|
etudid = form.etudid.data
|
||||||
stm = text(
|
etudiant = Identite.query.get_or_404(etudid)
|
||||||
"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_nomcomplet)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
formation = etudiant.inscription_courante_date(
|
formation = etudiant.inscription_courante_date(
|
||||||
form.date_debut.data, form.date_fin.data
|
form.date_debut.data, form.date_fin.data
|
||||||
)
|
)
|
||||||
@ -1538,7 +1531,7 @@ def add_stage_apprentissage(entreprise_id):
|
|||||||
@permission_required(Permission.RelationsEntreprisesChange)
|
@permission_required(Permission.RelationsEntreprisesChange)
|
||||||
def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
|
def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
|
||||||
"""
|
"""
|
||||||
Permet de modifier un étudiant ayant réalisé un stage ou une alternance sur la fiche entreprise de l'entreprise
|
Permet de modifier un étudiant ayant réalisé un stage ou alternance sur la fiche de l'entreprise
|
||||||
"""
|
"""
|
||||||
stage_apprentissage = EntrepriseStageApprentissage.query.filter_by(
|
stage_apprentissage = EntrepriseStageApprentissage.query.filter_by(
|
||||||
id=stage_apprentissage_id, entreprise_id=entreprise_id
|
id=stage_apprentissage_id, entreprise_id=entreprise_id
|
||||||
@ -1548,21 +1541,14 @@ def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
|
|||||||
etudiant = Identite.query.filter_by(id=stage_apprentissage.etudid).first_or_404(
|
etudiant = Identite.query.filter_by(id=stage_apprentissage.etudid).first_or_404(
|
||||||
description=f"etudiant {stage_apprentissage.etudid} inconnue"
|
description=f"etudiant {stage_apprentissage.etudid} inconnue"
|
||||||
)
|
)
|
||||||
form = StageApprentissageModificationForm()
|
form = StageApprentissageCreationForm()
|
||||||
if request.method == "POST" and form.cancel.data:
|
if request.method == "POST" and form.cancel.data:
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for("entreprises.fiche_entreprise", entreprise_id=entreprise_id)
|
url_for("entreprises.fiche_entreprise", entreprise_id=entreprise_id)
|
||||||
)
|
)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
etudiant_nomcomplet = form.etudiant.data.upper().strip()
|
etudid = form.etudid.data
|
||||||
stm = text(
|
etudiant = Identite.query.get_or_404(etudid)
|
||||||
"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_nomcomplet)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
formation = etudiant.inscription_courante_date(
|
formation = etudiant.inscription_courante_date(
|
||||||
form.date_debut.data, form.date_fin.data
|
form.date_debut.data, form.date_fin.data
|
||||||
)
|
)
|
||||||
@ -1577,6 +1563,7 @@ def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
|
|||||||
formation.formsemestre.formsemestre_id if formation else None,
|
formation.formsemestre.formsemestre_id if formation else None,
|
||||||
)
|
)
|
||||||
stage_apprentissage.notes = form.notes.data.strip()
|
stage_apprentissage.notes = form.notes.data.strip()
|
||||||
|
db.session.add(stage_apprentissage)
|
||||||
log = EntrepriseHistorique(
|
log = EntrepriseHistorique(
|
||||||
authenticated_user=current_user.user_name,
|
authenticated_user=current_user.user_name,
|
||||||
entreprise_id=stage_apprentissage.entreprise_id,
|
entreprise_id=stage_apprentissage.entreprise_id,
|
||||||
@ -1593,7 +1580,9 @@ def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif request.method == "GET":
|
elif request.method == "GET":
|
||||||
form.etudiant.data = f"{sco_etud.format_nom(etudiant.nom)} {sco_etud.format_prenom(etudiant.prenom)}"
|
form.etudiant.data = f"""{sco_etud.format_nom(etudiant.nom)} {
|
||||||
|
sco_etud.format_prenom(etudiant.prenom)}"""
|
||||||
|
form.etudid.data = etudiant.id
|
||||||
form.type_offre.data = stage_apprentissage.type_offre
|
form.type_offre.data = stage_apprentissage.type_offre
|
||||||
form.date_debut.data = stage_apprentissage.date_debut
|
form.date_debut.data = stage_apprentissage.date_debut
|
||||||
form.date_fin.data = stage_apprentissage.date_fin
|
form.date_fin.data = stage_apprentissage.date_fin
|
||||||
|
@ -43,8 +43,8 @@ class Identite(db.Model):
|
|||||||
"optionnel (si present, affiché à la place du nom)"
|
"optionnel (si present, affiché à la place du nom)"
|
||||||
civilite = db.Column(db.String(1), nullable=False)
|
civilite = db.Column(db.String(1), nullable=False)
|
||||||
|
|
||||||
# données d'état-civil. Si présent remplace les données d'usage dans les documents officiels (bulletins, PV)
|
# données d'état-civil. Si présent remplace les données d'usage dans les documents
|
||||||
# cf nomprenom_etat_civil()
|
# officiels (bulletins, PV): voir nomprenom_etat_civil()
|
||||||
civilite_etat_civil = db.Column(db.String(1), nullable=False, server_default="X")
|
civilite_etat_civil = db.Column(db.String(1), nullable=False, server_default="X")
|
||||||
prenom_etat_civil = db.Column(db.Text(), nullable=False, server_default="")
|
prenom_etat_civil = db.Column(db.Text(), nullable=False, server_default="")
|
||||||
|
|
||||||
|
@ -3173,6 +3173,19 @@ li.tf-msg {
|
|||||||
/* EMO_WARNING, "⚠️" */
|
/* EMO_WARNING, "⚠️" */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.error {
|
||||||
|
font-weight: bold;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.error::before {
|
||||||
|
content: "\2049 \fe0f";
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.infop {
|
.infop {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
65
app/static/js/etud_autocomplete.js
Normal file
65
app/static/js/etud_autocomplete.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
|
||||||
|
// Mécanisme d'auto-complétion (choix) d'un étudiant
|
||||||
|
// Il faut un champs #etudiant (text input) et à coté un champ hidden etudid qui sera rempli.
|
||||||
|
// utilise autoComplete.js, source https://tarekraafat.github.io/autoComplete.js
|
||||||
|
// EV 2023-06-01
|
||||||
|
|
||||||
|
function etud_autocomplete_config(with_dept = false) {
|
||||||
|
return {
|
||||||
|
selector: "#etudiant",
|
||||||
|
placeHolder: "Nom...",
|
||||||
|
threshold: 3,
|
||||||
|
data: {
|
||||||
|
src: async (query) => {
|
||||||
|
try {
|
||||||
|
// Fetch Data from external Source
|
||||||
|
const source = await fetch(`/ScoDoc/api/etudiants/name/${query}`);
|
||||||
|
// Data should be an array of `Objects` or `Strings`
|
||||||
|
const data = await source.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Data source 'Object' key to be searched
|
||||||
|
keys: ["nom"]
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
input: {
|
||||||
|
selection: (event) => {
|
||||||
|
const prenom = sco_capitalize(event.detail.selection.value.prenom);
|
||||||
|
const selection = with_dept ? `${event.detail.selection.value.nom} ${prenom} (${event.detail.selection.value.dept_acronym})` : `${event.detail.selection.value.nom} ${prenom}`;
|
||||||
|
// store etudid
|
||||||
|
const etudidField = document.getElementById('etudid');
|
||||||
|
etudidField.value = event.detail.selection.value.id;
|
||||||
|
autoCompleteJS.input.value = selection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resultsList: {
|
||||||
|
element: (list, data) => {
|
||||||
|
if (!data.results.length) {
|
||||||
|
// Create "No Results" message element
|
||||||
|
const message = document.createElement("div");
|
||||||
|
// Add class to the created element
|
||||||
|
message.setAttribute("class", "no_result");
|
||||||
|
// Add message text content
|
||||||
|
message.innerHTML = `<span>Pas de résultat pour "${data.query}"</span>`;
|
||||||
|
// Append message element to the results list
|
||||||
|
list.prepend(message);
|
||||||
|
// Efface l'etudid
|
||||||
|
const etudidField = document.getElementById('etudid');
|
||||||
|
etudidField.value = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
noResults: true,
|
||||||
|
},
|
||||||
|
resultItem: {
|
||||||
|
highlight: true,
|
||||||
|
element: (item, data) => {
|
||||||
|
const prenom = sco_capitalize(data.value.prenom);
|
||||||
|
item.innerHTML += with_dept ? ` ${prenom} (${data.value.dept_acronym})` : ` ${prenom}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -67,6 +67,10 @@ $(function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function sco_capitalize(string) {
|
||||||
|
return string[0].toUpperCase() + string.slice(1).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
// Affiche un message transitoire (duration milliseconds, 0 means infinity)
|
// Affiche un message transitoire (duration milliseconds, 0 means infinity)
|
||||||
function sco_message(msg, className = "message_custom", duration = 0) {
|
function sco_message(msg, className = "message_custom", duration = 0) {
|
||||||
var div = document.createElement("div");
|
var div = document.createElement("div");
|
||||||
|
1
app/static/libjs/autoComplete.js-10.2.7/dist/autoComplete.min.js
vendored
Normal file
1
app/static/libjs/autoComplete.js-10.2.7/dist/autoComplete.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
92
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.01.css
vendored
Normal file
92
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.01.css
vendored
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
.autoComplete_wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input {
|
||||||
|
width: 370px;
|
||||||
|
height: 40px;
|
||||||
|
padding-left: 20px;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: rgba(123, 123, 123, 1);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 0;
|
||||||
|
outline: none;
|
||||||
|
background-color: #f1f3f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input::placeholder {
|
||||||
|
color: rgba(123, 123, 123, 0.5);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul {
|
||||||
|
position: absolute;
|
||||||
|
max-height: 226px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0.5rem 0 0 0;
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 3px 6px rgba(149, 157, 165, 0.15);
|
||||||
|
border: 1px solid rgba(33, 33, 33, 0.07);
|
||||||
|
z-index: 1000;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul[hidden],
|
||||||
|
.autoComplete_wrapper > ul:empty {
|
||||||
|
display: block;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li {
|
||||||
|
margin: 0.3rem;
|
||||||
|
padding: 0.3rem 0.5rem;
|
||||||
|
list-style: none;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #212121;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li::selection {
|
||||||
|
color: rgba(#ffffff, 0);
|
||||||
|
background-color: rgba(#ffffff, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: rgba(123, 123, 123, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li mark {
|
||||||
|
background-color: transparent;
|
||||||
|
color: rgba(255, 122, 122, 1);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li mark::selection {
|
||||||
|
color: rgba(#ffffff, 0);
|
||||||
|
background-color: rgba(#ffffff, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li[aria-selected="true"] {
|
||||||
|
background-color: rgba(123, 123, 123, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.autoComplete_wrapper > input {
|
||||||
|
width: 18rem;
|
||||||
|
}
|
||||||
|
}
|
82
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.02.css
vendored
Normal file
82
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.02.css
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
.autoComplete_wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input {
|
||||||
|
width: 370px;
|
||||||
|
height: 40px;
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: rgb(116, 116, 116);
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid rgba(33, 33, 33, 0.2);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input::placeholder {
|
||||||
|
color: rgba(123, 123, 123, 0.5);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul {
|
||||||
|
position: absolute;
|
||||||
|
max-height: 226px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0.5rem 0 0 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid rgba(33, 33, 33, 0.1);
|
||||||
|
z-index: 1000;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li {
|
||||||
|
padding: 10px 20px;
|
||||||
|
list-style: none;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #212121;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li::selection {
|
||||||
|
color: rgba(#ffffff, 0);
|
||||||
|
background-color: rgba(#ffffff, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: rgba(123, 123, 123, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li mark {
|
||||||
|
background-color: transparent;
|
||||||
|
color: rgba(255, 122, 122, 1);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li mark::selection {
|
||||||
|
color: rgba(#ffffff, 0);
|
||||||
|
background-color: rgba(#ffffff, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li[aria-selected="true"] {
|
||||||
|
background-color: rgba(123, 123, 123, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.autoComplete_wrapper > input {
|
||||||
|
width: 18rem;
|
||||||
|
}
|
||||||
|
}
|
128
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.css
vendored
Normal file
128
app/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.css
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
.autoComplete_wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input {
|
||||||
|
height: 3rem;
|
||||||
|
width: 370px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 2rem 0 3.2rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
font-size: 1rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: rgba(255, 122, 122, 0.3);
|
||||||
|
outline: none;
|
||||||
|
border-radius: 10rem;
|
||||||
|
border: 0.05rem solid rgba(255, 122, 122, 0.5);
|
||||||
|
background-image: url(./images/search.svg);
|
||||||
|
background-size: 1.4rem;
|
||||||
|
background-position: left 1.05rem top 0.8rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-origin: border-box;
|
||||||
|
background-color: #fff;
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
-webkit-transition: all -webkit-transform 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input::placeholder {
|
||||||
|
color: rgba(255, 122, 122, 0.5);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
-webkit-transition: all -webkit-transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input:hover::placeholder {
|
||||||
|
color: rgba(255, 122, 122, 0.6);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
-webkit-transition: all -webkit-transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input:focus::placeholder {
|
||||||
|
padding: 0.1rem 0.6rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: rgba(255, 122, 122, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input:focus::selection {
|
||||||
|
background-color: rgba(255, 122, 122, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input::selection {
|
||||||
|
background-color: rgba(255, 122, 122, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input:hover {
|
||||||
|
color: rgba(255, 122, 122, 0.8);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
-webkit-transition: all -webkit-transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > input:focus {
|
||||||
|
color: rgba(255, 122, 122, 1);
|
||||||
|
border: 0.06rem solid rgba(255, 122, 122, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul {
|
||||||
|
position: absolute;
|
||||||
|
max-height: 226px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
box-sizing: border-box;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0.5rem 0 0 0;
|
||||||
|
padding: 0;
|
||||||
|
z-index: 1;
|
||||||
|
list-style: none;
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid rgba(33, 33, 33, 0.07);
|
||||||
|
box-shadow: 0 3px 6px rgba(149, 157, 165, 0.15);
|
||||||
|
outline: none;
|
||||||
|
transition: opacity 0.15s ease-in-out;
|
||||||
|
-moz-transition: opacity 0.15s ease-in-out;
|
||||||
|
-webkit-transition: opacity 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul[hidden],
|
||||||
|
.autoComplete_wrapper > ul:empty {
|
||||||
|
display: block;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li {
|
||||||
|
margin: 0.3rem;
|
||||||
|
padding: 0.3rem 0.5rem;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #212121;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li mark {
|
||||||
|
background-color: transparent;
|
||||||
|
color: rgba(255, 122, 122, 1);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: rgba(255, 122, 122, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoComplete_wrapper > ul > li[aria-selected="true"] {
|
||||||
|
background-color: rgba(255, 122, 122, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.autoComplete_wrapper > input {
|
||||||
|
width: 18rem;
|
||||||
|
}
|
||||||
|
}
|
8
app/static/libjs/autoComplete.js-10.2.7/dist/css/images/search.svg
vendored
Normal file
8
app/static/libjs/autoComplete.js-10.2.7/dist/css/images/search.svg
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" focusable="false" x="0px" y="0px" width="30" height="30" viewBox="0 0 171 171" style=" fill:#000000;">
|
||||||
|
<g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal">
|
||||||
|
<path d="M0,171.99609v-171.99609h171.99609v171.99609z" fill="none"></path>
|
||||||
|
<g fill="#ff7a7a">
|
||||||
|
<path d="M74.1,17.1c-31.41272,0 -57,25.58728 -57,57c0,31.41272 25.58728,57 57,57c13.6601,0 26.20509,-4.85078 36.03692,-12.90293l34.03301,34.03301c1.42965,1.48907 3.55262,2.08891 5.55014,1.56818c1.99752,-0.52073 3.55746,-2.08067 4.07819,-4.07819c0.52073,-1.99752 -0.0791,-4.12049 -1.56818,-5.55014l-34.03301,-34.03301c8.05215,-9.83182 12.90293,-22.37682 12.90293,-36.03692c0,-31.41272 -25.58728,-57 -57,-57zM74.1,28.5c25.2517,0 45.6,20.3483 45.6,45.6c0,25.2517 -20.3483,45.6 -45.6,45.6c-25.2517,0 -45.6,-20.3483 -45.6,-45.6c0,-25.2517 20.3483,-45.6 45.6,-45.6z"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -85,7 +85,15 @@
|
|||||||
{{ super() }}
|
{{ super() }}
|
||||||
{{ moment.include_moment() }}
|
{{ moment.include_moment() }}
|
||||||
{{ moment.lang(g.locale) }}
|
{{ moment.lang(g.locale) }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/menu.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/bubble.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/jQuery/jquery.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/jquery.field.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
var SCO_URL = "{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)[:-11] }}";
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -4,33 +4,27 @@
|
|||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
{{super()}}
|
{{super()}}
|
||||||
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/css/autosuggest_inquisitor.css" />
|
<link rel="stylesheet" href="/ScoDoc/static/libjs/autoComplete.js-10.2.7/dist/css/autoComplete.02.css">
|
||||||
<script src="/ScoDoc/static/libjs/AutoSuggest.js"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}</h1>
|
||||||
<br>
|
<br>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-3">
|
||||||
<p>
|
<p>
|
||||||
(*) champs requis
|
(*) champs requis
|
||||||
</p>
|
</p>
|
||||||
{{ wtf.quick_form(form, novalidate=True) }}
|
{{ wtf.quick_form(form, novalidate=True) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{super()}}
|
||||||
|
<script src="/ScoDoc/static/libjs/autoComplete.js-10.2.7/dist/autoComplete.min.js"></script>
|
||||||
|
<script src="/ScoDoc/static/js/etud_autocomplete.js"></script>
|
||||||
<script>
|
<script>
|
||||||
window.onload = function (e) {
|
const autoCompleteJS = new autoComplete(etud_autocomplete_config(with_dept = true));
|
||||||
var etudiants_options = {
|
|
||||||
script: "/ScoDoc/entreprises/etudiants?",
|
|
||||||
varname: "term",
|
|
||||||
json: true,
|
|
||||||
noresults: "Valeur invalide !",
|
|
||||||
minchars: 2,
|
|
||||||
timeout: 60000
|
|
||||||
};
|
|
||||||
var as_etudiants = new bsn.AutoSuggest('etudiant', etudiants_options);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
60
migrations/versions/d84bc592584e_extension_unaccent.py
Normal file
60
migrations/versions/d84bc592584e_extension_unaccent.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
"""Extension unaccent
|
||||||
|
|
||||||
|
Revision ID: d84bc592584e
|
||||||
|
Revises: b8df1b913c79
|
||||||
|
Create Date: 2023-06-01 13:46:52.927951
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.orm import sessionmaker # added by ev
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "d84bc592584e"
|
||||||
|
down_revision = "b8df1b913c79"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
Session = sessionmaker()
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
|
||||||
|
bind = op.get_bind()
|
||||||
|
session = Session(bind=bind)
|
||||||
|
# Ajout extension pour recherches sans accents:
|
||||||
|
session.execute(sa.text("""CREATE EXTENSION IF NOT EXISTS "unaccent";"""))
|
||||||
|
|
||||||
|
# Clé étrangère sur identite
|
||||||
|
session.execute(
|
||||||
|
sa.text(
|
||||||
|
"""UPDATE are_stages_apprentissages
|
||||||
|
SET etudid = NULL
|
||||||
|
WHERE are_stages_apprentissages.etudid NOT IN (
|
||||||
|
SELECT id
|
||||||
|
FROM Identite
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with op.batch_alter_table("are_stages_apprentissages", schema=None) as batch_op:
|
||||||
|
batch_op.create_foreign_key(
|
||||||
|
"are_stages_apprentissages_etudid_fkey",
|
||||||
|
"identite",
|
||||||
|
["etudid"],
|
||||||
|
["id"],
|
||||||
|
ondelete="CASCADE",
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table("are_stages_apprentissages", schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint(
|
||||||
|
"are_stages_apprentissages_etudid_fkey", type_="foreignkey"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.4.79"
|
SCOVERSION = "9.4.80"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
@ -224,6 +224,32 @@ def test_etudiants(api_headers):
|
|||||||
assert r.status_code == 404
|
assert r.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_etudiants_by_name(api_headers):
|
||||||
|
"""
|
||||||
|
Route: /etudiants/name/<string:start>
|
||||||
|
"""
|
||||||
|
r = requests.get(
|
||||||
|
API_URL + "/etudiants/name/A",
|
||||||
|
headers=api_headers,
|
||||||
|
verify=CHECK_CERTIFICATE,
|
||||||
|
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
etuds = r.json()
|
||||||
|
assert etuds == []
|
||||||
|
#
|
||||||
|
r = requests.get(
|
||||||
|
API_URL + "/etudiants/name/REG",
|
||||||
|
headers=api_headers,
|
||||||
|
verify=CHECK_CERTIFICATE,
|
||||||
|
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
etuds = r.json()
|
||||||
|
assert len(etuds) == 1
|
||||||
|
assert etuds[0]["nom"] == "RÉGNIER"
|
||||||
|
|
||||||
|
|
||||||
def test_etudiant_formsemestres(api_headers):
|
def test_etudiant_formsemestres(api_headers):
|
||||||
"""
|
"""
|
||||||
Route: /etudiant/etudid/<etudid:int>/formsemestres
|
Route: /etudiant/etudid/<etudid:int>/formsemestres
|
||||||
|
Loading…
Reference in New Issue
Block a user