Améliore visu jury BUT. + minor code cleaning.

This commit is contained in:
Emmanuel Viennet 2022-12-24 18:06:22 -03:00
parent 29869e543f
commit 1e73021c42
4 changed files with 152 additions and 96 deletions

View File

@ -8,6 +8,7 @@
""" """
import re import re
import numpy as np
import flask import flask
from flask import flash, url_for from flask import flash, url_for
@ -15,10 +16,15 @@ from flask import g, request
from app import db from app import db
from app.but import jury_but from app.but import jury_but
from app.but.jury_but import DecisionsProposeesAnnee, DecisionsProposeesUE from app.but.jury_but import (
DecisionsProposeesAnnee,
DecisionsProposeesRCUE,
DecisionsProposeesUE,
)
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_but import ResultatsSemestreBUT
from app.models import ( from app.models import (
ApcNiveau,
FormSemestre, FormSemestre,
FormSemestreInscription, FormSemestreInscription,
Identite, Identite,
@ -59,19 +65,9 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
""" """
) )
else: else:
H.append("""<div><em>Pas de décision annuelle (sem. impair)</em></div>""") H.append("""<div><em>Pas de décision annuelle (sem. impair).</em></div>""")
H.append("""</div>""") H.append("""</div>""")
if deca.formsemestre_pair is not None:
annee_sco_pair = deca.formsemestre_pair.annee_scolaire()
avertissement_redoublement = (
f"année {annee_sco_pair}-{annee_sco_pair+1}"
if annee_sco_pair != deca.annee_scolaire()
else ""
)
else:
avertissement_redoublement = ""
formsemestre_1 = deca.formsemestre_impair formsemestre_1 = deca.formsemestre_impair
formsemestre_2 = deca.formsemestre_pair formsemestre_2 = deca.formsemestre_pair
# Ordonne selon les dates des 2 semestres considérés (pour les redoublants à cheval): # Ordonne selon les dates des 2 semestres considérés (pour les redoublants à cheval):
@ -84,16 +80,19 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
formsemestre_1, formsemestre_2 = formsemestre_2, formsemestre_1 formsemestre_1, formsemestre_2 = formsemestre_2, formsemestre_1
H.append( H.append(
f""" f"""
<div class="titre_niveaux"><b>Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}</b></div> <div class="titre_niveaux"><b>Niveaux de compétences et unités d'enseignement du BUT{
deca.annee_but}</b></div>
<div class="but_annee"> <div class="but_annee">
<div class="titre"></div> <div class="titre"></div>
<div class="titre">S{formsemestre_1.semestre_id <div class="titre">{"S" +str(formsemestre_1.semestre_id)
if formsemestre_1 else "-"} if formsemestre_1 else "-"}
<span class="avertissement_redoublement">{formsemestre_1.annee_scolaire_str() if formsemestre_1 else ""}</span> <span class="avertissement_redoublement">{formsemestre_1.annee_scolaire_str()
if formsemestre_1 else ""}</span>
</div> </div>
<div class="titre">S{formsemestre_2.semestre_id <div class="titre">{"S"+str(formsemestre_2.semestre_id)
if formsemestre_2 else "-"} if formsemestre_2 else "-"}
<span class="avertissement_redoublement">{formsemestre_2.annee_scolaire_str() if formsemestre_2 else ""}</span> <span class="avertissement_redoublement">{formsemestre_2.annee_scolaire_str()
if formsemestre_2 else ""}</span>
</div> </div>
<div class="titre">RCUE</div> <div class="titre">RCUE</div>
""" """
@ -109,7 +108,8 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
ue_impair = ues[0] if ues else None ue_impair = ues[0] if ues else None
ues = [ue for ue in deca.ues_pair if ue.niveau_competence.id == niveau.id] ues = [ue for ue in deca.ues_pair if ue.niveau_competence.id == niveau.id]
ue_pair = ues[0] if ues else None ue_pair = ues[0] if ues else None
# Les UEs à afficher, toujours en readonly sur le formsemestre de l'année précédente du redoublant # Les UEs à afficher, toujours en readonly
# sur le formsemestre de l'année précédente du redoublant
ues_ro = [ ues_ro = [
( (
ue_impair, ue_impair,
@ -137,24 +137,9 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
else: else:
H.append("""<div class="niveau_vide"></div>""") H.append("""<div class="niveau_vide"></div>""")
# RCUE # Colonne RCUE
if dec_rcue is None: H.append(_gen_but_rcue(dec_rcue, niveau))
H.append("""<div class="niveau_vide"></div>""")
else:
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 H.append("</div>") # but_annee
return "\n".join(H) return "\n".join(H)
@ -169,7 +154,7 @@ def _gen_but_select(
"Le menu html select avec les codes" "Le menu html select avec les codes"
# if disabled: # mauvaise idée car le disabled est traité en JS # if disabled: # mauvaise idée car le disabled est traité en JS
# return f"""<div class="but_code {klass}">{code_valide}</div>""" # return f"""<div class="but_code {klass}">{code_valide}</div>"""
h = "\n".join( options_htm = "\n".join(
[ [
f"""<option value="{code}" f"""<option value="{code}"
{'selected' if code == code_valide else ''} {'selected' if code == code_valide else ''}
@ -182,7 +167,7 @@ def _gen_but_select(
class="but_code {klass}" class="but_code {klass}"
onchange="change_menu_code(this);" onchange="change_menu_code(this);"
{"disabled" if disabled else ""} {"disabled" if disabled else ""}
>{h}</select> >{options_htm}</select>
""" """
@ -191,18 +176,21 @@ def _gen_but_niveau_ue(
dec_ue: DecisionsProposeesUE, dec_ue: DecisionsProposeesUE,
disabled: bool = False, disabled: bool = False,
annee_prec: bool = False, annee_prec: bool = False,
): ) -> str:
if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]: if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
moy_ue_str = f"""<span class="ue_cap">{ moy_ue_str = f"""<span class="ue_cap">{
scu.fmt_note(dec_ue.moy_ue_with_cap)}</span>""" scu.fmt_note(dec_ue.moy_ue_with_cap)}</span>"""
scoplement = f"""<div class="scoplement"> scoplement = f"""<div class="scoplement">
<div> <div>
<b>UE {ue.acronyme} capitalisée le <b>UE {ue.acronyme} capitalisée </b>
{dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")} <span>le {dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")}
</b> </span>
</div> </div>
<div>UE en cours avec moyenne <div>UE en cours
{scu.fmt_note(dec_ue.moy_ue)} { "sans notes" if np.isnan(dec_ue.moy_ue)
else
("avec moyenne" + scu.fmt_note(dec_ue.moy_ue))
}
</div> </div>
</div> </div>
""" """
@ -229,6 +217,43 @@ def _gen_but_niveau_ue(
</div>""" </div>"""
def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
if dec_rcue is None:
return """
<div class="but_niveau_rcue niveau_vide with_scoplement">
<div></div>
<div class="scoplement">Pas de RCUE (UE non capitalisée ?)</div>
</div>
"""
scoplement = (
f"""<div class="scoplement">{
dec_rcue.validation.to_html()
}</div>"""
if dec_rcue.validation
else ""
)
return f"""
<div class="but_niveau_rcue
{'recorded' if dec_rcue.code_valide is not None else ''}
">
<div class="but_note with_scoplement">
<div>{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
{scoplement}
</div>
<div class="but_code">
<div>{_gen_but_select("code_rcue_"+str(niveau.id),
dec_rcue.codes,
dec_rcue.code_valide,
disabled=True, klass="manual"
)}
</div>
</div>
</div>
"""
def jury_but_semestriel( def jury_but_semestriel(
formsemestre: FormSemestre, formsemestre: FormSemestre,
etud: Identite, etud: Identite,
@ -265,9 +290,9 @@ def jury_but_semestriel(
for key in request.form: for key in request.form:
code = request.form[key] code = request.form[key]
# Codes d'UE # Codes d'UE
m = re.match(r"^code_ue_(\d+)$", key) code_match = re.match(r"^code_ue_(\d+)$", key)
if m: if code_match:
ue_id = int(m.group(1)) ue_id = int(code_match.group(1))
dec_ue = decisions_ues.get(ue_id) dec_ue = decisions_ues.get(ue_id)
if not dec_ue: if not dec_ue:
raise ScoValueError(f"UE invalide ue_id={ue_id}") raise ScoValueError(f"UE invalide ue_id={ue_id}")
@ -285,7 +310,8 @@ def jury_but_semestriel(
) )
db.session.commit() db.session.commit()
flash( flash(
f"autorisation de passage en S{formsemestre.semestre_id + 1} enregistrée" f"""autorisation de passage en S{formsemestre.semestre_id + 1
} enregistrée"""
) )
else: else:
if est_autorise_a_passer: if est_autorise_a_passer:
@ -442,11 +468,10 @@ def infos_fiche_etud_html(etudid: int) -> str:
# temporaire quick & dirty: affiche le dernier # temporaire quick & dirty: affiche le dernier
try: try:
deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1]) deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1])
if True: # len(deca.rcues_annee) > 0: return f"""<div class="infos_but">
return f"""<div class="infos_but">
{show_etud(deca, read_only=True)} {show_etud(deca, read_only=True)}
</div> </div>
""" """
except ScoValueError: except ScoValueError:
pass pass

View File

@ -2,19 +2,17 @@
"""Décisions de jury (validations) des RCUE et années du BUT """Décisions de jury (validations) des RCUE et années du BUT
""" """
import flask_sqlalchemy
from sqlalchemy.sql import text
from typing import Union from typing import Union
from app import db import flask_sqlalchemy
from app import db
from app.models import CODE_STR_LEN from app.models import CODE_STR_LEN
from app.models.but_refcomp import ApcNiveau from app.models.but_refcomp import ApcNiveau
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.ues import UniteEns
from app.models.formations import Formation from app.models.formations import Formation
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
from app.models.ues import UniteEns
from app.scodoc import sco_codes_parcours as sco_codes from app.scodoc import sco_codes_parcours as sco_codes
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -63,7 +61,14 @@ class ApcValidationRCUE(db.Model):
self.ue1}/{self.ue2}:{self.code!r}>""" self.ue1}/{self.ue2}:{self.code!r}>"""
def __str__(self): def __str__(self):
return f"""décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: {self.code}""" return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: {
self.code} enregistrée le {self.date.strftime("%d/%m/%Y")}"""
def to_html(self) -> str:
"description en HTML"
return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}:
<b>{self.code}</b>
<em>enregistrée le {self.date.strftime("%d/%m/%Y")}</em>"""
def niveau(self) -> ApcNiveau: def niveau(self) -> ApcNiveau:
"""Le niveau de compétence associé à cet RCUE.""" """Le niveau de compétence associé à cet RCUE."""
@ -96,10 +101,6 @@ class RegroupementCoherentUE:
dec_ue_2: "DecisionsProposeesUE", dec_ue_2: "DecisionsProposeesUE",
inscription_etat: str, inscription_etat: str,
): ):
from app.comp import res_sem
from app.comp.res_but import ResultatsSemestreBUT
# from app.but.jury_but import DecisionsProposeesUE
ue_1 = dec_ue_1.ue ue_1 = dec_ue_1.ue
ue_2 = dec_ue_2.ue ue_2 = dec_ue_2.ue
# Ordonne les UE dans le sens croissant (S1,S2) ou (S3,S4)... # Ordonne les UE dans le sens croissant (S1,S2) ou (S3,S4)...
@ -296,7 +297,8 @@ class ApcValidationAnnee(db.Model):
formsemestre = db.relationship("FormSemestre", backref="apc_validations_annees") formsemestre = db.relationship("FormSemestre", backref="apc_validations_annees")
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} {self.id} {self.etud} BUT{self.ordre}/{self.annee_scolaire}:{self.code!r}>" return f"""<{self.__class__.__name__} {self.id} {self.etud
} BUT{self.ordre}/{self.annee_scolaire}:{self.code!r}>"""
def __str__(self): def __str__(self):
return f"""décision sur année BUT{self.ordre} {self.annee_scolaire} : {self.code}""" return f"""décision sur année BUT{self.ordre} {self.annee_scolaire} : {self.code}"""
@ -333,7 +335,8 @@ def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
titres_rcues.append(f"""pas de compétence: code {dec_rcue["code"]}""") titres_rcues.append(f"""pas de compétence: code {dec_rcue["code"]}""")
else: else:
titres_rcues.append( titres_rcues.append(
f"""{niveau["competence"]["titre"]}&nbsp;{niveau["ordre"]}:&nbsp;{dec_rcue["code"]}""" f"""{niveau["competence"]["titre"]}&nbsp;{niveau["ordre"]}:&nbsp;{
dec_rcue["code"]}"""
) )
decisions["descr_decisions_rcue"] = ", ".join(titres_rcues) decisions["descr_decisions_rcue"] = ", ".join(titres_rcues)
decisions["descr_decisions_niveaux"] = ( decisions["descr_decisions_niveaux"] = (

View File

@ -35,6 +35,7 @@
.niveau_vide { .niveau_vide {
background-color: rgb(195, 195, 195) !important; background-color: rgb(195, 195, 195) !important;
position: relative;
} }
.but_annee>* { .but_annee>* {
@ -209,3 +210,7 @@ div.but_doc table tr td.amue {
color: rgb(127, 127, 206); color: rgb(127, 127, 206);
font-size: 90%; font-size: 90%;
} }
.but_niveau_rcue .scoplement {
font-weight: normal;
}

View File

@ -630,7 +630,8 @@ def index_html():
réfèrent.</p> réfèrent.</p>
<ul> <ul>
<li><a class="stdlink" href="formation_create" id="link-create-formation">Créer une formation</a> <li><a class="stdlink" href="formation_create" id="link-create-formation">Créer une
formation</a>
</li> </li>
<li><a class="stdlink" href="formation_import_xml_form">Importer une formation (xml)</a> <li><a class="stdlink" href="formation_import_xml_form">Importer une formation (xml)</a>
</li> </li>
@ -656,8 +657,7 @@ def index_html():
}">Liste des référentiels chargés</a> }">Liste des référentiels chargés</a>
</li> </li>
</ul> </ul>
"""
"""
) )
H.append(html_sco_header.sco_footer()) H.append(html_sco_header.sco_footer())
@ -855,7 +855,7 @@ def formsemestre_change_lock(formsemestre_id, dialog_confirmed=False):
else: else:
msg = "verrouillage" msg = "verrouillage"
return scu.confirm_dialog( return scu.confirm_dialog(
"<h2>Confirmer le %s du semestre ?</h2>" % msg, f"<h2>Confirmer le {msg} du semestre ?</h2>",
helpmsg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées. helpmsg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées.
Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment
(par son responsable ou un administrateur). (par son responsable ou un administrateur).
@ -863,7 +863,11 @@ def formsemestre_change_lock(formsemestre_id, dialog_confirmed=False):
Le programme d'une formation qui a un semestre verrouillé ne peut plus être modifié. Le programme d'une formation qui a un semestre verrouillé ne peut plus être modifié.
""", """,
dest_url="", dest_url="",
cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id, cancel_url=url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
),
parameters={"formsemestre_id": formsemestre_id}, parameters={"formsemestre_id": formsemestre_id},
) )
@ -940,21 +944,24 @@ def edit_enseignants_form(moduleimpl_id):
H.append( H.append(
f""" f"""
<li>{nom} (<a class="stdlink" href="{ <li>{nom} (<a class="stdlink" href="{
url_for('notes.edit_enseignants_form_delete', scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id, ens_id=ens["ens_id"]) url_for('notes.edit_enseignants_form_delete',
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id,
ens_id=ens["ens_id"])
}">supprimer</a>) }">supprimer</a>)
</li>""" </li>"""
) )
H.append("</ul>") H.append("</ul>")
F = """<p class="help">Les enseignants d'un module ont le droit de F = f"""<p class="help">Les enseignants d'un module ont le droit de
saisir et modifier toutes les notes des évaluations de ce module. saisir et modifier toutes les notes des évaluations de ce module.
</p> </p>
<p class="help">Pour changer le responsable du module, passez par la <p class="help">Pour changer le responsable du module, passez par la
page "<a class="stdlink" href="formsemestre_editwithmodules?formation_id=%s&formsemestre_id=%s">Modification du semestre</a>", accessible uniquement au responsable de la formation (chef de département) page "<a class="stdlink" href="{
url_for("notes.formsemestre_editwithmodules", scodoc_dept=g.scodoc_dept,
formation_id=sem["formation_id"], formsemestre_id=M["formsemestre_id"])
}">Modification du semestre</a>",
accessible uniquement au responsable de la formation (chef de département)
</p> </p>
""" % ( """
sem["formation_id"],
M["formsemestre_id"],
)
modform = [ modform = [
("moduleimpl_id", {"input_type": "hidden"}), ("moduleimpl_id", {"input_type": "hidden"}),
@ -1009,14 +1016,18 @@ def edit_enseignants_form(moduleimpl_id):
or ens_id == M["responsable_id"] or ens_id == M["responsable_id"]
): ):
H.append( H.append(
'<p class="help">Enseignant %s déjà dans la liste !</p>' % ens_id f"""<p class="help">Enseignant {ens_id} déjà dans la liste !</p>"""
) )
else: else:
sco_moduleimpl.do_ens_create( sco_moduleimpl.do_ens_create(
{"moduleimpl_id": moduleimpl_id, "ens_id": ens_id} {"moduleimpl_id": moduleimpl_id, "ens_id": ens_id}
) )
return flask.redirect( return flask.redirect(
"edit_enseignants_form?moduleimpl_id=%s" % moduleimpl_id url_for(
"notes.edit_enseignants_form",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=moduleimpl_id,
)
) )
return header + "\n".join(H) + tf[1] + F + footer return header + "\n".join(H) + tf[1] + F + footer
@ -1448,7 +1459,7 @@ def edit_enseignants_form_delete(moduleimpl_id, ens_id: int):
ok = True ok = True
break break
if not ok: if not ok:
raise ScoValueError("invalid ens_id (%s)" % ens_id) raise ScoValueError(f"invalid ens_id ({ens_id})")
ndb.SimpleQuery( ndb.SimpleQuery(
"""DELETE FROM notes_modules_enseignants """DELETE FROM notes_modules_enseignants
WHERE moduleimpl_id = %(moduleimpl_id)s WHERE moduleimpl_id = %(moduleimpl_id)s
@ -1456,7 +1467,13 @@ def edit_enseignants_form_delete(moduleimpl_id, ens_id: int):
""", """,
{"moduleimpl_id": moduleimpl_id, "ens_id": ens_id}, {"moduleimpl_id": moduleimpl_id, "ens_id": ens_id},
) )
return flask.redirect("edit_enseignants_form?moduleimpl_id=%s" % moduleimpl_id) return flask.redirect(
url_for(
"notes.edit_enseignants_form",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=moduleimpl_id,
)
)
# --- Gestion des inscriptions aux semestres # --- Gestion des inscriptions aux semestres
@ -2403,10 +2420,16 @@ def formsemestre_validation_but(
warning = "" warning = ""
if len(deca.niveaux_competences) != len(deca.decisions_rcue_by_niveau): if len(deca.niveaux_competences) != len(deca.decisions_rcue_by_niveau):
warning += f"""<div class="warning">Attention: {len(deca.niveaux_competences)} if deca.a_cheval:
niveaux mais {len(deca.decisions_rcue_by_niveau)} regroupements RCUE.</div>""" warning += f"""<div class="warning">Attention: regroupements RCUE
if deca.parcour is None: entre années (redoublement).</div>"""
warning += """<div class="warning">L'étudiant n'est pas inscrit à un parcours.</div>""" else:
warning += f"""<div class="warning">Attention: {len(deca.niveaux_competences)}
niveaux mais {len(deca.decisions_rcue_by_niveau)} regroupements RCUE.</div>"""
if (deca.parcour is None) and len(formsemestre.parcours) > 0:
warning += (
"""<div class="warning">L'étudiant n'est pas inscrit à un parcours.</div>"""
)
if deca.formsemestre_impair and deca.inscription_etat_impair != scu.INSCRIT: if deca.formsemestre_impair and deca.inscription_etat_impair != scu.INSCRIT:
etat_ins = scu.ETATS_INSCRIPTION.get(deca.inscription_etat_impair, "inconnu?") etat_ins = scu.ETATS_INSCRIPTION.get(deca.inscription_etat_impair, "inconnu?")
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_impair.semestre_id}""" warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_impair.semestre_id}"""