forked from ScoDoc/ScoDoc
Modifie modèles identite/Admission. Modernise code et tests unitaires.
This commit is contained in:
parent
3000cfe7ba
commit
90b4b4b5d6
@ -45,7 +45,7 @@ class ScoDocModel:
|
|||||||
The instance is added to the session (but not flushed nor committed).
|
The instance is added to the session (but not flushed nor committed).
|
||||||
Use only relevant arributes for the given model and ignore others.
|
Use only relevant arributes for the given model and ignore others.
|
||||||
"""
|
"""
|
||||||
args = cls.filter_model_attributes(data)
|
args = cls.convert_dict_fields(cls.filter_model_attributes(data))
|
||||||
obj = cls(**args)
|
obj = cls(**args)
|
||||||
db.session.add(obj)
|
db.session.add(obj)
|
||||||
return obj
|
return obj
|
||||||
|
@ -26,7 +26,17 @@ class Departement(db.Model):
|
|||||||
) # sur page d'accueil
|
) # sur page d'accueil
|
||||||
|
|
||||||
# entreprises = db.relationship("Entreprise", lazy="dynamic", backref="departement")
|
# entreprises = db.relationship("Entreprise", lazy="dynamic", backref="departement")
|
||||||
etudiants = db.relationship("Identite", lazy="dynamic", backref="departement")
|
etudiants = db.relationship(
|
||||||
|
"Identite",
|
||||||
|
back_populates="departement",
|
||||||
|
cascade="all,delete-orphan",
|
||||||
|
lazy="dynamic",
|
||||||
|
)
|
||||||
|
# This means if a Departement is deleted, all related Identite instances are also
|
||||||
|
# deleted (all,delete). The orphan part means that if an Identite instance becomes
|
||||||
|
# detached from its parent Departement (for example, by setting my_identite.departement = None),
|
||||||
|
# it will be deleted.
|
||||||
|
|
||||||
formations = db.relationship("Formation", lazy="dynamic", backref="departement")
|
formations = db.relationship("Formation", lazy="dynamic", backref="departement")
|
||||||
formsemestres = db.relationship(
|
formsemestres = db.relationship(
|
||||||
"FormSemestre", lazy="dynamic", backref="departement"
|
"FormSemestre", lazy="dynamic", backref="departement"
|
||||||
|
@ -22,21 +22,27 @@ from app.scodoc.sco_exceptions import ScoInvalidParamError, ScoValueError
|
|||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
class Identite(db.Model):
|
class Identite(db.Model, models.ScoDocModel):
|
||||||
"""étudiant"""
|
"""étudiant"""
|
||||||
|
|
||||||
__tablename__ = "identite"
|
__tablename__ = "identite"
|
||||||
__table_args__ = (
|
|
||||||
db.UniqueConstraint("dept_id", "code_nip"),
|
|
||||||
db.UniqueConstraint("dept_id", "code_ine"),
|
|
||||||
db.CheckConstraint("civilite IN ('M', 'F', 'X')"),
|
|
||||||
db.CheckConstraint("civilite_etat_civil IN ('M', 'F', 'X')"),
|
|
||||||
)
|
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
etudid = db.synonym("id")
|
etudid = db.synonym("id")
|
||||||
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
|
||||||
|
|
||||||
|
admission_id = db.Column(db.Integer, db.ForeignKey("admissions.id"), nullable=True)
|
||||||
|
admission = db.relationship(
|
||||||
|
"Admission",
|
||||||
|
back_populates="etud",
|
||||||
|
uselist=False,
|
||||||
|
cascade="all,delete",
|
||||||
|
single_parent=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
dept_id = db.Column(
|
||||||
|
db.Integer, db.ForeignKey("departement.id"), index=True, nullable=False
|
||||||
|
)
|
||||||
|
departement = db.relationship("Departement", back_populates="etudiants")
|
||||||
nom = db.Column(db.Text())
|
nom = db.Column(db.Text())
|
||||||
prenom = db.Column(db.Text())
|
prenom = db.Column(db.Text())
|
||||||
nom_usuel = db.Column(db.Text())
|
nom_usuel = db.Column(db.Text())
|
||||||
@ -53,7 +59,10 @@ class Identite(db.Model):
|
|||||||
dept_naissance = db.Column(db.Text())
|
dept_naissance = db.Column(db.Text())
|
||||||
nationalite = db.Column(db.Text())
|
nationalite = db.Column(db.Text())
|
||||||
statut = db.Column(db.Text())
|
statut = db.Column(db.Text())
|
||||||
boursier = db.Column(db.Boolean()) # True si boursier ('O' en ScoDoc7)
|
boursier = db.Column(
|
||||||
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
|
)
|
||||||
|
"True si boursier"
|
||||||
photo_filename = db.Column(db.Text())
|
photo_filename = db.Column(db.Text())
|
||||||
# Codes INE et NIP pas unique car le meme etud peut etre ds plusieurs dept
|
# Codes INE et NIP pas unique car le meme etud peut etre ds plusieurs dept
|
||||||
code_nip = db.Column(db.Text(), index=True)
|
code_nip = db.Column(db.Text(), index=True)
|
||||||
@ -61,11 +70,37 @@ class Identite(db.Model):
|
|||||||
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
||||||
# ne pas utiliser après migrate_scodoc7_dept_archives
|
# ne pas utiliser après migrate_scodoc7_dept_archives
|
||||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||||
#
|
|
||||||
adresses = db.relationship("Adresse", lazy="dynamic", backref="etud")
|
# ----- Contraintes
|
||||||
|
__table_args__ = (
|
||||||
|
# Define a unique constraint on (dept_id, code_nip) when code_nip is not NULL
|
||||||
|
db.UniqueConstraint("dept_id", "code_nip", name="unique_dept_nip_except_null"),
|
||||||
|
db.Index(
|
||||||
|
"unique_dept_nip_except_null",
|
||||||
|
"dept_id",
|
||||||
|
"code_nip",
|
||||||
|
unique=True,
|
||||||
|
postgresql_where=(code_nip.isnot(None)),
|
||||||
|
),
|
||||||
|
# Define a unique constraint on (dept_id, code_ine) when code_ine is not NULL
|
||||||
|
db.UniqueConstraint("dept_id", "code_ine", name="unique_dept_ine_except_null"),
|
||||||
|
db.Index(
|
||||||
|
"unique_dept_ine_except_null",
|
||||||
|
"dept_id",
|
||||||
|
"code_ine",
|
||||||
|
unique=True,
|
||||||
|
postgresql_where=(code_ine.isnot(None)),
|
||||||
|
),
|
||||||
|
db.CheckConstraint("civilite IN ('M', 'F', 'X')"),
|
||||||
|
db.CheckConstraint("civilite_etat_civil IN ('M', 'F', 'X')"),
|
||||||
|
)
|
||||||
|
# ----- Relations
|
||||||
|
adresses = db.relationship(
|
||||||
|
"Adresse", back_populates="etud", cascade="all,delete", lazy="dynamic"
|
||||||
|
)
|
||||||
|
|
||||||
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
|
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
|
||||||
#
|
#
|
||||||
admission = db.relationship("Admission", backref="identite", lazy="dynamic")
|
|
||||||
dispense_ues = db.relationship(
|
dispense_ues = db.relationship(
|
||||||
"DispenseUE",
|
"DispenseUE",
|
||||||
back_populates="etud",
|
back_populates="etud",
|
||||||
@ -125,8 +160,7 @@ class Identite(db.Model):
|
|||||||
copy = self.__class__(**d)
|
copy = self.__class__(**d)
|
||||||
db.session.add(copy)
|
db.session.add(copy)
|
||||||
copy.adresses = [adr.clone() for adr in self.adresses]
|
copy.adresses = [adr.clone() for adr in self.adresses]
|
||||||
for admission in self.admission:
|
copy.admission = self.admission.clone()
|
||||||
copy.admission.append(admission.clone())
|
|
||||||
log(
|
log(
|
||||||
f"cloning etud <{self.id} {self.nom!r} {self.prenom!r}> in dept_id={new_dept_id}"
|
f"cloning etud <{self.id} {self.nom!r} {self.prenom!r}> in dept_id={new_dept_id}"
|
||||||
)
|
)
|
||||||
@ -157,11 +191,25 @@ class Identite(db.Model):
|
|||||||
return cls.query.filter_by(id=etudid).first_or_404()
|
return cls.query.filter_by(id=etudid).first_or_404()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_etud(cls, **args):
|
def create_etud(cls, **args) -> "Identite":
|
||||||
"Crée un étudiant, avec admission et adresse vides."
|
"Crée un étudiant, avec admission et adresse vides."
|
||||||
etud: Identite = cls(**args)
|
etud: Identite = cls(**args)
|
||||||
etud.adresses.append(Adresse(typeadresse="domicile"))
|
etud.adresses.append(Adresse(typeadresse="domicile"))
|
||||||
etud.admission.append(Admission())
|
etud.admission = Admission()
|
||||||
|
return etud
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_dict(cls, data) -> "Identite":
|
||||||
|
"""Crée un étudiant à partir d'un dict, avec admission et adresse vides.
|
||||||
|
(added to session but not flushed nor commited)
|
||||||
|
"""
|
||||||
|
etud: Identite = super(cls, cls).create_from_dict(data)
|
||||||
|
if (data.get("admission_id", None) is None) and (
|
||||||
|
data.get("admission", None) is None
|
||||||
|
):
|
||||||
|
etud.admission = Admission()
|
||||||
|
etud.adresses.append(Adresse(typeadresse="domicile"))
|
||||||
|
db.session.flush()
|
||||||
return etud
|
return etud
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -263,7 +311,9 @@ class Identite(db.Model):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert_dict_fields(cls, args: dict) -> dict:
|
def convert_dict_fields(cls, args: dict) -> dict:
|
||||||
"Convert fields in the given dict. No other side effect"
|
"""Convert fields in the given dict. No other side effect.
|
||||||
|
If required dept_id is not specified, set it to the current dept.
|
||||||
|
"""
|
||||||
fs_uppercase = {"nom", "prenom", "prenom_etat_civil"}
|
fs_uppercase = {"nom", "prenom", "prenom_etat_civil"}
|
||||||
fs_empty_stored_as_nulls = {
|
fs_empty_stored_as_nulls = {
|
||||||
"nom",
|
"nom",
|
||||||
@ -279,6 +329,8 @@ class Identite(db.Model):
|
|||||||
"code_ine",
|
"code_ine",
|
||||||
}
|
}
|
||||||
args_dict = {}
|
args_dict = {}
|
||||||
|
if not "dept_id" in args:
|
||||||
|
args["dept_id"] = g.scodoc_dept_id
|
||||||
for key, value in args.items():
|
for key, value in args.items():
|
||||||
if hasattr(cls, key) and not isinstance(getattr(cls, key, None), property):
|
if hasattr(cls, key) and not isinstance(getattr(cls, key, None), property):
|
||||||
# compat scodoc7 (mauvaise idée de l'époque)
|
# compat scodoc7 (mauvaise idée de l'époque)
|
||||||
@ -286,8 +338,10 @@ class Identite(db.Model):
|
|||||||
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":
|
if key == "civilite": # requis
|
||||||
value = input_civilite(value)
|
value = input_civilite(value)
|
||||||
|
elif key == "civilite_etat_civil":
|
||||||
|
value = input_civilite(value) if value else None
|
||||||
elif key == "boursier":
|
elif key == "boursier":
|
||||||
value = bool(value)
|
value = bool(value)
|
||||||
elif key == "date_naissance":
|
elif key == "date_naissance":
|
||||||
@ -295,16 +349,6 @@ class Identite(db.Model):
|
|||||||
args_dict[key] = value
|
args_dict[key] = value
|
||||||
return args_dict
|
return args_dict
|
||||||
|
|
||||||
def from_dict(self, args: dict):
|
|
||||||
"update fields given in dict. Add to session but don't commit."
|
|
||||||
args_dict = Identite.convert_dict_fields(args)
|
|
||||||
args_dict.pop("id", None)
|
|
||||||
args_dict.pop("etudid", None)
|
|
||||||
for key, value in args_dict.items():
|
|
||||||
if hasattr(self, key):
|
|
||||||
setattr(self, key, value)
|
|
||||||
db.session.add(self)
|
|
||||||
|
|
||||||
def to_dict_short(self) -> dict:
|
def to_dict_short(self) -> dict:
|
||||||
"""Les champs essentiels"""
|
"""Les champs essentiels"""
|
||||||
return {
|
return {
|
||||||
@ -325,17 +369,17 @@ class Identite(db.Model):
|
|||||||
"""Représentation dictionnaire,
|
"""Représentation dictionnaire,
|
||||||
compatible ScoDoc7 mais sans infos admission
|
compatible ScoDoc7 mais sans infos admission
|
||||||
"""
|
"""
|
||||||
e = dict(self.__dict__)
|
e_dict = self.__dict__.copy() # dict(self.__dict__)
|
||||||
e.pop("_sa_instance_state", None)
|
e_dict.pop("_sa_instance_state", None)
|
||||||
# ScoDoc7 output_formators: (backward compat)
|
# ScoDoc7 output_formators: (backward compat)
|
||||||
e["etudid"] = self.id
|
e_dict["etudid"] = self.id
|
||||||
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
|
e_dict["date_naissance"] = ndb.DateISOtoDMY(e_dict["date_naissance"])
|
||||||
e["ne"] = self.e
|
e_dict["ne"] = self.e
|
||||||
e["nomprenom"] = self.nomprenom
|
e_dict["nomprenom"] = self.nomprenom
|
||||||
adresse = self.adresses.first()
|
adresse = self.adresses.first()
|
||||||
if adresse:
|
if adresse:
|
||||||
e.update(adresse.to_dict())
|
e_dict.update(adresse.to_dict())
|
||||||
return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty
|
return {k: v or "" for k, v in e_dict.items()} # convert_null_outputs_to_empty
|
||||||
|
|
||||||
def to_dict_bul(self, include_urls=True):
|
def to_dict_bul(self, include_urls=True):
|
||||||
"""Infos exportées dans les bulletins
|
"""Infos exportées dans les bulletins
|
||||||
@ -382,7 +426,7 @@ class Identite(db.Model):
|
|||||||
"""Représentation dictionnaire pour export API, avec adresses et admission."""
|
"""Représentation dictionnaire pour export API, avec adresses et admission."""
|
||||||
e = dict(self.__dict__)
|
e = dict(self.__dict__)
|
||||||
e.pop("_sa_instance_state", None)
|
e.pop("_sa_instance_state", None)
|
||||||
admission = self.admission.first()
|
admission = self.admission
|
||||||
e["admission"] = admission.to_dict() if admission is not None else None
|
e["admission"] = admission.to_dict() if admission is not None else None
|
||||||
e["adresses"] = [adr.to_dict() for adr in self.adresses]
|
e["adresses"] = [adr.to_dict() for adr in self.adresses]
|
||||||
e["dept_acronym"] = self.departement.acronym
|
e["dept_acronym"] = self.departement.acronym
|
||||||
@ -648,11 +692,14 @@ def make_etud_args(
|
|||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def input_civilite(s):
|
def input_civilite(s: str) -> str:
|
||||||
"""Converts external representation of civilite to internal:
|
"""Converts external representation of civilite to internal:
|
||||||
'M', 'F', or 'X' (and nothing else).
|
'M', 'F', or 'X' (and nothing else).
|
||||||
Raises ScoValueError if conversion fails.
|
Raises ScoValueError if conversion fails.
|
||||||
"""
|
"""
|
||||||
|
if not isinstance(s, str):
|
||||||
|
breakpoint()
|
||||||
|
raise ScoValueError("valeur invalide pour la civilité (chaine attendue)")
|
||||||
s = s.upper().strip()
|
s = s.upper().strip()
|
||||||
if s in ("M", "M.", "MR", "H"):
|
if s in ("M", "M.", "MR", "H"):
|
||||||
return "M"
|
return "M"
|
||||||
@ -688,10 +735,10 @@ class Adresse(db.Model, models.ScoDocModel):
|
|||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
adresse_id = db.synonym("id")
|
adresse_id = db.synonym("id")
|
||||||
etudid = db.Column(
|
etudid = db.Column(db.Integer, db.ForeignKey("identite.id"), nullable=False)
|
||||||
db.Integer,
|
# Relationship to Identite
|
||||||
db.ForeignKey("identite.id", ondelete="CASCADE"),
|
etud = db.relationship("Identite", back_populates="adresses")
|
||||||
)
|
|
||||||
email = db.Column(db.Text()) # mail institutionnel
|
email = db.Column(db.Text()) # mail institutionnel
|
||||||
emailperso = db.Column(db.Text) # email personnel (exterieur)
|
emailperso = db.Column(db.Text) # email personnel (exterieur)
|
||||||
domicile = db.Column(db.Text)
|
domicile = db.Column(db.Text)
|
||||||
@ -722,10 +769,13 @@ class Admission(db.Model, models.ScoDocModel):
|
|||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
adm_id = db.synonym("id")
|
adm_id = db.synonym("id")
|
||||||
etudid = db.Column(
|
# obsoleted by migration 497ba81343f7_identite_admission.py:
|
||||||
db.Integer,
|
# etudid = db.Column(
|
||||||
db.ForeignKey("identite.id", ondelete="CASCADE"),
|
# db.Integer,
|
||||||
)
|
# db.ForeignKey("identite.id", ondelete="CASCADE"),
|
||||||
|
# )
|
||||||
|
etud = db.relationship("Identite", back_populates="admission", uselist=False)
|
||||||
|
|
||||||
# Anciens champs de ScoDoc7, à revoir pour être plus générique et souple
|
# Anciens champs de ScoDoc7, à revoir pour être plus générique et souple
|
||||||
# notamment dans le cadre du bac 2021
|
# notamment dans le cadre du bac 2021
|
||||||
# de plus, certaines informations liées à APB ne sont plus disponibles
|
# de plus, certaines informations liées à APB ne sont plus disponibles
|
||||||
|
@ -584,14 +584,14 @@ class TF(object):
|
|||||||
elif input_type == "menu":
|
elif input_type == "menu":
|
||||||
lem.append('<select name="%s" id="%s" %s>' % (field, wid, attribs))
|
lem.append('<select name="%s" id="%s" %s>' % (field, wid, attribs))
|
||||||
labels = descr.get("labels", descr["allowed_values"])
|
labels = descr.get("labels", descr["allowed_values"])
|
||||||
for i in range(len(labels)):
|
allowed_values = list(descr["allowed_values"])
|
||||||
if str(descr["allowed_values"][i]) == str(values[field]):
|
for i, label in enumerate(labels):
|
||||||
|
if str(allowed_values[i]) == str(values[field]):
|
||||||
selected = "selected"
|
selected = "selected"
|
||||||
else:
|
else:
|
||||||
selected = ""
|
selected = ""
|
||||||
lem.append(
|
lem.append(
|
||||||
'<option value="%s" %s>%s</option>'
|
f"""<option value="{allowed_values[i]}" {selected}>{label}</option>"""
|
||||||
% (descr["allowed_values"][i], selected, labels[i])
|
|
||||||
)
|
)
|
||||||
lem.append("</select>")
|
lem.append("</select>")
|
||||||
elif input_type == "checkbox" or input_type == "boolcheckbox":
|
elif input_type == "checkbox" or input_type == "boolcheckbox":
|
||||||
|
@ -457,8 +457,8 @@ def dictfilter(d, fields, filter_nulls=True):
|
|||||||
# --- Misc Tools
|
# --- Misc Tools
|
||||||
|
|
||||||
|
|
||||||
def DateDMYtoISO(dmy: str, null_is_empty=False) -> str: # XXX deprecated
|
def DateDMYtoISO(dmy: str, null_is_empty=False) -> str | None: # XXX deprecated
|
||||||
"""Convert date string from french format to ISO.
|
"""Convert date string from french format (or ISO) to ISO.
|
||||||
If null_is_empty (default false), returns "" if no input.
|
If null_is_empty (default false), returns "" if no input.
|
||||||
"""
|
"""
|
||||||
if not dmy:
|
if not dmy:
|
||||||
@ -472,8 +472,11 @@ def DateDMYtoISO(dmy: str, null_is_empty=False) -> str: # XXX deprecated
|
|||||||
raise ScoValueError(f'Date (j/m/a) invalide: "{dmy}"')
|
raise ScoValueError(f'Date (j/m/a) invalide: "{dmy}"')
|
||||||
try:
|
try:
|
||||||
dt = datetime.datetime.strptime(dmy, "%d/%m/%Y")
|
dt = datetime.datetime.strptime(dmy, "%d/%m/%Y")
|
||||||
except ValueError as exc:
|
except ValueError:
|
||||||
raise ScoValueError(f'Date (j/m/a) invalide: "{dmy}"') from exc
|
try:
|
||||||
|
dt = datetime.datetime.fromisoformat(dmy)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError(f'Date (j/m/a or iso) invalide: "{dmy}"') from exc
|
||||||
return dt.date().isoformat()
|
return dt.date().isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
@ -509,7 +509,6 @@ _admissionEditor = ndb.EditableTable(
|
|||||||
"adm_id",
|
"adm_id",
|
||||||
(
|
(
|
||||||
"adm_id",
|
"adm_id",
|
||||||
"etudid",
|
|
||||||
"annee",
|
"annee",
|
||||||
"bac",
|
"bac",
|
||||||
"specialite",
|
"specialite",
|
||||||
@ -556,33 +555,33 @@ admission_edit = _admissionEditor.edit
|
|||||||
# Edition simultanee de identite et admission
|
# Edition simultanee de identite et admission
|
||||||
class EtudIdentEditor(object):
|
class EtudIdentEditor(object):
|
||||||
def create(self, cnx, args):
|
def create(self, cnx, args):
|
||||||
|
admission_id = admission_create(cnx, args)
|
||||||
|
args["admission_id"] = admission_id
|
||||||
etudid = identite_create(cnx, args)
|
etudid = identite_create(cnx, args)
|
||||||
args["etudid"] = etudid
|
|
||||||
admission_create(cnx, args)
|
|
||||||
return etudid
|
return etudid
|
||||||
|
|
||||||
def list(self, *args, **kw):
|
def list(self, *args, **kw) -> list[dict]:
|
||||||
R = identite_list(*args, **kw)
|
etuds_dict = identite_list(*args, **kw)
|
||||||
Ra = admission_list(*args, **kw)
|
|
||||||
# print len(R), len(Ra)
|
|
||||||
# merge: add admission fields to identite
|
|
||||||
A = {}
|
|
||||||
for r in Ra:
|
|
||||||
A[r["etudid"]] = r
|
|
||||||
res = []
|
res = []
|
||||||
for i in R:
|
for etud_dict in etuds_dict:
|
||||||
res.append(i)
|
res.append(etud_dict)
|
||||||
if i["etudid"] in A:
|
adms_dict = (
|
||||||
|
admission_list(args[0], args={"id": etud_dict["admission_id"]})
|
||||||
|
if etud_dict["admission_id"]
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
if adms_dict:
|
||||||
# merge
|
# merge
|
||||||
res[-1].update(A[i["etudid"]])
|
adms_dict[0].pop("id", None)
|
||||||
|
adms_dict[0].pop("etudid", None)
|
||||||
|
res[-1] |= adms_dict[0]
|
||||||
else: # pas d'etudiant trouve
|
else: # pas d'etudiant trouve
|
||||||
# print "*** pas d'info admission pour %s" % str(i)
|
|
||||||
void_adm = {
|
void_adm = {
|
||||||
k: None
|
k: None
|
||||||
for k in _admissionEditor.dbfields
|
for k in _admissionEditor.dbfields
|
||||||
if k != "etudid" and k != "adm_id"
|
if k not in ("id", "etudid", "adm_id")
|
||||||
}
|
}
|
||||||
res[-1].update(void_adm)
|
res[-1] |= void_adm
|
||||||
# tri par nom
|
# tri par nom
|
||||||
res.sort(key=itemgetter("nom", "prenom"))
|
res.sort(key=itemgetter("nom", "prenom"))
|
||||||
return res
|
return res
|
||||||
@ -638,7 +637,7 @@ def create_etud(cnx, args: dict = None):
|
|||||||
etud = Identite.create_etud(**args_dict)
|
etud = Identite.create_etud(**args_dict)
|
||||||
db.session.add(etud)
|
db.session.add(etud)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
admission = etud.admission.first()
|
admission = etud.admission
|
||||||
admission.from_dict(args)
|
admission.from_dict(args)
|
||||||
db.session.add(admission)
|
db.session.add(admission)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -37,7 +37,7 @@ import time
|
|||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import ScolarNews, GroupDescr
|
from app.models import Identite, GroupDescr, ScolarNews
|
||||||
from app.models.etudiants import input_civilite
|
from app.models.etudiants import input_civilite
|
||||||
|
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
@ -327,20 +327,18 @@ def scolars_import_excel_file(
|
|||||||
values = {}
|
values = {}
|
||||||
fs = line
|
fs = line
|
||||||
# remove quotes
|
# remove quotes
|
||||||
for i in range(len(fs)):
|
for i, field in enumerate(fs):
|
||||||
if fs[i] and (
|
if field and (
|
||||||
(fs[i][0] == '"' and fs[i][-1] == '"')
|
(field[0] == '"' and field[-1] == '"')
|
||||||
or (fs[i][0] == "'" and fs[i][-1] == "'")
|
or (field[0] == "'" and field[-1] == "'")
|
||||||
):
|
):
|
||||||
fs[i] = fs[i][1:-1]
|
fs[i] = field[1:-1]
|
||||||
for i in range(len(fs)):
|
for i, field in enumerate(fs):
|
||||||
val = fs[i].strip()
|
val = field.strip()
|
||||||
typ, table, an, descr, aliases = tuple(titles[titleslist[i]])
|
typ, table, allow_nulls, descr, aliases = tuple(titles[titleslist[i]])
|
||||||
# log('field %s: %s %s %s %s'%(titleslist[i], table, typ, an, descr))
|
if not val and not allow_nulls:
|
||||||
if not val and not an:
|
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"line %d: null value not allowed in column %s"
|
f"line {linenum}: null value not allowed in column {titleslist[i]}"
|
||||||
% (linenum, titleslist[i])
|
|
||||||
)
|
)
|
||||||
if val == "":
|
if val == "":
|
||||||
val = None
|
val = None
|
||||||
@ -349,11 +347,11 @@ def scolars_import_excel_file(
|
|||||||
val = val.replace(",", ".") # si virgule a la française
|
val = val.replace(",", ".") # si virgule a la française
|
||||||
try:
|
try:
|
||||||
val = float(val)
|
val = float(val)
|
||||||
except:
|
except (ValueError, TypeError) as exc:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"valeur nombre reel invalide (%s) sur line %d, colonne %s"
|
f"""valeur nombre reel invalide ({
|
||||||
% (val, linenum, titleslist[i])
|
val}) sur ligne {linenum}, colonne {titleslist[i]}"""
|
||||||
)
|
) from exc
|
||||||
elif typ == "integer":
|
elif typ == "integer":
|
||||||
try:
|
try:
|
||||||
# on doit accepter des valeurs comme "2006.0"
|
# on doit accepter des valeurs comme "2006.0"
|
||||||
@ -362,20 +360,22 @@ def scolars_import_excel_file(
|
|||||||
if val % 1.0 > 1e-4:
|
if val % 1.0 > 1e-4:
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
val = int(val)
|
val = int(val)
|
||||||
except:
|
except (ValueError, TypeError) as exc:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"valeur nombre entier invalide (%s) sur ligne %d, colonne %s"
|
f"""valeur nombre entier invalide ({
|
||||||
% (val, linenum, titleslist[i])
|
val}) sur ligne {linenum}, colonne {titleslist[i]}"""
|
||||||
)
|
) from exc
|
||||||
# xxx Ad-hoc checks (should be in format description)
|
# Ad-hoc checks (should be in format description)
|
||||||
if titleslist[i].lower() == "sexe":
|
if titleslist[i].lower() == "civilite":
|
||||||
try:
|
try:
|
||||||
val = input_civilite(val)
|
val = input_civilite(val)
|
||||||
except:
|
except ScoValueError as exc:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"valeur invalide pour 'SEXE' (doit etre 'M', 'F', ou 'MME', 'H', 'X' ou vide, mais pas '%s') ligne %d, colonne %s"
|
f"""valeur invalide pour 'civilite'
|
||||||
% (val, linenum, titleslist[i])
|
(doit etre 'M', 'F', 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":
|
||||||
if val:
|
if val:
|
||||||
@ -383,7 +383,8 @@ def scolars_import_excel_file(
|
|||||||
val = sco_excel.xldate_as_datetime(val)
|
val = sco_excel.xldate_as_datetime(val)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
f"date invalide ({val}) sur ligne {linenum}, colonne {titleslist[i]}"
|
f"""date invalide ({val}) sur ligne {
|
||||||
|
linenum}, colonne {titleslist[i]}"""
|
||||||
) from exc
|
) from exc
|
||||||
# INE
|
# INE
|
||||||
if (
|
if (
|
||||||
@ -392,8 +393,7 @@ def scolars_import_excel_file(
|
|||||||
and not val
|
and not val
|
||||||
):
|
):
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"Code INE manquant sur ligne %d, colonne %s"
|
"Code INE manquant sur ligne {linenum}, colonne {titleslist[i]}"
|
||||||
% (linenum, titleslist[i])
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# --
|
# --
|
||||||
@ -422,7 +422,6 @@ def scolars_import_excel_file(
|
|||||||
np_imported_homonyms += 1
|
np_imported_homonyms += 1
|
||||||
# Insert in DB tables
|
# Insert in DB tables
|
||||||
_import_one_student(
|
_import_one_student(
|
||||||
cnx,
|
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
values,
|
values,
|
||||||
GroupIdInferers,
|
GroupIdInferers,
|
||||||
@ -521,7 +520,6 @@ def students_import_admission(
|
|||||||
|
|
||||||
|
|
||||||
def _import_one_student(
|
def _import_one_student(
|
||||||
cnx,
|
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
values,
|
values,
|
||||||
GroupIdInferers,
|
GroupIdInferers,
|
||||||
@ -533,22 +531,22 @@ def _import_one_student(
|
|||||||
Import d'un étudiant et inscription dans le semestre.
|
Import d'un étudiant et inscription dans le semestre.
|
||||||
Return: id du semestre dans lequel il a été inscrit.
|
Return: id du semestre dans lequel il a été inscrit.
|
||||||
"""
|
"""
|
||||||
log(
|
log(f"scolars_import_excel_file: formsemestre_id={formsemestre_id} values={values}")
|
||||||
"scolars_import_excel_file: formsemestre_id=%s values=%s"
|
|
||||||
% (formsemestre_id, str(values))
|
|
||||||
)
|
|
||||||
# Identite
|
# Identite
|
||||||
args = values.copy()
|
args = values.copy()
|
||||||
etudid = sco_etud.identite_create(cnx, args)
|
|
||||||
created_etudids.append(etudid)
|
|
||||||
# Admissions
|
|
||||||
args["etudid"] = etudid
|
|
||||||
args["annee"] = annee_courante
|
args["annee"] = annee_courante
|
||||||
_ = sco_etud.admission_create(cnx, args)
|
etud: Identite = Identite.create_from_dict(args)
|
||||||
|
etud.admission.from_dict(args)
|
||||||
|
etudid = etud.id
|
||||||
|
created_etudids.append(etudid)
|
||||||
# Adresse
|
# Adresse
|
||||||
args["typeadresse"] = "domicile"
|
args["typeadresse"] = "domicile"
|
||||||
args["description"] = "(infos admission)"
|
args["description"] = "(infos admission)"
|
||||||
_ = sco_etud.adresse_create(cnx, args)
|
adresse = etud.adresses.first()
|
||||||
|
adresse.from_dict(args)
|
||||||
|
db.session.add(etud)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
# Inscription au semestre
|
# Inscription au semestre
|
||||||
args["etat"] = scu.INSCRIT # etat insc. semestre
|
args["etat"] = scu.INSCRIT # etat insc. semestre
|
||||||
if formsemestre_id:
|
if formsemestre_id:
|
||||||
@ -574,7 +572,7 @@ def _import_one_student(
|
|||||||
group_ids = list({}.fromkeys(group_ids).keys()) # uniq
|
group_ids = list({}.fromkeys(group_ids).keys()) # uniq
|
||||||
if None in group_ids:
|
if None in group_ids:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"groupe invalide sur la ligne %d (groupe %s)" % (linenum, groupes)
|
f"groupe invalide sur la ligne {linenum} (groupe {groupes})"
|
||||||
)
|
)
|
||||||
|
|
||||||
do_formsemestre_inscription_with_modules(
|
do_formsemestre_inscription_with_modules(
|
||||||
@ -605,16 +603,18 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
étant ignorés).
|
étant ignorés).
|
||||||
|
|
||||||
On tolère plusieurs variantes pour chaque nom de colonne (ici aussi, la casse, les espaces
|
On tolère plusieurs variantes pour chaque nom de colonne (ici aussi, la casse, les espaces
|
||||||
et les caractères spéciaux sont ignorés. Ainsi, la colonne "Prénom:" sera considéré comme "prenom".
|
et les caractères spéciaux sont ignorés.
|
||||||
|
Ainsi, la colonne "Prénom:" sera considéré comme "prenom".
|
||||||
|
|
||||||
Le parametre type_admission remplace les valeurs vides (dans la base ET dans le fichier importé) du champ type_admission.
|
Le parametre type_admission remplace les valeurs vides (dans la base ET
|
||||||
|
dans le fichier importé) du champ type_admission.
|
||||||
Si une valeur existe ou est présente dans le fichier importé, ce paramètre est ignoré.
|
Si une valeur existe ou est présente dans le fichier importé, ce paramètre est ignoré.
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
- choix onglet du classeur
|
- choix onglet du classeur
|
||||||
"""
|
"""
|
||||||
|
|
||||||
log("scolars_import_admission: formsemestre_id=%s" % formsemestre_id)
|
log(f"scolars_import_admission: formsemestre_id={formsemestre_id}")
|
||||||
members = sco_groups.get_group_members(
|
members = sco_groups.get_group_members(
|
||||||
sco_groups.get_default_group(formsemestre_id)
|
sco_groups.get_default_group(formsemestre_id)
|
||||||
)
|
)
|
||||||
@ -670,7 +670,7 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
diag.append(msg)
|
diag.append(msg)
|
||||||
else:
|
else:
|
||||||
etud = etuds_by_nomprenom[(nom, prenom)]
|
etud = etuds_by_nomprenom[(nom, prenom)]
|
||||||
cur_adm = sco_etud.admission_list(cnx, args={"etudid": etud["etudid"]})[0]
|
cur_adm = sco_etud.admission_list(cnx, args={"id": etud["admission_id"]})[0]
|
||||||
# peuple les champs presents dans le tableau
|
# peuple les champs presents dans le tableau
|
||||||
args = {}
|
args = {}
|
||||||
for idx, field in fields.items():
|
for idx, field in fields.items():
|
||||||
@ -680,8 +680,8 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
val = convertor(line[idx])
|
val = convertor(line[idx])
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ScoFormatError(
|
raise ScoFormatError(
|
||||||
'scolars_import_admission: valeur invalide, ligne %d colonne %s: "%s"'
|
f"""scolars_import_admission: valeur invalide, ligne {
|
||||||
% (nline, field_name, line[idx]),
|
nline} colonne {field_name}: '{line[idx]}'""",
|
||||||
dest_url=url_for(
|
dest_url=url_for(
|
||||||
"scolar.form_students_import_infos_admissions",
|
"scolar.form_students_import_infos_admissions",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
@ -732,10 +732,10 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
)
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
diag.append("import de %s" % (etud["nomprenom"]))
|
diag.append(f"import de {etud['nomprenom']}")
|
||||||
n_import += 1
|
n_import += 1
|
||||||
nline += 1
|
nline += 1
|
||||||
diag.append("%d lignes importées" % n_import)
|
diag.append(f"{n_import} lignes importées")
|
||||||
if n_import > 0:
|
if n_import > 0:
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
||||||
return diag
|
return diag
|
||||||
|
@ -57,7 +57,7 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
"""
|
"""
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
etuds: Identite = nt.get_inscrits(order_by="moy") # tri par moy gen
|
etuds = nt.get_inscrits(order_by="moy") # tri par moy gen
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
|
|
||||||
etud_groups = sco_groups.formsemestre_get_etud_groupnames(formsemestre_id)
|
etud_groups = sco_groups.formsemestre_get_etud_groupnames(formsemestre_id)
|
||||||
@ -240,7 +240,6 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
cells.append(sheet.make_cell(etud.code_nip))
|
cells.append(sheet.make_cell(etud.code_nip))
|
||||||
if sco_preferences.get_preference("prepa_jury_ine"):
|
if sco_preferences.get_preference("prepa_jury_ine"):
|
||||||
cells.append(sheet.make_cell(etud.code_ine))
|
cells.append(sheet.make_cell(etud.code_ine))
|
||||||
admission = etud.admission.first()
|
|
||||||
cells += sheet.make_row(
|
cells += sheet.make_row(
|
||||||
[
|
[
|
||||||
etud.id,
|
etud.id,
|
||||||
@ -248,9 +247,9 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
sco_etud.format_nom(etud.nom),
|
sco_etud.format_nom(etud.nom),
|
||||||
sco_etud.format_prenom(etud.prenom),
|
sco_etud.format_prenom(etud.prenom),
|
||||||
etud.date_naissance,
|
etud.date_naissance,
|
||||||
admission.bac,
|
etud.admission.bac if etud.admission else "",
|
||||||
admission.specialite,
|
etud.admission.specialite if etud.admission else "",
|
||||||
admission.classement,
|
etud.admission.classement if etud.admission else "",
|
||||||
parcours[etud.id],
|
parcours[etud.id],
|
||||||
groupestd[etud.id],
|
groupestd[etud.id],
|
||||||
]
|
]
|
||||||
|
@ -192,7 +192,7 @@ def _formsemestre_inscriptions_by_bac(formsemestre: FormSemestre) -> defaultdict
|
|||||||
"liste d'inscriptions, par type de bac"
|
"liste d'inscriptions, par type de bac"
|
||||||
inscriptions_by_bac = defaultdict(list) # bac : etuds
|
inscriptions_by_bac = defaultdict(list) # bac : etuds
|
||||||
for inscr in formsemestre.inscriptions:
|
for inscr in formsemestre.inscriptions:
|
||||||
adm = inscr.etud.admission.first()
|
adm = inscr.etud.admission
|
||||||
bac = adm.get_bac().abbrev() if adm else "?"
|
bac = adm.get_bac().abbrev() if adm else "?"
|
||||||
inscriptions_by_bac[bac].append(inscr)
|
inscriptions_by_bac[bac].append(inscr)
|
||||||
return inscriptions_by_bac
|
return inscriptions_by_bac
|
||||||
|
@ -34,8 +34,8 @@ from operator import itemgetter
|
|||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import log
|
from app import db, log
|
||||||
from app.models import ScolarNews
|
from app.models import Admission, Adresse, Identite, ScolarNews
|
||||||
|
|
||||||
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
|
||||||
@ -592,7 +592,8 @@ def gender2civilite(gender):
|
|||||||
return "X" # "X" en général n'est pas affiché, donc bon choix si invalide
|
return "X" # "X" en général n'est pas affiché, donc bon choix si invalide
|
||||||
|
|
||||||
|
|
||||||
def get_opt_str(etud, k):
|
def get_opt_str(etud: dict, k) -> str | None:
|
||||||
|
"etud[k].strip() ou None"
|
||||||
v = etud.get(k, None)
|
v = etud.get(k, None)
|
||||||
if not v:
|
if not v:
|
||||||
return v
|
return v
|
||||||
@ -611,7 +612,7 @@ def get_annee_naissance(ddmmyyyyy: str) -> int:
|
|||||||
|
|
||||||
def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
|
def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
|
||||||
"""Inscrit les etudiants Apogee dans ce semestre."""
|
"""Inscrit les etudiants Apogee dans ce semestre."""
|
||||||
log("do_import_etuds_from_portal: a_importer=%s" % a_importer)
|
log(f"do_import_etuds_from_portal: a_importer={a_importer}")
|
||||||
if not a_importer:
|
if not a_importer:
|
||||||
return
|
return
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
@ -619,51 +620,52 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
|
|||||||
|
|
||||||
try: # --- begin DB transaction
|
try: # --- begin DB transaction
|
||||||
for key in a_importer:
|
for key in a_importer:
|
||||||
etud = etudsapo_ident[
|
etud_portal: dict = etudsapo_ident[key]
|
||||||
key
|
# -> toutes les infos renvoyées par le portail
|
||||||
] # on a ici toutes les infos renvoyées par le portail
|
|
||||||
|
|
||||||
# Traduit les infos portail en infos pour ScoDoc:
|
# Traduit les infos portail en infos pour ScoDoc:
|
||||||
address = etud.get("address", "").strip()
|
address = etud_portal.get("address", "").strip()
|
||||||
if address[-2:] == "\\n": # certains champs se terminent par \n
|
if address[-2:] == "\\n": # certains champs se terminent par \n
|
||||||
address = address[:-2]
|
address = address[:-2]
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
"code_nip": etud["nip"],
|
"code_nip": etud_portal["nip"],
|
||||||
"nom": etud["nom"].strip(),
|
"nom": etud_portal["nom"].strip(),
|
||||||
"prenom": etud["prenom"].strip(),
|
"prenom": etud_portal["prenom"].strip(),
|
||||||
# Les champs suivants sont facultatifs (pas toujours renvoyés par le portail)
|
# Les champs suivants sont facultatifs (pas toujours renvoyés par le portail)
|
||||||
"code_ine": etud.get("ine", "").strip(),
|
"code_ine": etud_portal.get("ine", "").strip(),
|
||||||
"civilite": gender2civilite(etud["gender"].strip()),
|
"civilite": gender2civilite(etud_portal["gender"].strip()),
|
||||||
"etape": etud.get("etape", None),
|
"etape": etud_portal.get("etape", None),
|
||||||
"email": etud.get("mail", "").strip(),
|
"email": etud_portal.get("mail", "").strip(),
|
||||||
"emailperso": etud.get("mailperso", "").strip(),
|
"emailperso": etud_portal.get("mailperso", "").strip(),
|
||||||
"date_naissance": etud.get("naissance", "").strip(),
|
"date_naissance": etud_portal.get("naissance", "").strip(),
|
||||||
"lieu_naissance": etud.get("ville_naissance", "").strip(),
|
"lieu_naissance": etud_portal.get("ville_naissance", "").strip(),
|
||||||
"dept_naissance": etud.get("code_dep_naissance", "").strip(),
|
"dept_naissance": etud_portal.get("code_dep_naissance", "").strip(),
|
||||||
"domicile": address,
|
"domicile": address,
|
||||||
"codepostaldomicile": etud.get("postalcode", "").strip(),
|
"codepostaldomicile": etud_portal.get("postalcode", "").strip(),
|
||||||
"villedomicile": etud.get("city", "").strip(),
|
"villedomicile": etud_portal.get("city", "").strip(),
|
||||||
"paysdomicile": etud.get("country", "").strip(),
|
"paysdomicile": etud_portal.get("country", "").strip(),
|
||||||
"telephone": etud.get("phone", "").strip(),
|
"telephone": etud_portal.get("phone", "").strip(),
|
||||||
"typeadresse": "domicile",
|
"typeadresse": "domicile",
|
||||||
"boursier": etud.get("bourse", None),
|
"boursier": etud_portal.get("bourse", None),
|
||||||
"description": "infos portail",
|
"description": "infos portail",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Identite
|
# Identite
|
||||||
args["etudid"] = sco_etud.identite_create(cnx, args)
|
etud: Identite = Identite.create_from_dict(args)
|
||||||
created_etudids.append(args["etudid"])
|
db.session.flush()
|
||||||
# Admissions
|
created_etudids.append(etud.id)
|
||||||
do_import_etud_admission(cnx, args["etudid"], etud)
|
|
||||||
|
|
||||||
# Adresse
|
# Adresse
|
||||||
sco_etud.adresse_create(cnx, args)
|
adresse = etud.adresses.first()
|
||||||
|
adresse.from_dict(args)
|
||||||
|
|
||||||
|
# Admissions
|
||||||
|
do_import_etud_admission(etud, etud_portal)
|
||||||
|
|
||||||
# Inscription au semestre
|
# Inscription au semestre
|
||||||
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
||||||
sem["formsemestre_id"],
|
sem["formsemestre_id"],
|
||||||
args["etudid"],
|
etud.id,
|
||||||
etat=scu.INSCRIT,
|
etat=scu.INSCRIT,
|
||||||
etape=args["etape"],
|
etape=args["etape"],
|
||||||
method="synchro_apogee",
|
method="synchro_apogee",
|
||||||
@ -713,53 +715,32 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def do_import_etud_admission(
|
def do_import_etud_admission(etud: Identite, etud_data: dict, import_identite=False):
|
||||||
cnx, etudid, etud, import_naissance=False, import_identite=False
|
|
||||||
):
|
|
||||||
"""Importe les donnees admission pour cet etud.
|
"""Importe les donnees admission pour cet etud.
|
||||||
etud est un dictionnaire traduit du XML portail
|
etud_data est un dictionnaire traduit du XML portail
|
||||||
"""
|
"""
|
||||||
annee_courante = time.localtime()[0]
|
annee_courante = time.localtime()[0]
|
||||||
serie_bac, spe_bac = get_bac(etud)
|
serie_bac, spe_bac = _get_bac(etud_data)
|
||||||
# Les champs n'ont pas les mêmes noms dans Apogee et dans ScoDoc:
|
# Les champs n'ont pas les mêmes noms dans Apogee et dans ScoDoc:
|
||||||
args = {
|
args = {
|
||||||
"etudid": etudid,
|
"annee": get_opt_str(etud_data, "inscription") or annee_courante,
|
||||||
"annee": get_opt_str(etud, "inscription") or annee_courante,
|
|
||||||
"bac": serie_bac,
|
"bac": serie_bac,
|
||||||
"specialite": spe_bac,
|
"specialite": spe_bac,
|
||||||
"annee_bac": get_opt_str(etud, "anneebac"),
|
"annee_bac": get_opt_str(etud_data, "anneebac"),
|
||||||
"codelycee": get_opt_str(etud, "lycee"),
|
"codelycee": get_opt_str(etud_data, "lycee"),
|
||||||
"nomlycee": get_opt_str(etud, "nom_lycee"),
|
"nomlycee": get_opt_str(etud_data, "nom_lycee"),
|
||||||
"villelycee": get_opt_str(etud, "ville_lycee"),
|
"villelycee": get_opt_str(etud_data, "ville_lycee"),
|
||||||
"codepostallycee": get_opt_str(etud, "codepostal_lycee"),
|
"codepostallycee": get_opt_str(etud_data, "codepostal_lycee"),
|
||||||
"boursier": get_opt_str(etud, "bourse"),
|
"boursier": get_opt_str(etud_data, "bourse"),
|
||||||
}
|
}
|
||||||
# log("do_import_etud_admission: etud=%s" % pprint.pformat(etud))
|
if etud.admission is None:
|
||||||
adm_list = sco_etud.admission_list(cnx, args={"etudid": etudid})
|
etud.admission = Admission()
|
||||||
if not adm_list:
|
args = {k: v for k, v in args.items() if v not in ("", None)}
|
||||||
sco_etud.admission_create(cnx, args) # -> adm_id
|
etud.admission.from_dict(args)
|
||||||
else:
|
|
||||||
# existing data: merge
|
|
||||||
adm_info = adm_list[0]
|
|
||||||
if get_opt_str(etud, "inscription"):
|
|
||||||
adm_info["annee"] = args["annee"]
|
|
||||||
keys = list(args.keys())
|
|
||||||
for k in keys:
|
|
||||||
if not args[k]:
|
|
||||||
del args[k]
|
|
||||||
adm_info.update(args)
|
|
||||||
sco_etud.admission_edit(cnx, adm_info)
|
|
||||||
# Traite cas particulier de la date de naissance pour anciens
|
|
||||||
# etudiants IUTV
|
|
||||||
if import_naissance and "naissance" in etud:
|
|
||||||
date_naissance = etud["naissance"].strip()
|
|
||||||
if date_naissance:
|
|
||||||
sco_etud.identite_edit_nocheck(
|
|
||||||
cnx, {"etudid": etudid, "date_naissance": date_naissance}
|
|
||||||
)
|
|
||||||
# Reimport des identités
|
# Reimport des identités
|
||||||
if import_identite:
|
if import_identite:
|
||||||
args = {"etudid": etudid}
|
args = {}
|
||||||
# Les champs n'ont pas les mêmes noms dans Apogee et dans ScoDoc:
|
# Les champs n'ont pas les mêmes noms dans Apogee et dans ScoDoc:
|
||||||
fields_apo_sco = [
|
fields_apo_sco = [
|
||||||
("naissance", "date_naissance"),
|
("naissance", "date_naissance"),
|
||||||
@ -771,18 +752,21 @@ def do_import_etud_admission(
|
|||||||
("bourse", "boursier"),
|
("bourse", "boursier"),
|
||||||
]
|
]
|
||||||
for apo_field, sco_field in fields_apo_sco:
|
for apo_field, sco_field in fields_apo_sco:
|
||||||
x = etud.get(apo_field, "").strip()
|
x = etud_data.get(apo_field, "").strip()
|
||||||
if x:
|
if x:
|
||||||
args[sco_field] = x
|
args[sco_field] = x
|
||||||
# Champs spécifiques:
|
# Champs spécifiques:
|
||||||
civilite = gender2civilite(etud["gender"].strip())
|
civilite = gender2civilite(etud_data["gender"].strip())
|
||||||
if civilite:
|
if civilite:
|
||||||
args["civilite"] = civilite
|
args["civilite"] = civilite
|
||||||
|
|
||||||
sco_etud.identite_edit_nocheck(cnx, args)
|
etud.from_dict(args)
|
||||||
|
db.session.add(etud)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.refresh(etud)
|
||||||
|
|
||||||
|
|
||||||
def get_bac(etud):
|
def _get_bac(etud) -> tuple[str | None, str | None]:
|
||||||
bac = get_opt_str(etud, "bac")
|
bac = get_opt_str(etud, "bac")
|
||||||
if not bac:
|
if not bac:
|
||||||
return None, None
|
return None, None
|
||||||
@ -820,14 +804,10 @@ def formsemestre_import_etud_admission(
|
|||||||
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
{"formsemestre_id": formsemestre_id}
|
{"formsemestre_id": formsemestre_id}
|
||||||
)
|
)
|
||||||
log(
|
log(f"formsemestre_import_etud_admission: {formsemestre_id} ({len(ins)} etuds)")
|
||||||
"formsemestre_import_etud_admission: %s (%d etuds)"
|
|
||||||
% (formsemestre_id, len(ins))
|
|
||||||
)
|
|
||||||
no_nip = [] # liste d'etudids sans code NIP
|
no_nip = [] # liste d'etudids sans code NIP
|
||||||
unknowns = [] # etudiants avec NIP mais inconnus du portail
|
unknowns = [] # etudiants avec NIP mais inconnus du portail
|
||||||
changed_mails = [] # modification d'adresse mails
|
changed_mails: list[tuple[Identite, str]] = [] # modification d'adresse mails
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
|
|
||||||
# Essaie de recuperer les etudiants des étapes, car
|
# Essaie de recuperer les etudiants des étapes, car
|
||||||
# la requete get_inscrits_etape est en général beaucoup plus
|
# la requete get_inscrits_etape est en général beaucoup plus
|
||||||
@ -844,49 +824,47 @@ def formsemestre_import_etud_admission(
|
|||||||
|
|
||||||
for i in ins:
|
for i in ins:
|
||||||
etudid = i["etudid"]
|
etudid = i["etudid"]
|
||||||
info = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud: Identite = Identite.query.get_or_404(etudid)
|
||||||
code_nip = info["code_nip"]
|
code_nip = etud.code_nip
|
||||||
if not code_nip:
|
if not code_nip:
|
||||||
no_nip.append(etudid)
|
no_nip.append(etudid)
|
||||||
else:
|
else:
|
||||||
etud = apo_etuds.get(code_nip)
|
data_apo = apo_etuds.get(code_nip)
|
||||||
if not etud:
|
if not data_apo:
|
||||||
# pas vu dans les etudiants de l'étape, tente en individuel
|
# pas vu dans les etudiants de l'étape, tente en individuel
|
||||||
etud = sco_portal_apogee.get_etud_apogee(code_nip)
|
data_apo = sco_portal_apogee.get_etud_apogee(code_nip)
|
||||||
if etud:
|
if data_apo:
|
||||||
update_etape_formsemestre_inscription(i, etud)
|
update_etape_formsemestre_inscription(i, data_apo)
|
||||||
do_import_etud_admission(
|
do_import_etud_admission(
|
||||||
cnx,
|
|
||||||
etudid,
|
|
||||||
etud,
|
etud,
|
||||||
import_naissance=True,
|
data_apo,
|
||||||
import_identite=import_identite,
|
import_identite=import_identite,
|
||||||
)
|
)
|
||||||
apo_emailperso = etud.get("mailperso", "")
|
adresse = etud.adresses.first()
|
||||||
if info["emailperso"] and not apo_emailperso:
|
if adresse is None:
|
||||||
apo_emailperso = info["emailperso"]
|
adresse = Adresse()
|
||||||
|
etud.adresses.append(adresse)
|
||||||
|
apo_emailperso = data_apo.get("mailperso", "")
|
||||||
|
if adresse.emailperso and not apo_emailperso:
|
||||||
|
apo_emailperso = adresse.emailperso
|
||||||
if import_email:
|
if import_email:
|
||||||
if not "mail" in etud:
|
if not "mail" in data_apo:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"la réponse portail n'a pas le champs requis 'mail'"
|
"la réponse portail n'a pas le champs requis 'mail'"
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
info["email"] != etud["mail"]
|
adresse.email != data_apo["mail"]
|
||||||
or info["emailperso"] != apo_emailperso
|
or adresse.emailperso != apo_emailperso
|
||||||
):
|
):
|
||||||
sco_etud.adresse_edit(
|
old_mail = adresse.email
|
||||||
cnx,
|
adresse.email = data_apo["mail"]
|
||||||
args={
|
adresse.emailperso = apo_emailperso
|
||||||
"etudid": etudid,
|
db.session.add(adresse)
|
||||||
"adresse_id": info["adresse_id"],
|
|
||||||
"email": etud["mail"],
|
|
||||||
"emailperso": apo_emailperso,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
# notifie seulement les changements d'adresse mail institutionnelle
|
# notifie seulement les changements d'adresse mail institutionnelle
|
||||||
if info["email"] != etud["mail"]:
|
if adresse.email != data_apo["mail"]:
|
||||||
changed_mails.append((info, etud["mail"]))
|
changed_mails.append((etud, old_mail))
|
||||||
else:
|
else:
|
||||||
unknowns.append(code_nip)
|
unknowns.append(code_nip)
|
||||||
|
db.session.commit()
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=sem["formsemestre_id"])
|
sco_cache.invalidate_formsemestre(formsemestre_id=sem["formsemestre_id"])
|
||||||
return no_nip, unknowns, changed_mails
|
return no_nip, unknowns, changed_mails
|
||||||
|
@ -637,7 +637,15 @@ def get_mime_suffix(format_code: str) -> tuple[str, str]:
|
|||||||
# Différents types de voies d'admission:
|
# Différents types de voies d'admission:
|
||||||
# (stocké en texte libre dans la base, mais saisie par menus pour harmoniser)
|
# (stocké en texte libre dans la base, mais saisie par menus pour harmoniser)
|
||||||
TYPE_ADMISSION_DEFAULT = "Inconnue"
|
TYPE_ADMISSION_DEFAULT = "Inconnue"
|
||||||
TYPES_ADMISSION = (TYPE_ADMISSION_DEFAULT, "APB", "APB-PC", "CEF", "Direct")
|
TYPES_ADMISSION = (
|
||||||
|
TYPE_ADMISSION_DEFAULT,
|
||||||
|
"Parcoursup",
|
||||||
|
"Transfert",
|
||||||
|
"APB",
|
||||||
|
"APB-PC",
|
||||||
|
"CEF",
|
||||||
|
"Direct",
|
||||||
|
)
|
||||||
|
|
||||||
BULLETINS_VERSIONS = {
|
BULLETINS_VERSIONS = {
|
||||||
"short": "Version courte",
|
"short": "Version courte",
|
||||||
|
@ -481,7 +481,7 @@ class TableRecap(tb.Table):
|
|||||||
|
|
||||||
for row in self.rows:
|
for row in self.rows:
|
||||||
etud = row.etud
|
etud = row.etud
|
||||||
admission = etud.admission.first()
|
admission = etud.admission
|
||||||
if admission:
|
if admission:
|
||||||
first = True
|
first = True
|
||||||
for cid, title in fields.items():
|
for cid, title in fields.items():
|
||||||
|
@ -1768,14 +1768,12 @@ def _etudident_create_or_edit_form(edit):
|
|||||||
else:
|
else:
|
||||||
# modif d'un etudiant
|
# modif d'un etudiant
|
||||||
etud_o.from_dict(tf[2])
|
etud_o.from_dict(tf[2])
|
||||||
db.session.add(etud_o)
|
admission = etud_o.admission
|
||||||
admission = etud_o.admission.first()
|
|
||||||
if admission is None:
|
if admission is None:
|
||||||
# ? ne devrait pas arriver mais...
|
# ? ne devrait pas arriver mais...
|
||||||
admission = Admission()
|
admission = Admission()
|
||||||
admission.etudid = etud_o.id
|
etud_o.admission = admission
|
||||||
admission.from_dict(tf[2])
|
admission.from_dict(tf[2])
|
||||||
db.session.add(admission)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
etud = sco_etud.etudident_list(cnx, {"etudid": etud_o.id})[0]
|
etud = sco_etud.etudident_list(cnx, {"etudid": etud_o.id})[0]
|
||||||
@ -2200,7 +2198,7 @@ Les champs avec un astérisque (*) doivent être présents (nulls non autorisés
|
|||||||
<tr><td><b>Attribut</b></td><td><b>Type</b></td><td><b>Description</b></td></tr>"""
|
<tr><td><b>Attribut</b></td><td><b>Type</b></td><td><b>Description</b></td></tr>"""
|
||||||
]
|
]
|
||||||
for t in sco_import_etuds.sco_import_format(
|
for t in sco_import_etuds.sco_import_format(
|
||||||
with_codesemestre=(formsemestre_id == None)
|
with_codesemestre=(formsemestre_id is None)
|
||||||
):
|
):
|
||||||
if int(t[3]):
|
if int(t[3]):
|
||||||
ast = ""
|
ast = ""
|
||||||
@ -2291,37 +2289,33 @@ def form_students_import_infos_admissions(formsemestre_id=None):
|
|||||||
# On a le droit d'importer:
|
# On a le droit d'importer:
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Import données admissions Parcoursup"),
|
html_sco_header.sco_header(page_title="Import données admissions Parcoursup"),
|
||||||
"""<h2 class="formsemestre">Téléchargement des informations sur l'admission des étudiants depuis feuilles import Parcoursup</h2>
|
f"""<h2 class="formsemestre">Téléchargement des informations sur l'admission des étudiants depuis feuilles import Parcoursup</h2>
|
||||||
<div style="color: red">
|
<div style="color: red">
|
||||||
<p>A utiliser pour renseigner les informations sur l'origine des étudiants (lycées, bac, etc). Ces informations sont facultatives mais souvent utiles pour mieux connaitre les étudiants et aussi pour effectuer des statistiques (résultats suivant le type de bac...). Les données sont affichées sur les fiches individuelles des étudiants.</p>
|
<p>A utiliser pour renseigner les informations sur l'origine des étudiants (lycées, bac, etc). Ces informations sont facultatives mais souvent utiles pour mieux connaitre les étudiants et aussi pour effectuer des statistiques (résultats suivant le type de bac...). Les données sont affichées sur les fiches individuelles des étudiants.</p>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
Importer ici la feuille excel utilisée pour envoyer le classement Parcoursup.
|
Importer ici la feuille excel utilisée pour envoyer le classement Parcoursup.
|
||||||
Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés,
|
Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés,
|
||||||
les autres lignes de la feuille seront ignorées. Et seules les colonnes intéressant ScoDoc
|
les autres lignes de la feuille seront ignorées.
|
||||||
|
Et seules les colonnes intéressant ScoDoc
|
||||||
seront importées: il est inutile d'éliminer les autres.
|
seront importées: il est inutile d'éliminer les autres.
|
||||||
<br>
|
<br>
|
||||||
<em>Seules les données "admission" seront modifiées (et pas l'identité de l'étudiant).</em>
|
<em>Seules les données "admission" seront modifiées
|
||||||
|
(et pas l'identité de l'étudiant).</em>
|
||||||
<br>
|
<br>
|
||||||
<em>Les colonnes "nom" et "prenom" sont requises, ou bien une colonne "etudid".</em>
|
<em>Les colonnes "nom" et "prenom" sont requises, ou bien une colonne "etudid".</em>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Avant d'importer vos données, il est recommandé d'enregistrer les informations actuelles:
|
Avant d'importer vos données, il est recommandé d'enregistrer
|
||||||
<a href="import_generate_admission_sample?formsemestre_id=%(formsemestre_id)s">exporter les données actuelles de ScoDoc</a> (ce fichier peut être ré-importé après d'éventuelles modifications)
|
les informations actuelles:
|
||||||
|
<a class="stdlink" href="{
|
||||||
|
url_for("scolar.import_generate_admission_sample",
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||||
|
}">exporter les données actuelles de ScoDoc</a>
|
||||||
|
(ce fichier peut être ré-importé après d'éventuelles modifications)
|
||||||
</p>
|
</p>
|
||||||
"""
|
""",
|
||||||
% {"formsemestre_id": formsemestre_id},
|
]
|
||||||
] # '
|
|
||||||
|
|
||||||
type_admission_list = (
|
|
||||||
"Autre",
|
|
||||||
"Parcoursup",
|
|
||||||
"Parcoursup PC",
|
|
||||||
"APB",
|
|
||||||
"APB PC",
|
|
||||||
"CEF",
|
|
||||||
"Direct",
|
|
||||||
)
|
|
||||||
|
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
request.base_url,
|
request.base_url,
|
||||||
@ -2337,7 +2331,7 @@ def form_students_import_infos_admissions(formsemestre_id=None):
|
|||||||
"title": "Type d'admission",
|
"title": "Type d'admission",
|
||||||
"explanation": "sera attribué aux étudiants modifiés par cet import n'ayant pas déjà un type",
|
"explanation": "sera attribué aux étudiants modifiés par cet import n'ayant pas déjà un type",
|
||||||
"input_type": "menu",
|
"input_type": "menu",
|
||||||
"allowed_values": type_admission_list,
|
"allowed_values": scu.TYPES_ADMISSION,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
("formsemestre_id", {"input_type": "hidden"}),
|
("formsemestre_id", {"input_type": "hidden"}),
|
||||||
@ -2346,7 +2340,8 @@ def form_students_import_infos_admissions(formsemestre_id=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
help_text = (
|
help_text = (
|
||||||
"""<p>Les colonnes importables par cette fonction sont indiquées dans la table ci-dessous.
|
"""<p>Les colonnes importables par cette fonction sont indiquées
|
||||||
|
dans la table ci-dessous.
|
||||||
Seule la première feuille du classeur sera utilisée.
|
Seule la première feuille du classeur sera utilisée.
|
||||||
<div id="adm_table_description_format">
|
<div id="adm_table_description_format">
|
||||||
"""
|
"""
|
||||||
@ -2375,7 +2370,7 @@ def form_students_import_infos_admissions(formsemestre_id=None):
|
|||||||
@permission_required(Permission.EtudChangeAdr)
|
@permission_required(Permission.EtudChangeAdr)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def formsemestre_import_etud_admission(formsemestre_id, import_email=True):
|
def formsemestre_import_etud_admission(formsemestre_id, import_email=True):
|
||||||
"""Reimporte donnees admissions par synchro Portail Apogée"""
|
"""Ré-importe donnees admissions par synchro Portail Apogée"""
|
||||||
(
|
(
|
||||||
no_nip,
|
no_nip,
|
||||||
unknowns,
|
unknowns,
|
||||||
@ -2384,7 +2379,7 @@ def formsemestre_import_etud_admission(formsemestre_id, import_email=True):
|
|||||||
formsemestre_id, import_identite=True, import_email=import_email
|
formsemestre_id, import_identite=True, import_email=import_email
|
||||||
)
|
)
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header("Reimport données admission"),
|
html_sco_header.html_sem_header("Ré-import données admission"),
|
||||||
"<h3>Opération effectuée</h3>",
|
"<h3>Opération effectuée</h3>",
|
||||||
]
|
]
|
||||||
if no_nip:
|
if no_nip:
|
||||||
@ -2396,12 +2391,12 @@ def formsemestre_import_etud_admission(formsemestre_id, import_email=True):
|
|||||||
+ "</p>"
|
+ "</p>"
|
||||||
)
|
)
|
||||||
if changed_mails:
|
if changed_mails:
|
||||||
H.append("<h3>Adresses mails modifiées:</h3>")
|
H.append("<h3>Adresses mails modifiées:</h3><ul>")
|
||||||
for info, new_mail in changed_mails:
|
for etud, old_mail in changed_mails:
|
||||||
H.append(
|
H.append(
|
||||||
"%s: <tt>%s</tt> devient <tt>%s</tt><br>"
|
f"""<li>{etud.nom}: <tt>{old_mail}</tt> devient <tt>{etud.email}</tt></li>"""
|
||||||
% (info["nom"], info["email"], new_mail)
|
|
||||||
)
|
)
|
||||||
|
H.append("</ul>")
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
|
125
migrations/versions/497ba81343f7_identite_admission.py
Normal file
125
migrations/versions/497ba81343f7_identite_admission.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
"""identite_admission
|
||||||
|
|
||||||
|
Revision ID: 497ba81343f7
|
||||||
|
Revises: 5c44d0d215ca
|
||||||
|
Create Date: 2023-10-14 10:09:02.330634
|
||||||
|
|
||||||
|
Diverses amlioration du modèle Identite:
|
||||||
|
- boursier non null
|
||||||
|
- departement non null
|
||||||
|
- admission : 1 seule admission (one-to-one)
|
||||||
|
- adresse: etudid non null.
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.orm import sessionmaker # added by ev
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "497ba81343f7"
|
||||||
|
down_revision = "5c44d0d215ca"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
Session = sessionmaker()
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
bind = op.get_bind()
|
||||||
|
session = Session(bind=bind)
|
||||||
|
# Enleve les éventuels nulls de boursier
|
||||||
|
session.execute(
|
||||||
|
sa.text(
|
||||||
|
"""
|
||||||
|
UPDATE identite SET boursier = false WHERE boursier IS NULL;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Enleve les éventuelles adresses orphelines:
|
||||||
|
session.execute(
|
||||||
|
sa.text(
|
||||||
|
"""
|
||||||
|
DELETE FROM adresse WHERE etudid IS NULL;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Affecte arbitrairement les éventuels étudiants sans département au 1er
|
||||||
|
# (il ne devrait pas y en avoir, sauf essais manuels ou bugs)
|
||||||
|
session.execute(
|
||||||
|
sa.text(
|
||||||
|
"""
|
||||||
|
UPDATE identite SET dept_id = (
|
||||||
|
SELECT MIN(id)
|
||||||
|
FROM departement
|
||||||
|
) WHERE dept_id IS NULL;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with op.batch_alter_table("identite", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column("admission_id", sa.Integer(), nullable=True))
|
||||||
|
batch_op.alter_column("boursier", existing_type=sa.BOOLEAN(), nullable=False)
|
||||||
|
batch_op.alter_column("dept_id", existing_type=sa.Integer(), nullable=False)
|
||||||
|
batch_op.create_foreign_key(
|
||||||
|
"admissions_etudid_fkey", "admissions", ["admission_id"], ["id"]
|
||||||
|
)
|
||||||
|
batch_op.drop_constraint("identite_dept_id_code_ine_key", type_="unique")
|
||||||
|
batch_op.drop_constraint("identite_dept_id_code_nip_key", type_="unique")
|
||||||
|
|
||||||
|
with op.batch_alter_table("adresse", schema=None) as batch_op:
|
||||||
|
batch_op.alter_column("etudid", existing_type=sa.Integer(), nullable=False)
|
||||||
|
batch_op.drop_constraint("adresse_etudid_fkey", type_="foreignkey")
|
||||||
|
batch_op.create_foreign_key(
|
||||||
|
"adresse_etudid_fkey", "identite", ["etudid"], ["id"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Elimine eventuels duplicats dans Admission
|
||||||
|
session.execute(
|
||||||
|
sa.text(
|
||||||
|
"""
|
||||||
|
DELETE FROM admissions
|
||||||
|
WHERE id NOT IN (
|
||||||
|
SELECT MIN(id)
|
||||||
|
FROM admissions
|
||||||
|
GROUP BY etudid
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Copie id
|
||||||
|
session.execute(
|
||||||
|
sa.text(
|
||||||
|
"""
|
||||||
|
UPDATE identite SET admission_id = admissions.id
|
||||||
|
FROM admissions WHERE admissions.etudid = identite.id;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with op.batch_alter_table("admissions", schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint("admissions_etudid_fkey", type_="foreignkey")
|
||||||
|
# laisse l'ancienne colonne pour downgrade (tests)
|
||||||
|
# batch_op.drop_column('etudid')
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
with op.batch_alter_table("identite", schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint("admissions_etudid_fkey", type_="foreignkey")
|
||||||
|
batch_op.alter_column("boursier", existing_type=sa.BOOLEAN(), nullable=True)
|
||||||
|
batch_op.alter_column("dept_id", existing_type=sa.Integer(), nullable=True)
|
||||||
|
batch_op.drop_column("admission_id")
|
||||||
|
|
||||||
|
with op.batch_alter_table("admissions", schema=None) as batch_op:
|
||||||
|
# batch_op.add_column(
|
||||||
|
# sa.Column("etudid", sa.INTEGER(), autoincrement=False, nullable=True)
|
||||||
|
# )
|
||||||
|
batch_op.create_foreign_key(
|
||||||
|
"admissions_etudid_fkey", "identite", ["etudid"], ["id"], ondelete="CASCADE"
|
||||||
|
)
|
||||||
|
|
||||||
|
with op.batch_alter_table("adresse", schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint("adresse_etudid_fkey", type_="foreignkey")
|
||||||
|
batch_op.create_foreign_key(
|
||||||
|
"adresse_etudid_fkey", "identite", ["etudid"], ["id"], ondelete="CASCADE"
|
||||||
|
)
|
||||||
|
batch_op.alter_column("etudid", existing_type=sa.INTEGER(), nullable=True)
|
@ -21,6 +21,7 @@ from app.models import (
|
|||||||
Evaluation,
|
Evaluation,
|
||||||
Formation,
|
Formation,
|
||||||
FormationModalite,
|
FormationModalite,
|
||||||
|
Identite,
|
||||||
Matiere,
|
Matiere,
|
||||||
ModuleImpl,
|
ModuleImpl,
|
||||||
)
|
)
|
||||||
@ -29,8 +30,6 @@ from app.scodoc import codes_cursus
|
|||||||
from app.scodoc import sco_edit_matiere
|
from app.scodoc import sco_edit_matiere
|
||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
from app.scodoc import sco_etud
|
|
||||||
from app.scodoc import sco_evaluation_db
|
|
||||||
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
|
||||||
from app.scodoc import sco_formsemestre_validation
|
from app.scodoc import sco_formsemestre_validation
|
||||||
@ -83,12 +82,14 @@ def logging_meth(func):
|
|||||||
class ScoFake(object):
|
class ScoFake(object):
|
||||||
"""Helper for ScoDoc tests"""
|
"""Helper for ScoDoc tests"""
|
||||||
|
|
||||||
def __init__(self, verbose=True):
|
def __init__(self, verbose=True, dept=None):
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
self.default_user = User.query.filter_by(user_name="bach").first()
|
self.default_user = User.query.filter_by(user_name="bach").first()
|
||||||
if not self.default_user:
|
if not self.default_user:
|
||||||
raise ScoValueError('User test "bach" not found !')
|
raise ScoValueError('User test "bach" not found !')
|
||||||
self.dept = Departement.query.filter_by(acronym=TestConfig.DEPT_TEST).first()
|
self.dept = (
|
||||||
|
dept or Departement.query.filter_by(acronym=TestConfig.DEPT_TEST).first()
|
||||||
|
)
|
||||||
assert self.dept
|
assert self.dept
|
||||||
|
|
||||||
def log(self, msg):
|
def log(self, msg):
|
||||||
@ -116,10 +117,10 @@ class ScoFake(object):
|
|||||||
def create_etud(
|
def create_etud(
|
||||||
self,
|
self,
|
||||||
cnx=None,
|
cnx=None,
|
||||||
code_nip="",
|
code_nip=None,
|
||||||
nom="",
|
nom="",
|
||||||
prenom="",
|
prenom="",
|
||||||
code_ine="",
|
code_ine=None,
|
||||||
civilite="",
|
civilite="",
|
||||||
etape="TST1",
|
etape="TST1",
|
||||||
email="test@localhost",
|
email="test@localhost",
|
||||||
@ -135,10 +136,8 @@ class ScoFake(object):
|
|||||||
typeadresse="domicile",
|
typeadresse="domicile",
|
||||||
boursier=None,
|
boursier=None,
|
||||||
description="etudiant test",
|
description="etudiant test",
|
||||||
):
|
) -> dict:
|
||||||
"""Crée un étudiant"""
|
"""Crée un étudiant"""
|
||||||
if not cnx:
|
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
if code_nip == "":
|
if code_nip == "":
|
||||||
code_nip = str(random.randint(10000, 99999))
|
code_nip = str(random.randint(10000, 99999))
|
||||||
if not civilite or not nom or not prenom:
|
if not civilite or not nom or not prenom:
|
||||||
@ -149,10 +148,13 @@ class ScoFake(object):
|
|||||||
nom = r_nom
|
nom = r_nom
|
||||||
if not prenom:
|
if not prenom:
|
||||||
prenom = r_prenom
|
prenom = r_prenom
|
||||||
etud = sco_etud.create_etud(cnx, args=locals())
|
dept_id = self.dept.id # pylint: disable=possibly-unused-variable
|
||||||
inscription = "2020" # pylint: disable=possibly-unused-variable
|
inscription = "2020" # pylint: disable=possibly-unused-variable
|
||||||
sco_synchro_etuds.do_import_etud_admission(cnx, etud["etudid"], locals())
|
args = locals()
|
||||||
return etud
|
etud = Identite.create_from_dict(args)
|
||||||
|
db.session.commit()
|
||||||
|
sco_synchro_etuds.do_import_etud_admission(etud, args)
|
||||||
|
return etud.to_dict_scodoc7()
|
||||||
|
|
||||||
@logging_meth
|
@logging_meth
|
||||||
def create_formation(
|
def create_formation(
|
||||||
|
@ -86,7 +86,6 @@ def test_general(test_client):
|
|||||||
date_debut="01/01/2024",
|
date_debut="01/01/2024",
|
||||||
date_fin="31/07/2024",
|
date_fin="31/07/2024",
|
||||||
)
|
)
|
||||||
|
|
||||||
formsemestre_1 = FormSemestre.get_formsemestre(formsemestre_id_1)
|
formsemestre_1 = FormSemestre.get_formsemestre(formsemestre_id_1)
|
||||||
formsemestre_2 = FormSemestre.get_formsemestre(formsemestre_id_2)
|
formsemestre_2 = FormSemestre.get_formsemestre(formsemestre_id_2)
|
||||||
formsemestre_3 = FormSemestre.get_formsemestre(formsemestre_id_3)
|
formsemestre_3 = FormSemestre.get_formsemestre(formsemestre_id_3)
|
||||||
@ -110,37 +109,36 @@ def test_general(test_client):
|
|||||||
module_id=module_id_2,
|
module_id=module_id_2,
|
||||||
formsemestre_id=formsemestre_id_2,
|
formsemestre_id=formsemestre_id_2,
|
||||||
)
|
)
|
||||||
|
|
||||||
moduleimpls = [
|
moduleimpls = [
|
||||||
moduleimpl_1_1,
|
moduleimpl_1_1,
|
||||||
moduleimpl_1_2,
|
moduleimpl_1_2,
|
||||||
moduleimpl_2_1,
|
moduleimpl_2_1,
|
||||||
moduleimpl_2_2,
|
moduleimpl_2_2,
|
||||||
]
|
]
|
||||||
|
|
||||||
moduleimpls = [
|
moduleimpls = [
|
||||||
ModuleImpl.query.filter_by(id=mi_id).first() for mi_id in moduleimpls
|
ModuleImpl.query.filter_by(id=mi_id).first() for mi_id in moduleimpls
|
||||||
]
|
]
|
||||||
|
|
||||||
# Création des étudiants (3)
|
# Création de 3 étudiants
|
||||||
|
etud_0 = g_fake.create_etud(prenom="etud0")
|
||||||
etuds_dict = [
|
etud_1 = g_fake.create_etud(prenom="etud1")
|
||||||
g_fake.create_etud(code_nip=None, prenom=f"etud{i}") for i in range(3)
|
etud_2 = g_fake.create_etud(prenom="etud2")
|
||||||
]
|
etuds_dict = [etud_0, etud_1, etud_2]
|
||||||
|
# etuds_dict = [g_fake.create_etud(prenom=f"etud{i}") for i in range(3)]
|
||||||
|
|
||||||
etuds = []
|
etuds = []
|
||||||
for etud in etuds_dict:
|
for etud in etuds_dict:
|
||||||
g_fake.inscrit_etudiant(formsemestre_id=formsemestre_id_1, etud=etud)
|
g_fake.inscrit_etudiant(formsemestre_id=formsemestre_id_1, etud=etud)
|
||||||
g_fake.inscrit_etudiant(formsemestre_id=formsemestre_id_2, etud=etud)
|
g_fake.inscrit_etudiant(formsemestre_id=formsemestre_id_2, etud=etud)
|
||||||
|
|
||||||
etuds.append(Identite.query.filter_by(id=etud["id"]).first())
|
etuds.append(Identite.query.filter_by(id=etud["etudid"]).first())
|
||||||
|
|
||||||
assert None not in etuds, "Problème avec la conversion en Identite"
|
assert None not in etuds, "Problème avec la conversion en Identite"
|
||||||
|
|
||||||
# Etudiant faux
|
# Etudiant faux
|
||||||
|
|
||||||
etud_faux_dict = g_fake.create_etud(code_nip=None, prenom="etudfaux")
|
etud_faux_dict = g_fake.create_etud(prenom="etudfaux")
|
||||||
etud_faux = Identite.query.filter_by(id=etud_faux_dict["id"]).first()
|
etud_faux = Identite.query.filter_by(id=etud_faux_dict["etudid"]).first()
|
||||||
|
|
||||||
verif_migration_abs_assiduites()
|
verif_migration_abs_assiduites()
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ def test_preferences(test_client):
|
|||||||
# --- Preferences d'un semestre
|
# --- Preferences d'un semestre
|
||||||
# rejoue ce test pour avoir un semestre créé
|
# rejoue ce test pour avoir un semestre créé
|
||||||
app.set_sco_dept("D2")
|
app.set_sco_dept("D2")
|
||||||
test_sco_basic.run_sco_basic()
|
test_sco_basic.run_sco_basic(dept=Departement.query.filter_by(acronym="D2").first())
|
||||||
sem = sco_formsemestre.do_formsemestre_list()[0]
|
sem = sco_formsemestre.do_formsemestre_list()[0]
|
||||||
formsemestre_id = sem["formsemestre_id"]
|
formsemestre_id = sem["formsemestre_id"]
|
||||||
semp = sco_preferences.SemPreferences(formsemestre_id=formsemestre_id)
|
semp = sco_preferences.SemPreferences(formsemestre_id=formsemestre_id)
|
||||||
|
@ -48,12 +48,12 @@ def test_sco_basic(test_client):
|
|||||||
run_sco_basic()
|
run_sco_basic()
|
||||||
|
|
||||||
|
|
||||||
def run_sco_basic(verbose=False) -> FormSemestre:
|
def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
||||||
"""Scénario de base: création formation, semestre, étudiants, notes,
|
"""Scénario de base: création formation, semestre, étudiants, notes,
|
||||||
décisions jury
|
décisions jury
|
||||||
Renvoie le formsemestre créé.
|
Renvoie le formsemestre créé.
|
||||||
"""
|
"""
|
||||||
G = sco_fake_gen.ScoFake(verbose=verbose)
|
G = sco_fake_gen.ScoFake(verbose=verbose, dept=dept)
|
||||||
|
|
||||||
# --- Création d'étudiants
|
# --- Création d'étudiants
|
||||||
etuds = [G.create_etud(code_nip=None) for _ in range(10)]
|
etuds = [G.create_etud(code_nip=None) for _ in range(10)]
|
||||||
|
@ -170,9 +170,6 @@ def create_fake_etud(dept: Departement) -> Identite:
|
|||||||
etud.adresse = [models.Adresse(email=f"{etud.prenom}.{etud.nom}@example.com")]
|
etud.adresse = [models.Adresse(email=f"{etud.prenom}.{etud.nom}@example.com")]
|
||||||
db.session.add(etud)
|
db.session.add(etud)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
admission = models.Admission(etudid=etud.id)
|
|
||||||
db.session.add(admission)
|
|
||||||
db.session.commit()
|
|
||||||
return etud
|
return etud
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
Code_NIP; text; identite; 1; code etudiant (NIP Apogee);NIP
|
Code_NIP; text; identite; 1; code etudiant (NIP Apogee);NIP
|
||||||
Code_INE; text; identite; 1; code INE;INE
|
Code_INE; text; identite; 1; code INE;INE
|
||||||
#
|
#
|
||||||
nom; text; identite; 0; nom de l'etudiant;
|
nom; text; identite; 0; nom de l'étudiant;
|
||||||
nom_usuel; text; identite; 1; nom usuel (si different);
|
nom_usuel; text; identite; 1; nom usuel (si different);
|
||||||
prenom; text; identite; 0; prenom de l'etudiant
|
prenom; text; identite; 0; prénom de l'etudiant
|
||||||
civilite; text; identite; 1; sexe ('M', 'F', 'X');sexe;genre
|
civilite; text; identite; 0; sexe ('M', 'F', 'X');sexe;genre
|
||||||
prenom_etat_civil; text; identite; 1; prenom à 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
|
||||||
@ -22,14 +22,14 @@ codesemestre; text; INS; 0; code semestre inscription
|
|||||||
groupes; text; INS; 1; groupe(s), séparés par des point-virgules, doivent exister avant. On peut spécifier la partition sous la forme partition:groupe.
|
groupes; text; INS; 1; groupe(s), séparés par des point-virgules, doivent exister avant. On peut spécifier la partition sous la forme partition:groupe.
|
||||||
#
|
#
|
||||||
bac; text; admissions; 1; type de bac (S, STI, ...)
|
bac; text; admissions; 1; type de bac (S, STI, ...)
|
||||||
specialite; text; admissions; 1; specialite du bac (SVT, ...)
|
specialite; text; admissions; 1; specialité du bac (SVT, ...)
|
||||||
annee_bac; integer; admissions; 1; annee d'obtention du bac
|
annee_bac; integer; admissions; 1; annee d'obtention du bac
|
||||||
math; real; admissions; 1; note de math en terminale
|
math; real; admissions; 1; note de math en terminale
|
||||||
physique; real; admissions; 1; note de physique en terminale
|
physique; real; admissions; 1; note de physique en terminale
|
||||||
anglais; real; admissions; 1; note de anglais en terminale
|
anglais; real; admissions; 1; note de anglais en terminale
|
||||||
francais; real; admissions; 1; note de francais au bac
|
francais; real; admissions; 1; note de francais au bac
|
||||||
type_admission; text; admissions; 1; voie d'admission (APB, APB-PC, CEF, ...)
|
type_admission; text; admissions; 1; voie d'admission (APB, APB-PC, CEF, ...)
|
||||||
boursier_prec; integer; admissions; 1; 0/1 etait boursier dans le cycle precedent (lycee) ?
|
boursier_prec; integer; admissions; 1; 0/1 etait boursier dans le cycle précédent (lycee) ?
|
||||||
boursier; integer; identite; 1; 0/1 est actuellement boursier
|
boursier; integer; identite; 1; 0/1 est actuellement boursier
|
||||||
qualite; real; admissions; 1; note de qualite du dossier
|
qualite; real; admissions; 1; note de qualite du dossier
|
||||||
rapporteur; text; admissions; 1; identite du rapporteur (enseignant IUT)
|
rapporteur; text; admissions; 1; identite du rapporteur (enseignant IUT)
|
||||||
|
Loading…
Reference in New Issue
Block a user