Jury BUT sur semestres isolés.

This commit is contained in:
Emmanuel Viennet 2022-09-30 16:20:51 +02:00
parent d6be0e131f
commit 78bb9a706e
6 changed files with 288 additions and 65 deletions

View File

@ -206,6 +206,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
): ):
super().__init__(etud=etud) super().__init__(etud=etud)
self.formsemestre_id = formsemestre.id self.formsemestre_id = formsemestre.id
"l'id du formsemestre utilisé pour construire ce deca"
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)
@ -461,22 +462,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
if (formsemestre is None) or (not formsemestre.formation.is_apc()): if (formsemestre is None) or (not formsemestre.formation.is_apc()):
ues = [] ues = []
else: else:
formation: Formation = formsemestre.formation parcour, ues = list_ue_parcour_etud(formsemestre, self.etud, res)
# Parcour dans lequel l'étudiant est inscrit, et liste des UEs if parcour is not None:
if res.etuds_parcour_id[etudid] is None: self.parcour = parcour
# pas de parcour: prend toutes les UEs (non bonus)
ues = [ue for ue in res.etud_ues(etudid) if ue.type == UE_STANDARD]
ues.sort(key=lambda u: u.numero)
else:
parcour = ApcParcours.query.get(res.etuds_parcour_id[etudid])
if parcour is not None:
self.parcour = parcour
ues = (
formation.query_ues_parcour(parcour)
.filter_by(semestre_idx=formsemestre.semestre_id)
.order_by(UniteEns.numero)
.all()
)
ues_sems.append(ues) ues_sems.append(ues)
return ues_sems return ues_sems
@ -689,30 +677,40 @@ class DecisionsProposeesAnnee(DecisionsProposees):
# s'il n'y a pas de code, efface # s'il n'y a pas de code, efface
dec.record(code, no_overwrite=True) dec.record(code, no_overwrite=True)
def erase(self): def erase(self, only_one_sem=False):
"""Efface les décisions de jury de cet étudiant """Efface les décisions de jury de cet étudiant
pour cette année: décisions d'UE, de RCUE, d'année, pour cette année: décisions d'UE, de RCUE, d'année,
et autorisations d'inscription émises. et autorisations d'inscription émises.
""" """
for dec_ue in self.decisions_ues.values(): if only_one_sem:
dec_ue.erase() # N'efface que les autorisations venant de ce semestre,
for dec_rcue in self.decisions_rcue_by_niveau.values(): # et les validations de ses UEs
dec_rcue.erase()
if self.formsemestre_impair:
ScolarAutorisationInscription.delete_autorisation_etud( ScolarAutorisationInscription.delete_autorisation_etud(
self.etud.id, self.formsemestre_impair.id self.etud.id, self.formsemestre_id
) )
if self.formsemestre_pair: for dec_ue in self.decisions_ues.values():
ScolarAutorisationInscription.delete_autorisation_etud( if dec_ue.formsemestre.id == self.formsemestre_id:
self.etud.id, self.formsemestre_pair.id dec_ue.erase()
else:
for dec_ue in self.decisions_ues.values():
dec_ue.erase()
for dec_rcue in self.decisions_rcue_by_niveau.values():
dec_rcue.erase()
if self.formsemestre_impair:
ScolarAutorisationInscription.delete_autorisation_etud(
self.etud.id, self.formsemestre_impair.id
)
if self.formsemestre_pair:
ScolarAutorisationInscription.delete_autorisation_etud(
self.etud.id, self.formsemestre_pair.id
)
validations = ApcValidationAnnee.query.filter_by(
etudid=self.etud.id,
formsemestre_id=self.formsemestre_impair.id,
ordre=self.annee_but,
) )
validations = ApcValidationAnnee.query.filter_by( for validation in validations:
etudid=self.etud.id, db.session.delete(validation)
formsemestre_id=self.formsemestre_impair.id,
ordre=self.annee_but,
)
for validation in validations:
db.session.delete(validation)
db.session.flush() db.session.flush()
self.invalidate_formsemestre_cache() self.invalidate_formsemestre_cache()
@ -757,6 +755,26 @@ class DecisionsProposeesAnnee(DecisionsProposees):
return line_sep.join(validations) return line_sep.join(validations)
def list_ue_parcour_etud(
formsemestre: FormSemestre, etud: Identite, res: ResultatsSemestreBUT
) -> tuple[ApcParcours, list[UniteEns]]:
"""Parcour dans lequel l'étudiant est inscrit, et liste des UEs pour ce semestre"""
if res.etuds_parcour_id[etud.id] is None:
parcour = None
# pas de parcour: prend toutes les UEs (non bonus)
ues = [ue for ue in res.etud_ues(etud.id) if ue.type == UE_STANDARD]
ues.sort(key=lambda u: u.numero)
else:
parcour = ApcParcours.query.get(res.etuds_parcour_id[etud.id])
ues = (
formsemestre.formation.query_ues_parcour(parcour)
.filter_by(semestre_idx=formsemestre.semestre_id)
.order_by(UniteEns.numero)
.all()
)
return parcour, ues
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
le RCUE de cet étudiant dans cette année. le RCUE de cet étudiant dans cette année.
@ -995,8 +1013,8 @@ class DecisionsProposeesUE(DecisionsProposees):
etudid=self.etud.id, etudid=self.etud.id,
msg=f"Validation UE {self.ue.id}", msg=f"Validation UE {self.ue.id}",
) )
log(f"DecisionsProposeesUE: recording {self.validation}")
db.session.add(self.validation) db.session.add(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.recorded = True self.recorded = True

View File

@ -6,13 +6,28 @@
"""Jury BUT: affichage/formulaire """Jury BUT: affichage/formulaire
""" """
from flask import g, url_for
from app.models.etudiants import Identite
from app.scodoc import sco_utils as scu import re
import flask
from flask import flash, url_for
from flask import g, request
from app import db
from app.but import jury_but
from app.but.jury_but import DecisionsProposeesAnnee, DecisionsProposeesUE from app.but.jury_but import DecisionsProposeesAnnee, DecisionsProposeesUE
from app.models import FormSemestre, FormSemestreInscription, UniteEns from app.comp import res_sem
from app.comp.res_but import ResultatsSemestreBUT
from app.models import (
FormSemestre,
FormSemestreInscription,
Identite,
UniteEns,
ScolarAutorisationInscription,
)
from app.scodoc import html_sco_header
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import sco_utils as scu
def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
@ -29,17 +44,19 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
erase_span = "" erase_span = ""
H.append( H.append(
f"""<div class="but_section_annee"> f"""
<div> <div class="but_section_annee">
<b>Décision de jury pour l'année :</b> { <div>
_gen_but_select("code_annee", deca.codes, deca.code_valide, <b>Décision de jury pour l'année :</b> {
disabled=True, klass="manual") _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> <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> </div>
<div class="but_explanation">{deca.explanation}</div> """
</div>"""
) )
H.append( H.append(
@ -140,7 +157,181 @@ def _gen_but_niveau_ue(
</div>""" </div>"""
# def jury_but_semestriel(
formsemestre: FormSemestre, etud: Identite, read_only: bool
) -> str:
"""Formulaire saisie décision d'UE d'un semestre BUT isolé (pas jury annuel)"""
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
parcour, ues = jury_but.list_ue_parcour_etud(formsemestre, etud, res)
inscription_etat = etud.inscription_etat(formsemestre.id)
semestre_terminal = (
formsemestre.semestre_id >= formsemestre.formation.get_parcours().NB_SEM
)
est_autorise_a_passer = (formsemestre.semestre_id + 1) in (
a.semestre_id
for a in ScolarAutorisationInscription.query.filter_by(
etudid=etud.id,
origin_formsemestre_id=formsemestre.id,
)
)
decisions_ues = {
ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat)
for ue in ues
}
for dec_ue in decisions_ues.values():
dec_ue.compute_codes()
if request.method == "POST":
if not read_only:
for key in request.form:
code = request.form[key]
# Codes d'UE
m = re.match(r"^code_ue_(\d+)$", key)
if m:
ue_id = int(m.group(1))
dec_ue = decisions_ues.get(ue_id)
if not dec_ue:
raise ScoValueError(f"UE invalide ue_id={ue_id}")
dec_ue.record(code)
db.session.commit()
flash("codes enregistrés")
if not semestre_terminal:
if request.form.get("autorisation_passage"):
if not est_autorise_a_passer:
ScolarAutorisationInscription.autorise_etud(
etud.id,
formsemestre.formation.formation_code,
formsemestre.id,
formsemestre.semestre_id + 1,
)
db.session.commit()
flash(
f"autorisation de passage en S{formsemestre.semestre_id + 1} enregistrée"
)
else:
if est_autorise_a_passer:
ScolarAutorisationInscription.delete_autorisation_etud(
etud.id, formsemestre.id
)
db.session.commit()
flash(
f"autorisation de passage en S{formsemestre.semestre_id + 1} annulée"
)
return flask.redirect(
url_for(
"notes.formsemestre_validation_but",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
etudid=etud.id,
)
)
# GET
if formsemestre.semestre_id % 2 == 0:
warning = f"""<div class="warning">
Cet étudiant de S{formsemestre.semestre_id} ne peut pas passer
en jury BUT annuel car il lui manque le semestre précédent.
</div>"""
else:
warning = ""
H = [
html_sco_header.sco_header(
page_title="Validation BUT",
formsemestre_id=formsemestre.id,
etudid=etud.id,
cssstyles=("css/jury_but.css",),
javascripts=("js/jury_but.js",),
),
f"""
<div class="jury_but">
<div>
<div class="bull_head">
<div>
<div class="titre_parcours">Jury BUT S{formsemestre.id}
- Parcours {(parcour.libelle if parcour else False) or "non spécifié"}
</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>
<h3>Jury sur un semestre BUT isolé</h3>
{warning}
</div>
<form method="POST">
""",
]
if (not read_only) and any([dec.code_valide for dec in decisions_ues.values()]):
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 décisions</a>"""
else:
erase_span = "aucune décision enregistrée pour ce semestre"
H.append(
f"""
<div class="but_section_annee">
<span>{erase_span}</span>
</div>
<div><b>Unités d'enseignement de S{formsemestre.semestre_id}:</b></div>
<div class="but_annee">
<div class="titre"></div>
<div class="titre"></div>
<div class="titre"></div>
<div class="titre"></div>
"""
)
for ue in ues:
dec_ue = decisions_ues[ue.id]
H.append("""<div class="but_niveau_titre"><div></div></div>""")
H.append(
_gen_but_niveau_ue(
ue,
dec_ue.moy_ue,
dec_ue,
disabled=read_only,
)
)
H.append(
"""<div style=""></div>
<div class=""></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:
if formsemestre.semestre_id < formsemestre.formation.get_parcours().NB_SEM:
H.append(
f"""
<div class="but_settings">
<input type="checkbox" name="autorisation_passage" value="1" {
"checked" if est_autorise_a_passer else ""}>
<em>autoriser à passer dans le semestre S{formsemestre.semestre_id+1}</em>
</input>
</div>
"""
)
else:
H.append("""<div class="help">dernier semestre de la formation.</div>""")
H.append(
"""
<div class="but_buttons">
<input type="submit" value="Enregistrer ces décisions">
</div>
"""
)
return "\n".join(H)
# -------------
def infos_fiche_etud_html(etudid: int) -> str: def infos_fiche_etud_html(etudid: int) -> str:
"""Section html pour fiche etudiant """Section html pour fiche etudiant
provisoire pour BUT 2022 provisoire pour BUT 2022

View File

@ -311,7 +311,7 @@ class Identite(db.Model):
"situation": situation, "situation": situation,
} }
def inscription_etat(self, formsemestre_id): def inscription_etat(self, formsemestre_id: int) -> str:
"""État de l'inscription de cet étudiant au semestre: """État de l'inscription de cet étudiant au semestre:
False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF
""" """

