Sépare les UE externes dans la pae édition programme

This commit is contained in:
Emmanuel Viennet 2021-10-22 23:09:15 +02:00
parent c49aecaa2f
commit 2fe9e5ec39
17 changed files with 492 additions and 265 deletions

View File

@ -597,6 +597,22 @@ def float_null_is_null(x):
return float(x) return float(x)
BOOL_STR = {
"": False,
"false": False,
"0": False,
"1": True,
"true": "true",
}
def bool_or_str(x):
"""a boolean, may also be encoded as a string "0", "False", "1", "True" """
if isinstance(x, str):
return BOOL_STR[x.lower()]
return x
# post filtering # post filtering
# #
def UniqListofDicts(L, key): def UniqListofDicts(L, key):

View File

@ -190,7 +190,7 @@ def do_matiere_delete(oid):
def matiere_delete(matiere_id=None): def matiere_delete(matiere_id=None):
"""Delete an UE""" """Delete matière"""
from app.scodoc import sco_edit_ue from app.scodoc import sco_edit_ue
M = matiere_list(args={"matiere_id": matiere_id})[0] M = matiere_list(args={"matiere_id": matiere_id})[0]
@ -200,7 +200,11 @@ def matiere_delete(matiere_id=None):
"<h2>Suppression de la matière %(titre)s" % M, "<h2>Suppression de la matière %(titre)s" % M,
" dans l'UE (%(acronyme)s))</h2>" % UE, " dans l'UE (%(acronyme)s))</h2>" % 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( tf = TrivialFormulator(
request.base_url, request.base_url,
scu.get_request_args(), scu.get_request_args(),
@ -227,13 +231,13 @@ def matiere_edit(matiere_id=None):
if not F: if not F:
raise ScoValueError("Matière inexistante !") raise ScoValueError("Matière inexistante !")
F = F[0] F = F[0]
U = sco_edit_ue.ue_list(args={"ue_id": F["ue_id"]}) ues = sco_edit_ue.ue_list(args={"ue_id": F["ue_id"]})
if not F: if not ues:
raise ScoValueError("UE inexistante !") raise ScoValueError("UE inexistante !")
U = U[0] ue = ues[0]
Fo = sco_formations.formation_list(args={"formation_id": U["formation_id"]})[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_names = ["%(acronyme)s (%(titre)s)" % u for u in ues]
ue_ids = [u["ue_id"] for u in ues] ue_ids = [u["ue_id"] for u in ues]
H = [ H = [
@ -278,8 +282,11 @@ associé.
submitlabel="Modifier les valeurs", 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: if tf[0] == 0:
return "\n".join(H) + tf[1] + help + html_sco_header.sco_footer() return "\n".join(H) + tf[1] + help + html_sco_header.sco_footer()
elif tf[0] == -1: elif tf[0] == -1:

View File

@ -285,21 +285,25 @@ def module_delete(module_id=None):
"""Delete a module""" """Delete a module"""
if not module_id: if not module_id:
raise ScoValueError("invalid module !") raise ScoValueError("invalid module !")
Mods = module_list(args={"module_id": module_id}) modules = module_list(args={"module_id": module_id})
if not Mods: if not modules:
raise ScoValueError("Module inexistant !") raise ScoValueError("Module inexistant !")
Mod = Mods[0] mod = modules[0]
H = [ H = [
html_sco_header.sco_header(page_title="Suppression d'un module"), html_sco_header.sco_header(page_title="Suppression d'un module"),
"""<h2>Suppression du module %(titre)s (%(code)s)</h2>""" % Mod, """<h2>Suppression du module %(titre)s (%(code)s)</h2>""" % 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( tf = TrivialFormulator(
request.base_url, request.base_url,
scu.get_request_args(), scu.get_request_args(),
(("module_id", {"input_type": "hidden"}),), (("module_id", {"input_type": "hidden"}),),
initvalues=Mod, initvalues=mod,
submitlabel="Confirmer la suppression", submitlabel="Confirmer la suppression",
cancelbutton="Annuler", cancelbutton="Annuler",
) )
@ -367,9 +371,11 @@ def module_edit(module_id=None):
Mod["ue_matiere_id"] = "%s!%s" % (Mod["ue_id"], Mod["matiere_id"]) Mod["ue_matiere_id"] = "%s!%s" % (Mod["ue_id"], Mod["matiere_id"])
semestres_indices = list(range(1, parcours.NB_SEM + 1)) semestres_indices = list(range(1, parcours.NB_SEM + 1))
dest_url = url_for(
dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(Mod["formation_id"]) "notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=str(Mod["formation_id"]),
)
H = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(
page_title="Modification du module %(titre)s" % Mod, page_title="Modification du module %(titre)s" % Mod,

View File

@ -75,7 +75,7 @@ _ueEditor = ndb.EditableTable(
sortkey="numero", sortkey="numero",
input_formators={ input_formators={
"type": ndb.int_null_is_zero, "type": ndb.int_null_is_zero,
"is_external": bool, "is_external": ndb.bool_or_str,
}, },
output_formators={ output_formators={
"numero": ndb.int_null_is_zero, "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"]), % (len(validations), ue["acronyme"], ue["titre"]),
dest_url="", dest_url="",
target_variable="delete_validations", 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}, parameters={"ue_id": ue_id, "dialog_confirmed": 1},
) )
if delete_validations: 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", "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: if parcours.UE_IS_MODULE:
# demande le semestre pour creer le module immediatement: # demande le semestre pour creer le module immediatement:
@ -418,7 +430,11 @@ def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
"<h2>Suppression de l'UE %(titre)s (%(acronyme)s))</h2>" % ue, "<h2>Suppression de l'UE %(titre)s (%(acronyme)s))</h2>" % ue,
dest_url="", dest_url="",
parameters={"ue_id": ue_id}, 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) 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"]) parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
locked = sco_formations.formation_has_locked_sems(formation_id) 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: # tri par semestre et numero:
_add_ue_semestre_id(ues) _add_ue_semestre_id(ues)
_add_ue_semestre_id(ues_externes)
ues.sort(key=lambda u: (u["semestre_id"], u["numero"])) 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) has_duplicate_ue_codes = len(set([ue["ue_code"] for ue in ues])) != len(ues)
perm_change = current_user.has_permission(Permission.ScoChangeFormation) has_perm_change = current_user.has_permission(Permission.ScoChangeFormation)
# editable = (not locked) and perm_change # editable = (not locked) and has_perm_change
# On autorise maintanant la modification des formations qui ont des semestres verrouillés, # On autorise maintanant la modification des formations qui ont des semestres verrouillés,
# sauf si cela affect les notes passées (verrouillées): # sauf si cela affect les notes passées (verrouillées):
# - pas de modif des modules utilisés dans des semestres verrouillés # - 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 # - pas de changement des codes d'UE utilisés dans des semestres verrouillés
editable = perm_change editable = has_perm_change
tag_editable = ( tag_editable = (
current_user.has_permission(Permission.ScoEditFormationTags) or perm_change current_user.has_permission(Permission.ScoEditFormationTags) or has_perm_change
) )
if locked: if locked:
lockicon = scu.icontag("lock32_img", title="verrouillé") lockicon = scu.icontag("lock32_img", title="verrouillé")
@ -556,213 +575,20 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
H.append( H.append(
'<form><input type="checkbox" class="sco_tag_checkbox">montrer les tags</input></form>' '<form><input type="checkbox" class="sco_tag_checkbox">montrer les tags</input></form>'
) )
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: <span class="%s" data-url="edit_ue_set_code_apogee" id="%s" data-placeholder="%s">"""
% (klass, UE["ue_id"], scu.APO_MISSING_CODE_STR)
+ (UE["code_apogee"] or "")
+ "</span>"
)
if cur_ue_semestre_id != UE["semestre_id"]:
cur_ue_semestre_id = UE["semestre_id"]
if iue > 0:
H.append("</ul>")
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('<div class="ue_list_tit_sem">%s</div>' % lab)
H.append('<ul class="notes_ue_list">')
H.append('<li class="notes_ue_list">')
if iue != 0 and editable:
H.append( H.append(
'<a href="ue_move?ue_id=%s&after=0" class="aud">%s</a>' _ue_table_ues(
% (UE["ue_id"], arrow_up) parcours,
ues,
editable,
tag_editable,
has_perm_change,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
) )
else:
H.append(arrow_none)
if iue < len(ues) - 1 and editable:
H.append(
'<a href="ue_move?ue_id=%s&after=1" class="aud">%s</a>'
% (UE["ue_id"], arrow_down)
) )
else:
H.append(arrow_none)
iue += 1
UE["acro_titre"] = str(UE["acronyme"])
if UE["titre"] != UE["acronyme"]:
UE["acro_titre"] += " " + str(UE["titre"])
H.append(
"""%(acro_titre)s <span class="ue_code">(code %(ue_code)s%(ects_str)s, coef. %(coefficient)3.2f%(code_apogee_str)s)</span>
<span class="ue_coef"></span>
"""
% UE
)
if UE["type"] != sco_codes_parcours.UE_STANDARD:
H.append(
'<span class="ue_type">%s</span>'
% sco_codes_parcours.UE_TYPE_NAME[UE["type"]]
)
ue_editable = editable and not ue_is_locked(UE["ue_id"])
if ue_editable:
H.append(
'<a class="stdlink" href="ue_edit?ue_id=%(ue_id)s">modifier</a>' % UE
)
else:
H.append('<span class="locked">[verrouillé]</span>')
if not parcours.UE_IS_MODULE:
H.append('<ul class="notes_matiere_list">')
Matlist = sco_edit_matiere.matiere_list(args={"ue_id": UE["ue_id"]})
for Mat in Matlist:
if not parcours.UE_IS_MODULE:
H.append('<li class="notes_matiere_list">')
if editable and not sco_edit_matiere.matiere_is_locked(
Mat["matiere_id"]
):
H.append(
f"""<a class="stdlink" href="{
url_for("notes.matiere_edit",
scodoc_dept=g.scodoc_dept, matiere_id=Mat["matiere_id"])
}">
"""
)
H.append("%(titre)s" % Mat)
if editable and not sco_edit_matiere.matiere_is_locked(
Mat["matiere_id"]
):
H.append("</a>")
H.append('<ul class="notes_module_list">')
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('<li class="%s">' % klass)
H.append('<span class="notes_module_list_buts">')
if im != 0 and editable:
H.append(
'<a href="module_move?module_id=%s&after=0" class="aud">%s</a>'
% (Mod["module_id"], arrow_up)
)
else:
H.append(arrow_none)
if im < len(Modlist) - 1 and editable:
H.append(
'<a href="module_move?module_id=%s&after=1" class="aud">%s</a>'
% (Mod["module_id"], arrow_down)
)
else:
H.append(arrow_none)
im += 1
if Mod["nb_moduleimpls"] == 0 and editable:
H.append(
'<a class="smallbutton" href="module_delete?module_id=%s">%s</a>'
% (Mod["module_id"], delete_icon)
)
else:
H.append(delete_disabled_icon)
H.append("</span>")
mod_editable = editable # and not sco_edit_module.module_is_locked( Mod['module_id'])
if mod_editable:
H.append(
'<a class="discretelink" title="Modifier le module numéro %(numero)s, utilisé par %(nb_moduleimpls)d sessions" href="module_edit?module_id=%(module_id)s">'
% Mod
)
H.append(
'<span class="formation_module_tit">%s</span>'
% scu.join_words(Mod["code"], Mod["titre"])
)
if mod_editable:
H.append("</a>")
heurescoef = (
"%(heures_cours)s/%(heures_td)s/%(heures_tp)s, coef. %(coefficient)s"
% Mod
)
if mod_editable:
klass = "span_apo_edit"
else:
klass = ""
heurescoef += (
', Apo: <span class="%s" data-url="edit_module_set_code_apogee" id="%s" data-placeholder="%s">'
% (klass, Mod["module_id"], scu.APO_MISSING_CODE_STR)
+ (Mod["code_apogee"] or "")
+ "</span>"
)
if tag_editable:
tag_cls = "module_tag_editor"
else:
tag_cls = "module_tag_editor_ro"
tag_mk = """<span class="sco_tag_edit"><form><textarea data-module_id="{}" class="{}">{}</textarea></form></span>"""
tag_edit = tag_mk.format(
Mod["module_id"],
tag_cls,
",".join(sco_tag_module.module_tag_list(Mod["module_id"])),
)
H.append(
" %s %s" % (parcours.SESSION_NAME, Mod["semestre_id"])
+ " (%s)" % heurescoef
+ tag_edit
)
H.append("</li>")
if not Modlist:
H.append("<li>Aucun module dans cette matière !")
if editable:
H.append(
f"""<a class="stdlink" href="{
url_for("notes.matiere_delete",
scodoc_dept=g.scodoc_dept, matiere_id=Mat["matiere_id"])}"
>supprimer cette matière</a>
"""
)
H.append("</li>")
if editable: # and ((not parcours.UE_IS_MODULE) or len(Modlist) == 0):
H.append(
f"""<li> <a class="stdlink" href="{
url_for("notes.module_create",
scodoc_dept=g.scodoc_dept, matiere_id=Mat["matiere_id"])}"
>créer un module</a></li>
"""
)
H.append("</ul>")
H.append("</li>")
if not Matlist:
H.append("<li>Aucune matière dans cette UE ! ")
if editable:
H.append(
"""<a class="stdlink" href="ue_delete?ue_id=%(ue_id)s">supprimer l'UE</a>"""
% UE
)
H.append("</li>")
if editable and not parcours.UE_IS_MODULE:
H.append(
'<li><a class="stdlink" href="matiere_create?ue_id=%(ue_id)s">créer une matière</a> </li>'
% UE
)
if not parcours.UE_IS_MODULE:
H.append("</ul>")
H.append("</ul>")
if editable: if editable:
H.append( H.append(
'<ul><li><a class="stdlink" href="ue_create?formation_id=%s">Ajouter une UE</a></li>' '<ul><li><a class="stdlink" href="ue_create?formation_id=%s">Ajouter une UE</a></li>'
@ -774,6 +600,27 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
) )
H.append("</div>") # formation_ue_list H.append("</div>") # formation_ue_list
if ues_externes:
H.append('<div class="formation_ue_list formation_ue_list_externes">')
H.append(
'<div class="ue_list_tit">UE externes déclarées (pour information):</div>'
)
H.append(
_ue_table_ues(
parcours,
ues_externes,
editable,
tag_editable,
has_perm_change,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
)
)
H.append("</div>") # formation_ue_list
H.append("<p><ul>") H.append("<p><ul>")
if editable: if editable:
H.append( H.append(
@ -795,7 +642,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
</p>""" </p>"""
% F % F
) )
if perm_change: if has_perm_change:
H.append( H.append(
""" """
<h3> <a name="sems">Semestres ou sessions de cette formation</a></h3> <h3> <a name="sems">Semestres ou sessions de cette formation</a></h3>
@ -836,6 +683,294 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
return "".join(H) return "".join(H)
def _ue_table_ues(
parcours,
ues,
editable,
tag_editable,
has_perm_change,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
):
"""Édition de programme: liste des UEs (avec leurs matières et modules)."""
H = []
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: <span class="%s" data-url="edit_ue_set_code_apogee" id="%s" data-placeholder="%s">"""
% (klass, ue["ue_id"], scu.APO_MISSING_CODE_STR)
+ (ue["code_apogee"] or "")
+ "</span>"
)
if cur_ue_semestre_id != ue["semestre_id"]:
cur_ue_semestre_id = ue["semestre_id"]
if iue > 0:
H.append("</ul>")
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('<div class="ue_list_tit_sem">%s</div>' % lab)
H.append('<ul class="notes_ue_list">')
H.append('<li class="notes_ue_list">')
if iue != 0 and editable:
H.append(
'<a href="ue_move?ue_id=%s&after=0" class="aud">%s</a>'
% (ue["ue_id"], arrow_up)
)
else:
H.append(arrow_none)
if iue < len(ues) - 1 and editable:
H.append(
'<a href="ue_move?ue_id=%s&after=1" class="aud">%s</a>'
% (ue["ue_id"], arrow_down)
)
else:
H.append(arrow_none)
iue += 1
ue["acro_titre"] = str(ue["acronyme"])
if ue["titre"] != ue["acronyme"]:
ue["acro_titre"] += " " + str(ue["titre"])
H.append(
"""%(acro_titre)s <span class="ue_code">(code %(ue_code)s%(ects_str)s, coef. %(coefficient)3.2f%(code_apogee_str)s)</span>
<span class="ue_coef"></span>
"""
% ue
)
if ue["type"] != sco_codes_parcours.UE_STANDARD:
H.append(
'<span class="ue_type">%s</span>'
% sco_codes_parcours.UE_TYPE_NAME[ue["type"]]
)
if ue["is_external"]:
# Cas spécial: si l'UE externe a plus d'un module, c'est peut être une UE
# qui a été déclarée externe par erreur (ou suite à un bug d'import/export xml)
# Dans ce cas, propose de changer le type (même si verrouillée)
if len(sco_moduleimpl.moduleimpls_in_external_ue(ue["ue_id"])) > 1:
H.append('<span class="ue_is_external">')
if has_perm_change:
H.append(
f"""<a class="stdlink" href="{
url_for("notes.ue_set_internal", scodoc_dept=g.scodoc_dept, ue_id=ue["ue_id"])
}">transformer en UE ordinaire</a>&nbsp;"""
)
H.append("</span>")
ue_editable = editable and not ue_is_locked(ue["ue_id"])
if ue_editable:
H.append(
'<a class="stdlink" href="ue_edit?ue_id=%(ue_id)s">modifier</a>' % ue
)
else:
H.append('<span class="locked">[verrouillé]</span>')
H.append(
_ue_table_matieres(
parcours,
ue,
editable,
tag_editable,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
)
)
return "\n".join(H)
def _ue_table_matieres(
parcours,
ue,
editable,
tag_editable,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
):
"""Édition de programme: liste des matières (et leurs modules) d'une UE."""
H = []
if not parcours.UE_IS_MODULE:
H.append('<ul class="notes_matiere_list">')
matieres = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
for mat in matieres:
if not parcours.UE_IS_MODULE:
H.append('<li class="notes_matiere_list">')
if editable and not sco_edit_matiere.matiere_is_locked(mat["matiere_id"]):
H.append(
f"""<a class="stdlink" href="{
url_for("notes.matiere_edit",
scodoc_dept=g.scodoc_dept, matiere_id=mat["matiere_id"])
}">
"""
)
H.append("%(titre)s" % mat)
if editable and not sco_edit_matiere.matiere_is_locked(mat["matiere_id"]):
H.append("</a>")
modules = sco_edit_module.module_list(args={"matiere_id": mat["matiere_id"]})
H.append(
_ue_table_modules(
parcours,
mat,
modules,
editable,
tag_editable,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
)
)
if not matieres:
H.append("<li>Aucune matière dans cette UE ! ")
if editable:
H.append(
"""<a class="stdlink" href="ue_delete?ue_id=%(ue_id)s">supprimer l'UE</a>"""
% ue
)
H.append("</li>")
if editable and not parcours.UE_IS_MODULE:
H.append(
'<li><a class="stdlink" href="matiere_create?ue_id=%(ue_id)s">créer une matière</a> </li>'
% ue
)
if not parcours.UE_IS_MODULE:
H.append("</ul>")
return "\n".join(H)
def _ue_table_modules(
parcours,
mat,
modules,
editable,
tag_editable,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
):
"""Édition de programme: liste des modules d'une matière d'une UE"""
H = ['<ul class="notes_module_list">']
im = 0
for mod in modules:
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('<li class="%s">' % klass)
H.append('<span class="notes_module_list_buts">')
if im != 0 and editable:
H.append(
'<a href="module_move?module_id=%s&after=0" class="aud">%s</a>'
% (mod["module_id"], arrow_up)
)
else:
H.append(arrow_none)
if im < len(modules) - 1 and editable:
H.append(
'<a href="module_move?module_id=%s&after=1" class="aud">%s</a>'
% (mod["module_id"], arrow_down)
)
else:
H.append(arrow_none)
im += 1
if mod["nb_moduleimpls"] == 0 and editable:
H.append(
'<a class="smallbutton" href="module_delete?module_id=%s">%s</a>'
% (mod["module_id"], delete_icon)
)
else:
H.append(delete_disabled_icon)
H.append("</span>")
mod_editable = (
editable # and not sco_edit_module.module_is_locked( Mod['module_id'])
)
if mod_editable:
H.append(
'<a class="discretelink" title="Modifier le module numéro %(numero)s, utilisé par %(nb_moduleimpls)d sessions" href="module_edit?module_id=%(module_id)s">'
% mod
)
H.append(
'<span class="formation_module_tit">%s</span>'
% scu.join_words(mod["code"], mod["titre"])
)
if mod_editable:
H.append("</a>")
heurescoef = (
"%(heures_cours)s/%(heures_td)s/%(heures_tp)s, coef. %(coefficient)s" % mod
)
if mod_editable:
klass = "span_apo_edit"
else:
klass = ""
heurescoef += (
', Apo: <span class="%s" data-url="edit_module_set_code_apogee" id="%s" data-placeholder="%s">'
% (klass, mod["module_id"], scu.APO_MISSING_CODE_STR)
+ (mod["code_apogee"] or "")
+ "</span>"
)
if tag_editable:
tag_cls = "module_tag_editor"
else:
tag_cls = "module_tag_editor_ro"
tag_mk = """<span class="sco_tag_edit"><form><textarea data-module_id="{}" class="{}">{}</textarea></form></span>"""
tag_edit = tag_mk.format(
mod["module_id"],
tag_cls,
",".join(sco_tag_module.module_tag_list(mod["module_id"])),
)
H.append(
" %s %s" % (parcours.SESSION_NAME, mod["semestre_id"])
+ " (%s)" % heurescoef
+ tag_edit
)
H.append("</li>")
if not modules:
H.append("<li>Aucun module dans cette matière ! ")
if editable:
H.append(
f"""<a class="stdlink" href="{
url_for("notes.matiere_delete",
scodoc_dept=g.scodoc_dept, matiere_id=mat["matiere_id"])}"
>la supprimer</a>
"""
)
H.append("</li>")
if editable: # and ((not parcours.UE_IS_MODULE) or len(Modlist) == 0):
H.append(
f"""<li> <a class="stdlink" href="{
url_for("notes.module_create",
scodoc_dept=g.scodoc_dept, matiere_id=mat["matiere_id"])}"
>créer un module</a></li>
"""
)
H.append("</ul>")
H.append("</li>")
return "\n".join(H)
def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None): def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
"""HTML list of UE sharing this code """HTML list of UE sharing this code
Either ue_code or ue_id may be specified. Either ue_code or ue_id may be specified.

