diff --git a/app/but/jury_but.py b/app/but/jury_but.py index 244989ed..af1eed4f 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -630,19 +630,38 @@ class DecisionsProposeesAnnee(DecisionsProposees): d[dec_rcue.rcue.ue_2.id] = dec_rcue return d - def next_annee_semestre_id(self, code: str) -> int: - """L'indice du semestre dans lequel l'étudiant est autorisé à - poursuivre l'année suivante. None si aucun.""" - if self.formsemestre_pair is None: - return None # seulement sur année - if code == RED: - return self.formsemestre_pair.semestre_id - 1 - elif ( - code in sco_codes.BUT_CODES_PASSAGE + def next_semestre_ids(self, code: str) -> set[int]: + """Les indices des semestres dans lequels l'étudiant est autorisé + à poursuivre après le semestre courant. + """ + ids = set() + # La poursuite d'études dans un semestre pair d’une même année + # est de droit pour tout étudiant: + if (self.formsemestre.semestre_id % 2) and sco_codes.ParcoursBUT.NB_SEM: + ids.add(self.formsemestre.semestre_id + 1) + + # La poursuite d’études dans un semestre impair est possible si + # et seulement si l’étudiant a obtenu : + # - la moyenne à plus de la moitié des regroupements cohérents d’UE ; + # - et une moyenne égale ou supérieure à 8 sur 20 à chaque RCUE. + # + # La condition a paru trop stricte à de nombreux collègues. + # ScoDoc ne contraint donc pas à la respecter strictement. + # Si le code est dans BUT_CODES_PASSAGE (ADM, ADJ, PASD, PAS1NCI, ATJ), + # autorise à passer dans le semestre suivant + if ( + self.jury_annuel + and code in sco_codes.BUT_CODES_PASSAGE and self.formsemestre_pair.semestre_id < sco_codes.ParcoursBUT.NB_SEM ): - return self.formsemestre_pair.semestre_id + 1 - return None + ids.add(self.formsemestre.semestre_id + 1) + + if code == RED: + ids.add( + self.formsemestre.semestre_id - (self.formsemestre.semestre_id + 1) % 2 + ) + + return ids def record_form(self, form: dict): """Enregistre les codes de jury en base @@ -704,47 +723,43 @@ class DecisionsProposeesAnnee(DecisionsProposees): raise ScoValueError( f"code annee {html.escape(code)} invalide pour formsemestre {html.escape(self.formsemestre)}" ) - if code == self.code_valide or (self.code_valide is not None and no_overwrite): - self.recorded = True - return False # no change - if self.validation: - db.session.delete(self.validation) - db.session.commit() - if code is None: - self.validation = None - else: - self.validation = ApcValidationAnnee( - etudid=self.etud.id, - formsemestre=self.formsemestre_impair, - ordre=self.annee_but, - annee_scolaire=self.annee_scolaire(), - code=code, - ) - db.session.add(self.validation) - db.session.commit() - log(f"Recording {self}: {code}") - Scolog.logdb( - method="jury_but", - etudid=self.etud.id, - msg=f"Validation année BUT{self.annee_but}: {code}", - ) + + if code != self.code_valide and (self.code_valide is None or not no_overwrite): + # Enregistrement du code annuel BUT + if self.validation: + db.session.delete(self.validation) + db.session.commit() + if code is None: + self.validation = None + else: + self.validation = ApcValidationAnnee( + etudid=self.etud.id, + formsemestre=self.formsemestre_impair, + ordre=self.annee_but, + annee_scolaire=self.annee_scolaire(), + code=code, + ) + db.session.add(self.validation) + db.session.commit() + log(f"Recording {self}: {code}") + Scolog.logdb( + method="jury_but", + etudid=self.etud.id, + msg=f"Validation année BUT{self.annee_but}: {code}", + ) # --- Autorisation d'inscription dans semestre suivant ? - if self.formsemestre_pair is not None: - if code is None: - ScolarAutorisationInscription.delete_autorisation_etud( - etudid=self.etud.id, - origin_formsemestre_id=self.formsemestre_pair.id, - ) - else: - next_semestre_id = self.next_annee_semestre_id(code) - if next_semestre_id is not None: - ScolarAutorisationInscription.autorise_etud( - self.etud.id, - self.formsemestre_pair.formation.formation_code, - self.formsemestre_pair.id, - next_semestre_id, - ) + ScolarAutorisationInscription.delete_autorisation_etud( + etudid=self.etud.id, + origin_formsemestre_id=self.formsemestre.id, + ) + for next_semestre_id in self.next_semestre_ids(code): + ScolarAutorisationInscription.autorise_etud( + self.etud.id, + self.formsemestre.formation.formation_code, + self.formsemestre.id, + next_semestre_id, + ) db.session.commit() self.recorded = True @@ -872,18 +887,18 @@ class DecisionsProposeesAnnee(DecisionsProposees): self.invalidate_formsemestre_cache() def get_autorisations_passage(self) -> list[int]: - """Les liste des indices de semestres auxquels on est autorisé à - s'inscrire depuis cette année""" - formsemestre = self.formsemestre_pair or self.formsemestre_impair - if not formsemestre: - return [] - return [ - a.semestre_id - for a in ScolarAutorisationInscription.query.filter_by( - etudid=self.etud.id, - origin_formsemestre_id=formsemestre.id, - ) - ] + """Liste des indices de semestres auxquels on est autorisé à + s'inscrire depuis le semestre courant. + """ + return sorted( + [ + a.semestre_id + for a in ScolarAutorisationInscription.query.filter_by( + etudid=self.etud.id, + origin_formsemestre_id=self.formsemestre.id, + ) + ] + ) def descr_niveaux_validation(self, line_sep: str = "\n") -> str: """Description textuelle des niveaux validés (enregistrés) diff --git a/app/but/jury_but_view.py b/app/but/jury_but_view.py index acb6b695..a58f41e2 100644 --- a/app/but/jury_but_view.py +++ b/app/but/jury_but_view.py @@ -11,7 +11,7 @@ import re import numpy as np import flask -from flask import flash, url_for +from flask import flash, render_template, url_for from flask import g, request from app import db @@ -32,8 +32,10 @@ from app.models import ( ScolarAutorisationInscription, ScolarFormSemestreValidation, ) +from app.models.config import ScoDocSiteConfig from app.scodoc import html_sco_header from app.scodoc.sco_exceptions import ScoValueError +from app.scodoc import sco_preferences from app.scodoc import sco_utils as scu @@ -43,21 +45,20 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: """ H = [] - H.append("""
""") - H.append( - f""" -
- Décision de jury pour l'année : { - _gen_but_select("code_annee", deca.codes, deca.code_valide, - disabled=True, klass="manual") - } - ({deca.code_valide or 'non'} enregistrée) + if deca.jury_annuel: + H.append( + f""" +
+
+ Décision de jury pour l'année : { + _gen_but_select("code_annee", deca.codes, deca.code_valide, + disabled=True, klass="manual") + } + ({deca.code_valide or 'non'} enregistrée) +
""" - ) - div_explanation = f"""
{deca.explanation}
""" - - H.append("""
""") + ) formsemestre_1 = deca.formsemestre_impair formsemestre_2 = deca.formsemestre_pair @@ -74,7 +75,7 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}
- {div_explanation} +
{deca.explanation}
{"S" +str(formsemestre_1.semestre_id) @@ -285,7 +286,7 @@ def jury_but_semestriel( read_only: bool, navigation_div: str = "", ) -> str: - """Formulaire saisie décision d'UE d'un semestre BUT isolé (pas jury annuel)""" + """Page: 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) @@ -374,20 +375,20 @@ def jury_but_semestriel( f"""
-
-
-
Jury BUT S{formsemestre.id} - - Parcours {(parcour.libelle if parcour else False) or "non spécifié"} +
+
+
Jury BUT S{formsemestre.id} + - Parcours {(parcour.libelle if parcour else False) or "non spécifié"} +
+
{etud.nomprenom}
+
+
-
{etud.nomprenom}
-
- -
-

Jury sur un semestre BUT isolé (ne concerne que les UEs)

- {warning} +

Jury sur un semestre BUT isolé (ne concerne que les UEs)

+ {warning}
@@ -450,24 +451,37 @@ def jury_but_semestriel( ) H.append("
") # but_annee + div_autorisations_passage = ( + f""" +
+ Autorisé à passer en : + { ", ".join( ["S" + str(a.semestre_id or '') for a in autorisations_passage ] )} +
+ """ + if autorisations_passage + else """
pas d'autorisations de passage enregistrées.
""" + ) + H.append(div_autorisations_passage) + if read_only: H.append( - """
- Vous n'avez pas la permission de modifier ces décisions. - Les champs entourés en vert sont enregistrés.
""" + f"""
+ {"Vous n'avez pas la permission de modifier ces décisions." + if formsemestre.etat + else "Semestre verrouillé."} + Les champs entourés en vert sont enregistrés. +
+ """ ) else: if formsemestre.semestre_id < formsemestre.formation.get_parcours().NB_SEM: H.append( f"""
- - autoriser à passer dans le semestre S{formsemestre.semestre_id+1} - {("(autorisations enregistrées: " + ' '.join( - 'S' + str(a.semestre_id or '') for a in autorisations_passage) + ")" - ) if autorisations_passage else ""} - + + autoriser à passer dans le semestre S{formsemestre.semestre_id+1} +
""" ) @@ -481,7 +495,19 @@ def jury_but_semestriel(
""" ) - H.append(navigation_div) + + H.append(navigation_div) + H.append("
") + H.append( + render_template( + "but/documentation_codes_jury.html", + nom_univ=f"""Export {sco_preferences.get_preference("InstituteName") + or sco_preferences.get_preference("UnivName") + or "Apogée"}""", + codes=ScoDocSiteConfig.get_codes_apo_dict(), + ) + ) + return "\n".join(H) diff --git a/app/comp/res_common.py b/app/comp/res_common.py index 820675fa..9caf7283 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -740,7 +740,8 @@ class ResultatsSemestre(ResultatsCache): f"""{"saisir" if not jury_code_sem else "modifier"} décision""", + }">{("saisir" if not jury_code_sem else "modifier") + if self.formsemestre.etat else "voir"} décisions""", "col_jury_link", ) diff --git a/app/models/formations.py b/app/models/formations.py index 36e35647..986ef7e7 100644 --- a/app/models/formations.py +++ b/app/models/formations.py @@ -55,7 +55,8 @@ class Formation(db.Model): modules = db.relationship("Module", lazy="dynamic", backref="formation") def __repr__(self): - return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme!r}')>" + return f"""<{self.__class__.__name__}(id={self.id}, dept_id={ + self.dept_id}, acronyme={self.acronyme!r}, version={self.version})>""" def to_html(self) -> str: "titre complet pour affichage" diff --git a/app/models/ues.py b/app/models/ues.py index 94954b93..596e0bef 100644 --- a/app/models/ues.py +++ b/app/models/ues.py @@ -111,6 +111,7 @@ class UniteEns(db.Model): e["ects"] = e["ects"] e["coefficient"] = e["coefficient"] if e["coefficient"] else 0.0 e["code_apogee"] = e["code_apogee"] or "" # pas de None + e["parcour"] = self.parcour.to_dict() if self.parcour else None if with_module_ue_coefs: if convert_objects: e["module_ue_coefs"] = [ diff --git a/app/scodoc/sco_edit_apc.py b/app/scodoc/sco_edit_apc.py index 1bea3b71..b904bd5b 100644 --- a/app/scodoc/sco_edit_apc.py +++ b/app/scodoc/sco_edit_apc.py @@ -99,7 +99,7 @@ def html_edit_formation_apc( H = [ render_template( - "pn/form_ues.html", + "pn/form_ues.j2", formation=formation, semestre_ids=semestre_ids, editable=editable, @@ -122,7 +122,7 @@ def html_edit_formation_apc( ).first() H += [ render_template( - "pn/form_mods.html", + "pn/form_mods.j2", formation=formation, titre=f"Ressources du S{semestre_idx}", create_element_msg="créer une nouvelle ressource", @@ -138,7 +138,7 @@ def html_edit_formation_apc( if ues_by_sem[semestre_idx].count() > 0 else "", render_template( - "pn/form_mods.html", + "pn/form_mods.j2", formation=formation, titre=f"Situations d'Apprentissage et d'Évaluation (SAÉs) S{semestre_idx}", create_element_msg="créer une nouvelle SAÉ", @@ -154,7 +154,7 @@ def html_edit_formation_apc( if ues_by_sem[semestre_idx].count() > 0 else "", render_template( - "pn/form_mods.html", + "pn/form_mods.j2", formation=formation, titre=f"Autres modules (non BUT) du S{semestre_idx}", create_element_msg="créer un nouveau module", @@ -196,7 +196,7 @@ def html_ue_infos(ue): and ue.matieres.count() == 0 ) return render_template( - "pn/ue_infos.html", + "pn/ue_infos.j2", titre=f"UE {ue.acronyme} {ue.titre}", ue=ue, formsemestres=formsemestres, diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 5d269875..906493a2 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -723,7 +723,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list "libjs/jQuery-tagEditor/jquery.caret.min.js", "js/module_tag_editor.js", ], - page_title=f"Programme {formation.acronyme}", + page_title=f"Programme {formation.acronyme} v{formation.version}", ), f"""

