forked from ScoDoc/ScoDoc
389 lines
13 KiB
Python
389 lines
13 KiB
Python
##############################################################################
|
|
# ScoDoc
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
|
# See LICENSE
|
|
##############################################################################
|
|
|
|
"""Jury BUT: affichage/formulaire
|
|
"""
|
|
|
|
import re
|
|
import numpy as np
|
|
|
|
import flask
|
|
from flask import flash, render_template, url_for
|
|
from flask import g, request
|
|
|
|
from app import db
|
|
from app.but import jury_but
|
|
from app.but.jury_but import (
|
|
DecisionsProposeesAnnee,
|
|
DecisionsProposeesRCUE,
|
|
DecisionsProposeesUE,
|
|
)
|
|
from app.comp import res_sem
|
|
from app.comp.res_but import ResultatsSemestreBUT
|
|
from app.models import (
|
|
ApcNiveau,
|
|
FormSemestre,
|
|
FormSemestreInscription,
|
|
Identite,
|
|
UniteEns,
|
|
ScolarAutorisationInscription,
|
|
ScolarFormSemestreValidation,
|
|
ScolarNews,
|
|
)
|
|
from app.models.config import ScoDocSiteConfig
|
|
from app.scodoc import html_sco_header
|
|
from app.scodoc import codes_cursus as sco_codes
|
|
from app.scodoc.sco_exceptions import ScoValueError
|
|
from app.scodoc import sco_preferences
|
|
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.jury_annuel:
|
|
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>({deca.code_valide or 'non'} enregistrée)</span>
|
|
</div>
|
|
</div>
|
|
"""
|
|
)
|
|
|
|
formsemestre_1 = deca.formsemestre_impair
|
|
formsemestre_2 = deca.formsemestre_pair
|
|
# Ordonne selon les dates des 2 semestres considérés (pour les redoublants à cheval):
|
|
reverse_semestre = (
|
|
deca.formsemestre_pair
|
|
and deca.formsemestre_impair
|
|
and deca.formsemestre_pair.date_debut < deca.formsemestre_impair.date_debut
|
|
)
|
|
if reverse_semestre:
|
|
formsemestre_1, formsemestre_2 = formsemestre_2, formsemestre_1
|
|
H.append(
|
|
f"""
|
|
<div class="titre_niveaux">
|
|
<b>Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}</b>
|
|
<a style="margin-left: 32px;" class="stdlink" target="_blank" rel="noopener noreferrer"
|
|
href={
|
|
url_for("notes.validation_rcues", scodoc_dept=g.scodoc_dept,
|
|
etudid=deca.etud.id,
|
|
formsemestre_id=formsemestre_2.id if formsemestre_2 else formsemestre_1.id
|
|
)
|
|
}>visualiser son cursus</a>
|
|
</div>
|
|
<div class="but_explanation">{deca.explanation}</div>
|
|
<div class="but_annee">
|
|
<div class="titre"></div>
|
|
<div class="titre">{"S" +str(formsemestre_1.semestre_id)
|
|
if formsemestre_1 else "-"}
|
|
<span class="avertissement_redoublement">{formsemestre_1.annee_scolaire_str()
|
|
if formsemestre_1 else ""}</span>
|
|
</div>
|
|
<div class="titre">{"S"+str(formsemestre_2.semestre_id)
|
|
if formsemestre_2 else "-"}
|
|
<span class="avertissement_redoublement">{formsemestre_2.annee_scolaire_str()
|
|
if formsemestre_2 else ""}</span>
|
|
</div>
|
|
<div class="titre" title="Décisions sur RCUEs enregistrées sur l'ensemble du cursus">RCUE</div>
|
|
"""
|
|
)
|
|
for dec_rcue in deca.get_decisions_rcues_annee():
|
|
rcue = dec_rcue.rcue
|
|
niveau = rcue.niveau
|
|
H.append(
|
|
f"""<div class="but_niveau_titre">
|
|
<div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div>
|
|
</div>"""
|
|
)
|
|
ue_impair, ue_pair = rcue.ue_1, rcue.ue_2
|
|
# Les UEs à afficher : on regarde si read only et si dispense (non ré-inscription à l'UE)
|
|
# tuples (UniteEns, read_only, dispense)
|
|
ues_ro_dispense = [
|
|
(
|
|
ue_impair,
|
|
rcue.ue_cur_impair is None,
|
|
deca.res_impair
|
|
and ue_impair
|
|
and (deca.etud.id, ue_impair.id) in deca.res_impair.dispense_ues,
|
|
),
|
|
(
|
|
ue_pair,
|
|
rcue.ue_cur_pair is None,
|
|
deca.res_pair
|
|
and ue_pair
|
|
and (deca.etud.id, ue_pair.id) in deca.res_pair.dispense_ues,
|
|
),
|
|
]
|
|
# Ordonne selon les dates des 2 semestres considérés:
|
|
if reverse_semestre:
|
|
ues_ro_dispense[0], ues_ro_dispense[1] = (
|
|
ues_ro_dispense[1],
|
|
ues_ro_dispense[0],
|
|
)
|
|
# Colonnes d'UE:
|
|
for ue, ue_read_only, ue_dispense in ues_ro_dispense:
|
|
if ue:
|
|
H.append(
|
|
_gen_but_niveau_ue(
|
|
ue,
|
|
deca.decisions_ues[ue.id],
|
|
disabled=read_only or ue_read_only,
|
|
annee_prec=ue_read_only,
|
|
niveau_id=ue.niveau_competence.id,
|
|
ue_dispense=ue_dispense,
|
|
)
|
|
)
|
|
else:
|
|
H.append("""<div class="niveau_vide"></div>""")
|
|
|
|
# Colonne RCUE
|
|
H.append(_gen_but_rcue(dec_rcue, niveau))
|
|
|
|
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 = "",
|
|
data: dict = None,
|
|
code_valide_label: str = "",
|
|
) -> str:
|
|
"Le menu html select avec les codes"
|
|
# if disabled: # mauvaise idée car le disabled est traité en JS
|
|
# return f"""<div class="but_code {klass}">{code_valide}</div>"""
|
|
data = data or {}
|
|
options_htm = "\n".join(
|
|
[
|
|
f"""<option value="{code}"
|
|
{'selected' if code == code_valide else ''}
|
|
class="{'recorded' if code == code_valide else ''}"
|
|
>{code
|
|
if ((code != code_valide) or not code_valide_label)
|
|
else code_valide_label
|
|
}</option>"""
|
|
for code in codes
|
|
]
|
|
)
|
|
return f"""<select required name="{name}"
|
|
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);"
|
|
{"disabled" if disabled else ""}
|
|
{" ".join( f'data-{k}="{v}"' for (k,v) in data.items() )}
|
|
>{options_htm}</select>
|
|
"""
|
|
|
|
|
|
def _gen_but_niveau_ue(
|
|
ue: UniteEns,
|
|
dec_ue: DecisionsProposeesUE,
|
|
disabled: bool = False,
|
|
annee_prec: bool = False,
|
|
niveau_id: int = None,
|
|
ue_dispense: bool = False,
|
|
) -> str:
|
|
if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
|
|
moy_ue_str = f"""<span class="ue_cap">{
|
|
scu.fmt_note(dec_ue.moy_ue_with_cap)}</span>"""
|
|
|
|
if ue_dispense:
|
|
etat_en_cours = """Non (ré)inscrit à cette UE"""
|
|
else:
|
|
etat_en_cours = f"""UE en cours
|
|
{ "sans notes" if np.isnan(dec_ue.moy_ue)
|
|
else
|
|
("avec moyenne <b>" + scu.fmt_note(dec_ue.moy_ue) + "</b>")
|
|
}
|
|
"""
|
|
|
|
scoplement = f"""<div class="scoplement">
|
|
<div>
|
|
<b>UE {ue.acronyme} capitalisée </b>
|
|
<span>le {dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
{ etat_en_cours }
|
|
</div>
|
|
</div>
|
|
"""
|
|
elif dec_ue.formsemestre is None:
|
|
# Validation d'UE antérieure (semestre hors année scolaire courante)
|
|
if dec_ue.validation:
|
|
moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.validation.moy_ue)}</span>"""
|
|
scoplement = f"""<div class="scoplement">
|
|
<div>
|
|
<b>UE {ue.acronyme} antérieure </b>
|
|
<span>validée {dec_ue.validation.code}
|
|
le {dec_ue.validation.event_date.strftime("%d/%m/%Y")}
|
|
</span>
|
|
</div>
|
|
<div>Non reprise dans l'année en cours</div>
|
|
</div>
|
|
"""
|
|
else:
|
|
moy_ue_str = """<span>-</span>"""
|
|
scoplement = """<div class="scoplement">
|
|
<div>
|
|
<b>Pas d'UE en cours ou validée dans cette compétence de ce côté.</b>
|
|
</div>
|
|
</div>
|
|
"""
|
|
else:
|
|
moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>"""
|
|
if dec_ue.code_valide:
|
|
date_str = (
|
|
f"""enregistré le {dec_ue.validation.event_date.strftime("%d/%m/%Y")}
|
|
à {dec_ue.validation.event_date.strftime("%Hh%M")}
|
|
"""
|
|
if dec_ue.validation and dec_ue.validation.event_date
|
|
else ""
|
|
)
|
|
scoplement = f"""<div class="scoplement">
|
|
<div>Code {dec_ue.code_valide} {date_str}
|
|
</div>
|
|
</div>
|
|
"""
|
|
else:
|
|
if dec_ue.ue_status and dec_ue.ue_status["was_capitalized"]:
|
|
scoplement = """<div class="scoplement">
|
|
UE déjà capitalisée avec résultat moins favorable.
|
|
</div>
|
|
"""
|
|
else:
|
|
scoplement = ""
|
|
|
|
ue_class = "" # 'recorded' if dec_ue.code_valide is not None else ''
|
|
if dec_ue.code_valide is not None and dec_ue.codes:
|
|
if dec_ue.code_valide == dec_ue.codes[0]:
|
|
ue_class = "recorded"
|
|
else:
|
|
ue_class = "recorded_different"
|
|
|
|
return f"""<div class="but_niveau_ue {ue_class}
|
|
{'annee_prec' if annee_prec else ''}
|
|
">
|
|
<div title="{ue.titre or ''}">{ue.acronyme}</div>
|
|
<div class="but_note with_scoplement">
|
|
<div>{moy_ue_str}</div>
|
|
{scoplement}
|
|
</div>
|
|
<div class="but_code">{
|
|
_gen_but_select("code_ue_"+str(ue.id),
|
|
dec_ue.codes,
|
|
dec_ue.code_valide,
|
|
disabled=disabled,
|
|
klass=f"code_ue ue_rcue_{niveau_id}" if not disabled else ""
|
|
)
|
|
}</div>
|
|
|
|
</div>"""
|
|
|
|
|
|
def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
|
|
if dec_rcue is None or not dec_rcue.rcue.complete:
|
|
return """
|
|
<div class="but_niveau_rcue niveau_vide with_scoplement">
|
|
<div></div>
|
|
<div class="scoplement">Pas de RCUE (UE non capitalisée ?)</div>
|
|
</div>
|
|
"""
|
|
|
|
code_propose_menu = dec_rcue.code_valide # le code enregistré
|
|
code_valide_label = code_propose_menu
|
|
if dec_rcue.validation:
|
|
if dec_rcue.code_valide == dec_rcue.codes[0]:
|
|
descr_validation = dec_rcue.validation.html()
|
|
else: # on une validation enregistrée différence de celle proposée
|
|
descr_validation = f"""Décision recommandée: <b>{dec_rcue.codes[0]}.</b>
|
|
Il y avait {dec_rcue.validation.html()}"""
|
|
if (
|
|
sco_codes.BUT_CODES_ORDER[dec_rcue.codes[0]]
|
|
> sco_codes.BUT_CODES_ORDER[dec_rcue.code_valide]
|
|
):
|
|
code_propose_menu = dec_rcue.codes[0]
|
|
code_valide_label = (
|
|
f"{dec_rcue.codes[0]} (actuel {dec_rcue.code_valide})"
|
|
)
|
|
scoplement = f"""<div class="scoplement">{descr_validation}</div>"""
|
|
else:
|
|
scoplement = "" # "pas de validation"
|
|
|
|
# Déjà enregistré ?
|
|
niveau_rcue_class = ""
|
|
if dec_rcue.code_valide is not None and dec_rcue.codes:
|
|
if dec_rcue.code_valide == dec_rcue.codes[0]:
|
|
niveau_rcue_class = "recorded"
|
|
else:
|
|
niveau_rcue_class = "recorded_different"
|
|
|
|
return f"""
|
|
<div class="but_niveau_rcue {niveau_rcue_class}
|
|
">
|
|
<div class="but_note with_scoplement">
|
|
<div>{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
|
|
{scoplement}
|
|
</div>
|
|
<div class="but_code">
|
|
{_gen_but_select("code_rcue_"+str(niveau.id),
|
|
dec_rcue.codes,
|
|
code_propose_menu,
|
|
disabled=True,
|
|
klass="manual code_rcue",
|
|
data = { "niveau_id" : str(niveau.id)},
|
|
code_valide_label = code_valide_label,
|
|
)}
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
|
|
# -------------
|
|
def infos_fiche_etud_html(etudid: int) -> str:
|
|
"""Section html pour fiche etudiant
|
|
provisoire pour BUT 2022
|
|
"""
|
|
etud = Identite.get_etud(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])
|
|
return f"""<div class="infos_but">
|
|
{show_etud(deca, read_only=True)}
|
|
</div>
|
|
"""
|
|
except ScoValueError:
|
|
pass
|
|
|
|
return ""
|