WIP: jurys BUT: force jury annuel (en attendant page dédiée pour semestres isolés)

This commit is contained in:
Emmanuel Viennet 2022-06-22 14:09:08 +02:00
parent c17e2bae47
commit 0939feb9fc
5 changed files with 102 additions and 73 deletions

View File

@ -87,6 +87,7 @@ from app.models.formsemestre import FormSemestre, FormSemestreInscription
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.models.validations import ScolarFormSemestreValidation from app.models.validations import ScolarFormSemestreValidation
from app.scodoc import sco_codes_parcours as sco_codes from app.scodoc import sco_codes_parcours as sco_codes
from app.scodoc.sco_codes_parcours import UE_STANDARD
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import ScoException, ScoValueError from app.scodoc.sco_exceptions import ScoException, ScoValueError
@ -211,13 +212,17 @@ class DecisionsProposeesAnnee(DecisionsProposees):
for ue in self.ues_pair for ue in self.ues_pair
} }
) )
assert self.parcour is not None
self.rcues_annee = self.compute_rcues_annee() self.rcues_annee = self.compute_rcues_annee()
"RCUEs de l'année" "RCUEs de l'année"
formation = (
self.formsemestre_impair.formation
if self.formsemestre_impair
else self.formsemestre_pair.formation
)
self.niveaux_competences = ApcNiveau.niveaux_annee_de_parcours( self.niveaux_competences = ApcNiveau.niveaux_annee_de_parcours(
self.parcour, self.annee_but self.parcour, self.annee_but, formation.referentiel_competence
).all() # XXX à trier, selon l'ordre des UE associées ? ).all() # non triés
"liste des niveaux de compétences associés à cette année" "liste des niveaux de compétences associés à cette année"
self.decisions_rcue_by_niveau = self.compute_decisions_niveaux() self.decisions_rcue_by_niveau = self.compute_decisions_niveaux()
"les décisions rcue associées aux niveau_id" "les décisions rcue associées aux niveau_id"
@ -327,7 +332,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
# Parcour dans lequel l'étudiant est inscrit, et liste des UEs # Parcour dans lequel l'étudiant est inscrit, et liste des UEs
if res.etuds_parcour_id[etudid] is None: if res.etuds_parcour_id[etudid] is None:
# pas de parcour: prend toutes les UEs (non bonus) # pas de parcour: prend toutes les UEs (non bonus)
ues = list(res.etud_ues(etudid)) ues = [ue for ue in res.etud_ues(etudid) if ue.type == UE_STANDARD]
else: else:
parcour = ApcParcours.query.get(res.etuds_parcour_id[etudid]) parcour = ApcParcours.query.get(res.etuds_parcour_id[etudid])
if parcour is not None: if parcour is not None:
@ -402,11 +407,12 @@ class DecisionsProposeesAnnee(DecisionsProposees):
if rc.ue_1.niveau_competence_id == niveau.id: if rc.ue_1.niveau_competence_id == niveau.id:
rcue = rc rcue = rc
break break
dec_rcue = DecisionsProposeesRCUE(self, rcue) if rcue is not None:
rc_niveaux.append((dec_rcue, niveau.id)) dec_rcue = DecisionsProposeesRCUE(self, rcue)
# prévient les UE concernées :-) rc_niveaux.append((dec_rcue, niveau.id))
self.decisions_ues[dec_rcue.rcue.ue_1.id].set_rcue(dec_rcue.rcue) # prévient les UE concernées :-)
self.decisions_ues[dec_rcue.rcue.ue_2.id].set_rcue(dec_rcue.rcue) self.decisions_ues[dec_rcue.rcue.ue_1.id].set_rcue(dec_rcue.rcue)
self.decisions_ues[dec_rcue.rcue.ue_2.id].set_rcue(dec_rcue.rcue)
# Ordonne par numéro d'UE # Ordonne par numéro d'UE
rc_niveaux.sort(key=lambda x: x[0].rcue.ue_1.numero) rc_niveaux.sort(key=lambda x: x[0].rcue.ue_1.numero)
decisions_rcue_by_niveau = {x[1]: x[0] for x in rc_niveaux} decisions_rcue_by_niveau = {x[1]: x[0] for x in rc_niveaux}
@ -458,7 +464,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
def record(self, code: str): def record(self, code: str):
"""Enregistre le code""" """Enregistre le code"""
if not code in self.codes: if code and not code in self.codes:
raise ScoValueError( raise ScoValueError(
f"code annee <tt>{html.escape(code)}</tt> invalide pour formsemestre {html.escape(self.formsemestre)}" f"code annee <tt>{html.escape(code)}</tt> invalide pour formsemestre {html.escape(self.formsemestre)}"
) )
@ -467,20 +473,22 @@ class DecisionsProposeesAnnee(DecisionsProposees):
if self.validation: if self.validation:
db.session.delete(self.validation) db.session.delete(self.validation)
db.session.flush() db.session.flush()
if code is None:
self.validation = ApcValidationAnnee( self.validation = None
etudid=self.etud.id, else:
formsemestre=self.formsemestre_impair, self.validation = ApcValidationAnnee(
ordre=self.annee_but, etudid=self.etud.id,
annee_scolaire=self.annee_scolaire(), formsemestre=self.formsemestre_impair,
code=code, ordre=self.annee_but,
) annee_scolaire=self.annee_scolaire(),
Scolog.logdb( code=code,
method="jury_but", )
etudid=self.etud.id, Scolog.logdb(
msg=f"Validation année BUT{self.annee_but}: {code}", method="jury_but",
) etudid=self.etud.id,
db.session.add(self.validation) msg=f"Validation année BUT{self.annee_but}: {code}",
)
db.session.add(self.validation)
self.recorded = True self.recorded = True
def record_all(self): def record_all(self):
@ -494,7 +502,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
) )
for dec in decisions: for dec in decisions:
if not dec.recorded: if not dec.recorded:
dec.record(dec.codes[0]) # rappel: le code par défaut est en tête # rappel: le code par défaut est en tête
code = dec.codes[0] if dec.codes else None
# s'il n'y a pas de codee, efface
dec.record(dec.codes[0])
class DecisionsProposeesRCUE(DecisionsProposees): class DecisionsProposeesRCUE(DecisionsProposees):
@ -516,6 +527,9 @@ class DecisionsProposeesRCUE(DecisionsProposees):
): ):
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
self.codes = []
return
self.parcour = dec_prop_annee.parcour self.parcour = dec_prop_annee.parcour
self.validation = rcue.query_validations().first() self.validation = rcue.query_validations().first()
if self.validation is not None: if self.validation is not None:
@ -529,7 +543,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
def record(self, code: str): def record(self, code: str):
"""Enregistre le code""" """Enregistre le code"""
if not code in self.codes: if code and not code in self.codes:
raise ScoValueError( raise ScoValueError(
f"code UE invalide pour ue_id={self.ue.id}: {html.escape(code)}" f"code UE invalide pour ue_id={self.ue.id}: {html.escape(code)}"
) )
@ -539,20 +553,23 @@ class DecisionsProposeesRCUE(DecisionsProposees):
if self.validation: if self.validation:
db.session.delete(self.validation) db.session.delete(self.validation)
db.session.flush() db.session.flush()
self.validation = ApcValidationRCUE( if code is None:
etudid=self.etud.id, self.validation = None
formsemestre_id=self.rcue.formsemestre_2.id, else:
ue1_id=self.rcue.ue_1.id, self.validation = ApcValidationRCUE(
ue2_id=self.rcue.ue_2.id, etudid=self.etud.id,
parcours_id=parcours_id, formsemestre_id=self.rcue.formsemestre_2.id,
code=code, ue1_id=self.rcue.ue_1.id,
) ue2_id=self.rcue.ue_2.id,
Scolog.logdb( parcours_id=parcours_id,
method="jury_but", code=code,
etudid=self.etud.id, )
msg=f"Validation RCUE {repr(self.rcue)}", Scolog.logdb(
) method="jury_but",
db.session.add(self.validation) etudid=self.etud.id,
msg=f"Validation RCUE {repr(self.rcue)}",
)
db.session.add(self.validation)
self.recorded = True self.recorded = True
@ -633,7 +650,7 @@ class DecisionsProposeesUE(DecisionsProposees):
def record(self, code: str): def record(self, code: str):
"""Enregistre le code""" """Enregistre le code"""
if not code in self.codes: if code and not code in self.codes:
raise ScoValueError( raise ScoValueError(
f"code UE invalide pour ue_id={self.ue.id}: {html.escape(code)}" f"code UE invalide pour ue_id={self.ue.id}: {html.escape(code)}"
) )
@ -642,18 +659,21 @@ class DecisionsProposeesUE(DecisionsProposees):
if self.validation: if self.validation:
db.session.delete(self.validation) db.session.delete(self.validation)
db.session.flush() db.session.flush()
self.validation = ScolarFormSemestreValidation( if code is None:
etudid=self.etud.id, self.validation = None
formsemestre_id=self.formsemestre.id, else:
ue_id=self.ue.id, self.validation = ScolarFormSemestreValidation(
code=code, etudid=self.etud.id,
) formsemestre_id=self.formsemestre.id,
Scolog.logdb( ue_id=self.ue.id,
method="jury_but", code=code,
etudid=self.etud.id, )
msg=f"Validation UE {self.ue.id}", Scolog.logdb(
) method="jury_but",
db.session.add(self.validation) etudid=self.etud.id,
msg=f"Validation UE {self.ue.id}",
)
db.session.add(self.validation)
self.recorded = True self.recorded = True

View File

@ -228,5 +228,5 @@ class ResultatsSemestreBUT(NotesTableCompat):
return s.index[s.notna()] return s.index[s.notna()]
def etud_ues(self, etudid: int) -> Generator[UniteEns]: def etud_ues(self, etudid: int) -> Generator[UniteEns]:
"""Liste des UE auxquelles l'étudiant est inscrit (sans bonus).""" """Liste des UE auxquelles l'étudiant est inscrit."""
return (UniteEns.query.get(ue_id) for ue_id in self.etud_ues_ids(etudid)) return (UniteEns.query.get(ue_id) for ue_id in self.etud_ues_ids(etudid))

View File

@ -270,21 +270,33 @@ class ApcNiveau(db.Model, XMLModel):
@classmethod @classmethod
def niveaux_annee_de_parcours( def niveaux_annee_de_parcours(
cls, parcour: "ApcParcours", annee: int cls,
parcour: "ApcParcours",
annee: int,
referentiel_competence: ApcReferentielCompetences = None,
) -> flask_sqlalchemy.BaseQuery: ) -> flask_sqlalchemy.BaseQuery:
"""Les niveaux de l'année du parcours""" """Les niveaux de l'année du parcours
Si le parcour est None, tous les niveaux de l'année
"""
if annee not in {1, 2, 3}: if annee not in {1, 2, 3}:
raise ValueError("annee invalide pour un parcours BUT") raise ValueError("annee invalide pour un parcours BUT")
annee_formation = f"BUT{annee}" annee_formation = f"BUT{annee}"
return ApcNiveau.query.filter( if parcour is None:
ApcParcoursNiveauCompetence.annee_parcours_id == ApcAnneeParcours.id, return ApcNiveau.query.filter(
ApcParcours.id == ApcAnneeParcours.parcours_id, ApcNiveau.annee == annee_formation,
ApcParcours.referentiel == parcour.referentiel, ApcCompetence.id == ApcNiveau.competence_id,
ApcParcoursNiveauCompetence.competence_id == ApcCompetence.id, ApcCompetence.referentiel_id == referentiel_competence.id,
ApcCompetence.id == ApcNiveau.competence_id, )
ApcAnneeParcours.parcours == parcour, else:
ApcNiveau.annee == annee_formation, return ApcNiveau.query.filter(
) ApcParcoursNiveauCompetence.annee_parcours_id == ApcAnneeParcours.id,
ApcParcours.id == ApcAnneeParcours.parcours_id,
ApcParcours.referentiel == parcour.referentiel,
ApcParcoursNiveauCompetence.competence_id == ApcCompetence.id,
ApcCompetence.id == ApcNiveau.competence_id,
ApcAnneeParcours.parcours == parcour,
ApcNiveau.annee == annee_formation,
)
app_critiques_modules = db.Table( app_critiques_modules = db.Table(

View File

@ -18,6 +18,7 @@ from app.models.ues import UniteEns
from app.scodoc import sco_cache 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_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_codes_parcours import UE_STANDARD
class Formation(db.Model): class Formation(db.Model):
@ -166,6 +167,7 @@ class Formation(db.Model):
""" """
return UniteEns.query.filter_by(formation=self).filter( return UniteEns.query.filter_by(formation=self).filter(
UniteEns.niveau_competence_id == ApcNiveau.id, UniteEns.niveau_competence_id == ApcNiveau.id,
UniteEns.type == UE_STANDARD,
ApcParcoursNiveauCompetence.competence_id == ApcNiveau.competence_id, ApcParcoursNiveauCompetence.competence_id == ApcNiveau.competence_id,
ApcParcoursNiveauCompetence.annee_parcours_id == ApcAnneeParcours.id, ApcParcoursNiveauCompetence.annee_parcours_id == ApcAnneeParcours.id,
ApcAnneeParcours.parcours_id == parcour.id, ApcAnneeParcours.parcours_id == parcour.id,

View File

@ -2248,6 +2248,8 @@ def formsemestre_validation_but(formsemestre_id: int, etudid: int):
etud = Identite.query.get_or_404(etudid) etud = Identite.query.get_or_404(etudid)
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre) 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:
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) deca.record_form(request.form)
flash("codes enregistrés") flash("codes enregistrés")
@ -2264,7 +2266,7 @@ def formsemestre_validation_but(formsemestre_id: int, etudid: int):
<form method="POST"> <form method="POST">
<div class="titre_parcours"> <div class="titre_parcours">
<h2>Jury BUT{deca.annee_but} <h2>Jury BUT{deca.annee_but}
- Parcours {deca.parcour.libelle or "non spécifié"} - Parcours {(deca.parcour.libelle if deca.parcour else False) or "non spécifié"}
- {deca.annee_scolaire_str()}</h2> - {deca.annee_scolaire_str()}</h2>
</div> </div>
<div class="but_section_annee"> <div class="but_section_annee">
@ -2339,13 +2341,6 @@ def formsemestre_validation_but(formsemestre_id: int, etudid: int):
) )
H.append("</form>") # but_annee H.append("</form>") # but_annee
# ---- Toutes les UEs, pour infos
H.append(f"<ul>")
for ue in formsemestre.query_ues(): # volontairement toutes les UE
dec_proposee = jury_but.DecisionsProposeesUE(etud, formsemestre, ue)
H.append("<li>" + html.escape(f"""{ue} : {dec_proposee}""") + "</li>")
H.append(f"</ul>")
H.append(f"</div>")
return "\n".join(H) + html_sco_header.sco_footer() return "\n".join(H) + html_sco_header.sco_footer()