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)
self.formsemestre_id = formsemestre.id
"l'id du formsemestre utilisé pour construire ce deca"
formsemestre_impair, formsemestre_pair = self.comp_formsemestres(formsemestre)
assert (
(formsemestre_pair is None)
@ -461,22 +462,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
if (formsemestre is None) or (not formsemestre.formation.is_apc()):
ues = []
else:
formation: Formation = formsemestre.formation
# Parcour dans lequel l'étudiant est inscrit, et liste des UEs
if res.etuds_parcour_id[etudid] is None:
# 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()
)
parcour, ues = list_ue_parcour_etud(formsemestre, self.etud, res)
if parcour is not None:
self.parcour = parcour
ues_sems.append(ues)
return ues_sems
@ -689,30 +677,40 @@ class DecisionsProposeesAnnee(DecisionsProposees):
# s'il n'y a pas de code, efface
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
pour cette année: décisions d'UE, de RCUE, d'année,
et autorisations d'inscription émises.
"""
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:
if only_one_sem:
# N'efface que les autorisations venant de ce semestre,
# et les validations de ses UEs
ScolarAutorisationInscription.delete_autorisation_etud(
self.etud.id, self.formsemestre_impair.id
self.etud.id, self.formsemestre_id
)
if self.formsemestre_pair:
ScolarAutorisationInscription.delete_autorisation_etud(
self.etud.id, self.formsemestre_pair.id
for dec_ue in self.decisions_ues.values():
if dec_ue.formsemestre.id == self.formsemestre_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(
etudid=self.etud.id,
formsemestre_id=self.formsemestre_impair.id,
ordre=self.annee_but,
)
for validation in validations:
db.session.delete(validation)
for validation in validations:
db.session.delete(validation)
db.session.flush()
self.invalidate_formsemestre_cache()
@ -757,6 +755,26 @@ class DecisionsProposeesAnnee(DecisionsProposees):
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):
"""Liste des codes de décisions que l'on peut proposer pour
le RCUE de cet étudiant dans cette année.
@ -995,8 +1013,8 @@ class DecisionsProposeesUE(DecisionsProposees):
etudid=self.etud.id,
msg=f"Validation UE {self.ue.id}",
)
log(f"DecisionsProposeesUE: recording {self.validation}")
db.session.add(self.validation)
log(f"DecisionsProposeesUE: recording {self.validation}")
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id)
self.recorded = True

View File

@ -6,13 +6,28 @@
"""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.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 import sco_utils as scu
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 = ""
H.append(
f"""<div class="but_section_annee">
<div>
<b>Décision de jury pour l'année :</b> {
_gen_but_select("code_annee", deca.codes, deca.code_valide,
disabled=True, klass="manual")
}
<span>({'non ' if deca.code_valide is None else ''}enregistrée)</span>
<span>{erase_span}</span>
f"""
<div class="but_section_annee">
<div>
<b>Décision de jury pour l'année :</b> {
_gen_but_select("code_annee", deca.codes, deca.code_valide,
disabled=True, klass="manual")
}
<span>({'non ' if deca.code_valide is None else ''}enregistrée)</span>
<span>{erase_span}</span>
</div>
<div class="but_explanation">{deca.explanation}</div>
</div>
<div class="but_explanation">{deca.explanation}</div>
</div>"""
"""
)
H.append(
@ -140,7 +157,181 @@ def _gen_but_niveau_ue(
</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:
"""Section html pour fiche etudiant
provisoire pour BUT 2022

View File

@ -311,7 +311,7 @@ class Identite(db.Model):
"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:
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 ?
etud_add_group_infos(etud, sem["formsemestre_id"])
if group["group_name"] != None:
group_tit = "%s %s" % (group["partition_name"], group["group_name"])
else:
if group["partition_name"] is None:
group_tit = "tous"
else:
group_tit = f"""{group["partition_name"]} {group["group_name"]}"""
return members, group, group_tit, sem, nbdem

View File

@ -361,7 +361,7 @@ class DisplayedGroupsInfos(object):
self.formsemestre = sem
self.members.extend(group_members)
groups_titles.append(group_tit)
if group["group_name"] == None:
if group["partition_name"] == None:
self.tous_les_etuds_du_sem = True
else:
# liste les partitions explicitement sélectionnés (= des groupes de group_ids)
@ -416,7 +416,7 @@ class DisplayedGroupsInfos(object):
"""html hidden input with groups"""
H = []
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)

View File

@ -2285,7 +2285,8 @@ def formsemestre_validation_but(
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
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 not read_only:
deca.record_form(request.form)
@ -2322,7 +2323,7 @@ def formsemestre_validation_but(
</div>
{warning}
</div>
<form method="POST">
"""
)
@ -2331,15 +2332,16 @@ def formsemestre_validation_but(
if read_only:
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>"""
)
else:
H.append(
f"""<div class="but_settings">
<input type="checkbox" onchange="enable_manual_codes(this)">
<em>permettre la saisie manuelles des codes d'année et de niveaux.
Dans ce cas, il vous revient de vous assurer de la cohérence entre
<em>permettre la saisie manuelles des codes d'année et de niveaux.
Dans ce cas, il vous revient de vous assurer de la cohérence entre
vos codes d'UE/RCUE/Année !</em>
</input>
</div>
@ -2649,8 +2651,13 @@ def formsemestre_jury_but_recap(formsemestre_id: int, selected_etudid: int = Non
)
@scodoc
@permission_required(Permission.ScoView)
def formsemestre_jury_but_erase(formsemestre_id: int, etudid: int = None):
"""Supprime la décision de jury BUT pour cette année"""
def formsemestre_jury_but_erase(
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)
if not formsemestre.formation.is_apc():
raise ScoValueError("semestre non BUT")
@ -2665,16 +2672,23 @@ def formsemestre_jury_but_erase(formsemestre_id: int, etudid: int = None):
)
if request.method == "POST":
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
deca.erase()
deca.erase(only_one_sem=only_one_sem)
db.session.commit()
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 render_template(
"confirm_dialog.html",
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,
)