View File

@ -356,7 +356,7 @@ def apo_semset_maq_status(
H.append( H.append(
", ".join( ", ".join(
[ [
'<a class="stdlink" href="ue_list?formation_id=%(formation_id)s">%(acronyme)s v%(version)s</a>' '<a class="stdlink" href="ue_table?formation_id=%(formation_id)s">%(acronyme)s v%(version)s</a>'
% f % f
for f in formations for f in formations
] ]

View File

@ -152,7 +152,7 @@ def format_nom(s, uppercase=True):
def input_civilite(s): def input_civilite(s):
"""Converts external representation of civilite to internal: """Converts external representation of civilite to internal:
'M', 'F', or 'X' (and nothing else). 'M', 'F', or 'X' (and nothing else).
Raises valueError if conversion fails. Raises ScoValueError if conversion fails.
""" """
s = s.upper().strip() s = s.upper().strip()
if s in ("M", "M.", "MR", "H"): if s in ("M", "M.", "MR", "H"):
@ -161,12 +161,13 @@ def input_civilite(s):
return "F" return "F"
elif s == "X" or not s: elif s == "X" or not s:
return "X" return "X"
raise ValueError("valeur invalide pour la civilité: %s" % s) raise ScoValueError("valeur invalide pour la civilité: %s" % s)
def format_civilite(civilite): def format_civilite(civilite):
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre, """returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
personne ne souhaitant pas d'affichage) personne ne souhaitant pas d'affichage).
Raises ScoValueError if conversion fails.
""" """
try: try:
return { return {
@ -175,7 +176,7 @@ def format_civilite(civilite):
"X": "", "X": "",
}[civilite] }[civilite]
except KeyError: except KeyError:
raise ValueError("valeur invalide pour la civilité: %s" % civilite) raise ScoValueError("valeur invalide pour la civilité: %s" % civilite)
def format_lycee(nomlycee): def format_lycee(nomlycee):

View File

@ -254,7 +254,11 @@ def formation_list_table(formation_id=None, args={}):
).NAME ).NAME
except: except:
f["parcours_name"] = "" f["parcours_name"] = ""
f["_titre_target"] = "ue_list?formation_id=%(formation_id)s" % f f["_titre_target"] = url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=str(f["formation_id"]),
)
f["_titre_link_class"] = "stdlink" f["_titre_link_class"] = "stdlink"
f["_titre_id"] = "titre-%s" % f["acronyme"].lower().replace(" ", "-") f["_titre_id"] = "titre-%s" % f["acronyme"].lower().replace(" ", "-")
# Ajoute les semestres associés à chaque formation: # Ajoute les semestres associés à chaque formation:

View File

@ -675,7 +675,7 @@ def do_formsemestre_createwithmodules(edit=False):
if tf[0] == 0 or msg: if tf[0] == 0 or msg:
return ( return (
'<p>Formation <a class="discretelink" href="ue_list?formation_id=%(formation_id)s"><em>%(titre)s</em> (%(acronyme)s), version %(version)s, code %(formation_code)s</a></p>' '<p>Formation <a class="discretelink" href="ue_table?formation_id=%(formation_id)s"><em>%(titre)s</em> (%(acronyme)s), version %(version)s, code %(formation_code)s</a></p>'
% F % F
+ msg + msg
+ str(tf[1]) + str(tf[1])

View File

@ -221,12 +221,11 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid):
""" """
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
ue_list = _list_ue_with_coef_and_validations(sem, etudid) ues = _list_ue_with_coef_and_validations(sem, etudid)
descr = _ue_form_description(ue_list, scu.get_request_args()) descr = _ue_form_description(ues, scu.get_request_args())
if request.method == "GET": if request.method == "GET":
initvalues = { initvalues = {
"note_" + str(ue["ue_id"]): ue["validation"].get("moy_ue", "") "note_" + str(ue["ue_id"]): ue["validation"].get("moy_ue", "") for ue in ues
for ue in ue_list
} }
else: else:
initvalues = {} initvalues = {}
@ -247,15 +246,13 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid):
return "\n".join(H) return "\n".join(H)
else: # soumission else: # soumission
# simule erreur # simule erreur
ok, message = _check_values(ue_list, tf[2]) ok, message = _check_values(ues, tf[2])
if not ok: if not ok:
H = _make_page(etud, sem, tf, message=message) H = _make_page(etud, sem, tf, message=message)
return "\n".join(H) return "\n".join(H)
else: else:
# Submit # Submit
_record_ue_validations_and_coefs( _record_ue_validations_and_coefs(formsemestre_id, etudid, ues, tf[2])
formsemestre_id, etudid, ue_list, tf[2]
)
return flask.redirect( return flask.redirect(
"formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s"
% (formsemestre_id, etudid) % (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 """Description du formulaire de saisie des UE / validations
Pour chaque UE, on peut saisir: son code jury, sa note, son coefficient. 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"}), ("formsemestre_id", {"input_type": "hidden"}),
("etudid", {"input_type": "hidden"}), ("etudid", {"input_type": "hidden"}),
] ]
for ue in ue_list: for ue in ues:
# Menu pour code validation UE: # Menu pour code validation UE:
# Ne propose que ADM, CMP et "Non inscrit" # Ne propose que ADM, CMP et "Non inscrit"
select_name = "valid_" + str(ue["ue_id"]) select_name = "valid_" + str(ue["ue_id"])
@ -439,8 +436,8 @@ def _list_ue_with_coef_and_validations(sem, etudid):
""" """
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
formsemestre_id = sem["formsemestre_id"] formsemestre_id = sem["formsemestre_id"]
ue_list = sco_edit_ue.ue_list({"formation_id": sem["formation_id"]}) ues = sco_edit_ue.ue_list({"formation_id": sem["formation_id"]})
for ue in ue_list: for ue in ues:
# add coefficient # add coefficient
uecoef = sco_formsemestre.formsemestre_uecoef_list( uecoef = sco_formsemestre.formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]} cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]}
@ -462,11 +459,11 @@ def _list_ue_with_coef_and_validations(sem, etudid):
ue["validation"] = validation[0] ue["validation"] = validation[0]
else: else:
ue["validation"] = {} ue["validation"] = {}
return ue_list return ues
def _record_ue_validations_and_coefs(formsemestre_id, etudid, ue_list, values): def _record_ue_validations_and_coefs(formsemestre_id, etudid, ues, values):
for ue in ue_list: for ue in ues:
code = values.get("valid_" + str(ue["ue_id"]), False) code = values.get("valid_" + str(ue["ue_id"]), False)
if code == "None": if code == "None":
code = None code = None

