diff --git a/app/but/bulletin_but_xml_compat.py b/app/but/bulletin_but_xml_compat.py index 0b8c7b76e..07522f80c 100644 --- a/app/but/bulletin_but_xml_compat.py +++ b/app/but/bulletin_but_xml_compat.py @@ -38,14 +38,11 @@ import datetime from xml.etree import ElementTree from xml.etree.ElementTree import Element -from app import log +from app import db, log from app.but import bulletin_but -from app.models import BulAppreciations, FormSemestre, Identite +from app.models import BulAppreciations, FormSemestre, Identite, UniteEns import app.scodoc.sco_utils as scu -import app.scodoc.notesdb as ndb from app.scodoc import codes_cursus -from app.scodoc import sco_edit_ue -from app.scodoc import sco_etud from app.scodoc import sco_photos from app.scodoc import sco_preferences from app.scodoc import sco_xml @@ -202,12 +199,12 @@ def bulletin_but_xml_compat( if e.visibulletin or version == "long": x_eval = Element( "evaluation", - date_debut=e.date_debut.isoformat() - if e.date_debut - else "", - date_fin=e.date_fin.isoformat() - if e.date_debut - else "", + date_debut=( + e.date_debut.isoformat() if e.date_debut else "" + ), + date_fin=( + e.date_fin.isoformat() if e.date_debut else "" + ), coefficient=str(e.coefficient), # pas les poids en XML compat evaluation_type=str(e.evaluation_type), @@ -215,9 +212,9 @@ def bulletin_but_xml_compat( # notes envoyées sur 20, ceci juste pour garder trace: note_max_origin=str(e.note_max), # --- deprecated - jour=e.date_debut.isoformat() - if e.date_debut - else "", + jour=( + e.date_debut.isoformat() if e.date_debut else "" + ), heure_debut=e.heure_debut(), heure_fin=e.heure_fin(), ) @@ -294,17 +291,18 @@ def bulletin_but_xml_compat( "decisions_ue" ]: # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): always publish (car utile pour export Apogee) for ue_id in decision["decisions_ue"].keys(): - ue = sco_edit_ue.ue_list({"ue_id": ue_id})[0] - doc.append( - Element( - "decision_ue", - ue_id=str(ue["ue_id"]), - numero=quote_xml_attr(ue["numero"]), - acronyme=quote_xml_attr(ue["acronyme"]), - titre=quote_xml_attr(ue["titre"]), - code=decision["decisions_ue"][ue_id]["code"], + ue = db.session.get(UniteEns, ue_id) + if ue: + doc.append( + Element( + "decision_ue", + ue_id=str(ue.id), + numero=quote_xml_attr(ue.numero), + acronyme=quote_xml_attr(ue.acronyme), + titre=quote_xml_attr(ue.titre or ""), + code=decision["decisions_ue"][ue_id]["code"], + ) ) - ) for aut in decision["autorisations"]: doc.append( diff --git a/app/comp/res_compat.py b/app/comp/res_compat.py index 0e84538fb..765be90c1 100644 --- a/app/comp/res_compat.py +++ b/app/comp/res_compat.py @@ -58,7 +58,6 @@ class NotesTableCompat(ResultatsSemestre): self.moy_moy = "NA" self.moy_gen_rangs_by_group = {} # { group_id : (Series, Series) } self.ue_rangs_by_group = {} # { ue_id : {group_id : (Series, Series)}} - self.expr_diagnostics = "" self.parcours = self.formsemestre.formation.get_cursus() self._modimpls_dict_by_ue = {} # local cache @@ -217,9 +216,9 @@ class NotesTableCompat(ResultatsSemestre): # Rangs / UEs: for ue in ues: group_moys_ue = self.etud_moy_ue[ue.id][group_members] - self.ue_rangs_by_group.setdefault(ue.id, {})[ - group.id - ] = moy_sem.comp_ranks_series(group_moys_ue * mask_inscr) + self.ue_rangs_by_group.setdefault(ue.id, {})[group.id] = ( + moy_sem.comp_ranks_series(group_moys_ue * mask_inscr) + ) def get_etud_rang(self, etudid: int) -> str: """Le rang (classement) de l'étudiant dans le semestre. diff --git a/app/models/evaluations.py b/app/models/evaluations.py index d796381d6..7da2d84cc 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -79,6 +79,7 @@ class Evaluation(db.Model): ): """Create an evaluation. Check permission and all arguments. Ne crée pas les poids vers les UEs. + Add to session, do not commit. """ if not moduleimpl.can_edit_evaluation(current_user): raise AccessDenied( @@ -94,6 +95,8 @@ class Evaluation(db.Model): args["numero"] = cls.get_new_numero(moduleimpl, args["date_debut"]) # evaluation = Evaluation(**args) + db.session.add(evaluation) + db.session.flush() sco_cache.invalidate_formsemestre(formsemestre_id=moduleimpl.formsemestre_id) url = url_for( "notes.moduleimpl_status", @@ -210,9 +213,9 @@ class Evaluation(db.Model): "visibulletin": self.visibulletin, # Deprecated (supprimer avant #sco9.7) "date": self.date_debut.date().isoformat() if self.date_debut else "", - "heure_debut": self.date_debut.time().isoformat() - if self.date_debut - else "", + "heure_debut": ( + self.date_debut.time().isoformat() if self.date_debut else "" + ), "heure_fin": self.date_fin.time().isoformat() if self.date_fin else "", } diff --git a/app/models/modules.py b/app/models/modules.py index 2c3a91311..b0db0e68a 100644 --- a/app/models/modules.py +++ b/app/models/modules.py @@ -1,8 +1,10 @@ """ScoDoc 9 models : Modules """ + from flask import current_app, g from app import db +from app import models from app.models import APO_CODE_STR_LEN from app.models.but_refcomp import ApcParcours, app_critiques_modules, parcours_modules from app.scodoc import sco_utils as scu @@ -11,7 +13,7 @@ from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_utils import ModuleType -class Module(db.Model): +class Module(models.ScoDocModel): """Module""" __tablename__ = "notes_modules" @@ -76,6 +78,28 @@ class Module(db.Model): return f"""""" + @classmethod + def convert_dict_fields(cls, args: dict) -> dict: + """Convert fields in the given dict. No other side effect. + returns: dict to store in model's db. + """ + # s'assure que ects etc est non '' + fs_empty_stored_as_nulls = { + "coefficient", + "ects", + "heures_cours", + "heures_td", + "heures_tp", + } + args_dict = {} + for key, value in args.items(): + if hasattr(cls, key) and not isinstance(getattr(cls, key, None), property): + if key in fs_empty_stored_as_nulls and value == "": + value = None + args_dict[key] = value + + return args_dict + def clone(self): """Create a new copy of this module.""" mod = Module( diff --git a/app/scodoc/codes_cursus.py b/app/scodoc/codes_cursus.py index ea6d09ca0..8e595ead7 100644 --- a/app/scodoc/codes_cursus.py +++ b/app/scodoc/codes_cursus.py @@ -409,6 +409,7 @@ class CursusBUT(TypeCursus): APC_SAE = True USE_REFERENTIEL_COMPETENCES = True ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT] + ECTS_DIPLOME = 180 register_cursus(CursusBUT()) diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py index 499c16eb9..5e102c834 100644 --- a/app/scodoc/gen_tables.py +++ b/app/scodoc/gen_tables.py @@ -44,13 +44,15 @@ import random from collections import OrderedDict from xml.etree import ElementTree import json +from typing import Any +from urllib.parse import urlparse, urlencode, parse_qs, urlunparse + from openpyxl.utils import get_column_letter -from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak -from reportlab.platypus import Table, TableStyle, Image, KeepInFrame +from reportlab.platypus import Paragraph, Spacer +from reportlab.platypus import Table, KeepInFrame from reportlab.lib.colors import Color from reportlab.lib import styles -from reportlab.lib.units import inch, cm, mm -from reportlab.rl_config import defaultPageSize # pylint: disable=no-name-in-module +from reportlab.lib.units import cm from app.scodoc import html_sco_header from app.scodoc import sco_utils as scu @@ -62,16 +64,32 @@ from app.scodoc.sco_pdf import SU from app import log, ScoDocJSONEncoder -def mark_paras(L, tags) -> list[str]: - """Put each (string) element of L between ..., +def mark_paras(items: list[Any], tags: list[str]) -> list[str]: + """Put each string element of items between ..., for each supplied tag. Leave non string elements untouched. """ for tag in tags: start = "<" + tag + ">" end = "" - L = [(start + (x or "") + end) if isinstance(x, str) else x for x in L] - return L + items = [(start + (x or "") + end) if isinstance(x, str) else x for x in items] + return items + + +def add_query_param(url: str, key: str, value: str) -> str: + "add parameter key=value to the given URL" + # Parse the URL + parsed_url = urlparse(url) + # Parse the query parameters + query_params = parse_qs(parsed_url.query) + # Add or update the query parameter + query_params[key] = [value] + # Encode the query parameters + encoded_query_params = urlencode(query_params, doseq=True) + # Construct the new URL + new_url_parts = parsed_url._replace(query=encoded_query_params) + new_url = urlunparse(new_url_parts) + return new_url class DEFAULT_TABLE_PREFERENCES(object): @@ -477,13 +495,15 @@ class GenTable: H.append('') if self.xls_link: H.append( - ' %s' % (self.base_url, scu.ICON_XLS) + f""" {scu.ICON_XLS}""" ) if self.xls_link and self.pdf_link: H.append(" ") if self.pdf_link: H.append( - ' %s' % (self.base_url, scu.ICON_PDF) + f""" {scu.ICON_PDF}""" ) H.append("") H.append("

") @@ -582,9 +602,11 @@ class GenTable: for line in data_list: Pt.append( [ - Paragraph(SU(str(x)), CellStyle) - if (not isinstance(x, Paragraph)) - else x + ( + Paragraph(SU(str(x)), CellStyle) + if (not isinstance(x, Paragraph)) + else x + ) for x in line ] ) diff --git a/app/scodoc/html_sidebar.py b/app/scodoc/html_sidebar.py index af6454456..bc84fea58 100755 --- a/app/scodoc/html_sidebar.py +++ b/app/scodoc/html_sidebar.py @@ -109,7 +109,7 @@ def sidebar_common(): {sidebar_dept()}

Scolarité

Semestres
- Programmes
+ Formations
""" ] if current_user.has_permission(Permission.AbsChange): diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py index 3e050a817..02eba72f8 100644 --- a/app/scodoc/sco_dept.py +++ b/app/scodoc/sco_dept.py @@ -114,7 +114,7 @@ def index_html(showcodes=0, showsemtable=0): # aucun semestre courant: affiche aide H.append( """

Aucune session en cours !

-

Pour ajouter une session, aller dans Programmes, +

Pour ajouter une session, aller dans Formations, choisissez une formation, puis suivez le lien "UE, modules, semestres".

Là, en bas de page, suivez le lien @@ -336,15 +336,15 @@ def _style_sems(sems): else: sem["semestre_id_n"] = sem["semestre_id"] # pour édition codes Apogée: - sem[ - "_etapes_apo_str_td_attrs" - ] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['etapes_apo_str']}" """ - sem[ - "_elt_annee_apo_td_attrs" - ] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_annee_apo']}" """ - sem[ - "_elt_sem_apo_td_attrs" - ] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """ + sem["_etapes_apo_str_td_attrs"] = ( + f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['etapes_apo_str']}" """ + ) + sem["_elt_annee_apo_td_attrs"] = ( + f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_annee_apo']}" """ + ) + sem["_elt_sem_apo_td_attrs"] = ( + f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """ + ) def delete_dept(dept_id: int) -> str: diff --git a/app/scodoc/sco_edit_formation.py b/app/scodoc/sco_edit_formation.py index 5b1f4ec42..df748eb6c 100644 --- a/app/scodoc/sco_edit_formation.py +++ b/app/scodoc/sco_edit_formation.py @@ -412,7 +412,7 @@ def module_move(module_id, after=0, redirect=True): db.session.add(neigh) db.session.commit() module.formation.invalidate_cached_sems() - # redirect to ue_list page: + # redirect to ue_table page: if redirect: return flask.redirect( url_for( @@ -454,7 +454,7 @@ def ue_move(ue_id, after=0, redirect=1): db.session.commit() ue.formation.invalidate_cached_sems() - # redirect to ue_list page + # redirect to ue_table page if redirect: return flask.redirect( url_for( diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index c5b106a67..5cf9797ee 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -106,9 +106,9 @@ def do_module_create(args) -> int: if int(args.get("semestre_id", 0)) != ue.semestre_idx: raise ScoValueError("Formation incompatible: contacter le support ScoDoc") # create - cnx = ndb.GetDBConnexion() - module_id = _moduleEditor.create(cnx, args) - log(f"do_module_create: created {module_id} with {args}") + module = Module.create_from_dict(args) + db.session.commit() + log(f"do_module_create: created {module.id} with {args}") # news ScolarNews.add( @@ -117,7 +117,7 @@ def do_module_create(args) -> int: text=f"Modification de la formation {formation.acronyme}", ) formation.invalidate_cached_sems() - return module_id + return module.id def module_create( @@ -666,7 +666,7 @@ def module_edit( "explanation": "numéro (1, 2, 3, 4, ...) pour ordre d'affichage", "type": "int", "default": default_num, - "allow_null": False, + "allow_null": True, }, ), ] @@ -811,6 +811,10 @@ def module_edit( ) ) else: + if isinstance(tf[2]["numero"], str): + tf[2]["numero"] = tf[2]["numero"].strip() + if not isinstance(tf[2]["numero"], int) and not tf[2]["numero"]: + tf[2]["numero"] = tf[2]["numero"] or default_num if create: if not matiere_id: # formulaire avec choix UE de rattachement diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 9989f7e1b..fa40ad552 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -766,7 +766,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} v{formation.version}", + page_title=f"Formation {formation.acronyme} v{formation.version}", ), f"""

{formation.html()} {lockicon}

@@ -888,7 +888,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours); H.append( f"""
-
Programme pédagogique:
+
Formation (programme pédagogique):
(debug) Vérifier cohérence - warn, _ = sco_formsemestre_validation.check_formation_ues(formation_id) + warn, _ = sco_formsemestre_validation.check_formation_ues(formation) H.append(warn) H.append(html_sco_header.sco_footer()) diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py index afb640cdf..4f9a9bb72 100644 --- a/app/scodoc/sco_formations.py +++ b/app/scodoc/sco_formations.py @@ -30,7 +30,7 @@ import xml.dom.minidom import flask -from flask import flash, g, url_for +from flask import flash, g, request, url_for from flask_login import current_user import app.scodoc.sco_utils as scu @@ -495,7 +495,7 @@ def formation_list_table() -> GenTable: returns a table """ formations: list[Formation] = Formation.query.filter_by(dept_id=g.scodoc_dept_id) - title = "Programmes pédagogiques" + title = "Formations (programmes pédagogiques)" lockicon = scu.icontag( "lock32_img", title="Comporte des semestres verrouillés", border="0" ) @@ -627,7 +627,7 @@ def formation_list_table() -> GenTable: html_class="formation_list_table table_leftalign", html_with_td_classes=True, html_sortable=True, - base_url="{request.base_url}?formation_id={formation_id}", + base_url=f"{request.base_url}", page_title=title, pdf_title=title, preferences=sco_preferences.SemPreferences(), diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index 73c6180ff..a9f46380c 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -304,12 +304,16 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N { "input_type": "text_suggest", "size": 50, - "title": "(Co-)Directeur(s) des études" - if index - else "Directeur des études", - "explanation": "(facultatif) taper le début du nom et choisir dans le menu" - if index - else "(obligatoire) taper le début du nom et choisir dans le menu", + "title": ( + "(Co-)Directeur(s) des études" + if index + else "Directeur des études" + ), + "explanation": ( + "(facultatif) taper le début du nom et choisir dans le menu" + if index + else "(obligatoire) taper le début du nom et choisir dans le menu" + ), "allowed_values": allowed_user_names, "allow_null": index, # > 0, # il faut au moins un responsable de semestre "text_suggest_options": { @@ -356,9 +360,11 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N "title": "Semestre dans la formation", "allowed_values": semestre_id_list, "labels": semestre_id_labels, - "explanation": "en BUT, on ne peut pas modifier le semestre après création" - if is_apc - else "", + "explanation": ( + "en BUT, on ne peut pas modifier le semestre après création" + if is_apc + else "" + ), "attributes": ['onchange="change_semestre_id();"'] if is_apc else "", }, ), @@ -1636,13 +1642,13 @@ def formsemestre_change_publication_bul( def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): """Changement manuel des coefficients des UE capitalisées.""" - + formsemestre = FormSemestre.get_formsemestre(formsemestre_id) ok, err = sco_permissions_check.check_access_diretud(formsemestre_id) if not ok: return err footer = html_sco_header.sco_footer() - help = """

+ help_msg = """

Seuls les modules ont un coefficient. Cependant, il est nécessaire d'affecter un coefficient aux UE capitalisée pour pouvoir les prendre en compte dans la moyenne générale.

ScoDoc calcule normalement le coefficient d'une UE comme la somme des @@ -1665,17 +1671,16 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): """ H = [ html_sco_header.html_sem_header("Coefficients des UE du semestre"), - help, + help_msg, ] # - ues, modimpls = _get_sem_ues_modimpls(formsemestre_id) + ues, modimpls = _get_sem_ues_modimpls(formsemestre) + sum_coefs_by_ue_id = {} for ue in ues: - ue["sum_coefs"] = sum( - [ - mod["module"]["coefficient"] - for mod in modimpls - if mod["module"]["ue_id"] == ue["ue_id"] - ] + sum_coefs_by_ue_id[ue.id] = sum( + modimpl.module.coefficient + for modimpl in modimpls + if modimpl.module.ue_id == ue.id ) cnx = ndb.GetDBConnexion() @@ -1684,20 +1689,20 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): form = [("formsemestre_id", {"input_type": "hidden"})] for ue in ues: coefs = sco_formsemestre.formsemestre_uecoef_list( - cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]} + cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue.id} ) if coefs: - initvalues["ue_" + str(ue["ue_id"])] = coefs[0]["coefficient"] + initvalues["ue_" + str(ue.id)] = coefs[0]["coefficient"] else: - initvalues["ue_" + str(ue["ue_id"])] = "auto" + initvalues["ue_" + str(ue.id)] = "auto" descr = { "size": 10, - "title": ue["acronyme"], - "explanation": "somme coefs modules = %s" % ue["sum_coefs"], + "title": ue.acronyme, + "explanation": f"somme coefs modules = {sum_coefs_by_ue_id[ue.id]}", } - if ue["ue_id"] == err_ue_id: + if ue.id == err_ue_id: descr["dom_id"] = "erroneous_ue" - form.append(("ue_" + str(ue["ue_id"]), descr)) + form.append(("ue_" + str(ue.id), descr)) tf = TrivialFormulator( request.base_url, @@ -1722,12 +1727,12 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): # 1- supprime les coef qui ne sont plus forcés # 2- modifie ou cree les coefs ue_deleted = [] - ue_modified = [] + ue_modified: list[tuple[UniteEns, float]] = [] msg = [] for ue in ues: - val = tf[2]["ue_" + str(ue["ue_id"])] + val = tf[2]["ue_" + str(ue.id)] coefs = sco_formsemestre.formsemestre_uecoef_list( - cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]} + cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue.id} ) if val == "" or val == "auto": # supprime ce coef (il sera donc calculé automatiquement) @@ -1737,13 +1742,11 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): try: val = float(val) if (not coefs) or (coefs[0]["coefficient"] != val): - ue["coef"] = val - ue_modified.append(ue) - except: + ue_modified.append((ue, val)) + except ValueError: ok = False msg.append( - "valeur invalide (%s) pour le coefficient de l'UE %s" - % (val, ue["acronyme"]) + f"valeur invalide ({val}) pour le coefficient de l'UE {ue.acronyme}" ) if not ok: @@ -1755,26 +1758,24 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): ) # apply modifications - for ue in ue_modified: + for ue, val in ue_modified: sco_formsemestre.do_formsemestre_uecoef_edit_or_create( - cnx, formsemestre_id, ue["ue_id"], ue["coef"] + cnx, formsemestre_id, ue.id, val ) for ue in ue_deleted: - sco_formsemestre.do_formsemestre_uecoef_delete( - cnx, formsemestre_id, ue["ue_id"] - ) + sco_formsemestre.do_formsemestre_uecoef_delete(cnx, formsemestre_id, ue.id) if ue_modified or ue_deleted: message = ["""

Modification effectuées

"""] if ue_modified: message.append("""

Coefs modifiés dans les UE:

    """) - for ue in ue_modified: - message.append("
  • %(acronyme)s : %(coef)s
  • " % ue) + for ue, val in ue_modified: + message.append(f"
  • {ue.acronyme} : {val}
  • ") message.append("
") if ue_deleted: message.append("""

Coefs supprimés dans les UE:

    """) for ue in ue_deleted: - message.append("
  • %(acronyme)s
  • " % ue) + message.append(f"
  • {ue.acronyme}
  • ") message.append("
") else: message = ["""

Aucune modification

"""] @@ -1792,21 +1793,19 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): """ -def _get_sem_ues_modimpls(formsemestre_id, modimpls=None): +def _get_sem_ues_modimpls( + formsemestre: FormSemestre, +) -> tuple[list[UniteEns], list[ModuleImpl]]: """Get liste des UE du semestre (à partir des moduleimpls) (utilisé quand on ne peut pas construire nt et faire nt.get_ues_stat_dict()) """ - if modimpls is None: - modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) uedict = {} + modimpls = formsemestre.modimpls.all() for modimpl in modimpls: - mod = sco_edit_module.module_list(args={"module_id": modimpl["module_id"]})[0] - modimpl["module"] = mod - if not mod["ue_id"] in uedict: - ue = sco_edit_ue.ue_list(args={"ue_id": mod["ue_id"]})[0] - uedict[ue["ue_id"]] = ue + if not modimpl.module.ue_id in uedict: + uedict[modimpl.module.ue.id] = modimpl.module.ue ues = list(uedict.values()) - ues.sort(key=lambda u: u["numero"]) + ues.sort(key=lambda u: u.numero) return ues, modimpls diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py index 11c6b07d8..e3474ebf2 100644 --- a/app/scodoc/sco_formsemestre_inscriptions.py +++ b/app/scodoc/sco_formsemestre_inscriptions.py @@ -305,18 +305,15 @@ def do_formsemestre_inscription_with_modules( # 2- inscrit aux groupes for group_id in group_ids: if group_id and group_id not in gdone: - group = GroupDescr.query.get_or_404(group_id) + _ = GroupDescr.query.get_or_404(group_id) sco_groups.set_group(etudid, group_id) gdone[group_id] = 1 # Inscription à tous les modules de ce semestre - modimpls = sco_moduleimpl.moduleimpl_withmodule_list( - formsemestre_id=formsemestre_id - ) - for mod in modimpls: - if mod["ue"]["type"] != UE_SPORT: + for modimpl in formsemestre.modimpls: + if modimpl.module.ue.type != UE_SPORT: sco_moduleimpl.do_moduleimpl_inscription_create( - {"moduleimpl_id": mod["moduleimpl_id"], "etudid": etudid}, + {"moduleimpl_id": modimpl.id, "etudid": etudid}, formsemestre_id=formsemestre_id, ) # Mise à jour des inscriptions aux parcours: @@ -531,19 +528,17 @@ def formsemestre_inscription_option(etudid, formsemestre_id): if not sem["etat"]: raise ScoValueError("Modification impossible: semestre verrouille") - etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] + etud = Identite.get_etud(etudid) formsemestre = FormSemestre.get_formsemestre(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) footer = html_sco_header.sco_footer() H = [ - html_sco_header.sco_header() - + "

Inscription de %s aux modules de %s (%s - %s)

" - % (etud["nomprenom"], sem["titre_num"], sem["date_debut"], sem["date_fin"]) + html_sco_header.sco_header(), + f"""

Inscription de {etud.nomprenom} aux modules de {formsemestre.titre_mois()}

""", ] # Cherche les moduleimpls et les inscriptions - mods = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id) inscr = sco_moduleimpl.do_moduleimpl_inscription_list(etudid=etudid) # Formulaire modimpls_by_ue_ids = collections.defaultdict(list) # ue_id : [ moduleimpl_id ] @@ -551,26 +546,26 @@ def formsemestre_inscription_option(etudid, formsemestre_id): ues = [] ue_ids = set() initvalues = {} - for mod in mods: - ue_id = mod["ue"]["ue_id"] + for modimpl in formsemestre.modimpls: + ue_id = modimpl.module.ue.id if not ue_id in ue_ids: - ues.append(mod["ue"]) + ues.append(modimpl.module.ue) ue_ids.add(ue_id) - modimpls_by_ue_ids[ue_id].append(mod["moduleimpl_id"]) + modimpls_by_ue_ids[ue_id].append(modimpl.id) modimpls_by_ue_names[ue_id].append( - "%s %s" % (mod["module"]["code"] or "", mod["module"]["titre"] or "") + f"{modimpl.module.code or ''} {modimpl.module.titre or ''}" ) vals = scu.get_request_args() if not vals.get("tf_submitted", False): # inscrit ? for ins in inscr: - if ins["moduleimpl_id"] == mod["moduleimpl_id"]: - key = "moduleimpls_%s" % ue_id + if ins["moduleimpl_id"] == modimpl.id: + key = f"moduleimpls_{ue_id}" if key in initvalues: - initvalues[key].append(str(mod["moduleimpl_id"])) + initvalues[key].append(str(modimpl.id)) else: - initvalues[key] = [str(mod["moduleimpl_id"])] + initvalues[key] = [str(modimpl.id)] break descr = [ @@ -578,10 +573,10 @@ def formsemestre_inscription_option(etudid, formsemestre_id): ("etudid", {"input_type": "hidden"}), ] for ue in ues: - ue_id = ue["ue_id"] - ue_descr = ue["acronyme"] - if ue["type"] != UE_STANDARD: - ue_descr += " %s" % UE_TYPE_NAME[ue["type"]] + ue_id = ue.id + ue_descr = ue.acronyme + if ue.type != UE_STANDARD: + ue_descr += " %s" % UE_TYPE_NAME[ue.type] ue_status = nt.get_etud_ue_status(etudid, ue_id) if ue_status and ue_status["is_capitalized"]: sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"]) @@ -606,7 +601,7 @@ def formsemestre_inscription_option(etudid, formsemestre_id): ) descr.append( ( - "moduleimpls_%s" % ue_id, + f"moduleimpls_{ue_id}", { "input_type": "checkbox", "title": "", @@ -654,112 +649,98 @@ function chkbx_select(field_id, state) { """ ) return "\n".join(H) + "\n" + tf[1] + footer - elif tf[0] == -1: + if tf[0] == -1: return flask.redirect( url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid) ) - else: - # Inscriptions aux modules choisis - # il faut desinscrire des modules qui ne figurent pas - # et inscrire aux autres, sauf si deja inscrit - a_desinscrire = {}.fromkeys([x["moduleimpl_id"] for x in mods]) - insdict = {} - for ins in inscr: - insdict[ins["moduleimpl_id"]] = ins - for ue in ues: - ue_id = ue["ue_id"] - for moduleimpl_id in [int(x) for x in tf[2]["moduleimpls_%s" % ue_id]]: - if moduleimpl_id in a_desinscrire: - del a_desinscrire[moduleimpl_id] - # supprime ceux auxquel pas inscrit - moduleimpls_a_desinscrire = list(a_desinscrire.keys()) - for moduleimpl_id in moduleimpls_a_desinscrire: - if moduleimpl_id not in insdict: + + # Inscriptions aux modules choisis + # il faut desinscrire des modules qui ne figurent pas + # et inscrire aux autres, sauf si deja inscrit + a_desinscrire = {}.fromkeys([x.id for x in formsemestre.modimpls]) + insdict = {} + for ins in inscr: + insdict[ins["moduleimpl_id"]] = ins + for ue in ues: + for moduleimpl_id in [int(x) for x in tf[2][f"moduleimpls_{ue.id}"]]: + if moduleimpl_id in a_desinscrire: del a_desinscrire[moduleimpl_id] + # supprime ceux auxquel pas inscrit + moduleimpls_a_desinscrire = list(a_desinscrire.keys()) + for moduleimpl_id in moduleimpls_a_desinscrire: + if moduleimpl_id not in insdict: + del a_desinscrire[moduleimpl_id] - a_inscrire = set() - for ue in ues: - ue_id = ue["ue_id"] - a_inscrire.update( - int(x) for x in tf[2]["moduleimpls_%s" % ue_id] - ) # conversion en int ! - # supprime ceux auquel deja inscrit: - for ins in inscr: - if ins["moduleimpl_id"] in a_inscrire: - a_inscrire.remove(ins["moduleimpl_id"]) - # dict des modules: - modsdict = {} - for mod in mods: - modsdict[mod["moduleimpl_id"]] = mod - # - if (not a_inscrire) and (not a_desinscrire): - H.append( - """

Aucune modification à effectuer

-

retour à la fiche étudiant

- """ - % url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid) - ) - return "\n".join(H) + footer - - H.append("

Confirmer les modifications:

") - if a_desinscrire: - H.append( - "

%s va être désinscrit%s des modules:

  • " - % (etud["nomprenom"], etud["ne"]) - ) - H.append( - "
  • ".join( - [ - "%s (%s)" - % ( - modsdict[x]["module"]["titre"], - modsdict[x]["module"]["code"] or "(module sans code)", - ) - for x in a_desinscrire - ] - ) - + "

    " - ) - H.append("
") - if a_inscrire: - H.append( - "

%s va être inscrit%s aux modules:

  • " - % (etud["nomprenom"], etud["ne"]) - ) - H.append( - "
  • ".join( - [ - "%s (%s)" - % ( - modsdict[x]["module"]["titre"], - modsdict[x]["module"]["code"] or "(module sans code)", - ) - for x in a_inscrire - ] - ) - + "

    " - ) - H.append("
") - modulesimpls_ainscrire = ",".join(str(x) for x in a_inscrire) - modulesimpls_adesinscrire = ",".join(str(x) for x in a_desinscrire) + a_inscrire = set() + for ue in ues: + a_inscrire.update( + int(x) for x in tf[2][f"moduleimpls_{ue.id}"] + ) # conversion en int ! + # supprime ceux auquel deja inscrit: + for ins in inscr: + if ins["moduleimpl_id"] in a_inscrire: + a_inscrire.remove(ins["moduleimpl_id"]) + # dict des modules: + modimpls_by_id = {modimpl.id: modimpl for modimpl in formsemestre.modimpls} + # + if (not a_inscrire) and (not a_desinscrire): H.append( - """ - - - - - -
+ f"""

Aucune modification à effectuer

+

retour à la fiche étudiant

""" - % ( - etudid, - modulesimpls_ainscrire, - modulesimpls_adesinscrire, - url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid), - ) ) return "\n".join(H) + footer + H.append("

Confirmer les modifications:

") + if a_desinscrire: + H.append( + f"""

{etud.nomprenom} va être désinscrit{etud.e} des modules:

") + if a_inscrire: + H.append( + f"""

{etud.nomprenom} va être inscrit{etud.e} aux modules:

") + modulesimpls_ainscrire = ",".join(str(x) for x in a_inscrire) + modulesimpls_adesinscrire = ",".join(str(x) for x in a_desinscrire) + H.append( + f""" +
+ + + + + +
+ """ + ) + return "\n".join(H) + footer + def do_moduleimpl_incription_options( etudid, modulesimpls_ainscrire, modulesimpls_adesinscrire diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index 7da7746da..c431cb911 100755 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -909,37 +909,6 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str: return "\n".join(H) -def html_expr_diagnostic(diagnostics): - """Affiche messages d'erreur des formules utilisateurs""" - H = [] - H.append('
Erreur dans des formules utilisateurs:
    ') - last_id, last_msg = None, None - for diag in diagnostics: - if "moduleimpl_id" in diag: - mod = sco_moduleimpl.moduleimpl_withmodule_list( - moduleimpl_id=diag["moduleimpl_id"] - )[0] - H.append( - '
  • module %s: %s
  • ' - % ( - diag["moduleimpl_id"], - mod["module"]["abbrev"] or mod["module"]["code"] or "?", - diag["msg"], - ) - ) - else: - if diag["ue_id"] != last_id or diag["msg"] != last_msg: - ue = sco_edit_ue.ue_list({"ue_id": diag["ue_id"]})[0] - H.append( - '
  • UE "%s": %s
  • ' - % (ue["acronyme"] or ue["titre"] or "?", diag["msg"]) - ) - last_id, last_msg = diag["ue_id"], diag["msg"] - - H.append("
") - return "".join(H) - - def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None): """En-tête HTML des pages "semestre" """ formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id) @@ -1081,9 +1050,6 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True): >Toutes évaluations (même incomplètes) visibles
""" ) - if nt.expr_diagnostics: - H.append(html_expr_diagnostic(nt.expr_diagnostics)) - if nt.parcours.APC_SAE: # BUT: tableau ressources puis SAE ressources = [ diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index 22a16b151..68a18d602 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -1217,7 +1217,7 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
- {check_formation_ues(formation.id)[0]} + {check_formation_ues(formation)[0]} {html_sco_header.sco_footer()} """ @@ -1376,15 +1376,14 @@ def _invalidate_etud_formation_caches(etudid, formation_id): ) # > modif decision UE (inval tous semestres avec cet etudiant, ok mais conservatif) -def check_formation_ues(formation_id): +def check_formation_ues(formation: Formation) -> tuple[str, dict[int, list[UniteEns]]]: """Verifie que les UE d'une formation sont chacune utilisée dans un seul semestre_id Si ce n'est pas le cas, c'est probablement (mais pas forcément) une erreur de définition du programme: cette fonction retourne un bout de HTML à afficher pour prévenir l'utilisateur, ou '' si tout est ok. """ - ues = sco_edit_ue.ue_list({"formation_id": formation_id}) ue_multiples = {} # { ue_id : [ liste des formsemestre ] } - for ue in ues: + for ue in formation.ues: # formsemestres utilisant cette ue ? sems = ndb.SimpleDictFetch( """SELECT DISTINCT sem.id AS formsemestre_id, sem.* @@ -1394,9 +1393,9 @@ def check_formation_ues(formation_id): AND mi.formsemestre_id = sem.id AND mod.ue_id = %(ue_id)s """, - {"ue_id": ue["ue_id"], "formation_id": formation_id}, + {"ue_id": ue.id, "formation_id": formation.id}, ) - semestre_ids = set([x["semestre_id"] for x in sems]) + semestre_ids = {x["semestre_id"] for x in sems} if ( len(semestre_ids) > 1 ): # plusieurs semestres d'indices differents dans le cursus @@ -1416,11 +1415,11 @@ def check_formation_ues(formation_id): ") return "\n".join(H), ue_multiples diff --git a/app/scodoc/sco_moduleimpl.py b/app/scodoc/sco_moduleimpl.py index 9e3f48cb8..67cd380cf 100644 --- a/app/scodoc/sco_moduleimpl.py +++ b/app/scodoc/sco_moduleimpl.py @@ -56,6 +56,7 @@ _moduleimplEditor = ndb.EditableTable( def do_moduleimpl_create(args): "create a moduleimpl" + # TODO remplacer par une methode de ModuleImpl qui appelle super().create_from_dict() puis invalide le formsemestre cnx = ndb.GetDBConnexion() r = _moduleimplEditor.create(cnx, args) sco_cache.invalidate_formsemestre( @@ -109,91 +110,6 @@ def do_moduleimpl_edit(args, formsemestre_id=None, cnx=None): ) # > modif moduleimpl -def moduleimpl_withmodule_list( - moduleimpl_id=None, formsemestre_id=None, module_id=None, sort_by_ue=False -) -> list: - """Liste les moduleimpls et ajoute dans chacun - l'UE, la matière et le module auxquels ils appartiennent. - Tri la liste par: - - pour les formations classiques: semestre/UE/numero_matiere/numero_module; - - pour le BUT: ignore UEs sauf si sort_by_ue et matières dans le tri. - - NB: Cette fonction faisait partie de l'API ScoDoc 7. - """ - from app.scodoc import sco_edit_ue - from app.scodoc import sco_edit_matiere - from app.scodoc import sco_edit_module - - modimpls = moduleimpl_list( - **{ - "moduleimpl_id": moduleimpl_id, - "formsemestre_id": formsemestre_id, - "module_id": module_id, - } - ) - if not modimpls: - return [] - ues = {} - matieres = {} - modules = {} - for mi in modimpls: - module_id = mi["module_id"] - if not mi["module_id"] in modules: - modules[module_id] = sco_edit_module.module_list( - args={"module_id": module_id} - )[0] - mi["module"] = modules[module_id] - ue_id = mi["module"]["ue_id"] - if not ue_id in ues: - ues[ue_id] = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0] - mi["ue"] = ues[ue_id] - matiere_id = mi["module"]["matiere_id"] - if not matiere_id in matieres: - matieres[matiere_id] = sco_edit_matiere.matiere_list( - args={"matiere_id": matiere_id} - )[0] - mi["matiere"] = matieres[matiere_id] - - mod = modimpls[0]["module"] - formation = db.session.get(Formation, mod["formation_id"]) - - if formation.is_apc(): - # tri par numero_module - if sort_by_ue: - modimpls.sort( - key=lambda x: ( - x["ue"]["numero"], - x["ue"]["ue_id"], - x["module"]["module_type"], - x["module"]["numero"], - x["module"]["code"], - ) - ) - else: - modimpls.sort( - key=lambda x: ( - x["module"]["module_type"], - x["module"]["numero"], - x["module"]["code"], - ) - ) - else: - # Formations classiques, avec matières: - # tri par semestre/UE/numero_matiere/numero_module - modimpls.sort( - key=lambda x: ( - x["ue"]["numero"], - x["ue"]["ue_id"], - x["matiere"]["numero"], - x["matiere"]["matiere_id"], - x["module"]["numero"], - x["module"]["code"], - ) - ) - - return modimpls - - def moduleimpls_in_external_ue(ue_id): """List of modimpls in this ue""" cursor = ndb.SimpleQuery( @@ -254,9 +170,9 @@ _moduleimpl_inscriptionEditor = ndb.EditableTable( ) -def do_moduleimpl_inscription_create(args, formsemestre_id=None): +def do_moduleimpl_inscription_create(args, formsemestre_id=None, cnx=None): "create a moduleimpl_inscription" - cnx = ndb.GetDBConnexion() + cnx = cnx or ndb.GetDBConnexion() try: r = _moduleimpl_inscriptionEditor.create(cnx, args) except psycopg2.errors.UniqueViolation as exc: @@ -270,7 +186,7 @@ def do_moduleimpl_inscription_create(args, formsemestre_id=None): cnx, method="moduleimpl_inscription", etudid=args["etudid"], - msg="inscription module %s" % args["moduleimpl_id"], + msg=f"inscription module {args['moduleimpl_id']}", commit=False, ) return r @@ -297,32 +213,29 @@ def do_moduleimpl_inscrit_etuds(moduleimpl_id, formsemestre_id, etudids, reset=F args={"formsemestre_id": formsemestre_id, "etudid": etudid} ) if not insem: - raise ScoValueError("%s n'est pas inscrit au semestre !" % etudid) + raise ScoValueError(f"{etudid} n'est pas inscrit au semestre !") + cnx = ndb.GetDBConnexion() # Desinscriptions if reset: - cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor.execute( "delete from notes_moduleimpl_inscription where moduleimpl_id = %(moduleimpl_id)s", {"moduleimpl_id": moduleimpl_id}, ) # Inscriptions au module: - inmod_set = set( - [ - # hum ? - x["etudid"] - for x in do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id) - ] - ) + inmod_set = { + x["etudid"] for x in do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id) + } for etudid in etudids: - # deja inscrit ? + # déja inscrit ? if not etudid in inmod_set: do_moduleimpl_inscription_create( {"moduleimpl_id": moduleimpl_id, "etudid": etudid}, formsemestre_id=formsemestre_id, + cnx=cnx, ) - + cnx.commit() sco_cache.invalidate_formsemestre( formsemestre_id=formsemestre_id ) # > moduleimpl_inscrit_etuds diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py index 92401fb53..c4b683ebc 100644 --- a/app/scodoc/sco_moduleimpl_inscriptions.py +++ b/app/scodoc/sco_moduleimpl_inscriptions.py @@ -409,34 +409,32 @@ def moduleimpl_inscriptions_stats(formsemestre_id): H.append( '

Étudiants avec UEs capitalisées (ADM):