Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
10 changed files with 343 additions and 78 deletions
Showing only changes of commit e87bb9a8db - Show all commits

View File

@ -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":

View File

@ -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:

View File

@ -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":

View File

@ -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])

View File

@ -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",
}, },

View File

@ -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")

Binary file not shown.

View File

@ -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,
) )

View 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())

View File

@ -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;