View File

@ -336,10 +336,10 @@ def get_group_infos(group_id, etat=None): # was _getlisteetud
for etud in members: # long: comment eviter ces boucles ? for etud in members: # long: comment eviter ces boucles ?
etud_add_group_infos(etud, sem["formsemestre_id"]) etud_add_group_infos(etud, sem["formsemestre_id"])
if group["group_name"] != None: if group["partition_name"] is None:
group_tit = "%s %s" % (group["partition_name"], group["group_name"])
else:
group_tit = "tous" group_tit = "tous"
else:
group_tit = f"""{group["partition_name"]} {group["group_name"]}"""
return members, group, group_tit, sem, nbdem return members, group, group_tit, sem, nbdem

View File

@ -361,7 +361,7 @@ class DisplayedGroupsInfos(object):
self.formsemestre = sem self.formsemestre = sem
self.members.extend(group_members) self.members.extend(group_members)
groups_titles.append(group_tit) groups_titles.append(group_tit)
if group["group_name"] == None: if group["partition_name"] == None:
self.tous_les_etuds_du_sem = True self.tous_les_etuds_du_sem = True
else: else:
# liste les partitions explicitement sélectionnés (= des groupes de group_ids) # liste les partitions explicitement sélectionnés (= des groupes de group_ids)
@ -416,7 +416,7 @@ class DisplayedGroupsInfos(object):
"""html hidden input with groups""" """html hidden input with groups"""
H = [] H = []
for group_id in self.group_ids: for group_id in self.group_ids:
H.append('<input type="hidden" name="group_ids" value="%s"/>' % group_id) H.append(f'<input type="hidden" name="group_ids" value="{group_id}"/>')
return "\n".join(H) return "\n".join(H)

