diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py
index 1fccbda158..5856184fcf 100644
--- a/app/comp/moy_mod.py
+++ b/app/comp/moy_mod.py
@@ -85,6 +85,8 @@ class ModuleImplResults:
"{ evaluation.id : bool } indique si à prendre en compte ou non."
self.evaluations_etat = {}
"{ evaluation_id: EvaluationEtat }"
+ self.etudids_attente = set()
+ "etudids avec au moins une note ATT dans ce module"
self.en_attente = False
"Vrai si au moins une évaluation a une note en attente"
#
@@ -145,7 +147,6 @@ class ModuleImplResults:
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
self.evaluations_completes = []
self.evaluations_completes_dict = {}
- self.en_attente = False
for evaluation in moduleimpl.evaluations:
eval_df = self._load_evaluation_notes(evaluation)
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
@@ -172,15 +173,20 @@ class ModuleImplResults:
eval_df, how="left", left_index=True, right_index=True
)
# Notes en attente: (ne prend en compte que les inscrits, non démissionnaires)
- nb_att = sum(
- evals_notes[str(evaluation.id)][list(inscrits_module)]
- == scu.NOTES_ATTENTE
+ eval_notes_inscr = evals_notes[str(evaluation.id)][list(inscrits_module)]
+ eval_etudids_attente = set(
+ eval_notes_inscr.iloc[
+ (eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy()
+ ].index
)
+ self.etudids_attente |= eval_etudids_attente
self.evaluations_etat[evaluation.id] = EvaluationEtat(
- evaluation_id=evaluation.id, nb_attente=nb_att, is_complete=is_complete
+ evaluation_id=evaluation.id,
+ nb_attente=len(eval_etudids_attente),
+ is_complete=is_complete,
)
- if nb_att > 0:
- self.en_attente = True
+ # au moins une note en ATT dans ce modimpl:
+ self.en_attente = bool(self.etudids_attente)
# Force columns names to integers (evaluation ids)
evals_notes.columns = pd.Index([int(x) for x in evals_notes.columns], dtype=int)
diff --git a/app/models/moduleimpls.py b/app/models/moduleimpls.py
index 930909d341..42da823f83 100644
--- a/app/models/moduleimpls.py
+++ b/app/models/moduleimpls.py
@@ -5,10 +5,12 @@ import pandas as pd
import flask_sqlalchemy
from app import db
+from app.auth.models import User
from app.comp import df_cache
from app.models.etudiants import Identite
from app.models.modules import Module
-
+from app.scodoc.sco_exceptions import AccessDenied, ScoLockedSemError
+from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_utils as scu
@@ -99,6 +101,27 @@ class ModuleImpl(db.Model):
d.pop("module", None)
return d
+ def can_change_ens_by(self, user: User, raise_exc=False) -> bool:
+ """Check if user can modify module resp.
+ If raise_exc, raises exception (AccessDenied or ScoLockedSemError) if not.
+ = Admin, et dir des etud. (si option l'y autorise)
+ """
+ if not self.formsemestre.etat:
+ if raise_exc:
+ raise ScoLockedSemError("Modification impossible: semestre verrouille")
+ return False
+ # -- check access
+ # admin ou resp. semestre avec flag resp_can_change_resp
+ if user.has_permission(Permission.ScoImplement):
+ return True
+ if (
+ user.id in [resp.id for resp in self.formsemestre.responsables]
+ ) and self.formsemestre.resp_can_change_ens:
+ return True
+ if raise_exc:
+ raise AccessDenied(f"Modification impossible pour {user}")
+ return False
+
# Enseignants (chargés de TD ou TP) d'un moduleimpl
notes_modules_enseignants = db.Table(
diff --git a/app/scodoc/sco_exceptions.py b/app/scodoc/sco_exceptions.py
index 7896336f23..5019647083 100644
--- a/app/scodoc/sco_exceptions.py
+++ b/app/scodoc/sco_exceptions.py
@@ -122,6 +122,14 @@ class ScoLockedFormError(ScoValueError):
super().__init__(msg=msg, dest_url=dest_url)
+class ScoLockedSemError(ScoValueError):
+ "Modification d'un formsemestre verrouillé"
+
+ def __init__(self, msg="", dest_url=None):
+ msg = "Ce semestre est verrouillé ! " + str(msg)
+ super().__init__(msg=msg, dest_url=dest_url)
+
+
class ScoNonEmptyFormationObject(ScoValueError):
"""On ne peut pas supprimer un module/matiere ou UE si des formsemestre s'y réfèrent"""
diff --git a/app/scodoc/sco_moduleimpl.py b/app/scodoc/sco_moduleimpl.py
index 0e1edbee3b..052512b0ec 100644
--- a/app/scodoc/sco_moduleimpl.py
+++ b/app/scodoc/sco_moduleimpl.py
@@ -377,7 +377,7 @@ def can_change_module_resp(moduleimpl_id):
if not current_user.has_permission(Permission.ScoImplement) and (
(current_user.id not in sem["responsables"]) or (not sem["resp_can_change_ens"])
):
- raise AccessDenied("Modification impossible pour %s" % current_user)
+ raise AccessDenied(f"Modification impossible pour {current_user}")
return M, sem
diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py
index bf330adb34..39e97d0d2c 100644
--- a/app/scodoc/sco_moduleimpl_inscriptions.py
+++ b/app/scodoc/sco_moduleimpl_inscriptions.py
@@ -38,6 +38,7 @@ from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, Identite, ScolarFormSemestreValidation, UniteEns
from app import log
+from app.tables import list_etuds
from app.scodoc.scolog import logdb
from app.scodoc import html_sco_header
from app.scodoc import htmlutils
@@ -520,14 +521,15 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
H.append(f"""
{ue.acronyme} | """)
H.append("""""")
- for etudid, ues_etud in table_inscr.items():
- etud: Identite = Identite.query.get(etudid)
+ etuds = list_etuds.etuds_sorted_from_ids(table_inscr.keys())
+ for etud in etuds:
+ ues_etud = table_inscr[etud.id]
H.append(
- f""" | {etud.nomprenom} | """
)
@@ -539,7 +541,7 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
else:
# Validations d'UE déjà enregistrées dans d'autres semestres
validations_ue = (
- ScolarFormSemestreValidation.query.filter_by(etudid=etudid)
+ ScolarFormSemestreValidation.query.filter_by(etudid=etud.id)
.filter(
ScolarFormSemestreValidation.formsemestre_id
!= res.formsemestre.id,
@@ -556,7 +558,8 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
)
validation = validations_ue[-1] if validations_ue else None
expl_validation = (
- f"""Validée ({validation.code}) le {validation.event_date.strftime("%d/%m/%Y")}"""
+ f"""Validée ({validation.code}) le {
+ validation.event_date.strftime("%d/%m/%Y")}"""
if validation
else ""
)
@@ -567,13 +570,13 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
title="{etud.nomprenom} {'inscrit' if est_inscr else 'non inscrit'} à l'UE {ue.acronyme}. {expl_validation}",
onchange="change_ue_inscr(this);"
data-url_inscr={
- url_for("notes.etud_inscrit_ue",
- scodoc_dept=g.scodoc_dept, etudid=etudid,
+ url_for("notes.etud_inscrit_ue",
+ scodoc_dept=g.scodoc_dept, etudid=etud.id,
formsemestre_id=res.formsemestre.id, ue_id=ue.id)
}
data-url_desinscr={
- url_for("notes.etud_desinscrit_ue",
- scodoc_dept=g.scodoc_dept, etudid=etudid,
+ url_for("notes.etud_desinscrit_ue",
+ scodoc_dept=g.scodoc_dept, etudid=etud.id,
formsemestre_id=res.formsemestre.id, ue_id=ue.id)
}
/>
diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py
index 5bf2a7f831..1127a968e9 100644
--- a/app/scodoc/sco_moduleimpl_status.py
+++ b/app/scodoc/sco_moduleimpl_status.py
@@ -36,6 +36,7 @@ from flask_login import current_user
from app import db
from app.auth.models import User
from app.comp import res_sem
+from app.comp.res_common import ResultatsSemestre
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, ModuleImpl
from app.models.evaluations import Evaluation
@@ -59,9 +60,7 @@ from app.scodoc import sco_formsemestre_status
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_permissions_check
-from app.scodoc import sco_users
-
-# ported from old DTML code in oct 2009
+from app.tables import list_etuds
# menu evaluation dans moduleimpl
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
@@ -196,23 +195,20 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
if not isinstance(moduleimpl_id, int):
raise ScoInvalidIdType("moduleimpl_id must be an integer !")
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
- M = modimpl.to_dict()
+ mi_dict = modimpl.to_dict()
formsemestre_id = modimpl.formsemestre_id
formsemestre: FormSemestre = modimpl.formsemestre
- Mod = sco_edit_module.module_list(args={"module_id": modimpl.module_id})[0]
+ mod_dict = sco_edit_module.module_list(args={"module_id": modimpl.module_id})[0]
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
- F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
+ formation_dict = sco_formations.formation_list(
+ args={"formation_id": sem["formation_id"]}
+ )[0]
mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
moduleimpl_id=moduleimpl_id
)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
-
- # mod_evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
- # mod_evals.sort(
- # key=lambda x: (x["numero"], x["jour"], x["heure_debut"]), reverse=True
- # )
- # la plus RECENTE en tête
+ # Evaluations, la plus RECENTE en tête
evaluations = modimpl.evaluations.order_by(
Evaluation.numero.desc(),
Evaluation.jour.desc(),
@@ -240,18 +236,23 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
)
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
#
- module_resp = User.query.get(M["responsable_id"])
- mod_type_name = scu.MODULE_TYPE_NAMES[Mod["module_type"]]
+ module_resp = User.query.get(modimpl.responsable_id)
+ mod_type_name = scu.MODULE_TYPE_NAMES[mod_dict["module_type"]]
H = [
html_sco_header.sco_header(
- page_title=f"{mod_type_name} {Mod['code']} {Mod['titre']}"
+ page_title=f"{mod_type_name} {mod_dict['code']} {mod_dict['titre']}",
+ javascripts=["js/etud_info.js"],
+ init_qtip=True,
),
f"""
+ scu.ModuleType(mod_dict['module_type']).name.lower()}">
Responsable: |
@@ -259,18 +260,14 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
({module_resp.user_name})
""",
]
- try:
- sco_moduleimpl.can_change_module_resp(moduleimpl_id)
+ if modimpl.can_change_ens_by(current_user):
H.append(
- """modifier"""
- % moduleimpl_id
+ f"""modifier"""
)
- except:
- pass
H.append(""" | """)
- H.append(
- ", ".join([sco_users.user_info(m["ens_id"])["nomprenom"] for m in M["ens"]])
- )
+ H.append(", ".join([u.get_nomprenom() for u in modimpl.enseignants]))
H.append(""" | """)
try:
sco_moduleimpl.can_change_ens(moduleimpl_id)
@@ -302,7 +299,8 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
H.append(""" | |
""")
# 3ieme ligne: Formation
H.append(
- """Formation: | %(titre)s |
""" % F
+ """Formation: | %(titre)s |
"""
+ % formation_dict
)
# Ligne: Inscrits
H.append(
@@ -312,15 +310,18 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
if current_user.has_permission(Permission.ScoEtudInscrit):
H.append(
"""modifier"""
- % M["moduleimpl_id"]
+ % mi_dict["moduleimpl_id"]
)
H.append("")
# Ligne: règle de calcul
- has_expression = sco_compute_moy.moduleimpl_has_expression(M)
+ has_expression = sco_compute_moy.moduleimpl_has_expression(mi_dict)
if has_expression:
H.append(
- 'Règle de calcul: moyenne=%s'
- % M["computation_expr"]
+ f""" |
+ Règle de calcul:
+ moyenne={mi_dict["computation_expr"]}
+ """
)
H.append("""inutilisée dans cette version de ScoDoc""")
if sco_moduleimpl.can_change_ens(moduleimpl_id, raise_exc=False):
@@ -380,20 +381,24 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
#
if formsemestre_has_decisions(formsemestre_id):
H.append(
- """- Décisions de jury saisies: seul le responsable du semestre peut saisir des notes (il devra modifier les décisions de jury).
"""
+ """
+ - Décisions de jury saisies: seul le responsable du
+ semestre peut saisir des notes (il devra modifier les décisions de jury).
+
+ """
)
#
H.append(
- """
"""
- % M
+ % mi_dict
)
# -------- Tableau des evaluations
top_table_links = ""
if can_edit_evals:
top_table_links = f"""Créer nouvelle évaluation
"""
if nb_evaluations > 0:
top_table_links += f"""
Trier par date
"""
@@ -477,31 +482,35 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
f""" |
+
+ {_html_modimpl_etuds_attente(nt, modimpl)}
+
+
Légende
-- {scu.icontag("edit_img")} : modifie description de l'évaluation
+
- {scu.icontag("edit_img")} : modifie description de l'évaluation
(date, heure, coefficient, ...)
- {scu.icontag("notes_img")} : saisie des notes
-- {scu.icontag("delete_img")} : indique qu'il n'y a aucune note
+
- {scu.icontag("delete_img")} : indique qu'il n'y a aucune note
entrée (cliquer pour supprimer cette évaluation)
-- {scu.icontag("status_orange_img")} : indique qu'il manque
+
- {scu.icontag("status_orange_img")} : indique qu'il manque
quelques notes dans cette évaluation
-- {scu.icontag("status_green_img")} : toutes les notes sont
+
- {scu.icontag("status_green_img")} : toutes les notes sont
entrées (cliquer pour les afficher)
-- {scu.icontag("status_visible_img")} : indique que cette évaluation
+
- {scu.icontag("status_visible_img")} : indique que cette évaluation
sera mentionnée dans les bulletins au format "intermédiaire"
-Rappel : seules les notes des évaluations complètement saisies
+
Rappel : seules les notes des évaluations complètement saisies
(affichées en vert) apparaissent dans les bulletins.
"""
@@ -844,3 +853,22 @@ def _evaluation_poids_html(evaluation: Evaluation, max_poids: float = 0.0) -> st
+ ""
)
return H
+
+
+def _html_modimpl_etuds_attente(res: ResultatsSemestre, modimpl: ModuleImpl) -> str:
+ """Affiche la liste des étudiants ayant au moins une note en attente dans ce modimpl"""
+ m_res = res.modimpls_results.get(modimpl.id)
+ if m_res:
+ if not m_res.etudids_attente:
+ return "Aucun étudiant n'a de notes en attente.
"
+ elif len(m_res.etudids_attente) < 10:
+ return f"""
+ Étudiants avec une note en attente :
+ {list_etuds.html_table_etuds(m_res.etudids_attente)}
+ """
+ else:
+ return f"""{
+ len(m_res.etudids_attente)
+ } étudiants ont des notes en attente.
"""
+
+ return ""
diff --git a/app/static/css/gt_table.css b/app/static/css/gt_table.css
index 450ff68f3a..d35ca909fc 100644
--- a/app/static/css/gt_table.css
+++ b/app/static/css/gt_table.css
@@ -641,4 +641,9 @@ table.dataTable.order-column.stripe.hover tbody tr.even:hover td.sorting_1 {
table.dataTable.gt_table {
width: auto;
padding-right: 5px;
+}
+
+/* Tables non centrées */
+table.dataTable.gt_table.gt_left {
+ margin-left: 16px;
}
\ No newline at end of file
diff --git a/app/tables/list_etuds.py b/app/tables/list_etuds.py
new file mode 100644
index 0000000000..175cf0726b
--- /dev/null
+++ b/app/tables/list_etuds.py
@@ -0,0 +1,117 @@
+##############################################################################
+# ScoDoc
+# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
+# See LICENSE
+##############################################################################
+
+"""Liste simple d'étudiants
+"""
+
+from flask import g, url_for
+from app.models import Identite
+from app.tables import table_builder as tb
+
+
+class TableEtud(tb.Table):
+ """Table listant des étudiants
+ Peut-être sous-classée pour ajouter des colonnes.
+ L'id de la ligne est etuid, et le row stocke etud.
+ """
+
+ def __init__(
+ self,
+ etuds: list[Identite] = None,
+ classes: list[str] = None,
+ row_class=None,
+ with_foot_titles=False,
+ **kwargs,
+ ):
+ self.rows: list["RowEtud"] = [] # juste pour que VSCode nous aide sur .rows
+ classes = classes or ["gt_table", "gt_left"]
+ super().__init__(
+ row_class=row_class or RowEtud,
+ classes=classes,
+ with_foot_titles=with_foot_titles,
+ **kwargs,
+ )
+ self.add_etuds(etuds)
+
+ def add_etuds(self, etuds: list[Identite]):
+ "Ajoute des étudiants à la table"
+ for etud in etuds:
+ row = self.row_class(self, etud)
+ row.add_etud_cols()
+ self.add_row(row)
+
+
+class RowEtud(tb.Row):
+ "Ligne de la table d'étudiants"
+ # pour le moment très simple, extensible (codes, liens bulletins, ...)
+ def __init__(self, table: TableEtud, etud: Identite, *args, **kwargs):
+ super().__init__(table, etud.id, *args, **kwargs)
+ self.etud = etud
+
+ def add_etud_cols(self):
+ """Ajoute colonnes étudiant: codes, noms"""
+ etud = self.etud
+ self.table.group_titles.update(
+ {
+ "etud_codes": "Codes",
+ "identite_detail": "",
+ "identite_court": "",
+ }
+ )
+ # --- Codes (seront cachés, mais exportés en excel)
+ # self.add_cell("etudid", "etudid", etud.id, "etud_codes")
+ # self.add_cell(
+ # "code_nip",
+ # "code_nip",
+ # etud.code_nip or "",
+ # "etud_codes",
+ # )
+
+ # --- Identité étudiant
+ # url_bulletin = url_for(
+ # "notes.formsemestre_bulletinetud",
+ # scodoc_dept=g.scodoc_dept,
+ # formsemestre_id=res.formsemestre.id,
+ # etudid=etud.id,
+ # )
+ url_bulletin = None # pour extension future
+ self.add_cell("civilite_str", "Civ.", etud.civilite_str, "identite_detail")
+ self.add_cell(
+ "nom_disp",
+ "Nom",
+ etud.nom_disp(),
+ "identite_detail",
+ data={"order": etud.sort_key},
+ target=url_bulletin,
+ target_attrs={"class": "etudinfo discretelink", "id": str(etud.id)},
+ )
+ self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail")
+ # self.add_cell(
+ # "nom_short",
+ # "Nom",
+ # etud.nom_short,
+ # "identite_court",
+ # data={
+ # "order": etud.sort_key,
+ # "etudid": etud.id,
+ # "nomprenom": etud.nomprenom,
+ # },
+ # target=url_bulletin,
+ # target_attrs={"class": "etudinfo", "id": str(etud.id)},
+ # )
+
+
+def etuds_sorted_from_ids(etudids) -> list[Identite]:
+ "Liste triée d'etuds à partir d'une collections d'etudids"
+ etuds = [Identite.query.get_or_404(etudid) for etudid in etudids]
+ return sorted(etuds, key=lambda etud: etud.sort_key)
+
+
+def html_table_etuds(etudids) -> str:
+ """Table HTML simple des étudiants indiqués"""
+ etuds = etuds_sorted_from_ids(etudids)
+ table = TableEtud(etuds)
+ return table.html()
diff --git a/app/tables/recap.py b/app/tables/recap.py
index 12f0fcc747..c48acced79 100644
--- a/app/tables/recap.py
+++ b/app/tables/recap.py
@@ -38,8 +38,6 @@ class TableRecap(tb.Table):
moy_sae__, ... les moyennes de SAE dans l'UE
On ajoute aussi des classes:
- - pour les lignes:
- selected_row pour l'étudiant sélectionné
- les colonnes:
- la moyenne générale a la classe col_moy_gen
- les colonnes SAE ont la classe col_sae
diff --git a/app/tables/table_builder.py b/app/tables/table_builder.py
index 50a61f559e..829f3642a5 100644
--- a/app/tables/table_builder.py
+++ b/app/tables/table_builder.py
@@ -68,6 +68,7 @@ class Table(Element):
classes: list[str] = None,
attrs: dict[str, str] = None,
data: dict = None,
+ with_foot_titles=True,
row_class=None,
xls_sheet_name="feuille",
xls_before_table=[], # liste de cellules a placer avant la table
@@ -100,8 +101,10 @@ class Table(Element):
self.head_title_row: "Row" = Row(
self, "title_head", cell_elt="th", classes=["titles"]
)
- self.foot_title_row: "Row" = Row(
- self, "title_foot", cell_elt="th", classes=["titles"]
+ self.foot_title_row: "Row" = (
+ Row(self, "title_foot", cell_elt="th", classes=["titles"])
+ if with_foot_titles
+ else None
)
self.empty_cell = Cell.empty()
# Excel (xls) spécifique:
@@ -119,8 +122,10 @@ class Table(Element):
"""
self.sort_columns()
# Titres
- self.add_head_row(self.head_title_row)
- self.add_foot_row(self.foot_title_row)
+ if self.head_title_row:
+ self.add_head_row(self.head_title_row)
+ if self.foot_title_row:
+ self.add_foot_row(self.foot_title_row)
def get_row_by_id(self, row_id) -> "Row":
"return the row, or None"
@@ -261,18 +266,23 @@ class Table(Element):
title = title or ""
if col_id not in self.titles:
self.titles[col_id] = title
- self.head_title_row.cells[col_id] = self.head_title_row.add_cell(
- col_id,
- None,
- title,
- classes=classes,
- group=self.column_group.get(col_id),
- )
- self.foot_title_row.cells[col_id] = self.foot_title_row.add_cell(
- col_id, None, title, classes=classes
- )
-
- return self.head_title_row.cells.get(col_id), self.foot_title_row.cells[col_id]
+ if self.head_title_row:
+ self.head_title_row.cells[col_id] = self.head_title_row.add_cell(
+ col_id,
+ None,
+ title,
+ classes=classes,
+ group=self.column_group.get(col_id),
+ )
+ if self.foot_title_row:
+ self.foot_title_row.cells[col_id] = self.foot_title_row.add_cell(
+ col_id, None, title, classes=classes
+ )
+ head_cell = (
+ self.head_title_row.cells.get(col_id) if self.head_title_row else None
+ )
+ foot_cell = self.foot_title_row.cells[col_id] if self.foot_title_row else None
+ return head_cell, foot_cell
def excel(self, wb: Workbook = None):
"""Simple Excel representation of the table."""
diff --git a/app/views/notes.py b/app/views/notes.py
index 75f02ab6de..7ac2834a9d 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -1043,15 +1043,18 @@ def edit_enseignants_form(moduleimpl_id):
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
-def edit_moduleimpl_resp(moduleimpl_id):
+def edit_moduleimpl_resp(moduleimpl_id: int):
"""Changement d'un enseignant responsable de module
Accessible par Admin et dir des etud si flag resp_can_change_ens
"""
- M, sem = sco_moduleimpl.can_change_module_resp(moduleimpl_id)
+ modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
+ modimpl.can_change_ens_by(current_user, raise_exc=True) # access control
H = [
html_sco_header.html_sem_header(
- 'Modification du responsable du module %s'
- % (moduleimpl_id, M["module"]["titre"]),
+ f"""Modification du responsable du module {modimpl.module.titre or ""}""",
javascripts=["libjs/AutoSuggest.js"],
cssstyles=["css/autosuggest_inquisitor.css"],
bodyOnLoad="init_tf_form('')",
@@ -1065,9 +1068,9 @@ def edit_moduleimpl_resp(moduleimpl_id):
uid2display[u["id"]] = u["nomplogin"]
allowed_user_names = list(uid2display.values())
- initvalues = M
+ initvalues = modimpl.to_dict(with_module=False)
initvalues["responsable_id"] = uid2display.get(
- M["responsable_id"], M["responsable_id"]
+ modimpl.responsable_id, modimpl.responsable_id
)
form = [
("moduleimpl_id", {"input_type": "hidden"}),
@@ -1112,9 +1115,8 @@ def edit_moduleimpl_resp(moduleimpl_id):
)
else:
responsable_id = User.get_user_id_from_nomplogin(tf[2]["responsable_id"])
- if (
- not responsable_id
- ): # presque impossible: tf verifie les valeurs (mais qui peuvent changer entre temps)
+ if not responsable_id:
+ # presque impossible: tf verifie les valeurs (mais qui peuvent changer entre temps)
return flask.redirect(
url_for(
"notes.moduleimpl_status",
@@ -1123,16 +1125,15 @@ def edit_moduleimpl_resp(moduleimpl_id):
)
)
- sco_moduleimpl.do_moduleimpl_edit(
- {"moduleimpl_id": moduleimpl_id, "responsable_id": responsable_id},
- formsemestre_id=sem["formsemestre_id"],
- )
+ modimpl.responsable_id = responsable_id
+ db.session.add(modimpl)
+ db.session.commit()
+ flash("Responsable modifié")
return flask.redirect(
url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=moduleimpl_id,
- head_message="responsable modifié",
)
)
diff --git a/sco_version.py b/sco_version.py
index b4ee5fb499..6561608c52 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.4.39"
+SCOVERSION = "9.4.40"
SCONAME = "ScoDoc"