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 = "" + tag.split()[0] + ">"
- 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()}
-
+
"""
]
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):
+ 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:
- """
+ )
+ H.append(
+ "
- ".join(
+ [
+ f"""{modimpls_by_id[x].module.titre or ''} ({
+ modimpls_by_id[x].module.code or '(module sans code)'})"""
+ for x in a_desinscrire
+ ]
+ )
+ + ""
+ )
+ H.append("
")
+ if a_inscrire:
+ H.append(
+ f"""
{etud.nomprenom} va être inscrit{etud.e} aux modules:
- """
+ )
+ H.append(
+ "
- ".join(
+ [
+ f"""{modimpls_by_id[x].module.titre or ''} ({
+ modimpls_by_id[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)
+ 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):
"""
]
- for ue in ues:
- if ue["ue_id"] in ue_multiples:
+ for ue in formation.ues:
+ if ue.id in ue_multiples:
sems = [
sco_formsemestre.get_formsemestre(x["formsemestre_id"])
- for x in ue_multiples[ue["ue_id"]]
+ for x in ue_multiples[ue.id]
]
slist = ", ".join(
[
@@ -1429,7 +1428,7 @@ def check_formation_ues(formation_id):
for s in sems
]
)
- H.append("- %s : %s
" % (ue["acronyme"], slist))
+ H.append("- %s : %s
" % (ue.acronyme, slist))
H.append("
")
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):
'
)
- ues = [
- sco_edit_ue.ue_list({"ue_id": ue_id})[0] for ue_id in ues_cap_info.keys()
- ]
- ues.sort(key=lambda u: u["numero"])
+ ues = [UniteEns.query.get_or_404(ue_id) for ue_id in ues_cap_info.keys()]
+ ues.sort(key=lambda u: u.numero)
for ue in ues:
H.append(
- f"""- {ue['acronyme']}: {ue['titre']}"""
+ f"""
- {ue.acronyme}: {ue.titre or ''}"""
)
H.append("
")
- for info in ues_cap_info[ue["ue_id"]]:
- etud = sco_etud.get_etud_info(etudid=info["etudid"], filled=True)[0]
+ for info in ues_cap_info[ue.id]:
+ etud = Identite.get_etud(info["etudid"])
H.append(
f"""- {etud["nomprenom"]}"""
+ }">{etud.nomprenom}"""
)
if info["ue_status"]["event_date"]:
H.append(
f"""(cap. le {info["ue_status"]["event_date"].strftime("%d/%m/%Y")})"""
)
if is_apc:
- is_inscrit_ue = (etud["etudid"], ue["id"]) not in res.dispense_ues
+ is_inscrit_ue = (etud.id, ue.id) not in res.dispense_ues
else:
# CLASSIQUE
is_inscrit_ue = info["is_ins"]
@@ -468,8 +466,8 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
H.append(
f"""
"""
)
@@ -479,8 +477,8 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
H.append(
f"""
"""
)
diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py
index ca14145d6..9ee69aec3 100644
--- a/app/scodoc/sco_moduleimpl_status.py
+++ b/app/scodoc/sco_moduleimpl_status.py
@@ -127,12 +127,12 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
"args": {
"group_ids": group_id,
"evaluation_id": evaluation.id,
- "date_debut": evaluation.date_debut.isoformat()
- if evaluation.date_debut
- else "",
- "date_fin": evaluation.date_fin.isoformat()
- if evaluation.date_fin
- else "",
+ "date_debut": (
+ evaluation.date_debut.isoformat() if evaluation.date_debut else ""
+ ),
+ "date_fin": (
+ evaluation.date_fin.isoformat() if evaluation.date_fin else ""
+ ),
},
"enabled": evaluation.date_debut is not None
and evaluation.date_fin is not None,
@@ -355,10 +355,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
Ses notes ne peuvent pas être prises en compte dans les moyennes d'UE.
"""
)
- #
- if has_expression and nt.expr_diagnostics:
- H.append(sco_formsemestre_status.html_expr_diagnostic(nt.expr_diagnostics))
- #
+
if formsemestre_has_decisions(formsemestre_id):
H.append(
"""
diff --git a/app/scodoc/sco_pv_dict.py b/app/scodoc/sco_pv_dict.py
index 9944417af..e0a7223ed 100644
--- a/app/scodoc/sco_pv_dict.py
+++ b/app/scodoc/sco_pv_dict.py
@@ -139,9 +139,8 @@ def dict_pvjury(
dec_ue_list = _descr_decisions_ues(
nt, etudid, d["decisions_ue"], d["decision_sem"]
)
- d["decisions_ue_nb"] = len(
- dec_ue_list
- ) # avec les UE capitalisées, donc des éventuels doublons
+ # avec les UE capitalisées, donc des éventuels doublons:
+ d["decisions_ue_nb"] = len(dec_ue_list)
# Mais sur la description (eg sur les bulletins), on ne veut pas
# afficher ces doublons: on uniquifie sur ue_code
_codes = set()
@@ -291,8 +290,10 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem) -> list[dict]:
)
)
):
- ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
- uelist.append(ue)
+ ue = UniteEns.query.get(ue_id)
+ assert ue
+ # note modernisation code: on utilise des dict tant que get_etud_ue_status renvoie des dicts
+ uelist.append(ue.to_dict())
# Les UE capitalisées dans d'autres semestres:
if etudid in nt.validations.ue_capitalisees.index:
for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]:
diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py
index b902a88ad..8f84e8c6c 100644
--- a/app/scodoc/sco_saisie_notes.py
+++ b/app/scodoc/sco_saisie_notes.py
@@ -528,6 +528,7 @@ def notes_add(
Return: tuple (etudids_changed, nb_suppress, etudids_with_decision)
"""
+ assert evaluation_id is not None
now = psycopg2.Timestamp(*time.localtime()[:6])
# Vérifie inscription et valeur note
@@ -539,7 +540,7 @@ def notes_add(
}
for etudid, value in notes:
if check_inscription and (etudid not in inscrits):
- raise NoteProcessError(f"etudiant {etudid} non inscrit dans ce module")
+ raise NoteProcessError(f"étudiant {etudid} non inscrit dans ce module")
if (value is not None) and not isinstance(value, float):
raise NoteProcessError(
f"etudiant {etudid}: valeur de note invalide ({value})"
diff --git a/app/scodoc/sco_ue_external.py b/app/scodoc/sco_ue_external.py
index 593d0568b..4d32a4053 100644
--- a/app/scodoc/sco_ue_external.py
+++ b/app/scodoc/sco_ue_external.py
@@ -60,16 +60,14 @@ from app.models.formsemestre import FormSemestre
from app import db, log
-from app.models import Evaluation, ModuleImpl, UniteEns
+from app.models import Evaluation, Identite, ModuleImpl, UniteEns
from app.scodoc import html_sco_header
from app.scodoc import codes_cursus
from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue
-from app.scodoc import sco_evaluation_db
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_saisie_notes
-from app.scodoc import sco_etud
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.sco_permissions import Permission
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
@@ -83,10 +81,10 @@ def external_ue_create(
acronyme="",
ue_type=codes_cursus.UE_STANDARD,
ects=0.0,
-) -> int:
+) -> ModuleImpl:
"""Crée UE/matiere/module dans la formation du formsemestre
puis un moduleimpl.
- Return: moduleimpl_id
+ Return: moduleimpl
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
log(f"creating external UE in {formsemestre}: {acronyme}")
@@ -139,28 +137,30 @@ def external_ue_create(
"module_id": module_id,
"formsemestre_id": formsemestre_id,
# affecte le 1er responsable du semestre comme resp. du module
- "responsable_id": formsemestre.responsables[0].id
- if len(formsemestre.responsables)
- else None,
+ "responsable_id": (
+ formsemestre.responsables[0].id
+ if len(formsemestre.responsables)
+ else None
+ ),
},
)
-
- return moduleimpl_id
+ modimpl = ModuleImpl.query.get(moduleimpl_id)
+ assert modimpl
+ return modimpl
def external_ue_inscrit_et_note(
- moduleimpl_id: int, formsemestre_id: int, notes_etuds: dict
+ moduleimpl: ModuleImpl, formsemestre_id: int, notes_etuds: dict
):
"""Inscrit les étudiants au moduleimpl, crée au besoin une évaluation
et enregistre les notes.
"""
- moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
log(
- f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl_id}, notes_etuds={notes_etuds})"
+ f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl.id}, notes_etuds={notes_etuds})"
)
# Inscription des étudiants
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
- moduleimpl_id,
+ moduleimpl.id,
formsemestre_id,
list(notes_etuds.keys()),
)
@@ -188,12 +188,12 @@ def external_ue_inscrit_et_note(
)
-def get_existing_external_ue(formation_id: int) -> list[dict]:
- "Liste de toutes les UE externes définies dans cette formation"
- return sco_edit_ue.ue_list(args={"formation_id": formation_id, "is_external": True})
+def get_existing_external_ue(formation_id: int) -> list[UniteEns]:
+ "Liste de toutes les UEs externes définies dans cette formation"
+ return UniteEns.query.filter_by(formation_id=formation_id, is_external=True).all()
-def get_external_moduleimpl_id(formsemestre_id: int, ue_id: int) -> int:
+def get_external_moduleimpl(formsemestre_id: int, ue_id: int) -> ModuleImpl:
"moduleimpl correspondant à l'UE externe indiquée de ce formsemestre"
r = ndb.SimpleDictFetch(
"""
@@ -205,7 +205,10 @@ def get_external_moduleimpl_id(formsemestre_id: int, ue_id: int) -> int:
{"ue_id": ue_id, "formsemestre_id": formsemestre_id},
)
if r:
- return r[0]["moduleimpl_id"]
+ modimpl_id = r[0]["moduleimpl_id"]
+ modimpl = ModuleImpl.query.get(modimpl_id)
+ assert modimpl
+ return modimpl
else:
raise ScoValueError(
f"""Aucun module externe ne correspond
@@ -225,20 +228,20 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
En BUT, pas d'UEs externes. Voir https://scodoc.org/git/ScoDoc/ScoDoc/issues/542
"""
- formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# Contrôle d'accès:
if not formsemestre.can_be_edited_by(current_user):
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
if formsemestre.formation.is_apc():
raise ScoValueError("Impossible d'ajouter une UE externe en BUT")
- etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
+ etud = Identite.get_etud(etudid)
formation_id = formsemestre.formation.id
existing_external_ue = get_existing_external_ue(formation_id)
H = [
html_sco_header.html_sem_header(
- "Ajout d'une UE externe pour %(nomprenom)s" % etud,
+ f"Ajout d'une UE externe pour {etud.nomprenom}",
javascripts=["js/sco_ue_external.js"],
),
"""Cette page permet d'indiquer que l'étudiant a suivi une UE
@@ -275,10 +278,10 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
"input_type": "menu",
"title": "UE externe existante:",
"allowed_values": [""]
- + [str(ue["ue_id"]) for ue in existing_external_ue],
+ + [str(ue.id) for ue in existing_external_ue],
"labels": [default_label]
+ [
- "%s (%s)" % (ue["titre"], ue["acronyme"])
+ f"{ue.titre or ''} ({ue.acronyme})"
for ue in existing_external_ue
],
"attributes": ['onchange="update_external_ue_form();"'],
@@ -364,7 +367,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
)
if tf[2]["existing_ue"]:
ue_id = int(tf[2]["existing_ue"])
- moduleimpl_id = get_external_moduleimpl_id(formsemestre_id, ue_id)
+ modimpl = get_external_moduleimpl(formsemestre_id, ue_id)
else:
acronyme = tf[2]["acronyme"].strip()
if not acronyme:
@@ -375,7 +378,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
+ tf[1]
+ html_footer
)
- moduleimpl_id = external_ue_create(
+ modimpl = external_ue_create(
formsemestre_id,
titre=tf[2]["titre"],
acronyme=acronyme,
@@ -384,7 +387,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
)
external_ue_inscrit_et_note(
- moduleimpl_id,
+ modimpl,
formsemestre_id,
{etudid: note_value},
)
diff --git a/app/templates/sidebar.j2 b/app/templates/sidebar.j2
index 884931ad3..6870b0e07 100755
--- a/app/templates/sidebar.j2
+++ b/app/templates/sidebar.j2
@@ -23,7 +23,7 @@
-
+
{% if current_user.has_permission(sco.Permission.AbsChange)%}
diff --git a/app/views/notes.py b/app/views/notes.py
index 77a92e7d0..6fc17e7f2 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -30,6 +30,7 @@ Module notes: issu de ScoDoc7 / ZNotes.py
Emmanuel Viennet, 2021
"""
+import html
from operator import itemgetter
import time
@@ -487,7 +488,6 @@ def get_ue_niveaux_options_html():
return apc_edit_ue.get_ue_niveaux_options_html(ue)
-@bp.route("/ue_list") # backward compat
@bp.route("/ue_table")
@scodoc
@permission_required(Permission.ScoView)
@@ -682,21 +682,21 @@ def module_clone():
@bp.route("/index_html")
@scodoc
@permission_required(Permission.ScoView)
-@scodoc7func
def index_html():
"Page accueil formations"
-
+ fmt = request.args.get("fmt", "html")
editable = current_user.has_permission(Permission.EditFormation)
+ table = sco_formations.formation_list_table()
+
+ if fmt != "html":
+ return table.make_page(fmt=fmt, filename=f"Formations-{g.scodoc_dept}")
H = [
- html_sco_header.sco_header(page_title="Programmes formations"),
- """Programmes pédagogiques
- """,
+ html_sco_header.sco_header(page_title="Formations (programmes)"),
+ """Formations (programmes pédagogiques)
+ """,
+ table.html(),
]
- T = sco_formations.formation_list_table()
-
- H.append(T.html())
-
if editable:
H.append(
f"""
@@ -804,7 +804,7 @@ def formation_import_xml_form():
Import effectué !