View File

@ -2285,7 +2285,8 @@ def formsemestre_validation_but(
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")
return jury_but_view.jury_but_semestriel(formsemestre, etud, read_only)
if request.method == "POST": if request.method == "POST":
if not read_only: if not read_only:
deca.record_form(request.form) deca.record_form(request.form)
@ -2322,7 +2323,7 @@ def formsemestre_validation_but(
</div> </div>
{warning} {warning}
</div> </div>
<form method="POST"> <form method="POST">
""" """
) )
@ -2331,15 +2332,16 @@ def formsemestre_validation_but(
if read_only: if read_only:
H.append( H.append(
"""<div class="but_explanation">Vous n'avez pas la permission de modifier ces décisions. """<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>""" Les champs entourés en vert sont enregistrés.</div>"""
) )
else: else:
H.append( H.append(
f"""<div class="but_settings"> f"""<div class="but_settings">
<input type="checkbox" onchange="enable_manual_codes(this)"> <input type="checkbox" onchange="enable_manual_codes(this)">
<em>permettre la saisie manuelles des codes d'année et de niveaux. <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 Dans ce cas, il vous revient de vous assurer de la cohérence entre
vos codes d'UE/RCUE/Année !</em> vos codes d'UE/RCUE/Année !</em>
</input> </input>
</div> </div>
@ -2649,8 +2651,13 @@ def formsemestre_jury_but_recap(formsemestre_id: int, selected_etudid: int = Non
) )
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
def formsemestre_jury_but_erase(formsemestre_id: int, etudid: int = None): def formsemestre_jury_but_erase(
"""Supprime la décision de jury BUT pour cette année""" formsemestre_id: int, etudid: int = None, only_one_sem=False
):
"""Supprime la décision de jury BUT pour cette année.
Si only_one_sem, n'efface que pour le formsemestre indiqué, pas les deux de l'année.
"""
only_one_sem = int(request.args.get("only_one_sem") or False)
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
if not formsemestre.formation.is_apc(): if not formsemestre.formation.is_apc():
raise ScoValueError("semestre non BUT") raise ScoValueError("semestre non BUT")
@ -2665,16 +2672,23 @@ def formsemestre_jury_but_erase(formsemestre_id: int, etudid: int = None):
) )
if request.method == "POST": if request.method == "POST":
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre) deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
deca.erase() deca.erase(only_one_sem=only_one_sem)
db.session.commit() db.session.commit()
log(f"formsemestre_jury_but_erase({formsemestre_id}, {etudid})") log(f"formsemestre_jury_but_erase({formsemestre_id}, {etudid})")
flash("décisions de jury effacées") flash(
"décisions de jury du semestre effacées"
if only_one_sem
else "décisions de jury des semestres de l'année BUT effacées"
)
return redirect(dest_url) return redirect(dest_url)
return render_template( return render_template(
"confirm_dialog.html", "confirm_dialog.html",
title=f"Effacer les validations de jury de {etud.nomprenom} ?", title=f"Effacer les validations de jury de {etud.nomprenom} ?",
explanation="""Les validations de toutes les UE, RCUE (compétences) et année seront effacées.""", explanation=f"""Les validations d'UE et autorisations de passage
du semestre S{formsemestre.semestre_id} seront effacées."""
if only_one_sem
else """Les validations de toutes les UE, RCUE (compétences) et année seront effacées.""",
cancel_url=dest_url, cancel_url=dest_url,
) )