Améliore code gestion civilité. Tests unitaires étudiants et import excel. Diverses corrections.
This commit is contained in:
parent
194e58419e
commit
e87bb9a8db
@ -91,8 +91,8 @@ class Identite(db.Model, models.ScoDocModel):
|
|||||||
unique=True,
|
unique=True,
|
||||||
postgresql_where=(code_ine.isnot(None)),
|
postgresql_where=(code_ine.isnot(None)),
|
||||||
),
|
),
|
||||||
db.CheckConstraint("civilite IN ('M', 'F', 'X')"),
|
db.CheckConstraint("civilite IN ('M', 'F', 'X')"), # non nullable
|
||||||
db.CheckConstraint("civilite_etat_civil IN ('M', 'F', 'X')"),
|
db.CheckConstraint("civilite_etat_civil IN ('M', 'F', 'X')"), # nullable
|
||||||
)
|
)
|
||||||
# ----- Relations
|
# ----- Relations
|
||||||
adresses = db.relationship(
|
adresses = db.relationship(
|
||||||
@ -213,33 +213,40 @@ class Identite(db.Model, models.ScoDocModel):
|
|||||||
return etud
|
return etud
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def civilite_str(self):
|
def civilite_str(self) -> str:
|
||||||
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
"""returns civilité usuelle: 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
||||||
personnes ne souhaitant pas d'affichage).
|
personnes ne souhaitant pas d'affichage).
|
||||||
"""
|
"""
|
||||||
return {"M": "M.", "F": "Mme", "X": ""}[self.civilite]
|
return {"M": "M.", "F": "Mme", "X": ""}[self.civilite]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def civilite_etat_civil_str(self):
|
def civilite_etat_civil_str(self) -> str:
|
||||||
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
"""returns 'M.' ou 'Mme', selon état civil officiel.
|
||||||
personnes ne souhaitant pas d'affichage).
|
La France ne reconnait pas le genre neutre dans l'état civil:
|
||||||
|
si cette donnée état civil est précisée, elle est utilisée,
|
||||||
|
sinon on renvoie la civilité usuelle.
|
||||||
"""
|
"""
|
||||||
return {"M": "M.", "F": "Mme", "X": ""}[self.civilite_etat_civil]
|
return (
|
||||||
|
{"M": "M.", "F": "Mme"}.get(self.civilite_etat_civil, "")
|
||||||
|
if self.civilite_etat_civil
|
||||||
|
else self.civilite_str
|
||||||
|
)
|
||||||
|
|
||||||
def sex_nom(self, no_accents=False) -> str:
|
def sex_nom(self, no_accents=False) -> str:
|
||||||
"'M. DUPONTÉ', ou si no_accents, 'M. DUPONTE'"
|
"'M. DUPONTÉ', ou si no_accents, 'M. DUPONTE'. Civilité usuelle."
|
||||||
s = f"{self.civilite_str} {(self.nom_usuel or self.nom).upper()}"
|
s = f"{self.civilite_str} {(self.nom_usuel or self.nom).upper()}"
|
||||||
if no_accents:
|
if no_accents:
|
||||||
return scu.suppress_accents(s)
|
return scu.suppress_accents(s)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def e(self):
|
def e(self) -> str:
|
||||||
"terminaison en français: 'ne', '', 'ou '(e)'"
|
"terminaison en français: 'ne', '', 'ou '(e)', selon la civilité usuelle"
|
||||||
return {"M": "", "F": "e"}.get(self.civilite, "(e)")
|
return {"M": "", "F": "e"}.get(self.civilite, "(e)")
|
||||||
|
|
||||||
def nom_disp(self) -> str:
|
def nom_disp(self) -> str:
|
||||||
"Nom à afficher"
|
"""Nom à afficher.
|
||||||
|
Note: le nom est stocké en base en majuscules."""
|
||||||
if self.nom_usuel:
|
if self.nom_usuel:
|
||||||
return (
|
return (
|
||||||
(self.nom_usuel + " (" + self.nom + ")") if self.nom else self.nom_usuel
|
(self.nom_usuel + " (" + self.nom + ")") if self.nom else self.nom_usuel
|
||||||
@ -273,7 +280,8 @@ class Identite(db.Model, models.ScoDocModel):
|
|||||||
return " ".join(r)
|
return " ".join(r)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def etat_civil(self):
|
def etat_civil(self) -> str:
|
||||||
|
"M. Prénom NOM, utilisant les données état civil si présentes, usuelles sinon."
|
||||||
if self.prenom_etat_civil:
|
if self.prenom_etat_civil:
|
||||||
civ = {"M": "M.", "F": "Mme", "X": ""}[self.civilite_etat_civil]
|
civ = {"M": "M.", "F": "Mme", "X": ""}[self.civilite_etat_civil]
|
||||||
return f"{civ} {self.prenom_etat_civil} {self.nom}"
|
return f"{civ} {self.prenom_etat_civil} {self.nom}"
|
||||||
@ -343,7 +351,7 @@ class Identite(db.Model, models.ScoDocModel):
|
|||||||
if key == "civilite": # requis
|
if key == "civilite": # requis
|
||||||
value = input_civilite(value)
|
value = input_civilite(value)
|
||||||
elif key == "civilite_etat_civil":
|
elif key == "civilite_etat_civil":
|
||||||
value = input_civilite(value) if value else None
|
value = input_civilite_etat_civil(value)
|
||||||
elif key == "boursier":
|
elif key == "boursier":
|
||||||
value = bool(value)
|
value = bool(value)
|
||||||
elif key == "date_naissance":
|
elif key == "date_naissance":
|
||||||
@ -375,7 +383,7 @@ class Identite(db.Model, models.ScoDocModel):
|
|||||||
e_dict.pop("_sa_instance_state", None)
|
e_dict.pop("_sa_instance_state", None)
|
||||||
# ScoDoc7 output_formators: (backward compat)
|
# ScoDoc7 output_formators: (backward compat)
|
||||||
e_dict["etudid"] = self.id
|
e_dict["etudid"] = self.id
|
||||||
e_dict["date_naissance"] = ndb.DateISOtoDMY(e_dict["date_naissance"])
|
e_dict["date_naissance"] = ndb.DateISOtoDMY(e_dict.get("date_naissance", ""))
|
||||||
e_dict["ne"] = self.e
|
e_dict["ne"] = self.e
|
||||||
e_dict["nomprenom"] = self.nomprenom
|
e_dict["nomprenom"] = self.nomprenom
|
||||||
adresse = self.adresses.first()
|
adresse = self.adresses.first()
|
||||||
@ -711,6 +719,11 @@ def input_civilite(s: str) -> str:
|
|||||||
raise ScoValueError(f"valeur invalide pour la civilité: {s}")
|
raise ScoValueError(f"valeur invalide pour la civilité: {s}")
|
||||||
|
|
||||||
|
|
||||||
|
def input_civilite_etat_civil(s: str) -> str | None:
|
||||||
|
"""Same as input_civilite, but empty gives None (null)"""
|
||||||
|
return input_civilite(s) if s and s.strip() else None
|
||||||
|
|
||||||
|
|
||||||
PIVOT_YEAR = 70
|
PIVOT_YEAR = 70
|
||||||
|
|
||||||
|
|
||||||
@ -786,9 +799,9 @@ class Admission(db.Model, models.ScoDocModel):
|
|||||||
specialite = db.Column(db.Text)
|
specialite = db.Column(db.Text)
|
||||||
annee_bac = db.Column(db.Integer)
|
annee_bac = db.Column(db.Integer)
|
||||||
math = db.Column(db.Text)
|
math = db.Column(db.Text)
|
||||||
physique = db.Column(db.Float)
|
physique = db.Column(db.Text)
|
||||||
anglais = db.Column(db.Float)
|
anglais = db.Column(db.Text)
|
||||||
francais = db.Column(db.Float)
|
francais = db.Column(db.Text)
|
||||||
# Rang dans les voeux du candidat (inconnu avec APB et PS)
|
# Rang dans les voeux du candidat (inconnu avec APB et PS)
|
||||||
rang = db.Column(db.Integer)
|
rang = db.Column(db.Integer)
|
||||||
# Qualité et décision du jury d'admission (ou de l'examinateur)
|
# Qualité et décision du jury d'admission (ou de l'examinateur)
|
||||||
@ -852,8 +865,6 @@ class Admission(db.Model, models.ScoDocModel):
|
|||||||
value = None
|
value = None
|
||||||
if key in fs_uppercase and value:
|
if key in fs_uppercase and value:
|
||||||
value = value.upper()
|
value = value.upper()
|
||||||
if key == "civilite" or key == "civilite_etat_civil":
|
|
||||||
value = input_civilite(value)
|
|
||||||
elif key == "annee" or key == "annee_bac":
|
elif key == "annee" or key == "annee_bac":
|
||||||
value = pivot_year(value)
|
value = pivot_year(value)
|
||||||
elif key == "classement" or key == "apb_classement_gr":
|
elif key == "classement" or key == "apb_classement_gr":
|
||||||
|
@ -38,7 +38,12 @@ from flask import url_for, g
|
|||||||
from app import db, email
|
from app import db, email
|
||||||
from app import log
|
from app import log
|
||||||
from app.models import Admission, Identite
|
from app.models import Admission, Identite
|
||||||
from app.models.etudiants import input_civilite, make_etud_args, pivot_year
|
from app.models.etudiants import (
|
||||||
|
input_civilite,
|
||||||
|
input_civilite_etat_civil,
|
||||||
|
make_etud_args,
|
||||||
|
pivot_year,
|
||||||
|
)
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||||
@ -47,9 +52,13 @@ from app.scodoc import sco_preferences
|
|||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
|
|
||||||
|
|
||||||
def format_etud_ident(etud):
|
def format_etud_ident(etud: dict):
|
||||||
"""Format identite de l'étudiant (modifié en place)
|
"""Format identite de l'étudiant (modifié en place)
|
||||||
nom, prénom et formes associees
|
nom, prénom et formes associees.
|
||||||
|
|
||||||
|
Note: par rapport à Identite.to_dict_bul(),
|
||||||
|
ajoute les champs:
|
||||||
|
'email_default', 'nom_disp', 'nom_usuel', 'civilite_etat_civil_str', 'ne', 'civilite_str'
|
||||||
"""
|
"""
|
||||||
etud["nom"] = format_nom(etud["nom"])
|
etud["nom"] = format_nom(etud["nom"])
|
||||||
if "nom_usuel" in etud:
|
if "nom_usuel" in etud:
|
||||||
@ -65,7 +74,7 @@ def format_etud_ident(etud):
|
|||||||
etud["civilite_etat_civil_str"] = (
|
etud["civilite_etat_civil_str"] = (
|
||||||
format_civilite(etud["civilite_etat_civil"])
|
format_civilite(etud["civilite_etat_civil"])
|
||||||
if etud["civilite_etat_civil"]
|
if etud["civilite_etat_civil"]
|
||||||
else ""
|
else etud["civilite_str"]
|
||||||
)
|
)
|
||||||
# Nom à afficher:
|
# Nom à afficher:
|
||||||
if etud["nom_usuel"]:
|
if etud["nom_usuel"]:
|
||||||
@ -76,7 +85,7 @@ def format_etud_ident(etud):
|
|||||||
etud["nom_disp"] = etud["nom"]
|
etud["nom_disp"] = etud["nom"]
|
||||||
|
|
||||||
etud["nomprenom"] = format_nomprenom(etud) # M. Pierre DUPONT
|
etud["nomprenom"] = format_nomprenom(etud) # M. Pierre DUPONT
|
||||||
etud["etat_civil"] = format_etat_civil(etud)
|
etud["etat_civil"] = _format_etat_civil(etud)
|
||||||
if etud["civilite"] == "M":
|
if etud["civilite"] == "M":
|
||||||
etud["ne"] = ""
|
etud["ne"] = ""
|
||||||
elif etud["civilite"] == "F":
|
elif etud["civilite"] == "F":
|
||||||
@ -147,11 +156,12 @@ def format_civilite(civilite):
|
|||||||
raise ScoValueError(f"valeur invalide pour la civilité: {civilite}") from exc
|
raise ScoValueError(f"valeur invalide pour la civilité: {civilite}") from exc
|
||||||
|
|
||||||
|
|
||||||
def format_etat_civil(etud: dict):
|
def _format_etat_civil(etud: dict) -> str:
|
||||||
if etud["prenom_etat_civil"]:
|
"Mme Béatrice DUPONT, en utilisant les données d'état civil si indiquées."
|
||||||
civ = {"M": "M.", "F": "Mme", "X": ""}[etud.get("civilite_etat_civil", "X")]
|
if etud["prenom_etat_civil"] or etud["civilite_etat_civil"]:
|
||||||
return f'{civ} {etud["prenom_etat_civil"]} {etud["nom"]}'
|
return f"""{etud["civilite_etat_civil_str"]} {
|
||||||
else:
|
etud["prenom_etat_civil"] or etud["prenom"]
|
||||||
|
} {etud["nom"]}"""
|
||||||
return etud["nomprenom"]
|
return etud["nomprenom"]
|
||||||
|
|
||||||
|
|
||||||
@ -237,7 +247,7 @@ _identiteEditor = ndb.EditableTable(
|
|||||||
"prenom": force_uppercase,
|
"prenom": force_uppercase,
|
||||||
"prenom_etat_civil": force_uppercase,
|
"prenom_etat_civil": force_uppercase,
|
||||||
"civilite": input_civilite,
|
"civilite": input_civilite,
|
||||||
"civilite_etat_civil": input_civilite,
|
"civilite_etat_civil": input_civilite_etat_civil,
|
||||||
"date_naissance": ndb.DateDMYtoISO,
|
"date_naissance": ndb.DateDMYtoISO,
|
||||||
"boursier": bool,
|
"boursier": bool,
|
||||||
},
|
},
|
||||||
@ -300,7 +310,9 @@ def check_nom_prenom_homonyms(
|
|||||||
|
|
||||||
|
|
||||||
def _check_duplicate_code(cnx, args, code_name, disable_notify=False, edit=True):
|
def _check_duplicate_code(cnx, args, code_name, disable_notify=False, edit=True):
|
||||||
"""Vérifie que le code n'est pas dupliqué"""
|
"""Vérifie que le code n'est pas dupliqué.
|
||||||
|
Raises ScoGenError si problème.
|
||||||
|
"""
|
||||||
etudid = args.get("etudid", None)
|
etudid = args.get("etudid", None)
|
||||||
if args.get(code_name, None):
|
if args.get(code_name, None):
|
||||||
etuds = identite_list(cnx, {code_name: str(args[code_name])})
|
etuds = identite_list(cnx, {code_name: str(args[code_name])})
|
||||||
@ -355,11 +367,6 @@ def _check_duplicate_code(cnx, args, code_name, disable_notify=False, edit=True)
|
|||||||
raise ScoGenError(err_page)
|
raise ScoGenError(err_page)
|
||||||
|
|
||||||
|
|
||||||
def _check_civilite(args):
|
|
||||||
civilite = args.get("civilite", "X") or "X"
|
|
||||||
args["civilite"] = input_civilite(civilite) # TODO: A faire valider
|
|
||||||
|
|
||||||
|
|
||||||
def identite_edit(cnx, args, disable_notify=False):
|
def identite_edit(cnx, args, disable_notify=False):
|
||||||
"""Modifie l'identite d'un étudiant.
|
"""Modifie l'identite d'un étudiant.
|
||||||
Si pref notification et difference, envoie message notification, sauf si disable_notify
|
Si pref notification et difference, envoie message notification, sauf si disable_notify
|
||||||
@ -400,7 +407,6 @@ def identite_create(cnx, args):
|
|||||||
"check unique etudid, then create"
|
"check unique etudid, then create"
|
||||||
_check_duplicate_code(cnx, args, "code_nip", edit=False)
|
_check_duplicate_code(cnx, args, "code_nip", edit=False)
|
||||||
_check_duplicate_code(cnx, args, "code_ine", edit=False)
|
_check_duplicate_code(cnx, args, "code_ine", edit=False)
|
||||||
_check_civilite(args)
|
|
||||||
|
|
||||||
if "etudid" in args:
|
if "etudid" in args:
|
||||||
etudid = args["etudid"]
|
etudid = args["etudid"]
|
||||||
@ -915,12 +921,12 @@ def etud_inscriptions_infos(etudid: int, ne="") -> dict:
|
|||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
|
|
||||||
etud = {}
|
infos = {}
|
||||||
# Semestres dans lesquel il est inscrit
|
# Semestres dans lesquel il est inscrit
|
||||||
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
{"etudid": etudid}
|
{"etudid": etudid}
|
||||||
)
|
)
|
||||||
etud["ins"] = ins
|
infos["ins"] = ins
|
||||||
sems = []
|
sems = []
|
||||||
cursem = None # semestre "courant" ou il est inscrit
|
cursem = None # semestre "courant" ou il est inscrit
|
||||||
for i in ins:
|
for i in ins:
|
||||||
@ -933,29 +939,31 @@ def etud_inscriptions_infos(etudid: int, ne="") -> dict:
|
|||||||
# trie les semestres par date de debut, le plus recent d'abord
|
# trie les semestres par date de debut, le plus recent d'abord
|
||||||
# (important, ne pas changer (suivi cohortes))
|
# (important, ne pas changer (suivi cohortes))
|
||||||
sems.sort(key=itemgetter("dateord"), reverse=True)
|
sems.sort(key=itemgetter("dateord"), reverse=True)
|
||||||
etud["sems"] = sems
|
infos["sems"] = sems
|
||||||
etud["cursem"] = cursem
|
infos["cursem"] = cursem
|
||||||
if cursem:
|
if cursem:
|
||||||
etud["inscription"] = cursem["titremois"]
|
infos["inscription"] = cursem["titremois"]
|
||||||
etud["inscriptionstr"] = "Inscrit en " + cursem["titremois"]
|
infos["inscriptionstr"] = "Inscrit en " + cursem["titremois"]
|
||||||
etud["inscription_formsemestre_id"] = cursem["formsemestre_id"]
|
infos["inscription_formsemestre_id"] = cursem["formsemestre_id"]
|
||||||
etud["etatincursem"] = curi["etat"]
|
infos["etatincursem"] = curi["etat"]
|
||||||
etud["situation"] = descr_situation_etud(etudid, ne)
|
infos["situation"] = descr_situation_etud(etudid, ne)
|
||||||
else:
|
else:
|
||||||
if etud["sems"]:
|
if infos["sems"]:
|
||||||
if etud["sems"][0]["dateord"] > time.strftime("%Y-%m-%d", time.localtime()):
|
if infos["sems"][0]["dateord"] > time.strftime(
|
||||||
etud["inscription"] = "futur"
|
"%Y-%m-%d", time.localtime()
|
||||||
etud["situation"] = "futur élève"
|
):
|
||||||
|
infos["inscription"] = "futur"
|
||||||
|
infos["situation"] = "futur élève"
|
||||||
else:
|
else:
|
||||||
etud["inscription"] = "ancien"
|
infos["inscription"] = "ancien"
|
||||||
etud["situation"] = "ancien élève"
|
infos["situation"] = "ancien élève"
|
||||||
else:
|
else:
|
||||||
etud["inscription"] = "non inscrit"
|
infos["inscription"] = "non inscrit"
|
||||||
etud["situation"] = etud["inscription"]
|
infos["situation"] = infos["inscription"]
|
||||||
etud["inscriptionstr"] = etud["inscription"]
|
infos["inscriptionstr"] = infos["inscription"]
|
||||||
etud["inscription_formsemestre_id"] = None
|
infos["inscription_formsemestre_id"] = None
|
||||||
etud["etatincursem"] = "?"
|
infos["etatincursem"] = "?"
|
||||||
return etud
|
return infos
|
||||||
|
|
||||||
|
|
||||||
def descr_situation_etud(etudid: int, ne="") -> str:
|
def descr_situation_etud(etudid: int, ne="") -> str:
|
||||||
|
@ -38,18 +38,14 @@ from flask import g, url_for
|
|||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import Identite, GroupDescr, ScolarNews
|
from app.models import Identite, GroupDescr, ScolarNews
|
||||||
from app.models.etudiants import input_civilite
|
from app.models.etudiants import input_civilite, input_civilite_etat_civil
|
||||||
|
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc.sco_excel import COLORS
|
from app.scodoc.sco_excel import COLORS
|
||||||
from app.scodoc.sco_exceptions import (
|
from app.scodoc.sco_exceptions import (
|
||||||
AccessDenied,
|
|
||||||
ScoFormatError,
|
ScoFormatError,
|
||||||
ScoException,
|
ScoException,
|
||||||
ScoValueError,
|
ScoValueError,
|
||||||
ScoInvalidDateError,
|
|
||||||
ScoLockedFormError,
|
|
||||||
ScoGenError,
|
|
||||||
)
|
)
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
@ -375,6 +371,15 @@ def scolars_import_excel_file(
|
|||||||
(doit etre 'M', 'F', ou 'MME', 'H', 'X' mais pas '{
|
(doit etre 'M', 'F', ou 'MME', 'H', 'X' mais pas '{
|
||||||
val}') ligne {linenum}, colonne {titleslist[i]}"""
|
val}') ligne {linenum}, colonne {titleslist[i]}"""
|
||||||
) from exc
|
) from exc
|
||||||
|
if titleslist[i].lower() == "civilite_etat_civil":
|
||||||
|
try:
|
||||||
|
val = input_civilite_etat_civil(val)
|
||||||
|
except ScoValueError as exc:
|
||||||
|
raise ScoValueError(
|
||||||
|
f"""valeur invalide pour 'civilite'
|
||||||
|
(doit etre 'M', 'F', vide ou 'MME', 'H', 'X' mais pas '{
|
||||||
|
val}') ligne {linenum}, colonne {titleslist[i]}"""
|
||||||
|
) from exc
|
||||||
|
|
||||||
# Excel date conversion:
|
# Excel date conversion:
|
||||||
if titleslist[i].lower() == "date_naissance":
|
if titleslist[i].lower() == "date_naissance":
|
||||||
|
@ -79,6 +79,12 @@ STATIC_DIR = (
|
|||||||
# Attention: suppose que la timezone utilisée par postgresql soit la même !
|
# Attention: suppose que la timezone utilisée par postgresql soit la même !
|
||||||
TIME_ZONE = timezone("/".join(os.path.realpath("/etc/localtime").split("/")[-2:]))
|
TIME_ZONE = timezone("/".join(os.path.realpath("/etc/localtime").split("/")[-2:]))
|
||||||
|
|
||||||
|
# ----- CIVILITE ETUDIANTS
|
||||||
|
CIVILITES = {"M": "M.", "F": "Mme", "X": ""}
|
||||||
|
CIVILITES_ETAT_CIVIL = {"M": "M.", "F": "Mme"}
|
||||||
|
# Si l'état civil reconnait le genre neutre (X),:
|
||||||
|
# CIVILITES_ETAT_CIVIL = CIVILITES
|
||||||
|
|
||||||
# ----- CALCUL ET PRESENTATION DES NOTES
|
# ----- CALCUL ET PRESENTATION DES NOTES
|
||||||
NOTES_PRECISION = 1e-4 # evite eventuelles erreurs d'arrondis
|
NOTES_PRECISION = 1e-4 # evite eventuelles erreurs d'arrondis
|
||||||
NOTES_MIN = 0.0 # valeur minimale admise pour une note (sauf malus, dans [-20, 20])
|
NOTES_MIN = 0.0 # valeur minimale admise pour une note (sauf malus, dans [-20, 20])
|
||||||
|
@ -1542,7 +1542,6 @@ def _etudident_create_or_edit_form(edit):
|
|||||||
"math",
|
"math",
|
||||||
{
|
{
|
||||||
"size": 3,
|
"size": 3,
|
||||||
"type": "float",
|
|
||||||
"title": "Note de mathématiques",
|
"title": "Note de mathématiques",
|
||||||
"explanation": "note sur 20 en terminale",
|
"explanation": "note sur 20 en terminale",
|
||||||
},
|
},
|
||||||
@ -1551,7 +1550,6 @@ def _etudident_create_or_edit_form(edit):
|
|||||||
"physique",
|
"physique",
|
||||||
{
|
{
|
||||||
"size": 3,
|
"size": 3,
|
||||||
"type": "float",
|
|
||||||
"title": "Note de physique",
|
"title": "Note de physique",
|
||||||
"explanation": "note sur 20 en terminale",
|
"explanation": "note sur 20 en terminale",
|
||||||
},
|
},
|
||||||
@ -1560,7 +1558,6 @@ def _etudident_create_or_edit_form(edit):
|
|||||||
"anglais",
|
"anglais",
|
||||||
{
|
{
|
||||||
"size": 3,
|
"size": 3,
|
||||||
"type": "float",
|
|
||||||
"title": "Note d'anglais",
|
"title": "Note d'anglais",
|
||||||
"explanation": "note sur 20 en terminale",
|
"explanation": "note sur 20 en terminale",
|
||||||
},
|
},
|
||||||
@ -1569,7 +1566,6 @@ def _etudident_create_or_edit_form(edit):
|
|||||||
"francais",
|
"francais",
|
||||||
{
|
{
|
||||||
"size": 3,
|
"size": 3,
|
||||||
"type": "float",
|
|
||||||
"title": "Note de français",
|
"title": "Note de français",
|
||||||
"explanation": "note sur 20 obtenue au bac",
|
"explanation": "note sur 20 obtenue au bac",
|
||||||
},
|
},
|
||||||
|
@ -122,6 +122,24 @@ def upgrade():
|
|||||||
|
|
||||||
with op.batch_alter_table("admissions", schema=None) as batch_op:
|
with op.batch_alter_table("admissions", schema=None) as batch_op:
|
||||||
batch_op.drop_constraint("admissions_etudid_fkey", type_="foreignkey")
|
batch_op.drop_constraint("admissions_etudid_fkey", type_="foreignkey")
|
||||||
|
batch_op.alter_column(
|
||||||
|
"physique",
|
||||||
|
existing_type=sa.DOUBLE_PRECISION(precision=53),
|
||||||
|
type_=sa.Text(),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
batch_op.alter_column(
|
||||||
|
"anglais",
|
||||||
|
existing_type=sa.DOUBLE_PRECISION(precision=53),
|
||||||
|
type_=sa.Text(),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
batch_op.alter_column(
|
||||||
|
"francais",
|
||||||
|
existing_type=sa.DOUBLE_PRECISION(precision=53),
|
||||||
|
type_=sa.Text(),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
# laisse l'ancienne colonne pour downgrade (tests)
|
# laisse l'ancienne colonne pour downgrade (tests)
|
||||||
# batch_op.drop_column('etudid')
|
# batch_op.drop_column('etudid')
|
||||||
|
|
||||||
@ -160,6 +178,24 @@ def downgrade():
|
|||||||
batch_op.create_foreign_key(
|
batch_op.create_foreign_key(
|
||||||
"admissions_etudid_fkey", "identite", ["etudid"], ["id"], ondelete="CASCADE"
|
"admissions_etudid_fkey", "identite", ["etudid"], ["id"], ondelete="CASCADE"
|
||||||
)
|
)
|
||||||
|
batch_op.alter_column(
|
||||||
|
"francais",
|
||||||
|
existing_type=sa.Text(),
|
||||||
|
type_=sa.DOUBLE_PRECISION(precision=53),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
batch_op.alter_column(
|
||||||
|
"anglais",
|
||||||
|
existing_type=sa.Text(),
|
||||||
|
type_=sa.DOUBLE_PRECISION(precision=53),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
batch_op.alter_column(
|
||||||
|
"physique",
|
||||||
|
existing_type=sa.Text(),
|
||||||
|
type_=sa.DOUBLE_PRECISION(precision=53),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
|
||||||
with op.batch_alter_table("adresse", schema=None) as batch_op:
|
with op.batch_alter_table("adresse", schema=None) as batch_op:
|
||||||
batch_op.drop_constraint("adresse_etudid_fkey", type_="foreignkey")
|
batch_op.drop_constraint("adresse_etudid_fkey", type_="foreignkey")
|
||||||
|
BIN
tests/ressources/misc/ImportEtudiants.xlsx
Normal file
BIN
tests/ressources/misc/ImportEtudiants.xlsx
Normal file
Binary file not shown.
@ -4,6 +4,7 @@ Quelques fonctions d'initialisation pour tests unitaires
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from app import db, models
|
from app import db, models
|
||||||
|
from app.models import Formation
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
|
|
||||||
@ -16,16 +17,34 @@ from tests.unit import sco_fake_gen
|
|||||||
|
|
||||||
|
|
||||||
def build_formation_test(
|
def build_formation_test(
|
||||||
nb_mods=1, parcours=codes_cursus.CursusBUT, with_ue_sport=False
|
nb_mods=1,
|
||||||
):
|
parcours=codes_cursus.CursusBUT,
|
||||||
|
with_ue_sport=False,
|
||||||
|
acronyme="F3",
|
||||||
|
titre="Formation 2",
|
||||||
|
) -> tuple[sco_fake_gen.ScoFake, int, list[int], list[int]]:
|
||||||
"""Crée une formation simple pour les tests.
|
"""Crée une formation simple pour les tests.
|
||||||
Création à partir de zéro (n'importe pas un fichier xml).
|
Création à partir de zéro (n'importe pas un fichier xml).
|
||||||
Avec 3 UEs en S2 et une UE en S4.
|
Avec 3 UEs en S2 et une UE en S4.
|
||||||
"""
|
"""
|
||||||
G = sco_fake_gen.ScoFake(verbose=False)
|
G = sco_fake_gen.ScoFake(verbose=False)
|
||||||
|
# If already exists, just use it
|
||||||
|
formation = Formation.query.filter_by(
|
||||||
|
dept_id=G.dept.id,
|
||||||
|
acronyme=acronyme,
|
||||||
|
titre=titre,
|
||||||
|
version=1,
|
||||||
|
).first()
|
||||||
|
if formation is not None:
|
||||||
|
return (
|
||||||
|
G,
|
||||||
|
formation.id,
|
||||||
|
[ue.id for ue in formation.ues],
|
||||||
|
[m.id for m in formation.modules],
|
||||||
|
)
|
||||||
formation_id = G.create_formation(
|
formation_id = G.create_formation(
|
||||||
acronyme="F3",
|
acronyme=acronyme,
|
||||||
titre="Formation 2",
|
titre=titre,
|
||||||
titre_officiel="Titre officiel 2",
|
titre_officiel="Titre officiel 2",
|
||||||
type_parcours=parcours.TYPE_CURSUS,
|
type_parcours=parcours.TYPE_CURSUS,
|
||||||
)
|
)
|
||||||
|
187
tests/unit/test_etudiants.py
Normal file
187
tests/unit/test_etudiants.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Test modèle étudiant (identite)
|
||||||
|
|
||||||
|
|
||||||
|
Utiliser comme:
|
||||||
|
pytest tests/unit/test_etudiants.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from app import db
|
||||||
|
from app.models import Admission, Adresse, Departement, FormSemestre, Identite
|
||||||
|
from app.scodoc import sco_etud
|
||||||
|
from app.scodoc import sco_import_etuds
|
||||||
|
|
||||||
|
from tests.unit import setup
|
||||||
|
|
||||||
|
|
||||||
|
def test_identite(test_client):
|
||||||
|
"Test de base du modèle identité: création, clonage, ..."
|
||||||
|
args = {
|
||||||
|
"civilite": "X",
|
||||||
|
"code_ine": "code_ine",
|
||||||
|
"code_nip": "code_nip",
|
||||||
|
"date_naissance": datetime.date(2000, 1, 2),
|
||||||
|
"dept_id": 1,
|
||||||
|
"dept_naissance": "dept_naissance",
|
||||||
|
"lieu_naissance": "lieu_naissance",
|
||||||
|
"nationalite": "nationalite",
|
||||||
|
"nom_usuel": "nom_usuel",
|
||||||
|
"nom": "nom",
|
||||||
|
"prenom_etat_civil": "prenom_etat_civil",
|
||||||
|
"prenom": "prenom",
|
||||||
|
}
|
||||||
|
e = Identite.create_etud(**args)
|
||||||
|
db.session.add(e)
|
||||||
|
db.session.flush()
|
||||||
|
admission_id = e.admission_id
|
||||||
|
admission = db.session.get(Admission, admission_id)
|
||||||
|
assert admission is not None
|
||||||
|
assert e.boursier is False
|
||||||
|
assert e.adresses.count() == 1
|
||||||
|
adresses_ids = [a.id for a in e.adresses]
|
||||||
|
# --- Teste cascade admission:
|
||||||
|
db.session.delete(e)
|
||||||
|
db.session.flush()
|
||||||
|
assert db.session.get(Admission, admission_id) is None
|
||||||
|
assert db.session.get(Departement, 1) is not None
|
||||||
|
# --- Teste cascade adresses
|
||||||
|
for adresse_id in adresses_ids:
|
||||||
|
assert db.session.get(Adresse, adresse_id) is None
|
||||||
|
# --- Test cascade département
|
||||||
|
dept = Departement(acronym="test_identite")
|
||||||
|
db.session.add(dept)
|
||||||
|
db.session.flush()
|
||||||
|
args1 = args | {"dept_id": dept.id}
|
||||||
|
e = Identite.create_etud(**args1)
|
||||||
|
db.session.add(e)
|
||||||
|
db.session.flush()
|
||||||
|
etudid = e.id
|
||||||
|
db.session.delete(dept)
|
||||||
|
db.session.flush()
|
||||||
|
assert db.session.get(Identite, etudid) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_etat_civil(test_client):
|
||||||
|
"Test des attributs état civil"
|
||||||
|
dept = Departement.query.first()
|
||||||
|
args = {"nom": "nom", "prenom": "prénom", "civilite": "M", "dept_id": dept.id}
|
||||||
|
# Homme
|
||||||
|
e = Identite(**args)
|
||||||
|
db.session.add(e)
|
||||||
|
db.session.flush()
|
||||||
|
assert e.civilite_etat_civil_str == "M."
|
||||||
|
assert e.e == ""
|
||||||
|
# Femme
|
||||||
|
e = Identite(**args | {"civilite": "F"})
|
||||||
|
db.session.add(e)
|
||||||
|
db.session.flush()
|
||||||
|
assert e.civilite_etat_civil_str == "Mme"
|
||||||
|
assert e.e == "e"
|
||||||
|
# Homme devenu femme
|
||||||
|
e = Identite(**(args | {"civilite_etat_civil": "F"}))
|
||||||
|
db.session.add(e)
|
||||||
|
db.session.flush()
|
||||||
|
assert e.civilite_etat_civil_str == "Mme"
|
||||||
|
assert e.civilite_str == "M."
|
||||||
|
assert e.e == ""
|
||||||
|
# Femme devenue neutre
|
||||||
|
e = Identite(**(args | {"civilite": "X", "civilite_etat_civil": "F"}))
|
||||||
|
db.session.add(e)
|
||||||
|
db.session.flush()
|
||||||
|
assert e.civilite_etat_civil_str == "Mme"
|
||||||
|
assert e.civilite_str == ""
|
||||||
|
assert e.e == "(e)"
|
||||||
|
assert e.prenom_etat_civil is None
|
||||||
|
# La version dict
|
||||||
|
e_d = e.to_dict_scodoc7()
|
||||||
|
assert e_d["civilite"] == "X"
|
||||||
|
assert e_d["civilite_etat_civil"] == "F"
|
||||||
|
assert e_d["ne"] == "(e)"
|
||||||
|
|
||||||
|
|
||||||
|
def test_etud_legacy(test_client):
|
||||||
|
"Test certaines fonctions scodoc7 (sco_etud)"
|
||||||
|
dept = Departement.query.first()
|
||||||
|
args = {"nom": "nom", "prenom": "prénom", "civilite": "M", "dept_id": dept.id}
|
||||||
|
# Prénom état civil
|
||||||
|
e = Identite(**(args))
|
||||||
|
db.session.add(e)
|
||||||
|
db.session.flush()
|
||||||
|
e_dict = e.to_dict_bul()
|
||||||
|
sco_etud.format_etud_ident(e_dict)
|
||||||
|
assert e_dict["nom_disp"] == "NOM"
|
||||||
|
assert e_dict["prenom_etat_civil"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_etuds_xlsx(test_client):
|
||||||
|
"test import étudiant depuis xlsx"
|
||||||
|
G, formation_id, (ue1_id, ue2_id, ue3_id), module_ids = setup.build_formation_test(
|
||||||
|
acronyme="IMPXLSX"
|
||||||
|
)
|
||||||
|
formsemestre_id = G.create_formsemestre(
|
||||||
|
formation_id=formation_id,
|
||||||
|
semestre_id=1,
|
||||||
|
date_debut="01/01/2021",
|
||||||
|
date_fin="30/06/2021",
|
||||||
|
)
|
||||||
|
filename = (
|
||||||
|
Path(current_app.config["SCODOC_DIR"])
|
||||||
|
/ "tests/ressources/misc/ImportEtudiants.xlsx"
|
||||||
|
)
|
||||||
|
with open(filename, mode="rb") as f:
|
||||||
|
sco_import_etuds.scolars_import_excel_file(
|
||||||
|
f, formsemestre_id=formsemestre_id, exclude_cols=["photo_filename"]
|
||||||
|
)
|
||||||
|
formsemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||||
|
# Vérifie tous les champs du premier étudiant
|
||||||
|
etud = formsemestre.etuds.first()
|
||||||
|
assert etud.code_nip == "nip1"
|
||||||
|
assert etud.code_ine == "ine1"
|
||||||
|
assert etud.nom == "NOM1"
|
||||||
|
assert etud.nom_usuel == "nom_usuel1"
|
||||||
|
assert etud.prenom == "PRÉNOM1"
|
||||||
|
assert etud.civilite == "M"
|
||||||
|
assert etud.prenom_etat_civil == "PRÉNOM_CIVIL1"
|
||||||
|
assert etud.civilite_etat_civil == "M"
|
||||||
|
assert etud.date_naissance == datetime.date(2001, 5, 1)
|
||||||
|
assert etud.lieu_naissance == "Paris"
|
||||||
|
assert etud.nationalite == "Belge"
|
||||||
|
assert etud.boursier is True
|
||||||
|
# Admission
|
||||||
|
adm = etud.admission
|
||||||
|
assert adm.bac == "C"
|
||||||
|
assert adm.specialite == "SPÉ"
|
||||||
|
assert adm.annee_bac == 2023
|
||||||
|
assert adm.math == "11.0" # deprecated field
|
||||||
|
assert adm.physique == "12.0" # deprecated field
|
||||||
|
assert adm.anglais == "13.0" # deprecated field
|
||||||
|
assert adm.francais == "14.0" # deprecated field
|
||||||
|
assert adm.boursier_prec is False
|
||||||
|
assert adm.qualite == 10
|
||||||
|
assert adm.rapporteur == "xx"
|
||||||
|
assert adm.score == 5
|
||||||
|
assert adm.classement == 111
|
||||||
|
assert adm.nomlycee == "nomlycée"
|
||||||
|
assert adm.codepostallycee == "75005"
|
||||||
|
# Adresse
|
||||||
|
adresse: Adresse = etud.adresses.first()
|
||||||
|
assert adresse.email == "etud1@etud.no"
|
||||||
|
assert adresse.emailperso == "etud1@perso.no"
|
||||||
|
assert adresse.domicile == "1 rue A"
|
||||||
|
assert adresse.codepostaldomicile == "12345"
|
||||||
|
assert adresse.villedomicile == "Lima"
|
||||||
|
assert adresse.paysdomicile == "Pérou"
|
||||||
|
assert adresse.telephone == "102030405"
|
||||||
|
assert adresse.telephonemobile == "605040302"
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
# mapp.set_sco_dept("TEST_")
|
||||||
|
# ctx.push()
|
||||||
|
# login_user(User.query.filter_by(user_name="admin").first())
|
@ -10,7 +10,7 @@ nom_usuel; text; identite; 1; nom usuel (si different);
|
|||||||
prenom; text; identite; 0; prénom de l'etudiant
|
prenom; text; identite; 0; prénom de l'etudiant
|
||||||
civilite; text; identite; 0; sexe ('M', 'F', 'X');sexe;genre
|
civilite; text; identite; 0; sexe ('M', 'F', 'X');sexe;genre
|
||||||
prenom_etat_civil; text; identite; 1; prénom à l'état-civil (si différent);prenom_etat_civil
|
prenom_etat_civil; text; identite; 1; prénom à l'état-civil (si différent);prenom_etat_civil
|
||||||
civilite_etat_civil; text; identite; 1; sexe ('M', 'F', 'X') à l'état civil;civilite_etat_civil
|
civilite_etat_civil; text; identite; 1; sexe ('M', 'F', 'X', '') à l'état civil;civilite_etat_civil
|
||||||
date_naissance;text;identite; 1; date de naissance (jj/mm/aaaa)
|
date_naissance;text;identite; 1; date de naissance (jj/mm/aaaa)
|
||||||
lieu_naissance;text;identite; 1; lieu de naissance
|
lieu_naissance;text;identite; 1; lieu de naissance
|
||||||
nationalite; text; identite; 1; nationalite
|
nationalite; text; identite; 1; nationalite
|
||||||
@ -53,6 +53,3 @@ paysdomicile; text; adresse; 1; pays domicile
|
|||||||
telephone; text; adresse; 1; num. telephone (fixe)
|
telephone; text; adresse; 1; num. telephone (fixe)
|
||||||
telephonemobile; text; adresse; 1; num. telephone (mobile)
|
telephonemobile; text; adresse; 1; num. telephone (mobile)
|
||||||
#
|
#
|
||||||
# Pas tout à fait admission:
|
|
||||||
debouche;text; admissions;1;(OBSOLETE, ne plus utiliser) situation APRES être passé par chez nous;
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user