diff --git a/app/but/jury_but_view.py b/app/but/jury_but_view.py index f6437ad92..a3c0dc0a0 100644 --- a/app/but/jury_but_view.py +++ b/app/but/jury_but_view.py @@ -8,6 +8,7 @@ """ import re +import numpy as np import flask from flask import flash, url_for @@ -15,10 +16,15 @@ 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, + DecisionsProposeesRCUE, + DecisionsProposeesUE, +) from app.comp import res_sem from app.comp.res_but import ResultatsSemestreBUT from app.models import ( + ApcNiveau, FormSemestre, FormSemestreInscription, Identite, @@ -59,19 +65,9 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: """ ) else: - H.append("""
Pas de décision annuelle (sem. impair)
""") + H.append("""
Pas de décision annuelle (sem. impair).
""") H.append("""""") - 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_2 = deca.formsemestre_pair # 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 H.append( f""" -
Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}
+
Niveaux de compétences et unités d'enseignement du BUT{ + deca.annee_but}
-
S{formsemestre_1.semestre_id +
{"S" +str(formsemestre_1.semestre_id) if formsemestre_1 else "-"} - {formsemestre_1.annee_scolaire_str() if formsemestre_1 else ""} + {formsemestre_1.annee_scolaire_str() + if formsemestre_1 else ""}
-
S{formsemestre_2.semestre_id +
{"S"+str(formsemestre_2.semestre_id) if formsemestre_2 else "-"} - {formsemestre_2.annee_scolaire_str() if formsemestre_2 else ""} + {formsemestre_2.annee_scolaire_str() + if formsemestre_2 else ""}
RCUE
""" @@ -109,7 +108,8 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: ue_impair = ues[0] if ues else None ues = [ue for ue in deca.ues_pair if ue.niveau_competence.id == niveau.id] 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 = [ ( ue_impair, @@ -137,24 +137,9 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: else: H.append("""
""") - # RCUE - if dec_rcue is None: - H.append("""
""") - else: - H.append( - f"""
-
{scu.fmt_note(dec_rcue.rcue.moy_rcue)}
-
{ - _gen_but_select("code_rcue_"+str(niveau.id), - dec_rcue.codes, - dec_rcue.code_valide, - disabled=True, klass="manual" - ) - }
-
""" - ) + # Colonne RCUE + H.append(_gen_but_rcue(dec_rcue, niveau)) + H.append("
") # but_annee return "\n".join(H) @@ -169,9 +154,9 @@ def _gen_but_select( "Le menu html select avec les codes" # if disabled: # mauvaise idée car le disabled est traité en JS # return f"""
{code_valide}
""" - h = "\n".join( + options_htm = "\n".join( [ - f"""""" @@ -182,7 +167,7 @@ def _gen_but_select( class="but_code {klass}" onchange="change_menu_code(this);" {"disabled" if disabled else ""} - >{h} + >{options_htm} """ @@ -191,18 +176,21 @@ def _gen_but_niveau_ue( dec_ue: DecisionsProposeesUE, disabled: bool = False, annee_prec: bool = False, -): +) -> str: if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]: moy_ue_str = f"""{ scu.fmt_note(dec_ue.moy_ue_with_cap)}""" scoplement = f"""
- UE {ue.acronyme} capitalisée le - {dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")} - + UE {ue.acronyme} capitalisée + le {dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")} +
-
UE en cours avec moyenne - {scu.fmt_note(dec_ue.moy_ue)} +
UE en cours + { "sans notes" if np.isnan(dec_ue.moy_ue) + else + ("avec moyenne" + scu.fmt_note(dec_ue.moy_ue)) + }
""" @@ -229,6 +217,43 @@ def _gen_but_niveau_ue(
""" +def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str: + if dec_rcue is None: + return """ +
+
+
Pas de RCUE (UE non capitalisée ?)
+
+ """ + + scoplement = ( + f"""
{ + dec_rcue.validation.to_html() + }
""" + if dec_rcue.validation + else "" + ) + + return f""" +
+
+
{scu.fmt_note(dec_rcue.rcue.moy_rcue)}
+ {scoplement} +
+
+
{_gen_but_select("code_rcue_"+str(niveau.id), + dec_rcue.codes, + dec_rcue.code_valide, + disabled=True, klass="manual" + )} +
+
+
+ """ + + def jury_but_semestriel( formsemestre: FormSemestre, etud: Identite, @@ -265,9 +290,9 @@ def jury_but_semestriel( 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)) + code_match = re.match(r"^code_ue_(\d+)$", key) + if code_match: + ue_id = int(code_match.group(1)) dec_ue = decisions_ues.get(ue_id) if not dec_ue: raise ScoValueError(f"UE invalide ue_id={ue_id}") @@ -285,7 +310,8 @@ def jury_but_semestriel( ) db.session.commit() 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: if est_autorise_a_passer: @@ -442,11 +468,10 @@ def infos_fiche_etud_html(etudid: int) -> str: # temporaire quick & dirty: affiche le dernier try: deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1]) - if True: # len(deca.rcues_annee) > 0: - return f"""
+ return f"""
{show_etud(deca, read_only=True)}
- """ + """ except ScoValueError: pass diff --git a/app/models/but_validations.py b/app/models/but_validations.py index 298cb98be..7e729a961 100644 --- a/app/models/but_validations.py +++ b/app/models/but_validations.py @@ -2,19 +2,17 @@ """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 app import db +import flask_sqlalchemy +from app import db from app.models import CODE_STR_LEN from app.models.but_refcomp import ApcNiveau from app.models.etudiants import Identite -from app.models.ues import UniteEns from app.models.formations import Formation 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_utils as scu @@ -63,7 +61,14 @@ class ApcValidationRCUE(db.Model): self.ue1}/{self.ue2}:{self.code!r}>""" 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}: + {self.code} + enregistrée le {self.date.strftime("%d/%m/%Y")}""" def niveau(self) -> ApcNiveau: """Le niveau de compétence associé à cet RCUE.""" @@ -96,10 +101,6 @@ class RegroupementCoherentUE: dec_ue_2: "DecisionsProposeesUE", 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_2 = dec_ue_2.ue # 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") 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): 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"]}""") else: titres_rcues.append( - f"""{niveau["competence"]["titre"]} {niveau["ordre"]}: {dec_rcue["code"]}""" + f"""{niveau["competence"]["titre"]} {niveau["ordre"]}: { + dec_rcue["code"]}""" ) decisions["descr_decisions_rcue"] = ", ".join(titres_rcues) decisions["descr_decisions_niveaux"] = ( diff --git a/app/static/css/jury_but.css b/app/static/css/jury_but.css index 2387754d3..8efa4a614 100644 --- a/app/static/css/jury_but.css +++ b/app/static/css/jury_but.css @@ -35,6 +35,7 @@ .niveau_vide { background-color: rgb(195, 195, 195) !important; + position: relative; } .but_annee>* { @@ -208,4 +209,8 @@ div.but_doc table tbody tr:nth-child(odd) { div.but_doc table tr td.amue { color: rgb(127, 127, 206); font-size: 90%; +} + +.but_niveau_rcue .scoplement { + font-weight: normal; } \ No newline at end of file diff --git a/app/views/notes.py b/app/views/notes.py index 6ba9bf92c..275216167 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -624,13 +624,14 @@ def index_html(): if editable: H.append( f""" -