{formation.to_html()} {lockicon}

@@ -765,7 +765,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours); # Description de la formation H.append( render_template( - "pn/form_descr.html", + "pn/form_descr.j2", formation=formation, parcours=parcours, editable=editable, @@ -913,8 +913,12 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
  • Export XML de la formation - (permet de la sauvegarder pour l'échanger avec un autre site) + }">Export XML de la formation ou + sans codes Apogée + (permet de l'enregistrer pour l'échanger avec un autre site)
  • {sem['mois_debut']} {formsemestre.titre_annee()}{parcours_name} + title="Bulletin de notes">{formsemestre.titre_annee()}{parcours_name} """ ) + if nt.is_apc: + H.append( + f"""jury""" + ) + H.append("""""") + if nt.is_apc: H.append('BUT') elif decision_sem: diff --git a/app/scodoc/sco_inscr_passage.py b/app/scodoc/sco_inscr_passage.py index d9a562f4..58b5a0d9 100644 --- a/app/scodoc/sco_inscr_passage.py +++ b/app/scodoc/sco_inscr_passage.py @@ -39,21 +39,22 @@ from app import log from app.models import FormSemestre from app.scodoc.gen_tables import GenTable from app.scodoc import html_sco_header +from app.scodoc import sco_cache from app.scodoc import sco_codes_parcours -from app.scodoc import sco_preferences -from app.scodoc import sco_pvjury +from app.scodoc import sco_etud +from app.scodoc import sco_formations from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions -from app.scodoc import sco_formations from app.scodoc import sco_groups -from app.scodoc import sco_etud +from app.scodoc import sco_preferences +from app.scodoc import sco_pvjury from app.scodoc.sco_exceptions import ScoValueError def list_authorized_etuds_by_sem(sem, delai=274, ignore_jury=False): """Liste des etudiants autorisés à s'inscrire dans sem. delai = nb de jours max entre la date de l'autorisation et celle de debut du semestre cible. - ignore_jury: si vrai, considère tous les étudiants comem autorisés, même + ignore_jury: si vrai, considère tous les étudiants comme autorisés, même s'ils n'ont pas de décision de jury. """ src_sems = list_source_sems(sem, delai=delai) @@ -276,8 +277,8 @@ def formsemestre_inscr_passage( submitted=False, dialog_confirmed=False, ignore_jury=False, -): - """Form. pour inscription des etudiants d'un semestre dans un autre +) -> str: + """Page Form. pour inscription des etudiants d'un semestre dans un autre (donné par formsemestre_id). Permet de selectionner parmi les etudiants autorisés à s'inscrire. Principe: @@ -285,8 +286,8 @@ def formsemestre_inscr_passage( - afficher chaque semestre "boites" avec cases à cocher - si l'étudiant est déjà inscrit, le signaler (gras, nom de groupes): il peut être désinscrit - on peut choisir les groupes TD, TP, TA - - seuls les etudiants non inscrits changent (de groupe) - - les etudiants inscrit qui se trouvent décochés sont désinscrits + - seuls les étudiants non inscrits changent (de groupe) + - les étudiants inscrit qui se trouvent décochés sont désinscrits - Confirmation: indiquer les étudiants inscrits et ceux désinscrits, le total courant. """ @@ -326,11 +327,9 @@ def formsemestre_inscr_passage( a_desinscrire = inscrits_set - etuds_set else: a_inscrire = a_desinscrire = [] - # log('formsemestre_inscr_passage: a_inscrire=%s' % str(a_inscrire) ) - # log('formsemestre_inscr_passage: a_desinscrire=%s' % str(a_desinscrire) ) if not submitted: - H += build_page( + H += _build_page( sem, auth_etuds_by_sem, inscrits, @@ -343,7 +342,7 @@ def formsemestre_inscr_passage( if not dialog_confirmed: # Confirmation if a_inscrire: - H.append("

    Etudiants à inscrire

      ") + H.append("

      Étudiants à inscrire

        ") for etud in set_to_sorted_etud_list(a_inscrire): H.append("
      1. %(nomprenom)s
      2. " % etud) H.append("
      ") @@ -354,7 +353,7 @@ def formsemestre_inscr_passage( H.append('
    1. %(nomprenom)s
    2. ' % etud) H.append("") if a_desinscrire: - H.append("

      Etudiants à désinscrire

        ") + H.append("

        Étudiants à désinscrire

          ") for etudid in a_desinscrire: H.append( '
        1. %(nomprenom)s
        2. ' @@ -384,21 +383,29 @@ def formsemestre_inscr_passage( ) ) else: - # Inscription des étudiants au nouveau semestre: - do_inscrit( - sem, - a_inscrire, - inscrit_groupes=inscrit_groupes, - ) - - # Desincriptions: - do_desinscrit(sem, a_desinscrire) + with sco_cache.DeferredSemCacheManager(): + # Inscription des étudiants au nouveau semestre: + do_inscrit( + sem, + a_inscrire, + inscrit_groupes=inscrit_groupes, + ) + # Désinscriptions: + do_desinscrit(sem, a_desinscrire) H.append( - """

          Opération effectuée

          -