MonScoDocEssai/app/scodoc/sco_edit_ue.py

1490 lines
51 KiB
Python
Raw Normal View History

2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2022-01-01 14:49:42 +01:00
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
2020-09-26 16:19:37 +02:00
#
# 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
#
##############################################################################
2021-06-16 18:18:32 +02:00
"""Ajout/Modification/Suppression UE
2020-09-26 16:19:37 +02:00
"""
2021-07-31 18:01:10 +02:00
import flask
2022-02-27 10:19:25 +01:00
from flask import flash, render_template, url_for
2021-11-18 00:24:56 +01:00
from flask import g, request
2021-07-31 18:01:10 +02:00
from flask_login import current_user
2022-01-27 00:18:50 +01:00
from app import db
from app import log
from app.models import APO_CODE_STR_LEN
from app.models import Formation, UniteEns, ModuleImpl, Module
2022-01-27 00:18:50 +01:00
from app.models.formations import Matiere
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
2022-01-27 00:18:50 +01:00
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_permissions import Permission
2022-01-04 20:03:38 +01:00
from app.scodoc.sco_exceptions import (
ScoGenError,
ScoValueError,
ScoLockedFormError,
ScoNonEmptyFormationObject,
)
from app.scodoc import html_sco_header
2021-07-19 19:53:01 +02:00
from app.scodoc import sco_cache
from app.scodoc import sco_codes_parcours
2021-11-17 10:28:51 +01:00
from app.scodoc import sco_edit_apc
2021-07-19 19:53:01 +02:00
from app.scodoc import sco_edit_formation
from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module
2021-07-19 19:53:01 +02:00
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
2020-09-26 16:19:37 +02:00
2021-06-16 18:18:32 +02:00
_ueEditor = ndb.EditableTable(
"notes_ue",
"ue_id",
(
"ue_id",
"formation_id",
"acronyme",
"numero",
"titre",
2021-11-14 18:09:20 +01:00
"semestre_idx",
2021-06-16 18:18:32 +02:00
"type",
"ue_code",
"ects",
"is_external",
"code_apogee",
"coefficient",
2022-01-25 10:45:13 +01:00
"color",
2021-06-16 18:18:32 +02:00
),
sortkey="numero",
2021-08-11 00:36:07 +02:00
input_formators={
"type": ndb.int_null_is_zero,
"is_external": ndb.bool_or_str,
"ects": ndb.float_null_is_null,
2021-08-11 00:36:07 +02:00
},
2021-06-16 18:18:32 +02:00
output_formators={
"numero": ndb.int_null_is_zero,
"ects": ndb.float_null_is_null,
"coefficient": ndb.float_null_is_zero,
2021-11-14 18:09:20 +01:00
"semestre_idx": ndb.int_null_is_null,
2021-06-16 18:18:32 +02:00
},
)
2021-10-17 23:19:26 +02:00
def ue_list(*args, **kw):
2021-06-16 18:18:32 +02:00
"list UEs"
cnx = ndb.GetDBConnexion()
return _ueEditor.list(cnx, *args, **kw)
2021-08-20 01:09:55 +02:00
def do_ue_create(args):
2021-06-16 18:18:32 +02:00
"create an ue"
cnx = ndb.GetDBConnexion()
# check duplicates
2021-10-17 23:19:26 +02:00
ues = ue_list({"formation_id": args["formation_id"], "acronyme": args["acronyme"]})
2021-06-16 18:18:32 +02:00
if ues:
raise ScoValueError(
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
(chaque UE doit avoir un acronyme unique dans la formation)"""
)
2022-02-27 10:19:25 +01:00
if not "ue_code" in args:
# évite les conflits de code
while True:
cursor = db.session.execute("select notes_newid_ucod();")
code = cursor.fetchone()[0]
if UniteEns.query.filter_by(ue_code=code).count() == 0:
break
args["ue_code"] = code
2021-06-16 18:18:32 +02:00
# create
ue_id = _ueEditor.create(cnx, args)
2021-06-16 18:18:32 +02:00
# Invalidate cache: vire les poids de toutes les évals de la formation
for modimpl in ModuleImpl.query.filter(
ModuleImpl.module_id == Module.id, Module.formation_id == args["formation_id"]
):
modimpl.invalidate_evaluations_poids()
formation = Formation.query.get(args["formation_id"])
formation.invalidate_module_coefs()
2021-06-16 18:18:32 +02:00
# news
2022-02-27 10:19:25 +01:00
ue = UniteEns.query.get(ue_id)
flash(f"UE créée (code {ue.ue_code})")
formation = Formation.query.get(args["formation_id"])
2021-06-16 18:18:32 +02:00
sco_news.add(
typ=sco_news.NEWS_FORM,
2021-06-16 18:18:32 +02:00
object=args["formation_id"],
text="Modification de la formation {formation.acronyme}",
2021-08-01 10:16:16 +02:00
max_frequency=3,
2021-06-16 18:18:32 +02:00
)
formation.invalidate_cached_sems()
return ue_id
2021-06-16 18:18:32 +02:00
2022-01-04 20:03:38 +01:00
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)
2021-08-30 23:28:15 +02:00
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
2022-01-04 20:03:38 +01:00
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,
),
)
2021-06-16 18:18:32 +02:00
cnx = ndb.GetDBConnexion()
2022-01-04 20:03:38 +01:00
log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue.id, delete_validations))
2021-06-16 18:18:32 +02:00
# check
2022-01-04 20:03:38 +01:00
# if ue_is_locked(ue.id):
# raise ScoLockedFormError()
2021-06-16 18:18:32 +02:00
# 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(
2022-01-04 20:03:38 +01:00
cnx, args={"ue_id": ue.id}
2021-06-16 18:18:32 +02:00
)
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>"
2022-01-04 20:03:38 +01:00
% (len(validations), ue.acronyme, ue.titre),
2021-06-16 18:18:32 +02:00
dest_url="",
target_variable="delete_validations",
cancel_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
2022-01-04 20:03:38 +01:00
formation_id=ue.formation_id,
semestre_idx=ue.semestre_idx,
),
2022-01-04 20:03:38 +01:00
parameters={"ue_id": ue.id, "dialog_confirmed": 1},
2021-06-16 18:18:32 +02:00
)
if delete_validations:
2022-01-04 20:03:38 +01:00
log("deleting all validations of UE %s" % ue.id)
2021-06-16 18:18:32 +02:00
ndb.SimpleQuery(
"DELETE FROM scolar_formsemestre_validation WHERE ue_id=%(ue_id)s",
2022-01-04 20:03:38 +01:00
{"ue_id": ue.id},
2021-06-16 18:18:32 +02:00
)
# delete all matiere in this UE
2022-01-04 20:03:38 +01:00
mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})
2021-06-16 18:18:32 +02:00
for mat in mats:
2021-08-20 01:09:55 +02:00
sco_edit_matiere.do_matiere_delete(mat["matiere_id"])
2021-06-16 18:18:32 +02:00
# delete uecoef and events
ndb.SimpleQuery(
"DELETE FROM notes_formsemestre_uecoef WHERE ue_id=%(ue_id)s",
2022-01-04 20:03:38 +01:00
{"ue_id": ue.id},
2021-06-16 18:18:32 +02:00
)
2022-01-04 20:03:38 +01:00
ndb.SimpleQuery("DELETE FROM scolar_events WHERE ue_id=%(ue_id)s", {"ue_id": ue.id})
2021-06-16 18:18:32 +02:00
cnx = ndb.GetDBConnexion()
2022-01-04 20:03:38 +01:00
_ueEditor.delete(cnx, ue.id)
# > UE delete + supr. validations associées etudiants (cas compliqué, mais rarement
# utilisé: acceptable de tout invalider):
2021-07-19 19:53:01 +02:00
sco_cache.invalidate_formsemestre()
2021-06-16 18:18:32 +02:00
# news
2022-01-04 20:03:38 +01:00
F = sco_formations.formation_list(args={"formation_id": ue.formation_id})[0]
2021-06-16 18:18:32 +02:00
sco_news.add(
typ=sco_news.NEWS_FORM,
2022-01-04 20:03:38 +01:00
object=ue.formation_id,
2021-06-16 18:18:32 +02:00
text="Modification de la formation %(acronyme)s" % F,
2021-08-01 10:16:16 +02:00
max_frequency=3,
2021-06-16 18:18:32 +02:00
)
#
if not force:
2021-07-31 18:01:10 +02:00
return flask.redirect(
url_for(
2021-10-17 23:19:26 +02:00
"notes.ue_table",
2021-07-31 18:01:10 +02:00
scodoc_dept=g.scodoc_dept,
2022-01-04 20:03:38 +01:00
formation_id=ue.formation_id,
semestre_idx=ue.semestre_idx,
2021-07-31 18:01:10 +02:00
)
2021-06-16 18:18:32 +02:00
)
2022-01-04 20:03:38 +01:00
return None
2021-06-16 18:18:32 +02:00
2020-09-26 16:19:37 +02:00
2022-02-02 10:23:40 +01:00
def ue_create(formation_id=None, default_semestre_idx=None):
"""Formulaire création d'une UE"""
return ue_edit(
create=True,
formation_id=formation_id,
default_semestre_idx=default_semestre_idx,
)
2020-09-26 16:19:37 +02:00
2022-02-02 10:23:40 +01:00
def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=None):
"""Formulaire modification ou création d'une UE"""
2020-09-26 16:19:37 +02:00
create = int(create)
if not create:
ue: UniteEns = UniteEns.query.get_or_404(ue_id)
ue_dict = ue.to_dict()
formation_id = ue.formation_id
title = f"Modification de l'UE {ue.acronyme} {ue.titre}"
initvalues = ue_dict
2020-09-26 16:19:37 +02:00
submitlabel = "Modifier les valeurs"
2022-02-11 09:43:53 +01:00
can_change_semestre_id = (ue.modules.count() == 0) or (ue.semestre_idx is None)
2020-09-26 16:19:37 +02:00
else:
ue = None
2020-09-26 16:19:37 +02:00
title = "Création d'une UE"
2022-02-02 22:04:18 +01:00
initvalues = {
"semestre_idx": default_semestre_idx,
"color": ue_guess_color_default(formation_id, default_semestre_idx),
}
2020-09-26 16:19:37 +02:00
submitlabel = "Créer cette UE"
can_change_semestre_id = True
2021-11-14 18:09:20 +01:00
formation = Formation.query.get(formation_id)
if not formation:
raise ScoValueError(f"Formation inexistante ! (id={formation_id})")
parcours = formation.get_parcours()
is_apc = parcours.APC_SAE
semestres_indices = list(range(1, parcours.NB_SEM + 1))
2020-09-26 16:19:37 +02:00
H = [
html_sco_header.sco_header(page_title=title, javascripts=["js/edit_ue.js"]),
2020-09-26 16:19:37 +02:00
"<h2>" + title,
2021-11-14 18:09:20 +01:00
f" (formation {formation.acronyme}, version {formation.version})</h2>",
2020-09-26 16:19:37 +02:00
"""
2021-11-14 18:09:20 +01:00
<p class="help">Les UE sont des groupes de modules dans une formation donnée,
utilisés pour la validation (on calcule des moyennes par UE et applique des
seuils ("barres")).
</p>
<p class="help">Note: sauf exception, l'UE n'a pas de coefficient associé.
Seuls les <em>modules</em> ont des coefficients.
</p>""",
2020-09-26 16:19:37 +02:00
]
ue_types = parcours.ALLOWED_UE_TYPES
ue_types.sort()
2021-02-03 22:00:41 +01:00
ue_types_names = [sco_codes_parcours.UE_TYPE_NAME[k] for k in ue_types]
2020-09-26 16:19:37 +02:00
ue_types = [str(x) for x in ue_types]
form_descr = [
2020-09-26 16:19:37 +02:00
("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",
},
),
]
if can_change_semestre_id:
form_descr += [
(
"semestre_idx",
{
"input_type": "menu",
"type": "int",
"allow_null": False,
"title": parcours.SESSION_NAME.capitalize(),
"explanation": "%s de l'UE dans la formation"
% parcours.SESSION_NAME,
"labels": ["non spécifié"] + [str(x) for x in semestres_indices],
"allowed_values": [""] + semestres_indices,
},
),
]
else:
form_descr += [
("semestre_idx", {"default": ue.semestre_idx, "input_type": "hidden"}),
]
form_descr += [
2020-09-26 16:19:37 +02:00
(
"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",
"allow_null": not is_apc, # ects requis en APC
2020-09-26 16:19:37 +02:00
},
),
(
"coefficient",
{
"size": 4,
"type": "float",
"title": "Coefficient",
2021-11-14 18:09:20 +01:00
"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.
Jamais utilisé en BUT.
2021-11-14 18:09:20 +01:00
""",
"enabled": not is_apc,
2020-09-26 16:19:37 +02:00
},
),
(
"ue_code",
{
"size": 12,
"title": "Code UE",
"explanation": "code interne (non vide). 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.",
2020-09-26 16:19:37 +02:00
},
),
(
"code_apogee",
{
"title": "Code Apogée",
2021-06-24 23:09:06 +02:00
"size": 25,
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
2020-09-26 16:19:37 +02:00
},
),
(
"is_external",
{
"input_type": "boolcheckbox",
"title": "UE externe",
"explanation": "réservé pour les capitalisations d'UE effectuées à l'extérieur de l'établissement",
},
),
2022-01-25 10:45:13 +01:00
(
"color",
{
"input_type": "color",
"title": "Couleur",
"explanation": "pour affichages",
},
),
2020-09-26 16:19:37 +02:00
]
2021-11-18 00:24:56 +01:00
if create and not parcours.UE_IS_MODULE and not is_apc:
form_descr.append(
2020-09-26 16:19:37 +02:00
(
"create_matiere",
{
"input_type": "boolcheckbox",
2021-11-14 18:09:20 +01:00
"default": True,
2020-09-26 16:19:37 +02:00
"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.base_url,
scu.get_request_args(),
form_descr,
initvalues=initvalues,
submitlabel=submitlabel,
2020-09-26 16:19:37 +02:00
)
if tf[0] == 0:
2022-02-11 09:43:53 +01:00
if ue and ue.modules.count() and ue.semestre_idx is not None:
modules_div = f"""<div id="ue_list_modules">
<div><b>{ue.modules.count()} modules sont rattachés
à cette UE</b> du semestre S{ue.semestre_idx},
elle ne peut donc pas être changée de semestre.</div>
<ul>"""
for m in ue.modules:
modules_div += f"""<li><a class="stdlink" href="{url_for(
"notes.module_edit",scodoc_dept=g.scodoc_dept, module_id=m.id)}">{m.code} {m.titre}</a></li>"""
modules_div += """</ul></div>"""
else:
modules_div = ""
2022-01-27 18:12:40 +01:00
bonus_div = """<div id="bonus_description"></div>"""
ue_div = """<div id="ue_list_code"></div>"""
return (
"\n".join(H)
+ tf[1]
+ modules_div
+ bonus_div
+ ue_div
+ html_sco_header.sco_footer()
)
2020-09-26 16:19:37 +02:00
else:
if create:
if not tf[2]["ue_code"]:
del tf[2]["ue_code"]
if not tf[2]["numero"]:
# numero regroupant par semestre ou année:
tf[2]["numero"] = next_ue_numero(
2021-11-14 18:09:20 +01:00
formation_id, int(tf[2]["semestre_idx"])
2020-09-26 16:19:37 +02:00
)
2021-08-20 01:09:55 +02:00
ue_id = do_ue_create(tf[2])
2021-11-18 00:24:56 +01:00
if is_apc or parcours.UE_IS_MODULE or tf[2]["create_matiere"]:
# rappel: en APC, toutes les UE ont une matière, créée ici
# (inutilisée mais à laquelle les modules sont rattachés)
2021-06-17 00:08:37 +02:00
matiere_id = sco_edit_matiere.do_matiere_create(
{"ue_id": ue_id, "titre": tf[2]["titre"], "numero": 1},
2020-09-26 16:19:37 +02:00
)
if parcours.UE_IS_MODULE:
# dans ce mode, crée un (unique) module dans l'UE:
2021-06-17 00:08:37 +02:00
_ = sco_edit_module.do_module_create(
2020-09-26 16:19:37 +02:00
{
"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,
2021-11-14 18:09:20 +01:00
"semestre_id": tf[2]["semestre_idx"],
2020-09-26 16:19:37 +02:00
},
)
flash("UE créée")
2020-09-26 16:19:37 +02:00
else:
2021-08-20 01:09:55 +02:00
do_ue_edit(tf[2])
flash("UE modifiée")
2021-07-31 18:01:10 +02:00
return flask.redirect(
url_for(
2021-12-12 23:03:36 +01:00
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=formation_id,
semestre_idx=tf[2]["semestre_idx"],
2021-07-31 18:01:10 +02:00
)
2020-09-26 16:19:37 +02:00
)
2021-11-17 10:28:51 +01:00
def _add_ue_semestre_id(ues: list[dict], is_apc):
2021-11-14 18:09:20 +01:00
"""ajoute semestre_id dans les ue, en regardant
2021-11-17 10:28:51 +01:00
semestre_idx ou à défaut, pour les formations non APC, le premier module
de chacune.
Les UE sans modules se voient attribuer le numero UE_SEM_DEFAULT (1000000),
2020-09-26 16:19:37 +02:00
qui les place à la fin de la liste.
"""
2021-10-21 06:32:03 +02:00
for ue in ues:
2021-11-14 18:09:20 +01:00
if ue["semestre_idx"] is not None:
ue["semestre_id"] = ue["semestre_idx"]
2021-11-17 10:28:51 +01:00
elif is_apc:
ue["semestre_id"] = sco_codes_parcours.UE_SEM_DEFAULT
2020-09-26 16:19:37 +02:00
else:
2021-11-17 10:28:51 +01:00
# était le comportement ScoDoc7
2021-11-14 18:09:20 +01:00
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
if modules:
ue["semestre_id"] = modules[0]["semestre_id"]
else:
2021-11-17 10:28:51 +01:00
ue["semestre_id"] = sco_codes_parcours.UE_SEM_DEFAULT
2020-09-26 16:19:37 +02:00
2021-08-20 01:09:55 +02:00
def next_ue_numero(formation_id, semestre_id=None):
2020-09-26 16:19:37 +02:00
"""Numero d'une nouvelle UE dans cette formation.
Si le semestre est specifie, cherche les UE ayant des modules de ce semestre
"""
2021-11-17 10:28:51 +01:00
formation = Formation.query.get(formation_id)
2021-10-21 06:32:03 +02:00
ues = ue_list(args={"formation_id": formation_id})
if not ues:
2020-09-26 16:19:37 +02:00
return 0
if semestre_id is None:
2021-10-21 06:32:03 +02:00
return ues[-1]["numero"] + 1000
2020-09-26 16:19:37 +02:00
else:
# Avec semestre: (prend le semestre du 1er module de l'UE)
2021-11-17 10:28:51 +01:00
_add_ue_semestre_id(ues, formation.get_parcours().APC_SAE)
2021-10-21 06:32:03 +02:00
ue_list_semestre = [ue for ue in ues if ue["semestre_id"] == semestre_id]
2020-09-26 16:19:37 +02:00
if ue_list_semestre:
return ue_list_semestre[-1]["numero"] + 10
else:
2021-10-21 06:32:03 +02:00
return ues[-1]["numero"] + 1000
2020-09-26 16:19:37 +02:00
2021-08-31 08:21:30 +02:00
def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
2020-09-26 16:19:37 +02:00
"""Delete an UE"""
2022-01-04 20:03:38 +01:00
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.""",
dest_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=ue.formation.id,
semestre_idx=ue.semestre_idx,
),
2022-01-04 20:03:38 +01:00
)
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,
),
)
2020-09-26 16:19:37 +02:00
if not dialog_confirmed:
return scu.confirm_dialog(
2022-01-04 20:03:38 +01:00
f"<h2>Suppression de l'UE {ue.titre} ({ue.acronyme})</h2>",
2020-09-26 16:19:37 +02:00
dest_url="",
2022-01-04 20:03:38 +01:00
parameters={"ue_id": ue.id},
cancel_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
2022-01-04 20:03:38 +01:00
formation_id=ue.formation_id,
semestre_idx=ue.semestre_idx,
),
2020-09-26 16:19:37 +02:00
)
2022-01-04 20:03:38 +01:00
return do_ue_delete(ue.id, delete_validations=delete_validations)
2020-09-26 16:19:37 +02:00
2021-11-18 00:24:56 +01:00
def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
2021-01-01 18:40:47 +01:00
"""Liste des matières et modules d'une formation, avec liens pour
2021-10-17 23:19:26 +02:00
éditer (si non verrouillée).
2020-09-26 16:19:37 +02:00
"""
from app.scodoc import sco_formsemestre_validation
formation: Formation = Formation.query.get(formation_id)
2021-11-17 10:28:51 +01:00
if not formation:
2020-09-26 16:19:37 +02:00
raise ScoValueError("invalid formation_id")
2021-11-17 10:28:51 +01:00
parcours = formation.get_parcours()
2021-11-13 12:09:30 +01:00
is_apc = parcours.APC_SAE
locked = formation.has_locked_sems()
2021-11-18 00:24:56 +01:00
if semestre_idx == "all":
semestre_idx = None
else:
semestre_idx = int(semestre_idx)
semestre_ids = range(1, parcours.NB_SEM + 1)
# transition: on requete ici via l'ORM mais on utilise les fonctions ScoDoc7
# basées sur des dicts
2022-02-10 23:14:33 +01:00
ues_obj = UniteEns.query.filter_by(
formation_id=formation_id, is_external=False
).order_by(UniteEns.semestre_idx, UniteEns.numero)
ues_externes_obj = UniteEns.query.filter_by(
formation_id=formation_id, is_external=True
)
if is_apc:
2022-02-02 22:04:18 +01:00
# Pour faciliter la transition des anciens programmes non APC
for ue in ues_obj:
ue.guess_semestre_idx()
2022-02-02 22:04:18 +01:00
# vérifie qu'on a bien au moins une matière dans chaque UE
2022-01-27 00:18:50 +01:00
if ue.matieres.count() < 1:
mat = Matiere(ue_id=ue.id)
db.session.add(mat)
2022-02-02 22:04:18 +01:00
# donne des couleurs aux UEs crées avant
colorie_anciennes_ues(ues_obj)
2022-01-27 00:18:50 +01:00
db.session.commit()
ues = [ue.to_dict() for ue in ues_obj]
ues_externes = [ue.to_dict() for ue in ues_externes_obj]
2020-09-26 16:19:37 +02:00
# tri par semestre et numero:
2021-11-17 10:28:51 +01:00
_add_ue_semestre_id(ues, is_apc)
_add_ue_semestre_id(ues_externes, is_apc)
2021-10-21 06:32:03 +02:00
ues.sort(key=lambda u: (u["semestre_id"], u["numero"]))
ues_externes.sort(key=lambda u: (u["semestre_id"], u["numero"]))
# Codes dupliqués (pour aider l'utilisateur)
seen = set()
duplicated_codes = {
ue["ue_code"] for ue in ues if ue["ue_code"] in seen or seen.add(ue["ue_code"])
}
ues_with_duplicated_code = [ue for ue in ues if ue["ue_code"] in duplicated_codes]
2020-09-26 16:19:37 +02:00
has_perm_change = current_user.has_permission(Permission.ScoChangeFormation)
# editable = (not locked) and has_perm_change
2021-11-17 10:28:51 +01:00
# On autorise maintenant la modification des formations qui ont
# des semestres verrouillés, sauf si cela affect les notes passées
# (verrouillées):
2020-09-26 16:19:37 +02:00
# - 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 = has_perm_change
2021-06-12 22:43:22 +02:00
tag_editable = (
current_user.has_permission(Permission.ScoEditFormationTags) or has_perm_change
2021-06-12 22:43:22 +02:00
)
2020-09-26 16:19:37 +02:00
if locked:
2021-02-04 20:02:44 +01:00
lockicon = scu.icontag("lock32_img", title="verrouillé")
2020-09-26 16:19:37 +02:00
else:
lockicon = ""
2021-10-12 16:05:50 +02:00
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
2021-02-04 20:02:44 +01:00
delete_icon = scu.icontag(
2020-09-26 16:19:37 +02:00
"delete_small_img", title="Supprimer (module inutilisé)", alt="supprimer"
)
2021-02-04 20:02:44 +01:00
delete_disabled_icon = scu.icontag(
2020-09-26 16:19:37 +02:00
"delete_small_dis_img", title="Suppression impossible (module utilisé)"
)
H = [
2021-06-13 19:12:20 +02:00
html_sco_header.sco_header(
2020-09-26 16:19:37 +02:00
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",
],
2021-11-17 10:28:51 +01:00
page_title=f"Programme {formation.acronyme}",
2020-09-26 16:19:37 +02:00
),
2021-11-17 10:28:51 +01:00
f"""<h2>Formation {formation.titre} ({formation.acronyme})
[version {formation.version}] code {formation.formation_code}
{lockicon}
</h2>
""",
2020-09-26 16:19:37 +02:00
]
if locked:
H.append(
2021-08-30 23:28:15 +02:00
f"""<p class="help">Cette formation est verrouillée car
des semestres verrouillés s'y réferent.
2021-08-30 23:28:15 +02:00
Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module),
vous devez:
2020-09-26 16:19:37 +02:00
</p>
<ul class="help">
2021-08-30 23:28:15 +02:00
<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>
2020-09-26 16:19:37 +02:00
</ul>"""
)
if msg:
H.append('<p class="msg">' + msg + "</p>")
if ues_with_duplicated_code:
2020-09-26 16:19:37 +02:00
H.append(
f"""<div class="ue_warning"><span>Attention: plusieurs UE de cette
formation ont le même code : <tt>{
', '.join([
'<a class="stdlink" href="' + url_for( "notes.ue_edit", scodoc_dept=g.scodoc_dept, ue_id=ue["ue_id"] )
+ '">' + ue["acronyme"] + " (code " + ue["ue_code"] + ")</a>"
for ue in ues_with_duplicated_code ])
}</tt>.
Il faut corriger cela, sinon les capitalisations et ECTS seront
erronés !</span></div>"""
2020-09-26 16:19:37 +02:00
)
# Description de la formation
H.append(
2021-11-18 00:24:56 +01:00
render_template(
"pn/form_descr.html",
formation=formation,
parcours=parcours,
editable=editable,
2020-09-26 16:19:37 +02:00
)
2021-11-18 00:24:56 +01:00
)
2021-11-17 10:28:51 +01:00
2021-11-13 12:09:30 +01:00
# Formation APC (BUT) ?
if is_apc:
H.append(
f"""<div class="formation_apc_infos">
2021-11-18 00:24:56 +01:00
<div class="ue_list_tit">Formation par compétences (BUT)
- Semestre {_html_select_semestre_idx(formation_id, semestre_ids, semestre_idx)}
</form>
</div>
"""
)
2021-12-03 11:03:33 +01:00
if formation.referentiel_competence is None:
descr_refcomp = ""
msg_refcomp = "associer à un référentiel de compétences"
else:
2022-01-08 14:35:02 +01:00
descr_refcomp = f"""Référentiel de compétences:
<a href="{url_for('notes.refcomp_show',
scodoc_dept=g.scodoc_dept, refcomp_id=formation.referentiel_competence.id)}">
{formation.referentiel_competence.type_titre} {formation.referentiel_competence.specialite_long}
</a> """
2021-12-03 11:03:33 +01:00
msg_refcomp = "changer"
2021-11-18 00:24:56 +01:00
H.append(
f"""
2021-11-13 12:09:30 +01:00
<ul>
2022-01-08 14:35:02 +01:00
<li>{descr_refcomp}&nbsp; <a class="stdlink" href="{url_for('notes.refcomp_assoc_formation',
2021-12-03 11:03:33 +01:00
scodoc_dept=g.scodoc_dept, formation_id=formation_id)
}">{msg_refcomp}</a>
</li>
2021-11-13 12:09:30 +01:00
<li><a class="stdlink" href="{
2021-11-18 00:24:56 +01:00
url_for('notes.edit_modules_ue_coefs', scodoc_dept=g.scodoc_dept, formation_id=formation_id, semestre_idx=semestre_idx)
2021-12-03 11:03:33 +01:00
}">éditer les coefficients des ressources et SAÉs</a>
</li>
2021-11-13 12:09:30 +01:00
</ul>
2021-11-18 00:24:56 +01:00
"""
2021-11-13 12:09:30 +01:00
)
2020-09-26 16:19:37 +02:00
# Description des UE/matières/modules
H.append(
2021-11-17 10:28:51 +01:00
"""
<div class="formation_ue_list">
<div class="ue_list_tit">Programme pédagogique:</div>
<form>
<input type="checkbox" class="sco_tag_checkbox">montrer les tags</input>
</form>
"""
)
2021-11-17 10:28:51 +01:00
if is_apc:
2020-09-26 16:19:37 +02:00
H.append(
2021-11-17 10:28:51 +01:00
sco_edit_apc.html_edit_formation_apc(
2021-11-18 00:24:56 +01:00
formation,
semestre_idx=semestre_idx,
editable=editable,
tag_editable=tag_editable,
2021-11-17 10:28:51 +01:00
)
2020-09-26 16:19:37 +02:00
)
2021-11-17 10:28:51 +01:00
else:
2022-02-27 17:49:39 +01:00
H.append('<div class="formation_classic_infos">')
2020-09-26 16:19:37 +02:00
H.append(
2021-11-17 10:28:51 +01:00
_ue_table_ues(
parcours,
ues,
editable,
tag_editable,
has_perm_change,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
)
2020-09-26 16:19:37 +02:00
)
2021-11-17 10:28:51 +01:00
if editable:
H.append(
f"""<ul>
<li><a class="stdlink" href="{
url_for('notes.ue_create', scodoc_dept=g.scodoc_dept, formation_id=formation_id)
}">Ajouter une UE</a>
</li>
<li><a href="{
url_for('notes.formation_add_malus_modules',
scodoc_dept=g.scodoc_dept, formation_id=formation_id)
}" class="stdlink">Ajouter des modules de malus dans chaque UE</a>
</li>
</ul>
"""
)
2022-02-27 17:49:39 +01:00
H.append("</div>")
2020-09-26 16:19:37 +02:00
H.append("</div>") # formation_ue_list
if ues_externes:
H.append(
2021-11-17 10:28:51 +01:00
f"""
<div class="formation_ue_list formation_ue_list_externes">
<div class="ue_list_tit">UE externes déclarées (pour information):
</div>
{_ue_table_ues(
parcours,
ues_externes,
editable,
tag_editable,
has_perm_change,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
2021-11-17 10:28:51 +01:00
)}
</div>
"""
)
2020-09-26 16:19:37 +02:00
H.append("<p><ul>")
if editable:
H.append(
2021-11-17 10:28:51 +01:00
f"""
<li><a class="stdlink" href="{
url_for('notes.formation_create_new_version',
scodoc_dept=g.scodoc_dept, formation_id=formation_id
)
}">Créer une nouvelle version (non verrouillée)</a>
</li>
"""
2020-09-26 16:19:37 +02:00
)
H.append(
2021-11-17 10:28:51 +01:00
f"""
<li><a class="stdlink" href="{
url_for('notes.formation_table_recap', scodoc_dept=g.scodoc_dept,
formation_id=formation_id)
}">Table récapitulative de la formation</a>
</li>
<li><a class="stdlink" href="{
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
formation_id=formation_id, format='xml')
}">Export XML de la formation</a>
(permet de la sauvegarder pour l'échanger avec un autre site)
</li>
2020-09-26 16:19:37 +02:00
2021-11-17 10:28:51 +01:00
<li><a class="stdlink" href="{
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
formation_id=formation_id, format='json')
}">Export JSON de la formation</a>
</li>
2020-09-26 16:19:37 +02:00
2021-11-17 10:28:51 +01:00
<li><a class="stdlink" href="{
url_for('notes.module_table', scodoc_dept=g.scodoc_dept,
formation_id=formation_id)
}">Liste détaillée des modules de la formation</a> (debug)
</li>
</ul>
</p>"""
2020-09-26 16:19:37 +02:00
)
if has_perm_change:
2020-09-26 16:19:37 +02:00
H.append(
"""
<h3> <a name="sems">Semestres ou sessions de cette formation</a></h3>
<p><ul>"""
)
for sem in sco_formsemestre.do_formsemestre_list(
2021-08-19 10:28:35 +02:00
args={"formation_id": formation_id}
2020-09-26 16:19:37 +02:00
):
H.append(
'<li><a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titremois)s</a>'
% sem
)
2021-08-10 12:57:38 +02:00
if not sem["etat"]:
2020-09-26 16:19:37 +02:00
H.append(" [verrouillé]")
else:
H.append(
2021-05-11 11:48:32 +02:00
' <a class="stdlink" href="formsemestre_editwithmodules?formation_id=%(formation_id)s&formsemestre_id=%(formsemestre_id)s">Modifier</a>'
2020-09-26 16:19:37 +02:00
% sem
)
H.append("</li>")
H.append("</ul>")
2021-07-31 18:01:10 +02:00
if current_user.has_permission(Permission.ScoImplement):
2020-09-26 16:19:37 +02:00
H.append(
2021-11-17 10:28:51 +01:00
f"""<ul>
<li><a class="stdlink" href="{
url_for('notes.formsemestre_createwithmodules', scodoc_dept=g.scodoc_dept,
formation_id=formation_id, semestre_id=1)
2021-11-18 00:24:56 +01:00
}">Mettre en place un nouveau semestre de formation {formation.acronyme}</a>
2021-11-17 10:28:51 +01:00
</li>
</ul>"""
2020-09-26 16:19:37 +02:00
)
# <li>(debug) <a class="stdlink" href="check_form_integrity?formation_id=%(formation_id)s">Vérifier cohérence</a></li>
2021-08-20 01:09:55 +02:00
warn, _ = sco_formsemestre_validation.check_formation_ues(formation_id)
2020-09-26 16:19:37 +02:00
H.append(warn)
H.append(html_sco_header.sco_footer())
2020-09-26 16:19:37 +02:00
return "".join(H)
2021-11-18 00:24:56 +01:00
def _html_select_semestre_idx(formation_id, semestre_ids, semestre_idx):
htm = """<form method="get">Semestre:
<select onchange="this.form.submit()" name="semestre_idx" id="semestre_idx" >
"""
for i in list(semestre_ids) + ["all"]:
if i == "all":
label = "tous"
else:
label = f"S{i}"
htm += f"""<option value="{i}" {
'selected'
if (semestre_idx == i)
or (i == "all" and semestre_idx is None)
else ''
}>{label}</option>
"""
htm += f"""
</select>
<input type="hidden" name="formation_id" value="{formation_id}"></input>
</form>"""
return htm
def _ue_table_ues(
parcours,
ues,
editable,
tag_editable,
has_perm_change,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
):
2021-11-17 10:28:51 +01:00
"""Édition de programme: liste des UEs (avec leurs matières et modules).
Pour les formations classiques (non APC/BUT)
"""
H = []
cur_ue_semestre_id = None
iue = 0
for ue in ues:
if ue["ects"] is None:
ue["ects_str"] = ""
else:
ue["ects_str"] = ", %g ECTS" % ue["ects"]
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)
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(
2021-11-18 00:24:56 +01:00
_ue_table_matieres(
parcours,
ue,
editable,
tag_editable,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
)
)
if (iue >= len(ues) - 1) or ue["semestre_id"] != ues[iue + 1]["semestre_id"]:
H.append(
f"""</ul><ul><li><a href="{url_for('notes.ue_create', scodoc_dept=g.scodoc_dept,
formation_id=ue['formation_id'], semestre_idx=ue['semestre_id'])
}">Ajouter une UE dans le semestre {ue['semestre_id'] or ''}</a></li></ul>"""
)
iue += 1
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,
2022-02-01 11:37:05 +01:00
ue,
mat,
modules,
editable,
tag_editable,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
)
)
if not parcours.UE_IS_MODULE:
H.append("</li>")
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,
2022-02-01 11:37:05 +01:00
ue,
mat,
modules,
editable,
tag_editable,
arrow_up,
arrow_down,
arrow_none,
delete_icon,
delete_disabled_icon,
unit_name="matière",
add_suppress_link=True, # lien "supprimer cette matière"
empty_list_msg="Aucun élément dans cette matière",
create_element_msg="créer un module",
):
"""É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"] == ModuleType.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:
2022-01-04 20:03:38 +01:00
icon = delete_icon
else:
2022-01-04 20:03:38 +01:00
icon = delete_disabled_icon
H.append(
'<a class="smallbutton" href="module_delete?module_id=%s">%s</a>'
% (mod["module_id"], 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"])),
)
2022-02-01 11:37:05 +01:00
if ue["semestre_idx"] is not None and mod["semestre_id"] != ue["semestre_idx"]:
warning_semestre = ' <span class="red">incohérent ?</span>'
else:
warning_semestre = ""
H.append(
2022-02-01 11:37:05 +01:00
" %s %s%s" % (parcours.SESSION_NAME, mod["semestre_id"], warning_semestre)
+ " (%s)" % heurescoef
+ tag_edit
)
H.append("</li>")
if not modules:
H.append(f"<li>{empty_list_msg} ! ")
if editable and add_suppress_link:
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"])}"
>{create_element_msg}</a></li>
"""
)
H.append("</ul>")
return "\n".join(H)
2021-08-20 01:09:55 +02:00
def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
2020-09-26 16:19:37 +02:00
"""HTML list of UE sharing this code
Either ue_code or ue_id may be specified.
2021-08-09 07:43:41 +02:00
hide_ue_id spécifie un id à retirer de la liste.
2020-09-26 16:19:37 +02:00
"""
from app.scodoc import sco_formations
ue_code = str(ue_code)
2020-09-26 16:19:37 +02:00
if ue_id:
2021-10-17 23:19:26 +02:00
ue = ue_list(args={"ue_id": ue_id})[0]
2020-09-26 16:19:37 +02:00
if not ue_code:
ue_code = ue["ue_code"]
2021-08-19 10:28:35 +02:00
F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
2020-09-26 16:19:37 +02:00
formation_code = F["formation_code"]
2021-09-27 17:18:43 +02:00
# UE du même code, code formation et departement:
q_ues = (
UniteEns.query.filter_by(ue_code=ue_code)
.join(UniteEns.formation, aliased=True)
2021-09-27 17:18:43 +02:00
.filter_by(dept_id=g.scodoc_dept_id, formation_code=formation_code)
)
2020-09-26 16:19:37 +02:00
else:
2021-09-27 17:18:43 +02:00
# Toutes les UE du departement avec ce code:
q_ues = (
UniteEns.query.filter_by(ue_code=ue_code)
.join(UniteEns.formation, aliased=True)
2021-09-27 17:18:43 +02:00
.filter_by(dept_id=g.scodoc_dept_id)
)
2020-09-26 16:19:37 +02:00
if hide_ue_id: # enlève l'ue de depart
q_ues = q_ues.filter(UniteEns.id != hide_ue_id)
2020-09-26 16:19:37 +02:00
ues = q_ues.all()
if not ues:
2020-09-26 16:19:37 +02:00
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 ues:
2020-09-26 16:19:37 +02:00
H.append(
f"""<li>{ue.acronyme} ({ue.titre}) dans <a class="stdlink"
2021-10-17 23:19:26 +02:00
href="{url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=ue.formation.id)}"
>{ue.formation.acronyme} ({ue.formation.titre})</a>, version {ue.formation.version}
</li>
"""
2020-09-26 16:19:37 +02:00
)
H.append("</ul>")
return "\n".join(H)
2021-08-20 01:09:55 +02:00
def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False):
2020-09-26 16:19:37 +02:00
"edit an UE"
# check
ue_id = args["ue_id"]
2021-10-17 23:19:26 +02:00
ue = ue_list({"ue_id": ue_id})[0]
2021-08-20 01:09:55 +02:00
if (not bypass_lock) and ue_is_locked(ue["ue_id"]):
2020-09-26 16:19:37 +02:00
raise ScoLockedFormError()
# check: acronyme unique dans cette formation
2021-07-09 17:47:06 +02:00
if "acronyme" in args:
2020-09-26 16:19:37 +02:00
new_acro = args["acronyme"]
2021-10-17 23:19:26 +02:00
ues = ue_list({"formation_id": ue["formation_id"], "acronyme": new_acro})
2020-09-26 16:19:37 +02:00
if ues and ues[0]["ue_id"] != ue_id:
raise ScoValueError(
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
(chaque UE doit avoir un acronyme unique dans la formation)"""
)
2020-09-26 16:19:37 +02:00
# On ne peut pas supprimer le code UE:
2021-07-09 17:47:06 +02:00
if "ue_code" in args and not args["ue_code"]:
2020-09-26 16:19:37 +02:00
del args["ue_code"]
2021-06-15 13:59:56 +02:00
cnx = ndb.GetDBConnexion()
2021-06-17 00:08:37 +02:00
_ueEditor.edit(cnx, args)
2020-09-26 16:19:37 +02:00
formation = Formation.query.get(ue["formation_id"])
2020-09-26 16:19:37 +02:00
if not dont_invalidate_cache:
# Invalide les semestres utilisant cette formation:
formation.invalidate_cached_sems()
2020-09-26 16:19:37 +02:00
# essai edition en ligne:
2021-08-30 23:28:15 +02:00
def edit_ue_set_code_apogee(id=None, value=None):
2020-09-26 16:19:37 +02:00
"set UE code apogee"
ue_id = id
value = value.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque
2020-09-26 16:19:37 +02:00
log("edit_ue_set_code_apogee: ue_id=%s code_apogee=%s" % (ue_id, value))
2021-10-17 23:19:26 +02:00
ues = ue_list(args={"ue_id": ue_id})
2020-09-26 16:19:37 +02:00
if not ues:
return "ue invalide"
do_ue_edit(
{"ue_id": ue_id, "code_apogee": value},
bypass_lock=True,
dont_invalidate_cache=False,
)
if not value:
2021-02-04 20:02:44 +01:00
value = scu.APO_MISSING_CODE_STR
2020-09-26 16:19:37 +02:00
return value
2021-08-20 01:09:55 +02:00
def ue_is_locked(ue_id):
2021-06-13 18:29:53 +02:00
"""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
2021-06-13 18:29:53 +02:00
""",
{"ue_id": ue_id},
)
return len(r) > 0
2020-09-26 16:19:37 +02:00
# ---- Table recap formation
def formation_table_recap(formation_id, format="html"):
2021-01-16 11:49:02 +01:00
"""Table recapitulant formation."""
from app.scodoc import sco_formations
2021-08-19 10:28:35 +02:00
F = sco_formations.formation_list(args={"formation_id": formation_id})
2020-09-26 16:19:37 +02:00
if not F:
raise ScoValueError("invalid formation_id")
F = F[0]
T = []
2021-10-21 06:32:03 +02:00
ues = ue_list(args={"formation_id": formation_id})
for ue in ues:
Matlist = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
2020-09-26 16:19:37 +02:00
for Mat in Matlist:
2021-10-16 19:20:36 +02:00
Modlist = sco_edit_module.module_list(
2021-08-20 01:09:55 +02:00
args={"matiere_id": Mat["matiere_id"]}
2021-06-17 00:08:37 +02:00
)
2020-09-26 16:19:37 +02:00
for Mod in Modlist:
2021-06-13 19:12:20 +02:00
Mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls(
2021-08-20 01:09:55 +02:00
Mod["module_id"]
2020-09-26 16:19:37 +02:00
)
#
T.append(
{
2021-10-21 06:32:03 +02:00
"UE_acro": ue["acronyme"],
2020-09-26 16:19:37 +02:00
"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,
2021-08-21 17:07:44 +02:00
origin="Généré par %s le " % scu.sco_version.SCONAME
2021-02-05 21:35:21 +01:00
+ scu.timedate_human_repr()
+ "",
2020-09-26 16:19:37 +02:00
caption=title,
html_caption=title,
html_class="table_leftalign",
base_url="%s?formation_id=%s" % (request.base_url, formation_id),
2020-09-26 16:19:37 +02:00
page_title=title,
html_title="<h2>" + title + "</h2>",
pdf_title=title,
preferences=sco_preferences.SemPreferences(),
2020-09-26 16:19:37 +02:00
)
return tab.make_page(format=format)
2020-09-26 16:19:37 +02:00
2022-02-02 22:04:18 +01:00
def ue_list_semestre_ids(ue: dict):
2020-09-26 16:19:37 +02:00
"""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.
"""
2021-11-17 10:28:51 +01:00
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
return sorted(list(set([mod["semestre_id"] for mod in modules])))
2022-02-02 22:04:18 +01:00
UE_PALETTE = [
2022-02-10 23:14:33 +01:00
"#B80004", # rouge
"#F97B3D", # Orange Crayola
"#FEB40B", # Honey Yellow
"#80CB3F", # Yellow Green
"#05162E", # Oxford Blue
"#548687", # Steel Teal
"#444054", # Independence
"#889696", # Spanish Gray
"#0CA4A5", # Viridian Green
2022-02-02 22:04:18 +01:00
]
def colorie_anciennes_ues(ues: list[UniteEns]) -> None:
"""Avant ScoDoc 9.2, les ue n'avaient pas de couleurs
Met des défauts raisonnables
"""
nb_colors = len(UE_PALETTE)
index = 0
last_sem_idx = 0
for ue in ues:
if ue.semestre_idx != last_sem_idx:
index = 0
2022-02-10 23:14:33 +01:00
last_sem_idx = ue.semestre_idx
2022-02-02 22:04:18 +01:00
if ue.color is None:
ue.color = UE_PALETTE[index % nb_colors]
index += 1
db.session.add(ue)
def ue_guess_color_default(formation_id: int, default_semestre_idx: int) -> str:
"""Un code couleur pour une nouvelle UE dans ce semestre"""
nb_colors = len(UE_PALETTE)
# UE existantes dans ce semestre:
nb_ues = UniteEns.query.filter(
UniteEns.formation_id == formation_id,
UniteEns.semestre_idx == default_semestre_idx,
).count()
index = nb_ues
return UE_PALETTE[index % nb_colors]