forked from ScoDoc/ScoDoc
1063 lines
39 KiB
Python
1063 lines
39 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
|
#
|
|
##############################################################################
|
|
|
|
"""Ajout/Modification/Suppression UE
|
|
|
|
"""
|
|
import flask
|
|
from flask import g, url_for
|
|
from flask_login import current_user
|
|
|
|
import app.scodoc.notesdb as ndb
|
|
import app.scodoc.sco_utils as scu
|
|
from app.scodoc.notes_log 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 import html_sco_header
|
|
from app.scodoc import sco_cache
|
|
from app.scodoc import sco_codes_parcours
|
|
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_etud
|
|
from app.scodoc import sco_formsemestre
|
|
from app.scodoc import sco_groups
|
|
from app.scodoc import sco_moduleimpl
|
|
from app.scodoc import sco_news
|
|
from app.scodoc import sco_permissions
|
|
from app.scodoc import sco_preferences
|
|
from app.scodoc import sco_tag_module
|
|
|
|
_ueEditor = ndb.EditableTable(
|
|
"notes_ue",
|
|
"ue_id",
|
|
(
|
|
"ue_id",
|
|
"formation_id",
|
|
"acronyme",
|
|
"numero",
|
|
"titre",
|
|
"type",
|
|
"ue_code",
|
|
"ects",
|
|
"is_external",
|
|
"code_apogee",
|
|
"coefficient",
|
|
),
|
|
sortkey="numero",
|
|
input_formators={
|
|
"type": ndb.int_null_is_zero,
|
|
"is_external": bool,
|
|
},
|
|
output_formators={
|
|
"numero": ndb.int_null_is_zero,
|
|
"ects": ndb.float_null_is_null,
|
|
"coefficient": ndb.float_null_is_zero,
|
|
},
|
|
)
|
|
|
|
|
|
def do_ue_list(context, *args, **kw):
|
|
"list UEs"
|
|
cnx = ndb.GetDBConnexion()
|
|
return _ueEditor.list(cnx, *args, **kw)
|
|
|
|
|
|
def do_ue_create(context, args):
|
|
"create an ue"
|
|
from app.scodoc import sco_formations
|
|
|
|
cnx = ndb.GetDBConnexion()
|
|
# check duplicates
|
|
ues = do_ue_list(
|
|
context, {"formation_id": args["formation_id"], "acronyme": args["acronyme"]}
|
|
)
|
|
if ues:
|
|
raise ScoValueError('Acronyme d\'UE "%s" déjà utilisé !' % args["acronyme"])
|
|
# create
|
|
r = _ueEditor.create(cnx, args)
|
|
|
|
# news
|
|
F = sco_formations.formation_list(
|
|
context, args={"formation_id": args["formation_id"]}
|
|
)[0]
|
|
sco_news.add(
|
|
typ=sco_news.NEWS_FORM,
|
|
object=args["formation_id"],
|
|
text="Modification de la formation %(acronyme)s" % F,
|
|
max_frequency=3,
|
|
)
|
|
return r
|
|
|
|
|
|
def do_ue_delete(context, ue_id, delete_validations=False, REQUEST=None, force=False):
|
|
"delete UE and attached matieres (but not modules)"
|
|
from app.scodoc import sco_formations
|
|
from app.scodoc import sco_parcours_dut
|
|
|
|
cnx = ndb.GetDBConnexion()
|
|
log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue_id, delete_validations))
|
|
# check
|
|
ue = do_ue_list(context, {"ue_id": ue_id})
|
|
if not ue:
|
|
raise ScoValueError("UE inexistante !")
|
|
ue = ue[0]
|
|
if ue_is_locked(context, ue["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}
|
|
)
|
|
if validations and not delete_validations and not force:
|
|
return scu.confirm_dialog(
|
|
"<p>%d étudiants ont validé l'UE %s (%s)</p><p>Si vous supprimez cette UE, ces validations vont être supprimées !</p>"
|
|
% (len(validations), ue["acronyme"], ue["titre"]),
|
|
dest_url="",
|
|
target_variable="delete_validations",
|
|
cancel_url="ue_list?formation_id=%s" % ue["formation_id"],
|
|
parameters={"ue_id": ue_id, "dialog_confirmed": 1},
|
|
)
|
|
if delete_validations:
|
|
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},
|
|
)
|
|
|
|
# delete all matiere in this UE
|
|
mats = sco_edit_matiere.do_matiere_list(context, {"ue_id": ue_id})
|
|
for mat in mats:
|
|
sco_edit_matiere.do_matiere_delete(context, mat["matiere_id"])
|
|
# delete uecoef and events
|
|
ndb.SimpleQuery(
|
|
"DELETE FROM notes_formsemestre_uecoef 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 ?):
|
|
sco_cache.invalidate_formsemestre()
|
|
# news
|
|
F = sco_formations.formation_list(
|
|
context, args={"formation_id": ue["formation_id"]}
|
|
)[0]
|
|
sco_news.add(
|
|
typ=sco_news.NEWS_FORM,
|
|
object=ue["formation_id"],
|
|
text="Modification de la formation %(acronyme)s" % F,
|
|
max_frequency=3,
|
|
)
|
|
#
|
|
if not force:
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.ue_list",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formation_id=ue["formation_id"],
|
|
)
|
|
)
|
|
else:
|
|
return None
|
|
|
|
|
|
def ue_create(context, formation_id=None, REQUEST=None):
|
|
"""Creation d'une UE"""
|
|
return ue_edit(context, create=True, formation_id=formation_id, REQUEST=REQUEST)
|
|
|
|
|
|
def ue_edit(context, ue_id=None, create=False, formation_id=None, REQUEST=None):
|
|
"""Modification ou creation d'une UE"""
|
|
from app.scodoc import sco_formations
|
|
|
|
create = int(create)
|
|
if not create:
|
|
U = do_ue_list(context, args={"ue_id": ue_id})
|
|
if not U:
|
|
raise ScoValueError("UE inexistante !")
|
|
U = U[0]
|
|
formation_id = U["formation_id"]
|
|
title = "Modification de l'UE %(titre)s" % U
|
|
initvalues = U
|
|
submitlabel = "Modifier les valeurs"
|
|
else:
|
|
title = "Création d'une UE"
|
|
initvalues = {}
|
|
submitlabel = "Créer cette UE"
|
|
Fol = sco_formations.formation_list(context, args={"formation_id": formation_id})
|
|
if not Fol:
|
|
raise ScoValueError(
|
|
"Formation %s inexistante ! (si vous avez suivi un lien valide, merci de signaler le problème)"
|
|
% formation_id
|
|
)
|
|
Fo = Fol[0]
|
|
parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
|
|
|
|
H = [
|
|
html_sco_header.sco_header(page_title=title, javascripts=["js/edit_ue.js"]),
|
|
"<h2>" + title,
|
|
" (formation %(acronyme)s, version %(version)s)</h2>" % Fo,
|
|
"""
|
|
<p class="help">Les UE sont des groupes de modules dans une formation donnée, utilisés pour l'évaluation (on calcule des moyennes par UE et applique des seuils ("barres")).
|
|
</p>
|
|
|
|
<p class="help">Note: L'UE n'a pas de coefficient associé. Seuls les <em>modules</em> ont des coefficients.
|
|
</p>""",
|
|
]
|
|
|
|
ue_types = parcours.ALLOWED_UE_TYPES
|
|
ue_types.sort()
|
|
ue_types_names = [sco_codes_parcours.UE_TYPE_NAME[k] for k in ue_types]
|
|
ue_types = [str(x) for x in ue_types]
|
|
|
|
fw = [
|
|
("ue_id", {"input_type": "hidden"}),
|
|
("create", {"input_type": "hidden", "default": create}),
|
|
("formation_id", {"input_type": "hidden", "default": formation_id}),
|
|
("titre", {"size": 30, "explanation": "nom de l'UE"}),
|
|
("acronyme", {"size": 8, "explanation": "abbréviation", "allow_null": False}),
|
|
(
|
|
"numero",
|
|
{
|
|
"size": 2,
|
|
"explanation": "numéro (1,2,3,4) de l'UE pour l'ordre d'affichage",
|
|
"type": "int",
|
|
},
|
|
),
|
|
(
|
|
"type",
|
|
{
|
|
"explanation": "type d'UE",
|
|
"input_type": "menu",
|
|
"allowed_values": ue_types,
|
|
"labels": ue_types_names,
|
|
},
|
|
),
|
|
(
|
|
"ects",
|
|
{
|
|
"size": 4,
|
|
"type": "float",
|
|
"title": "ECTS",
|
|
"explanation": "nombre de crédits ECTS",
|
|
},
|
|
),
|
|
(
|
|
"coefficient",
|
|
{
|
|
"size": 4,
|
|
"type": "float",
|
|
"title": "Coefficient",
|
|
"explanation": """les coefficients d'UE ne sont utilisés que lorsque
|
|
l'option <em>Utiliser les coefficients d'UE pour calculer la moyenne générale</em>
|
|
est activée. Par défaut, le coefficient d'une UE est simplement la somme des
|
|
coefficients des modules dans lesquels l'étudiant a des notes.
|
|
""",
|
|
},
|
|
),
|
|
(
|
|
"ue_code",
|
|
{
|
|
"size": 12,
|
|
"title": "Code UE",
|
|
"explanation": "code interne (optionnel). Toutes les UE partageant le même code (et le même code de formation) sont compatibles (compensation de semestres, capitalisation d'UE). Voir liste ci-dessous.",
|
|
},
|
|
),
|
|
(
|
|
"code_apogee",
|
|
{
|
|
"title": "Code Apogée",
|
|
"size": 25,
|
|
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
|
|
},
|
|
),
|
|
]
|
|
if parcours.UE_IS_MODULE:
|
|
# demande le semestre pour creer le module immediatement:
|
|
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
|
fw.append(
|
|
(
|
|
"semestre_id",
|
|
{
|
|
"input_type": "menu",
|
|
"type": "int",
|
|
"title": scu.strcapitalize(parcours.SESSION_NAME),
|
|
"explanation": "%s de début du module dans la formation"
|
|
% parcours.SESSION_NAME,
|
|
"labels": [str(x) for x in semestres_indices],
|
|
"allowed_values": semestres_indices,
|
|
},
|
|
)
|
|
)
|
|
if create and not parcours.UE_IS_MODULE:
|
|
fw.append(
|
|
(
|
|
"create_matiere",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"default": False,
|
|
"title": "Créer matière identique",
|
|
"explanation": "créer immédiatement une matière dans cette UE (utile si on n'utilise pas de matières)",
|
|
},
|
|
)
|
|
)
|
|
tf = TrivialFormulator(
|
|
REQUEST.URL0, REQUEST.form, fw, initvalues=initvalues, submitlabel=submitlabel
|
|
)
|
|
if tf[0] == 0:
|
|
X = """<div id="ue_list_code"></div>
|
|
"""
|
|
return "\n".join(H) + tf[1] + X + html_sco_header.sco_footer()
|
|
else:
|
|
if create:
|
|
if not tf[2]["ue_code"]:
|
|
del tf[2]["ue_code"]
|
|
if not tf[2]["numero"]:
|
|
if not "semestre_id" in tf[2]:
|
|
tf[2]["semestre_id"] = 0
|
|
# numero regroupant par semestre ou année:
|
|
tf[2]["numero"] = next_ue_numero(
|
|
context, formation_id, int(tf[2]["semestre_id"] or 0)
|
|
)
|
|
|
|
ue_id = do_ue_create(context, tf[2])
|
|
if parcours.UE_IS_MODULE or tf[2]["create_matiere"]:
|
|
matiere_id = sco_edit_matiere.do_matiere_create(
|
|
context,
|
|
{"ue_id": ue_id, "titre": tf[2]["titre"], "numero": 1},
|
|
)
|
|
if parcours.UE_IS_MODULE:
|
|
# dans ce mode, crée un (unique) module dans l'UE:
|
|
_ = sco_edit_module.do_module_create(
|
|
context,
|
|
{
|
|
"titre": tf[2]["titre"],
|
|
"code": tf[2]["acronyme"],
|
|
"coefficient": 1.0, # tous les modules auront coef 1, et on utilisera les ECTS
|
|
"ue_id": ue_id,
|
|
"matiere_id": matiere_id,
|
|
"formation_id": formation_id,
|
|
"semestre_id": tf[2]["semestre_id"],
|
|
},
|
|
)
|
|
else:
|
|
do_ue_edit(context, tf[2])
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.ue_list", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
|
)
|
|
)
|
|
|
|
|
|
def _add_ue_semestre_id(context, ue_list):
|
|
"""ajoute semestre_id dans les ue, en regardant le premier module de chacune.
|
|
Les UE sans modules se voient attribuer le numero UE_SEM_DEFAULT (1000000),
|
|
qui les place à la fin de la liste.
|
|
"""
|
|
for ue in ue_list:
|
|
Modlist = sco_edit_module.do_module_list(context, args={"ue_id": ue["ue_id"]})
|
|
if Modlist:
|
|
ue["semestre_id"] = Modlist[0]["semestre_id"]
|
|
else:
|
|
ue["semestre_id"] = 1000000
|
|
|
|
|
|
def next_ue_numero(context, formation_id, semestre_id=None):
|
|
"""Numero d'une nouvelle UE dans cette formation.
|
|
Si le semestre est specifie, cherche les UE ayant des modules de ce semestre
|
|
"""
|
|
ue_list = do_ue_list(context, args={"formation_id": formation_id})
|
|
if not ue_list:
|
|
return 0
|
|
if semestre_id is None:
|
|
return ue_list[-1]["numero"] + 1000
|
|
else:
|
|
# Avec semestre: (prend le semestre du 1er module de l'UE)
|
|
_add_ue_semestre_id(context, ue_list)
|
|
ue_list_semestre = [ue for ue in ue_list if ue["semestre_id"] == semestre_id]
|
|
if ue_list_semestre:
|
|
return ue_list_semestre[-1]["numero"] + 10
|
|
else:
|
|
return ue_list[-1]["numero"] + 1000
|
|
|
|
|
|
def ue_delete(
|
|
context, ue_id=None, delete_validations=False, dialog_confirmed=False, REQUEST=None
|
|
):
|
|
"""Delete an UE"""
|
|
ue = do_ue_list(context, args={"ue_id": ue_id})
|
|
if not ue:
|
|
raise ScoValueError("UE inexistante !")
|
|
ue = ue[0]
|
|
|
|
if not dialog_confirmed:
|
|
return scu.confirm_dialog(
|
|
"<h2>Suppression de l'UE %(titre)s (%(acronyme)s))</h2>" % ue,
|
|
dest_url="",
|
|
parameters={"ue_id": ue_id},
|
|
cancel_url="ue_list?formation_id=%s" % ue["formation_id"],
|
|
)
|
|
|
|
return do_ue_delete(
|
|
context, ue_id, delete_validations=delete_validations, REQUEST=REQUEST
|
|
)
|
|
|
|
|
|
def ue_list(context, formation_id=None, msg="", REQUEST=None):
|
|
"""Liste des matières et modules d'une formation, avec liens pour
|
|
editer (si non verrouillée).
|
|
"""
|
|
from app.scodoc import sco_formations
|
|
from app.scodoc import sco_formsemestre_validation
|
|
|
|
F = sco_formations.formation_list(context, args={"formation_id": formation_id})
|
|
if not F:
|
|
raise ScoValueError("invalid formation_id")
|
|
F = F[0]
|
|
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
|
locked = sco_formations.formation_has_locked_sems(context, formation_id)
|
|
|
|
ue_list = do_ue_list(context, args={"formation_id": formation_id})
|
|
# tri par semestre et numero:
|
|
_add_ue_semestre_id(context, ue_list)
|
|
ue_list.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
|
has_duplicate_ue_codes = len(set([ue["ue_code"] for ue in ue_list])) != len(ue_list)
|
|
|
|
perm_change = current_user.has_permission(Permission.ScoChangeFormation)
|
|
# editable = (not locked) and 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
|
|
tag_editable = (
|
|
current_user.has_permission(Permission.ScoEditFormationTags) or perm_change
|
|
)
|
|
if locked:
|
|
lockicon = scu.icontag("lock32_img", title="verrouillé")
|
|
else:
|
|
lockicon = ""
|
|
|
|
arrow_up, arrow_down, arrow_none = sco_groups.getArrowIconsTags()
|
|
delete_icon = scu.icontag(
|
|
"delete_small_img", title="Supprimer (module inutilisé)", alt="supprimer"
|
|
)
|
|
delete_disabled_icon = scu.icontag(
|
|
"delete_small_dis_img", title="Suppression impossible (module utilisé)"
|
|
)
|
|
H = [
|
|
html_sco_header.sco_header(
|
|
cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css"],
|
|
javascripts=[
|
|
"libjs/jinplace-1.2.1.min.js",
|
|
"js/ue_list.js",
|
|
"libjs/jQuery-tagEditor/jquery.tag-editor.min.js",
|
|
"libjs/jQuery-tagEditor/jquery.caret.min.js",
|
|
"js/module_tag_editor.js",
|
|
],
|
|
page_title="Programme %s" % F["acronyme"],
|
|
),
|
|
"""<h2>Formation %(titre)s (%(acronyme)s) [version %(version)s] code %(formation_code)s"""
|
|
% F,
|
|
lockicon,
|
|
"</h2>",
|
|
]
|
|
if locked:
|
|
H.append(
|
|
"""<p class="help">Cette formation est verrouillée car %d semestres verrouillés s'y réferent.
|
|
Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module), vous devez:
|
|
</p>
|
|
<ul class="help">
|
|
<li>soit créer une nouvelle version de cette formation pour pouvoir l'éditer librement (vous pouvez passer par la fonction "Associer à une nouvelle version du programme" (menu "Semestre") si vous avez un semestre en cours);</li>
|
|
<li>soit déverrouiller le ou les semestres qui s'y réfèrent (attention, en principe ces semestres sont archivés
|
|
et ne devraient pas être modifiés).</li>
|
|
</ul>"""
|
|
% len(locked)
|
|
)
|
|
if msg:
|
|
H.append('<p class="msg">' + msg + "</p>")
|
|
|
|
if has_duplicate_ue_codes:
|
|
H.append(
|
|
"""<div class="ue_warning"><span>Attention: plusieurs UE de cette formation ont le même code. Il faut corriger cela ci-dessous, sinon les calculs d'ECTS seront erronés !</span></div>"""
|
|
)
|
|
|
|
# Description de la formation
|
|
H.append('<div class="formation_descr">')
|
|
H.append(
|
|
'<div class="fd_d"><span class="fd_t">Titre:</span><span class="fd_v">%(titre)s</span></div>'
|
|
% F
|
|
)
|
|
H.append(
|
|
'<div class="fd_d"><span class="fd_t">Titre officiel:</span><span class="fd_v">%(titre_officiel)s</span></div>'
|
|
% F
|
|
)
|
|
H.append(
|
|
'<div class="fd_d"><span class="fd_t">Acronyme:</span><span class="fd_v">%(acronyme)s</span></div>'
|
|
% F
|
|
)
|
|
H.append(
|
|
'<div class="fd_d"><span class="fd_t">Code:</span><span class="fd_v">%(formation_code)s</span></div>'
|
|
% F
|
|
)
|
|
H.append(
|
|
'<div class="fd_d"><span class="fd_t">Version:</span><span class="fd_v">%(version)s</span></div>'
|
|
% F
|
|
)
|
|
H.append(
|
|
'<div class="fd_d"><span class="fd_t">Type parcours:</span><span class="fd_v">%s</span></div>'
|
|
% parcours.__doc__
|
|
)
|
|
if parcours.UE_IS_MODULE:
|
|
H.append(
|
|
'<div class="fd_d"><span class="fd_t"> </span><span class="fd_n">(Chaque module est une UE)</span></div>'
|
|
)
|
|
|
|
if editable:
|
|
H.append(
|
|
'<div><a href="formation_edit?formation_id=%(formation_id)s" class="stdlink">modifier ces informations</a></div>'
|
|
% F
|
|
)
|
|
|
|
H.append("</div>")
|
|
|
|
# Description des UE/matières/modules
|
|
H.append('<div class="formation_ue_list">')
|
|
H.append('<div class="ue_list_tit">Programme pédagogique:</div>')
|
|
|
|
H.append(
|
|
'<form><input type="checkbox" class="sco_tag_checkbox">montrer les tags</input></form>'
|
|
)
|
|
|
|
cur_ue_semestre_id = None
|
|
iue = 0
|
|
for UE in ue_list:
|
|
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(ue_list) - 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(context, 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.do_matiere_list(context, 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(
|
|
context, 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(
|
|
context, Mat["matiere_id"]
|
|
):
|
|
H.append("</a>")
|
|
|
|
H.append('<ul class="notes_module_list">')
|
|
Modlist = sco_edit_module.do_module_list(
|
|
context, args={"matiere_id": Mat["matiere_id"]}
|
|
)
|
|
im = 0
|
|
for Mod in Modlist:
|
|
Mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls(
|
|
context, 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(context, 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(context, 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:
|
|
H.append(
|
|
'<ul><li><a class="stdlink" href="ue_create?formation_id=%s">Ajouter une UE</a></li>'
|
|
% formation_id
|
|
)
|
|
H.append(
|
|
'<li><a href="formation_add_malus_modules?formation_id=%(formation_id)s" class="stdlink">Ajouter des modules de malus dans chaque UE</a></li></ul>'
|
|
% F
|
|
)
|
|
H.append("</div>") # formation_ue_list
|
|
|
|
H.append("<p><ul>")
|
|
if editable:
|
|
H.append(
|
|
"""
|
|
<li><a class="stdlink" href="formation_create_new_version?formation_id=%(formation_id)s">Créer une nouvelle version (non verrouillée)</a></li>
|
|
"""
|
|
% F
|
|
)
|
|
H.append(
|
|
"""
|
|
<li><a class="stdlink" href="formation_table_recap?formation_id=%(formation_id)s">Table récapitulative de la formation</a></li>
|
|
|
|
<li><a class="stdlink" href="formation_export?formation_id=%(formation_id)s&format=xml">Export XML de la formation</a> (permet de la sauvegarder pour l'échanger avec un autre site)</li>
|
|
|
|
<li><a class="stdlink" href="formation_export?formation_id=%(formation_id)s&format=json">Export JSON de la formation</a></li>
|
|
|
|
<li><a class="stdlink" href="module_list?formation_id=%(formation_id)s">Liste détaillée des modules de la formation</a> (debug) </li>
|
|
</ul>
|
|
</p>"""
|
|
% F
|
|
)
|
|
if perm_change:
|
|
H.append(
|
|
"""
|
|
<h3> <a name="sems">Semestres ou sessions de cette formation</a></h3>
|
|
<p><ul>"""
|
|
)
|
|
for sem in sco_formsemestre.do_formsemestre_list(
|
|
context, args={"formation_id": formation_id}
|
|
):
|
|
H.append(
|
|
'<li><a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titremois)s</a>'
|
|
% sem
|
|
)
|
|
if not sem["etat"]:
|
|
H.append(" [verrouillé]")
|
|
else:
|
|
H.append(
|
|
' <a class="stdlink" href="formsemestre_editwithmodules?formation_id=%(formation_id)s&formsemestre_id=%(formsemestre_id)s">Modifier</a>'
|
|
% sem
|
|
)
|
|
H.append("</li>")
|
|
H.append("</ul>")
|
|
|
|
if current_user.has_permission(Permission.ScoImplement):
|
|
H.append(
|
|
"""<ul>
|
|
<li><a class="stdlink" href="formsemestre_createwithmodules?formation_id=%(formation_id)s&semestre_id=1">Mettre en place un nouveau semestre de formation %(acronyme)s</a>
|
|
</li>
|
|
|
|
</ul>"""
|
|
% F
|
|
)
|
|
# <li>(debug) <a class="stdlink" href="check_form_integrity?formation_id=%(formation_id)s">Vérifier cohérence</a></li>
|
|
|
|
warn, _ = sco_formsemestre_validation.check_formation_ues(context, formation_id)
|
|
H.append(warn)
|
|
|
|
H.append(html_sco_header.sco_footer())
|
|
return "".join(H)
|
|
|
|
|
|
def ue_sharing_code(context, ue_code=None, ue_id=None, hide_ue_id=None):
|
|
"""HTML list of UE sharing this code
|
|
Either ue_code or ue_id may be specified.
|
|
hide_ue_id spécifie un id à retirer de la liste.
|
|
"""
|
|
from app.scodoc import sco_formations
|
|
|
|
if ue_id:
|
|
ue = do_ue_list(context, args={"ue_id": ue_id})[0]
|
|
if not ue_code:
|
|
ue_code = ue["ue_code"]
|
|
F = sco_formations.formation_list(
|
|
context, args={"formation_id": ue["formation_id"]}
|
|
)[0]
|
|
formation_code = F["formation_code"]
|
|
|
|
ue_list_all = do_ue_list(context, args={"ue_code": ue_code})
|
|
if ue_id:
|
|
# retire les UE d'autres formations:
|
|
# log('checking ucode %s formation %s' % (ue_code, formation_code))
|
|
ue_list = []
|
|
for ue in ue_list_all:
|
|
F = sco_formations.formation_list(
|
|
context, args={"formation_id": ue["formation_id"]}
|
|
)[0]
|
|
if formation_code == F["formation_code"]:
|
|
ue_list.append(ue)
|
|
else:
|
|
ue_list = ue_list_all
|
|
|
|
if hide_ue_id: # enlève l'ue de depart
|
|
ue_list = [ue for ue in ue_list if ue["ue_id"] != hide_ue_id]
|
|
|
|
if not ue_list:
|
|
if ue_id:
|
|
return """<span class="ue_share">Seule UE avec code %s</span>""" % ue_code
|
|
else:
|
|
return """<span class="ue_share">Aucune UE avec code %s</span>""" % ue_code
|
|
H = []
|
|
if ue_id:
|
|
H.append('<span class="ue_share">Autres UE avec le code %s:</span>' % ue_code)
|
|
else:
|
|
H.append('<span class="ue_share">UE avec le code %s:</span>' % ue_code)
|
|
H.append("<ul>")
|
|
for ue in ue_list:
|
|
F = sco_formations.formation_list(
|
|
context, args={"formation_id": ue["formation_id"]}
|
|
)[0]
|
|
H.append(
|
|
'<li>%s (%s) dans <a class="stdlink" href="ue_list?formation_id=%s">%s (%s)</a>, version %s</li>'
|
|
% (
|
|
ue["acronyme"],
|
|
ue["titre"],
|
|
F["formation_id"],
|
|
F["acronyme"],
|
|
F["titre"],
|
|
F["version"],
|
|
)
|
|
)
|
|
H.append("</ul>")
|
|
return "\n".join(H)
|
|
|
|
|
|
def do_ue_edit(context, args, bypass_lock=False, dont_invalidate_cache=False):
|
|
"edit an UE"
|
|
# check
|
|
ue_id = args["ue_id"]
|
|
ue = do_ue_list(context, {"ue_id": ue_id})[0]
|
|
if (not bypass_lock) and ue_is_locked(context, ue["ue_id"]):
|
|
raise ScoLockedFormError()
|
|
# check: acronyme unique dans cette formation
|
|
if "acronyme" in args:
|
|
new_acro = args["acronyme"]
|
|
ues = do_ue_list(
|
|
context, {"formation_id": ue["formation_id"], "acronyme": new_acro}
|
|
)
|
|
if ues and ues[0]["ue_id"] != ue_id:
|
|
raise ScoValueError('Acronyme d\'UE "%s" déjà utilisé !' % args["acronyme"])
|
|
|
|
# On ne peut pas supprimer le code UE:
|
|
if "ue_code" in args and not args["ue_code"]:
|
|
del args["ue_code"]
|
|
|
|
cnx = ndb.GetDBConnexion()
|
|
_ueEditor.edit(cnx, args)
|
|
|
|
if not dont_invalidate_cache:
|
|
# Invalide les semestres utilisant cette formation:
|
|
sco_edit_formation.invalidate_sems_in_formation(ue["formation_id"])
|
|
|
|
|
|
# essai edition en ligne:
|
|
def edit_ue_set_code_apogee(context, id=None, value=None, REQUEST=None):
|
|
"set UE code apogee"
|
|
ue_id = id
|
|
value = value.strip("-_ \t")
|
|
log("edit_ue_set_code_apogee: ue_id=%s code_apogee=%s" % (ue_id, value))
|
|
|
|
ues = do_ue_list(context, args={"ue_id": ue_id})
|
|
if not ues:
|
|
return "ue invalide"
|
|
|
|
do_ue_edit(
|
|
context,
|
|
{"ue_id": ue_id, "code_apogee": value},
|
|
bypass_lock=True,
|
|
dont_invalidate_cache=False,
|
|
)
|
|
if not value:
|
|
value = scu.APO_MISSING_CODE_STR
|
|
return value
|
|
|
|
|
|
def ue_is_locked(context, ue_id):
|
|
"""True if UE should not be modified
|
|
(contains modules used in a locked formsemestre)
|
|
"""
|
|
r = ndb.SimpleDictFetch(
|
|
"""SELECT ue.id
|
|
FROM notes_ue ue, notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi
|
|
WHERE ue.id = mod.ue_id
|
|
AND mi.module_id = mod.id AND mi.formsemestre_id = sem.id
|
|
AND ue.id = %(ue_id)s AND sem.etat = false
|
|
""",
|
|
{"ue_id": ue_id},
|
|
)
|
|
return len(r) > 0
|
|
|
|
|
|
# ---- Table recap formation
|
|
def formation_table_recap(context, formation_id, format="html", REQUEST=None):
|
|
"""Table recapitulant formation."""
|
|
from app.scodoc import sco_formations
|
|
|
|
F = sco_formations.formation_list(context, args={"formation_id": formation_id})
|
|
if not F:
|
|
raise ScoValueError("invalid formation_id")
|
|
F = F[0]
|
|
T = []
|
|
ue_list = do_ue_list(context, args={"formation_id": formation_id})
|
|
for UE in ue_list:
|
|
Matlist = sco_edit_matiere.do_matiere_list(context, args={"ue_id": UE["ue_id"]})
|
|
for Mat in Matlist:
|
|
Modlist = sco_edit_module.do_module_list(
|
|
context, args={"matiere_id": Mat["matiere_id"]}
|
|
)
|
|
for Mod in Modlist:
|
|
Mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls(
|
|
context, Mod["module_id"]
|
|
)
|
|
#
|
|
T.append(
|
|
{
|
|
"UE_acro": UE["acronyme"],
|
|
"Mat_tit": Mat["titre"],
|
|
"Mod_tit": Mod["abbrev"] or Mod["titre"],
|
|
"Mod_code": Mod["code"],
|
|
"Mod_coef": Mod["coefficient"],
|
|
"Mod_sem": Mod["semestre_id"],
|
|
"nb_moduleimpls": Mod["nb_moduleimpls"],
|
|
"heures_cours": Mod["heures_cours"],
|
|
"heures_td": Mod["heures_td"],
|
|
"heures_tp": Mod["heures_tp"],
|
|
"ects": Mod["ects"],
|
|
}
|
|
)
|
|
columns_ids = [
|
|
"UE_acro",
|
|
"Mat_tit",
|
|
"Mod_tit",
|
|
"Mod_code",
|
|
"Mod_coef",
|
|
"Mod_sem",
|
|
"nb_moduleimpls",
|
|
"heures_cours",
|
|
"heures_td",
|
|
"heures_tp",
|
|
"ects",
|
|
]
|
|
titles = {
|
|
"UE_acro": "UE",
|
|
"Mat_tit": "Matière",
|
|
"Mod_tit": "Module",
|
|
"Mod_code": "Code",
|
|
"Mod_coef": "Coef.",
|
|
"Mod_sem": "Sem.",
|
|
"nb_moduleimpls": "Nb utilisé",
|
|
"heures_cours": "Cours (h)",
|
|
"heures_td": "TD (h)",
|
|
"heures_tp": "TP (h)",
|
|
"ects": "ECTS",
|
|
}
|
|
|
|
title = (
|
|
"""Formation %(titre)s (%(acronyme)s) [version %(version)s] code %(formation_code)s"""
|
|
% F
|
|
)
|
|
tab = GenTable(
|
|
columns_ids=columns_ids,
|
|
rows=T,
|
|
titles=titles,
|
|
origin="Généré par %s le " % scu.VERSION.SCONAME
|
|
+ scu.timedate_human_repr()
|
|
+ "",
|
|
caption=title,
|
|
html_caption=title,
|
|
html_class="table_leftalign",
|
|
base_url="%s?formation_id=%s" % (REQUEST.URL0, formation_id),
|
|
page_title=title,
|
|
html_title="<h2>" + title + "</h2>",
|
|
pdf_title=title,
|
|
preferences=sco_preferences.SemPreferences(),
|
|
)
|
|
return tab.make_page(context, format=format, REQUEST=REQUEST)
|
|
|
|
|
|
def ue_list_semestre_ids(context, ue):
|
|
"""Liste triée des numeros de semestres des modules dans cette UE
|
|
Il est recommandable que tous les modules d'une UE aient le même indice de semestre.
|
|
Mais cela n'a pas toujours été le cas dans les programmes pédagogiques officiels,
|
|
aussi ScoDoc laisse le choix.
|
|
"""
|
|
Modlist = sco_edit_module.do_module_list(context, args={"ue_id": ue["ue_id"]})
|
|
return sorted(list(set([mod["semestre_id"] for mod in Modlist])))
|