From 2fe9e5ec3945eae808c2fa28ca8e9591ae83e8de Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet Suppression de la matière %(titre)s" % M,
" dans l'UE (%(acronyme)s))
" % UE,
]
- dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(UE["formation_id"])
+ dest_url = url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=str(UE["formation_id"]),
+ )
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
@@ -227,13 +231,13 @@ def matiere_edit(matiere_id=None):
if not F:
raise ScoValueError("Matière inexistante !")
F = F[0]
- U = sco_edit_ue.ue_list(args={"ue_id": F["ue_id"]})
- if not F:
+ ues = sco_edit_ue.ue_list(args={"ue_id": F["ue_id"]})
+ if not ues:
raise ScoValueError("UE inexistante !")
- U = U[0]
- Fo = sco_formations.formation_list(args={"formation_id": U["formation_id"]})[0]
+ ue = ues[0]
+ Fo = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
- ues = sco_edit_ue.ue_list(args={"formation_id": U["formation_id"]})
+ ues = sco_edit_ue.ue_list(args={"formation_id": ue["formation_id"]})
ue_names = ["%(acronyme)s (%(titre)s)" % u for u in ues]
ue_ids = [u["ue_id"] for u in ues]
H = [
@@ -278,8 +282,11 @@ associé.
submitlabel="Modifier les valeurs",
)
- dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(U["formation_id"])
-
+ dest_url = url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=str(ue["formation_id"]),
+ )
if tf[0] == 0:
return "\n".join(H) + tf[1] + help + html_sco_header.sco_footer()
elif tf[0] == -1:
diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py
index 301ef585a..54d7fc846 100644
--- a/app/scodoc/sco_edit_module.py
+++ b/app/scodoc/sco_edit_module.py
@@ -285,21 +285,25 @@ def module_delete(module_id=None):
"""Delete a module"""
if not module_id:
raise ScoValueError("invalid module !")
- Mods = module_list(args={"module_id": module_id})
- if not Mods:
+ modules = module_list(args={"module_id": module_id})
+ if not modules:
raise ScoValueError("Module inexistant !")
- Mod = Mods[0]
+ mod = modules[0]
H = [
html_sco_header.sco_header(page_title="Suppression d'un module"),
- """Suppression du module %(titre)s (%(code)s)
""" % Mod,
+ """Suppression du module %(titre)s (%(code)s)
""" % mod,
]
- dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(Mod["formation_id"])
+ dest_url = url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=str(mod["formation_id"]),
+ )
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
(("module_id", {"input_type": "hidden"}),),
- initvalues=Mod,
+ initvalues=mod,
submitlabel="Confirmer la suppression",
cancelbutton="Annuler",
)
@@ -367,9 +371,11 @@ def module_edit(module_id=None):
Mod["ue_matiere_id"] = "%s!%s" % (Mod["ue_id"], Mod["matiere_id"])
semestres_indices = list(range(1, parcours.NB_SEM + 1))
-
- dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(Mod["formation_id"])
-
+ dest_url = url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=str(Mod["formation_id"]),
+ )
H = [
html_sco_header.sco_header(
page_title="Modification du module %(titre)s" % Mod,
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index 2e99407de..66d8777d5 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -75,7 +75,7 @@ _ueEditor = ndb.EditableTable(
sortkey="numero",
input_formators={
"type": ndb.int_null_is_zero,
- "is_external": bool,
+ "is_external": ndb.bool_or_str,
},
output_formators={
"numero": ndb.int_null_is_zero,
@@ -139,7 +139,11 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
% (len(validations), ue["acronyme"], ue["titre"]),
dest_url="",
target_variable="delete_validations",
- cancel_url="ue_list?formation_id=%s" % ue["formation_id"],
+ cancel_url=url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=str(ue["formation_id"]),
+ ),
parameters={"ue_id": ue_id, "dialog_confirmed": 1},
)
if delete_validations:
@@ -294,6 +298,14 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
},
),
+ (
+ "is_external",
+ {
+ "input_type": "boolcheckbox",
+ "title": "UE externe",
+ "explanation": "réservé pour les capitalisations d'UE effectuées à l'extérieur de l'établissement",
+ },
+ ),
]
if parcours.UE_IS_MODULE:
# demande le semestre pour creer le module immediatement:
@@ -418,7 +430,11 @@ def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
"Suppression de l'UE %(titre)s (%(acronyme)s))
" % ue,
dest_url="",
parameters={"ue_id": ue_id},
- cancel_url="ue_list?formation_id=%s" % ue["formation_id"],
+ cancel_url=url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=str(ue["formation_id"]),
+ ),
)
return do_ue_delete(ue_id, delete_validations=delete_validations)
@@ -438,21 +454,24 @@ def ue_table(formation_id=None, msg=""): # was ue_list
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
locked = sco_formations.formation_has_locked_sems(formation_id)
- ues = ue_list(args={"formation_id": formation_id})
+ ues = ue_list(args={"formation_id": formation_id, "is_external": False})
+ ues_externes = ue_list(args={"formation_id": formation_id, "is_external": True})
# tri par semestre et numero:
_add_ue_semestre_id(ues)
+ _add_ue_semestre_id(ues_externes)
ues.sort(key=lambda u: (u["semestre_id"], u["numero"]))
+ ues_externes.sort(key=lambda u: (u["semestre_id"], u["numero"]))
has_duplicate_ue_codes = len(set([ue["ue_code"] for ue in ues])) != len(ues)
- perm_change = current_user.has_permission(Permission.ScoChangeFormation)
- # editable = (not locked) and perm_change
+ has_perm_change = current_user.has_permission(Permission.ScoChangeFormation)
+ # editable = (not locked) and has_perm_change
# On autorise maintanant la modification des formations qui ont des semestres verrouillés,
# sauf si cela affect les notes passées (verrouillées):
# - pas de modif des modules utilisés dans des semestres verrouillés
# - pas de changement des codes d'UE utilisés dans des semestres verrouillés
- editable = perm_change
+ editable = has_perm_change
tag_editable = (
- current_user.has_permission(Permission.ScoEditFormationTags) or perm_change
+ current_user.has_permission(Permission.ScoEditFormationTags) or has_perm_change
)
if locked:
lockicon = scu.icontag("lock32_img", title="verrouillé")
@@ -556,213 +575,20 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
H.append(
''
)
-
- cur_ue_semestre_id = None
- iue = 0
- for UE in ues:
- if UE["ects"]:
- UE["ects_str"] = ", %g ECTS" % UE["ects"]
- else:
- UE["ects_str"] = ""
- if editable:
- klass = "span_apo_edit"
- else:
- klass = ""
- UE["code_apogee_str"] = (
- """, Apo: """
- % (klass, UE["ue_id"], scu.APO_MISSING_CODE_STR)
- + (UE["code_apogee"] or "")
- + ""
+ H.append(
+ _ue_table_ues(
+ parcours,
+ ues,
+ editable,
+ tag_editable,
+ has_perm_change,
+ arrow_up,
+ arrow_down,
+ arrow_none,
+ delete_icon,
+ delete_disabled_icon,
)
-
- if cur_ue_semestre_id != UE["semestre_id"]:
- cur_ue_semestre_id = UE["semestre_id"]
- if iue > 0:
- H.append("")
- if UE["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT:
- lab = "Pas d'indication de semestre:"
- else:
- lab = "Semestre %s:" % UE["semestre_id"]
- H.append('')
- H.append('
")
+ )
if editable:
H.append(
'')
- Matlist = sco_edit_matiere.matiere_list(args={"ue_id": UE["ue_id"]})
- for Mat in Matlist:
- if not parcours.UE_IS_MODULE:
- H.append('
")
- H.append("')
- Modlist = sco_edit_module.module_list(
- args={"matiere_id": Mat["matiere_id"]}
- )
- im = 0
- for Mod in Modlist:
- Mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls(
- Mod["module_id"]
- )
- klass = "notes_module_list"
- if Mod["module_type"] == scu.MODULE_MALUS:
- klass += " module_malus"
- H.append('
")
- H.append("")
if editable:
H.append(
@@ -795,7 +642,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
Formation %(titre)s (%(acronyme)s), version %(version)s, code %(formation_code)s
' + 'Formation %(titre)s (%(acronyme)s), version %(version)s, code %(formation_code)s
' % F + msg + str(tf[1]) diff --git a/app/scodoc/sco_formsemestre_exterieurs.py b/app/scodoc/sco_formsemestre_exterieurs.py index 412e06d4e..a4c782d49 100644 --- a/app/scodoc/sco_formsemestre_exterieurs.py +++ b/app/scodoc/sco_formsemestre_exterieurs.py @@ -221,12 +221,11 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid): """ sem = sco_formsemestre.get_formsemestre(formsemestre_id) etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - ue_list = _list_ue_with_coef_and_validations(sem, etudid) - descr = _ue_form_description(ue_list, scu.get_request_args()) + ues = _list_ue_with_coef_and_validations(sem, etudid) + descr = _ue_form_description(ues, scu.get_request_args()) if request.method == "GET": initvalues = { - "note_" + str(ue["ue_id"]): ue["validation"].get("moy_ue", "") - for ue in ue_list + "note_" + str(ue["ue_id"]): ue["validation"].get("moy_ue", "") for ue in ues } else: initvalues = {} @@ -247,15 +246,13 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid): return "\n".join(H) else: # soumission # simule erreur - ok, message = _check_values(ue_list, tf[2]) + ok, message = _check_values(ues, tf[2]) if not ok: H = _make_page(etud, sem, tf, message=message) return "\n".join(H) else: # Submit - _record_ue_validations_and_coefs( - formsemestre_id, etudid, ue_list, tf[2] - ) + _record_ue_validations_and_coefs(formsemestre_id, etudid, ues, tf[2]) return flask.redirect( "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" % (formsemestre_id, etudid) @@ -303,7 +300,7 @@ _UE_VALID_CODES = { } -def _ue_form_description(ue_list, values): +def _ue_form_description(ues, values): """Description du formulaire de saisie des UE / validations Pour chaque UE, on peut saisir: son code jury, sa note, son coefficient. """ @@ -320,7 +317,7 @@ def _ue_form_description(ue_list, values): ("formsemestre_id", {"input_type": "hidden"}), ("etudid", {"input_type": "hidden"}), ] - for ue in ue_list: + for ue in ues: # Menu pour code validation UE: # Ne propose que ADM, CMP et "Non inscrit" select_name = "valid_" + str(ue["ue_id"]) @@ -439,8 +436,8 @@ def _list_ue_with_coef_and_validations(sem, etudid): """ cnx = ndb.GetDBConnexion() formsemestre_id = sem["formsemestre_id"] - ue_list = sco_edit_ue.ue_list({"formation_id": sem["formation_id"]}) - for ue in ue_list: + ues = sco_edit_ue.ue_list({"formation_id": sem["formation_id"]}) + for ue in ues: # add coefficient uecoef = sco_formsemestre.formsemestre_uecoef_list( cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]} @@ -462,11 +459,11 @@ def _list_ue_with_coef_and_validations(sem, etudid): ue["validation"] = validation[0] else: ue["validation"] = {} - return ue_list + return ues -def _record_ue_validations_and_coefs(formsemestre_id, etudid, ue_list, values): - for ue in ue_list: +def _record_ue_validations_and_coefs(formsemestre_id, etudid, ues, values): + for ue in ues: code = values.get("valid_" + str(ue["ue_id"]), False) if code == "None": code = None diff --git a/app/scodoc/sco_moduleimpl.py b/app/scodoc/sco_moduleimpl.py index 739061b12..2ffed0f48 100644 --- a/app/scodoc/sco_moduleimpl.py +++ b/app/scodoc/sco_moduleimpl.py @@ -178,6 +178,19 @@ def moduleimpl_withmodule_list( return modimpls +def moduleimpls_in_external_ue(ue_id): + """List of modimpls in this ue""" + cursor = ndb.SimpleQuery( + """SELECT DISTINCT mi.* + FROM notes_ue u, notes_moduleimpl mi, notes_modules m + WHERE u.is_external is true + AND mi.module_id = m.id AND m.ue_id = %(ue_id)s + """, + {"ue_id": ue_id}, + ) + return cursor.dictfetchall() + + def do_moduleimpl_inscription_list(moduleimpl_id=None, etudid=None): "list moduleimpl_inscriptions" args = locals() diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py index 06f7b95eb..0ac27c8b3 100644 --- a/app/scodoc/sco_recapcomplet.py +++ b/app/scodoc/sco_recapcomplet.py @@ -348,7 +348,6 @@ def make_formsemestre_recapcomplet( if not hidemodules: h.append("") pass - if not hidemodules and not ue["is_external"]: for modimpl in modimpls: if modimpl["module"]["ue_id"] == ue["ue_id"]: diff --git a/app/scodoc/sco_xml.py b/app/scodoc/sco_xml.py index ea3c3ee93..222ef84b1 100644 --- a/app/scodoc/sco_xml.py +++ b/app/scodoc/sco_xml.py @@ -82,25 +82,35 @@ def simple_dictlist2xml(dictlist, tagname=None, quote=False, pretty=True): return ans +def _repr_as_xml(v): + if isinstance(v, bool): + return str(int(v)) # booleans as "0" / "1" + return str(v) + + def _dictlist2xml(dictlist, root=None, tagname=None, quote=False): - scalar_types = (bytes, str, int, float) + scalar_types = (bytes, str, int, float, bool) for d in dictlist: elem = ElementTree.Element(tagname) root.append(elem) if isinstance(d, scalar_types) or isinstance(d, ApoEtapeVDI): - elem.set("code", str(d)) + elem.set("code", _repr_as_xml(d)) else: if quote: d_scalar = dict( [ - (k, quote_xml_attr(v)) + (k, quote_xml_attr(_repr_as_xml(v))) for (k, v) in d.items() if isinstance(v, scalar_types) ] ) else: d_scalar = dict( - [(k, str(v)) for (k, v) in d.items() if isinstance(v, scalar_types)] + [ + (k, _repr_as_xml(v)) + for (k, v) in d.items() + if isinstance(v, scalar_types) + ] ) for k in d_scalar: elem.set(k, d_scalar[k]) diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index 59a96712b..9bb52b1a6 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -1511,6 +1511,19 @@ span.ue_type { margin-right: 1.5em; } +div.formation_ue_list_externes { + background-color: #98cc98; +} +div.formation_ue_list_externes ul.notes_ue_list, div.formation_ue_list_externes li.notes_ue_list { + background-color: #98cc98; +} +span.ue_is_external span { + color: orange; +} +span.ue_is_external a { + font-weight: normal; +} + li.notes_matiere_list { margin-top: 2px; } diff --git a/app/views/notes.py b/app/views/notes.py index bd69ffbe3..90d70c466 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -41,9 +41,12 @@ import flask from flask import url_for from flask import current_app, g, request from flask_login import current_user +from werkzeug.utils import redirect from config import Config +from app import db +from app import models from app.auth.models import User from app.decorators import ( @@ -336,8 +339,8 @@ sco_publish( ) -@bp.route("/ue_table") @bp.route("/ue_list") # backward compat +@bp.route("/ue_table") @scodoc @permission_required(Permission.ScoView) @scodoc7func @@ -345,6 +348,25 @@ def ue_table(formation_id=None, msg=""): return sco_edit_ue.ue_table(formation_id=formation_id, msg=msg) +@bp.route("/ue_set_internal", methods=["GET", "POST"]) +@scodoc +@permission_required(Permission.ScoChangeFormation) +@scodoc7func +def ue_set_internal(ue_id): + """""" + ue = models.formations.NotesUE.query.get(ue_id) + if not ue: + raise ScoValueError("invalid ue_id") + ue.is_external = False + db.session.add(ue) + db.session.commit() + return redirect( + url_for( + "notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=ue.formation_id + ) + ) + + sco_publish("/ue_sharing_code", sco_edit_ue.ue_sharing_code, Permission.ScoView) sco_publish( "/edit_ue_set_code_apogee", diff --git a/sco_version.py b/sco_version.py index 4bb48370f..b464e1d19 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.0.54" +SCOVERSION = "9.0.55" SCONAME = "ScoDoc" diff --git a/tests/unit/sco_fake_gen.py b/tests/unit/sco_fake_gen.py index 8b98e35df..f99365226 100644 --- a/tests/unit/sco_fake_gen.py +++ b/tests/unit/sco_fake_gen.py @@ -324,7 +324,7 @@ class ScoFake(object): formation (dict), liste d'ue (dicts), liste de modules. """ f = self.create_formation(acronyme=acronyme, titre=titre) - ue_list = [] + ues = [] mod_list = [] for semestre_id in range(1, nb_semestre + 1): for n in range(1, nb_ue_per_semestre + 1): @@ -333,7 +333,7 @@ class ScoFake(object): acronyme="TSU%s%s" % (semestre_id, n), titre="ue test %s%s" % (semestre_id, n), ) - ue_list.append(ue) + ues.append(ue) mat = self.create_matiere(ue_id=ue["ue_id"], titre="matière test") for _ in range(nb_module_per_ue): mod = self.create_module( @@ -346,7 +346,7 @@ class ScoFake(object): formation_id=f["formation_id"], # faiblesse de l'API ) mod_list.append(mod) - return f, ue_list, mod_list + return f, ues, mod_list def setup_formsemestre( self, diff --git a/tests/unit/test_formations.py b/tests/unit/test_formations.py index 3d3cdbc50..613d35739 100644 --- a/tests/unit/test_formations.py +++ b/tests/unit/test_formations.py @@ -339,6 +339,10 @@ def test_import_formation(test_client): f = sco_formations.formation_import_xml(doc) assert len(f) == 3 # 3-uple formation_id = f[0] + # --- Vérification des UE + ues = sco_edit_ue.ue_list({"formation_id": formation_id}) + assert len(ues) == 10 + assert all(not ue["is_external"] for ue in ues) # aucune UE externe dans le XML # --- Mise en place de 4 semestres sems = [ G.create_formsemestre(