Une "formation" est un programme pédagogique structuré +

Une "formation" est un programme pédagogique structuré en UE, matières et modules. Chaque semestre se réfère à une formation. - La modification d'une formation affecte tous les semestres qui s'y + La modification d'une formation affecte tous les semestres qui s'y réfèrent.

- - """ + """ ) H.append(html_sco_header.sco_footer()) @@ -855,7 +855,7 @@ def formsemestre_change_lock(formsemestre_id, dialog_confirmed=False): else: msg = "verrouillage" return scu.confirm_dialog( - "

Confirmer le %s du semestre ?

" % msg, + f"

Confirmer le {msg} du semestre ?

", helpmsg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées. Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment (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é. """, 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}, ) @@ -940,21 +944,24 @@ def edit_enseignants_form(moduleimpl_id): H.append( f"""
  • {nom} (supprimer)
  • """ ) H.append("") - F = """

    Les enseignants d'un module ont le droit de + F = f"""

    Les enseignants d'un module ont le droit de saisir et modifier toutes les notes des évaluations de ce module.

    Pour changer le responsable du module, passez par la - page "Modification du semestre", accessible uniquement au responsable de la formation (chef de département) + page "Modification du semestre", + accessible uniquement au responsable de la formation (chef de département)

    - """ % ( - sem["formation_id"], - M["formsemestre_id"], - ) + """ modform = [ ("moduleimpl_id", {"input_type": "hidden"}), @@ -1009,14 +1016,18 @@ def edit_enseignants_form(moduleimpl_id): or ens_id == M["responsable_id"] ): H.append( - '

    Enseignant %s déjà dans la liste !

    ' % ens_id + f"""

    Enseignant {ens_id} déjà dans la liste !

    """ ) else: sco_moduleimpl.do_ens_create( {"moduleimpl_id": moduleimpl_id, "ens_id": ens_id} ) 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 @@ -1372,12 +1383,12 @@ def formsemestre_enseignants_list(formsemestre_id, format="html"): if not u: continue cursor.execute( - """SELECT * FROM scolog L, notes_formsemestre_inscription I - WHERE method = 'AddAbsence' - and authenticated_user = %(authenticated_user)s - and L.etudid = I.etudid - and I.formsemestre_id = %(formsemestre_id)s - and date > %(date_debut)s + """SELECT * FROM scolog L, notes_formsemestre_inscription I + WHERE method = 'AddAbsence' + and authenticated_user = %(authenticated_user)s + and L.etudid = I.etudid + and I.formsemestre_id = %(formsemestre_id)s + and date > %(date_debut)s and date < %(date_fin)s """, { @@ -1448,15 +1459,21 @@ def edit_enseignants_form_delete(moduleimpl_id, ens_id: int): ok = True break if not ok: - raise ScoValueError("invalid ens_id (%s)" % ens_id) + raise ScoValueError(f"invalid ens_id ({ens_id})") ndb.SimpleQuery( """DELETE FROM notes_modules_enseignants - WHERE moduleimpl_id = %(moduleimpl_id)s + WHERE moduleimpl_id = %(moduleimpl_id)s AND ens_id = %(ens_id)s """, {"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 @@ -2403,10 +2420,16 @@ def formsemestre_validation_but( warning = "" if len(deca.niveaux_competences) != len(deca.decisions_rcue_by_niveau): - warning += f"""
    Attention: {len(deca.niveaux_competences)} - niveaux mais {len(deca.decisions_rcue_by_niveau)} regroupements RCUE.
    """ - if deca.parcour is None: - warning += """
    L'étudiant n'est pas inscrit à un parcours.
    """ + if deca.a_cheval: + warning += f"""
    Attention: regroupements RCUE + entre années (redoublement).
    """ + else: + warning += f"""
    Attention: {len(deca.niveaux_competences)} + niveaux mais {len(deca.decisions_rcue_by_niveau)} regroupements RCUE.
    """ + if (deca.parcour is None) and len(formsemestre.parcours) > 0: + warning += ( + """
    L'étudiant n'est pas inscrit à un parcours.
    """ + ) if deca.formsemestre_impair and deca.inscription_etat_impair != scu.INSCRIT: etat_ins = scu.ETATS_INSCRIPTION.get(deca.inscription_etat_impair, "inconnu?") warning += f"""
    {etat_ins} en S{deca.formsemestre_impair.semestre_id}""" @@ -2764,7 +2787,7 @@ def formsemestre_jury_but_erase( return render_template( "confirm_dialog.html", title=f"Effacer les validations de jury de {etud.nomprenom} ?", - explanation=f"""Les validations d'UE et autorisations de passage + 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.""", @@ -3148,7 +3171,7 @@ def check_sem_integrity(formsemestre_id, fix=False): else: H.append( """ -

    Problème détecté réparable: +

    Problème détecté réparable: réparer maintenant

    """ % (formsemestre_id,)