Date: Tue, 4 Jan 2022 17:33:02 +0100
Subject: [PATCH 09/21] =?UTF-8?q?meilleure=20gestion=20des=20suppressions?=
=?UTF-8?q?=20d'objets=20dans=20l'=C3=A9dition=20des=20formations?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/__init__.py | 1 +
app/scodoc/sco_edit_apc.py | 3 +-
app/scodoc/sco_edit_matiere.py | 56 +++++++++----
app/scodoc/sco_edit_module.py | 53 ++++++++++---
app/scodoc/sco_edit_ue.py | 122 +++++++++++++++++++----------
app/scodoc/sco_exceptions.py | 27 +++++--
app/templates/pn/form_ues.html | 9 +--
app/templates/sco_value_error.html | 5 +-
8 files changed, 193 insertions(+), 83 deletions(-)
diff --git a/app/__init__.py b/app/__init__.py
index 0943a91f..04e28207 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -190,6 +190,7 @@ def create_app(config_class=DevConfig):
app.register_error_handler(ScoGenError, handle_sco_value_error)
app.register_error_handler(ScoValueError, handle_sco_value_error)
+
app.register_error_handler(AccessDenied, handle_access_denied)
app.register_error_handler(500, internal_server_error)
app.register_error_handler(503, postgresql_server_error)
diff --git a/app/scodoc/sco_edit_apc.py b/app/scodoc/sco_edit_apc.py
index 773cf81d..05b72b01 100644
--- a/app/scodoc/sco_edit_apc.py
+++ b/app/scodoc/sco_edit_apc.py
@@ -78,7 +78,8 @@ def html_edit_formation_apc(
alt="supprimer",
),
"delete_disabled": scu.icontag(
- "delete_small_dis_img", title="Suppression impossible (module utilisé)"
+ "delete_small_dis_img",
+ title="Suppression impossible (utilisé dans des semestres)",
),
}
diff --git a/app/scodoc/sco_edit_matiere.py b/app/scodoc/sco_edit_matiere.py
index 62a7e1d2..13ddb2a9 100644
--- a/app/scodoc/sco_edit_matiere.py
+++ b/app/scodoc/sco_edit_matiere.py
@@ -30,13 +30,18 @@
"""
import flask
from flask import g, url_for, request
+from app.models.formations import Matiere
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app import log
from app.models import Formation
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
-from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError
+from app.scodoc.sco_exceptions import (
+ ScoValueError,
+ ScoLockedFormError,
+ ScoNonEmptyFormationObject,
+)
from app.scodoc import html_sco_header
_matiereEditor = ndb.EditableTable(
@@ -156,6 +161,16 @@ associé.
return flask.redirect(dest_url)
+def can_delete_matiere(matiere: Matiere) -> tuple[bool, str]:
+ "True si la matiere n'est pas utilisée dans des formsemestre"
+ locked = matiere_is_locked(matiere.id)
+ if locked:
+ return False
+ if any(m.modimpls.all() for m in matiere.modules):
+ return False
+ return True
+
+
def do_matiere_delete(oid):
"delete matiere and attached modules"
from app.scodoc import sco_formations
@@ -165,17 +180,16 @@ def do_matiere_delete(oid):
cnx = ndb.GetDBConnexion()
# check
- mat = matiere_list({"matiere_id": oid})[0]
+ matiere = Matiere.query.get_or_404(oid)
+ mat = matiere_list({"matiere_id": oid})[0] # compat sco7
ue = sco_edit_ue.ue_list({"ue_id": mat["ue_id"]})[0]
- locked = matiere_is_locked(mat["matiere_id"])
- if locked:
- log("do_matiere_delete: mat=%s" % mat)
- log("do_matiere_delete: ue=%s" % ue)
- log("do_matiere_delete: locked sems: %s" % locked)
- raise ScoLockedFormError()
- log("do_matiere_delete: matiere_id=%s" % oid)
+ if not can_delete_matiere(matiere):
+ # il y a au moins un modimpl dans un module de cette matière
+ raise ScoNonEmptyFormationObject("Matière", matiere.titre)
+
+ log("do_matiere_delete: matiere_id=%s" % matiere.id)
# delete all modules in this matiere
- mods = sco_edit_module.module_list({"matiere_id": oid})
+ mods = sco_edit_module.module_list({"matiere_id": matiere.id})
for mod in mods:
sco_edit_module.do_module_delete(mod["module_id"])
_matiereEditor.delete(cnx, oid)
@@ -194,11 +208,25 @@ def matiere_delete(matiere_id=None):
"""Delete matière"""
from app.scodoc import sco_edit_ue
- M = matiere_list(args={"matiere_id": matiere_id})[0]
- UE = sco_edit_ue.ue_list(args={"ue_id": M["ue_id"]})[0]
+ matiere = Matiere.query.get_or_404(matiere_id)
+ if not can_delete_matiere(matiere):
+ # il y a au moins un modimpl dans un module de cette matière
+ raise ScoNonEmptyFormationObject(
+ "Matière",
+ matiere.titre,
+ dest_url=url_for(
+ "notes.ue_table",
+ formation_id=matiere.ue.formation_id,
+ semestre_idx=matiere.ue.semestre_idx,
+ scodoc_dept=g.scodoc_dept,
+ ),
+ )
+
+ mat = matiere_list(args={"matiere_id": matiere_id})[0]
+ UE = sco_edit_ue.ue_list(args={"ue_id": mat["ue_id"]})[0]
H = [
html_sco_header.sco_header(page_title="Suppression d'une matière"),
- "Suppression de la matière %(titre)s" % M,
+ "Suppression de la matière %(titre)s" % mat,
" dans l'UE (%(acronyme)s))
" % UE,
]
dest_url = url_for(
@@ -210,7 +238,7 @@ def matiere_delete(matiere_id=None):
request.base_url,
scu.get_request_args(),
(("matiere_id", {"input_type": "hidden"}),),
- initvalues=M,
+ initvalues=mat,
submitlabel="Confirmer la suppression",
cancelbutton="Annuler",
)
diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py
index 1c3481b0..813320d5 100644
--- a/app/scodoc/sco_edit_module.py
+++ b/app/scodoc/sco_edit_module.py
@@ -43,7 +43,12 @@ from app import models
from app.models import Formation
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_permissions import Permission
-from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError, ScoGenError
+from app.scodoc.sco_exceptions import (
+ ScoValueError,
+ ScoLockedFormError,
+ ScoGenError,
+ ScoNonEmptyFormationObject,
+)
from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_edit_matiere
@@ -330,20 +335,37 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None):
)
+def can_delete_module(module):
+ "True si le module n'est pas utilisée dans des formsemestre"
+ return len(module.modimpls.all()) == 0
+
+
def do_module_delete(oid):
"delete module"
from app.scodoc import sco_formations
- mod = module_list({"module_id": oid})[0]
- if module_is_locked(mod["module_id"]):
+ module = Module.query.get_or_404(oid)
+ mod = module_list({"module_id": oid})[0] # sco7
+ if module_is_locked(module.id):
raise ScoLockedFormError()
+ if not can_delete_module(module):
+ raise ScoNonEmptyFormationObject(
+ "Module",
+ msg=module.titre,
+ dest_url=url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=module.formation_id,
+ semestre_idx=module.ue.semestre_idx,
+ ),
+ )
# S'il y a des moduleimpls, on ne peut pas detruire le module !
mods = sco_moduleimpl.moduleimpl_list(module_id=oid)
if mods:
err_page = f"""Destruction du module impossible car il est utilisé dans des semestres existants !
-
Il faut d'abord supprimer le semestre. Mais il est peut être préférable de
- laisser ce programme intact et d'en créer une nouvelle version pour la modifier.
+
Il faut d'abord supprimer le semestre (ou en retirer ce module). Mais il est peut être préférable de
+ laisser ce programme intact et d'en créer une nouvelle version pour la modifier sans affecter les semestres déjà en place.
reprendre
@@ -365,12 +387,21 @@ def do_module_delete(oid):
def module_delete(module_id=None):
"""Delete a module"""
- if not module_id:
- raise ScoValueError("invalid module !")
- modules = module_list(args={"module_id": module_id})
- if not modules:
- raise ScoValueError("Module inexistant !")
- mod = modules[0]
+ module = Module.query.get_or_404(module_id)
+ mod = module_list(args={"module_id": module_id})[0] # sco7
+
+ if not can_delete_module(module):
+ raise ScoNonEmptyFormationObject(
+ "Module",
+ msg=module.titre,
+ dest_url=url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=module.formation_id,
+ semestre_idx=module.ue.semestre_idx,
+ ),
+ )
+
H = [
html_sco_header.sco_header(page_title="Suppression d'un module"),
"""Suppression du module %(titre)s (%(code)s)
""" % mod,
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index bdf37375..c93bdb1b 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -42,7 +42,12 @@ from app import log
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_permissions import Permission
-from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError
+from app.scodoc.sco_exceptions import (
+ ScoGenError,
+ ScoValueError,
+ ScoLockedFormError,
+ ScoNonEmptyFormationObject,
+)
from app.scodoc import html_sco_header
from app.scodoc import sco_cache
@@ -130,61 +135,80 @@ def do_ue_create(args):
return ue_id
+def can_delete_ue(ue: UniteEns) -> bool:
+ """True si l'UE n'est pas utilisée dans des formsemestre
+ et n'a pas de module rattachés
+ """
+ # "pas un seul module de cette UE n'a de modimpl...""
+ return (not len(ue.modules.all())) and not any(m.modimpls.all() for m in ue.modules)
+
+
def do_ue_delete(ue_id, delete_validations=False, force=False):
"delete UE and attached matieres (but not modules)"
from app.scodoc import sco_formations
from app.scodoc import sco_parcours_dut
+ ue = UniteEns.query.get_or_404(ue_id)
+ if not can_delete_ue(ue):
+ raise ScoNonEmptyFormationObject(
+ "UE",
+ msg=ue.titre,
+ dest_url=url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=ue.formation_id,
+ semestre_idx=ue.semestre_idx,
+ ),
+ )
+
cnx = ndb.GetDBConnexion()
- log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue_id, delete_validations))
+ log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue.id, delete_validations))
# check
- ue = ue_list({"ue_id": ue_id})
- if not ue:
- raise ScoValueError("UE inexistante !")
- ue = ue[0]
- if ue_is_locked(ue["ue_id"]):
- raise ScoLockedFormError()
+ # if ue_is_locked(ue.id):
+ # raise ScoLockedFormError()
# Il y a-t-il des etudiants ayant validé cette UE ?
# si oui, propose de supprimer les validations
validations = sco_parcours_dut.scolar_formsemestre_validation_list(
- cnx, args={"ue_id": ue_id}
+ cnx, args={"ue_id": ue.id}
)
if validations and not delete_validations and not force:
return scu.confirm_dialog(
"%d étudiants ont validé l'UE %s (%s)
Si vous supprimez cette UE, ces validations vont être supprimées !
"
- % (len(validations), ue["acronyme"], ue["titre"]),
+ % (len(validations), ue.acronyme, ue.titre),
dest_url="",
target_variable="delete_validations",
cancel_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
- formation_id=str(ue["formation_id"]),
+ formation_id=ue.formation_id,
+ semestre_idx=ue.semestre_idx,
),
- parameters={"ue_id": ue_id, "dialog_confirmed": 1},
+ parameters={"ue_id": ue.id, "dialog_confirmed": 1},
)
if delete_validations:
- log("deleting all validations of UE %s" % ue_id)
+ log("deleting all validations of UE %s" % ue.id)
ndb.SimpleQuery(
"DELETE FROM scolar_formsemestre_validation WHERE ue_id=%(ue_id)s",
- {"ue_id": ue_id},
+ {"ue_id": ue.id},
)
# delete all matiere in this UE
- mats = sco_edit_matiere.matiere_list({"ue_id": ue_id})
+ mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})
for mat in mats:
sco_edit_matiere.do_matiere_delete(mat["matiere_id"])
# delete uecoef and events
ndb.SimpleQuery(
"DELETE FROM notes_formsemestre_uecoef WHERE ue_id=%(ue_id)s",
- {"ue_id": ue_id},
+ {"ue_id": ue.id},
)
- ndb.SimpleQuery("DELETE FROM scolar_events WHERE ue_id=%(ue_id)s", {"ue_id": ue_id})
+ ndb.SimpleQuery("DELETE FROM scolar_events WHERE ue_id=%(ue_id)s", {"ue_id": ue.id})
cnx = ndb.GetDBConnexion()
- _ueEditor.delete(cnx, ue_id)
- # > UE delete + supr. validations associées etudiants (cas compliqué, mais rarement utilisé: acceptable de tout invalider ?):
+ _ueEditor.delete(cnx, ue.id)
+ # > UE delete + supr. validations associées etudiants (cas compliqué, mais rarement
+ # utilisé: acceptable de tout invalider):
sco_cache.invalidate_formsemestre()
# news
- F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
+ F = sco_formations.formation_list(args={"formation_id": ue.formation_id})[0]
sco_news.add(
typ=sco_news.NEWS_FORM,
object=ue["formation_id"],
@@ -198,10 +222,10 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=ue["formation_id"],
+ semestre_idx=ue.semestre_idx,
)
)
- else:
- return None
+ return None
def ue_create(formation_id=None):
@@ -211,8 +235,6 @@ def ue_create(formation_id=None):
def ue_edit(ue_id=None, create=False, formation_id=None):
"""Modification ou création d'une UE"""
- from app.scodoc import sco_formations
-
create = int(create)
if not create:
U = ue_list(args={"ue_id": ue_id})
@@ -444,24 +466,38 @@ def next_ue_numero(formation_id, semestre_id=None):
def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
"""Delete an UE"""
- ues = ue_list(args={"ue_id": ue_id})
- if not ues:
- raise ScoValueError("UE inexistante !")
- ue = ues[0]
-
- if not dialog_confirmed:
- return scu.confirm_dialog(
- "Suppression de l'UE %(titre)s (%(acronyme)s))
" % ue,
- dest_url="",
- parameters={"ue_id": ue_id},
- cancel_url=url_for(
+ ue = UniteEns.query.get_or_404(ue_id)
+ if ue.modules.all():
+ raise ScoValueError(
+ f"""Suppression de l'UE {ue.titre} impossible car
+ des modules (ou SAÉ ou ressources) lui sont rattachés."""
+ )
+ if not can_delete_ue(ue):
+ raise ScoNonEmptyFormationObject(
+ "UE",
+ msg=ue.titre,
+ dest_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
- formation_id=str(ue["formation_id"]),
+ formation_id=ue.formation_id,
+ semestre_idx=ue.semestre_idx,
),
)
- return do_ue_delete(ue_id, delete_validations=delete_validations)
+ if not dialog_confirmed:
+ return scu.confirm_dialog(
+ f"Suppression de l'UE {ue.titre} ({ue.acronyme})
",
+ dest_url="",
+ parameters={"ue_id": ue.id},
+ cancel_url=url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=ue.formation_id,
+ semestre_idx=ue.semestre_idx,
+ ),
+ )
+
+ return do_ue_delete(ue.id, delete_validations=delete_validations)
def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
@@ -1010,12 +1046,14 @@ def _ue_table_modules(
H.append(arrow_none)
im += 1
if mod["nb_moduleimpls"] == 0 and editable:
- H.append(
- '%s'
- % (mod["module_id"], delete_icon)
- )
+ icon = delete_icon
else:
- H.append(delete_disabled_icon)
+ icon = delete_disabled_icon
+ H.append(
+ '%s'
+ % (mod["module_id"], icon)
+ )
+
H.append("")
mod_editable = (
diff --git a/app/scodoc/sco_exceptions.py b/app/scodoc/sco_exceptions.py
index 635a724b..1099986b 100644
--- a/app/scodoc/sco_exceptions.py
+++ b/app/scodoc/sco_exceptions.py
@@ -52,7 +52,7 @@ class InvalidNoteValue(ScoException):
# Exception qui stoque dest_url, utilisee dans Zope standard_error_message
class ScoValueError(ScoException):
def __init__(self, msg, dest_url=None):
- ScoException.__init__(self, msg)
+ super().__init__(msg)
self.dest_url = dest_url
@@ -72,20 +72,35 @@ class ScoConfigurationError(ScoValueError):
pass
-class ScoLockedFormError(ScoException):
- def __init__(self, msg=""):
+class ScoLockedFormError(ScoValueError):
+ "Modification d'une formation verrouillée"
+
+ def __init__(self, msg="", dest_url=None):
msg = (
"Cette formation est verrouillée (car il y a un semestre verrouillé qui s'y réfère). "
+ str(msg)
)
- ScoException.__init__(self, 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"""
+
+ def __init__(self, type_objet="objet'", msg="", dest_url=None):
+ msg = f"""{type_objet} "{msg}" utilisé dans des semestres: suppression impossible.
+ Il faut d'abord supprimer le semestre (ou en retirer ce {type_objet}).
+ Mais il est peut-être préférable de laisser ce programme intact et d'en créer une
+ nouvelle version pour la modifier sans affecter les semestres déjà en place.
+
+ """
+ super().__init__(msg=msg, dest_url=dest_url)
class ScoGenError(ScoException):
"exception avec affichage d'une page explicative ad-hoc"
def __init__(self, msg=""):
- ScoException.__init__(self, msg)
+ super().__init__(msg)
class AccessDenied(ScoGenError):
@@ -101,7 +116,7 @@ class APIInvalidParams(Exception):
status_code = 400
def __init__(self, message, status_code=None, payload=None):
- Exception.__init__(self)
+ super().__init__()
self.message = message
if status_code is not None:
self.status_code = status_code
diff --git a/app/templates/pn/form_ues.html b/app/templates/pn/form_ues.html
index 69d69839..12e03973 100644
--- a/app/templates/pn/form_ues.html
+++ b/app/templates/pn/form_ues.html
@@ -23,14 +23,11 @@
{{icons.arrow_none|safe}}
{% endif %}
- {% if editable and not ue.modules.count() %}
+
{{icons.delete|safe}}
- {% else %}
- {{icons.delete_disabled|safe}}
- {% endif %}
-
+ }}">{% if editable and not ue.modules.count() %}{{icons.delete|safe}}{% else %}{{icons.delete_disabled|safe}}{% endif %}
+
{{ue.acronyme}} {{ue.titre}}
diff --git a/app/templates/sco_value_error.html b/app/templates/sco_value_error.html
index 9b7f3b54..3c464842 100644
--- a/app/templates/sco_value_error.html
+++ b/app/templates/sco_value_error.html
@@ -7,10 +7,9 @@
{{ exc | safe }}
-
{% if g.scodoc_dept %}
- retour page d'accueil
- departement {{ g.scodoc_dept }}
+ continuer
{% else %}
retour page d'accueil
{% endif %}
From 6fe77988a0283c2fe1d8209c4823bf68bf7c90dc Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Tue, 4 Jan 2022 17:44:56 +0100
Subject: [PATCH 10/21] Fix: liste des indices de semestres
---
app/scodoc/sco_formsemestre_edit.py | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index 6c889c54..060289e9 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -198,10 +198,13 @@ def do_formsemestre_createwithmodules(edit=False):
NB_SEM = parcours.NB_SEM
else:
NB_SEM = 10 # fallback, max 10 semestres
- semestre_id_list = [-1] + list(range(1, NB_SEM + 1))
+ if NB_SEM == 1:
+ semestre_id_list = [-1]
+ else:
+ semestre_id_list = [-1] + list(range(1, NB_SEM + 1))
semestre_id_labels = []
for sid in semestre_id_list:
- if sid == "-1":
+ if sid == -1:
semestre_id_labels.append("pas de semestres")
else:
semestre_id_labels.append(f"S{sid}")
@@ -329,6 +332,8 @@ def do_formsemestre_createwithmodules(edit=False):
"labels": modalites_titles,
},
),
+ ]
+ modform.append(
(
"semestre_id",
{
@@ -338,7 +343,7 @@ def do_formsemestre_createwithmodules(edit=False):
"labels": semestre_id_labels,
},
),
- ]
+ )
etapes = sco_portal_apogee.get_etapes_apogee_dept()
# Propose les etapes renvoyées par le portail
# et ajoute les étapes du semestre qui ne sont pas dans la liste (soit la liste a changé, soit l'étape a été ajoutée manuellement)
From 9b9b2f270b2be860ec07bce7d8f65f07b9117237 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Tue, 4 Jan 2022 17:49:13 +0100
Subject: [PATCH 11/21] Fix: flag publication bulletins
---
app/but/bulletin_but_xml_compat.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/but/bulletin_but_xml_compat.py b/app/but/bulletin_but_xml_compat.py
index 4307788d..8582bed7 100644
--- a/app/but/bulletin_but_xml_compat.py
+++ b/app/but/bulletin_but_xml_compat.py
@@ -72,7 +72,7 @@ def bulletin_but_xml_compat(
etud = Identite.query.get_or_404(etudid)
results = bulletin_but.ResultatsSemestreBUT(sem)
nb_inscrits = len(results.etuds)
- if sem.bul_hide_xml or force_publishing:
+ if (not sem.bul_hide_xml) or force_publishing:
published = "1"
else:
published = "0"
From be2227f8a353f9533db8d0a094b3dc23dcfb5505 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Tue, 4 Jan 2022 18:10:14 +0100
Subject: [PATCH 12/21] =?UTF-8?q?L'admin=20peut=20cacher/montrer=20des=20d?=
=?UTF-8?q?=C3=A9partements?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/forms/main/config_forms.py | 2 +-
app/forms/main/create_dept.py | 14 ++++++--------
app/models/departements.py | 4 ++--
app/templates/scodoc.html | 10 ++++++++++
app/views/scodoc.py | 18 ++++++++++++++----
5 files changed, 33 insertions(+), 15 deletions(-)
diff --git a/app/forms/main/config_forms.py b/app/forms/main/config_forms.py
index 03589795..16be8451 100644
--- a/app/forms/main/config_forms.py
+++ b/app/forms/main/config_forms.py
@@ -245,7 +245,7 @@ class DeptForm(FlaskForm):
def _make_dept_id_name():
- """Cette section assute que tous les départements sont traités (y compris ceux qu'ont pas de logo au départ)
+ """Cette section assure que tous les départements sont traités (y compris ceux qu'ont pas de logo au départ)
et détermine l'ordre d'affichage des DeptForm (GLOBAL d'abord, puis par ordre alpha de nom de département)
-> [ (None, None), (dept_id, dept_name)... ]"""
depts = [(None, GLOBAL)]
diff --git a/app/forms/main/create_dept.py b/app/forms/main/create_dept.py
index 7bc26b42..d1e143be 100644
--- a/app/forms/main/create_dept.py
+++ b/app/forms/main/create_dept.py
@@ -31,16 +31,10 @@ Formulaires création département
from flask import flash, url_for, redirect, render_template
from flask_wtf import FlaskForm
-from wtforms import SelectField, SubmitField, FormField, validators, FieldList
-from wtforms.fields.simple import StringField, HiddenField
+from wtforms import SubmitField, validators
+from wtforms.fields.simple import StringField, BooleanField
-from app import AccessDenied
-from app.models import Departement
-from app.models import ScoPreference
from app.models import SHORT_STR_LEN
-from app.scodoc import sco_utils as scu
-
-from flask_login import current_user
class CreateDeptForm(FlaskForm):
@@ -60,5 +54,9 @@ class CreateDeptForm(FlaskForm):
validators.DataRequired("acronyme du département requis"),
],
)
+ visible = BooleanField(
+ "Visible sur page d'accueil",
+ default=True,
+ )
submit = SubmitField("Valider")
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
diff --git a/app/models/departements.py b/app/models/departements.py
index 0734e35b..d83ccfc2 100644
--- a/app/models/departements.py
+++ b/app/models/departements.py
@@ -49,11 +49,11 @@ class Departement(db.Model):
return dept
-def create_dept(acronym: str) -> Departement:
+def create_dept(acronym: str, visible=True) -> Departement:
"Create new departement"
from app.models import ScoPreference
- departement = Departement(acronym=acronym)
+ departement = Departement(acronym=acronym, visible=visible)
p1 = ScoPreference(name="DeptName", value=acronym, departement=departement)
db.session.add(p1)
db.session.add(departement)
diff --git a/app/templates/scodoc.html b/app/templates/scodoc.html
index 2aaf9e22..23667c24 100644
--- a/app/templates/scodoc.html
+++ b/app/templates/scodoc.html
@@ -12,11 +12,21 @@
{% for dept in depts %}
+ {% if dept.visible or current_user.is_administrator() %}
-
Département
{{dept.preferences.filter_by(name="DeptName").first().value}}
+ {% if current_user.is_administrator() %}
+
+ {% if dept.visible %}visible{% else %}caché aux utilisateurs{% endif %}
+
+ {% if dept.visible %}cacher{% else %}rendre visible{% endif %}
+
+
+ {% endif %}
+ {% endif %}
{% else %}
-
Aucun département défini !
diff --git a/app/views/scodoc.py b/app/views/scodoc.py
index 9377dc56..3e2fdbdc 100644
--- a/app/views/scodoc.py
+++ b/app/views/scodoc.py
@@ -53,6 +53,7 @@ from wtforms.fields.simple import BooleanField, StringField, TextAreaField, Hidd
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
import app
+from app import db
from app.forms.main import config_forms
from app.forms.main.create_dept import CreateDeptForm
from app.models import Departement, Identite
@@ -82,9 +83,7 @@ from PIL import Image as PILImage
@bp.route("/ScoDoc/index")
def index():
"Page d'accueil: liste des départements"
- depts = (
- Departement.query.filter_by(visible=True).order_by(Departement.acronym).all()
- )
+ depts = Departement.query.filter_by().order_by(Departement.acronym).all()
return render_template(
"scodoc.html",
title=sco_version.SCONAME,
@@ -108,7 +107,7 @@ def create_dept():
if request.method == "POST" and form.cancel.data: # cancel button
return redirect(url_for("scodoc.index"))
if form.validate_on_submit():
- departements.create_dept(form.acronym.data)
+ departements.create_dept(form.acronym.data, visible=form.visible.data)
flash(f"Département {form.acronym.data} créé.")
return redirect(url_for("scodoc.index"))
return render_template(
@@ -118,6 +117,17 @@ def create_dept():
)
+@bp.route("/ScoDoc/toggle_dept_vis/", methods=["GET", "POST"])
+@admin_required
+def toggle_dept_vis(dept_id):
+ """Cache ou rend visible un dept"""
+ dept = Departement.query.get_or_404(dept_id)
+ dept.visible = not dept.visible
+ db.session.add(dept)
+ db.session.commit()
+ return redirect(url_for("scodoc.index"))
+
+
@bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"])
@login_required
def table_etud_in_accessible_depts():
From e1adf93bf0d2edfccf0779041f460f7a4bc5978c Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Tue, 4 Jan 2022 19:32:58 +0100
Subject: [PATCH 13/21] Fix: edition des users sans dept
---
app/views/users.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/app/views/users.py b/app/views/users.py
index fee36edf..21c992fd 100644
--- a/app/views/users.py
+++ b/app/views/users.py
@@ -152,7 +152,6 @@ def user_info(user_name, format="json"):
def create_user_form(user_name=None, edit=0, all_roles=1):
"form. création ou edition utilisateur"
auth_dept = current_user.dept
- auth_username = current_user.user_name
from_mail = current_user.email
initvalues = {}
edit = int(edit)
@@ -204,7 +203,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
administrable_dept_acronyms = sorted(
set(
[
- x.dept
+ x.dept or ""
for x in UserRole.query.filter_by(user=current_user)
if x.role.has_permission(Permission.ScoUsersAdmin) and x.dept
]
@@ -249,7 +248,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
r.name + "_" + (dept or "") for (r, dept) in displayed_roles
]
displayed_roles_labels = [f"{dept}: {r.name}" for (r, dept) in displayed_roles]
- disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer
+ disabled_roles = {} # pour désactiver les roles que l'on ne peut pas éditer
for i in range(len(displayed_roles_strings)):
if displayed_roles_strings[i] not in editable_roles_strings:
disabled_roles[i] = True
@@ -375,7 +374,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
can_choose_dept = True
else:
selectable_dept_acronyms = set(administrable_dept_acronyms)
- if edit: # ajoute dept actuel de l'utilisateur
+ if edit and the_user.dept is not None: # ajoute dept actuel de l'utilisateur
selectable_dept_acronyms |= {the_user.dept}
if len(selectable_dept_acronyms) > 1:
can_choose_dept = True
From e3535aa4dacd897a677fe6b6c86215b3c47031fc Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Tue, 4 Jan 2022 19:46:35 +0100
Subject: [PATCH 14/21] =?UTF-8?q?check=5Fmoduleimpl=5Fconformity:=20modif?=
=?UTF-8?q?=20exc=20incoh=C3=A9rence?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/comp/moy_mod.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py
index 41000fd4..f21e6acd 100644
--- a/app/comp/moy_mod.py
+++ b/app/comp/moy_mod.py
@@ -36,6 +36,7 @@ import pandas as pd
from pandas.core.frame import DataFrame
from app import db
+from app import log
from app import models
from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
from app.scodoc import sco_utils as scu
@@ -79,7 +80,11 @@ def check_moduleimpl_conformity(
if nb_ues == 0:
return False # situation absurde (pas d'UE)
if len(modules_coefficients) != nb_ues:
- raise ValueError("check_moduleimpl_conformity: nb ue incoherent")
+ # bug ?
+ log(
+ "check_moduleimpl_conformity: nb ue incoherent (moduleimpl.id={moduleimpl.id})"
+ )
+ return False
module_evals_poids = evals_poids.transpose().sum(axis=1).to_numpy() != 0
check = all(
(modules_coefficients[moduleimpl.module.id].to_numpy() != 0)
From 82e3de02f6349b8a87afb290a760af427e0d912b Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Tue, 4 Jan 2022 20:03:38 +0100
Subject: [PATCH 15/21] update
---
app/__init__.py | 1 +
app/auth/models.py | 13 +--
app/but/bulletin_but_xml_compat.py | 2 +-
app/forms/main/config_forms.py | 2 +-
app/forms/main/create_dept.py | 14 ++--
app/models/departements.py | 4 +-
app/scodoc/sco_edit_apc.py | 3 +-
app/scodoc/sco_edit_matiere.py | 56 +++++++++----
app/scodoc/sco_edit_module.py | 53 +++++++++---
app/scodoc/sco_edit_ue.py | 126 ++++++++++++++++++----------
app/scodoc/sco_exceptions.py | 27 ++++--
app/scodoc/sco_formsemestre_edit.py | 11 ++-
app/scodoc/sco_utils.py | 1 +
app/templates/pn/form_ues.html | 9 +-
app/templates/sco_value_error.html | 5 +-
app/templates/scodoc.html | 10 +++
app/views/scodoc.py | 18 +++-
app/views/users.py | 7 +-
tests/unit/test_formations.py | 12 ---
19 files changed, 249 insertions(+), 125 deletions(-)
diff --git a/app/__init__.py b/app/__init__.py
index 0ea6bb52..0a589144 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -190,6 +190,7 @@ def create_app(config_class=DevConfig):
app.register_error_handler(ScoGenError, handle_sco_value_error)
app.register_error_handler(ScoValueError, handle_sco_value_error)
+
app.register_error_handler(AccessDenied, handle_access_denied)
app.register_error_handler(500, internal_server_error)
app.register_error_handler(503, postgresql_server_error)
diff --git a/app/auth/models.py b/app/auth/models.py
index bc4edb65..8f187b7e 100644
--- a/app/auth/models.py
+++ b/app/auth/models.py
@@ -11,7 +11,7 @@ from time import time
from typing import Optional
import cracklib # pylint: disable=import-error
-from flask import current_app, url_for, g
+from flask import current_app, g
from flask_login import UserMixin, AnonymousUserMixin
from werkzeug.security import generate_password_hash, check_password_hash
@@ -136,6 +136,7 @@ class User(UserMixin, db.Model):
return check_password_hash(self.password_hash, password)
def get_reset_password_token(self, expires_in=600):
+ "Un token pour réinitialiser son mot de passe"
return jwt.encode(
{"reset_password": self.id, "exp": time() + expires_in},
current_app.config["SECRET_KEY"],
@@ -144,15 +145,17 @@ class User(UserMixin, db.Model):
@staticmethod
def verify_reset_password_token(token):
+ "Vérification du token de reéinitialisation du mot de passe"
try:
- id = jwt.decode(
+ user_id = jwt.decode(
token, current_app.config["SECRET_KEY"], algorithms=["HS256"]
)["reset_password"]
except:
return
- return User.query.get(id)
+ return User.query.get(user_id)
def to_dict(self, include_email=True):
+ """l'utilisateur comme un dict, avec des champs supplémentaires"""
data = {
"date_expiration": self.date_expiration.isoformat() + "Z"
if self.date_expiration
@@ -472,5 +475,5 @@ def get_super_admin():
@login.user_loader
-def load_user(id):
- return User.query.get(int(id))
+def load_user(uid):
+ return User.query.get(int(uid))
diff --git a/app/but/bulletin_but_xml_compat.py b/app/but/bulletin_but_xml_compat.py
index 9743c218..69fc4ef1 100644
--- a/app/but/bulletin_but_xml_compat.py
+++ b/app/but/bulletin_but_xml_compat.py
@@ -72,7 +72,7 @@ def bulletin_but_xml_compat(
etud = Identite.query.get_or_404(etudid)
results = bulletin_but.ResultatsSemestreBUT(sem)
nb_inscrits = len(results.etuds)
- if sem.bul_hide_xml or force_publishing:
+ if (not sem.bul_hide_xml) or force_publishing:
published = "1"
else:
published = "0"
diff --git a/app/forms/main/config_forms.py b/app/forms/main/config_forms.py
index 03589795..16be8451 100644
--- a/app/forms/main/config_forms.py
+++ b/app/forms/main/config_forms.py
@@ -245,7 +245,7 @@ class DeptForm(FlaskForm):
def _make_dept_id_name():
- """Cette section assute que tous les départements sont traités (y compris ceux qu'ont pas de logo au départ)
+ """Cette section assure que tous les départements sont traités (y compris ceux qu'ont pas de logo au départ)
et détermine l'ordre d'affichage des DeptForm (GLOBAL d'abord, puis par ordre alpha de nom de département)
-> [ (None, None), (dept_id, dept_name)... ]"""
depts = [(None, GLOBAL)]
diff --git a/app/forms/main/create_dept.py b/app/forms/main/create_dept.py
index 7bc26b42..d1e143be 100644
--- a/app/forms/main/create_dept.py
+++ b/app/forms/main/create_dept.py
@@ -31,16 +31,10 @@ Formulaires création département
from flask import flash, url_for, redirect, render_template
from flask_wtf import FlaskForm
-from wtforms import SelectField, SubmitField, FormField, validators, FieldList
-from wtforms.fields.simple import StringField, HiddenField
+from wtforms import SubmitField, validators
+from wtforms.fields.simple import StringField, BooleanField
-from app import AccessDenied
-from app.models import Departement
-from app.models import ScoPreference
from app.models import SHORT_STR_LEN
-from app.scodoc import sco_utils as scu
-
-from flask_login import current_user
class CreateDeptForm(FlaskForm):
@@ -60,5 +54,9 @@ class CreateDeptForm(FlaskForm):
validators.DataRequired("acronyme du département requis"),
],
)
+ visible = BooleanField(
+ "Visible sur page d'accueil",
+ default=True,
+ )
submit = SubmitField("Valider")
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
diff --git a/app/models/departements.py b/app/models/departements.py
index 7ed2f4b5..a8c1bb1e 100644
--- a/app/models/departements.py
+++ b/app/models/departements.py
@@ -49,11 +49,11 @@ class Departement(db.Model):
return dept
-def create_dept(acronym: str) -> Departement:
+def create_dept(acronym: str, visible=True) -> Departement:
"Create new departement"
from app.models import ScoPreference
- departement = Departement(acronym=acronym)
+ departement = Departement(acronym=acronym, visible=visible)
p1 = ScoPreference(name="DeptName", value=acronym, departement=departement)
db.session.add(p1)
db.session.add(departement)
diff --git a/app/scodoc/sco_edit_apc.py b/app/scodoc/sco_edit_apc.py
index 773cf81d..05b72b01 100644
--- a/app/scodoc/sco_edit_apc.py
+++ b/app/scodoc/sco_edit_apc.py
@@ -78,7 +78,8 @@ def html_edit_formation_apc(
alt="supprimer",
),
"delete_disabled": scu.icontag(
- "delete_small_dis_img", title="Suppression impossible (module utilisé)"
+ "delete_small_dis_img",
+ title="Suppression impossible (utilisé dans des semestres)",
),
}
diff --git a/app/scodoc/sco_edit_matiere.py b/app/scodoc/sco_edit_matiere.py
index 62a7e1d2..13ddb2a9 100644
--- a/app/scodoc/sco_edit_matiere.py
+++ b/app/scodoc/sco_edit_matiere.py
@@ -30,13 +30,18 @@
"""
import flask
from flask import g, url_for, request
+from app.models.formations import Matiere
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app import log
from app.models import Formation
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
-from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError
+from app.scodoc.sco_exceptions import (
+ ScoValueError,
+ ScoLockedFormError,
+ ScoNonEmptyFormationObject,
+)
from app.scodoc import html_sco_header
_matiereEditor = ndb.EditableTable(
@@ -156,6 +161,16 @@ associé.
return flask.redirect(dest_url)
+def can_delete_matiere(matiere: Matiere) -> tuple[bool, str]:
+ "True si la matiere n'est pas utilisée dans des formsemestre"
+ locked = matiere_is_locked(matiere.id)
+ if locked:
+ return False
+ if any(m.modimpls.all() for m in matiere.modules):
+ return False
+ return True
+
+
def do_matiere_delete(oid):
"delete matiere and attached modules"
from app.scodoc import sco_formations
@@ -165,17 +180,16 @@ def do_matiere_delete(oid):
cnx = ndb.GetDBConnexion()
# check
- mat = matiere_list({"matiere_id": oid})[0]
+ matiere = Matiere.query.get_or_404(oid)
+ mat = matiere_list({"matiere_id": oid})[0] # compat sco7
ue = sco_edit_ue.ue_list({"ue_id": mat["ue_id"]})[0]
- locked = matiere_is_locked(mat["matiere_id"])
- if locked:
- log("do_matiere_delete: mat=%s" % mat)
- log("do_matiere_delete: ue=%s" % ue)
- log("do_matiere_delete: locked sems: %s" % locked)
- raise ScoLockedFormError()
- log("do_matiere_delete: matiere_id=%s" % oid)
+ if not can_delete_matiere(matiere):
+ # il y a au moins un modimpl dans un module de cette matière
+ raise ScoNonEmptyFormationObject("Matière", matiere.titre)
+
+ log("do_matiere_delete: matiere_id=%s" % matiere.id)
# delete all modules in this matiere
- mods = sco_edit_module.module_list({"matiere_id": oid})
+ mods = sco_edit_module.module_list({"matiere_id": matiere.id})
for mod in mods:
sco_edit_module.do_module_delete(mod["module_id"])
_matiereEditor.delete(cnx, oid)
@@ -194,11 +208,25 @@ def matiere_delete(matiere_id=None):
"""Delete matière"""
from app.scodoc import sco_edit_ue
- M = matiere_list(args={"matiere_id": matiere_id})[0]
- UE = sco_edit_ue.ue_list(args={"ue_id": M["ue_id"]})[0]
+ matiere = Matiere.query.get_or_404(matiere_id)
+ if not can_delete_matiere(matiere):
+ # il y a au moins un modimpl dans un module de cette matière
+ raise ScoNonEmptyFormationObject(
+ "Matière",
+ matiere.titre,
+ dest_url=url_for(
+ "notes.ue_table",
+ formation_id=matiere.ue.formation_id,
+ semestre_idx=matiere.ue.semestre_idx,
+ scodoc_dept=g.scodoc_dept,
+ ),
+ )
+
+ mat = matiere_list(args={"matiere_id": matiere_id})[0]
+ UE = sco_edit_ue.ue_list(args={"ue_id": mat["ue_id"]})[0]
H = [
html_sco_header.sco_header(page_title="Suppression d'une matière"),
- "
Suppression de la matière %(titre)s" % M,
+ "Suppression de la matière %(titre)s" % mat,
" dans l'UE (%(acronyme)s))
" % UE,
]
dest_url = url_for(
@@ -210,7 +238,7 @@ def matiere_delete(matiere_id=None):
request.base_url,
scu.get_request_args(),
(("matiere_id", {"input_type": "hidden"}),),
- initvalues=M,
+ initvalues=mat,
submitlabel="Confirmer la suppression",
cancelbutton="Annuler",
)
diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py
index 1c3481b0..813320d5 100644
--- a/app/scodoc/sco_edit_module.py
+++ b/app/scodoc/sco_edit_module.py
@@ -43,7 +43,12 @@ from app import models
from app.models import Formation
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_permissions import Permission
-from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError, ScoGenError
+from app.scodoc.sco_exceptions import (
+ ScoValueError,
+ ScoLockedFormError,
+ ScoGenError,
+ ScoNonEmptyFormationObject,
+)
from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_edit_matiere
@@ -330,20 +335,37 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None):
)
+def can_delete_module(module):
+ "True si le module n'est pas utilisée dans des formsemestre"
+ return len(module.modimpls.all()) == 0
+
+
def do_module_delete(oid):
"delete module"
from app.scodoc import sco_formations
- mod = module_list({"module_id": oid})[0]
- if module_is_locked(mod["module_id"]):
+ module = Module.query.get_or_404(oid)
+ mod = module_list({"module_id": oid})[0] # sco7
+ if module_is_locked(module.id):
raise ScoLockedFormError()
+ if not can_delete_module(module):
+ raise ScoNonEmptyFormationObject(
+ "Module",
+ msg=module.titre,
+ dest_url=url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=module.formation_id,
+ semestre_idx=module.ue.semestre_idx,
+ ),
+ )
# S'il y a des moduleimpls, on ne peut pas detruire le module !
mods = sco_moduleimpl.moduleimpl_list(module_id=oid)
if mods:
err_page = f"""Destruction du module impossible car il est utilisé dans des semestres existants !
-
Il faut d'abord supprimer le semestre. Mais il est peut être préférable de
- laisser ce programme intact et d'en créer une nouvelle version pour la modifier.
+
Il faut d'abord supprimer le semestre (ou en retirer ce module). Mais il est peut être préférable de
+ laisser ce programme intact et d'en créer une nouvelle version pour la modifier sans affecter les semestres déjà en place.
reprendre
@@ -365,12 +387,21 @@ def do_module_delete(oid):
def module_delete(module_id=None):
"""Delete a module"""
- if not module_id:
- raise ScoValueError("invalid module !")
- modules = module_list(args={"module_id": module_id})
- if not modules:
- raise ScoValueError("Module inexistant !")
- mod = modules[0]
+ module = Module.query.get_or_404(module_id)
+ mod = module_list(args={"module_id": module_id})[0] # sco7
+
+ if not can_delete_module(module):
+ raise ScoNonEmptyFormationObject(
+ "Module",
+ msg=module.titre,
+ dest_url=url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=module.formation_id,
+ semestre_idx=module.ue.semestre_idx,
+ ),
+ )
+
H = [
html_sco_header.sco_header(page_title="Suppression d'un module"),
"""Suppression du module %(titre)s (%(code)s)
""" % mod,
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index bdf37375..766fbd9b 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -42,7 +42,12 @@ from app import log
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_permissions import Permission
-from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError
+from app.scodoc.sco_exceptions import (
+ ScoGenError,
+ ScoValueError,
+ ScoLockedFormError,
+ ScoNonEmptyFormationObject,
+)
from app.scodoc import html_sco_header
from app.scodoc import sco_cache
@@ -130,64 +135,83 @@ def do_ue_create(args):
return ue_id
+def can_delete_ue(ue: UniteEns) -> bool:
+ """True si l'UE n'est pas utilisée dans des formsemestre
+ et n'a pas de module rattachés
+ """
+ # "pas un seul module de cette UE n'a de modimpl...""
+ return (not len(ue.modules.all())) and not any(m.modimpls.all() for m in ue.modules)
+
+
def do_ue_delete(ue_id, delete_validations=False, force=False):
"delete UE and attached matieres (but not modules)"
from app.scodoc import sco_formations
from app.scodoc import sco_parcours_dut
+ ue = UniteEns.query.get_or_404(ue_id)
+ if not can_delete_ue(ue):
+ raise ScoNonEmptyFormationObject(
+ "UE",
+ msg=ue.titre,
+ dest_url=url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=ue.formation_id,
+ semestre_idx=ue.semestre_idx,
+ ),
+ )
+
cnx = ndb.GetDBConnexion()
- log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue_id, delete_validations))
+ log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue.id, delete_validations))
# check
- ue = ue_list({"ue_id": ue_id})
- if not ue:
- raise ScoValueError("UE inexistante !")
- ue = ue[0]
- if ue_is_locked(ue["ue_id"]):
- raise ScoLockedFormError()
+ # if ue_is_locked(ue.id):
+ # raise ScoLockedFormError()
# Il y a-t-il des etudiants ayant validé cette UE ?
# si oui, propose de supprimer les validations
validations = sco_parcours_dut.scolar_formsemestre_validation_list(
- cnx, args={"ue_id": ue_id}
+ cnx, args={"ue_id": ue.id}
)
if validations and not delete_validations and not force:
return scu.confirm_dialog(
"%d étudiants ont validé l'UE %s (%s)
Si vous supprimez cette UE, ces validations vont être supprimées !
"
- % (len(validations), ue["acronyme"], ue["titre"]),
+ % (len(validations), ue.acronyme, ue.titre),
dest_url="",
target_variable="delete_validations",
cancel_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
- formation_id=str(ue["formation_id"]),
+ formation_id=ue.formation_id,
+ semestre_idx=ue.semestre_idx,
),
- parameters={"ue_id": ue_id, "dialog_confirmed": 1},
+ parameters={"ue_id": ue.id, "dialog_confirmed": 1},
)
if delete_validations:
- log("deleting all validations of UE %s" % ue_id)
+ log("deleting all validations of UE %s" % ue.id)
ndb.SimpleQuery(
"DELETE FROM scolar_formsemestre_validation WHERE ue_id=%(ue_id)s",
- {"ue_id": ue_id},
+ {"ue_id": ue.id},
)
# delete all matiere in this UE
- mats = sco_edit_matiere.matiere_list({"ue_id": ue_id})
+ mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})
for mat in mats:
sco_edit_matiere.do_matiere_delete(mat["matiere_id"])
# delete uecoef and events
ndb.SimpleQuery(
"DELETE FROM notes_formsemestre_uecoef WHERE ue_id=%(ue_id)s",
- {"ue_id": ue_id},
+ {"ue_id": ue.id},
)
- ndb.SimpleQuery("DELETE FROM scolar_events WHERE ue_id=%(ue_id)s", {"ue_id": ue_id})
+ ndb.SimpleQuery("DELETE FROM scolar_events WHERE ue_id=%(ue_id)s", {"ue_id": ue.id})
cnx = ndb.GetDBConnexion()
- _ueEditor.delete(cnx, ue_id)
- # > UE delete + supr. validations associées etudiants (cas compliqué, mais rarement utilisé: acceptable de tout invalider ?):
+ _ueEditor.delete(cnx, ue.id)
+ # > UE delete + supr. validations associées etudiants (cas compliqué, mais rarement
+ # utilisé: acceptable de tout invalider):
sco_cache.invalidate_formsemestre()
# news
- F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
+ F = sco_formations.formation_list(args={"formation_id": ue.formation_id})[0]
sco_news.add(
typ=sco_news.NEWS_FORM,
- object=ue["formation_id"],
+ object=ue.formation_id,
text="Modification de la formation %(acronyme)s" % F,
max_frequency=3,
)
@@ -197,11 +221,11 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
- formation_id=ue["formation_id"],
+ formation_id=ue.formation_id,
+ semestre_idx=ue.semestre_idx,
)
)
- else:
- return None
+ return None
def ue_create(formation_id=None):
@@ -211,8 +235,6 @@ def ue_create(formation_id=None):
def ue_edit(ue_id=None, create=False, formation_id=None):
"""Modification ou création d'une UE"""
- from app.scodoc import sco_formations
-
create = int(create)
if not create:
U = ue_list(args={"ue_id": ue_id})
@@ -444,24 +466,38 @@ def next_ue_numero(formation_id, semestre_id=None):
def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
"""Delete an UE"""
- ues = ue_list(args={"ue_id": ue_id})
- if not ues:
- raise ScoValueError("UE inexistante !")
- ue = ues[0]
-
- if not dialog_confirmed:
- return scu.confirm_dialog(
- "Suppression de l'UE %(titre)s (%(acronyme)s))
" % ue,
- dest_url="",
- parameters={"ue_id": ue_id},
- cancel_url=url_for(
+ ue = UniteEns.query.get_or_404(ue_id)
+ if ue.modules.all():
+ raise ScoValueError(
+ f"""Suppression de l'UE {ue.titre} impossible car
+ des modules (ou SAÉ ou ressources) lui sont rattachés."""
+ )
+ if not can_delete_ue(ue):
+ raise ScoNonEmptyFormationObject(
+ "UE",
+ msg=ue.titre,
+ dest_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
- formation_id=str(ue["formation_id"]),
+ formation_id=ue.formation_id,
+ semestre_idx=ue.semestre_idx,
),
)
- return do_ue_delete(ue_id, delete_validations=delete_validations)
+ if not dialog_confirmed:
+ return scu.confirm_dialog(
+ f"Suppression de l'UE {ue.titre} ({ue.acronyme})
",
+ dest_url="",
+ parameters={"ue_id": ue.id},
+ cancel_url=url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=ue.formation_id,
+ semestre_idx=ue.semestre_idx,
+ ),
+ )
+
+ return do_ue_delete(ue.id, delete_validations=delete_validations)
def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
@@ -1010,12 +1046,14 @@ def _ue_table_modules(
H.append(arrow_none)
im += 1
if mod["nb_moduleimpls"] == 0 and editable:
- H.append(
- '%s'
- % (mod["module_id"], delete_icon)
- )
+ icon = delete_icon
else:
- H.append(delete_disabled_icon)
+ icon = delete_disabled_icon
+ H.append(
+ '%s'
+ % (mod["module_id"], icon)
+ )
+
H.append("")
mod_editable = (
diff --git a/app/scodoc/sco_exceptions.py b/app/scodoc/sco_exceptions.py
index 635a724b..1099986b 100644
--- a/app/scodoc/sco_exceptions.py
+++ b/app/scodoc/sco_exceptions.py
@@ -52,7 +52,7 @@ class InvalidNoteValue(ScoException):
# Exception qui stoque dest_url, utilisee dans Zope standard_error_message
class ScoValueError(ScoException):
def __init__(self, msg, dest_url=None):
- ScoException.__init__(self, msg)
+ super().__init__(msg)
self.dest_url = dest_url
@@ -72,20 +72,35 @@ class ScoConfigurationError(ScoValueError):
pass
-class ScoLockedFormError(ScoException):
- def __init__(self, msg=""):
+class ScoLockedFormError(ScoValueError):
+ "Modification d'une formation verrouillée"
+
+ def __init__(self, msg="", dest_url=None):
msg = (
"Cette formation est verrouillée (car il y a un semestre verrouillé qui s'y réfère). "
+ str(msg)
)
- ScoException.__init__(self, 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"""
+
+ def __init__(self, type_objet="objet'", msg="", dest_url=None):
+ msg = f"""{type_objet} "{msg}" utilisé dans des semestres: suppression impossible.
+ Il faut d'abord supprimer le semestre (ou en retirer ce {type_objet}).
+ Mais il est peut-être préférable de laisser ce programme intact et d'en créer une
+ nouvelle version pour la modifier sans affecter les semestres déjà en place.
+
+ """
+ super().__init__(msg=msg, dest_url=dest_url)
class ScoGenError(ScoException):
"exception avec affichage d'une page explicative ad-hoc"
def __init__(self, msg=""):
- ScoException.__init__(self, msg)
+ super().__init__(msg)
class AccessDenied(ScoGenError):
@@ -101,7 +116,7 @@ class APIInvalidParams(Exception):
status_code = 400
def __init__(self, message, status_code=None, payload=None):
- Exception.__init__(self)
+ super().__init__()
self.message = message
if status_code is not None:
self.status_code = status_code
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index 6c889c54..060289e9 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -198,10 +198,13 @@ def do_formsemestre_createwithmodules(edit=False):
NB_SEM = parcours.NB_SEM
else:
NB_SEM = 10 # fallback, max 10 semestres
- semestre_id_list = [-1] + list(range(1, NB_SEM + 1))
+ if NB_SEM == 1:
+ semestre_id_list = [-1]
+ else:
+ semestre_id_list = [-1] + list(range(1, NB_SEM + 1))
semestre_id_labels = []
for sid in semestre_id_list:
- if sid == "-1":
+ if sid == -1:
semestre_id_labels.append("pas de semestres")
else:
semestre_id_labels.append(f"S{sid}")
@@ -329,6 +332,8 @@ def do_formsemestre_createwithmodules(edit=False):
"labels": modalites_titles,
},
),
+ ]
+ modform.append(
(
"semestre_id",
{
@@ -338,7 +343,7 @@ def do_formsemestre_createwithmodules(edit=False):
"labels": semestre_id_labels,
},
),
- ]
+ )
etapes = sco_portal_apogee.get_etapes_apogee_dept()
# Propose les etapes renvoyées par le portail
# et ajoute les étapes du semestre qui ne sont pas dans la liste (soit la liste a changé, soit l'étape a été ajoutée manuellement)
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index 8cf8c9be..4c473491 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -93,6 +93,7 @@ MODULE_TYPE_NAMES = {
ModuleType.MALUS: "Malus",
ModuleType.RESSOURCE: "Ressource",
ModuleType.SAE: "SAÉ",
+ None: "Module",
}
MALUS_MAX = 20.0
diff --git a/app/templates/pn/form_ues.html b/app/templates/pn/form_ues.html
index cc9812dc..500ee42a 100644
--- a/app/templates/pn/form_ues.html
+++ b/app/templates/pn/form_ues.html
@@ -24,14 +24,11 @@
{{icons.arrow_none|safe}}
{% endif %}
- {% if editable and not ue.modules.count() %}
+
{{icons.delete|safe}}
- {% else %}
- {{icons.delete_disabled|safe}}
- {% endif %}
-
+ }}">{% if editable and not ue.modules.count() %}{{icons.delete|safe}}{% else %}{{icons.delete_disabled|safe}}{% endif %}
+
{{ue.acronyme}} {{ue.titre}}
diff --git a/app/templates/sco_value_error.html b/app/templates/sco_value_error.html
index 36ed8d20..8a54f619 100644
--- a/app/templates/sco_value_error.html
+++ b/app/templates/sco_value_error.html
@@ -8,10 +8,9 @@
{{ exc | safe }}
-
{% if g.scodoc_dept %}
- retour page d'accueil
- departement {{ g.scodoc_dept }}
+ continuer
{% else %}
retour page d'accueil
{% endif %}
diff --git a/app/templates/scodoc.html b/app/templates/scodoc.html
index 2b8255e5..6ac10dbd 100644
--- a/app/templates/scodoc.html
+++ b/app/templates/scodoc.html
@@ -13,11 +13,21 @@
{% for dept in depts %}
+ {% if dept.visible or current_user.is_administrator() %}
-
Département
{{dept.preferences.filter_by(name="DeptName").first().value}}
+ {% if current_user.is_administrator() %}
+
+ {% if dept.visible %}visible{% else %}caché aux utilisateurs{% endif %}
+
+ {% if dept.visible %}cacher{% else %}rendre visible{% endif %}
+
+
+ {% endif %}
+ {% endif %}
{% else %}
-
Aucun département défini !
diff --git a/app/views/scodoc.py b/app/views/scodoc.py
index 9377dc56..3e2fdbdc 100644
--- a/app/views/scodoc.py
+++ b/app/views/scodoc.py
@@ -53,6 +53,7 @@ from wtforms.fields.simple import BooleanField, StringField, TextAreaField, Hidd
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
import app
+from app import db
from app.forms.main import config_forms
from app.forms.main.create_dept import CreateDeptForm
from app.models import Departement, Identite
@@ -82,9 +83,7 @@ from PIL import Image as PILImage
@bp.route("/ScoDoc/index")
def index():
"Page d'accueil: liste des départements"
- depts = (
- Departement.query.filter_by(visible=True).order_by(Departement.acronym).all()
- )
+ depts = Departement.query.filter_by().order_by(Departement.acronym).all()
return render_template(
"scodoc.html",
title=sco_version.SCONAME,
@@ -108,7 +107,7 @@ def create_dept():
if request.method == "POST" and form.cancel.data: # cancel button
return redirect(url_for("scodoc.index"))
if form.validate_on_submit():
- departements.create_dept(form.acronym.data)
+ departements.create_dept(form.acronym.data, visible=form.visible.data)
flash(f"Département {form.acronym.data} créé.")
return redirect(url_for("scodoc.index"))
return render_template(
@@ -118,6 +117,17 @@ def create_dept():
)
+@bp.route("/ScoDoc/toggle_dept_vis/", methods=["GET", "POST"])
+@admin_required
+def toggle_dept_vis(dept_id):
+ """Cache ou rend visible un dept"""
+ dept = Departement.query.get_or_404(dept_id)
+ dept.visible = not dept.visible
+ db.session.add(dept)
+ db.session.commit()
+ return redirect(url_for("scodoc.index"))
+
+
@bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"])
@login_required
def table_etud_in_accessible_depts():
diff --git a/app/views/users.py b/app/views/users.py
index fee36edf..21c992fd 100644
--- a/app/views/users.py
+++ b/app/views/users.py
@@ -152,7 +152,6 @@ def user_info(user_name, format="json"):
def create_user_form(user_name=None, edit=0, all_roles=1):
"form. création ou edition utilisateur"
auth_dept = current_user.dept
- auth_username = current_user.user_name
from_mail = current_user.email
initvalues = {}
edit = int(edit)
@@ -204,7 +203,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
administrable_dept_acronyms = sorted(
set(
[
- x.dept
+ x.dept or ""
for x in UserRole.query.filter_by(user=current_user)
if x.role.has_permission(Permission.ScoUsersAdmin) and x.dept
]
@@ -249,7 +248,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
r.name + "_" + (dept or "") for (r, dept) in displayed_roles
]
displayed_roles_labels = [f"{dept}: {r.name}" for (r, dept) in displayed_roles]
- disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer
+ disabled_roles = {} # pour désactiver les roles que l'on ne peut pas éditer
for i in range(len(displayed_roles_strings)):
if displayed_roles_strings[i] not in editable_roles_strings:
disabled_roles[i] = True
@@ -375,7 +374,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
can_choose_dept = True
else:
selectable_dept_acronyms = set(administrable_dept_acronyms)
- if edit: # ajoute dept actuel de l'utilisateur
+ if edit and the_user.dept is not None: # ajoute dept actuel de l'utilisateur
selectable_dept_acronyms |= {the_user.dept}
if len(selectable_dept_acronyms) > 1:
can_choose_dept = True
diff --git a/tests/unit/test_formations.py b/tests/unit/test_formations.py
index 613d3573..9f8f0755 100644
--- a/tests/unit/test_formations.py
+++ b/tests/unit/test_formations.py
@@ -274,22 +274,10 @@ def test_formations(test_client):
# --- Suppression du module, matiere et ue test du semestre 2
# on doit d'abbord supprimer le semestre
-
- # sco_formsemestre_edit.formsemestre_delete( formsemestre_id=sem2["formsemestre_id"])
- # sco_formsemestre_edit.formsemestre_createwithmodules( formsemestre_id=sem2["formsemestre_id"])
-
- # RIEN NE SE PASSE AVEC CES FONCTIONS
-
sco_formsemestre_edit.do_formsemestre_delete(
formsemestre_id=sem2["formsemestre_id"]
)
- # sco_edit_module.module_delete( module_id=modt["module_id"])
- # sco_edit_matiere.matiere_delete( matiere_id=matt["matiere_id"])
- # sco_edit_ue.ue_delete( ue_id=uet["ue_id"])
-
- # RIEN NE SE PASSE AVEC CES FONCTIONS
-
li_module = sco_edit_module.module_list()
assert len(li_module) == 4
sco_edit_module.do_module_delete(oid=modt["module_id"]) # on supprime le semestre
From 5895e5c33c172b3414d2d5930c1526de14c881a2 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Tue, 4 Jan 2022 23:05:37 +0100
Subject: [PATCH 16/21] edition formations / tests unitaires ok
---
app/models/modules.py | 4 +---
app/scodoc/sco_edit_ue.py | 4 ++--
tests/unit/test_formations.py | 42 ++++++++++++++++-------------------
3 files changed, 22 insertions(+), 28 deletions(-)
diff --git a/app/models/modules.py b/app/models/modules.py
index 00a6d4c9..8f305507 100644
--- a/app/models/modules.py
+++ b/app/models/modules.py
@@ -49,9 +49,7 @@ class Module(db.Model):
super(Module, self).__init__(**kwargs)
def __repr__(self):
- return (
- f""
- )
+ return f""
def to_dict(self):
e = dict(self.__dict__)
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index c93bdb1b..766fbd9b 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -211,7 +211,7 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
F = sco_formations.formation_list(args={"formation_id": ue.formation_id})[0]
sco_news.add(
typ=sco_news.NEWS_FORM,
- object=ue["formation_id"],
+ object=ue.formation_id,
text="Modification de la formation %(acronyme)s" % F,
max_frequency=3,
)
@@ -221,7 +221,7 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
- formation_id=ue["formation_id"],
+ formation_id=ue.formation_id,
semestre_idx=ue.semestre_idx,
)
)
diff --git a/tests/unit/test_formations.py b/tests/unit/test_formations.py
index 613d3573..786ccba2 100644
--- a/tests/unit/test_formations.py
+++ b/tests/unit/test_formations.py
@@ -42,17 +42,17 @@
# - do_formation_delete
import json
-import xml.dom.minidom
import flask
from flask import g
-
+import pytest
from tests.unit import sco_fake_gen
from app.scodoc import sco_edit_formation
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_exceptions
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre_edit
from app.scodoc import sco_moduleimpl
@@ -273,31 +273,31 @@ def test_formations(test_client):
# --- Suppression du module, matiere et ue test du semestre 2
- # on doit d'abbord supprimer le semestre
-
- # sco_formsemestre_edit.formsemestre_delete( formsemestre_id=sem2["formsemestre_id"])
- # sco_formsemestre_edit.formsemestre_createwithmodules( formsemestre_id=sem2["formsemestre_id"])
-
- # RIEN NE SE PASSE AVEC CES FONCTIONS
+ # on doit d'abord supprimer le semestre:
sco_formsemestre_edit.do_formsemestre_delete(
formsemestre_id=sem2["formsemestre_id"]
)
- # sco_edit_module.module_delete( module_id=modt["module_id"])
- # sco_edit_matiere.matiere_delete( matiere_id=matt["matiere_id"])
- # sco_edit_ue.ue_delete( ue_id=uet["ue_id"])
-
- # RIEN NE SE PASSE AVEC CES FONCTIONS
-
li_module = sco_edit_module.module_list()
assert len(li_module) == 4
- sco_edit_module.do_module_delete(oid=modt["module_id"]) # on supprime le semestre
- # sco_formsemestre_edit.formsemestre_delete_moduleimpls( formsemestre_id=sem2["formsemestre_id"], module_ids_to_del=[modt["module_id"]])
- # deuxieme methode de supression d'un module
- li_module2 = sco_edit_module.module_list()
+ # Suppression impossible car utilisé dans le semestre semt:
+ with pytest.raises(sco_exceptions.ScoNonEmptyFormationObject):
+ sco_edit_module.module_delete(module_id=mi3["module_id"])
- assert len(li_module2) == 3 # verification de la suppression du module
+ sco_formsemestre_edit.do_formsemestre_delete(semt["formsemestre_id"])
+
+ li_module2_before = sco_edit_module.module_list()
+
+ sco_edit_module.do_module_delete(mi3["module_id"])
+ sco_edit_module.do_module_delete(modt["module_id"])
+
+ # deuxieme methode de supression d'un module
+ li_module2_after = sco_edit_module.module_list()
+
+ assert (
+ len(li_module2_after) == len(li_module2_before) - 2
+ ) # verification de la suppression
lim_sem2 = sco_moduleimpl.moduleimpl_list(formsemestre_id=sem2["formsemestre_id"])
@@ -316,10 +316,6 @@ def test_formations(test_client):
assert len(li_ue2) == 3 # verification de la suppression de l'UE
# --- Suppression d'une formation
- # Il faut d'abbord supprimer le semestre aussi.
- sco_formsemestre_edit.do_formsemestre_delete(
- formsemestre_id=semt["formsemestre_id"]
- )
sco_edit_formation.do_formation_delete(oid=f2["formation_id"])
lif3 = notes.formation_list(format="json").get_data(as_text=True)
From 4b63fe81e437601132e4b2b748222946792bf775 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Wed, 5 Jan 2022 01:03:25 +0100
Subject: [PATCH 17/21] fix: het sort
---
app/scodoc/sco_report.py | 18 +++++++-----------
app/scodoc/sco_utils.py | 5 +++++
2 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py
index d480679b..6c2582ef 100644
--- a/app/scodoc/sco_report.py
+++ b/app/scodoc/sco_report.py
@@ -49,16 +49,12 @@ from app.scodoc import sco_etud
from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
-from app.scodoc import sco_formsemestre_status
from app.scodoc import sco_parcours_dut
-from app.scodoc import sco_pdf
from app.scodoc import sco_preferences
import sco_version
from app.scodoc.gen_tables import GenTable
from app import log
from app.scodoc.sco_codes_parcours import code_semestre_validant
-from app.scodoc.sco_exceptions import ScoValueError
-from app.scodoc.sco_pdf import SU
MAX_ETUD_IN_DESCR = 20
@@ -121,9 +117,9 @@ def _categories_and_results(etuds, category, result):
categories[etud[category]] = True
results[etud[result]] = True
categories = list(categories.keys())
- categories.sort()
+ categories.sort(key=scu.heterogeneous_sorting_key)
results = list(results.keys())
- results.sort()
+ results.sort(key=scu.heterogeneous_sorting_key)
return categories, results
@@ -166,7 +162,7 @@ def _results_by_category(
l["sumpercent"] = "%2.1f%%" % ((100.0 * l["sum"]) / tot)
#
codes = list(results.keys())
- codes.sort()
+ codes.sort(key=scu.heterogeneous_sorting_key)
bottom_titles = []
if C: # ligne du bas avec totaux:
@@ -314,7 +310,7 @@ def formsemestre_report_counts(
"type_admission",
"boursier_prec",
]
- keys.sort()
+ keys.sort(key=scu.heterogeneous_sorting_key)
F = [
"""