forked from ScoDoc/ScoDoc
Merge branch 'new_api' of https://scodoc.org/git/leonard.montalbano/ScoDoc into new_api
This commit is contained in:
commit
e822ab4da0
@ -212,7 +212,9 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
|||||||
|
|
||||||
formsemestres = query.order_by(FormSemestre.date_debut)
|
formsemestres = query.order_by(FormSemestre.date_debut)
|
||||||
|
|
||||||
return jsonify([formsemestre.to_dict() for formsemestre in formsemestres])
|
return jsonify(
|
||||||
|
[formsemestre.to_dict(convert_parcours=True) for formsemestre in formsemestres]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
@ -471,7 +473,7 @@ def etudiant_bulletin_semestre( # XXX TODO Ajouter la possibilité de retourner
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
return sco_bulletins.get_formsemestre_bulletin_etud_json(
|
return sco_bulletins.get_formsemestre_bulletin_etud_json(
|
||||||
formsemestre, etud, version
|
formsemestre, etud, version=version
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ def formsemestre(formsemestre_id: int):
|
|||||||
formsemestre: FormSemestre = models.FormSemestre.query.filter_by(
|
formsemestre: FormSemestre = models.FormSemestre.query.filter_by(
|
||||||
id=formsemestre_id
|
id=formsemestre_id
|
||||||
).first_or_404()
|
).first_or_404()
|
||||||
data = formsemestre.to_dict()
|
data = formsemestre.to_dict(convert_parcours=True)
|
||||||
# Pour le moment on a besoin de fixer le departement
|
# Pour le moment on a besoin de fixer le departement
|
||||||
# pour accéder aux préferences
|
# pour accéder aux préferences
|
||||||
dept = Departement.query.get(formsemestre.dept_id)
|
dept = Departement.query.get(formsemestre.dept_id)
|
||||||
@ -92,13 +92,9 @@ def formsemestre_apo(etape_apo: str):
|
|||||||
FormSemestreEtape.formsemestre_id == FormSemestre.id,
|
FormSemestreEtape.formsemestre_id == FormSemestre.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
res = [formsemestre.to_dict() for formsemestre in formsemestres]
|
return jsonify(
|
||||||
if len(res) == 0:
|
[formsemestre.to_dict(convert_parcours=True) for formsemestre in formsemestres]
|
||||||
return error_response(
|
)
|
||||||
404, message="Aucun formsemestre trouvé avec cette étape apogée"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return jsonify(res)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins", methods=["GET"])
|
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins", methods=["GET"])
|
||||||
|
@ -14,6 +14,7 @@ from flask import url_for, g
|
|||||||
|
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.models import FormSemestre, Identite
|
from app.models import FormSemestre, Identite
|
||||||
|
from app.models import but_validations
|
||||||
from app.models.groups import GroupDescr
|
from app.models.groups import GroupDescr
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
from app.scodoc import sco_bulletins, sco_utils as scu
|
from app.scodoc import sco_bulletins, sco_utils as scu
|
||||||
@ -323,9 +324,13 @@ class BulletinBUT:
|
|||||||
ects_tot = sum([ue.ects or 0 for ue in res.ues]) if res.ues else 0.0
|
ects_tot = sum([ue.ects or 0 for ue in res.ues]) if res.ues else 0.0
|
||||||
ects_acquis = sum([d.get("ects", 0) for d in decisions_ues.values()])
|
ects_acquis = sum([d.get("ects", 0) for d in decisions_ues.values()])
|
||||||
semestre_infos["ECTS"] = {"acquis": ects_acquis, "total": ects_tot}
|
semestre_infos["ECTS"] = {"acquis": ects_acquis, "total": ects_tot}
|
||||||
semestre_infos.update(
|
if sco_preferences.get_preference("bul_show_decision", formsemestre.id):
|
||||||
sco_bulletins_json.dict_decision_jury(etud.id, formsemestre.id)
|
semestre_infos.update(
|
||||||
)
|
sco_bulletins_json.dict_decision_jury(etud.id, formsemestre.id)
|
||||||
|
)
|
||||||
|
semestre_infos.update(
|
||||||
|
but_validations.dict_decision_jury(etud, formsemestre)
|
||||||
|
)
|
||||||
if etat_inscription == scu.INSCRIT:
|
if etat_inscription == scu.INSCRIT:
|
||||||
# moyenne des moyennes générales du semestre
|
# moyenne des moyennes générales du semestre
|
||||||
semestre_infos["notes"] = {
|
semestre_infos["notes"] = {
|
||||||
|
@ -68,7 +68,7 @@ from flask import g, url_for
|
|||||||
from app import db
|
from app import db
|
||||||
from app import log
|
from app import log
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.comp import res_sem
|
from app.comp import inscr_mod, res_sem
|
||||||
from app.models import formsemestre
|
from app.models import formsemestre
|
||||||
|
|
||||||
from app.models.but_refcomp import (
|
from app.models.but_refcomp import (
|
||||||
@ -189,6 +189,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
sco_codes.RAT,
|
sco_codes.RAT,
|
||||||
sco_codes.ABAN,
|
sco_codes.ABAN,
|
||||||
sco_codes.ABL,
|
sco_codes.ABL,
|
||||||
|
sco_codes.ATJ,
|
||||||
sco_codes.DEF,
|
sco_codes.DEF,
|
||||||
sco_codes.DEM,
|
sco_codes.DEM,
|
||||||
sco_codes.EXCLU,
|
sco_codes.EXCLU,
|
||||||
@ -200,6 +201,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
):
|
):
|
||||||
super().__init__(etud=etud)
|
super().__init__(etud=etud)
|
||||||
|
self.formsemestre_id = formsemestre.id
|
||||||
formsemestre_impair, formsemestre_pair = self.comp_formsemestres(formsemestre)
|
formsemestre_impair, formsemestre_pair = self.comp_formsemestres(formsemestre)
|
||||||
assert (
|
assert (
|
||||||
(formsemestre_pair is None)
|
(formsemestre_pair is None)
|
||||||
@ -217,15 +219,16 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
"le 1er semestre de l'année scolaire considérée (S1, S3, S5)"
|
"le 1er semestre de l'année scolaire considérée (S1, S3, S5)"
|
||||||
self.formsemestre_pair = formsemestre_pair
|
self.formsemestre_pair = formsemestre_pair
|
||||||
"le second formsemestre de la même année scolaire (S2, S4, S6)"
|
"le second formsemestre de la même année scolaire (S2, S4, S6)"
|
||||||
self.annee_but = (
|
formsemestre_last = formsemestre_pair or formsemestre_impair
|
||||||
formsemestre_impair.semestre_id // 2 + 1
|
"le formsemestre le plus avancé dans cette année"
|
||||||
if formsemestre_impair
|
|
||||||
else formsemestre_pair.semestre_id // 2
|
self.annee_but = (formsemestre_last.semestre_id + 1) // 2
|
||||||
)
|
|
||||||
"le rang de l'année dans le BUT: 1, 2, 3"
|
"le rang de l'année dans le BUT: 1, 2, 3"
|
||||||
assert self.annee_but in (1, 2, 3)
|
assert self.annee_but in (1, 2, 3)
|
||||||
self.rcues_annee = []
|
self.rcues_annee = []
|
||||||
"RCUEs de l'année"
|
"RCUEs de l'année"
|
||||||
|
self.inscription_etat = etud.inscription_etat(formsemestre_last.id)
|
||||||
|
|
||||||
if self.formsemestre_impair is not None:
|
if self.formsemestre_impair is not None:
|
||||||
self.validation = ApcValidationAnnee.query.filter_by(
|
self.validation = ApcValidationAnnee.query.filter_by(
|
||||||
etudid=self.etud.id,
|
etudid=self.etud.id,
|
||||||
@ -253,13 +256,17 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
|
|
||||||
self.ues_impair, self.ues_pair = self.compute_ues_annee() # pylint: disable=all
|
self.ues_impair, self.ues_pair = self.compute_ues_annee() # pylint: disable=all
|
||||||
self.decisions_ues = {
|
self.decisions_ues = {
|
||||||
ue.id: DecisionsProposeesUE(etud, formsemestre_impair, ue)
|
ue.id: DecisionsProposeesUE(
|
||||||
|
etud, formsemestre_impair, ue, self.inscription_etat
|
||||||
|
)
|
||||||
for ue in self.ues_impair
|
for ue in self.ues_impair
|
||||||
}
|
}
|
||||||
"{ue_id : DecisionsProposeesUE} pour toutes les UE de l'année"
|
"{ue_id : DecisionsProposeesUE} pour toutes les UE de l'année"
|
||||||
self.decisions_ues.update(
|
self.decisions_ues.update(
|
||||||
{
|
{
|
||||||
ue.id: DecisionsProposeesUE(etud, formsemestre_pair, ue)
|
ue.id: DecisionsProposeesUE(
|
||||||
|
etud, formsemestre_pair, ue, self.inscription_etat
|
||||||
|
)
|
||||||
for ue in self.ues_pair
|
for ue in self.ues_pair
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -289,8 +296,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
[rcue for rcue in rcues_avec_niveau if not rcue.est_suffisant()]
|
[rcue for rcue in rcues_avec_niveau if not rcue.est_suffisant()]
|
||||||
)
|
)
|
||||||
"le nb de comp. sous la barre de 8/20"
|
"le nb de comp. sous la barre de 8/20"
|
||||||
# année ADM si toutes RCUE validées (sinon PASD)
|
# année ADM si toutes RCUE validées (sinon PASD) et non DEM ou DEF
|
||||||
self.admis = self.nb_validables == self.nb_competences
|
self.admis = (self.nb_validables == self.nb_competences) and (
|
||||||
|
self.inscription_etat == scu.INSCRIT
|
||||||
|
)
|
||||||
"vrai si l'année est réussie, tous niveaux validables"
|
"vrai si l'année est réussie, tous niveaux validables"
|
||||||
self.valide_moitie_rcue = self.nb_validables > (self.nb_competences // 2)
|
self.valide_moitie_rcue = self.nb_validables > (self.nb_competences // 2)
|
||||||
# Peut passer si plus de la moitié validables et tous > 8
|
# Peut passer si plus de la moitié validables et tous > 8
|
||||||
@ -308,6 +317,19 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
if self.admis:
|
if self.admis:
|
||||||
self.codes = [sco_codes.ADM] + self.codes
|
self.codes = [sco_codes.ADM] + self.codes
|
||||||
self.explanation = expl_rcues
|
self.explanation = expl_rcues
|
||||||
|
elif self.inscription_etat != scu.INSCRIT:
|
||||||
|
self.codes = [
|
||||||
|
sco_codes.DEM
|
||||||
|
if self.inscription_etat == scu.DEMISSION
|
||||||
|
else sco_codes.DEF,
|
||||||
|
# propose aussi d'autres codes, au cas où...
|
||||||
|
sco_codes.DEM
|
||||||
|
if self.inscription_etat != scu.DEMISSION
|
||||||
|
else sco_codes.DEF,
|
||||||
|
sco_codes.ABAN,
|
||||||
|
sco_codes.ABL,
|
||||||
|
sco_codes.EXCLU,
|
||||||
|
]
|
||||||
elif self.passage_de_droit:
|
elif self.passage_de_droit:
|
||||||
self.codes = [sco_codes.PASD, sco_codes.ADJ] + self.codes
|
self.codes = [sco_codes.PASD, sco_codes.ADJ] + self.codes
|
||||||
self.explanation = expl_rcues
|
self.explanation = expl_rcues
|
||||||
@ -385,7 +407,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
def comp_formsemestres(
|
def comp_formsemestres(
|
||||||
self, formsemestre: FormSemestre
|
self, formsemestre: FormSemestre
|
||||||
) -> tuple[FormSemestre, FormSemestre]:
|
) -> tuple[FormSemestre, FormSemestre]:
|
||||||
"les deux formsemestres de l'année scolaire à laquelle appartient formsemestre"
|
"""les deux formsemestres de l'année scolaire à laquelle appartient formsemestre."""
|
||||||
|
if not formsemestre.formation.is_apc(): # garde fou
|
||||||
|
return None, None
|
||||||
if formsemestre.semestre_id % 2 == 0:
|
if formsemestre.semestre_id % 2 == 0:
|
||||||
other_semestre_id = formsemestre.semestre_id - 1
|
other_semestre_id = formsemestre.semestre_id - 1
|
||||||
else:
|
else:
|
||||||
@ -419,7 +443,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
(self.formsemestre_impair, self.res_impair),
|
(self.formsemestre_impair, self.res_impair),
|
||||||
(self.formsemestre_pair, self.res_pair),
|
(self.formsemestre_pair, self.res_pair),
|
||||||
):
|
):
|
||||||
if formsemestre is None:
|
if (formsemestre is None) or (not formsemestre.formation.is_apc()):
|
||||||
ues = []
|
ues = []
|
||||||
else:
|
else:
|
||||||
formation: Formation = formsemestre.formation
|
formation: Formation = formsemestre.formation
|
||||||
@ -478,6 +502,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
ue_impair,
|
ue_impair,
|
||||||
self.formsemestre_pair,
|
self.formsemestre_pair,
|
||||||
ue_pair,
|
ue_pair,
|
||||||
|
self.inscription_etat,
|
||||||
)
|
)
|
||||||
ues_impair_sans_rcue.discard(ue_impair.id)
|
ues_impair_sans_rcue.discard(ue_impair.id)
|
||||||
break
|
break
|
||||||
@ -505,7 +530,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
rcue = rc
|
rcue = rc
|
||||||
break
|
break
|
||||||
if rcue is not None:
|
if rcue is not None:
|
||||||
dec_rcue = DecisionsProposeesRCUE(self, rcue)
|
dec_rcue = DecisionsProposeesRCUE(self, rcue, self.inscription_etat)
|
||||||
rc_niveaux.append((dec_rcue, niveau.id))
|
rc_niveaux.append((dec_rcue, niveau.id))
|
||||||
# prévient les UE concernées :-)
|
# prévient les UE concernées :-)
|
||||||
self.decisions_ues[dec_rcue.rcue.ue_1.id].set_rcue(dec_rcue.rcue)
|
self.decisions_ues[dec_rcue.rcue.ue_1.id].set_rcue(dec_rcue.rcue)
|
||||||
@ -663,6 +688,46 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
db.session.delete(validation)
|
db.session.delete(validation)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
|
def get_autorisations_passage(self) -> list[int]:
|
||||||
|
"""Les liste des indices de semestres auxquels on est autorisé à
|
||||||
|
s'inscrire depuis cette année"""
|
||||||
|
formsemestre = self.formsemestre_pair or self.formsemestre_impair
|
||||||
|
if not formsemestre:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
a.semestre_id
|
||||||
|
for a in ScolarAutorisationInscription.query.filter_by(
|
||||||
|
etudid=self.etud.id,
|
||||||
|
origin_formsemestre_id=formsemestre.id,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
def descr_niveaux_validation(self, line_sep: str = "\n") -> str:
|
||||||
|
"""Description textuelle des niveaux validés (enregistrés)
|
||||||
|
pour PV jurys
|
||||||
|
"""
|
||||||
|
validations = [
|
||||||
|
dec_rcue.descr_validation()
|
||||||
|
for dec_rcue in self.decisions_rcue_by_niveau.values()
|
||||||
|
]
|
||||||
|
return line_sep.join(v for v in validations if v)
|
||||||
|
|
||||||
|
def descr_ues_validation(self, line_sep: str = "\n") -> str:
|
||||||
|
"""Description textuelle des UE validées (enregistrés)
|
||||||
|
pour PV jurys
|
||||||
|
"""
|
||||||
|
validations = []
|
||||||
|
for res in (self.res_impair, self.res_pair):
|
||||||
|
if res:
|
||||||
|
dec_ues = [
|
||||||
|
self.decisions_ues[ue.id]
|
||||||
|
for ue in res.ues
|
||||||
|
if ue.type == UE_STANDARD and ue.id in self.decisions_ues
|
||||||
|
]
|
||||||
|
valids = [dec_ue.descr_validation() for dec_ue in dec_ues]
|
||||||
|
validations.append(", ".join(v for v in valids if v))
|
||||||
|
return line_sep.join(validations)
|
||||||
|
|
||||||
|
|
||||||
class DecisionsProposeesRCUE(DecisionsProposees):
|
class DecisionsProposeesRCUE(DecisionsProposees):
|
||||||
"""Liste des codes de décisions que l'on peut proposer pour
|
"""Liste des codes de décisions que l'on peut proposer pour
|
||||||
@ -673,20 +738,33 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
|
|
||||||
codes_communs = [
|
codes_communs = [
|
||||||
sco_codes.ADJ,
|
sco_codes.ADJ,
|
||||||
|
sco_codes.ATJ,
|
||||||
sco_codes.RAT,
|
sco_codes.RAT,
|
||||||
sco_codes.DEF,
|
sco_codes.DEF,
|
||||||
sco_codes.ABAN,
|
sco_codes.ABAN,
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, dec_prop_annee: DecisionsProposeesAnnee, rcue: RegroupementCoherentUE
|
self,
|
||||||
|
dec_prop_annee: DecisionsProposeesAnnee,
|
||||||
|
rcue: RegroupementCoherentUE,
|
||||||
|
inscription_etat: str = scu.INSCRIT,
|
||||||
):
|
):
|
||||||
super().__init__(etud=dec_prop_annee.etud)
|
super().__init__(etud=dec_prop_annee.etud)
|
||||||
self.rcue = rcue
|
self.rcue = rcue
|
||||||
if rcue is None: # RCUE non dispo, eg un seul semestre
|
if rcue is None: # RCUE non dispo, eg un seul semestre
|
||||||
self.codes = []
|
self.codes = []
|
||||||
return
|
return
|
||||||
|
self.inscription_etat = inscription_etat
|
||||||
|
"inscription: I, DEM, DEF"
|
||||||
self.parcour = dec_prop_annee.parcour
|
self.parcour = dec_prop_annee.parcour
|
||||||
|
if inscription_etat != scu.INSCRIT:
|
||||||
|
self.validation = None # cache toute validation
|
||||||
|
self.explanation = "non incrit (dem. ou déf.)"
|
||||||
|
self.codes = [
|
||||||
|
sco_codes.DEM if inscription_etat == scu.DEMISSION else sco_codes.DEF
|
||||||
|
]
|
||||||
|
return
|
||||||
self.validation = rcue.query_validations().first()
|
self.validation = rcue.query_validations().first()
|
||||||
if self.validation is not None:
|
if self.validation is not None:
|
||||||
self.code_valide = self.validation.code
|
self.code_valide = self.validation.code
|
||||||
@ -737,6 +815,21 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
db.session.delete(validation)
|
db.session.delete(validation)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
|
def descr_validation(self) -> str:
|
||||||
|
"""Description validation niveau enregistrée, pour PV jury.
|
||||||
|
Si le niveau est validé, done son acronyme, sinon chaine vide.
|
||||||
|
"""
|
||||||
|
if self.code_valide in sco_codes.CODES_RCUE_VALIDES:
|
||||||
|
if (
|
||||||
|
self.rcue and self.rcue.ue_1 and self.rcue.ue_1.niveau_competence
|
||||||
|
): # prudence !
|
||||||
|
niveau_titre = self.rcue.ue_1.niveau_competence.competence.titre or ""
|
||||||
|
ordre = self.rcue.ue_1.niveau_competence.ordre
|
||||||
|
else:
|
||||||
|
return "?" # oups ?
|
||||||
|
return f"{niveau_titre} niv. {ordre}"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
class DecisionsProposeesUE(DecisionsProposees):
|
class DecisionsProposeesUE(DecisionsProposees):
|
||||||
"""Décisions de jury sur une UE du BUT
|
"""Décisions de jury sur une UE du BUT
|
||||||
@ -758,6 +851,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
sco_codes.RAT,
|
sco_codes.RAT,
|
||||||
sco_codes.DEF,
|
sco_codes.DEF,
|
||||||
sco_codes.ABAN,
|
sco_codes.ABAN,
|
||||||
|
sco_codes.ATJ,
|
||||||
sco_codes.DEM,
|
sco_codes.DEM,
|
||||||
sco_codes.UEBSL,
|
sco_codes.UEBSL,
|
||||||
]
|
]
|
||||||
@ -767,12 +861,27 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
etud: Identite,
|
etud: Identite,
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
ue: UniteEns,
|
ue: UniteEns,
|
||||||
|
inscription_etat: str = scu.INSCRIT,
|
||||||
):
|
):
|
||||||
super().__init__(etud=etud)
|
super().__init__(etud=etud)
|
||||||
self.formsemestre = formsemestre
|
self.formsemestre = formsemestre
|
||||||
self.ue: UniteEns = ue
|
self.ue: UniteEns = ue
|
||||||
self.rcue: RegroupementCoherentUE = None
|
self.rcue: RegroupementCoherentUE = None
|
||||||
"Le rcu auquel est rattaché cette UE, ou None"
|
"Le rcu auquel est rattaché cette UE, ou None"
|
||||||
|
self.inscription_etat = inscription_etat
|
||||||
|
"inscription: I, DEM, DEF"
|
||||||
|
if ue.type == sco_codes.UE_SPORT:
|
||||||
|
self.explanation = "UE bonus, pas de décision de jury"
|
||||||
|
self.codes = [] # aucun code proposé
|
||||||
|
return
|
||||||
|
if inscription_etat != scu.INSCRIT:
|
||||||
|
self.validation = None # cache toute validation
|
||||||
|
self.explanation = "non incrit (dem. ou déf.)"
|
||||||
|
self.codes = [
|
||||||
|
sco_codes.DEM if inscription_etat == scu.DEMISSION else sco_codes.DEF
|
||||||
|
]
|
||||||
|
self.moy_ue = "-"
|
||||||
|
return
|
||||||
# Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non)
|
# Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non)
|
||||||
# mais ici on a restreint au formsemestre donc une seule (prend la première)
|
# mais ici on a restreint au formsemestre donc une seule (prend la première)
|
||||||
self.validation = ScolarFormSemestreValidation.query.filter_by(
|
self.validation = ScolarFormSemestreValidation.query.filter_by(
|
||||||
@ -780,10 +889,6 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
).first()
|
).first()
|
||||||
if self.validation is not None:
|
if self.validation is not None:
|
||||||
self.code_valide = self.validation.code
|
self.code_valide = self.validation.code
|
||||||
if ue.type == sco_codes.UE_SPORT:
|
|
||||||
self.explanation = "UE bonus, pas de décision de jury"
|
|
||||||
self.codes = [] # aucun code proposé
|
|
||||||
return
|
|
||||||
|
|
||||||
# Moyenne de l'UE ?
|
# Moyenne de l'UE ?
|
||||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||||
@ -802,6 +907,8 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
|
|
||||||
def compute_codes(self):
|
def compute_codes(self):
|
||||||
"""Calcul des .codes attribuables et de l'explanation associée"""
|
"""Calcul des .codes attribuables et de l'explanation associée"""
|
||||||
|
if self.inscription_etat != scu.INSCRIT:
|
||||||
|
return
|
||||||
if self.moy_ue > (sco_codes.ParcoursBUT.BARRE_MOY - sco_codes.NOTES_TOLERANCE):
|
if self.moy_ue > (sco_codes.ParcoursBUT.BARRE_MOY - sco_codes.NOTES_TOLERANCE):
|
||||||
self.codes.insert(0, sco_codes.ADM)
|
self.codes.insert(0, sco_codes.ADM)
|
||||||
self.explanation = (f"Moyenne >= {sco_codes.ParcoursBUT.BARRE_MOY}/20",)
|
self.explanation = (f"Moyenne >= {sco_codes.ParcoursBUT.BARRE_MOY}/20",)
|
||||||
@ -853,6 +960,14 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
db.session.delete(validation)
|
db.session.delete(validation)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
|
def descr_validation(self) -> str:
|
||||||
|
"""Description validation niveau enregistrée, pour PV jury.
|
||||||
|
Si l'UE est validée, donne son acronyme, sinon chaine vide.
|
||||||
|
"""
|
||||||
|
if self.code_valide in sco_codes.CODES_UE_VALIDES:
|
||||||
|
return f"{self.ue.acronyme}"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
class BUTCursusEtud: # WIP TODO
|
class BUTCursusEtud: # WIP TODO
|
||||||
"""Validation du cursus d'un étudiant"""
|
"""Validation du cursus d'un étudiant"""
|
||||||
@ -932,7 +1047,7 @@ class BUTCursusEtud: # WIP TODO
|
|||||||
"""La liste des UE à valider si on valide ce niveau.
|
"""La liste des UE à valider si on valide ce niveau.
|
||||||
Ne liste que les UE qui ne sont pas déjà acquises.
|
Ne liste que les UE qui ne sont pas déjà acquises.
|
||||||
|
|
||||||
Selon la règle donéne par l'arrêté BUT:
|
Selon la règle donnée par l'arrêté BUT:
|
||||||
* La validation des deux UE du niveau d’une compétence emporte la validation de
|
* La validation des deux UE du niveau d’une compétence emporte la validation de
|
||||||
l'ensemble des UE du niveau inférieur de cette même compétence.
|
l'ensemble des UE du niveau inférieur de cette même compétence.
|
||||||
"""
|
"""
|
||||||
|
137
app/but/jury_but_pv.py
Normal file
137
app/but/jury_but_pv.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Jury BUT: table synthèse résultats semestre / PV
|
||||||
|
"""
|
||||||
|
from flask import g, request, url_for
|
||||||
|
|
||||||
|
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
||||||
|
|
||||||
|
from app import log
|
||||||
|
from app.but import jury_but
|
||||||
|
from app.models.etudiants import Identite
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.scodoc.gen_tables import GenTable
|
||||||
|
from app.scodoc import sco_excel
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
from app.scodoc import sco_preferences
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
|
def _descr_cursus_but(etud: Identite) -> str:
|
||||||
|
"description de la liste des semestres BUT suivis"
|
||||||
|
# prend simplement tous les semestre de type APC, ce qui sera faux si
|
||||||
|
# l'étudiant change de spécialité au sein du même département
|
||||||
|
# (ce qui ne peut normalement pas se produire)
|
||||||
|
indices = sorted(
|
||||||
|
[
|
||||||
|
ins.formsemestre.semestre_id
|
||||||
|
if ins.formsemestre.semestre_id is not None
|
||||||
|
else -1
|
||||||
|
for ins in etud.formsemestre_inscriptions
|
||||||
|
if ins.formsemestre.formation.is_apc()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return ", ".join(f"S{indice}" for indice in indices)
|
||||||
|
|
||||||
|
|
||||||
|
def pvjury_table_but(formsemestre_id: int, format="html") -> list[dict]:
|
||||||
|
"""Page récapitulant les décisions de jury BUT
|
||||||
|
formsemestre peut être pair ou impair
|
||||||
|
"""
|
||||||
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
assert formsemestre.formation.is_apc()
|
||||||
|
title = "Procès-verbal de jury BUT annuel"
|
||||||
|
|
||||||
|
if format == "html":
|
||||||
|
line_sep = "<br/>"
|
||||||
|
else:
|
||||||
|
line_sep = "\n"
|
||||||
|
# remplace pour le BUT la fonction sco_pvjury.pvjury_table
|
||||||
|
annee_but = (formsemestre.semestre_id + 1) // 2
|
||||||
|
titles = {
|
||||||
|
"nom": "Nom",
|
||||||
|
"cursus": "Cursus",
|
||||||
|
"ues": "UE validées",
|
||||||
|
"niveaux": "Niveaux de compétences validés",
|
||||||
|
"decision_but": f"Décision BUT{annee_but}",
|
||||||
|
"diplome": "Résultat au diplôme",
|
||||||
|
"devenir": "Devenir",
|
||||||
|
"observations": "Observations",
|
||||||
|
}
|
||||||
|
rows = []
|
||||||
|
for etudid in formsemestre.etuds_inscriptions:
|
||||||
|
etud: Identite = Identite.query.get(etudid)
|
||||||
|
try:
|
||||||
|
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||||
|
if deca.annee_but != annee_but: # wtf ?
|
||||||
|
log(
|
||||||
|
f"pvjury_table_but: inconsistent annee_but {deca.annee_but} != {annee_but}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
except ScoValueError:
|
||||||
|
deca = None
|
||||||
|
row = {
|
||||||
|
"nom": etud.etat_civil_pv(line_sep=line_sep),
|
||||||
|
"_nom_order": etud.sort_key,
|
||||||
|
"_nom_target_attrs": f'class="etudinfo" id="{etud.id}"',
|
||||||
|
"_nom_td_attrs": f'id="{etud.id}" class="etudinfo"',
|
||||||
|
"_nom_target": url_for(
|
||||||
|
"scolar.ficheEtud",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
etudid=etud.id,
|
||||||
|
),
|
||||||
|
"cursus": _descr_cursus_but(etud),
|
||||||
|
"ues": deca.descr_ues_validation(line_sep=line_sep) if deca else "-",
|
||||||
|
"niveaux": deca.descr_niveaux_validation(line_sep=line_sep)
|
||||||
|
if deca
|
||||||
|
else "-",
|
||||||
|
"decision_but": deca.code_valide if deca else "",
|
||||||
|
"devenir": ", ".join([f"S{i}" for i in deca.get_autorisations_passage()]),
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
rows.sort(key=lambda x: x["_nom_order"])
|
||||||
|
|
||||||
|
# Style excel... passages à la ligne sur \n
|
||||||
|
xls_style_base = sco_excel.excel_make_style()
|
||||||
|
xls_style_base["alignment"] = Alignment(wrapText=True, vertical="top")
|
||||||
|
|
||||||
|
tab = GenTable(
|
||||||
|
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}",
|
||||||
|
caption=title,
|
||||||
|
columns_ids=titles.keys(),
|
||||||
|
html_caption=title,
|
||||||
|
html_class="pvjury_table_but table_leftalign",
|
||||||
|
html_title=f"""<div style="margin-bottom: 8px;"><span style="font-size: 120%; font-weight: bold;">{title}</span>
|
||||||
|
<span style="padding-left: 20px;">
|
||||||
|
<a href="{url_for("notes.pvjury_table_but",
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, format="xlsx")}"
|
||||||
|
class="stdlink">version excel</a></span></div>
|
||||||
|
|
||||||
|
""",
|
||||||
|
html_with_td_classes=True,
|
||||||
|
origin=f"Généré par {scu.sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||||
|
page_title=title,
|
||||||
|
pdf_title=title,
|
||||||
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
rows=rows,
|
||||||
|
table_id="formation_table_recap",
|
||||||
|
titles=titles,
|
||||||
|
xls_columns_width={
|
||||||
|
"nom": 32,
|
||||||
|
"cursus": 12,
|
||||||
|
"ues": 32,
|
||||||
|
"niveaux": 32,
|
||||||
|
"decision_but": 14,
|
||||||
|
"diplome": 17,
|
||||||
|
"devenir": 8,
|
||||||
|
"observations": 12,
|
||||||
|
},
|
||||||
|
xls_style_base=xls_style_base,
|
||||||
|
)
|
||||||
|
return tab.make_page(format=format, javascripts=["js/etud_info.js"], init_qtip=True)
|
@ -36,7 +36,7 @@ from app.scodoc.sco_exceptions import ScoValueError
|
|||||||
|
|
||||||
def formsemestre_saisie_jury_but(
|
def formsemestre_saisie_jury_but(
|
||||||
formsemestre2: FormSemestre,
|
formsemestre2: FormSemestre,
|
||||||
readonly: bool = False,
|
read_only: bool = False,
|
||||||
selected_etudid: int = None,
|
selected_etudid: int = None,
|
||||||
mode="jury",
|
mode="jury",
|
||||||
) -> str:
|
) -> str:
|
||||||
@ -72,7 +72,7 @@ def formsemestre_saisie_jury_but(
|
|||||||
)
|
)
|
||||||
|
|
||||||
rows, titles, column_ids = get_table_jury_but(
|
rows, titles, column_ids = get_table_jury_but(
|
||||||
formsemestre2, readonly=readonly, mode=mode
|
formsemestre2, read_only=read_only, mode=mode
|
||||||
)
|
)
|
||||||
if not rows:
|
if not rows:
|
||||||
return (
|
return (
|
||||||
@ -98,7 +98,16 @@ def formsemestre_saisie_jury_but(
|
|||||||
]
|
]
|
||||||
if mode == "recap":
|
if mode == "recap":
|
||||||
H.append(
|
H.append(
|
||||||
"""<h3>Décisions de jury enregistrées pour les étudiants de ce semestre</h3>"""
|
f"""<h3>Décisions de jury enregistrées pour les étudiants de ce semestre</h3>
|
||||||
|
<div class="table_jury_but_links">
|
||||||
|
<div>
|
||||||
|
<a href="{url_for(
|
||||||
|
"notes.pvjury_table_but",
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
|
||||||
|
}" class="stdlink">tableau PV de jury</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
@ -109,7 +118,7 @@ def formsemestre_saisie_jury_but(
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
if (mode == "recap") and not readonly:
|
if (mode == "recap") and not read_only:
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<p><a class="stdlink" href="{url_for(
|
<p><a class="stdlink" href="{url_for(
|
||||||
@ -333,6 +342,10 @@ class RowCollector:
|
|||||||
+ ((" " + scu.EMO_WARNING) if deca.nb_rcues_under_8 > 0 else ""),
|
+ ((" " + scu.EMO_WARNING) if deca.nb_rcues_under_8 > 0 else ""),
|
||||||
"col_rcue col_rcues_validables" + klass,
|
"col_rcue col_rcues_validables" + klass,
|
||||||
)
|
)
|
||||||
|
self["_rcues_validables_data"] = {
|
||||||
|
"etudid": deca.etud.id,
|
||||||
|
"nomprenom": deca.etud.nomprenom,
|
||||||
|
}
|
||||||
if len(deca.rcues_annee) > 0:
|
if len(deca.rcues_annee) > 0:
|
||||||
# permet un tri par nb de niveaux validables + moyenne gen indicative S_pair
|
# permet un tri par nb de niveaux validables + moyenne gen indicative S_pair
|
||||||
if deca.res_pair and deca.etud.id in deca.res_pair.etud_moy_gen:
|
if deca.res_pair and deca.etud.id in deca.res_pair.etud_moy_gen:
|
||||||
@ -353,7 +366,7 @@ class RowCollector:
|
|||||||
|
|
||||||
|
|
||||||
def get_table_jury_but(
|
def get_table_jury_but(
|
||||||
formsemestre2: FormSemestre, readonly: bool = False, mode="jury"
|
formsemestre2: FormSemestre, read_only: bool = False, mode="jury"
|
||||||
) -> tuple[list[dict], list[str], list[str]]:
|
) -> tuple[list[dict], list[str], list[str]]:
|
||||||
"""Construit la table des résultats annuels pour le jury BUT"""
|
"""Construit la table des résultats annuels pour le jury BUT"""
|
||||||
res2: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre2)
|
res2: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre2)
|
||||||
@ -383,7 +396,7 @@ def get_table_jury_but(
|
|||||||
"col_code_annee",
|
"col_code_annee",
|
||||||
)
|
)
|
||||||
# --- Le lien de saisie
|
# --- Le lien de saisie
|
||||||
if not readonly and not mode == "recap":
|
if mode != "recap":
|
||||||
row.add_cell(
|
row.add_cell(
|
||||||
"lien_saisie",
|
"lien_saisie",
|
||||||
"",
|
"",
|
||||||
@ -394,9 +407,11 @@ def get_table_jury_but(
|
|||||||
etudid=etud.id,
|
etudid=etud.id,
|
||||||
formsemestre_id=formsemestre2.id,
|
formsemestre_id=formsemestre2.id,
|
||||||
)}" class="stdlink">
|
)}" class="stdlink">
|
||||||
{"modif." if deca.code_valide else "saisie"}
|
{"voir" if read_only else ("modif." if deca.code_valide else "saisie")}
|
||||||
décision</a>
|
décision</a>
|
||||||
""",
|
"""
|
||||||
|
if deca.inscription_etat == scu.INSCRIT
|
||||||
|
else deca.inscription_etat,
|
||||||
"col_lien_saisie_but",
|
"col_lien_saisie_but",
|
||||||
)
|
)
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
|
173
app/but/jury_but_view.py
Normal file
173
app/but/jury_but_view.py
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Jury BUT: affichage/formulaire
|
||||||
|
"""
|
||||||
|
from flask import g, url_for
|
||||||
|
from app.models.etudiants import Identite
|
||||||
|
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.but.jury_but import DecisionsProposeesAnnee, DecisionsProposeesUE
|
||||||
|
from app.models import FormSemestre, FormSemestreInscription, UniteEns
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
|
def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
||||||
|
"""Affichage des décisions annuelles BUT
|
||||||
|
Si pas read_only, menus sélection codes jury.
|
||||||
|
"""
|
||||||
|
H = []
|
||||||
|
if deca.code_valide and not read_only:
|
||||||
|
erase_span = f"""<a href="{
|
||||||
|
url_for("notes.formsemestre_jury_but_erase",
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id,
|
||||||
|
etudid=deca.etud.id)}" class="stdlink">effacer décisions</a>"""
|
||||||
|
else:
|
||||||
|
erase_span = ""
|
||||||
|
|
||||||
|
H.append(
|
||||||
|
f"""<div class="but_section_annee">
|
||||||
|
<div>
|
||||||
|
<b>Décision de jury pour l'année :</b> {
|
||||||
|
_gen_but_select("code_annee", deca.codes, deca.code_valide,
|
||||||
|
disabled=True, klass="manual")
|
||||||
|
}
|
||||||
|
<span>({'non ' if deca.code_valide is None else ''}enregistrée)</span>
|
||||||
|
<span>{erase_span}</span>
|
||||||
|
</div>
|
||||||
|
<div class="but_explanation">{deca.explanation}</div>
|
||||||
|
</div>"""
|
||||||
|
)
|
||||||
|
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
<div><b>Niveaux de compétences et unités d'enseignement :</b></div>
|
||||||
|
<div class="but_annee">
|
||||||
|
<div class="titre"></div>
|
||||||
|
<div class="titre">S{1}</div>
|
||||||
|
<div class="titre">S{2}</div>
|
||||||
|
<div class="titre">RCUE</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
for niveau in deca.niveaux_competences:
|
||||||
|
H.append(
|
||||||
|
f"""<div class="but_niveau_titre">
|
||||||
|
<div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div>
|
||||||
|
</div>"""
|
||||||
|
)
|
||||||
|
dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id)
|
||||||
|
if dec_rcue is None:
|
||||||
|
break
|
||||||
|
# Semestre impair
|
||||||
|
H.append(
|
||||||
|
_gen_but_niveau_ue(
|
||||||
|
dec_rcue.rcue.ue_1,
|
||||||
|
dec_rcue.rcue.moy_ue_1,
|
||||||
|
deca.decisions_ues[dec_rcue.rcue.ue_1.id],
|
||||||
|
disabled=read_only,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Semestre pair
|
||||||
|
H.append(
|
||||||
|
_gen_but_niveau_ue(
|
||||||
|
dec_rcue.rcue.ue_2,
|
||||||
|
dec_rcue.rcue.moy_ue_2,
|
||||||
|
deca.decisions_ues[dec_rcue.rcue.ue_2.id],
|
||||||
|
disabled=read_only,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# RCUE
|
||||||
|
H.append(
|
||||||
|
f"""<div class="but_niveau_rcue
|
||||||
|
{'recorded' if dec_rcue.code_valide is not None else ''}
|
||||||
|
">
|
||||||
|
<div class="but_note">{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
|
||||||
|
<div class="but_code">{
|
||||||
|
_gen_but_select("code_rcue_"+str(niveau.id),
|
||||||
|
dec_rcue.codes,
|
||||||
|
dec_rcue.code_valide,
|
||||||
|
disabled=True, klass="manual"
|
||||||
|
)
|
||||||
|
}</div>
|
||||||
|
</div>"""
|
||||||
|
)
|
||||||
|
H.append("</div>") # but_annee
|
||||||
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
|
def _gen_but_select(
|
||||||
|
name: str,
|
||||||
|
codes: list[str],
|
||||||
|
code_valide: str,
|
||||||
|
disabled: bool = False,
|
||||||
|
klass: str = "",
|
||||||
|
) -> str:
|
||||||
|
"Le menu html select avec les codes"
|
||||||
|
h = "\n".join(
|
||||||
|
[
|
||||||
|
f"""<option value="{code}"
|
||||||
|
{'selected' if code == code_valide else ''}
|
||||||
|
class="{'recorded' if code == code_valide else ''}"
|
||||||
|
>{code}</option>"""
|
||||||
|
for code in codes
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return f"""<select required name="{name}"
|
||||||
|
class="but_code {klass}"
|
||||||
|
onchange="change_menu_code(this);"
|
||||||
|
{"disabled" if disabled else ""}
|
||||||
|
>{h}</select>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _gen_but_niveau_ue(
|
||||||
|
ue: UniteEns, moy_ue: float, dec_ue: DecisionsProposeesUE, disabled=False
|
||||||
|
):
|
||||||
|
return f"""<div class="but_niveau_ue {
|
||||||
|
'recorded' if dec_ue.code_valide is not None else ''}
|
||||||
|
">
|
||||||
|
<div title="{ue.titre}">{ue.acronyme}</div>
|
||||||
|
<div class="but_note">{scu.fmt_note(moy_ue)}</div>
|
||||||
|
<div class="but_code">{
|
||||||
|
_gen_but_select("code_ue_"+str(ue.id),
|
||||||
|
dec_ue.codes,
|
||||||
|
dec_ue.code_valide, disabled=disabled
|
||||||
|
)
|
||||||
|
}</div>
|
||||||
|
</div>"""
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
def infos_fiche_etud_html(etudid: int) -> str:
|
||||||
|
"""Section html pour fiche etudiant
|
||||||
|
provisoire pour BUT 2022
|
||||||
|
"""
|
||||||
|
etud: Identite = Identite.query.get_or_404(etudid)
|
||||||
|
inscriptions = (
|
||||||
|
FormSemestreInscription.query.join(FormSemestreInscription.formsemestre)
|
||||||
|
.filter(
|
||||||
|
FormSemestreInscription.etudid == etud.id,
|
||||||
|
)
|
||||||
|
.order_by(FormSemestre.date_debut)
|
||||||
|
)
|
||||||
|
formsemestres_but = [
|
||||||
|
i.formsemestre for i in inscriptions if i.formsemestre.formation.is_apc()
|
||||||
|
]
|
||||||
|
if len(formsemestres_but) == 0:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# temporaire quick & dirty: affiche le dernier
|
||||||
|
try:
|
||||||
|
deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1])
|
||||||
|
if len(deca.rcues_annee) > 0:
|
||||||
|
return f"""<div class="infos_but">
|
||||||
|
{show_etud(deca, read_only=True)}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
except ScoValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return ""
|
@ -496,17 +496,26 @@ def compute_malus(
|
|||||||
"""
|
"""
|
||||||
ues_idx = [ue.id for ue in ues]
|
ues_idx = [ue.id for ue in ues]
|
||||||
malus = pd.DataFrame(index=modimpl_inscr_df.index, columns=ues_idx, dtype=float)
|
malus = pd.DataFrame(index=modimpl_inscr_df.index, columns=ues_idx, dtype=float)
|
||||||
|
if len(sem_modimpl_moys.flat) == 0: # vide
|
||||||
|
return malus
|
||||||
|
if len(sem_modimpl_moys.shape) > 2:
|
||||||
|
# BUT: ne retient que la 1er composante du malus qui est scalaire
|
||||||
|
# au sens ou chaque note de malus n'affecte que la moyenne de l'UE
|
||||||
|
# de rattachement de son module.
|
||||||
|
sem_modimpl_moys_scalar = sem_modimpl_moys[:, :, 0]
|
||||||
|
else: # classic
|
||||||
|
sem_modimpl_moys_scalar = sem_modimpl_moys
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
if ue.type != UE_SPORT:
|
if ue.type != UE_SPORT:
|
||||||
modimpl_mask = np.array(
|
modimpl_mask = np.array(
|
||||||
[
|
[
|
||||||
(m.module.module_type == ModuleType.MALUS)
|
(m.module.module_type == ModuleType.MALUS)
|
||||||
and (m.module.ue.id == ue.id)
|
and (m.module.ue.id == ue.id) # UE de rattachement
|
||||||
for m in formsemestre.modimpls_sorted
|
for m in formsemestre.modimpls_sorted
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if len(modimpl_mask):
|
if len(modimpl_mask):
|
||||||
malus_moys = sem_modimpl_moys[:, modimpl_mask].sum(axis=1)
|
malus_moys = sem_modimpl_moys_scalar[:, modimpl_mask].sum(axis=1)
|
||||||
malus[ue.id] = malus_moys
|
malus[ue.id] = malus_moys
|
||||||
|
|
||||||
malus.fillna(0.0, inplace=True)
|
malus.fillna(0.0, inplace=True)
|
||||||
|
@ -399,7 +399,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
# --- TABLEAU RECAP
|
# --- TABLEAU RECAP
|
||||||
|
|
||||||
def get_table_recap(
|
def get_table_recap(
|
||||||
self, convert_values=False, include_evaluations=False, modejury=False
|
self, convert_values=False, include_evaluations=False, mode_jury=False
|
||||||
):
|
):
|
||||||
"""Result: tuple avec
|
"""Result: tuple avec
|
||||||
- rows: liste de dicts { column_id : value }
|
- rows: liste de dicts { column_id : value }
|
||||||
@ -550,7 +550,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
titles_bot[
|
titles_bot[
|
||||||
f"_{col_id}_target_attrs"
|
f"_{col_id}_target_attrs"
|
||||||
] = f"""title="{ue.titre} S{ue.semestre_idx or '?'}" """
|
] = f"""title="{ue.titre} S{ue.semestre_idx or '?'}" """
|
||||||
if modejury:
|
if mode_jury:
|
||||||
# pas d'autre colonnes de résultats
|
# pas d'autre colonnes de résultats
|
||||||
continue
|
continue
|
||||||
# Bonus (sport) dans cette UE ?
|
# Bonus (sport) dans cette UE ?
|
||||||
@ -650,7 +650,17 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
elif nb_ues_validables < len(ues_sans_bonus):
|
elif nb_ues_validables < len(ues_sans_bonus):
|
||||||
row["_ues_validables_class"] += " moy_inf"
|
row["_ues_validables_class"] += " moy_inf"
|
||||||
row["_ues_validables_order"] = nb_ues_validables # pour tri
|
row["_ues_validables_order"] = nb_ues_validables # pour tri
|
||||||
if modejury:
|
if mode_jury and self.validations:
|
||||||
|
dec_sem = self.validations.decisions_jury.get(etudid)
|
||||||
|
jury_code_sem = dec_sem["code"] if dec_sem else ""
|
||||||
|
idx = add_cell(
|
||||||
|
row,
|
||||||
|
"jury_code_sem",
|
||||||
|
"Jury",
|
||||||
|
jury_code_sem,
|
||||||
|
"jury_code_sem",
|
||||||
|
1000,
|
||||||
|
)
|
||||||
idx = add_cell(
|
idx = add_cell(
|
||||||
row,
|
row,
|
||||||
"jury_link",
|
"jury_link",
|
||||||
@ -660,7 +670,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
)
|
)
|
||||||
}">saisir décision</a>""",
|
}">saisir décision</a>""",
|
||||||
"col_jury_link",
|
"col_jury_link",
|
||||||
1000,
|
idx,
|
||||||
)
|
)
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
self.ue_rangs_by_group = {} # { ue_id : {group_id : (Series, Series)}}
|
self.ue_rangs_by_group = {} # { ue_id : {group_id : (Series, Series)}}
|
||||||
self.expr_diagnostics = ""
|
self.expr_diagnostics = ""
|
||||||
self.parcours = self.formsemestre.formation.get_parcours()
|
self.parcours = self.formsemestre.formation.get_parcours()
|
||||||
|
self._modimpls_dict_by_ue = {} # local cache
|
||||||
|
|
||||||
def get_inscrits(self, include_demdef=True, order_by=False) -> list[Identite]:
|
def get_inscrits(self, include_demdef=True, order_by=False) -> list[Identite]:
|
||||||
"""Liste des étudiants inscrits
|
"""Liste des étudiants inscrits
|
||||||
@ -145,6 +146,10 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
"""Liste des modules pour une UE (ou toutes si ue_id==None),
|
"""Liste des modules pour une UE (ou toutes si ue_id==None),
|
||||||
triés par numéros (selon le type de formation)
|
triés par numéros (selon le type de formation)
|
||||||
"""
|
"""
|
||||||
|
# cached ?
|
||||||
|
modimpls_dict = self._modimpls_dict_by_ue.get(ue_id)
|
||||||
|
if modimpls_dict:
|
||||||
|
return modimpls_dict
|
||||||
modimpls_dict = []
|
modimpls_dict = []
|
||||||
for modimpl in self.formsemestre.modimpls_sorted:
|
for modimpl in self.formsemestre.modimpls_sorted:
|
||||||
if (ue_id is None) or (modimpl.module.ue.id == ue_id):
|
if (ue_id is None) or (modimpl.module.ue.id == ue_id):
|
||||||
@ -152,6 +157,7 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
# compat ScoDoc < 9.2: ajoute matières
|
# compat ScoDoc < 9.2: ajoute matières
|
||||||
d["mat"] = modimpl.module.matiere.to_dict()
|
d["mat"] = modimpl.module.matiere.to_dict()
|
||||||
modimpls_dict.append(d)
|
modimpls_dict.append(d)
|
||||||
|
self._modimpls_dict_by_ue[ue_id] = modimpls_dict
|
||||||
return modimpls_dict
|
return modimpls_dict
|
||||||
|
|
||||||
def compute_rangs(self):
|
def compute_rangs(self):
|
||||||
|
@ -43,7 +43,7 @@ from app.scodoc import sco_logos, html_sco_header
|
|||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
from app.scodoc.sco_config_actions import LogoInsert
|
from app.scodoc.sco_config_actions import LogoInsert
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.sco_logos import find_logo
|
from app.scodoc.sco_logos import find_logo
|
||||||
|
|
||||||
|
|
||||||
@ -108,6 +108,8 @@ def dept_key_to_id(dept_key):
|
|||||||
def logo_name_validator(message=None):
|
def logo_name_validator(message=None):
|
||||||
def validate_logo_name(form, field):
|
def validate_logo_name(form, field):
|
||||||
name = field.data if field.data else ""
|
name = field.data if field.data else ""
|
||||||
|
if "." in name:
|
||||||
|
raise ValidationError(message)
|
||||||
if not scu.is_valid_filename(name):
|
if not scu.is_valid_filename(name):
|
||||||
raise ValidationError(message)
|
raise ValidationError(message)
|
||||||
|
|
||||||
@ -199,9 +201,12 @@ class LogoForm(FlaskForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs["meta"] = {"csrf": False}
|
kwargs["meta"] = {"csrf": False}
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.logo = find_logo(
|
logo = find_logo(
|
||||||
logoname=self.logo_id.data, dept_id=dept_key_to_id(self.dept_key.data)
|
logoname=self.logo_id.data, dept_id=dept_key_to_id(self.dept_key.data)
|
||||||
).select()
|
)
|
||||||
|
if logo is None:
|
||||||
|
raise ScoValueError("logo introuvable")
|
||||||
|
self.logo = logo.select()
|
||||||
self.description = None
|
self.description = None
|
||||||
self.titre = None
|
self.titre = None
|
||||||
self.can_delete = True
|
self.can_delete = True
|
||||||
|
@ -255,6 +255,7 @@ class ApcCompetence(db.Model, XMLModel):
|
|||||||
return f"<ApcCompetence {self.id} {self.titre!r}>"
|
return f"<ApcCompetence {self.id} {self.titre!r}>"
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
|
"repr dict recursive sur situations, composantes, niveaux"
|
||||||
return {
|
return {
|
||||||
"id_orebut": self.id_orebut,
|
"id_orebut": self.id_orebut,
|
||||||
"titre": self.titre,
|
"titre": self.titre,
|
||||||
@ -268,6 +269,16 @@ class ApcCompetence(db.Model, XMLModel):
|
|||||||
"niveaux": {x.annee: x.to_dict() for x in self.niveaux},
|
"niveaux": {x.annee: x.to_dict() for x in self.niveaux},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def to_dict_bul(self) -> dict:
|
||||||
|
"dict court pour bulletins"
|
||||||
|
return {
|
||||||
|
"id_orebut": self.id_orebut,
|
||||||
|
"titre": self.titre,
|
||||||
|
"titre_long": self.titre_long,
|
||||||
|
"couleur": self.couleur,
|
||||||
|
"numero": self.numero,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ApcSituationPro(db.Model, XMLModel):
|
class ApcSituationPro(db.Model, XMLModel):
|
||||||
"Situation professionnelle"
|
"Situation professionnelle"
|
||||||
@ -341,6 +352,7 @@ class ApcNiveau(db.Model, XMLModel):
|
|||||||
self.annee!r} {self.competence!r}>"""
|
self.annee!r} {self.competence!r}>"""
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
|
"as a dict, recursif sur les AC"
|
||||||
return {
|
return {
|
||||||
"libelle": self.libelle,
|
"libelle": self.libelle,
|
||||||
"annee": self.annee,
|
"annee": self.annee,
|
||||||
@ -348,6 +360,15 @@ class ApcNiveau(db.Model, XMLModel):
|
|||||||
"app_critiques": {x.code: x.to_dict() for x in self.app_critiques},
|
"app_critiques": {x.code: x.to_dict() for x in self.app_critiques},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def to_dict_bul(self):
|
||||||
|
"dict pour bulletins: indique la compétence, pas les ACs (pour l'instant ?)"
|
||||||
|
return {
|
||||||
|
"libelle": self.libelle,
|
||||||
|
"annee": self.annee,
|
||||||
|
"ordre": self.ordre,
|
||||||
|
"competence": self.competence.to_dict_bul(),
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def niveaux_annee_de_parcours(
|
def niveaux_annee_de_parcours(
|
||||||
cls,
|
cls,
|
||||||
@ -430,6 +451,7 @@ class ApcAppCritique(db.Model, XMLModel):
|
|||||||
if competence is not None:
|
if competence is not None:
|
||||||
query = query.filter(ApcNiveau.competence == competence)
|
query = query.filter(ApcNiveau.competence == competence)
|
||||||
return query
|
return query
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
def __init__(self, id, niveau_id, code, libelle, modules):
|
def __init__(self, id, niveau_id, code, libelle, modules):
|
||||||
self.id = id
|
self.id = id
|
||||||
@ -437,6 +459,8 @@ class ApcAppCritique(db.Model, XMLModel):
|
|||||||
self.code = code
|
self.code = code
|
||||||
self.libelle = libelle
|
self.libelle = libelle
|
||||||
self.modules = modules
|
self.modules = modules
|
||||||
|
=======
|
||||||
|
>>>>>>> 7c340c798ad59c41653efc83bfd079f11fce1938
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
return {"libelle": self.libelle}
|
return {"libelle": self.libelle}
|
||||||
@ -523,11 +547,14 @@ class ApcAnneeParcours(db.Model, XMLModel):
|
|||||||
)
|
)
|
||||||
ordre = db.Column(db.Integer)
|
ordre = db.Column(db.Integer)
|
||||||
"numéro de l'année: 1, 2, 3"
|
"numéro de l'année: 1, 2, 3"
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
def __init__(self, id, parcours_id, ordre):
|
def __init__(self, id, parcours_id, ordre):
|
||||||
self.id = id
|
self.id = id
|
||||||
self.parcours_id = parcours_id
|
self.parcours_id = parcours_id
|
||||||
self.ordre = ordre
|
self.ordre = ordre
|
||||||
|
=======
|
||||||
|
>>>>>>> 7c340c798ad59c41653efc83bfd079f11fce1938
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__} ordre={self.ordre!r} parcours={self.parcours.code!r}>"
|
return f"<{self.__class__.__name__} ordre={self.ordre!r} parcours={self.parcours.code!r}>"
|
||||||
|
@ -13,8 +13,10 @@ from app.models import CODE_STR_LEN
|
|||||||
from app.models.but_refcomp import ApcNiveau
|
from app.models.but_refcomp import ApcNiveau
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
|
from app.models.formations import Formation
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
from app.scodoc import sco_codes_parcours as sco_codes
|
from app.scodoc import sco_codes_parcours as sco_codes
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
class ApcValidationRCUE(db.Model):
|
class ApcValidationRCUE(db.Model):
|
||||||
@ -41,6 +43,7 @@ class ApcValidationRCUE(db.Model):
|
|||||||
formsemestre_id = db.Column(
|
formsemestre_id = db.Column(
|
||||||
db.Integer, db.ForeignKey("notes_formsemestre.id"), index=True, nullable=True
|
db.Integer, db.ForeignKey("notes_formsemestre.id"), index=True, nullable=True
|
||||||
)
|
)
|
||||||
|
"formsemestre pair du RCUE"
|
||||||
# Les deux UE associées à ce niveau:
|
# Les deux UE associées à ce niveau:
|
||||||
ue1_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
|
ue1_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
|
||||||
ue2_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
|
ue2_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
|
||||||
@ -63,6 +66,10 @@ class ApcValidationRCUE(db.Model):
|
|||||||
# Par convention, il est donné par la seconde UE
|
# Par convention, il est donné par la seconde UE
|
||||||
return self.ue2.niveau_competence
|
return self.ue2.niveau_competence
|
||||||
|
|
||||||
|
def to_dict_bul(self) -> dict:
|
||||||
|
"Export dict pour bulletins"
|
||||||
|
return {"code": self.code, "niveau": self.niveau().to_dict_bul()}
|
||||||
|
|
||||||
|
|
||||||
# Attention: ce n'est pas un modèle mais une classe ordinaire:
|
# Attention: ce n'est pas un modèle mais une classe ordinaire:
|
||||||
class RegroupementCoherentUE:
|
class RegroupementCoherentUE:
|
||||||
@ -79,6 +86,7 @@ class RegroupementCoherentUE:
|
|||||||
ue_1: UniteEns,
|
ue_1: UniteEns,
|
||||||
formsemestre_2: FormSemestre,
|
formsemestre_2: FormSemestre,
|
||||||
ue_2: UniteEns,
|
ue_2: UniteEns,
|
||||||
|
inscription_etat: str,
|
||||||
):
|
):
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
@ -104,6 +112,11 @@ class RegroupementCoherentUE:
|
|||||||
"semestre pair"
|
"semestre pair"
|
||||||
self.ue_2 = ue_2
|
self.ue_2 = ue_2
|
||||||
# Stocke les moyennes d'UE
|
# Stocke les moyennes d'UE
|
||||||
|
if inscription_etat != scu.INSCRIT:
|
||||||
|
self.moy_rcue = None
|
||||||
|
self.moy_ue_1 = self.moy_ue_2 = "-"
|
||||||
|
self.moy_ue_1_val = self.moy_ue_2_val = 0.0
|
||||||
|
return
|
||||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre_1)
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre_1)
|
||||||
if ue_1.id in res.etud_moy_ue and etud.id in res.etud_moy_ue[ue_1.id]:
|
if ue_1.id in res.etud_moy_ue and etud.id in res.etud_moy_ue[ue_1.id]:
|
||||||
self.moy_ue_1 = res.etud_moy_ue[ue_1.id][etud.id]
|
self.moy_ue_1 = res.etud_moy_ue[ue_1.id][etud.id]
|
||||||
@ -190,14 +203,15 @@ class RegroupementCoherentUE:
|
|||||||
"Si ce RCUE est ADM, CMP ou ADJ, la validation. Sinon, None"
|
"Si ce RCUE est ADM, CMP ou ADJ, la validation. Sinon, None"
|
||||||
validation = self.query_validations().first()
|
validation = self.query_validations().first()
|
||||||
if (validation is not None) and (
|
if (validation is not None) and (
|
||||||
validation.code in {sco_codes.ADM, sco_codes.ADJ, sco_codes.CMP}
|
validation.code in sco_codes.CODES_RCUE_VALIDES
|
||||||
):
|
):
|
||||||
return validation
|
return validation
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# unused
|
||||||
def find_rcues(
|
def find_rcues(
|
||||||
formsemestre: FormSemestre, ue: UniteEns, etud: Identite
|
formsemestre: FormSemestre, ue: UniteEns, etud: Identite, inscription_etat: str
|
||||||
) -> list[RegroupementCoherentUE]:
|
) -> list[RegroupementCoherentUE]:
|
||||||
"""Les RCUE (niveau de compétence) à considérer pour cet étudiant dans
|
"""Les RCUE (niveau de compétence) à considérer pour cet étudiant dans
|
||||||
ce semestre pour cette UE.
|
ce semestre pour cette UE.
|
||||||
@ -245,7 +259,9 @@ def find_rcues(
|
|||||||
other_ue = UniteEns.query.get(ue_id)
|
other_ue = UniteEns.query.get(ue_id)
|
||||||
other_formsemestre = FormSemestre.query.get(formsemestre_id)
|
other_formsemestre = FormSemestre.query.get(formsemestre_id)
|
||||||
rcues.append(
|
rcues.append(
|
||||||
RegroupementCoherentUE(etud, formsemestre, ue, other_formsemestre, other_ue)
|
RegroupementCoherentUE(
|
||||||
|
etud, formsemestre, ue, other_formsemestre, other_ue, inscription_etat
|
||||||
|
)
|
||||||
)
|
)
|
||||||
# safety check: 1 seul niveau de comp. concerné:
|
# safety check: 1 seul niveau de comp. concerné:
|
||||||
assert len({rcue.ue_1.niveau_competence_id for rcue in rcues}) == 1
|
assert len({rcue.ue_1.niveau_competence_id for rcue in rcues}) == 1
|
||||||
@ -280,3 +296,45 @@ class ApcValidationAnnee(db.Model):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__} {self.id} {self.etud} BUT{self.ordre}/{self.annee_scolaire}:{self.code!r}>"
|
return f"<{self.__class__.__name__} {self.id} {self.etud} BUT{self.ordre}/{self.annee_scolaire}:{self.code!r}>"
|
||||||
|
|
||||||
|
def to_dict_bul(self) -> dict:
|
||||||
|
"dict pour bulletins"
|
||||||
|
return {
|
||||||
|
"annee_scolaire": self.annee_scolaire,
|
||||||
|
"date": self.date.isoformat(),
|
||||||
|
"code": self.code,
|
||||||
|
"ordre": self.ordre,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
|
||||||
|
"""
|
||||||
|
Un dict avec les décisions de jury BUT enregistrées.
|
||||||
|
Ne reprend pas les décisions d'UE, non spécifiques au BUT.
|
||||||
|
"""
|
||||||
|
decisions = {}
|
||||||
|
# --- RCUEs: seulement sur semestres pairs XXX à améliorer
|
||||||
|
if formsemestre.semestre_id % 2 == 0:
|
||||||
|
# validations émises depuis ce formsemestre:
|
||||||
|
validations_rcues = ApcValidationRCUE.query.filter_by(
|
||||||
|
etudid=etud.id, formsemestre_id=formsemestre.id
|
||||||
|
)
|
||||||
|
decisions["decision_rcue"] = [v.to_dict_bul() for v in validations_rcues]
|
||||||
|
else:
|
||||||
|
decisions["decision_rcue"] = []
|
||||||
|
# --- Année: prend la validation pour l'année scolaire de ce semestre
|
||||||
|
validation = (
|
||||||
|
ApcValidationAnnee.query.filter_by(
|
||||||
|
etudid=etud.id,
|
||||||
|
annee_scolaire=formsemestre.annee_scolaire(),
|
||||||
|
)
|
||||||
|
.join(ApcValidationAnnee.formsemestre)
|
||||||
|
.join(FormSemestre.formation)
|
||||||
|
.filter(Formation.formation_code == formsemestre.formation.formation_code)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if validation:
|
||||||
|
decisions["decision_annee"] = validation.to_dict_bul()
|
||||||
|
else:
|
||||||
|
decisions["decision_annee"] = None
|
||||||
|
return decisions
|
||||||
|
@ -26,6 +26,7 @@ from app.scodoc.sco_codes_parcours import (
|
|||||||
PASD,
|
PASD,
|
||||||
PAS1NCI,
|
PAS1NCI,
|
||||||
RAT,
|
RAT,
|
||||||
|
RED,
|
||||||
)
|
)
|
||||||
|
|
||||||
CODES_SCODOC_TO_APO = {
|
CODES_SCODOC_TO_APO = {
|
||||||
@ -46,6 +47,7 @@ CODES_SCODOC_TO_APO = {
|
|||||||
PASD: "PASD",
|
PASD: "PASD",
|
||||||
PAS1NCI: "PAS1NCI",
|
PAS1NCI: "PAS1NCI",
|
||||||
RAT: "ATT",
|
RAT: "ATT",
|
||||||
|
RED: "RED",
|
||||||
"NOTES_FMT": "%3.2f",
|
"NOTES_FMT": "%3.2f",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,9 +136,9 @@ class Identite(db.Model):
|
|||||||
"clé pour tris par ordre alphabétique"
|
"clé pour tris par ordre alphabétique"
|
||||||
return (
|
return (
|
||||||
scu.sanitize_string(
|
scu.sanitize_string(
|
||||||
scu.suppress_accents(self.nom_usuel or self.nom or "").lower()
|
self.nom_usuel or self.nom or "", remove_spaces=False
|
||||||
),
|
).lower(),
|
||||||
scu.sanitize_string(scu.suppress_accents(self.prenom or "").lower()),
|
scu.sanitize_string(self.prenom or "", remove_spaces=False).lower(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_first_email(self, field="email") -> str:
|
def get_first_email(self, field="email") -> str:
|
||||||
@ -205,6 +205,19 @@ class Identite(db.Model):
|
|||||||
d.update(adresse.to_dict(convert_nulls_to_str=True))
|
d.update(adresse.to_dict(convert_nulls_to_str=True))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def inscriptions(self) -> list["FormSemestreInscription"]:
|
||||||
|
"Liste des inscriptions à des formsemestres, triée, la plus récente en tête"
|
||||||
|
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
||||||
|
|
||||||
|
return (
|
||||||
|
FormSemestreInscription.query.join(FormSemestreInscription.formsemestre)
|
||||||
|
.filter(
|
||||||
|
FormSemestreInscription.etudid == self.id,
|
||||||
|
)
|
||||||
|
.order_by(desc(FormSemestre.date_debut))
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
def inscription_courante(self):
|
def inscription_courante(self):
|
||||||
"""La première inscription à un formsemestre _actuellement_ en cours.
|
"""La première inscription à un formsemestre _actuellement_ en cours.
|
||||||
None s'il n'y en a pas (ou plus, ou pas encore).
|
None s'il n'y en a pas (ou plus, ou pas encore).
|
||||||
@ -216,7 +229,7 @@ class Identite(db.Model):
|
|||||||
]
|
]
|
||||||
return r[0] if r else None
|
return r[0] if r else None
|
||||||
|
|
||||||
def inscriptions_courantes(self) -> list: # -> list[FormSemestreInscription]:
|
def inscriptions_courantes(self) -> list["FormSemestreInscription"]:
|
||||||
"""Liste des inscriptions à des semestres _courants_
|
"""Liste des inscriptions à des semestres _courants_
|
||||||
(il est rare qu'il y en ai plus d'une, mais c'est possible).
|
(il est rare qu'il y en ai plus d'une, mais c'est possible).
|
||||||
Triées par date de début de semestre décroissante (le plus récent en premier).
|
Triées par date de début de semestre décroissante (le plus récent en premier).
|
||||||
@ -244,18 +257,6 @@ class Identite(db.Model):
|
|||||||
]
|
]
|
||||||
return r[0] if r else None
|
return r[0] if r else None
|
||||||
|
|
||||||
def inscription_etat(self, formsemestre_id):
|
|
||||||
"""État de l'inscription de cet étudiant au semestre:
|
|
||||||
False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF
|
|
||||||
"""
|
|
||||||
# voir si ce n'est pas trop lent:
|
|
||||||
ins = models.FormSemestreInscription.query.filter_by(
|
|
||||||
etudid=self.id, formsemestre_id=formsemestre_id
|
|
||||||
).first()
|
|
||||||
if ins:
|
|
||||||
return ins.etat
|
|
||||||
return False
|
|
||||||
|
|
||||||
def inscription_descr(self) -> dict:
|
def inscription_descr(self) -> dict:
|
||||||
"""Description de l'état d'inscription"""
|
"""Description de l'état d'inscription"""
|
||||||
inscription_courante = self.inscription_courante()
|
inscription_courante = self.inscription_courante()
|
||||||
@ -294,6 +295,18 @@ class Identite(db.Model):
|
|||||||
"situation": situation,
|
"situation": situation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def inscription_etat(self, formsemestre_id):
|
||||||
|
"""État de l'inscription de cet étudiant au semestre:
|
||||||
|
False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF
|
||||||
|
"""
|
||||||
|
# voir si ce n'est pas trop lent:
|
||||||
|
ins = models.FormSemestreInscription.query.filter_by(
|
||||||
|
etudid=self.id, formsemestre_id=formsemestre_id
|
||||||
|
).first()
|
||||||
|
if ins:
|
||||||
|
return ins.etat
|
||||||
|
return False
|
||||||
|
|
||||||
def descr_situation_etud(self) -> str:
|
def descr_situation_etud(self) -> str:
|
||||||
"""Chaîne décrivant la situation _actuelle_ de l'étudiant.
|
"""Chaîne décrivant la situation _actuelle_ de l'étudiant.
|
||||||
Exemple:
|
Exemple:
|
||||||
@ -365,6 +378,15 @@ class Identite(db.Model):
|
|||||||
|
|
||||||
return situation
|
return situation
|
||||||
|
|
||||||
|
def etat_civil_pv(self, line_sep="\n") -> str:
|
||||||
|
"""Présentation, pour PV jury
|
||||||
|
M. Pierre Dupont
|
||||||
|
n° 12345678
|
||||||
|
né(e) le 7/06/1974
|
||||||
|
à Paris
|
||||||
|
"""
|
||||||
|
return f"""{self.nomprenom}{line_sep}n°{self.code_nip or ""}{line_sep}né{self.e} le {self.date_naissance.strftime("%d/%m/%Y") if self.date_naissance else ""}{line_sep}à {self.lieu_naissance or ""}"""
|
||||||
|
|
||||||
def photo_html(self, title=None, size="small") -> str:
|
def photo_html(self, title=None, size="small") -> str:
|
||||||
"""HTML img tag for the photo, either in small size (h90)
|
"""HTML img tag for the photo, either in small size (h90)
|
||||||
or original size (size=="orig")
|
or original size (size=="orig")
|
||||||
|
@ -141,7 +141,7 @@ class FormSemestre(db.Model):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>"
|
return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>"
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self, convert_parcours=False):
|
||||||
"dict (compatible ScoDoc7)"
|
"dict (compatible ScoDoc7)"
|
||||||
d = dict(self.__dict__)
|
d = dict(self.__dict__)
|
||||||
d.pop("_sa_instance_state", None)
|
d.pop("_sa_instance_state", None)
|
||||||
@ -160,6 +160,8 @@ class FormSemestre(db.Model):
|
|||||||
d["date_fin"] = d["date_fin_iso"] = ""
|
d["date_fin"] = d["date_fin_iso"] = ""
|
||||||
d["responsables"] = [u.id for u in self.responsables]
|
d["responsables"] = [u.id for u in self.responsables]
|
||||||
d["titre_formation"] = self.titre_formation()
|
d["titre_formation"] = self.titre_formation()
|
||||||
|
if convert_parcours:
|
||||||
|
d["parcours"] = [p.to_dict() for p in self.parcours]
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def to_dict_api(self):
|
def to_dict_api(self):
|
||||||
@ -507,6 +509,19 @@ class FormSemestre(db.Model):
|
|||||||
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
|
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_codes_apogee(self, category=None) -> set[str]:
|
||||||
|
"""Les codes Apogée (codés en base comme "VRT1,VRT2")
|
||||||
|
category: None: tous, "etapes": étapes associées, "sem: code semestre", "annee": code annuel
|
||||||
|
"""
|
||||||
|
codes = set()
|
||||||
|
if category is None or category == "etapes":
|
||||||
|
codes |= {e.etape_apo for e in self.etapes if e}
|
||||||
|
if (category is None or category == "sem") and self.elt_sem_apo:
|
||||||
|
codes |= {x.strip() for x in self.elt_sem_apo.split(",") if x}
|
||||||
|
if (category is None or category == "annee") and self.elt_annee_apo:
|
||||||
|
codes |= {x.strip() for x in self.elt_annee_apo.split(",") if x}
|
||||||
|
return codes
|
||||||
|
|
||||||
def get_inscrits(self, include_demdef=False, order=False) -> list[Identite]:
|
def get_inscrits(self, include_demdef=False, order=False) -> list[Identite]:
|
||||||
"""Liste des étudiants inscrits à ce semestre
|
"""Liste des étudiants inscrits à ce semestre
|
||||||
Si include_demdef, tous les étudiants, avec les démissionnaires
|
Si include_demdef, tous les étudiants, avec les démissionnaires
|
||||||
|
@ -175,6 +175,12 @@ class Module(db.Model):
|
|||||||
# Liste seulement les coefs définis:
|
# Liste seulement les coefs définis:
|
||||||
return [(c.ue, c.coef) for c in self.get_ue_coefs_sorted()]
|
return [(c.ue, c.coef) for c in self.get_ue_coefs_sorted()]
|
||||||
|
|
||||||
|
def get_codes_apogee(self) -> set[str]:
|
||||||
|
"""Les codes Apogée (codés en base comme "VRT1,VRT2")"""
|
||||||
|
if self.code_apogee:
|
||||||
|
return {x.strip() for x in self.code_apogee.split(",") if x}
|
||||||
|
return set()
|
||||||
|
|
||||||
|
|
||||||
class ModuleUECoef(db.Model):
|
class ModuleUECoef(db.Model):
|
||||||
"""Coefficients des modules vers les UE (APC, BUT)
|
"""Coefficients des modules vers les UE (APC, BUT)
|
||||||
|
@ -120,3 +120,9 @@ class UniteEns(db.Model):
|
|||||||
(Module.module_type != scu.ModuleType.SAE),
|
(Module.module_type != scu.ModuleType.SAE),
|
||||||
(Module.module_type != scu.ModuleType.RESSOURCE),
|
(Module.module_type != scu.ModuleType.RESSOURCE),
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
def get_codes_apogee(self) -> set[str]:
|
||||||
|
"""Les codes Apogée (codés en base comme "VRT1,VRT2")"""
|
||||||
|
if self.code_apogee:
|
||||||
|
return {x.strip() for x in self.code_apogee.split(",") if x}
|
||||||
|
return set()
|
||||||
|
@ -45,7 +45,7 @@ import random
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
import json
|
import json
|
||||||
|
from openpyxl.utils import get_column_letter
|
||||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
|
||||||
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
||||||
from reportlab.lib.colors import Color
|
from reportlab.lib.colors import Color
|
||||||
@ -127,6 +127,8 @@ class GenTable(object):
|
|||||||
filename="table", # filename, without extension
|
filename="table", # filename, without extension
|
||||||
xls_sheet_name="feuille",
|
xls_sheet_name="feuille",
|
||||||
xls_before_table=[], # liste de cellules a placer avant la table
|
xls_before_table=[], # liste de cellules a placer avant la table
|
||||||
|
xls_style_base=None, # style excel pour les cellules
|
||||||
|
xls_columns_width=None, # { col_id : largeur en "pixels excel" }
|
||||||
pdf_title="", # au dessus du tableau en pdf
|
pdf_title="", # au dessus du tableau en pdf
|
||||||
pdf_table_style=None,
|
pdf_table_style=None,
|
||||||
pdf_col_widths=None,
|
pdf_col_widths=None,
|
||||||
@ -151,6 +153,8 @@ class GenTable(object):
|
|||||||
self.page_title = page_title
|
self.page_title = page_title
|
||||||
self.pdf_link = pdf_link
|
self.pdf_link = pdf_link
|
||||||
self.xls_link = xls_link
|
self.xls_link = xls_link
|
||||||
|
self.xls_style_base = xls_style_base
|
||||||
|
self.xls_columns_width = xls_columns_width or {}
|
||||||
self.xml_link = xml_link
|
self.xml_link = xml_link
|
||||||
# HTML parameters:
|
# HTML parameters:
|
||||||
if not table_id: # random id
|
if not table_id: # random id
|
||||||
@ -495,7 +499,8 @@ class GenTable(object):
|
|||||||
sheet = wb.create_sheet(sheet_name=self.xls_sheet_name)
|
sheet = wb.create_sheet(sheet_name=self.xls_sheet_name)
|
||||||
sheet.rows += self.xls_before_table
|
sheet.rows += self.xls_before_table
|
||||||
style_bold = sco_excel.excel_make_style(bold=True)
|
style_bold = sco_excel.excel_make_style(bold=True)
|
||||||
style_base = sco_excel.excel_make_style()
|
style_base = self.xls_style_base or sco_excel.excel_make_style()
|
||||||
|
|
||||||
sheet.append_row(sheet.make_row(self.get_titles_list(), style_bold))
|
sheet.append_row(sheet.make_row(self.get_titles_list(), style_bold))
|
||||||
for line in self.get_data_list(xls_mode=True):
|
for line in self.get_data_list(xls_mode=True):
|
||||||
sheet.append_row(sheet.make_row(line, style_base))
|
sheet.append_row(sheet.make_row(line, style_base))
|
||||||
@ -505,6 +510,16 @@ class GenTable(object):
|
|||||||
if self.origin:
|
if self.origin:
|
||||||
sheet.append_blank_row() # empty line
|
sheet.append_blank_row() # empty line
|
||||||
sheet.append_single_cell_row(self.origin, style_base)
|
sheet.append_single_cell_row(self.origin, style_base)
|
||||||
|
# Largeurs des colonnes
|
||||||
|
columns_ids = list(self.columns_ids)
|
||||||
|
for col_id, width in self.xls_columns_width.items():
|
||||||
|
try:
|
||||||
|
idx = columns_ids.index(col_id)
|
||||||
|
col = get_column_letter(idx + 1)
|
||||||
|
sheet.set_column_dimension_width(col, width)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
if wb is None:
|
if wb is None:
|
||||||
return sheet.generate()
|
return sheet.generate()
|
||||||
|
|
||||||
|
@ -258,11 +258,16 @@ class ApoEtud(dict):
|
|||||||
self["nom"] = nom
|
self["nom"] = nom
|
||||||
self["prenom"] = prenom
|
self["prenom"] = prenom
|
||||||
self["naissance"] = naissance
|
self["naissance"] = naissance
|
||||||
self.cols = cols # { col_id : value } colid = 'apoL_c0001'
|
self.cols = cols
|
||||||
|
"{ col_id : value } colid = 'apoL_c0001'"
|
||||||
|
self.col_elts = {}
|
||||||
|
"{'V1RT': {'R': 'ADM', 'J': '', 'B': 20, 'N': '12.14'}}"
|
||||||
self.new_cols = {} # { col_id : value to record in csv }
|
self.new_cols = {} # { col_id : value to record in csv }
|
||||||
self.etud = None # etud ScoDoc
|
self.etud: Identite = None
|
||||||
|
"etudiant ScoDoc associé"
|
||||||
self.etat = None # ETUD_OK, ...
|
self.etat = None # ETUD_OK, ...
|
||||||
self.is_NAR = False # set to True si NARé dans un semestre
|
self.is_NAR = False
|
||||||
|
"True si NARé dans un semestre"
|
||||||
self.log = []
|
self.log = []
|
||||||
self.has_logged_no_decision = False
|
self.has_logged_no_decision = False
|
||||||
self.export_res_etape = export_res_etape # VET, ...
|
self.export_res_etape = export_res_etape # VET, ...
|
||||||
@ -276,7 +281,7 @@ class ApoEtud(dict):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "ApoEtud( nom='%s', nip='%s' )" % (self["nom"], self["nip"])
|
return f"""ApoEtud( nom='{self["nom"]}', nip='{self["nip"]}' )"""
|
||||||
|
|
||||||
def lookup_scodoc(self, etape_formsemestre_ids):
|
def lookup_scodoc(self, etape_formsemestre_ids):
|
||||||
"""Cherche l'étudiant ScoDoc associé à cet étudiant Apogée.
|
"""Cherche l'étudiant ScoDoc associé à cet étudiant Apogée.
|
||||||
@ -284,6 +289,10 @@ class ApoEtud(dict):
|
|||||||
met .etud à None.
|
met .etud à None.
|
||||||
Sinon, cherche le semestre, et met l'état à ETUD_OK ou ETUD_NON_INSCRIT.
|
Sinon, cherche le semestre, et met l'état à ETUD_OK ou ETUD_NON_INSCRIT.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# futur: #WIP
|
||||||
|
# etud: Identite = Identite.query.filter_by(code_nip=self["nip"]).first()
|
||||||
|
# self.etud = etud
|
||||||
etuds = sco_etud.get_etud_info(code_nip=self["nip"], filled=True)
|
etuds = sco_etud.get_etud_info(code_nip=self["nip"], filled=True)
|
||||||
if not etuds:
|
if not etuds:
|
||||||
# pas dans ScoDoc
|
# pas dans ScoDoc
|
||||||
@ -291,13 +300,16 @@ class ApoEtud(dict):
|
|||||||
self.log.append("non inscrit dans ScoDoc")
|
self.log.append("non inscrit dans ScoDoc")
|
||||||
self.etat = ETUD_ORPHELIN
|
self.etat = ETUD_ORPHELIN
|
||||||
else:
|
else:
|
||||||
|
# futur: #WIP
|
||||||
|
# formsemestre_ids = {
|
||||||
|
# ins.formsemestre_id for ins in etud.formsemestre_inscriptions
|
||||||
|
# }
|
||||||
|
# in_formsemestre_ids = formsemestre_ids.intersection(etape_formsemestre_ids)
|
||||||
self.etud = etuds[0]
|
self.etud = etuds[0]
|
||||||
# cherche le semestre ScoDoc correspondant à l'un de ceux de l'etape:
|
# cherche le semestre ScoDoc correspondant à l'un de ceux de l'etape:
|
||||||
formsemestre_ids = {s["formsemestre_id"] for s in self.etud["sems"]}
|
formsemestre_ids = {s["formsemestre_id"] for s in self.etud["sems"]}
|
||||||
self.in_formsemestre_ids = formsemestre_ids.intersection(
|
in_formsemestre_ids = formsemestre_ids.intersection(etape_formsemestre_ids)
|
||||||
etape_formsemestre_ids
|
if not in_formsemestre_ids:
|
||||||
)
|
|
||||||
if not self.in_formsemestre_ids:
|
|
||||||
self.log.append(
|
self.log.append(
|
||||||
"connu dans ScoDoc, mais pas inscrit dans un semestre de cette étape"
|
"connu dans ScoDoc, mais pas inscrit dans un semestre de cette étape"
|
||||||
)
|
)
|
||||||
@ -305,7 +317,7 @@ class ApoEtud(dict):
|
|||||||
else:
|
else:
|
||||||
self.etat = ETUD_OK
|
self.etat = ETUD_OK
|
||||||
|
|
||||||
def associate_sco(self, apo_data):
|
def associate_sco(self, apo_data: "ApoData"):
|
||||||
"""Recherche les valeurs des éléments Apogée pour cet étudiant
|
"""Recherche les valeurs des éléments Apogée pour cet étudiant
|
||||||
Set .new_cols
|
Set .new_cols
|
||||||
"""
|
"""
|
||||||
@ -327,7 +339,7 @@ class ApoEtud(dict):
|
|||||||
cur_sem, autre_sem = self.etud_semestres_de_etape(apo_data)
|
cur_sem, autre_sem = self.etud_semestres_de_etape(apo_data)
|
||||||
for sem in apo_data.sems_etape:
|
for sem in apo_data.sems_etape:
|
||||||
el = self.search_elt_in_sem(code, sem, cur_sem, autre_sem)
|
el = self.search_elt_in_sem(code, sem, cur_sem, autre_sem)
|
||||||
if el != None:
|
if el is not None:
|
||||||
sco_elts[code] = el
|
sco_elts[code] = el
|
||||||
break
|
break
|
||||||
self.col_elts[code] = el
|
self.col_elts[code] = el
|
||||||
@ -338,15 +350,15 @@ class ApoEtud(dict):
|
|||||||
self.new_cols[col_id] = sco_elts[code][
|
self.new_cols[col_id] = sco_elts[code][
|
||||||
apo_data.cols[col_id]["Type Rés."]
|
apo_data.cols[col_id]["Type Rés."]
|
||||||
]
|
]
|
||||||
except KeyError:
|
except KeyError as exc:
|
||||||
log(
|
log(
|
||||||
"associate_sco: missing key, etud=%s\ncode='%s'\netape='%s'"
|
f"associate_sco: missing key, etud={self}\ncode='{code}'\netape='{apo_data.etape_apogee}'"
|
||||||
% (self, code, apo_data.etape_apogee)
|
|
||||||
)
|
)
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"""L'élément %s n'a pas de résultat: peut-être une erreur dans les codes sur le programme pédagogique (vérifier qu'il est bien associé à une UE ou semestre)?"""
|
f"""L'élément {code} n'a pas de résultat: peut-être une erreur
|
||||||
% code
|
dans les codes sur le programme pédagogique
|
||||||
)
|
(vérifier qu'il est bien associé à une UE ou semestre)?"""
|
||||||
|
) from exc
|
||||||
# recopie les 4 premieres colonnes (nom, ..., naissance):
|
# recopie les 4 premieres colonnes (nom, ..., naissance):
|
||||||
for col_id in apo_data.col_ids[:4]:
|
for col_id in apo_data.col_ids[:4]:
|
||||||
self.new_cols[col_id] = self.cols[col_id]
|
self.new_cols[col_id] = self.cols[col_id]
|
||||||
@ -356,7 +368,7 @@ class ApoEtud(dict):
|
|||||||
# codes = set([apo_data.cols[col_id].code for col_id in apo_data.col_ids])
|
# codes = set([apo_data.cols[col_id].code for col_id in apo_data.col_ids])
|
||||||
# return codes - set(sco_elts)
|
# return codes - set(sco_elts)
|
||||||
|
|
||||||
def search_elt_in_sem(self, code, sem, cur_sem, autre_sem):
|
def search_elt_in_sem(self, code, sem, cur_sem, autre_sem) -> dict:
|
||||||
"""
|
"""
|
||||||
VET code jury etape
|
VET code jury etape
|
||||||
ELP élément pédagogique: UE, module
|
ELP élément pédagogique: UE, module
|
||||||
@ -820,10 +832,8 @@ class ApoData(object):
|
|||||||
elts[col["Code"]] = ApoElt([col])
|
elts[col["Code"]] = ApoElt([col])
|
||||||
return elts # { code apo : ApoElt }
|
return elts # { code apo : ApoElt }
|
||||||
|
|
||||||
def apo_read_etuds(self, f):
|
def apo_read_etuds(self, f) -> list[ApoEtud]:
|
||||||
"""Lecture des etudiants (et resultats) du fichier CSV Apogée
|
"""Lecture des etudiants (et resultats) du fichier CSV Apogée"""
|
||||||
-> liste de dicts
|
|
||||||
"""
|
|
||||||
L = []
|
L = []
|
||||||
while True:
|
while True:
|
||||||
line = f.readline()
|
line = f.readline()
|
||||||
@ -958,36 +968,38 @@ class ApoData(object):
|
|||||||
"""
|
"""
|
||||||
codes_by_sem = {}
|
codes_by_sem = {}
|
||||||
for sem in self.sems_etape:
|
for sem in self.sems_etape:
|
||||||
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(
|
||||||
|
sem["formsemestre_id"]
|
||||||
|
)
|
||||||
|
# L'ensemble des codes apo associés aux éléments:
|
||||||
|
codes_semestre = formsemestre.get_codes_apogee()
|
||||||
|
codes_modules = set().union(
|
||||||
|
*[
|
||||||
|
modimpl.module.get_codes_apogee()
|
||||||
|
for modimpl in formsemestre.modimpls
|
||||||
|
]
|
||||||
|
)
|
||||||
|
codes_ues = set().union(
|
||||||
|
*[
|
||||||
|
ue.get_codes_apogee()
|
||||||
|
for ue in formsemestre.query_ues(with_sport=True)
|
||||||
|
]
|
||||||
|
)
|
||||||
s = set()
|
s = set()
|
||||||
codes_by_sem[sem["formsemestre_id"]] = s
|
codes_by_sem[sem["formsemestre_id"]] = s
|
||||||
for col_id in self.col_ids[4:]:
|
for col_id in self.col_ids[4:]:
|
||||||
code = self.cols[col_id]["Code"] # 'V1RT'
|
code = self.cols[col_id]["Code"] # 'V1RT'
|
||||||
# associé à l'étape, l'année ou les semestre:
|
# associé à l'étape, l'année ou le semestre:
|
||||||
if (
|
if code in codes_semestre:
|
||||||
sco_formsemestre.sem_has_etape(sem, code)
|
|
||||||
or (code in {x.strip() for x in sem["elt_sem_apo"].split(",")})
|
|
||||||
or (code in {x.strip() for x in sem["elt_annee_apo"].split(",")})
|
|
||||||
):
|
|
||||||
s.add(code)
|
s.add(code)
|
||||||
continue
|
continue
|
||||||
# associé à une UE:
|
# associé à une UE:
|
||||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
if code in codes_ues:
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
s.add(code)
|
||||||
for ue in nt.get_ues_stat_dict():
|
continue
|
||||||
if ue["code_apogee"]:
|
|
||||||
codes = {x.strip() for x in ue["code_apogee"].split(",")}
|
|
||||||
if code in codes:
|
|
||||||
s.add(code)
|
|
||||||
continue
|
|
||||||
# associé à un module:
|
# associé à un module:
|
||||||
modimpls = nt.get_modimpls_dict()
|
if code in codes_modules:
|
||||||
for modimpl in modimpls:
|
s.add(code)
|
||||||
module = modimpl["module"]
|
|
||||||
if module["code_apogee"]:
|
|
||||||
codes = {x.strip() for x in module["code_apogee"].split(",")}
|
|
||||||
if code in codes:
|
|
||||||
s.add(code)
|
|
||||||
continue
|
|
||||||
# log('codes_by_sem=%s' % pprint.pformat(codes_by_sem))
|
# log('codes_by_sem=%s' % pprint.pformat(codes_by_sem))
|
||||||
return codes_by_sem
|
return codes_by_sem
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
qui est une description (humaine, format libre) de l'archive.
|
qui est une description (humaine, format libre) de l'archive.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import chardet
|
||||||
import datetime
|
import datetime
|
||||||
import glob
|
import glob
|
||||||
import json
|
import json
|
||||||
@ -55,7 +56,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
import chardet
|
from typing import Union
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
@ -232,14 +233,17 @@ class BaseArchiver(object):
|
|||||||
os.mkdir(archive_id) # if exists, raises an OSError
|
os.mkdir(archive_id) # if exists, raises an OSError
|
||||||
finally:
|
finally:
|
||||||
scu.GSL.release()
|
scu.GSL.release()
|
||||||
self.store(archive_id, "_description.txt", description.encode("utf-8"))
|
self.store(archive_id, "_description.txt", description)
|
||||||
return archive_id
|
return archive_id
|
||||||
|
|
||||||
def store(self, archive_id: str, filename: str, data: bytes):
|
def store(self, archive_id: str, filename: str, data: Union[str, bytes]):
|
||||||
"""Store data in archive, under given filename.
|
"""Store data in archive, under given filename.
|
||||||
Filename may be modified (sanitized): return used filename
|
Filename may be modified (sanitized): return used filename
|
||||||
The file is created or replaced.
|
The file is created or replaced.
|
||||||
|
data may be str or bytes
|
||||||
"""
|
"""
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = data.encode(scu.SCO_ENCODING)
|
||||||
self.initialize()
|
self.initialize()
|
||||||
filename = scu.sanitize_filename(filename)
|
filename = scu.sanitize_filename(filename)
|
||||||
log("storing %s (%d bytes) in %s" % (filename, len(data), archive_id))
|
log("storing %s (%d bytes) in %s" % (filename, len(data), archive_id))
|
||||||
@ -350,13 +354,11 @@ def do_formsemestre_archive(
|
|||||||
html_sco_header.sco_footer(),
|
html_sco_header.sco_footer(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
data = data.encode(scu.SCO_ENCODING)
|
|
||||||
PVArchive.store(archive_id, "Tableau_moyennes.html", data)
|
PVArchive.store(archive_id, "Tableau_moyennes.html", data)
|
||||||
|
|
||||||
# Bulletins en JSON
|
# Bulletins en JSON
|
||||||
data = gen_formsemestre_recapcomplet_json(formsemestre_id, xml_with_decisions=True)
|
data = gen_formsemestre_recapcomplet_json(formsemestre_id, xml_with_decisions=True)
|
||||||
data_js = json.dumps(data, indent=1, cls=scu.ScoDocJSONEncoder)
|
data_js = json.dumps(data, indent=1, cls=scu.ScoDocJSONEncoder)
|
||||||
data_js = data_js.encode(scu.SCO_ENCODING)
|
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, "Bulletins.json", data_js)
|
PVArchive.store(archive_id, "Bulletins.json", data_js)
|
||||||
# Decisions de jury, en XLS
|
# Decisions de jury, en XLS
|
||||||
|
@ -58,7 +58,6 @@ from app.scodoc import sco_formations
|
|||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_permissions_check
|
from app.scodoc import sco_permissions_check
|
||||||
from app.scodoc import sco_photos
|
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_pvjury
|
from app.scodoc import sco_pvjury
|
||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
@ -66,15 +65,6 @@ import app.scodoc.sco_utils as scu
|
|||||||
from app.scodoc.sco_utils import ModuleType, fmt_note
|
from app.scodoc.sco_utils import ModuleType, fmt_note
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
|
||||||
# ----- CLASSES DE BULLETINS DE NOTES
|
|
||||||
from app.scodoc import sco_bulletins_standard
|
|
||||||
from app.scodoc import sco_bulletins_legacy
|
|
||||||
|
|
||||||
# import sco_bulletins_example # format exemple (à désactiver en production)
|
|
||||||
|
|
||||||
# ... ajouter ici vos modules ...
|
|
||||||
from app.scodoc import sco_bulletins_ucac # format expérimental UCAC Cameroun
|
|
||||||
|
|
||||||
|
|
||||||
def get_formsemestre_bulletin_etud_json(
|
def get_formsemestre_bulletin_etud_json(
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
|
@ -92,7 +92,6 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
d = {"type": "classic", "version": "0"}
|
d = {"type": "classic", "version": "0"}
|
||||||
|
|
||||||
if (not sem["bul_hide_xml"]) or force_publishing:
|
if (not sem["bul_hide_xml"]) or force_publishing:
|
||||||
published = True
|
published = True
|
||||||
else:
|
else:
|
||||||
@ -134,6 +133,7 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
)
|
)
|
||||||
d["etudiant"]["sexe"] = d["etudiant"]["civilite"] # backward compat for our clients
|
d["etudiant"]["sexe"] = d["etudiant"]["civilite"] # backward compat for our clients
|
||||||
# Disponible pour publication ?
|
# Disponible pour publication ?
|
||||||
|
d["publie"] = published
|
||||||
if not published:
|
if not published:
|
||||||
return d # stop !
|
return d # stop !
|
||||||
|
|
||||||
@ -364,8 +364,35 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def dict_decision_jury(etudid, formsemestre_id, with_decisions=False):
|
def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
|
||||||
"dict avec decision pour bulletins json"
|
"""dict avec decision pour bulletins json
|
||||||
|
- decision : décision semestre
|
||||||
|
- decision_ue : list des décisions UE
|
||||||
|
- situation
|
||||||
|
|
||||||
|
with_decision donne les décision même si bul_show_decision est faux.
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
{
|
||||||
|
'autorisation_inscription': [{'semestre_id': 4}],
|
||||||
|
'decision': {'code': 'ADM',
|
||||||
|
'compense_formsemestre_id': None,
|
||||||
|
'date': '2022-01-21',
|
||||||
|
'etat': 'I'},
|
||||||
|
'decision_ue': [
|
||||||
|
{
|
||||||
|
'acronyme': 'UE31',
|
||||||
|
'code': 'ADM',
|
||||||
|
'ects': 16.0,
|
||||||
|
'numero': 23,
|
||||||
|
'titre': 'Approfondissement métiers',
|
||||||
|
'ue_id': 1787
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
'situation': 'Inscrit le 25/06/2021. Décision jury: Validé. UE acquises: '
|
||||||
|
'UE31, UE32. Diplôme obtenu.'}
|
||||||
|
"""
|
||||||
from app.scodoc import sco_bulletins
|
from app.scodoc import sco_bulletins
|
||||||
|
|
||||||
d = {}
|
d = {}
|
||||||
|
@ -441,13 +441,13 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
t = {
|
t = {
|
||||||
"titre": ue["acronyme"] + " " + (ue["titre"] or ""),
|
"titre": ue["acronyme"] + " " + (ue["titre"] or ""),
|
||||||
"_titre_html": plusminus
|
"_titre_html": plusminus
|
||||||
+ ue["acronyme"]
|
+ (ue["acronyme"] or "")
|
||||||
+ " "
|
+ " "
|
||||||
+ ue["titre"]
|
+ (ue["titre"] or "")
|
||||||
+ ' <span class="bul_ue_descr">'
|
+ ' <span class="bul_ue_descr">'
|
||||||
+ ue["ue_descr_txt"]
|
+ (ue["ue_descr_txt"] or "")
|
||||||
+ "</span>",
|
+ "</span>",
|
||||||
"_titre_help": ue["ue_descr_txt"],
|
"_titre_help": ue["ue_descr_txt"] or "",
|
||||||
"_titre_colspan": 2,
|
"_titre_colspan": 2,
|
||||||
"module": ue_descr,
|
"module": ue_descr,
|
||||||
"note": ue["moy_ue_txt"],
|
"note": ue["moy_ue_txt"],
|
||||||
|
@ -189,7 +189,7 @@ CODES_SEM_ATTENTES = {ATT: True, ATB: True, ATJ: True} # semestre en attente
|
|||||||
CODES_SEM_REO = {NAR: 1} # reorientation
|
CODES_SEM_REO = {NAR: 1} # reorientation
|
||||||
|
|
||||||
CODES_UE_VALIDES = {ADM: True, CMP: True, ADJ: True} # UE validée
|
CODES_UE_VALIDES = {ADM: True, CMP: True, ADJ: True} # UE validée
|
||||||
|
CODES_RCUE_VALIDES = CODES_UE_VALIDES # Niveau RCUE validé
|
||||||
# Pour le BUT:
|
# Pour le BUT:
|
||||||
CODES_ANNEE_ARRET = {DEF, DEM, ABAN, ABL}
|
CODES_ANNEE_ARRET = {DEF, DEM, ABAN, ABL}
|
||||||
CODES_RCUE = {ADM, AJ, CMP}
|
CODES_RCUE = {ADM, AJ, CMP}
|
||||||
@ -201,6 +201,7 @@ BUT_CODES_PASSAGE = {
|
|||||||
ADJ,
|
ADJ,
|
||||||
PASD,
|
PASD,
|
||||||
PAS1NCI,
|
PAS1NCI,
|
||||||
|
ATJ,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -683,7 +683,7 @@ def module_edit(
|
|||||||
]
|
]
|
||||||
# Choix des Apprentissages Critiques
|
# Choix des Apprentissages Critiques
|
||||||
if ue is not None:
|
if ue is not None:
|
||||||
annee = f"BUT{orig_semestre_idx//2 + 1}"
|
annee = f"BUT{(orig_semestre_idx+1)//2}"
|
||||||
app_critiques = ApcAppCritique.app_critiques_ref_comp(ref_comp, annee)
|
app_critiques = ApcAppCritique.app_critiques_ref_comp(ref_comp, annee)
|
||||||
descr += (
|
descr += (
|
||||||
[
|
[
|
||||||
|
@ -52,7 +52,6 @@ from app.scodoc.sco_exceptions import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
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_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
from app.scodoc import sco_edit_apc
|
from app.scodoc import sco_edit_apc
|
||||||
from app.scodoc import sco_edit_matiere
|
from app.scodoc import sco_edit_matiere
|
||||||
@ -188,7 +187,11 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
|
|||||||
"DELETE FROM scolar_formsemestre_validation WHERE ue_id=%(ue_id)s",
|
"DELETE FROM scolar_formsemestre_validation WHERE ue_id=%(ue_id)s",
|
||||||
{"ue_id": ue.id},
|
{"ue_id": ue.id},
|
||||||
)
|
)
|
||||||
|
# delete old formulas
|
||||||
|
ndb.SimpleQuery(
|
||||||
|
"DELETE FROM notes_formsemestre_ue_computation_expr WHERE ue_id=%(ue_id)s",
|
||||||
|
{"ue_id": ue.id},
|
||||||
|
)
|
||||||
# delete all matiere in this UE
|
# delete all matiere in this UE
|
||||||
mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})
|
mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})
|
||||||
for mat in mats:
|
for mat in mats:
|
||||||
@ -448,7 +451,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||||||
<ul>"""
|
<ul>"""
|
||||||
for m in ue.modules:
|
for m in ue.modules:
|
||||||
modules_div += f"""<li><a class="stdlink" href="{url_for(
|
modules_div += f"""<li><a class="stdlink" href="{url_for(
|
||||||
"notes.module_edit",scodoc_dept=g.scodoc_dept, module_id=m.id)}">{m.code} {m.titre}</a></li>"""
|
"notes.module_edit",scodoc_dept=g.scodoc_dept, module_id=m.id)}">{m.code} {m.titre or "sans titre"}</a></li>"""
|
||||||
modules_div += """</ul></div>"""
|
modules_div += """</ul></div>"""
|
||||||
else:
|
else:
|
||||||
modules_div = ""
|
modules_div = ""
|
||||||
|
@ -59,7 +59,7 @@ class COLORS(Enum):
|
|||||||
LIGHT_YELLOW = "FFFFFF99"
|
LIGHT_YELLOW = "FFFFFF99"
|
||||||
|
|
||||||
|
|
||||||
# Un style est enregistré comme un dictionnaire qui précise la valeur d'un attributdans la liste suivante:
|
# Un style est enregistré comme un dictionnaire qui précise la valeur d'un attribut dans la liste suivante:
|
||||||
# font, border, number_format, fill,...
|
# font, border, number_format, fill,...
|
||||||
# (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
|
# (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
|
||||||
|
|
||||||
@ -288,7 +288,7 @@ class ScoExcelSheet:
|
|||||||
value -- contenu de la cellule (texte, numérique, booléen ou date)
|
value -- contenu de la cellule (texte, numérique, booléen ou date)
|
||||||
style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
|
style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
|
||||||
"""
|
"""
|
||||||
# adapatation des valeurs si nécessaire
|
# adaptation des valeurs si nécessaire
|
||||||
if value is None:
|
if value is None:
|
||||||
value = ""
|
value = ""
|
||||||
elif value is True:
|
elif value is True:
|
||||||
|
@ -1206,7 +1206,7 @@ def formsemestre_tableau_modules(
|
|||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
'<td class="scotext"><a href="moduleimpl_status?moduleimpl_id=%s" title="%s" class="formsemestre_status_link">%s</a></td>'
|
'<td class="scotext"><a href="moduleimpl_status?moduleimpl_id=%s" title="%s" class="formsemestre_status_link">%s</a></td>'
|
||||||
% (modimpl["moduleimpl_id"], mod_descr, mod.abbrev or mod.titre)
|
% (modimpl["moduleimpl_id"], mod_descr, mod.abbrev or mod.titre or "")
|
||||||
)
|
)
|
||||||
H.append('<td class="formsemestre_status_inscrits">%s</td>' % len(mod_inscrits))
|
H.append('<td class="formsemestre_status_inscrits">%s</td>' % len(mod_inscrits))
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -35,13 +35,17 @@ from app.models.etudiants import Identite
|
|||||||
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import db, log
|
||||||
|
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import FormSemestre
|
from app.models import FormSemestre
|
||||||
from app.models.notes import etud_has_notes_attente
|
from app.models.notes import etud_has_notes_attente
|
||||||
|
from app.models.validations import (
|
||||||
|
ScolarAutorisationInscription,
|
||||||
|
ScolarFormSemestreValidation,
|
||||||
|
)
|
||||||
|
from app.models.but_validations import ApcValidationRCUE, ApcValidationAnnee
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
from app.scodoc.sco_codes_parcours import *
|
from app.scodoc.sco_codes_parcours import *
|
||||||
@ -111,7 +115,7 @@ def formsemestre_validation_etud_form(
|
|||||||
url_tableau = url_for(
|
url_tableau = url_for(
|
||||||
"notes.formsemestre_recapcomplet",
|
"notes.formsemestre_recapcomplet",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
modejury=1,
|
mode_jury=1,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
selected_etudid=etudid, # va a la bonne ligne
|
selected_etudid=etudid, # va a la bonne ligne
|
||||||
)
|
)
|
||||||
@ -596,10 +600,12 @@ def formsemestre_recap_parcours_table(
|
|||||||
title="Bulletin de notes">{formsemestre.titre_annee()}{parcours_name}</a></td>
|
title="Bulletin de notes">{formsemestre.titre_annee()}{parcours_name}</a></td>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
if decision_sem:
|
if nt.is_apc:
|
||||||
|
H.append('<td class="rcp_but">BUT</td>')
|
||||||
|
elif decision_sem:
|
||||||
H.append('<td class="rcp_dec">%s</td>' % decision_sem["code"])
|
H.append('<td class="rcp_dec">%s</td>' % decision_sem["code"])
|
||||||
else:
|
else:
|
||||||
H.append('<td colspan="%d"><em>en cours</em></td>')
|
H.append("<td><em>en cours</em></td>")
|
||||||
H.append('<td class="rcp_nonass">%s</td>' % ass) # abs
|
H.append('<td class="rcp_nonass">%s</td>' % ass) # abs
|
||||||
# acronymes UEs auxquelles l'étudiant est inscrit (ou capitalisé)
|
# acronymes UEs auxquelles l'étudiant est inscrit (ou capitalisé)
|
||||||
ues = nt.get_ues_stat_dict(filter_sport=True)
|
ues = nt.get_ues_stat_dict(filter_sport=True)
|
||||||
@ -979,7 +985,7 @@ def do_formsemestre_validation_auto(formsemestre_id):
|
|||||||
H.append("</ul>")
|
H.append("</ul>")
|
||||||
H.append(
|
H.append(
|
||||||
f"""<a href="{url_for('notes.formsemestre_recapcomplet',
|
f"""<a href="{url_for('notes.formsemestre_recapcomplet',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, modejury=1)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, mode_jury=1)
|
||||||
}">continuer</a>"""
|
}">continuer</a>"""
|
||||||
)
|
)
|
||||||
H.append(html_sco_header.sco_footer())
|
H.append(html_sco_header.sco_footer())
|
||||||
@ -987,28 +993,32 @@ def do_formsemestre_validation_auto(formsemestre_id):
|
|||||||
|
|
||||||
|
|
||||||
def formsemestre_validation_suppress_etud(formsemestre_id, etudid):
|
def formsemestre_validation_suppress_etud(formsemestre_id, etudid):
|
||||||
"""Suppression des decisions de jury pour un etudiant."""
|
"""Suppression des décisions de jury pour un étudiant/formsemestre.
|
||||||
log("formsemestre_validation_suppress_etud( %s, %s)" % (formsemestre_id, etudid))
|
Efface toutes les décisions enregistrées concernant ce formsemestre et cet étudiant:
|
||||||
cnx = ndb.GetDBConnexion()
|
code semestre, UEs, autorisations d'inscription
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
"""
|
||||||
args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
|
log(f"formsemestre_validation_suppress_etud( {formsemestre_id}, {etudid})")
|
||||||
try:
|
|
||||||
# -- Validation du semestre et des UEs
|
# Validations jury classiques (semestres, UEs, autorisations)
|
||||||
cursor.execute(
|
for v in ScolarFormSemestreValidation.query.filter_by(
|
||||||
"""delete from scolar_formsemestre_validation
|
etudid=etudid, formsemestre_id=formsemestre_id
|
||||||
where etudid = %(etudid)s and formsemestre_id=%(formsemestre_id)s""",
|
):
|
||||||
args,
|
db.session.delete(v)
|
||||||
)
|
for v in ScolarAutorisationInscription.query.filter_by(
|
||||||
# -- Autorisations d'inscription
|
etudid=etudid, origin_formsemestre_id=formsemestre_id
|
||||||
cursor.execute(
|
):
|
||||||
"""delete from scolar_autorisation_inscription
|
db.session.delete(v)
|
||||||
where etudid = %(etudid)s and origin_formsemestre_id=%(formsemestre_id)s""",
|
# Validations jury spécifiques BUT
|
||||||
args,
|
for v in ApcValidationRCUE.query.filter_by(
|
||||||
)
|
etudid=etudid, formsemestre_id=formsemestre_id
|
||||||
cnx.commit()
|
):
|
||||||
except:
|
db.session.delete(v)
|
||||||
cnx.rollback()
|
for v in ApcValidationAnnee.query.filter_by(
|
||||||
raise
|
etudid=etudid, formsemestre_id=formsemestre_id
|
||||||
|
):
|
||||||
|
db.session.delete(v)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
_invalidate_etud_formation_caches(
|
_invalidate_etud_formation_caches(
|
||||||
|
@ -150,22 +150,22 @@ def import_users(users, force=""):
|
|||||||
* ok: import ok or aborted
|
* ok: import ok or aborted
|
||||||
* messages: the list of messages
|
* messages: the list of messages
|
||||||
* the # of users created
|
* the # of users created
|
||||||
"""
|
|
||||||
""" Implémentation:
|
Implémentation:
|
||||||
Pour chaque utilisateur à créer:
|
Pour chaque utilisateur à créer:
|
||||||
* vérifier données (y compris que le même nom d'utilisateur n'est pas utilisé plusieurs fois)
|
* vérifier données (y compris que le même nom d'utilisateur n'est pas utilisé plusieurs fois)
|
||||||
* générer mot de passe aléatoire
|
* générer mot de passe aléatoire
|
||||||
* créer utilisateur et mettre le mot de passe
|
* créer utilisateur et mettre le mot de passe
|
||||||
* envoyer mot de passe par mail
|
* envoyer mot de passe par mail
|
||||||
Les utilisateurs à créer sont stockés dans un dictionnaire.
|
Les utilisateurs à créer sont stockés dans un dictionnaire.
|
||||||
L'ajout effectif ne se fait qu'en fin de fonction si aucune erreur n'a été détectée
|
L'ajout effectif ne se fait qu'en fin de fonction si aucune erreur n'a été détectée
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
created = {} # uid créés
|
||||||
if len(users) == 0:
|
if len(users) == 0:
|
||||||
import_ok = False
|
import_ok = False
|
||||||
msg_list = ["Feuille vide ou illisible"]
|
msg_list = ["Feuille vide ou illisible"]
|
||||||
else:
|
else:
|
||||||
created = {} # liste de uid créés
|
|
||||||
msg_list = []
|
msg_list = []
|
||||||
line = 1 # start from excel line #2
|
line = 1 # start from excel line #2
|
||||||
import_ok = True
|
import_ok = True
|
||||||
@ -217,7 +217,7 @@ def import_users(users, force=""):
|
|||||||
else:
|
else:
|
||||||
import_ok = False
|
import_ok = False
|
||||||
except ScoValueError as value_error:
|
except ScoValueError as value_error:
|
||||||
log("import_users: exception: abort create %s" % str(created.keys()))
|
log(f"import_users: exception: abort create {str(created.keys())}")
|
||||||
raise ScoValueError(msg) from value_error
|
raise ScoValueError(msg) from value_error
|
||||||
if import_ok:
|
if import_ok:
|
||||||
for u in created.values():
|
for u in created.values():
|
||||||
@ -228,7 +228,7 @@ def import_users(users, force=""):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
mail_password(u)
|
mail_password(u)
|
||||||
else:
|
else:
|
||||||
created = [] # reset # of created users to 0
|
created = {} # reset # of created users to 0
|
||||||
return import_ok, msg_list, len(created)
|
return import_ok, msg_list, len(created)
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,7 +121,8 @@ def _list_dept_logos(dept_id=None, prefix=scu.LOGO_FILE_PREFIX):
|
|||||||
:return: le résultat de la recherche ou None si aucune image trouvée
|
:return: le résultat de la recherche ou None si aucune image trouvée
|
||||||
"""
|
"""
|
||||||
allowed_ext = "|".join(scu.LOGOS_IMAGES_ALLOWED_TYPES)
|
allowed_ext = "|".join(scu.LOGOS_IMAGES_ALLOWED_TYPES)
|
||||||
filename_parser = re.compile(f"{prefix}([^.]*).({allowed_ext})")
|
# parse filename 'logo_<logoname>.<ext> . be carefull: logoname may include '.'
|
||||||
|
filename_parser = re.compile(f"{prefix}(([^.]*.)+)({allowed_ext})")
|
||||||
logos = {}
|
logos = {}
|
||||||
path_dir = Path(scu.SCODOC_LOGOS_DIR)
|
path_dir = Path(scu.SCODOC_LOGOS_DIR)
|
||||||
if dept_id:
|
if dept_id:
|
||||||
@ -135,7 +136,7 @@ def _list_dept_logos(dept_id=None, prefix=scu.LOGO_FILE_PREFIX):
|
|||||||
if os.access(path_dir.joinpath(entry).absolute(), os.R_OK):
|
if os.access(path_dir.joinpath(entry).absolute(), os.R_OK):
|
||||||
result = filename_parser.match(entry.name)
|
result = filename_parser.match(entry.name)
|
||||||
if result:
|
if result:
|
||||||
logoname = result.group(1)
|
logoname = result.group(1)[:-1] # retreive logoname from filename (less final dot)
|
||||||
logos[logoname] = Logo(logoname=logoname, dept_id=dept_id).select()
|
logos[logoname] = Logo(logoname=logoname, dept_id=dept_id).select()
|
||||||
return logos if len(logos.keys()) > 0 else None
|
return logos if len(logos.keys()) > 0 else None
|
||||||
|
|
||||||
@ -191,6 +192,9 @@ class Logo:
|
|||||||
)
|
)
|
||||||
self.mm = "Not initialized: call the select or create function before access"
|
self.mm = "Not initialized: call the select or create function before access"
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Logo(logoname='{self.logoname}', filename='{self.filename}')"
|
||||||
|
|
||||||
def _set_format(self, fmt):
|
def _set_format(self, fmt):
|
||||||
self.suffix = fmt
|
self.suffix = fmt
|
||||||
self.filepath = self.basepath + "." + fmt
|
self.filepath = self.basepath + "." + fmt
|
||||||
|
@ -36,6 +36,7 @@ from flask_login import current_user
|
|||||||
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 import log
|
from app import log
|
||||||
|
from app.but import jury_but_view
|
||||||
from app.models.etudiants import make_etud_args
|
from app.models.etudiants import make_etud_args
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import htmlutils
|
from app.scodoc import htmlutils
|
||||||
@ -445,6 +446,10 @@ def ficheEtud(etudid=None):
|
|||||||
else:
|
else:
|
||||||
info["groupes_row"] = ""
|
info["groupes_row"] = ""
|
||||||
info["menus_etud"] = menus_etud(etudid)
|
info["menus_etud"] = menus_etud(etudid)
|
||||||
|
|
||||||
|
# raccordement provisoire pour juillet 2022, avant refonte complète de cette fiche...
|
||||||
|
info["but_infos_mkup"] = jury_but_view.infos_fiche_etud_html(etudid)
|
||||||
|
|
||||||
tmpl = """<div class="menus_etud">%(menus_etud)s</div>
|
tmpl = """<div class="menus_etud">%(menus_etud)s</div>
|
||||||
<div class="ficheEtud" id="ficheEtud"><table>
|
<div class="ficheEtud" id="ficheEtud"><table>
|
||||||
<tr><td>
|
<tr><td>
|
||||||
@ -477,6 +482,8 @@ def ficheEtud(etudid=None):
|
|||||||
|
|
||||||
%(inscriptions_mkup)s
|
%(inscriptions_mkup)s
|
||||||
|
|
||||||
|
%(but_infos_mkup)s
|
||||||
|
|
||||||
<div class="ficheadmission">
|
<div class="ficheadmission">
|
||||||
%(adm_data)s
|
%(adm_data)s
|
||||||
|
|
||||||
@ -513,7 +520,7 @@ def ficheEtud(etudid=None):
|
|||||||
"""
|
"""
|
||||||
header = html_sco_header.sco_header(
|
header = html_sco_header.sco_header(
|
||||||
page_title="Fiche étudiant %(prenom)s %(nom)s" % info,
|
page_title="Fiche étudiant %(prenom)s %(nom)s" % info,
|
||||||
cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css"],
|
cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css", "css/jury_but.css"],
|
||||||
javascripts=[
|
javascripts=[
|
||||||
"libjs/jinplace-1.2.1.min.js",
|
"libjs/jinplace-1.2.1.min.js",
|
||||||
"js/ue_list.js",
|
"js/ue_list.js",
|
||||||
|
@ -109,10 +109,14 @@ class DecisionSem(object):
|
|||||||
# log('%s: %s %s %s %s %s' % (self.codechoice,code_etat,new_code_prev,formsemestre_id_utilise_pour_compenser,devenir,assiduite) )
|
# log('%s: %s %s %s %s %s' % (self.codechoice,code_etat,new_code_prev,formsemestre_id_utilise_pour_compenser,devenir,assiduite) )
|
||||||
|
|
||||||
|
|
||||||
def SituationEtudParcours(etud, formsemestre_id):
|
def SituationEtudParcours(etud: dict, formsemestre_id: int):
|
||||||
"""renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)"""
|
"""renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)"""
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
|
||||||
|
# if formsemestre.formation.is_apc():
|
||||||
|
# return SituationEtudParcoursBUT(etud, formsemestre_id, nt)
|
||||||
|
|
||||||
parcours = nt.parcours
|
parcours = nt.parcours
|
||||||
#
|
#
|
||||||
if parcours.ECTS_ONLY:
|
if parcours.ECTS_ONLY:
|
||||||
@ -121,10 +125,10 @@ def SituationEtudParcours(etud, formsemestre_id):
|
|||||||
return SituationEtudParcoursGeneric(etud, formsemestre_id, nt)
|
return SituationEtudParcoursGeneric(etud, formsemestre_id, nt)
|
||||||
|
|
||||||
|
|
||||||
class SituationEtudParcoursGeneric(object):
|
class SituationEtudParcoursGeneric:
|
||||||
"Semestre dans un parcours"
|
"Semestre dans un parcours"
|
||||||
|
|
||||||
def __init__(self, etud, formsemestre_id, nt):
|
def __init__(self, etud: dict, formsemestre_id: int, nt: NotesTableCompat):
|
||||||
"""
|
"""
|
||||||
etud: dict filled by fill_etuds_info()
|
etud: dict filled by fill_etuds_info()
|
||||||
"""
|
"""
|
||||||
@ -132,7 +136,7 @@ class SituationEtudParcoursGeneric(object):
|
|||||||
self.etudid = etud["etudid"]
|
self.etudid = etud["etudid"]
|
||||||
self.formsemestre_id = formsemestre_id
|
self.formsemestre_id = formsemestre_id
|
||||||
self.sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
self.sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
self.nt = nt
|
self.nt: NotesTableCompat = nt
|
||||||
self.formation = self.nt.formsemestre.formation
|
self.formation = self.nt.formsemestre.formation
|
||||||
self.parcours = self.nt.parcours
|
self.parcours = self.nt.parcours
|
||||||
# Ce semestre est-il le dernier de la formation ? (e.g. semestre 4 du DUT)
|
# Ce semestre est-il le dernier de la formation ? (e.g. semestre 4 du DUT)
|
||||||
|
@ -52,7 +52,8 @@ from reportlab.platypus import Paragraph
|
|||||||
from reportlab.lib import styles
|
from reportlab.lib import styles
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g, redirect, request
|
from flask import flash, redirect, url_for
|
||||||
|
from flask import g, request
|
||||||
|
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
@ -274,7 +275,10 @@ def dict_pvjury(
|
|||||||
_codes.add(ue["ue_code"])
|
_codes.add(ue["ue_code"])
|
||||||
|
|
||||||
d["decisions_ue_descr"] = ", ".join([ue["acronyme"] for ue in ue_uniq])
|
d["decisions_ue_descr"] = ", ".join([ue["acronyme"] for ue in ue_uniq])
|
||||||
d["decision_sem_descr"] = _descr_decision_sem(d["etat"], d["decision_sem"])
|
if nt.is_apc:
|
||||||
|
d["decision_sem_descr"] = "" # pas de validation de semestre en BUT
|
||||||
|
else:
|
||||||
|
d["decision_sem_descr"] = _descr_decision_sem(d["etat"], d["decision_sem"])
|
||||||
|
|
||||||
d["autorisations"] = sco_parcours_dut.formsemestre_get_autorisation_inscription(
|
d["autorisations"] = sco_parcours_dut.formsemestre_get_autorisation_inscription(
|
||||||
etudid, formsemestre_id
|
etudid, formsemestre_id
|
||||||
@ -501,7 +505,7 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
|||||||
from app.but import jury_but_recap
|
from app.but import jury_but_recap
|
||||||
|
|
||||||
return jury_but_recap.formsemestre_saisie_jury_but(
|
return jury_but_recap.formsemestre_saisie_jury_but(
|
||||||
formsemestre, readonly=True, mode="recap"
|
formsemestre, read_only=True, mode="recap"
|
||||||
)
|
)
|
||||||
# /XXX
|
# /XXX
|
||||||
footer = html_sco_header.sco_footer()
|
footer = html_sco_header.sco_footer()
|
||||||
@ -795,7 +799,7 @@ def descrform_pvjury(sem):
|
|||||||
|
|
||||||
def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
||||||
"Lettres avis jury en PDF"
|
"Lettres avis jury en PDF"
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
if not group_ids:
|
if not group_ids:
|
||||||
# tous les inscrits du semestre
|
# tous les inscrits du semestre
|
||||||
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
||||||
@ -811,10 +815,15 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
|||||||
cssstyles=sco_groups_view.CSSSTYLES,
|
cssstyles=sco_groups_view.CSSSTYLES,
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
),
|
),
|
||||||
"""<p class="help">Utiliser cette page pour éditer des versions provisoires des PV.
|
f"""<p class="help">Utiliser cette page pour éditer des versions provisoires des PV.
|
||||||
<span class="fontred">Il est recommandé d'archiver les versions définitives: <a href="formsemestre_archive?formsemestre_id=%s">voir cette page</a></span></p>
|
<span class="fontred">Il est recommandé d'archiver les versions définitives: <a
|
||||||
"""
|
href="{url_for(
|
||||||
% formsemestre_id,
|
"notes.formsemestre_archive",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)}"
|
||||||
|
>voir cette page</a></span></p>
|
||||||
|
""",
|
||||||
]
|
]
|
||||||
F = html_sco_header.sco_footer()
|
F = html_sco_header.sco_footer()
|
||||||
descr = descrform_lettres_individuelles()
|
descr = descrform_lettres_individuelles()
|
||||||
@ -839,7 +848,11 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
|||||||
return "\n".join(H) + "\n" + tf[1] + F
|
return "\n".join(H) + "\n" + tf[1] + F
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"formsemestre_pvjury?formsemestre_id=%s" % (formsemestre_id)
|
url_for(
|
||||||
|
"notes.formsemestre_pvjury",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# submit
|
# submit
|
||||||
@ -857,15 +870,17 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
|||||||
finally:
|
finally:
|
||||||
PDFLOCK.release()
|
PDFLOCK.release()
|
||||||
if not pdfdoc:
|
if not pdfdoc:
|
||||||
|
flash("Aucun étudiant n'a de décision de jury !")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"formsemestre_status?formsemestre_id={}&head_message=Aucun%20%C3%A9tudiant%20n%27a%20de%20d%C3%A9cision%20de%20jury".format(
|
url_for(
|
||||||
formsemestre_id
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
||||||
dt = time.strftime("%Y-%m-%d")
|
|
||||||
groups_filename = "-" + groups_infos.groups_filename
|
groups_filename = "-" + groups_infos.groups_filename
|
||||||
filename = "lettres-%s%s-%s.pdf" % (sem["titre_num"], groups_filename, dt)
|
filename = f"""lettres-{formsemestre.titre_num()}{groups_filename}-{time.strftime("%Y-%m-%d")}.pdf"""
|
||||||
return scu.sendPDFFile(pdfdoc, filename)
|
return scu.sendPDFFile(pdfdoc, filename)
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,13 +45,14 @@ from flask import g
|
|||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_bulletins_pdf
|
from app.scodoc import sco_bulletins_pdf
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_pdf
|
from app.scodoc import sco_pdf
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_etud
|
|
||||||
import sco_version
|
|
||||||
from app.scodoc.sco_logos import find_logo
|
from app.scodoc.sco_logos import find_logo
|
||||||
|
from app.scodoc.sco_parcours_dut import SituationEtudParcours
|
||||||
from app.scodoc.sco_pdf import SU
|
from app.scodoc.sco_pdf import SU
|
||||||
|
import sco_version
|
||||||
|
|
||||||
LOGO_FOOTER_ASPECT = scu.CONFIG.LOGO_FOOTER_ASPECT # XXX A AUTOMATISER
|
LOGO_FOOTER_ASPECT = scu.CONFIG.LOGO_FOOTER_ASPECT # XXX A AUTOMATISER
|
||||||
LOGO_FOOTER_HEIGHT = scu.CONFIG.LOGO_FOOTER_HEIGHT * mm
|
LOGO_FOOTER_HEIGHT = scu.CONFIG.LOGO_FOOTER_HEIGHT * mm
|
||||||
@ -62,7 +63,7 @@ LOGO_HEADER_HEIGHT = scu.CONFIG.LOGO_HEADER_HEIGHT * mm
|
|||||||
LOGO_HEADER_WIDTH = LOGO_HEADER_HEIGHT * scu.CONFIG.LOGO_HEADER_ASPECT
|
LOGO_HEADER_WIDTH = LOGO_HEADER_HEIGHT * scu.CONFIG.LOGO_HEADER_ASPECT
|
||||||
|
|
||||||
|
|
||||||
def pageFooter(canvas, doc, logo, preferences, with_page_numbers=True):
|
def page_footer(canvas, doc, logo, preferences, with_page_numbers=True):
|
||||||
"Add footer on page"
|
"Add footer on page"
|
||||||
width = doc.pagesize[0] # - doc.pageTemplate.left_p - doc.pageTemplate.right_p
|
width = doc.pagesize[0] # - doc.pageTemplate.left_p - doc.pageTemplate.right_p
|
||||||
foot = Frame(
|
foot = Frame(
|
||||||
@ -78,24 +79,24 @@ def pageFooter(canvas, doc, logo, preferences, with_page_numbers=True):
|
|||||||
showBoundary=0,
|
showBoundary=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
LeftFootStyle = reportlab.lib.styles.ParagraphStyle({})
|
left_foot_style = reportlab.lib.styles.ParagraphStyle({})
|
||||||
LeftFootStyle.fontName = preferences["SCOLAR_FONT"]
|
left_foot_style.fontName = preferences["SCOLAR_FONT"]
|
||||||
LeftFootStyle.fontSize = preferences["SCOLAR_FONT_SIZE_FOOT"]
|
left_foot_style.fontSize = preferences["SCOLAR_FONT_SIZE_FOOT"]
|
||||||
LeftFootStyle.leftIndent = 0
|
left_foot_style.leftIndent = 0
|
||||||
LeftFootStyle.firstLineIndent = 0
|
left_foot_style.firstLineIndent = 0
|
||||||
LeftFootStyle.alignment = TA_RIGHT
|
left_foot_style.alignment = TA_RIGHT
|
||||||
RightFootStyle = reportlab.lib.styles.ParagraphStyle({})
|
right_foot_style = reportlab.lib.styles.ParagraphStyle({})
|
||||||
RightFootStyle.fontName = preferences["SCOLAR_FONT"]
|
right_foot_style.fontName = preferences["SCOLAR_FONT"]
|
||||||
RightFootStyle.fontSize = preferences["SCOLAR_FONT_SIZE_FOOT"]
|
right_foot_style.fontSize = preferences["SCOLAR_FONT_SIZE_FOOT"]
|
||||||
RightFootStyle.alignment = TA_RIGHT
|
right_foot_style.alignment = TA_RIGHT
|
||||||
|
|
||||||
p = sco_pdf.makeParas(
|
p = sco_pdf.makeParas(
|
||||||
"""<para>%s</para><para>%s</para>"""
|
f"""<para>{preferences["INSTITUTION_NAME"]}</para><para>{
|
||||||
% (preferences["INSTITUTION_NAME"], preferences["INSTITUTION_ADDRESS"]),
|
preferences["INSTITUTION_ADDRESS"]}</para>""",
|
||||||
LeftFootStyle,
|
left_foot_style,
|
||||||
)
|
)
|
||||||
|
|
||||||
np = Paragraph('<para fontSize="14">%d</para>' % doc.page, RightFootStyle)
|
np = Paragraph(f'<para fontSize="14">{doc.page}</para>', right_foot_style)
|
||||||
tabstyle = TableStyle(
|
tabstyle = TableStyle(
|
||||||
[
|
[
|
||||||
("LEFTPADDING", (0, 0), (-1, -1), 0),
|
("LEFTPADDING", (0, 0), (-1, -1), 0),
|
||||||
@ -123,7 +124,7 @@ def pageFooter(canvas, doc, logo, preferences, with_page_numbers=True):
|
|||||||
canvas.restoreState()
|
canvas.restoreState()
|
||||||
|
|
||||||
|
|
||||||
def pageHeader(canvas, doc, logo, preferences, only_on_first_page=False):
|
def page_header(canvas, doc, logo, preferences, only_on_first_page=False):
|
||||||
if only_on_first_page and int(doc.page) > 1:
|
if only_on_first_page and int(doc.page) > 1:
|
||||||
return
|
return
|
||||||
height = doc.pagesize[1]
|
height = doc.pagesize[1]
|
||||||
@ -260,7 +261,7 @@ class CourrierIndividuelTemplate(PageTemplate):
|
|||||||
|
|
||||||
# ---- Header/Footer
|
# ---- Header/Footer
|
||||||
if self.with_header:
|
if self.with_header:
|
||||||
pageHeader(
|
page_header(
|
||||||
canvas,
|
canvas,
|
||||||
doc,
|
doc,
|
||||||
self.logo_header,
|
self.logo_header,
|
||||||
@ -268,7 +269,7 @@ class CourrierIndividuelTemplate(PageTemplate):
|
|||||||
self.header_only_on_first_page,
|
self.header_only_on_first_page,
|
||||||
)
|
)
|
||||||
if self.with_footer:
|
if self.with_footer:
|
||||||
pageFooter(
|
page_footer(
|
||||||
canvas,
|
canvas,
|
||||||
doc,
|
doc,
|
||||||
self.logo_footer,
|
self.logo_footer,
|
||||||
@ -427,7 +428,7 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None):
|
|||||||
"""
|
"""
|
||||||
#
|
#
|
||||||
formsemestre_id = sem["formsemestre_id"]
|
formsemestre_id = sem["formsemestre_id"]
|
||||||
Se = decision["Se"]
|
Se: SituationEtudParcours = decision["Se"]
|
||||||
t, s = _descr_jury(sem, Se.parcours_validated() or not Se.semestre_non_terminal)
|
t, s = _descr_jury(sem, Se.parcours_validated() or not Se.semestre_non_terminal)
|
||||||
objects = []
|
objects = []
|
||||||
style = reportlab.lib.styles.ParagraphStyle({})
|
style = reportlab.lib.styles.ParagraphStyle({})
|
||||||
|
@ -57,7 +57,7 @@ from app.scodoc import sco_preferences
|
|||||||
|
|
||||||
def formsemestre_recapcomplet(
|
def formsemestre_recapcomplet(
|
||||||
formsemestre_id=None,
|
formsemestre_id=None,
|
||||||
modejury=False,
|
mode_jury=False,
|
||||||
tabformat="html",
|
tabformat="html",
|
||||||
sortcol=None,
|
sortcol=None,
|
||||||
xml_with_decisions=False,
|
xml_with_decisions=False,
|
||||||
@ -78,7 +78,7 @@ def formsemestre_recapcomplet(
|
|||||||
xml, json : concaténation de tous les bulletins, au format demandé
|
xml, json : concaténation de tous les bulletins, au format demandé
|
||||||
pdf : NON SUPPORTE (car tableau trop grand pour générer un pdf utilisable)
|
pdf : NON SUPPORTE (car tableau trop grand pour générer un pdf utilisable)
|
||||||
|
|
||||||
modejury: cache modules, affiche lien saisie decision jury
|
mode_jury: cache modules, affiche lien saisie decision jury
|
||||||
xml_with_decisions: publie décisions de jury dans xml et json
|
xml_with_decisions: publie décisions de jury dans xml et json
|
||||||
force_publishing: publie les xml et json même si bulletins non publiés
|
force_publishing: publie les xml et json même si bulletins non publiés
|
||||||
selected_etudid: etudid sélectionné (pour scroller au bon endroit)
|
selected_etudid: etudid sélectionné (pour scroller au bon endroit)
|
||||||
@ -91,14 +91,14 @@ def formsemestre_recapcomplet(
|
|||||||
if tabformat not in supported_formats:
|
if tabformat not in supported_formats:
|
||||||
raise ScoValueError(f"Format non supporté: {tabformat}")
|
raise ScoValueError(f"Format non supporté: {tabformat}")
|
||||||
is_file = tabformat in file_formats
|
is_file = tabformat in file_formats
|
||||||
modejury = int(modejury)
|
mode_jury = int(mode_jury)
|
||||||
xml_with_decisions = int(xml_with_decisions)
|
xml_with_decisions = int(xml_with_decisions)
|
||||||
force_publishing = int(force_publishing)
|
force_publishing = int(force_publishing)
|
||||||
|
|
||||||
data = _do_formsemestre_recapcomplet(
|
data = _do_formsemestre_recapcomplet(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
format=tabformat,
|
format=tabformat,
|
||||||
modejury=modejury,
|
mode_jury=mode_jury,
|
||||||
sortcol=sortcol,
|
sortcol=sortcol,
|
||||||
xml_with_decisions=xml_with_decisions,
|
xml_with_decisions=xml_with_decisions,
|
||||||
force_publishing=force_publishing,
|
force_publishing=force_publishing,
|
||||||
@ -123,9 +123,9 @@ def formsemestre_recapcomplet(
|
|||||||
<input type="hidden" name="formsemestre_id" value="{formsemestre_id}"></input>
|
<input type="hidden" name="formsemestre_id" value="{formsemestre_id}"></input>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
if modejury:
|
if mode_jury:
|
||||||
H.append(
|
H.append(
|
||||||
f'<input type="hidden" name="modejury" value="{modejury}"></input>'
|
f'<input type="hidden" name="mode_jury" value="{mode_jury}"></input>'
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
'<select name="tabformat" onchange="document.f.submit()" class="noprint">'
|
'<select name="tabformat" onchange="document.f.submit()" class="noprint">'
|
||||||
@ -163,7 +163,7 @@ def formsemestre_recapcomplet(
|
|||||||
)
|
)
|
||||||
if sco_permissions_check.can_validate_sem(formsemestre_id):
|
if sco_permissions_check.can_validate_sem(formsemestre_id):
|
||||||
H.append("<p>")
|
H.append("<p>")
|
||||||
if modejury:
|
if mode_jury:
|
||||||
H.append(
|
H.append(
|
||||||
f"""<a class="stdlink" href="{url_for('notes.formsemestre_validation_auto',
|
f"""<a class="stdlink" href="{url_for('notes.formsemestre_validation_auto',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||||
@ -172,7 +172,7 @@ def formsemestre_recapcomplet(
|
|||||||
else:
|
else:
|
||||||
H.append(
|
H.append(
|
||||||
f"""<a class="stdlink" href="{url_for('notes.formsemestre_recapcomplet',
|
f"""<a class="stdlink" href="{url_for('notes.formsemestre_recapcomplet',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, modejury=1)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, mode_jury=1)
|
||||||
}">Saisie des décisions du jury</a>"""
|
}">Saisie des décisions du jury</a>"""
|
||||||
)
|
)
|
||||||
H.append("</p>")
|
H.append("</p>")
|
||||||
@ -196,7 +196,7 @@ def _do_formsemestre_recapcomplet(
|
|||||||
formsemestre_id=None,
|
formsemestre_id=None,
|
||||||
format="html", # html, xml, xls, xlsall, json
|
format="html", # html, xml, xls, xlsall, json
|
||||||
xml_nodate=False, # format XML sans dates (sert pour debug cache: comparaison de XML)
|
xml_nodate=False, # format XML sans dates (sert pour debug cache: comparaison de XML)
|
||||||
modejury=False, # saisie décisions jury
|
mode_jury=False, # saisie décisions jury
|
||||||
sortcol=None, # indice colonne a trier dans table T
|
sortcol=None, # indice colonne a trier dans table T
|
||||||
xml_with_decisions=False,
|
xml_with_decisions=False,
|
||||||
force_publishing=True,
|
force_publishing=True,
|
||||||
@ -215,7 +215,7 @@ def _do_formsemestre_recapcomplet(
|
|||||||
formsemestre,
|
formsemestre,
|
||||||
res,
|
res,
|
||||||
include_evaluations=(format == "evals"),
|
include_evaluations=(format == "evals"),
|
||||||
modejury=modejury,
|
mode_jury=mode_jury,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
selected_etudid=selected_etudid,
|
selected_etudid=selected_etudid,
|
||||||
)
|
)
|
||||||
@ -368,34 +368,34 @@ def gen_formsemestre_recapcomplet_html(
|
|||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
res: NotesTableCompat,
|
res: NotesTableCompat,
|
||||||
include_evaluations=False,
|
include_evaluations=False,
|
||||||
modejury=False,
|
mode_jury=False,
|
||||||
filename="",
|
filename="",
|
||||||
selected_etudid=None,
|
selected_etudid=None,
|
||||||
):
|
):
|
||||||
"""Construit table recap pour le BUT
|
"""Construit table recap pour le BUT
|
||||||
Cache le résultat pour le semestre (sauf en mode jury).
|
Cache le résultat pour le semestre (sauf en mode jury).
|
||||||
|
|
||||||
Si modejury, cache colonnes modules et affiche un lien vers la saisie de la décision de jury
|
Si mode_jury, cache colonnes modules et affiche un lien vers la saisie de la décision de jury
|
||||||
|
|
||||||
Return: data, filename
|
Return: data, filename
|
||||||
data est une chaine, le <div>...</div> incluant le tableau.
|
data est une chaine, le <div>...</div> incluant le tableau.
|
||||||
"""
|
"""
|
||||||
table_html = None
|
table_html = None
|
||||||
if not (modejury or selected_etudid):
|
if not (mode_jury or selected_etudid):
|
||||||
if include_evaluations:
|
if include_evaluations:
|
||||||
table_html = sco_cache.TableRecapWithEvalsCache.get(formsemestre.id)
|
table_html = sco_cache.TableRecapWithEvalsCache.get(formsemestre.id)
|
||||||
else:
|
else:
|
||||||
table_html = sco_cache.TableRecapCache.get(formsemestre.id)
|
table_html = sco_cache.TableRecapCache.get(formsemestre.id)
|
||||||
if modejury or (table_html is None):
|
if mode_jury or (table_html is None):
|
||||||
table_html = _gen_formsemestre_recapcomplet_html(
|
table_html = _gen_formsemestre_recapcomplet_html(
|
||||||
formsemestre,
|
formsemestre,
|
||||||
res,
|
res,
|
||||||
include_evaluations,
|
include_evaluations,
|
||||||
modejury,
|
mode_jury,
|
||||||
filename,
|
filename,
|
||||||
selected_etudid=selected_etudid,
|
selected_etudid=selected_etudid,
|
||||||
)
|
)
|
||||||
if not modejury:
|
if not mode_jury:
|
||||||
if include_evaluations:
|
if include_evaluations:
|
||||||
sco_cache.TableRecapWithEvalsCache.set(formsemestre.id, table_html)
|
sco_cache.TableRecapWithEvalsCache.set(formsemestre.id, table_html)
|
||||||
else:
|
else:
|
||||||
@ -408,13 +408,15 @@ def _gen_formsemestre_recapcomplet_html(
|
|||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
res: NotesTableCompat,
|
res: NotesTableCompat,
|
||||||
include_evaluations=False,
|
include_evaluations=False,
|
||||||
modejury=False,
|
mode_jury=False,
|
||||||
filename: str = "",
|
filename: str = "",
|
||||||
selected_etudid=None,
|
selected_etudid=None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Génère le html"""
|
"""Génère le html"""
|
||||||
rows, footer_rows, titles, column_ids = res.get_table_recap(
|
rows, footer_rows, titles, column_ids = res.get_table_recap(
|
||||||
convert_values=True, include_evaluations=include_evaluations, modejury=modejury
|
convert_values=True,
|
||||||
|
include_evaluations=include_evaluations,
|
||||||
|
mode_jury=mode_jury,
|
||||||
)
|
)
|
||||||
if not rows:
|
if not rows:
|
||||||
return (
|
return (
|
||||||
@ -423,7 +425,7 @@ def _gen_formsemestre_recapcomplet_html(
|
|||||||
H = [
|
H = [
|
||||||
f"""<div class="table_recap"><table class="table_recap {
|
f"""<div class="table_recap"><table class="table_recap {
|
||||||
'apc' if formsemestre.formation.is_apc() else 'classic'
|
'apc' if formsemestre.formation.is_apc() else 'classic'
|
||||||
} {'jury' if modejury else ''}"
|
} {'jury' if mode_jury else ''}"
|
||||||
data-filename="{filename}">"""
|
data-filename="{filename}">"""
|
||||||
]
|
]
|
||||||
# header
|
# header
|
||||||
|
@ -588,7 +588,7 @@ def purge_chars(s, allowed_chars=""):
|
|||||||
return s.translate(PurgeChars(allowed_chars=allowed_chars))
|
return s.translate(PurgeChars(allowed_chars=allowed_chars))
|
||||||
|
|
||||||
|
|
||||||
def sanitize_string(s):
|
def sanitize_string(s, remove_spaces=True):
|
||||||
"""s is an ordinary string, encoding given by SCO_ENCODING"
|
"""s is an ordinary string, encoding given by SCO_ENCODING"
|
||||||
suppress accents and chars interpreted in XML
|
suppress accents and chars interpreted in XML
|
||||||
Irreversible (not a quote)
|
Irreversible (not a quote)
|
||||||
@ -596,8 +596,10 @@ def sanitize_string(s):
|
|||||||
For ids and some filenames
|
For ids and some filenames
|
||||||
"""
|
"""
|
||||||
# Table suppressing some chars:
|
# Table suppressing some chars:
|
||||||
trans = str.maketrans("", "", "'`\"<>!&\\ ")
|
to_del = "'`\"<>!&\\ " if remove_spaces else "'`\"<>!&"
|
||||||
return suppress_accents(s.translate(trans)).replace(" ", "_").replace("\t", "_")
|
trans = str.maketrans("", "", to_del)
|
||||||
|
|
||||||
|
return suppress_accents(s.translate(trans)).replace("\t", "_")
|
||||||
|
|
||||||
|
|
||||||
_BAD_FILENAME_CHARS = str.maketrans("", "", ":/\\&[]*?'")
|
_BAD_FILENAME_CHARS = str.maketrans("", "", ":/\\&[]*?'")
|
||||||
@ -968,6 +970,8 @@ ICON_XLS = icontag("xlsicon_img", title="Version tableur")
|
|||||||
# HTML emojis
|
# HTML emojis
|
||||||
EMO_WARNING = "⚠️" # warning /!\
|
EMO_WARNING = "⚠️" # warning /!\
|
||||||
EMO_RED_TRIANGLE_DOWN = "🔻" # red triangle pointed down
|
EMO_RED_TRIANGLE_DOWN = "🔻" # red triangle pointed down
|
||||||
|
EMO_PREV_ARROW = "❮"
|
||||||
|
EMO_NEXT_ARROW = "❯"
|
||||||
|
|
||||||
|
|
||||||
def sort_dates(L, reverse=False):
|
def sort_dates(L, reverse=False):
|
||||||
@ -1097,6 +1101,10 @@ def gen_cell(key: str, row: dict, elt="td", with_col_class=False):
|
|||||||
if with_col_class:
|
if with_col_class:
|
||||||
klass = key + " " + klass
|
klass = key + " " + klass
|
||||||
attrs = f'class="{klass}"' if klass else ""
|
attrs = f'class="{klass}"' if klass else ""
|
||||||
|
data = row.get(f"_{key}_data") # dict
|
||||||
|
if data:
|
||||||
|
for k in data:
|
||||||
|
attrs += f' data-{k}="{data[k]}"'
|
||||||
order = row.get(f"_{key}_order")
|
order = row.get(f"_{key}_order")
|
||||||
if order:
|
if order:
|
||||||
attrs += f' data-order="{order}"'
|
attrs += f' data-order="{order}"'
|
||||||
|
@ -65,6 +65,19 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.but_navigation {
|
||||||
|
padding-top: 16px;
|
||||||
|
margin-left: 50px;
|
||||||
|
margin-right: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.but_navigation div {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 50px;
|
||||||
|
margin-right: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
div.but_section_annee {
|
div.but_section_annee {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
@ -73,9 +86,10 @@ div.but_settings {
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.but_explanation {
|
.but_explanation {
|
||||||
color: blueviolet;
|
color: blueviolet;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
padding-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
select:disabled {
|
select:disabled {
|
||||||
|
@ -189,10 +189,10 @@ section>div:nth-child(1){
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
#ects_tot {
|
#ects_tot, .decision, .decision_annee {
|
||||||
margin-left: 8px;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
.enteteSemestre{
|
.enteteSemestre{
|
||||||
color: black;
|
color: black;
|
||||||
|
@ -2980,7 +2980,8 @@ td.rcp_dec {
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.rcp_nonass {
|
td.rcp_nonass,
|
||||||
|
td.rcp_but {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3770,6 +3771,7 @@ table.table_recap .rang {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table.table_recap .col_ue,
|
table.table_recap .col_ue,
|
||||||
|
table.table_recap .col_ue_code,
|
||||||
table.table_recap .col_moy_gen,
|
table.table_recap .col_moy_gen,
|
||||||
table.table_recap .group {
|
table.table_recap .group {
|
||||||
border-left: 1px solid blue;
|
border-left: 1px solid blue;
|
||||||
@ -3783,15 +3785,18 @@ table.table_recap.jury .col_ue {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.table_recap.jury .col_rcue {
|
table.table_recap.jury .col_rcue,
|
||||||
|
table.table_recap.jury .col_rcue_code {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.table_recap.jury tr.even td.col_rcue {
|
table.table_recap.jury tr.even td.col_rcue,
|
||||||
|
table.table_recap.jury tr.even td.col_rcue_code {
|
||||||
background-color: #b0d4f8;
|
background-color: #b0d4f8;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.table_recap.jury tr.odd td.col_rcue {
|
table.table_recap.jury tr.odd td.col_rcue,
|
||||||
|
table.table_recap.jury tr.odd td.col_rcue_code {
|
||||||
background-color: #abcdef;
|
background-color: #abcdef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,4 +11,52 @@ function change_menu_code(elt) {
|
|||||||
// TODO: comparer avec valeur enregistrée (à mettre en data-orig ?)
|
// TODO: comparer avec valeur enregistrée (à mettre en data-orig ?)
|
||||||
// et colorer en fonction
|
// et colorer en fonction
|
||||||
elt.parentElement.parentElement.classList.add("modified");
|
elt.parentElement.parentElement.classList.add("modified");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
// Recupère la liste ordonnées des etudids
|
||||||
|
// pour avoir le "suivant" etr le "précédent"
|
||||||
|
// (liens de navigation)
|
||||||
|
const url = new URL(document.URL);
|
||||||
|
const frags = url.pathname.split("/"); // .../formsemestre_validation_but/formsemestre_id/etudid
|
||||||
|
const etudid = frags[frags.length - 1];
|
||||||
|
const formsemestre_id = frags[frags.length - 2];
|
||||||
|
const etudids_key = JSON.stringify(["etudids", url.origin, formsemestre_id]);
|
||||||
|
const etudids_str = localStorage.getItem(etudids_key);
|
||||||
|
const noms_key = JSON.stringify(["noms", url.origin, formsemestre_id]);
|
||||||
|
const noms_str = localStorage.getItem(noms_key);
|
||||||
|
if (etudids_str && noms_str) {
|
||||||
|
const etudids = JSON.parse(etudids_str);
|
||||||
|
const noms = JSON.parse(noms_str);
|
||||||
|
const cur_idx = etudids.indexOf(etudid);
|
||||||
|
let prev_idx = -1;
|
||||||
|
let next_idx = -1
|
||||||
|
if (cur_idx != -1) {
|
||||||
|
if (cur_idx > 0) {
|
||||||
|
prev_idx = cur_idx - 1;
|
||||||
|
}
|
||||||
|
if (cur_idx < etudids.length - 1) {
|
||||||
|
next_idx = cur_idx + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prev_idx != -1) {
|
||||||
|
let elem = document.querySelector("div.prev a");
|
||||||
|
if (elem) {
|
||||||
|
elem.href = elem.href.replace("PREV", etudids[prev_idx]);
|
||||||
|
elem.innerHTML = noms[prev_idx];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.querySelector("div.prev").innerHTML = "";
|
||||||
|
}
|
||||||
|
if (next_idx != -1) {
|
||||||
|
let elem = document.querySelector("div.next a");
|
||||||
|
if (elem) {
|
||||||
|
elem.href = elem.href.replace("NEXT", etudids[next_idx]);
|
||||||
|
elem.innerHTML = noms[next_idx];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.querySelector("div.next").innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
@ -83,7 +83,9 @@ class releveBUT extends HTMLElement {
|
|||||||
<div>
|
<div>
|
||||||
<div class=infoSemestre></div>
|
<div class=infoSemestre></div>
|
||||||
<div>
|
<div>
|
||||||
<div><span class=decision></span><span class="ects" id="ects_tot"></span></div>
|
<div class=decision_annee></div>
|
||||||
|
<div class=decision></div>
|
||||||
|
<div class="ects" id="ects_tot"></div>
|
||||||
<div class=dateInscription>Inscrit le </div>
|
<div class=dateInscription>Inscrit le </div>
|
||||||
<em>Les moyennes ci-dessus servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.</em>
|
<em>Les moyennes ci-dessus servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.</em>
|
||||||
</div>
|
</div>
|
||||||
@ -192,6 +194,20 @@ class releveBUT extends HTMLElement {
|
|||||||
/* Information sur le semestre */
|
/* Information sur le semestre */
|
||||||
/*******************************/
|
/*******************************/
|
||||||
showSemestre(data) {
|
showSemestre(data) {
|
||||||
|
let correspondanceCodes = {
|
||||||
|
"ADM": "Admis",
|
||||||
|
"AJD": "Admis par décision de jury",
|
||||||
|
"PASD": "Passage de droit : tout n'est pas validé, mais d'après les règles du BUT, vous passez",
|
||||||
|
"PAS1NCI": "Vous passez par décision de jury mais attention, vous n'avez pas partout le niveau suffisant",
|
||||||
|
"RED": "Ajourné mais autorisé à redoubler",
|
||||||
|
"NAR": "Non admis et non autorisé à redoubler : réorientation",
|
||||||
|
"DEM": "Démission",
|
||||||
|
"ABAN": "Abandon constaté sans lettre de démission",
|
||||||
|
"RAT": "En attente d'un rattrapage",
|
||||||
|
"EXCLU": "Exclusion dans le cadre d'une décision disciplinaire",
|
||||||
|
"DEF": "Défaillance : non évalué par manque d'assiduité",
|
||||||
|
"ABL": "Année blanche"
|
||||||
|
}
|
||||||
|
|
||||||
this.shadow.querySelector("#identite_etudiant").innerHTML = ` <a href="${data.etudiant.fiche_url}">${data.etudiant.nomprenom}</a> `;
|
this.shadow.querySelector("#identite_etudiant").innerHTML = ` <a href="${data.etudiant.fiche_url}">${data.etudiant.nomprenom}</a> `;
|
||||||
this.shadow.querySelector(".dateInscription").innerHTML += this.ISOToDate(data.semestre.inscription);
|
this.shadow.querySelector(".dateInscription").innerHTML += this.ISOToDate(data.semestre.inscription);
|
||||||
@ -208,9 +224,26 @@ class releveBUT extends HTMLElement {
|
|||||||
<div class=abs>Non justifiées</div>
|
<div class=abs>Non justifiées</div>
|
||||||
<div>${data.semestre.absences?.injustifie ?? "-"}</div>
|
<div>${data.semestre.absences?.injustifie ?? "-"}</div>
|
||||||
<div class=abs>Total</div><div>${data.semestre.absences?.total ?? "-"}</div>
|
<div class=abs>Total</div><div>${data.semestre.absences?.total ?? "-"}</div>
|
||||||
</div>
|
</div>`;
|
||||||
<a class=photo href="${data.etudiant.fiche_url}"><img src="${data.etudiant.photo_url || "default_Student.svg"}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0"></a>
|
if(data.semestre.decision_rcue?.length){
|
||||||
`;
|
output += `
|
||||||
|
<div>
|
||||||
|
<div class=enteteSemestre>RCUE</div><div></div>
|
||||||
|
${(()=>{
|
||||||
|
let output = "";
|
||||||
|
data.semestre.decision_rcue.forEach(competence=>{
|
||||||
|
output += `<div class=rang>${competence.niveau.competence.titre}</div><div>${competence.code}</div>`;
|
||||||
|
})
|
||||||
|
return output;
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
output += `
|
||||||
|
<a class=photo href="${data.etudiant.fiche_url}">
|
||||||
|
<img src="${data.etudiant.photo_url || "default_Student.svg"}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0">
|
||||||
|
</a>`;
|
||||||
/*${data.semestre.groupes.map(groupe => {
|
/*${data.semestre.groupes.map(groupe => {
|
||||||
return `
|
return `
|
||||||
<div>
|
<div>
|
||||||
@ -224,10 +257,17 @@ class releveBUT extends HTMLElement {
|
|||||||
}).join("")
|
}).join("")
|
||||||
}*/
|
}*/
|
||||||
this.shadow.querySelector(".infoSemestre").innerHTML = output;
|
this.shadow.querySelector(".infoSemestre").innerHTML = output;
|
||||||
if (data.semestre.decision?.code) {
|
|
||||||
|
|
||||||
|
/*if(data.semestre.decision_annee?.code){
|
||||||
|
this.shadow.querySelector(".decision_annee").innerHTML = "Décision année : " + data.semestre.decision_annee.code + " - " + correspondanceCodes[data.semestre.decision_annee.code];
|
||||||
|
}*/
|
||||||
|
|
||||||
|
this.shadow.querySelector(".decision").innerHTML = data.semestre.situation || "";
|
||||||
|
/*if (data.semestre.decision?.code) {
|
||||||
this.shadow.querySelector(".decision").innerHTML = "Décision jury: " + (data.semestre.decision?.code || "");
|
this.shadow.querySelector(".decision").innerHTML = "Décision jury: " + (data.semestre.decision?.code || "");
|
||||||
}
|
}*/
|
||||||
this.shadow.querySelector("#ects_tot").innerHTML = "ECTS : " + (data.semestre.ECTS?.acquis || "-") + " / " + (data.semestre.ECTS?.total || "-");
|
this.shadow.querySelector("#ects_tot").innerHTML = "ECTS : " + (data.semestre.ECTS?.acquis ?? "-") + " / " + (data.semestre.ECTS?.total ?? "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************/
|
/*******************************/
|
||||||
@ -254,13 +294,13 @@ class releveBUT extends HTMLElement {
|
|||||||
${ue}${(dataUE.titre) ? " - " + dataUE.titre : ""}
|
${ue}${(dataUE.titre) ? " - " + dataUE.titre : ""}
|
||||||
</h3>
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
<div class=moyenne>Moyenne : ${dataUE.moyenne?.value || "-"}</div>
|
<div class=moyenne>Moyenne : ${dataUE.moyenne?.value ?? "-"}</div>
|
||||||
<div class=rang>Rang : ${dataUE.moyenne?.rang} / ${dataUE.moyenne?.total}</div>
|
<div class=rang>Rang : ${dataUE.moyenne?.rang} / ${dataUE.moyenne?.total}</div>
|
||||||
<div class=info>
|
<div class=info>
|
||||||
Bonus : ${dataUE.bonus || 0} -
|
Bonus : ${dataUE.bonus || 0} -
|
||||||
Malus : ${dataUE.malus || 0}
|
Malus : ${dataUE.malus || 0}
|
||||||
<span class=ects> -
|
<span class=ects> -
|
||||||
ECTS : ${dataUE.ECTS?.acquis || "-"} / ${dataUE.ECTS?.total || "-"}
|
ECTS : ${dataUE.ECTS?.acquis ?? "-"} / ${dataUE.ECTS?.total ?? "-"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
@ -11,6 +11,7 @@ function build_table(data) {
|
|||||||
let output = "";
|
let output = "";
|
||||||
let sumsUE = {};
|
let sumsUE = {};
|
||||||
let sumsRessources = {};
|
let sumsRessources = {};
|
||||||
|
let value;
|
||||||
|
|
||||||
data.forEach((cellule) => {
|
data.forEach((cellule) => {
|
||||||
output += `
|
output += `
|
||||||
@ -31,13 +32,16 @@ function build_table(data) {
|
|||||||
--y:${cellule.y};
|
--y:${cellule.y};
|
||||||
--nbX:${cellule.nbX || 1};
|
--nbX:${cellule.nbX || 1};
|
||||||
--nbY: ${cellule.nbY || 1};
|
--nbY: ${cellule.nbY || 1};
|
||||||
">
|
">${cellule.data}</div>`; // ne pas mettre d'espace car c'est utilisé par :not(:empty) après
|
||||||
${cellule.data}
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
if (cellule.style.includes("champs")) {
|
if (cellule.style.includes("champs")) {
|
||||||
sumsRessources[cellule.y] = (sumsRessources[cellule.y] ?? 0) + (parseFloat(cellule.data) || 0);
|
if (cellule.editable == true && cellule.data) {
|
||||||
sumsUE[cellule.x] = (sumsUE[cellule.x] ?? 0) + (parseFloat(cellule.data) || 0);
|
value = parseFloat(cellule.data) *100;
|
||||||
|
} else {
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
sumsRessources[cellule.y] = (sumsRessources[cellule.y] ?? 0) + value;
|
||||||
|
sumsUE[cellule.x] = (sumsUE[cellule.x] ?? 0) + value;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -65,7 +69,7 @@ function showSums(sumsRessources, sumsUE) {
|
|||||||
--nbX:1;
|
--nbX:1;
|
||||||
--nbY:1;
|
--nbY:1;
|
||||||
">
|
">
|
||||||
${value}
|
${value / 100}
|
||||||
</div>`;
|
</div>`;
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -82,7 +86,7 @@ function showSums(sumsRessources, sumsUE) {
|
|||||||
--nbX:1;
|
--nbX:1;
|
||||||
--nbY:1;
|
--nbY:1;
|
||||||
">
|
">
|
||||||
${value}
|
${value / 100}
|
||||||
</div>`;
|
</div>`;
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -186,16 +190,16 @@ function keyCell(event) {
|
|||||||
|
|
||||||
function processSums() {
|
function processSums() {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
document.querySelectorAll(`[data-editable="true"][data-x="${this.dataset.x}"]`).forEach(e => {
|
document.querySelectorAll(`[data-editable="true"][data-x="${this.dataset.x}"]:not(:empty)`).forEach(e => {
|
||||||
sum += parseFloat(e.innerText) || 0;
|
sum += parseFloat(e.innerText) * 100;
|
||||||
})
|
})
|
||||||
document.querySelector(`.sums[data-x="${this.dataset.x}"][data-y="${lastY}"]`).innerText = sum;
|
document.querySelector(`.sums[data-x="${this.dataset.x}"][data-y="${lastY}"]`).innerText = sum / 100;
|
||||||
|
|
||||||
sum = 0;
|
sum = 0;
|
||||||
document.querySelectorAll(`[data-editable="true"][data-y="${this.dataset.y}"]`).forEach(e => {
|
document.querySelectorAll(`[data-editable="true"][data-y="${this.dataset.y}"]:not(:empty)`).forEach(e => {
|
||||||
sum += parseFloat(e.innerText) || 0;
|
sum += parseFloat(e.innerText) * 100;
|
||||||
})
|
})
|
||||||
document.querySelector(`.sums[data-x="${lastX}"][data-y="${this.dataset.y}"]`).innerText = sum;
|
document.querySelector(`.sums[data-x="${lastX}"][data-y="${this.dataset.y}"]`).innerText = sum / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************/
|
/******************************/
|
||||||
|
@ -133,7 +133,7 @@ $(function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$('table.table_recap').DataTable(
|
let table = $('table.table_recap').DataTable(
|
||||||
{
|
{
|
||||||
paging: false,
|
paging: false,
|
||||||
searching: true,
|
searching: true,
|
||||||
@ -146,6 +146,7 @@ $(function () {
|
|||||||
orderCellsTop: true, // cellules ligne 1 pour tri
|
orderCellsTop: true, // cellules ligne 1 pour tri
|
||||||
aaSorting: [], // Prevent initial sorting
|
aaSorting: [], // Prevent initial sorting
|
||||||
colReorder: true,
|
colReorder: true,
|
||||||
|
stateSave: true, // enregistre état de la table (tris, ...)
|
||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{
|
{
|
||||||
// cache les codes, le détail de l'identité, les groupes, les colonnes admission et les vides
|
// cache les codes, le détail de l'identité, les groupes, les colonnes admission et les vides
|
||||||
@ -154,7 +155,7 @@ $(function () {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Elimine les 0 à gauche pour les exports excel et les "copy"
|
// Elimine les 0 à gauche pour les exports excel et les "copy"
|
||||||
targets: ["col_mod", "col_moy_gen", "col_ue", "col_res", "col_sae", "evaluation"],
|
targets: ["col_mod", "col_moy_gen", "col_ue", "col_res", "col_sae", "evaluation", "col_rcue"],
|
||||||
render: function (data, type, row) {
|
render: function (data, type, row) {
|
||||||
return type === 'export' ? data.replace(/0(\d\..*)/, '$1') : data;
|
return type === 'export' ? data.replace(/0(\d\..*)/, '$1') : data;
|
||||||
}
|
}
|
||||||
@ -192,11 +193,22 @@ $(function () {
|
|||||||
if (formsemestre_id) {
|
if (formsemestre_id) {
|
||||||
localStorage.setItem(order_info_key, order_info);
|
localStorage.setItem(order_info_key, order_info);
|
||||||
}
|
}
|
||||||
|
let etudids = [];
|
||||||
|
document.querySelectorAll("td.col_rcues_validables").forEach(e => {
|
||||||
|
etudids.push(e.dataset.etudid);
|
||||||
|
});
|
||||||
|
let noms = [];
|
||||||
|
document.querySelectorAll("td.col_rcues_validables").forEach(e => {
|
||||||
|
noms.push(e.dataset.nomprenom);
|
||||||
|
});
|
||||||
|
const etudids_key = JSON.stringify(["etudids", url.origin, formsemestre_id]);
|
||||||
|
localStorage.setItem(etudids_key, JSON.stringify(etudids));
|
||||||
|
const noms_key = JSON.stringify(["noms", url.origin, formsemestre_id]);
|
||||||
|
localStorage.setItem(noms_key, JSON.stringify(noms));
|
||||||
},
|
},
|
||||||
"order": order_info,
|
"order": order_info,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
$('table.table_recap tbody').on('click', 'tr', function () {
|
$('table.table_recap tbody').on('click', 'tr', function () {
|
||||||
if ($(this).hasClass('selected')) {
|
if ($(this).hasClass('selected')) {
|
||||||
@ -211,8 +223,8 @@ $(function () {
|
|||||||
$(function () {
|
$(function () {
|
||||||
let row_selected = document.querySelector("#row_selected");
|
let row_selected = document.querySelector("#row_selected");
|
||||||
if (row_selected) {
|
if (row_selected) {
|
||||||
row_selected.scrollIntoView();
|
/*row_selected.scrollIntoView();
|
||||||
window.scrollBy(0, -50);
|
window.scrollBy(0, -50);*/
|
||||||
row_selected.classList.add("selected");
|
row_selected.classList.add("selected");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
<div class="but_doc_codes">
|
<div class="but_doc_codes">
|
||||||
<p><em>Ci-dessous la signification de chaque code est expliquée,
|
<p><em>Ci-dessous la signification de chaque code est expliquée,
|
||||||
ainsi que la correspondance avec les codes préconisés par
|
ainsi que la correspondance avec certains codes préconisés par
|
||||||
l'AMUE pour Apogée dans un document informel qui a circulé début
|
l'AMUE et l'ADIUT pour Apogée.
|
||||||
2022 (les éventuelles erreurs n'engagent personne).
|
</em>
|
||||||
</em></p>
|
On distingue les codes ScoDoc (utilisés ci-dessus et dans les différentes
|
||||||
|
tables générées par ScoDoc) et leur transcription vers Apogée lors des exports
|
||||||
|
(transcription paramétrable par votre administrateur ScoDoc).
|
||||||
|
</p>
|
||||||
<div class="but_doc_section">Codes d'année</div>
|
<div class="but_doc_section">Codes d'année</div>
|
||||||
<div class="but_doc">
|
<div class="but_doc">
|
||||||
<table>
|
<table>
|
||||||
@ -63,6 +66,12 @@
|
|||||||
<td class="amue">ABAN</td>
|
<td class="amue">ABAN</td>
|
||||||
<td>ABANdon constaté (sans lettre de démission)</td>
|
<td>ABANdon constaté (sans lettre de démission)</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>ATJ</td>
|
||||||
|
<td>{{codes["ATJ"]}}</td>
|
||||||
|
<td class="amue">nd</td>
|
||||||
|
<td>Non validé pour une autre raison, voir règlement local</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>RAT</td>
|
<td>RAT</td>
|
||||||
<td>{{codes["RAT"]}}</td>
|
<td>{{codes["RAT"]}}</td>
|
||||||
@ -124,6 +133,12 @@
|
|||||||
<td class="amue">AJ</td>
|
<td class="amue">AJ</td>
|
||||||
<td>Attente pour problème de moyenne</td>
|
<td>Attente pour problème de moyenne</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>ATJ</td>
|
||||||
|
<td>{{codes["ATJ"]}}</td>
|
||||||
|
<td class="amue">nd</td>
|
||||||
|
<td>Non validé pour une autre raison, voir règlement local</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>RAT</td>
|
<td>RAT</td>
|
||||||
<td>{{codes["RAT"]}}</td>
|
<td>{{codes["RAT"]}}</td>
|
||||||
@ -180,6 +195,12 @@
|
|||||||
<td class="amue">AJ</td>
|
<td class="amue">AJ</td>
|
||||||
<td>Attente pour problème de moyenne</td>
|
<td>Attente pour problème de moyenne</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>ATJ</td>
|
||||||
|
<td>{{codes["ATJ"]}}</td>
|
||||||
|
<td class="amue">nd</td>
|
||||||
|
<td>Non validé pour une autre raison, voir règlement local</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>RAT</td>
|
<td>RAT</td>
|
||||||
<td>{{codes["RAT"]}}</td>
|
<td>{{codes["RAT"]}}</td>
|
||||||
@ -212,4 +233,34 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="but_doc_section">Rappels de l'arrêté BUT (extraits)</div>
|
||||||
|
<div class="but_doc">
|
||||||
|
<ul>
|
||||||
|
<li>Au sein de chaque regroupement cohérent d’UE, la compensation est intégrale.
|
||||||
|
Si une UE n’a pas été acquise en raison d’une moyenne inférieure à 10,
|
||||||
|
cette UE sera acquise par compensation si et seulement si l’étudiant
|
||||||
|
a obtenu la moyenne au regroupement cohérent auquel l’UE appartient.</li>
|
||||||
|
<li>La poursuite d'études dans un semestre pair d’une même année est de droit
|
||||||
|
pour tout étudiant.
|
||||||
|
La poursuite d’études dans un semestre impair est possible
|
||||||
|
<em>si et seulement si</em> l’étudiant a obtenu :
|
||||||
|
<ul>
|
||||||
|
<li>la moyenne à plus de la moitié des regroupements cohérents d’UE</li>
|
||||||
|
<li>et une moyenne égale ou supérieure à 8 sur 20 à chaque regroupement cohérent d’UE.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>La poursuite d'études dans le semestre 5 nécessite de plus la validation de toutes les UE des
|
||||||
|
semestres 1 et 2 dans les conditions de validation des points 4.3 et 4.4, ou par décision de jury.</li>
|
||||||
|
</ul>
|
||||||
|
<b>Textes de référence:</b>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://www.enseignementsup-recherche.gouv.fr/fr/bo/21/Special4/ESRS2114777A.htm">Bulletin
|
||||||
|
officiel spécial n°4 du 17 juin 2021</a></li>
|
||||||
|
<li><a
|
||||||
|
href="https://cache.media.enseignementsup-recherche.gouv.fr//file/SPE4-MESRI-17-6-2021/19/4/SP4_ESR_17_6_2021_1413194.pdf">Version
|
||||||
|
pdf complète</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
@ -31,7 +31,6 @@ Module notes: issu de ScoDoc7 / ZNotes.py
|
|||||||
Emmanuel Viennet, 2021
|
Emmanuel Viennet, 2021
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import html
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
import time
|
import time
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
@ -43,6 +42,8 @@ from flask_login import current_user
|
|||||||
|
|
||||||
from app.but import jury_but, jury_but_validation_auto
|
from app.but import jury_but, jury_but_validation_auto
|
||||||
from app.but.forms import jury_but_forms
|
from app.but.forms import jury_but_forms
|
||||||
|
from app.but import jury_but_pv
|
||||||
|
from app.but import jury_but_view
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
@ -56,23 +57,21 @@ from app.models.ues import UniteEns
|
|||||||
from app import api
|
from app import api
|
||||||
from app import db
|
from app import db
|
||||||
from app import models
|
from app import models
|
||||||
from app.models import ScolarNews
|
from app.models import ScolarNews, but_validations
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.but import apc_edit_ue, bulletin_but, jury_but_recap
|
from app.but import apc_edit_ue, jury_but_recap
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
scodoc,
|
scodoc,
|
||||||
scodoc7func,
|
scodoc7func,
|
||||||
permission_required,
|
permission_required,
|
||||||
permission_required_compat_scodoc7,
|
permission_required_compat_scodoc7,
|
||||||
admin_required,
|
|
||||||
login_required,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from app.views import notes_bp as bp
|
from app.views import notes_bp as bp
|
||||||
|
|
||||||
# ---------------
|
# ---------------
|
||||||
|
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_bulletins_json, sco_utils as scu
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app import log, send_scodoc_alarm
|
from app import log, send_scodoc_alarm
|
||||||
|
|
||||||
@ -297,7 +296,7 @@ def formsemestre_bulletinetud(
|
|||||||
format = format or "html"
|
format = format or "html"
|
||||||
|
|
||||||
if not isinstance(formsemestre_id, int):
|
if not isinstance(formsemestre_id, int):
|
||||||
abort(404, description="formsemestre_id must be an integer !")
|
raise ScoInvalidIdType("formsemestre_id must be an integer !")
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
if etudid:
|
if etudid:
|
||||||
etud = models.Identite.query.get_or_404(etudid)
|
etud = models.Identite.query.get_or_404(etudid)
|
||||||
@ -2144,6 +2143,16 @@ def formsemestre_validation_etud_form(
|
|||||||
):
|
):
|
||||||
"Formulaire choix jury pour un étudiant"
|
"Formulaire choix jury pour un étudiant"
|
||||||
readonly = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
readonly = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
||||||
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
if formsemestre.formation.is_apc():
|
||||||
|
return redirect(
|
||||||
|
url_for(
|
||||||
|
"notes.formsemestre_validation_but",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
etudid=etudid,
|
||||||
|
)
|
||||||
|
)
|
||||||
return sco_formsemestre_validation.formsemestre_validation_etud_form(
|
return sco_formsemestre_validation.formsemestre_validation_etud_form(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
@ -2217,22 +2226,24 @@ def formsemestre_validation_etud_manu(
|
|||||||
|
|
||||||
# --- Jurys BUT
|
# --- Jurys BUT
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/formsemestre_validation_but/<int:formsemestre_id>/<int:etudid>",
|
"/formsemestre_validation_but/<int:formsemestre_id>/<etudid>",
|
||||||
methods=["GET", "POST"],
|
methods=["GET", "POST"],
|
||||||
)
|
)
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def formsemestre_validation_but(formsemestre_id: int, etudid: int):
|
def formsemestre_validation_but(
|
||||||
|
formsemestre_id: int,
|
||||||
|
etudid: int,
|
||||||
|
):
|
||||||
"Form. saisie décision jury semestre BUT"
|
"Form. saisie décision jury semestre BUT"
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
# la route ne donne pas le type d'etudid pour pouvoir construire des URLs
|
||||||
return scu.confirm_dialog(
|
# provisoires avec NEXT et PREV
|
||||||
message=f"<p>Opération non autorisée pour {current_user}</h2>",
|
try:
|
||||||
dest_url=url_for(
|
etudid = int(etudid)
|
||||||
"notes.formsemestre_status",
|
except:
|
||||||
scodoc_dept=g.scodoc_dept,
|
abort(404, "invalid etudid")
|
||||||
formsemestre_id=formsemestre_id,
|
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
||||||
),
|
|
||||||
)
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title="Validation BUT",
|
page_title="Validation BUT",
|
||||||
@ -2265,13 +2276,13 @@ def formsemestre_validation_but(formsemestre_id: int, etudid: int):
|
|||||||
+ html_sco_header.sco_footer()
|
+ html_sco_header.sco_footer()
|
||||||
)
|
)
|
||||||
|
|
||||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
|
||||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||||
if len(deca.rcues_annee) == 0:
|
if len(deca.rcues_annee) == 0:
|
||||||
raise ScoValueError("année incomplète: pas de jury BUT annuel possible")
|
raise ScoValueError("année incomplète: pas de jury BUT annuel possible")
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
deca.record_form(request.form)
|
if not read_only:
|
||||||
flash("codes enregistrés")
|
deca.record_form(request.form)
|
||||||
|
flash("codes enregistrés")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.formsemestre_validation_but",
|
"notes.formsemestre_validation_but",
|
||||||
@ -2280,13 +2291,7 @@ def formsemestre_validation_but(formsemestre_id: int, etudid: int):
|
|||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if deca.code_valide:
|
|
||||||
erase_span = f"""<a href="{
|
|
||||||
url_for("notes.formsemestre_jury_but_erase",
|
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id,
|
|
||||||
etudid=etudid)}" class="stdlink">effacer décisions</a>"""
|
|
||||||
else:
|
|
||||||
erase_span = ""
|
|
||||||
warning = ""
|
warning = ""
|
||||||
if len(deca.niveaux_competences) != len(deca.decisions_rcue_by_niveau):
|
if len(deca.niveaux_competences) != len(deca.decisions_rcue_by_niveau):
|
||||||
warning += f"""<div class="warning">Attention: {len(deca.niveaux_competences)}
|
warning += f"""<div class="warning">Attention: {len(deca.niveaux_competences)}
|
||||||
@ -2296,95 +2301,77 @@ def formsemestre_validation_but(formsemestre_id: int, etudid: int):
|
|||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<div>
|
<div>
|
||||||
<div class="titre_parcours">Jury BUT{deca.annee_but}
|
<div class="bull_head">
|
||||||
- Parcours {(deca.parcour.libelle if deca.parcour else False) or "non spécifié"}
|
<div>
|
||||||
- {deca.annee_scolaire_str()}</div>
|
<div class="titre_parcours">Jury BUT{deca.annee_but}
|
||||||
<div class="nom_etud">{etud.nomprenom}</div>
|
- Parcours {(deca.parcour.libelle if deca.parcour else False) or "non spécifié"}
|
||||||
{warning}
|
- {deca.annee_scolaire_str()}</div>
|
||||||
|
<div class="nom_etud">{etud.nomprenom}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bull_photo"><a href="{
|
||||||
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||||
|
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<div class="but_section_annee">
|
|
||||||
<div>
|
|
||||||
<b>Décision de jury pour l'année :</b> {
|
|
||||||
_gen_but_select("code_annee", deca.codes, deca.code_valide,
|
|
||||||
disabled=True, klass="manual")
|
|
||||||
}
|
|
||||||
<span>({'non ' if deca.code_valide is None else ''}enregistrée)</span>
|
|
||||||
<span>{erase_span}</span>
|
|
||||||
</div>
|
|
||||||
<span class="but_explanation">{deca.explanation}</span>
|
|
||||||
</div>
|
|
||||||
<div><b>Niveaux de compétences et unités d'enseignement :</b></div>
|
|
||||||
<div class="but_annee">
|
|
||||||
<div class="titre"></div>
|
|
||||||
<div class="titre">S{1}</div>
|
|
||||||
<div class="titre">S{2}</div>
|
|
||||||
<div class="titre">RCUE</div>
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
for niveau in deca.niveaux_competences:
|
H.append(jury_but_view.show_etud(deca, read_only=read_only))
|
||||||
H.append(
|
|
||||||
f"""<div class="but_niveau_titre">
|
|
||||||
<div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div>
|
|
||||||
</div>"""
|
|
||||||
)
|
|
||||||
dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id)
|
|
||||||
if dec_rcue is None:
|
|
||||||
break
|
|
||||||
# Semestre impair
|
|
||||||
H.append(
|
|
||||||
_gen_but_niveau_ue(
|
|
||||||
dec_rcue.rcue.ue_1,
|
|
||||||
dec_rcue.rcue.moy_ue_1,
|
|
||||||
deca.decisions_ues[dec_rcue.rcue.ue_1.id],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# Semestre pair
|
|
||||||
H.append(
|
|
||||||
_gen_but_niveau_ue(
|
|
||||||
dec_rcue.rcue.ue_2,
|
|
||||||
dec_rcue.rcue.moy_ue_2,
|
|
||||||
deca.decisions_ues[dec_rcue.rcue.ue_2.id],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# RCUE
|
|
||||||
H.append(
|
|
||||||
f"""<div class="but_niveau_rcue
|
|
||||||
{'recorded' if dec_rcue.code_valide is not None else ''}
|
|
||||||
">
|
|
||||||
<div class="but_note">{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
|
|
||||||
<div class="but_code">{
|
|
||||||
_gen_but_select("code_rcue_"+str(niveau.id),
|
|
||||||
dec_rcue.codes,
|
|
||||||
dec_rcue.code_valide,
|
|
||||||
disabled=True, klass="manual"
|
|
||||||
)
|
|
||||||
}</div>
|
|
||||||
</div>"""
|
|
||||||
)
|
|
||||||
H.append("</div>") # but_annee
|
|
||||||
|
|
||||||
|
if read_only:
|
||||||
|
H.append(
|
||||||
|
"""<div class="but_explanation">Vous n'avez pas la permission de modifier ces décisions.
|
||||||
|
Les champs entourés en vert sont enregistrés.</div>"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append(
|
||||||
|
f"""<div class="but_settings">
|
||||||
|
<input type="checkbox" onchange="enable_manual_codes(this)">
|
||||||
|
<em>permettre la saisie manuelles des codes d'année et de niveaux.
|
||||||
|
Dans ce cas, il vous revient de vous assurer de la cohérence entre
|
||||||
|
vos codes d'UE/RCUE/Année !</em>
|
||||||
|
</input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="but_buttons">
|
||||||
|
<input type="submit" value="Enregistrer ces décisions">
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
# --- Navigation
|
||||||
|
prev = f"""{scu.EMO_PREV_ARROW} <a href="{url_for(
|
||||||
|
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id, etudid="PREV"
|
||||||
|
)}" class="stdlink"">précédent</a>
|
||||||
|
"""
|
||||||
|
next = f"""<a href="{url_for(
|
||||||
|
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id, etudid="NEXT"
|
||||||
|
)}" class="stdlink"">suivant</a> {scu.EMO_NEXT_ARROW}
|
||||||
|
"""
|
||||||
H.append(
|
H.append(
|
||||||
f"""<div class="but_settings">
|
f"""
|
||||||
<input type="checkbox" onchange="enable_manual_codes(this)">
|
<div class="but_navigation">
|
||||||
<em>permettre la saisie manuelles des codes d'année et de niveaux.
|
<div class="prev">
|
||||||
Dans ce cas, il vous revient de vous assurer de la cohérence entre
|
{prev}
|
||||||
vos codes d'UE/RCUE/Année !</em>
|
|
||||||
</input>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="back_list">
|
||||||
<div class="but_buttons">
|
<a href="{url_for(
|
||||||
<span><input type="submit" value="Enregistrer ces décisions"></span>
|
|
||||||
<span><a href="{url_for(
|
|
||||||
"notes.formsemestre_saisie_jury", scodoc_dept=g.scodoc_dept,
|
"notes.formsemestre_saisie_jury", scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id, selected_etudid=etud.id
|
formsemestre_id=formsemestre_id, selected_etudid=etud.id
|
||||||
)}">retour à la liste</a></span>
|
)}" class="stdlink">retour à la liste</a>
|
||||||
</div>
|
</div>
|
||||||
"""
|
<div class="next">
|
||||||
|
{next}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
H.append("</form>") # but_annee
|
H.append("</form>")
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
render_template(
|
render_template(
|
||||||
@ -2399,48 +2386,6 @@ def formsemestre_validation_but(formsemestre_id: int, etudid: int):
|
|||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
def _gen_but_select(
|
|
||||||
name: str,
|
|
||||||
codes: list[str],
|
|
||||||
code_valide: str,
|
|
||||||
disabled: bool = False,
|
|
||||||
klass: str = "",
|
|
||||||
) -> str:
|
|
||||||
"Le menu html select avec les codes"
|
|
||||||
h = "\n".join(
|
|
||||||
[
|
|
||||||
f"""<option value="{code}"
|
|
||||||
{'selected' if code == code_valide else ''}
|
|
||||||
class="{'recorded' if code == code_valide else ''}"
|
|
||||||
>{code}</option>"""
|
|
||||||
for code in codes
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return f"""<select required name="{name}"
|
|
||||||
class="but_code {klass}"
|
|
||||||
onchange="change_menu_code(this);"
|
|
||||||
{"disabled" if disabled else ""}
|
|
||||||
>{h}</select>
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def _gen_but_niveau_ue(
|
|
||||||
ue: UniteEns, moy_ue: float, dec_ue: jury_but.DecisionsProposeesUE
|
|
||||||
):
|
|
||||||
return f"""<div class="but_niveau_ue {
|
|
||||||
'recorded' if dec_ue.code_valide is not None else ''}
|
|
||||||
">
|
|
||||||
<div title="{ue.titre}">{ue.acronyme}</div>
|
|
||||||
<div class="but_note">{scu.fmt_note(moy_ue)}</div>
|
|
||||||
<div class="but_code">{
|
|
||||||
_gen_but_select("code_ue_"+str(ue.id),
|
|
||||||
dec_ue.codes,
|
|
||||||
dec_ue.code_valide
|
|
||||||
)
|
|
||||||
}</div>
|
|
||||||
</div>"""
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/formsemestre_validation_auto_but/<int:formsemestre_id>", methods=["GET", "POST"]
|
"/formsemestre_validation_auto_but/<int:formsemestre_id>", methods=["GET", "POST"]
|
||||||
)
|
)
|
||||||
@ -2580,56 +2525,75 @@ def do_formsemestre_validation_auto(formsemestre_id):
|
|||||||
def formsemestre_validation_suppress_etud(
|
def formsemestre_validation_suppress_etud(
|
||||||
formsemestre_id, etudid, dialog_confirmed=False
|
formsemestre_id, etudid, dialog_confirmed=False
|
||||||
):
|
):
|
||||||
"""Suppression des decisions de jury pour un etudiant."""
|
"""Suppression des décisions de jury pour un étudiant."""
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
||||||
dest_url=scu.ScoURL(),
|
dest_url=scu.ScoURL(),
|
||||||
)
|
)
|
||||||
|
etud = Identite.query.get_or_404(etudid)
|
||||||
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
if formsemestre.formation.is_apc():
|
||||||
|
next_url = url_for(
|
||||||
|
"scolar.ficheEtud",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
etudid=etudid,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
next_url = url_for(
|
||||||
|
"notes.formsemestre_validation_etud_form",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
etudid=etudid,
|
||||||
|
)
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
d = sco_bulletins_json.dict_decision_jury(
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
etudid, formsemestre_id, with_decisions=True
|
||||||
sem = formsemestre.to_dict()
|
)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
d.update(but_validations.dict_decision_jury(etud, formsemestre))
|
||||||
decision_jury = nt.get_etud_decision_sem(etudid)
|
|
||||||
if decision_jury:
|
descr_ues = [f"{u['acronyme']}: {u['code']}" for u in d.get("decision_ue", [])]
|
||||||
existing = (
|
dec_annee = d.get("decision_annee")
|
||||||
"<p>Décision existante: %(code)s du %(event_date)s</p>" % decision_jury
|
if dec_annee:
|
||||||
)
|
descr_annee = dec_annee.get("code", "-")
|
||||||
else:
|
else:
|
||||||
existing = ""
|
descr_annee = "-"
|
||||||
|
|
||||||
|
existing = f"""
|
||||||
|
<ul>
|
||||||
|
<li>Semestre : {d.get("decision", {"code":"-"})['code'] or "-"}</li>
|
||||||
|
<li>Année BUT: {descr_annee}</li>
|
||||||
|
<li>UEs : {", ".join(descr_ues)}</li>
|
||||||
|
<li>RCUEs: {len(d.get("decision_rcue", []))} décisions</li>
|
||||||
|
</ul>
|
||||||
|
"""
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
"""<h2>Confirmer la suppression des décisions du semestre %s (%s - %s) pour %s ?</h2>%s
|
f"""<h2>Confirmer la suppression des décisions du semestre
|
||||||
<p>Cette opération est irréversible.
|
{formsemestre.titre_mois()} pour {etud.nomprenom}
|
||||||
</p>
|
</h2>
|
||||||
"""
|
<p>Cette opération est irréversible.</p>
|
||||||
% (
|
<div>
|
||||||
sem["titre_num"],
|
{existing}
|
||||||
sem["date_debut"],
|
</div>
|
||||||
sem["date_fin"],
|
""",
|
||||||
etud["nomprenom"],
|
|
||||||
existing,
|
|
||||||
),
|
|
||||||
OK="Supprimer",
|
OK="Supprimer",
|
||||||
dest_url="",
|
dest_url="",
|
||||||
cancel_url="formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s"
|
cancel_url=next_url,
|
||||||
% (formsemestre_id, etudid),
|
|
||||||
parameters={"etudid": etudid, "formsemestre_id": formsemestre_id},
|
parameters={"etudid": etudid, "formsemestre_id": formsemestre_id},
|
||||||
)
|
)
|
||||||
|
|
||||||
sco_formsemestre_validation.formsemestre_validation_suppress_etud(
|
sco_formsemestre_validation.formsemestre_validation_suppress_etud(
|
||||||
formsemestre_id, etudid
|
formsemestre_id, etudid
|
||||||
)
|
)
|
||||||
return flask.redirect(
|
flash("Décisions supprimées")
|
||||||
scu.ScoURL()
|
return flask.redirect(next_url)
|
||||||
+ "/Notes/formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&head_message=Décision%%20supprimée"
|
|
||||||
% (formsemestre_id, etudid)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ------------- PV de JURY et archives
|
# ------------- PV de JURY et archives
|
||||||
sco_publish("/formsemestre_pvjury", sco_pvjury.formsemestre_pvjury, Permission.ScoView)
|
sco_publish("/formsemestre_pvjury", sco_pvjury.formsemestre_pvjury, Permission.ScoView)
|
||||||
|
|
||||||
|
sco_publish("/pvjury_table_but", jury_but_pv.pvjury_table_but, Permission.ScoView)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre_saisie_jury")
|
@bp.route("/formsemestre_saisie_jury")
|
||||||
@scodoc
|
@scodoc
|
||||||
@ -2640,18 +2604,18 @@ def formsemestre_saisie_jury(formsemestre_id: int, selected_etudid: int = None):
|
|||||||
en semestres pairs de BUT, table spécifique avec l'année
|
en semestres pairs de BUT, table spécifique avec l'année
|
||||||
sinon, redirect vers page recap en mode jury
|
sinon, redirect vers page recap en mode jury
|
||||||
"""
|
"""
|
||||||
readonly = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
if formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0:
|
if formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0:
|
||||||
return jury_but_recap.formsemestre_saisie_jury_but(
|
return jury_but_recap.formsemestre_saisie_jury_but(
|
||||||
formsemestre, readonly, selected_etudid=selected_etudid
|
formsemestre, read_only, selected_etudid=selected_etudid
|
||||||
)
|
)
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.formsemestre_recapcomplet",
|
"notes.formsemestre_recapcomplet",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
modejury=1,
|
mode_jury=1,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2662,14 +2626,14 @@ def formsemestre_saisie_jury(formsemestre_id: int, selected_etudid: int = None):
|
|||||||
@scodoc7func
|
@scodoc7func
|
||||||
def formsemestre_jury_but_recap(formsemestre_id: int, selected_etudid: int = None):
|
def formsemestre_jury_but_recap(formsemestre_id: int, selected_etudid: int = None):
|
||||||
"""Tableau affichage des codes"""
|
"""Tableau affichage des codes"""
|
||||||
readonly = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
if not (formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0):
|
if not (formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0):
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"formsemestre_jury_but_recap: réservé aux semestres pairs de BUT"
|
"formsemestre_jury_but_recap: réservé aux semestres pairs de BUT"
|
||||||
)
|
)
|
||||||
return jury_but_recap.formsemestre_saisie_jury_but(
|
return jury_but_recap.formsemestre_saisie_jury_but(
|
||||||
formsemestre, readonly=readonly, selected_etudid=selected_etudid, mode="recap"
|
formsemestre, read_only=read_only, selected_etudid=selected_etudid, mode="recap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
2
sco_version.py
Normal file → Executable file
2
sco_version.py
Normal file → Executable file
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.3.5"
|
SCOVERSION = "9.3.16"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ except NameError:
|
|||||||
|
|
||||||
load_dotenv(os.path.join(BASEDIR, ".env"))
|
load_dotenv(os.path.join(BASEDIR, ".env"))
|
||||||
CHK_CERT = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
|
CHK_CERT = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
|
||||||
SCODOC_URL = os.environ["SCODOC_URL"] or "http://localhost:5000"
|
SCODOC_URL = os.environ.get("SCODOC_URL") or "http://localhost:5000"
|
||||||
API_URL = SCODOC_URL + "/ScoDoc/api"
|
API_URL = SCODOC_URL + "/ScoDoc/api"
|
||||||
SCODOC_USER = os.environ["SCODOC_USER"]
|
SCODOC_USER = os.environ["SCODOC_USER"]
|
||||||
SCODOC_PASSWORD = os.environ["SCODOC_PASSWORD"]
|
SCODOC_PASSWORD = os.environ["SCODOC_PASSWORD"]
|
||||||
@ -85,13 +85,13 @@ if r.status_code != 200:
|
|||||||
print(f"{len(r.json())} étudiants courants")
|
print(f"{len(r.json())} étudiants courants")
|
||||||
|
|
||||||
# Bulletin d'un BUT
|
# Bulletin d'un BUT
|
||||||
formsemestre_id = 1052 # A adapter
|
formsemestre_id = 1063 # A adapter
|
||||||
etudid = 16400
|
etudid = 16450
|
||||||
bul = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
|
bul = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
|
||||||
|
|
||||||
# d'un DUT
|
# d'un DUT
|
||||||
formsemestre_id = 1028 # A adapter
|
formsemestre_id = 1062 # A adapter
|
||||||
etudid = 14721
|
etudid = 16309
|
||||||
bul_dut = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
|
bul_dut = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,14 +21,36 @@ import requests
|
|||||||
from app.api.formsemestres import formsemestre
|
from app.api.formsemestres import formsemestre
|
||||||
|
|
||||||
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
|
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
|
||||||
|
|
||||||
from tests.api.tools_test_api import (
|
from tests.api.tools_test_api import (
|
||||||
MODIMPL_FIELDS,
|
|
||||||
verify_fields,
|
verify_fields,
|
||||||
|
MODIMPL_FIELDS,
|
||||||
EVAL_FIELDS,
|
EVAL_FIELDS,
|
||||||
SAISIE_NOTES_FIELDS,
|
SAISIE_NOTES_FIELDS,
|
||||||
FORMSEMESTRE_ETUS_FIELDS,
|
FORMSEMESTRE_ETUS_FIELDS,
|
||||||
|
FSEM_FIELDS,
|
||||||
|
FSEM_FIELDS,
|
||||||
|
UE_FIELDS,
|
||||||
|
MODULE_FIELDS,
|
||||||
|
FORMSEMESTRE_BULLETINS_FIELDS,
|
||||||
|
FORMSEMESTRE_BULLETINS_ETU_FIELDS,
|
||||||
|
FORMSEMESTRE_BULLETINS_FORMATION_FIELDS,
|
||||||
|
FORMSEMESTRE_BULLETINS_OPT_FIELDS,
|
||||||
|
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_FIELDS,
|
||||||
|
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_FIELDS,
|
||||||
|
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_POIDS_FIELDS,
|
||||||
|
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_NOTE_FIELDS,
|
||||||
|
BULLETIN_UES_UE_FIELDS,
|
||||||
|
BULLETIN_UES_UE_MOYENNE_FIELDS,
|
||||||
|
BULLETIN_UES_UE_RESSOURCES_RESSOURCE_FIELDS,
|
||||||
|
BULLETIN_UES_UE_SAES_SAE_FIELDS,
|
||||||
|
BULLETIN_UES_UE_ECTS_FIELDS,
|
||||||
|
BULLETIN_SEMESTRE_FIELDS,
|
||||||
|
BULLETIN_SEMESTRE_ABSENCES_FIELDS,
|
||||||
|
BULLETIN_SEMESTRE_ECTS_FIELDS,
|
||||||
|
BULLETIN_SEMESTRE_NOTES_FIELDS,
|
||||||
|
BULLETIN_SEMESTRE_RANG_FIELDS,
|
||||||
)
|
)
|
||||||
from tests.api.tools_test_api import FSEM_FIELDS, UE_FIELDS, MODULE_FIELDS
|
|
||||||
|
|
||||||
# Etudiant pour les tests
|
# Etudiant pour les tests
|
||||||
ETUDID = 1
|
ETUDID = 1
|
||||||
@ -143,26 +165,318 @@ def test_formsemestre_apo(api_headers):
|
|||||||
assert isinstance(formsemestre["titre"], str)
|
assert isinstance(formsemestre["titre"], str)
|
||||||
|
|
||||||
### ERROR ###
|
### ERROR ###
|
||||||
etape_apo_inexistante = "aoefiaozidaoẑidjnoaiznjd"
|
# etape_apo_inexistante = "aoefiaozidaoẑidjnoaiznjd"
|
||||||
r_error = requests.get(
|
# r_error = requests.get(
|
||||||
f"{API_URL}/formsemestre/apo/{etape_apo_inexistante}",
|
# f"{API_URL}/formsemestre/apo/{etape_apo_inexistante}",
|
||||||
headers=api_headers,
|
# headers=api_headers,
|
||||||
verify=CHECK_CERTIFICATE,
|
# verify=CHECK_CERTIFICATE,
|
||||||
)
|
# )
|
||||||
assert r_error.status_code == 404
|
# assert r_error.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_bulletins(api_headers):
|
def test_bulletins(api_headers):
|
||||||
"""
|
"""
|
||||||
Route: /formsemestre/<int:formsemestre_id>/bulletins
|
Route: /formsemestre/<int:formsemestre_id>/bulletins
|
||||||
"""
|
"""
|
||||||
|
formsemestre_id = 1
|
||||||
r = requests.get(
|
r = requests.get(
|
||||||
API_URL + "/formsemestre/1/bulletins",
|
f"{API_URL}/formsemestre/{formsemestre_id}/bulletins",
|
||||||
headers=api_headers,
|
headers=api_headers,
|
||||||
verify=CHECK_CERTIFICATE,
|
verify=CHECK_CERTIFICATE,
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
bulletins = r.json()
|
||||||
|
|
||||||
|
assert isinstance(bulletins, list)
|
||||||
|
for bul in bulletins:
|
||||||
|
assert verify_fields(bul, FORMSEMESTRE_BULLETINS_FIELDS) is True
|
||||||
|
assert isinstance(bul["version"], str)
|
||||||
|
assert isinstance(bul["type"], str)
|
||||||
|
assert isinstance(bul["date"], str)
|
||||||
|
assert isinstance(bul["publie"], bool)
|
||||||
|
assert isinstance(bul["etudiant"], dict)
|
||||||
|
assert isinstance(bul["formation"], dict)
|
||||||
|
assert isinstance(bul["formsemestre_id"], int)
|
||||||
|
assert isinstance(bul["etat_inscription"], str)
|
||||||
|
assert isinstance(bul["options"], dict)
|
||||||
|
assert isinstance(bul["ressources"], dict)
|
||||||
|
assert isinstance(bul["saes"], dict)
|
||||||
|
assert isinstance(bul["ues"], dict)
|
||||||
|
assert isinstance(bul["semestre"], dict)
|
||||||
|
|
||||||
|
formsemestre_id_bul = bul["formsemestre_id"]
|
||||||
|
assert formsemestre_id == formsemestre_id_bul
|
||||||
|
|
||||||
|
etudiant = bul["etudiant"]
|
||||||
|
assert verify_fields(etudiant, FORMSEMESTRE_BULLETINS_ETU_FIELDS) is True
|
||||||
|
assert isinstance(etudiant["civilite"], str)
|
||||||
|
assert isinstance(etudiant["code_ine"], str)
|
||||||
|
assert isinstance(etudiant["code_nip"], str)
|
||||||
|
assert isinstance(etudiant["date_naissance"], str)
|
||||||
|
assert isinstance(etudiant["dept_id"], int)
|
||||||
|
assert isinstance(etudiant["dept_acronym"], str)
|
||||||
|
assert isinstance(etudiant["email"], str)
|
||||||
|
assert isinstance(etudiant["emailperso"], str)
|
||||||
|
assert isinstance(etudiant["etudid"], int)
|
||||||
|
assert isinstance(etudiant["nom"], str)
|
||||||
|
assert isinstance(etudiant["prenom"], str)
|
||||||
|
assert isinstance(etudiant["nomprenom"], str)
|
||||||
|
assert isinstance(etudiant["lieu_naissance"], str)
|
||||||
|
assert isinstance(etudiant["dept_naissance"], str)
|
||||||
|
assert isinstance(etudiant["nationalite"], str)
|
||||||
|
assert isinstance(etudiant["boursier"], str)
|
||||||
|
assert isinstance(etudiant["fiche_url"], str)
|
||||||
|
assert isinstance(etudiant["photo_url"], str)
|
||||||
|
assert isinstance(etudiant["id"], int)
|
||||||
|
assert isinstance(etudiant["codepostaldomicile"], str)
|
||||||
|
assert isinstance(etudiant["paysdomicile"], str)
|
||||||
|
assert isinstance(etudiant["telephonemobile"], str)
|
||||||
|
assert isinstance(etudiant["typeadresse"], str)
|
||||||
|
assert isinstance(etudiant["domicile"], str)
|
||||||
|
assert isinstance(etudiant["villedomicile"], str)
|
||||||
|
assert isinstance(etudiant["telephone"], str)
|
||||||
|
assert isinstance(etudiant["fax"], str)
|
||||||
|
assert isinstance(etudiant["description"], str)
|
||||||
|
|
||||||
|
formation = bul["formation"]
|
||||||
|
assert verify_fields(formation, FORMSEMESTRE_BULLETINS_FORMATION_FIELDS) is True
|
||||||
|
assert isinstance(formation["id"], int)
|
||||||
|
assert isinstance(formation["acronyme"], str)
|
||||||
|
assert isinstance(formation["titre_officiel"], str)
|
||||||
|
assert isinstance(formation["titre"], str)
|
||||||
|
|
||||||
|
options = bul["options"]
|
||||||
|
assert verify_fields(options, FORMSEMESTRE_BULLETINS_OPT_FIELDS) is True
|
||||||
|
assert isinstance(options["show_abs"], bool)
|
||||||
|
assert isinstance(options["show_abs_modules"], bool)
|
||||||
|
assert isinstance(options["show_ects"], bool)
|
||||||
|
assert isinstance(options["show_codemodules"], bool)
|
||||||
|
assert isinstance(options["show_matieres"], bool)
|
||||||
|
assert isinstance(options["show_rangs"], bool)
|
||||||
|
assert isinstance(options["show_ue_rangs"], bool)
|
||||||
|
assert isinstance(options["show_mod_rangs"], bool)
|
||||||
|
assert isinstance(options["show_moypromo"], bool)
|
||||||
|
assert isinstance(options["show_minmax"], bool)
|
||||||
|
assert isinstance(options["show_minmax_mod"], bool)
|
||||||
|
assert isinstance(options["show_minmax_eval"], bool)
|
||||||
|
assert isinstance(options["show_coef"], bool)
|
||||||
|
assert isinstance(options["show_ue_cap_details"], bool)
|
||||||
|
assert isinstance(options["show_ue_cap_current"], bool)
|
||||||
|
assert isinstance(options["show_temporary"], bool)
|
||||||
|
assert isinstance(options["temporary_txt"], str)
|
||||||
|
assert isinstance(options["show_uevalid"], bool)
|
||||||
|
assert isinstance(options["show_date_inscr"], bool)
|
||||||
|
|
||||||
|
bulletin_ressources = bul["ressources"]
|
||||||
|
assert isinstance(bulletin_ressources, dict)
|
||||||
|
|
||||||
|
for ressource in bulletin_ressources.values():
|
||||||
|
assert (
|
||||||
|
verify_fields(
|
||||||
|
ressource, BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_FIELDS
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(ressource, dict)
|
||||||
|
assert isinstance(ressource["evaluations"], list)
|
||||||
|
for evaluation in ressource["evaluations"]:
|
||||||
|
assert (
|
||||||
|
verify_fields(
|
||||||
|
evaluation,
|
||||||
|
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_FIELDS,
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(evaluation["id"], int)
|
||||||
|
assert evaluation["description"] is None or isinstance(
|
||||||
|
evaluation["description"], str
|
||||||
|
)
|
||||||
|
assert evaluation["date"] is None or isinstance(evaluation["date"], str)
|
||||||
|
assert isinstance(evaluation["heure_debut"], str)
|
||||||
|
assert isinstance(evaluation["heure_fin"], str)
|
||||||
|
assert isinstance(evaluation["coef"], str)
|
||||||
|
assert isinstance(evaluation["poids"], dict)
|
||||||
|
assert isinstance(evaluation["note"], dict)
|
||||||
|
assert isinstance(evaluation["url"], str)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
verify_fields(
|
||||||
|
evaluation["poids"],
|
||||||
|
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_POIDS_FIELDS,
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(evaluation["poids"]["RT1.1"], float)
|
||||||
|
assert isinstance(evaluation["poids"]["RT2.1"], float)
|
||||||
|
assert isinstance(evaluation["poids"]["RT3.1"], float)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
verify_fields(
|
||||||
|
evaluation["note"],
|
||||||
|
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_NOTE_FIELDS,
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(evaluation["note"]["value"], str)
|
||||||
|
assert isinstance(evaluation["note"]["min"], str)
|
||||||
|
assert isinstance(evaluation["note"]["max"], str)
|
||||||
|
assert isinstance(evaluation["note"]["moy"], str)
|
||||||
|
|
||||||
|
bulletin_saes = bul["saes"]
|
||||||
|
assert isinstance(bulletin_saes, dict)
|
||||||
|
|
||||||
|
for sae in bulletin_saes.values():
|
||||||
|
assert (
|
||||||
|
verify_fields(sae, BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_FIELDS)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(sae, dict)
|
||||||
|
assert isinstance(sae["evaluations"], list)
|
||||||
|
for evaluation in sae["evaluations"]:
|
||||||
|
assert (
|
||||||
|
verify_fields(
|
||||||
|
evaluation,
|
||||||
|
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_FIELDS,
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(evaluation["id"], int)
|
||||||
|
assert evaluation["description"] is None or isinstance(
|
||||||
|
evaluation["description"], str
|
||||||
|
)
|
||||||
|
assert evaluation["date"] is None or isinstance(evaluation["date"], str)
|
||||||
|
assert isinstance(evaluation["heure_debut"], str)
|
||||||
|
assert isinstance(evaluation["heure_fin"], str)
|
||||||
|
assert isinstance(evaluation["coef"], str)
|
||||||
|
assert isinstance(evaluation["poids"], dict)
|
||||||
|
assert isinstance(evaluation["note"], dict)
|
||||||
|
assert isinstance(evaluation["url"], str)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
verify_fields(
|
||||||
|
evaluation["poids"],
|
||||||
|
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_POIDS_FIELDS,
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(evaluation["poids"]["RT1.1"], float)
|
||||||
|
assert isinstance(evaluation["poids"]["RT2.1"], float)
|
||||||
|
assert isinstance(evaluation["poids"]["RT3.1"], float)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
verify_fields(
|
||||||
|
evaluation["note"],
|
||||||
|
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_NOTE_FIELDS,
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(evaluation["note"]["value"], str)
|
||||||
|
assert isinstance(evaluation["note"]["min"], str)
|
||||||
|
assert isinstance(evaluation["note"]["max"], str)
|
||||||
|
assert isinstance(evaluation["note"]["moy"], str)
|
||||||
|
|
||||||
|
bulletin_ues = bul["ues"]
|
||||||
|
assert isinstance(bulletin_ues, dict)
|
||||||
|
|
||||||
|
for (key_ue, value_ue) in bulletin_ues.items():
|
||||||
|
assert verify_fields(value_ue, BULLETIN_UES_UE_FIELDS) is True
|
||||||
|
assert isinstance(value_ue["id"], int)
|
||||||
|
assert isinstance(value_ue["titre"], str)
|
||||||
|
assert isinstance(value_ue["numero"], int)
|
||||||
|
assert isinstance(value_ue["type"], int)
|
||||||
|
assert isinstance(value_ue["color"], str)
|
||||||
|
assert value_ue["competence"] is None or isinstance(
|
||||||
|
value_ue["competence"], str
|
||||||
|
)
|
||||||
|
assert isinstance(value_ue["moyenne"], dict)
|
||||||
|
assert isinstance(value_ue["bonus"], str)
|
||||||
|
assert isinstance(value_ue["malus"], str)
|
||||||
|
assert value_ue["capitalise"] is None or isinstance(
|
||||||
|
value_ue["capitalise"], str
|
||||||
|
)
|
||||||
|
assert isinstance(value_ue["ressources"], dict)
|
||||||
|
assert isinstance(value_ue["saes"], dict)
|
||||||
|
assert isinstance(value_ue["ECTS"], dict)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
verify_fields(value_ue["moyenne"], BULLETIN_UES_UE_MOYENNE_FIELDS)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(value_ue["moyenne"]["value"], str)
|
||||||
|
assert isinstance(value_ue["moyenne"]["min"], str)
|
||||||
|
assert isinstance(value_ue["moyenne"]["max"], str)
|
||||||
|
assert isinstance(value_ue["moyenne"]["moy"], str)
|
||||||
|
assert isinstance(value_ue["moyenne"]["rang"], str)
|
||||||
|
assert isinstance(value_ue["moyenne"]["total"], int)
|
||||||
|
|
||||||
|
for ressource in value_ue["ressources"].values():
|
||||||
|
assert (
|
||||||
|
verify_fields(
|
||||||
|
ressource, BULLETIN_UES_UE_RESSOURCES_RESSOURCE_FIELDS
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(ressource["id"], int)
|
||||||
|
assert isinstance(ressource["coef"], float)
|
||||||
|
assert isinstance(ressource["moyenne"], str)
|
||||||
|
|
||||||
|
for sae in value_ue["saes"].values():
|
||||||
|
assert verify_fields(sae, BULLETIN_UES_UE_SAES_SAE_FIELDS) is True
|
||||||
|
assert isinstance(sae["id"], int)
|
||||||
|
assert isinstance(sae["coef"], float)
|
||||||
|
assert isinstance(sae["moyenne"], str)
|
||||||
|
|
||||||
|
assert verify_fields(value_ue["ECTS"], BULLETIN_UES_UE_ECTS_FIELDS) is True
|
||||||
|
assert isinstance(value_ue["ECTS"]["acquis"], float)
|
||||||
|
assert isinstance(value_ue["ECTS"]["total"], float)
|
||||||
|
|
||||||
|
bulletin_semestre = bul["semestre"]
|
||||||
|
assert verify_fields(bulletin_semestre, BULLETIN_SEMESTRE_FIELDS) is True
|
||||||
|
assert isinstance(bulletin_semestre["etapes"], list)
|
||||||
|
assert isinstance(bulletin_semestre["date_debut"], str)
|
||||||
|
assert isinstance(bulletin_semestre["date_fin"], str)
|
||||||
|
assert isinstance(bulletin_semestre["annee_universitaire"], str)
|
||||||
|
assert isinstance(bulletin_semestre["numero"], int)
|
||||||
|
assert isinstance(bulletin_semestre["inscription"], str)
|
||||||
|
assert isinstance(bulletin_semestre["groupes"], list)
|
||||||
|
assert isinstance(bulletin_semestre["absences"], dict)
|
||||||
|
assert isinstance(bulletin_semestre["ECTS"], dict)
|
||||||
|
assert isinstance(bulletin_semestre["notes"], dict)
|
||||||
|
assert isinstance(bulletin_semestre["rang"], dict)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
verify_fields(
|
||||||
|
bulletin_semestre["absences"], BULLETIN_SEMESTRE_ABSENCES_FIELDS
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(bulletin_semestre["absences"]["injustifie"], int)
|
||||||
|
assert isinstance(bulletin_semestre["absences"]["total"], int)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
verify_fields(bulletin_semestre["ECTS"], BULLETIN_SEMESTRE_ECTS_FIELDS)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(bulletin_semestre["ECTS"]["acquis"], int)
|
||||||
|
assert isinstance(bulletin_semestre["ECTS"]["total"], float)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
verify_fields(bulletin_semestre["notes"], BULLETIN_SEMESTRE_NOTES_FIELDS)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(bulletin_semestre["notes"]["value"], str)
|
||||||
|
assert isinstance(bulletin_semestre["notes"]["min"], str)
|
||||||
|
assert isinstance(bulletin_semestre["notes"]["max"], str)
|
||||||
|
assert isinstance(bulletin_semestre["notes"]["moy"], str)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
verify_fields(bulletin_semestre["rang"], BULLETIN_SEMESTRE_RANG_FIELDS)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
assert isinstance(bulletin_semestre["rang"]["value"], str)
|
||||||
|
assert isinstance(bulletin_semestre["rang"]["total"], int)
|
||||||
|
|
||||||
|
|
||||||
# # jury
|
# # jury
|
||||||
# def test_jury():
|
# def test_jury():
|
||||||
|
@ -641,3 +641,79 @@ PARTITIONS_GROUPS_ETU_FIELDS = {
|
|||||||
"ne",
|
"ne",
|
||||||
"email_default",
|
"email_default",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FORMSEMESTRE_BULLETINS_FIELDS = {
|
||||||
|
"version",
|
||||||
|
"type",
|
||||||
|
"date",
|
||||||
|
"publie",
|
||||||
|
"etudiant",
|
||||||
|
"formation",
|
||||||
|
"formsemestre_id",
|
||||||
|
"etat_inscription",
|
||||||
|
"options",
|
||||||
|
"ressources",
|
||||||
|
"saes",
|
||||||
|
"ues",
|
||||||
|
"semestre",
|
||||||
|
}
|
||||||
|
|
||||||
|
FORMSEMESTRE_BULLETINS_ETU_FIELDS = {
|
||||||
|
"civilite",
|
||||||
|
"code_ine",
|
||||||
|
"code_nip",
|
||||||
|
"date_naissance",
|
||||||
|
"dept_id",
|
||||||
|
"dept_acronym",
|
||||||
|
"email",
|
||||||
|
"emailperso",
|
||||||
|
"etudid",
|
||||||
|
"nom",
|
||||||
|
"prenom",
|
||||||
|
"nomprenom",
|
||||||
|
"lieu_naissance",
|
||||||
|
"dept_naissance",
|
||||||
|
"nationalite",
|
||||||
|
"boursier",
|
||||||
|
"fiche_url",
|
||||||
|
"photo_url",
|
||||||
|
"id",
|
||||||
|
"codepostaldomicile",
|
||||||
|
"paysdomicile",
|
||||||
|
"telephonemobile",
|
||||||
|
"typeadresse",
|
||||||
|
"domicile",
|
||||||
|
"villedomicile",
|
||||||
|
"telephone",
|
||||||
|
"fax",
|
||||||
|
"description",
|
||||||
|
}
|
||||||
|
|
||||||
|
FORMSEMESTRE_BULLETINS_FORMATION_FIELDS = {
|
||||||
|
"id",
|
||||||
|
"acronyme",
|
||||||
|
"titre_officiel",
|
||||||
|
"titre",
|
||||||
|
}
|
||||||
|
|
||||||
|
FORMSEMESTRE_BULLETINS_OPT_FIELDS = {
|
||||||
|
"show_abs",
|
||||||
|
"show_abs_modules",
|
||||||
|
"show_ects",
|
||||||
|
"show_codemodules",
|
||||||
|
"show_matieres",
|
||||||
|
"show_rangs",
|
||||||
|
"show_ue_rangs",
|
||||||
|
"show_mod_rangs",
|
||||||
|
"show_moypromo",
|
||||||
|
"show_minmax",
|
||||||
|
"show_minmax_mod",
|
||||||
|
"show_minmax_eval",
|
||||||
|
"show_coef",
|
||||||
|
"show_ue_cap_details",
|
||||||
|
"show_ue_cap_current",
|
||||||
|
"show_temporary",
|
||||||
|
"temporary_txt",
|
||||||
|
"show_uevalid",
|
||||||
|
"show_date_inscr",
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user