Jury BUT: amélioration front et back. Voir #547. Tests YAML: refonte circuit jury. Cas lyon43. Tests ok.

This commit is contained in:
Emmanuel Viennet 2023-01-11 09:37:02 -03:00
parent 5eed392400
commit c833974569
12 changed files with 459 additions and 277 deletions

View File

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

View File

@ -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 ''}
@ -209,8 +223,10 @@ def _gen_but_niveau_ue(
</div> </div>
<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>
""" """

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,204 +1,279 @@
# Tests unitaires jury BUT - IUT Lyon GEII # Tests unitaires jury BUT - IUT Lyon GEII
# Essais avec un BUT GEII, 2 UE en BUT1 / 4 UE en BUT2-BUT3 et 3 parcours # Essais avec un BUT GEII, 2 UE en BUT1 / 4 UE en BUT2-BUT3 et 3 parcours
# Contrib Pascal B. # Contrib Pascal B.
ReferentielCompetences: ReferentielCompetences:
filename: but-GEII-05012022-081639.xml filename: but-GEII-05012022-081639.xml
specialite: GEII specialite: GEII
Formation: Formation:
filename: scodoc_formation_BUT_GEII_lyon_v1.xml filename: scodoc_formation_BUT_GEII_lyon_v1.xml
# Association des UE aux compétences: # Association des UE aux compétences:
ues: ues:
# S1 : Tronc commun GEII # S1 : Tronc commun GEII
'UE11': 'UE11':
annee: BUT1 annee: BUT1
competence: Concevoir competence: Concevoir
'UE12': 'UE12':
annee: BUT1 annee: BUT1
competence: Vérifier competence: Vérifier
# S2 : Tronc commun GEII # S2 : Tronc commun GEII
'UE21': 'UE21':
annee: BUT1 annee: BUT1
competence: Concevoir competence: Concevoir
'UE22': 'UE22':
annee: BUT1 annee: BUT1
competence: Vérifier competence: Vérifier
# S3 : Tronc commun GEII # S3 : Tronc commun GEII
'UE31': 'UE31':
annee: BUT2 annee: BUT2
competence: Concevoir competence: Concevoir
'UE32': 'UE32':
annee: BUT2 annee: BUT2
competence: Vérifier competence: Vérifier
'UE33': 'UE33':
annee: BUT2 annee: BUT2
competence: Maintenir competence: Maintenir
# S3 : Parcours EME # S3 : Parcours EME
'UE34EME': 'UE34EME':
annee: BUT2 annee: BUT2
competence: Installer competence: Installer
parcours: EME parcours: EME
# S3 : Parcours ESE # S3 : Parcours ESE
'UE34ESE': 'UE34ESE':
annee: BUT2 annee: BUT2
competence: Implanter competence: Implanter
parcours: ESE parcours: ESE
# S3 : Parcours AII # S3 : Parcours AII
'UE34AII': 'UE34AII':
annee: BUT2 annee: BUT2
competence: Intégrer competence: Intégrer
parcours: AII parcours: AII
# S4 : Tronc commun GEII # S4 : Tronc commun GEII
'UE41': 'UE41':
annee: BUT2 annee: BUT2
competence: Concevoir competence: Concevoir
'UE42': 'UE42':
annee: BUT2 annee: BUT2
competence: Vérifier competence: Vérifier
'UE43': 'UE43':
annee: BUT2 annee: BUT2
competence: Maintenir competence: Maintenir
# S4 : Parcours EME # S4 : Parcours EME
'UE44EME': 'UE44EME':
annee: BUT2 annee: BUT2
competence: Installer competence: Installer
parcours: EME parcours: EME
# S4 : Parcours ESE # S4 : Parcours ESE
'UE44ESE': 'UE44ESE':
annee: BUT2 annee: BUT2
competence: Implanter competence: Implanter
parcours: ESE parcours: ESE
# S4 : Parcours AII # S4 : Parcours AII
'UE44AII': 'UE44AII':
annee: BUT2 annee: BUT2
competence: Intégrer competence: Intégrer
parcours: AII parcours: AII
modules_parcours: modules_parcours:
# cette section permet d'associer des modules à des parcours # cette section permet d'associer des modules à des parcours
# les codes modules peuvent être des regexp # les codes modules peuvent être des regexp
EME: [ .*EME.* ] EME: [ .*EME.* ]
ESE: [ .*ESE.* ] ESE: [ .*ESE.* ]
AII: [ .*AII.* ] AII: [ .*AII.* ]
FormSemestres: FormSemestres:
# S1 et S2 : # S1 et S2 :
S1: S1:
idx: 1 idx: 1
date_debut: 2021-09-01 date_debut: 2021-09-01
date_fin: 2022-01-15 date_fin: 2022-01-15
S2: S2:
idx: 2 idx: 2
date_debut: 2022-01-16 date_debut: 2022-01-16
date_fin: 2022-06-30 date_fin: 2022-06-30
# S3 avec les trois parcours réunis: # S3 avec les trois parcours réunis:
# S3: # S3:
# idx: 3 # idx: 3
# date_debut: 2022-09-01 # date_debut: 2022-09-01
# date_fin: 2023-01-13 # date_fin: 2023-01-13
# codes_parcours: ['AII', 'EME', 'ESE'] # codes_parcours: ['AII', 'EME', 'ESE']
# Un S1 pour les redoublants # Un S1 pour les redoublants
S1-red: S1-red:
idx: 1 idx: 1
date_debut: 2022-09-02 date_debut: 2022-09-02
date_fin: 2023-01-12 date_fin: 2023-01-12
Etudiants: Etudiants:
geii8: geii8:
prenom: etugeii8 prenom: etugeii8
civilite: M civilite: M
formsemestres: formsemestres:
S1: S1:
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
"S1.1": 7.00 "S1.1": 7.00
"S1.2": 9.00 "S1.2": 9.00
attendu: # les codes jury que l'on doit vérifier attendu: # les codes jury que l'on doit vérifier
deca: deca:
passage_de_droit: False passage_de_droit: False
nb_competences: 2 nb_competences: 2
nb_rcue_annee: 0 nb_rcue_annee: 0
decisions_ues: decisions_ues:
"UE11": "UE11":
codes: [ "AJ", "..." ] codes: [ "AJ", "..." ]
code_valide: AJ code_valide: AJ
decision_jury: AJ decision_jury: AJ
moy_ue: 7.00 moy_ue: 7.00
"UE12": "UE12":
codes: [ "AJ", "..." ] codes: [ "AJ", "..." ]
code_valide: AJ code_valide: AJ
decision_jury: AJ decision_jury: AJ
moy_ue: 9.00 moy_ue: 9.00
S2: S2:
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
"S2.1": 12.00 "S2.1": 12.00
"S2.2": 12.00 "S2.2": 12.00
attendu: # les codes jury que l'on doit vérifier attendu: # les codes jury que l'on doit vérifier
deca: deca:
passage_de_droit: False passage_de_droit: False
nb_competences: 2 nb_competences: 2
nb_rcue_annee: 2 nb_rcue_annee: 2
valide_moitie_rcue: False valide_moitie_rcue: False
codes: [ "RED", "..." ] codes: [ "RED", "..." ]
decisions_ues: decisions_ues:
"UE21": "UE21":
codes: [ "ADM", "..." ] codes: [ "ADM", "..." ]
code_valide: ADM code_valide: ADM
decision_jury: ADM decision_jury: ADM
moy_ue: 12.00 moy_ue: 12.00
"UE22": "UE22":
codes: [ "ADM", "..." ] codes: [ "ADM", "..." ]
code_valide: ADM code_valide: ADM
decision_jury: ADM decision_jury: ADM
moy_ue: 12.00 moy_ue: 12.00
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1) decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
"UE11": "UE11":
code_valide: AJ code_valide: AJ
decision_jury: AJ decision_jury: AJ
rcue: rcue:
moy_rcue: 9.50 moy_rcue: 9.50
est_compensable: False est_compensable: False
"UE12": "UE12":
code_valide: CMP code_valide: CMP
decision_jury: CMP decision_jury: CMP
rcue: rcue:
moy_rcue: 10.50 moy_rcue: 10.50
est_compensable: True est_compensable: True
decision_annee: RED decision_annee: RED
S1-red: S1-red:
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
"S1.1": 9.50 "S1.1": 9.50
"S1.2": 7.00 "S1.2": 7.00
attendu: # les codes jury que l'on doit vérifier attendu: # les codes jury que l'on doit vérifier
deca: deca:
passage_de_droit: False passage_de_droit: False
nb_competences: 2 nb_competences: 2
nb_rcue_annee: 2 nb_rcue_annee: 2
decisions_ues: decisions_ues:
"UE11": "UE11":
codes: [ "CMP", "..." ] codes: [ "CMP", "..." ]
code_valide: CMP code_valide: CMP
decision_jury: CMP decision_jury: CMP
moy_ue: 9.50 moy_ue: 9.50
"UE12": "UE12":
codes: [ "AJ", "..." ] codes: [ "AJ", "..." ]
code_valide: AJ code_valide: AJ
decision_jury: AJ decision_jury: AJ
moy_ue: 7.00 moy_ue: 7.00
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1) decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
"UE11": "UE11":
code_valide: CMP code_valide: CMP
decision_jury: CMP decision_jury: CMP
rcue: rcue:
moy_rcue: 10.75 moy_rcue: 10.75
est_compensable: True est_compensable: True
"UE12": "UE12":
code_valide: CMP # car validé en fin de S2 code_valide: CMP # car validé en fin de S2
rcue: rcue:
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

View File

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

View File

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

View File

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