forked from ScoDoc/ScoDoc
Jury BUT: amélioration front et back. Voir #547. Tests YAML: refonte circuit jury. Cas lyon43. Tests ok.
This commit is contained in:
parent
5eed392400
commit
c833974569
@ -584,9 +584,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
|
|
||||||
def compute_decisions_niveaux(self) -> dict[int, "DecisionsProposeesRCUE"]:
|
def compute_decisions_niveaux(self) -> dict[int, "DecisionsProposeesRCUE"]:
|
||||||
"""Pour chaque niveau de compétence de cette année, construit
|
"""Pour chaque niveau de compétence de cette année, construit
|
||||||
le DecisionsProposeesRCUE,
|
le DecisionsProposeesRCUE, ou None s'il n'y en a pas
|
||||||
ou None s'il n'y en a pas
|
|
||||||
(ne devrait pas arriver car compute_rcues_annee vérifie déjà cela).
|
(ne devrait pas arriver car compute_rcues_annee vérifie déjà cela).
|
||||||
|
|
||||||
|
Appelé à la construction du deca, donc avant décisions manuelles.
|
||||||
Return: { niveau_id : DecisionsProposeesRCUE }
|
Return: { niveau_id : DecisionsProposeesRCUE }
|
||||||
"""
|
"""
|
||||||
# Retrouve le RCUE associé à chaque niveau
|
# Retrouve le RCUE associé à chaque niveau
|
||||||
@ -633,6 +634,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
|
|
||||||
def record_form(self, form: dict):
|
def record_form(self, form: dict):
|
||||||
"""Enregistre les codes de jury en base
|
"""Enregistre les codes de jury en base
|
||||||
|
à partir d'un dict représentant le formulaire jury BUT:
|
||||||
form dict:
|
form dict:
|
||||||
- 'code_ue_1896' : 'AJ' code pour l'UE id 1896
|
- 'code_ue_1896' : 'AJ' code pour l'UE id 1896
|
||||||
- 'code_rcue_6" : 'ADM' code pour le RCUE du niveau 6
|
- 'code_rcue_6" : 'ADM' code pour le RCUE du niveau 6
|
||||||
@ -642,31 +644,41 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
et qu'il n'y en a pas déjà, enregistre ceux par défaut.
|
et qu'il n'y en a pas déjà, enregistre ceux par défaut.
|
||||||
"""
|
"""
|
||||||
log("jury_but.DecisionsProposeesAnnee.record_form")
|
log("jury_but.DecisionsProposeesAnnee.record_form")
|
||||||
with sco_cache.DeferredSemCacheManager():
|
code_annee = None
|
||||||
for key in form:
|
codes_rcues = [] # [ (dec_rcue, code), ... ]
|
||||||
code = form[key]
|
codes_ues = [] # [ (dec_ue, code), ... ]
|
||||||
# Codes d'UE
|
for key in form:
|
||||||
m = re.match(r"^code_ue_(\d+)$", key)
|
code = form[key]
|
||||||
|
# Codes d'UE
|
||||||
|
m = re.match(r"^code_ue_(\d+)$", key)
|
||||||
|
if m:
|
||||||
|
ue_id = int(m.group(1))
|
||||||
|
dec_ue = self.decisions_ues.get(ue_id)
|
||||||
|
if not dec_ue:
|
||||||
|
raise ScoValueError(f"UE invalide ue_id={ue_id}")
|
||||||
|
codes_ues.append((dec_ue, code))
|
||||||
|
else:
|
||||||
|
# Codes de RCUE
|
||||||
|
m = re.match(r"^code_rcue_(\d+)$", key)
|
||||||
if m:
|
if m:
|
||||||
ue_id = int(m.group(1))
|
niveau_id = int(m.group(1))
|
||||||
dec_ue = self.decisions_ues.get(ue_id)
|
dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id)
|
||||||
if not dec_ue:
|
if not dec_rcue:
|
||||||
raise ScoValueError(f"UE invalide ue_id={ue_id}")
|
raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}")
|
||||||
dec_ue.record(code)
|
codes_rcues.append((dec_rcue, code))
|
||||||
else:
|
elif key == "code_annee":
|
||||||
# Codes de RCUE
|
# Code annuel
|
||||||
m = re.match(r"^code_rcue_(\d+)$", key)
|
code_annee = code
|
||||||
if m:
|
|
||||||
niveau_id = int(m.group(1))
|
with sco_cache.DeferredSemCacheManager():
|
||||||
dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id)
|
# Enregistre les codes, dans l'ordre UE, RCUE, Année
|
||||||
if not dec_rcue:
|
for dec_ue, code in codes_ues:
|
||||||
raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}")
|
dec_ue.record(code)
|
||||||
dec_rcue.record(code)
|
for dec_rcue, code in codes_rcues:
|
||||||
elif key == "code_annee":
|
dec_rcue.record(code)
|
||||||
# Code annuel
|
self.record(code_annee)
|
||||||
self.record(code)
|
self.record_all()
|
||||||
|
|
||||||
self.record_all()
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
def record(self, code: str, no_overwrite=False):
|
def record(self, code: str, no_overwrite=False):
|
||||||
@ -790,6 +802,15 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
msg=f"Validation année BUT{self.annee_but}: effacée",
|
msg=f"Validation année BUT{self.annee_but}: effacée",
|
||||||
)
|
)
|
||||||
db.session.delete(validation)
|
db.session.delete(validation)
|
||||||
|
# Efface éventuelle validation de semestre
|
||||||
|
# (en principe inutilisées en BUT)
|
||||||
|
# et autres UEs (en cas de changement d'architecture de formation depuis le jury ?)
|
||||||
|
#
|
||||||
|
for validation in ScolarFormSemestreValidation.query.filter_by(
|
||||||
|
etudid=self.etud.id, formsemestre_id=self.formsemestre_id
|
||||||
|
):
|
||||||
|
db.session.delete(validation)
|
||||||
|
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
self.invalidate_formsemestre_cache()
|
self.invalidate_formsemestre_cache()
|
||||||
|
|
||||||
@ -878,6 +899,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
inscription_etat: str = scu.INSCRIT,
|
inscription_etat: str = scu.INSCRIT,
|
||||||
):
|
):
|
||||||
super().__init__(etud=dec_prop_annee.etud)
|
super().__init__(etud=dec_prop_annee.etud)
|
||||||
|
self.deca = dec_prop_annee
|
||||||
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 = []
|
||||||
@ -928,7 +950,11 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
} codes={self.codes} explanation={self.explanation}"""
|
} codes={self.codes} explanation={self.explanation}"""
|
||||||
|
|
||||||
def record(self, code: str, no_overwrite=False):
|
def record(self, code: str, no_overwrite=False):
|
||||||
"""Enregistre le code"""
|
"""Enregistre le code RCUE.
|
||||||
|
Note:
|
||||||
|
- si le RCUE est ADJ, les UE non validées sont passées à ADJ
|
||||||
|
XXX on pourra imposer ici d'autres règles de cohérence
|
||||||
|
"""
|
||||||
if self.rcue is None:
|
if self.rcue is None:
|
||||||
return # pas de RCUE a enregistrer
|
return # pas de RCUE a enregistrer
|
||||||
if self.inscription_etat != scu.INSCRIT:
|
if self.inscription_etat != scu.INSCRIT:
|
||||||
@ -964,6 +990,15 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
msg=f"Validation {self.rcue}: {code}",
|
msg=f"Validation {self.rcue}: {code}",
|
||||||
)
|
)
|
||||||
db.session.add(self.validation)
|
db.session.add(self.validation)
|
||||||
|
# Modifie au besoin les codes d'UE
|
||||||
|
if code == "ADJ":
|
||||||
|
deca = self.deca
|
||||||
|
for ue_id in (self.rcue.ue_1.id, self.rcue.ue_2.id):
|
||||||
|
dec_ue = deca.decisions_ues.get(ue_id)
|
||||||
|
if dec_ue and dec_ue.code_valide not in CODES_UE_VALIDES:
|
||||||
|
log(f"rcue.record: force ADJ sur {dec_ue}")
|
||||||
|
dec_ue.record("ADJ")
|
||||||
|
|
||||||
if self.rcue.formsemestre_1 is not None:
|
if self.rcue.formsemestre_1 is not None:
|
||||||
sco_cache.invalidate_formsemestre(
|
sco_cache.invalidate_formsemestre(
|
||||||
formsemestre_id=self.rcue.formsemestre_1.id
|
formsemestre_id=self.rcue.formsemestre_1.id
|
||||||
@ -972,6 +1007,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
sco_cache.invalidate_formsemestre(
|
sco_cache.invalidate_formsemestre(
|
||||||
formsemestre_id=self.rcue.formsemestre_2.id
|
formsemestre_id=self.rcue.formsemestre_2.id
|
||||||
)
|
)
|
||||||
|
self.code_valide = code # mise à jour état
|
||||||
self.recorded = True
|
self.recorded = True
|
||||||
|
|
||||||
def erase(self):
|
def erase(self):
|
||||||
@ -1032,14 +1068,14 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
):
|
):
|
||||||
# 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(
|
validation = ScolarFormSemestreValidation.query.filter_by(
|
||||||
etudid=etud.id, formsemestre_id=formsemestre.id, ue_id=ue.id
|
etudid=etud.id, formsemestre_id=formsemestre.id, ue_id=ue.id
|
||||||
).first()
|
).first()
|
||||||
super().__init__(
|
super().__init__(
|
||||||
etud=etud,
|
etud=etud,
|
||||||
code_valide=self.validation.code if self.validation is not None else None,
|
code_valide=validation.code if validation is not None else None,
|
||||||
)
|
)
|
||||||
# log(f"built {self}")
|
self.validation = validation
|
||||||
self.formsemestre = formsemestre
|
self.formsemestre = formsemestre
|
||||||
self.ue: UniteEns = ue
|
self.ue: UniteEns = ue
|
||||||
self.rcue: RegroupementCoherentUE = None
|
self.rcue: RegroupementCoherentUE = None
|
||||||
@ -1082,7 +1118,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
|
|
||||||
def set_rcue(self, rcue: RegroupementCoherentUE):
|
def set_rcue(self, rcue: RegroupementCoherentUE):
|
||||||
"""Rattache cette UE à un RCUE. Cela peut modifier les codes
|
"""Rattache cette UE à un RCUE. Cela peut modifier les codes
|
||||||
proposés (si compensation)"""
|
proposés par compute_codes() (si compensation)"""
|
||||||
self.rcue = rcue
|
self.rcue = rcue
|
||||||
|
|
||||||
def compute_codes(self):
|
def compute_codes(self):
|
||||||
@ -1138,6 +1174,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
log(f"DecisionsProposeesUE: recording {self.validation}")
|
log(f"DecisionsProposeesUE: recording {self.validation}")
|
||||||
|
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id)
|
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id)
|
||||||
|
self.code_valide = code # mise à jour
|
||||||
self.recorded = True
|
self.recorded = True
|
||||||
|
|
||||||
def erase(self):
|
def erase(self):
|
||||||
|
@ -30,6 +30,7 @@ from app.models import (
|
|||||||
Identite,
|
Identite,
|
||||||
UniteEns,
|
UniteEns,
|
||||||
ScolarAutorisationInscription,
|
ScolarAutorisationInscription,
|
||||||
|
ScolarFormSemestreValidation,
|
||||||
)
|
)
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
@ -50,7 +51,7 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
|||||||
_gen_but_select("code_annee", deca.codes, deca.code_valide,
|
_gen_but_select("code_annee", deca.codes, deca.code_valide,
|
||||||
disabled=True, klass="manual")
|
disabled=True, klass="manual")
|
||||||
}
|
}
|
||||||
<span>({'non ' if deca.code_valide is None else ''}enregistrée)</span>
|
<span>({deca.code_valide or 'non'} enregistrée)</span>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@ -108,8 +109,8 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
|||||||
if ue.niveau_competence and ue.niveau_competence.id == niveau.id
|
if ue.niveau_competence and ue.niveau_competence.id == niveau.id
|
||||||
]
|
]
|
||||||
ue_pair = ues[0] if ues else None
|
ue_pair = ues[0] if ues else None
|
||||||
# Les UEs à afficher, toujours en readonly
|
# Les UEs à afficher,
|
||||||
# sur le formsemestre de l'année précédente du redoublant
|
# qui seront toujours en readonly sur le formsemestre de l'année précédente du redoublant
|
||||||
ues_ro = [
|
ues_ro = [
|
||||||
(
|
(
|
||||||
ue_impair,
|
ue_impair,
|
||||||
@ -132,6 +133,7 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
|||||||
deca.decisions_ues[ue.id],
|
deca.decisions_ues[ue.id],
|
||||||
disabled=read_only or ue_read_only,
|
disabled=read_only or ue_read_only,
|
||||||
annee_prec=ue_read_only,
|
annee_prec=ue_read_only,
|
||||||
|
niveau_id=ue.niveau_competence.id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -150,6 +152,7 @@ def _gen_but_select(
|
|||||||
code_valide: str,
|
code_valide: str,
|
||||||
disabled: bool = False,
|
disabled: bool = False,
|
||||||
klass: str = "",
|
klass: str = "",
|
||||||
|
data: dict = {},
|
||||||
) -> str:
|
) -> str:
|
||||||
"Le menu html select avec les codes"
|
"Le menu html select avec les codes"
|
||||||
# if disabled: # mauvaise idée car le disabled est traité en JS
|
# if disabled: # mauvaise idée car le disabled est traité en JS
|
||||||
@ -165,8 +168,11 @@ def _gen_but_select(
|
|||||||
)
|
)
|
||||||
return f"""<select required name="{name}"
|
return f"""<select required name="{name}"
|
||||||
class="but_code {klass}"
|
class="but_code {klass}"
|
||||||
|
data-orig_code="{code_valide or (codes[0] if codes else '')}"
|
||||||
|
data-orig_recorded="{code_valide or ''}"
|
||||||
onchange="change_menu_code(this);"
|
onchange="change_menu_code(this);"
|
||||||
{"disabled" if disabled else ""}
|
{"disabled" if disabled else ""}
|
||||||
|
{" ".join( f'data-{k}="{v}"' for (k,v) in data.items() )}
|
||||||
>{options_htm}</select>
|
>{options_htm}</select>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -176,6 +182,7 @@ def _gen_but_niveau_ue(
|
|||||||
dec_ue: DecisionsProposeesUE,
|
dec_ue: DecisionsProposeesUE,
|
||||||
disabled: bool = False,
|
disabled: bool = False,
|
||||||
annee_prec: bool = False,
|
annee_prec: bool = False,
|
||||||
|
niveau_id: int = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
|
if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
|
||||||
moy_ue_str = f"""<span class="ue_cap">{
|
moy_ue_str = f"""<span class="ue_cap">{
|
||||||
@ -196,7 +203,14 @@ def _gen_but_niveau_ue(
|
|||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>"""
|
moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>"""
|
||||||
scoplement = ""
|
if dec_ue.code_valide:
|
||||||
|
scoplement = f"""<div class="scoplement">
|
||||||
|
Code {dec_ue.code_valide} enregistré le {dec_ue.validation.event_date.strftime("%d/%m/%Y")}
|
||||||
|
à {dec_ue.validation.event_date.strftime("%Hh%M")}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
scoplement = ""
|
||||||
|
|
||||||
return f"""<div class="but_niveau_ue {
|
return f"""<div class="but_niveau_ue {
|
||||||
'recorded' if dec_ue.code_valide is not None else ''}
|
'recorded' if dec_ue.code_valide is not None else ''}
|
||||||
@ -210,7 +224,9 @@ def _gen_but_niveau_ue(
|
|||||||
<div class="but_code">{
|
<div class="but_code">{
|
||||||
_gen_but_select("code_ue_"+str(ue.id),
|
_gen_but_select("code_ue_"+str(ue.id),
|
||||||
dec_ue.codes,
|
dec_ue.codes,
|
||||||
dec_ue.code_valide, disabled=disabled
|
dec_ue.code_valide,
|
||||||
|
disabled=disabled,
|
||||||
|
klass=f"code_ue ue_rcue_{niveau_id}" if not disabled else ""
|
||||||
)
|
)
|
||||||
}</div>
|
}</div>
|
||||||
|
|
||||||
@ -250,12 +266,13 @@ def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
|
|||||||
{scoplement}
|
{scoplement}
|
||||||
</div>
|
</div>
|
||||||
<div class="but_code">
|
<div class="but_code">
|
||||||
<div>{_gen_but_select("code_rcue_"+str(niveau.id),
|
{_gen_but_select("code_rcue_"+str(niveau.id),
|
||||||
dec_rcue.codes,
|
dec_rcue.codes,
|
||||||
dec_rcue.code_valide,
|
dec_rcue.code_valide,
|
||||||
disabled=True, klass="manual"
|
disabled=True,
|
||||||
|
klass="manual code_rcue",
|
||||||
|
data = { "niveau_id" : str(niveau.id)}
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
@ -274,17 +291,15 @@ def jury_but_semestriel(
|
|||||||
semestre_terminal = (
|
semestre_terminal = (
|
||||||
formsemestre.semestre_id >= formsemestre.formation.get_parcours().NB_SEM
|
formsemestre.semestre_id >= formsemestre.formation.get_parcours().NB_SEM
|
||||||
)
|
)
|
||||||
|
autorisations_passage = ScolarAutorisationInscription.query.filter_by(
|
||||||
|
etudid=etud.id,
|
||||||
|
origin_formsemestre_id=formsemestre.id,
|
||||||
|
).all()
|
||||||
# Par défaut: autorisé à passer dans le semestre suivant si sem. impair,
|
# Par défaut: autorisé à passer dans le semestre suivant si sem. impair,
|
||||||
# ou si décision déjà enregistrée:
|
# ou si décision déjà enregistrée:
|
||||||
est_autorise_a_passer = (formsemestre.semestre_id % 2) or (
|
est_autorise_a_passer = (formsemestre.semestre_id % 2) or (
|
||||||
formsemestre.semestre_id + 1
|
formsemestre.semestre_id + 1
|
||||||
) in (
|
) in (a.semestre_id for a in autorisations_passage)
|
||||||
a.semestre_id
|
|
||||||
for a in ScolarAutorisationInscription.query.filter_by(
|
|
||||||
etudid=etud.id,
|
|
||||||
origin_formsemestre_id=formsemestre.id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
decisions_ues = {
|
decisions_ues = {
|
||||||
ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat)
|
ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat)
|
||||||
for ue in ues
|
for ue in ues
|
||||||
@ -308,7 +323,9 @@ def jury_but_semestriel(
|
|||||||
flash("codes enregistrés")
|
flash("codes enregistrés")
|
||||||
if not semestre_terminal:
|
if not semestre_terminal:
|
||||||
if request.form.get("autorisation_passage"):
|
if request.form.get("autorisation_passage"):
|
||||||
if not est_autorise_a_passer:
|
if not formsemestre.semestre_id + 1 in (
|
||||||
|
a.semestre_id for a in autorisations_passage
|
||||||
|
):
|
||||||
ScolarAutorisationInscription.autorise_etud(
|
ScolarAutorisationInscription.autorise_etud(
|
||||||
etud.id,
|
etud.id,
|
||||||
formsemestre.formation.formation_code,
|
formsemestre.formation.formation_code,
|
||||||
@ -368,21 +385,31 @@ def jury_but_semestriel(
|
|||||||
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3>Jury sur un semestre BUT isolé</h3>
|
<h3>Jury sur un semestre BUT isolé (ne concerne que les UEs)</h3>
|
||||||
{warning}
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" id="jury_but">
|
<form method="post" id="jury_but">
|
||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
if (not read_only) and any([dec.code_valide for dec in decisions_ues.values()]):
|
|
||||||
erase_span = f"""<a href="{
|
erase_span = ""
|
||||||
url_for("notes.formsemestre_jury_but_erase",
|
if not read_only:
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id,
|
# Requête toutes les validations (pas seulement celles du deca courant),
|
||||||
etudid=etud.id, only_one_sem=1)
|
# au cas où: changement d'architecture, saisie en mode classique, ...
|
||||||
}" class="stdlink">effacer les décisions enregistrées</a>"""
|
validations = ScolarFormSemestreValidation.query.filter_by(
|
||||||
else:
|
etudid=etud.id, formsemestre_id=formsemestre.id
|
||||||
erase_span = "Cet étudiant n'a aucune décision enregistrée pour ce semestre."
|
).all()
|
||||||
|
if validations:
|
||||||
|
erase_span = f"""<a href="{
|
||||||
|
url_for("notes.formsemestre_jury_but_erase",
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id,
|
||||||
|
etudid=etud.id, only_one_sem=1)
|
||||||
|
}" class="stdlink">effacer les décisions enregistrées</a>"""
|
||||||
|
else:
|
||||||
|
erase_span = (
|
||||||
|
"Cet étudiant n'a aucune décision enregistrée pour ce semestre."
|
||||||
|
)
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
@ -436,6 +463,9 @@ def jury_but_semestriel(
|
|||||||
<input type="checkbox" name="autorisation_passage" value="1" {
|
<input type="checkbox" name="autorisation_passage" value="1" {
|
||||||
"checked" if est_autorise_a_passer else ""}>
|
"checked" if est_autorise_a_passer else ""}>
|
||||||
<em>autoriser à passer dans le semestre S{formsemestre.semestre_id+1}</em>
|
<em>autoriser à passer dans le semestre S{formsemestre.semestre_id+1}</em>
|
||||||
|
{("(autorisations enregistrées: " + ' '.join(
|
||||||
|
'S' + str(a.semestre_id or '') for a in autorisations_passage) + ")"
|
||||||
|
) if autorisations_passage else ""}
|
||||||
</input>
|
</input>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
@ -68,7 +68,8 @@ class ApcValidationRCUE(db.Model):
|
|||||||
"description en HTML"
|
"description en HTML"
|
||||||
return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}:
|
return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}:
|
||||||
<b>{self.code}</b>
|
<b>{self.code}</b>
|
||||||
<em>enregistrée le {self.date.strftime("%d/%m/%Y")}</em>"""
|
<em>enregistrée le {self.date.strftime("%d/%m/%Y")}
|
||||||
|
à {self.date.strftime("%Hh%M")}</em>"""
|
||||||
|
|
||||||
def niveau(self) -> ApcNiveau:
|
def niveau(self) -> ApcNiveau:
|
||||||
"""Le niveau de compétence associé à cet RCUE."""
|
"""Le niveau de compétence associé à cet RCUE."""
|
||||||
|
@ -93,6 +93,10 @@ class ScolarAutorisationInscription(db.Model):
|
|||||||
db.ForeignKey("notes_formsemestre.id"),
|
db.ForeignKey("notes_formsemestre.id"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"""{self.__class__.__name__}(id={self.id}, etudid={
|
||||||
|
self.etudid}, semestre_id={self.semestre_id})"""
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
"as a dict"
|
"as a dict"
|
||||||
d = dict(self.__dict__)
|
d = dict(self.__dict__)
|
||||||
|
@ -87,6 +87,8 @@ _formsemestreEditor = ndb.EditableTable(
|
|||||||
"resp_can_edit": bool,
|
"resp_can_edit": bool,
|
||||||
"resp_can_change_ens": bool,
|
"resp_can_change_ens": bool,
|
||||||
"ens_can_edit_eval": bool,
|
"ens_can_edit_eval": bool,
|
||||||
|
"bul_bgcolor": lambda color: color or "white",
|
||||||
|
"titre": lambda titre: titre or "sans titre",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -312,6 +312,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||||||
le titre: ils seront automatiquement ajoutés <input type="button"
|
le titre: ils seront automatiquement ajoutés <input type="button"
|
||||||
value="remettre titre par défaut" onClick="document.tf.titre.value='{
|
value="remettre titre par défaut" onClick="document.tf.titre.value='{
|
||||||
_default_sem_title(formation)}';"/>""",
|
_default_sem_title(formation)}';"/>""",
|
||||||
|
"allow_null": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -157,6 +157,8 @@ div.but_niveau_ue.annee_prec {
|
|||||||
background-color: rgb(167, 167, 0);
|
background-color: rgb(167, 167, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.but_section_annee,
|
||||||
|
div.but_niveau_rcue.modified,
|
||||||
div.but_niveau_ue.modified {
|
div.but_niveau_ue.modified {
|
||||||
background-color: rgb(255, 214, 254);
|
background-color: rgb(255, 214, 254);
|
||||||
}
|
}
|
||||||
|
@ -5,17 +5,36 @@ function enable_manual_codes(elt) {
|
|||||||
$(".jury_but select.manual").prop("disabled", !elt.checked);
|
$(".jury_but select.manual").prop("disabled", !elt.checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
// changement menu code:
|
// changement d'un menu code:
|
||||||
function change_menu_code(elt) {
|
function change_menu_code(elt) {
|
||||||
elt.parentElement.parentElement.classList.remove("recorded");
|
// Ajuste styles pour visualiser codes enregistrés/modifiés
|
||||||
// TODO: comparer avec valeur enregistrée (à mettre en data-orig ?)
|
if (elt.value != elt.dataset.orig_code) {
|
||||||
// et colorer en fonction
|
elt.parentElement.parentElement.classList.add("modified");
|
||||||
elt.parentElement.parentElement.classList.add("modified");
|
} else {
|
||||||
|
elt.parentElement.parentElement.classList.remove("modified");
|
||||||
|
}
|
||||||
|
if (elt.value == elt.dataset.orig_recorded) {
|
||||||
|
elt.parentElement.parentElement.classList.add("recorded");
|
||||||
|
} else {
|
||||||
|
elt.parentElement.parentElement.classList.remove("recorded");
|
||||||
|
}
|
||||||
|
// Si RCUE passant en ADJ, change les menus des UEs associées
|
||||||
|
if (elt.classList.contains("code_rcue")
|
||||||
|
&& elt.dataset.niveau_id
|
||||||
|
&& elt.value == "ADJ"
|
||||||
|
&& elt.value != elt.dataset.orig_recorded) {
|
||||||
|
let ue_selects = elt.parentElement.parentElement.parentElement.querySelectorAll(
|
||||||
|
"select.ue_rcue_" + elt.dataset.niveau_id);
|
||||||
|
ue_selects.forEach(select => {
|
||||||
|
select.value = "ADJ";
|
||||||
|
change_menu_code(select); // pour changer les styles
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
// Recupère la liste ordonnées des etudids
|
// Recupère la liste ordonnées des etudids
|
||||||
// pour avoir le "suivant" etr le "précédent"
|
// pour avoir le "suivant" et le "précédent"
|
||||||
// (liens de navigation)
|
// (liens de navigation)
|
||||||
const url = new URL(document.URL);
|
const url = new URL(document.URL);
|
||||||
const frags = url.pathname.split("/"); // .../formsemestre_validation_but/formsemestre_id/etudid
|
const frags = url.pathname.split("/"); // .../formsemestre_validation_but/formsemestre_id/etudid
|
||||||
|
@ -201,4 +201,79 @@ Etudiants:
|
|||||||
moy_rcue: 9.50 # la moyenne courante (et non enregistrée), donc pas 10.5
|
moy_rcue: 9.50 # la moyenne courante (et non enregistrée), donc pas 10.5
|
||||||
est_compensable: False
|
est_compensable: False
|
||||||
decision_annee: ADM
|
decision_annee: ADM
|
||||||
|
geii43:
|
||||||
|
prenom: etugeii43
|
||||||
|
civilite: M
|
||||||
|
formsemestres:
|
||||||
|
S1:
|
||||||
|
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||||
|
"S1.1": 9.00
|
||||||
|
"S1.2": 9.00
|
||||||
|
attendu: # les codes jury que l'on doit vérifier
|
||||||
|
deca:
|
||||||
|
passage_de_droit: False
|
||||||
|
nb_competences: 2
|
||||||
|
nb_rcue_annee: 0
|
||||||
|
decisions_ues:
|
||||||
|
"UE11":
|
||||||
|
codes: [ "AJ", "..." ]
|
||||||
|
code_valide: AJ
|
||||||
|
decision_jury: AJ
|
||||||
|
moy_ue: 9.00
|
||||||
|
"UE12":
|
||||||
|
codes: [ "AJ", "..." ]
|
||||||
|
code_valide: AJ
|
||||||
|
decision_jury: AJ
|
||||||
|
moy_ue: 9.00
|
||||||
|
S2:
|
||||||
|
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||||
|
"S2.1": 9.00
|
||||||
|
"S2.2": 9.00
|
||||||
|
attendu: # les codes jury que l'on doit vérifier
|
||||||
|
deca:
|
||||||
|
passage_de_droit: False
|
||||||
|
nb_competences: 2
|
||||||
|
nb_rcue_annee: 2
|
||||||
|
valide_moitie_rcue: False
|
||||||
|
codes: [ "RED", "..." ]
|
||||||
|
decisions_ues:
|
||||||
|
"UE21":
|
||||||
|
codes: [ "AJ", "..." ]
|
||||||
|
code_valide: AJ
|
||||||
|
moy_ue: 9.00
|
||||||
|
"UE22":
|
||||||
|
codes: [ "AJ", "..." ]
|
||||||
|
code_valide: AJ # va basculer en ADJ car RCUE en ADJ (mais le test est AVANT !)
|
||||||
|
moy_ue: 9.00
|
||||||
|
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||||
|
"UE11":
|
||||||
|
code_valide: AJ
|
||||||
|
decision_jury: AJ
|
||||||
|
rcue:
|
||||||
|
moy_rcue: 9.00
|
||||||
|
est_compensable: False
|
||||||
|
"UE12":
|
||||||
|
code_valide: AJ # code par défaut proposé
|
||||||
|
decision_jury: ADJ # code donné par le jury de S2
|
||||||
|
rcue:
|
||||||
|
moy_rcue: 9.00
|
||||||
|
est_compensable: False
|
||||||
|
decision_annee: RED
|
||||||
|
S1-red:
|
||||||
|
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||||
|
"S1.1": 11.00
|
||||||
|
"S1.2": 7.00
|
||||||
|
attendu: # les codes jury que l'on doit vérifier
|
||||||
|
deca:
|
||||||
|
passage_de_droit: false
|
||||||
|
nb_competences: 2
|
||||||
|
nb_rcue_annee: 0
|
||||||
|
decisions_ues:
|
||||||
|
"UE11":
|
||||||
|
codes: [ "ADM", "..." ]
|
||||||
|
code_valide: ADM
|
||||||
|
moy_ue: 11.00
|
||||||
|
"UE12":
|
||||||
|
code_valide: AJ
|
||||||
|
moy_ue: 7.00
|
||||||
|
decision_annee: AJ
|
||||||
|
@ -232,7 +232,7 @@ class ScoFake(object):
|
|||||||
self,
|
self,
|
||||||
formation_id=None,
|
formation_id=None,
|
||||||
semestre_id=None,
|
semestre_id=None,
|
||||||
titre=None,
|
titre="",
|
||||||
date_debut=None,
|
date_debut=None,
|
||||||
date_fin=None,
|
date_fin=None,
|
||||||
etat=None,
|
etat=None,
|
||||||
@ -253,6 +253,7 @@ class ScoFake(object):
|
|||||||
) -> int:
|
) -> int:
|
||||||
if responsables is None:
|
if responsables is None:
|
||||||
responsables = (self.default_user.id,)
|
responsables = (self.default_user.id,)
|
||||||
|
titre = titre or "sans titre"
|
||||||
oid = sco_formsemestre.do_formsemestre_create(locals())
|
oid = sco_formsemestre.do_formsemestre_create(locals())
|
||||||
oids = sco_formsemestre.do_formsemestre_list(
|
oids = sco_formsemestre.do_formsemestre_list(
|
||||||
args={"formsemestre_id": oid}
|
args={"formsemestre_id": oid}
|
||||||
|
@ -1,19 +1,29 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
""" Test jury BUT avec parcours
|
""" Test jury BUT avec parcours
|
||||||
|
|
||||||
|
Ces tests sont généralement lents (construction de la base),
|
||||||
|
et donc marqués par `@pytest.mark.slow`.
|
||||||
|
|
||||||
|
Certains sont aussi marqués par @pytest.mark.lemans ou @pytest.mark.lyon
|
||||||
|
pour lancer certains tests spécifiques seulement.
|
||||||
|
|
||||||
|
Exemple utilisation spécifique:
|
||||||
|
# test sur "Lyon" seulement:
|
||||||
|
pytest --pdb -m lyon tests/unit/test_but_jury.py
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from tests.unit import yaml_setup
|
from tests.unit import yaml_setup
|
||||||
|
|
||||||
import app
|
import app
|
||||||
from app.but.jury_but import DecisionsProposeesAnnee
|
|
||||||
from app.but.jury_but_validation_auto import formsemestre_validation_auto_but
|
from app.but.jury_but_validation_auto import formsemestre_validation_auto_but
|
||||||
from app.models import (
|
from app.models import FormSemestre
|
||||||
Formation,
|
|
||||||
FormSemestre,
|
|
||||||
Identite,
|
|
||||||
UniteEns,
|
|
||||||
)
|
|
||||||
from app.scodoc import sco_utils as scu
|
|
||||||
from config import TestConfig
|
from config import TestConfig
|
||||||
|
|
||||||
DEPT = TestConfig.DEPT_TEST
|
DEPT = TestConfig.DEPT_TEST
|
||||||
|
@ -34,7 +34,7 @@ formsemestre_validation_auto_but(only_adm=False)
|
|||||||
|
|
||||||
test_but_jury()
|
test_but_jury()
|
||||||
- compare décisions attendues indiquées dans le YAML avec celles de ScoDoc
|
- compare décisions attendues indiquées dans le YAML avec celles de ScoDoc
|
||||||
et enregistre immédiatement après la décision manuelle indiquée par `decision_jury`
|
et enregistre immédiatement APRES la décision manuelle indiquée par `decision_jury`
|
||||||
dans le YAML.
|
dans le YAML.
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user