View File

@ -178,6 +178,19 @@ def moduleimpl_withmodule_list(
return modimpls 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): def do_moduleimpl_inscription_list(moduleimpl_id=None, etudid=None):
"list moduleimpl_inscriptions" "list moduleimpl_inscriptions"
args = locals() args = locals()

View File

@ -348,7 +348,6 @@ def make_formsemestre_recapcomplet(
if not hidemodules: if not hidemodules:
h.append("") h.append("")
pass pass
if not hidemodules and not ue["is_external"]: if not hidemodules and not ue["is_external"]:
for modimpl in modimpls: for modimpl in modimpls:
if modimpl["module"]["ue_id"] == ue["ue_id"]: if modimpl["module"]["ue_id"] == ue["ue_id"]:

View File

@ -82,25 +82,35 @@ def simple_dictlist2xml(dictlist, tagname=None, quote=False, pretty=True):
return ans 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): 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: for d in dictlist:
elem = ElementTree.Element(tagname) elem = ElementTree.Element(tagname)
root.append(elem) root.append(elem)
if isinstance(d, scalar_types) or isinstance(d, ApoEtapeVDI): if isinstance(d, scalar_types) or isinstance(d, ApoEtapeVDI):
elem.set("code", str(d)) elem.set("code", _repr_as_xml(d))
else: else:
if quote: if quote:
d_scalar = dict( d_scalar = dict(
[ [
(k, quote_xml_attr(v)) (k, quote_xml_attr(_repr_as_xml(v)))
for (k, v) in d.items() for (k, v) in d.items()
if isinstance(v, scalar_types) if isinstance(v, scalar_types)
] ]
) )
else: else:
d_scalar = dict( 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: for k in d_scalar:
elem.set(k, d_scalar[k]) elem.set(k, d_scalar[k])

View File

@ -1511,6 +1511,19 @@ span.ue_type {
margin-right: 1.5em; 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 { li.notes_matiere_list {
margin-top: 2px; margin-top: 2px;
} }

View File

@ -41,9 +41,12 @@ import flask
from flask import url_for from flask import url_for
from flask import current_app, g, request from flask import current_app, g, request
from flask_login import current_user from flask_login import current_user
from werkzeug.utils import redirect
from config import Config from config import Config
from app import db
from app import models
from app.auth.models import User from app.auth.models import User
from app.decorators import ( from app.decorators import (
@ -336,8 +339,8 @@ sco_publish(
) )
@bp.route("/ue_table")
@bp.route("/ue_list") # backward compat @bp.route("/ue_list") # backward compat
@bp.route("/ue_table")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
@scodoc7func @scodoc7func
@ -345,6 +348,25 @@ def ue_table(formation_id=None, msg=""):
return sco_edit_ue.ue_table(formation_id=formation_id, msg=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("/ue_sharing_code", sco_edit_ue.ue_sharing_code, Permission.ScoView)
sco_publish( sco_publish(
"/edit_ue_set_code_apogee", "/edit_ue_set_code_apogee",

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.0.54" SCOVERSION = "9.0.55"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"

View File

@ -324,7 +324,7 @@ class ScoFake(object):
formation (dict), liste d'ue (dicts), liste de modules. formation (dict), liste d'ue (dicts), liste de modules.
""" """
f = self.create_formation(acronyme=acronyme, titre=titre) f = self.create_formation(acronyme=acronyme, titre=titre)
ue_list = [] ues = []
mod_list = [] mod_list = []
for semestre_id in range(1, nb_semestre + 1): for semestre_id in range(1, nb_semestre + 1):
for n in range(1, nb_ue_per_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), acronyme="TSU%s%s" % (semestre_id, n),
titre="ue test %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") mat = self.create_matiere(ue_id=ue["ue_id"], titre="matière test")
for _ in range(nb_module_per_ue): for _ in range(nb_module_per_ue):
mod = self.create_module( mod = self.create_module(
@ -346,7 +346,7 @@ class ScoFake(object):
formation_id=f["formation_id"], # faiblesse de l'API formation_id=f["formation_id"], # faiblesse de l'API
) )
mod_list.append(mod) mod_list.append(mod)
return f, ue_list, mod_list return f, ues, mod_list
def setup_formsemestre( def setup_formsemestre(
self, self,

View File

@ -339,6 +339,10 @@ def test_import_formation(test_client):
f = sco_formations.formation_import_xml(doc) f = sco_formations.formation_import_xml(doc)
assert len(f) == 3 # 3-uple assert len(f) == 3 # 3-uple
formation_id = f[0] 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 # --- Mise en place de 4 semestres
sems = [ sems = [
G.create_formsemestre( G.create_formsemestre(