Jury BUT sur semestres isolés.

This commit is contained in:
Emmanuel Viennet 2022-09-30 16:20:51 +02:00 committed by iziram
parent 3314c1738e
commit bb5347514a
6 changed files with 6973 additions and 6750 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

@ -1,173 +1,364 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""Jury BUT: affichage/formulaire
"""
from flask import g, url_for
from app.models.etudiants import Identite
from app.scodoc import sco_utils as scu
from app.but.jury_but import DecisionsProposeesAnnee, DecisionsProposeesUE
from app.models import FormSemestre, FormSemestreInscription, UniteEns
from app.scodoc.sco_exceptions import ScoValueError
def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
"""Affichage des décisions annuelles BUT
Si pas read_only, menus sélection codes jury.
"""
H = []
if deca.code_valide and not read_only:
erase_span = f"""<a href="{
url_for("notes.formsemestre_jury_but_erase",
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id,
etudid=deca.etud.id)}" class="stdlink">effacer décisions</a>"""
else:
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>
</div>
<div class="but_explanation">{deca.explanation}</div>
</div>"""
)
H.append(
f"""
<div><b>Niveaux de compétences et unités d'enseignement :</b></div>
<div class="but_annee">
<div class="titre"></div>
<div class="titre">S{1}</div>
<div class="titre">S{2}</div>
<div class="titre">RCUE</div>
"""
)
for niveau in deca.niveaux_competences:
H.append(
f"""<div class="but_niveau_titre">
<div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div>
</div>"""
)
dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id)
if dec_rcue is None:
break
# Semestre impair
H.append(
_gen_but_niveau_ue(
dec_rcue.rcue.ue_1,
dec_rcue.rcue.moy_ue_1,
deca.decisions_ues[dec_rcue.rcue.ue_1.id],
disabled=read_only,
)
)
# Semestre pair
H.append(
_gen_but_niveau_ue(
dec_rcue.rcue.ue_2,
dec_rcue.rcue.moy_ue_2,
deca.decisions_ues[dec_rcue.rcue.ue_2.id],
disabled=read_only,
)
)
# RCUE
H.append(
f"""<div class="but_niveau_rcue
{'recorded' if dec_rcue.code_valide is not None else ''}
">
<div class="but_note">{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
<div class="but_code">{
_gen_but_select("code_rcue_"+str(niveau.id),
dec_rcue.codes,
dec_rcue.code_valide,
disabled=True, klass="manual"
)
}</div>
</div>"""
)
H.append("</div>") # but_annee
return "\n".join(H)
def _gen_but_select(
name: str,
codes: list[str],
code_valide: str,
disabled: bool = False,
klass: str = "",
) -> str:
"Le menu html select avec les codes"
h = "\n".join(
[
f"""<option value="{code}"
{'selected' if code == code_valide else ''}
class="{'recorded' if code == code_valide else ''}"
>{code}</option>"""
for code in codes
]
)
return f"""<select required name="{name}"
class="but_code {klass}"
onchange="change_menu_code(this);"
{"disabled" if disabled else ""}
>{h}</select>
"""
def _gen_but_niveau_ue(
ue: UniteEns, moy_ue: float, dec_ue: DecisionsProposeesUE, disabled=False
):
return f"""<div class="but_niveau_ue {
'recorded' if dec_ue.code_valide is not None else ''}
">
<div title="{ue.titre}">{ue.acronyme}</div>
<div class="but_note">{scu.fmt_note(moy_ue)}</div>
<div class="but_code">{
_gen_but_select("code_ue_"+str(ue.id),
dec_ue.codes,
dec_ue.code_valide, disabled=disabled
)
}</div>
</div>"""
#
def infos_fiche_etud_html(etudid: int) -> str:
"""Section html pour fiche etudiant
provisoire pour BUT 2022
"""
etud: Identite = Identite.query.get_or_404(etudid)
inscriptions = (
FormSemestreInscription.query.join(FormSemestreInscription.formsemestre)
.filter(
FormSemestreInscription.etudid == etud.id,
)
.order_by(FormSemestre.date_debut)
)
formsemestres_but = [
i.formsemestre for i in inscriptions if i.formsemestre.formation.is_apc()
]
if len(formsemestres_but) == 0:
return ""
# temporaire quick & dirty: affiche le dernier
try:
deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1])
if len(deca.rcues_annee) > 0:
return f"""<div class="infos_but">
{show_etud(deca, read_only=True)}
</div>
"""
except ScoValueError:
pass
return ""
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""Jury BUT: affichage/formulaire
"""
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.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:
"""Affichage des décisions annuelles BUT
Si pas read_only, menus sélection codes jury.
"""
H = []
if deca.code_valide and not read_only:
erase_span = f"""<a href="{
url_for("notes.formsemestre_jury_but_erase",
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id,
etudid=deca.etud.id)}" class="stdlink">effacer décisions</a>"""
else:
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>
</div>
<div class="but_explanation">{deca.explanation}</div>
</div>
"""
)
H.append(
f"""
<div><b>Niveaux de compétences et unités d'enseignement :</b></div>
<div class="but_annee">
<div class="titre"></div>
<div class="titre">S{1}</div>
<div class="titre">S{2}</div>
<div class="titre">RCUE</div>
"""
)
for niveau in deca.niveaux_competences:
H.append(
f"""<div class="but_niveau_titre">
<div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div>
</div>"""
)
dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id)
if dec_rcue is None:
break
# Semestre impair
H.append(
_gen_but_niveau_ue(
dec_rcue.rcue.ue_1,
dec_rcue.rcue.moy_ue_1,
deca.decisions_ues[dec_rcue.rcue.ue_1.id],
disabled=read_only,
)
)
# Semestre pair
H.append(
_gen_but_niveau_ue(
dec_rcue.rcue.ue_2,
dec_rcue.rcue.moy_ue_2,
deca.decisions_ues[dec_rcue.rcue.ue_2.id],
disabled=read_only,
)
)
# RCUE
H.append(
f"""<div class="but_niveau_rcue
{'recorded' if dec_rcue.code_valide is not None else ''}
">
<div class="but_note">{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
<div class="but_code">{
_gen_but_select("code_rcue_"+str(niveau.id),
dec_rcue.codes,
dec_rcue.code_valide,
disabled=True, klass="manual"
)
}</div>
</div>"""
)
H.append("</div>") # but_annee
return "\n".join(H)
def _gen_but_select(
name: str,
codes: list[str],
code_valide: str,
disabled: bool = False,
klass: str = "",
) -> str:
"Le menu html select avec les codes"
h = "\n".join(
[
f"""<option value="{code}"
{'selected' if code == code_valide else ''}
class="{'recorded' if code == code_valide else ''}"
>{code}</option>"""
for code in codes
]
)
return f"""<select required name="{name}"
class="but_code {klass}"
onchange="change_menu_code(this);"
{"disabled" if disabled else ""}
>{h}</select>
"""
def _gen_but_niveau_ue(
ue: UniteEns, moy_ue: float, dec_ue: DecisionsProposeesUE, disabled=False
):
return f"""<div class="but_niveau_ue {
'recorded' if dec_ue.code_valide is not None else ''}
">
<div title="{ue.titre}">{ue.acronyme}</div>
<div class="but_note">{scu.fmt_note(moy_ue)}</div>
<div class="but_code">{
_gen_but_select("code_ue_"+str(ue.id),
dec_ue.codes,
dec_ue.code_valide, disabled=disabled
)
}</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:
"""Section html pour fiche etudiant
provisoire pour BUT 2022
"""
etud: Identite = Identite.query.get_or_404(etudid)
inscriptions = (
FormSemestreInscription.query.join(FormSemestreInscription.formsemestre)
.filter(
FormSemestreInscription.etudid == etud.id,
)
.order_by(FormSemestre.date_debut)
)
formsemestres_but = [
i.formsemestre for i in inscriptions if i.formsemestre.formation.is_apc()
]
if len(formsemestres_but) == 0:
return ""
# temporaire quick & dirty: affiche le dernier
try:
deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1])
if len(deca.rcues_annee) > 0:
return f"""<div class="infos_but">
{show_etud(deca, read_only=True)}
</div>
"""
except ScoValueError:
pass
return ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff