ScoDoc-PE/app/scodoc/sco_edit_module.py

922 lines
30 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
#
##############################################################################
"""Ajout/Modification/Suppression modules
2020-09-26 16:19:37 +02:00
(portage from DTML)
"""
2021-07-31 18:01:10 +02:00
import flask
from flask import url_for, render_template
from flask import g, request
from flask_login import current_user
from app import db, log
from app import models
from app.models import APO_CODE_STR_LEN
from app.models import Formation, Matiere, Module, UniteEns
from app.models import FormSemestre, ModuleImpl
2022-04-12 17:12:51 +02:00
from app.models import ScolarNews
2021-07-31 18:01:10 +02:00
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_permissions import Permission
2022-01-04 20:03:38 +01:00
from app.scodoc.sco_exceptions import (
ScoValueError,
ScoLockedFormError,
ScoGenError,
ScoNonEmptyFormationObject,
)
from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_edit_matiere
from app.scodoc import sco_moduleimpl
2020-09-26 16:19:37 +02:00
2021-06-16 18:18:32 +02:00
_moduleEditor = ndb.EditableTable(
"notes_modules",
"module_id",
(
"module_id",
"titre",
"code",
"abbrev",
"heures_cours",
"heures_td",
"heures_tp",
"coefficient",
"ue_id",
"matiere_id",
"formation_id",
"semestre_id",
"numero",
"code_apogee",
"module_type"
#'ects'
),
sortkey="numero, code, titre",
output_formators={
"heures_cours": ndb.float_null_is_zero,
"heures_td": ndb.float_null_is_zero,
"heures_tp": ndb.float_null_is_zero,
"numero": ndb.int_null_is_zero,
"coefficient": ndb.float_null_is_zero,
"module_type": ndb.int_null_is_zero
#'ects' : ndb.float_null_is_null
},
)
2021-10-16 19:20:36 +02:00
def module_list(*args, **kw):
2021-06-16 18:18:32 +02:00
"list modules"
cnx = ndb.GetDBConnexion()
return _moduleEditor.list(cnx, *args, **kw)
2021-08-20 01:09:55 +02:00
def do_module_create(args) -> int:
"Create a module. Returns id of new object."
2021-06-16 18:18:32 +02:00
# create
cnx = ndb.GetDBConnexion()
r = _moduleEditor.create(cnx, args)
# news
formation = Formation.query.get(args["formation_id"])
2022-04-12 17:12:51 +02:00
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=formation.id,
text=f"Modification de la formation {formation.acronyme}",
2022-04-13 00:32:31 +02:00
max_frequency=10 * 60,
2021-06-16 18:18:32 +02:00
)
formation.invalidate_cached_sems()
2021-06-16 18:18:32 +02:00
return r
2020-09-26 16:19:37 +02:00
2022-02-01 11:37:05 +01:00
def module_create(
matiere_id=None, module_type=None, semestre_id=None, formation_id=None
):
"""Formulaire de création d'un module
Si matiere_id est spécifié, le module sera créé dans cette matière (cas normal).
Sinon, donne le choix de l'UE de rattachement et utilise la première
matière de cette UE (si elle n'existe pas, la crée).
"""
if matiere_id:
matiere = Matiere.query.get_or_404(matiere_id)
ue = matiere.ue
formation = ue.formation
else:
formation = Formation.query.get_or_404(formation_id)
parcours = formation.get_parcours()
is_apc = parcours.APC_SAE
2022-02-01 11:37:05 +01:00
ues = formation.ues.order_by(
UniteEns.semestre_idx, UniteEns.numero, UniteEns.acronyme
).all()
# cherche le numero adéquat (pour placer le module en fin de liste)
2022-02-01 11:37:05 +01:00
modules = formation.modules.all()
if modules:
2021-12-13 19:05:31 +01:00
default_num = max([m.numero or 0 for m in modules]) + 10
else:
default_num = 10
2021-11-17 10:28:51 +01:00
if is_apc and module_type is not None:
object_name = scu.MODULE_TYPE_NAMES[module_type]
else:
object_name = "Module"
2020-09-26 16:19:37 +02:00
H = [
2021-11-17 10:28:51 +01:00
html_sco_header.sco_header(page_title=f"Création {object_name}"),
2021-11-18 00:24:56 +01:00
]
2022-02-01 11:37:05 +01:00
if not matiere_id:
2021-11-18 00:24:56 +01:00
H += [
2022-02-01 11:37:05 +01:00
f"""<h2>Création {object_name} dans la formation {formation.acronyme}
</h2>
"""
2021-11-18 00:24:56 +01:00
]
else:
H += [
f"""<h2>Création {object_name} dans la matière {matiere.titre},
(UE {ue.acronyme}), semestre {ue.semestre_idx}</h2>
2021-11-18 00:24:56 +01:00
"""
]
H += [
2021-11-17 10:28:51 +01:00
render_template(
"scodoc/help/modules.html",
is_apc=is_apc,
semestre_id=semestre_id,
2021-11-18 00:24:56 +01:00
)
2020-09-26 16:19:37 +02:00
]
2021-11-17 10:28:51 +01:00
descr = [
2020-09-26 16:19:37 +02:00
(
2021-11-17 10:28:51 +01:00
"code",
{
"size": 10,
2021-11-18 00:24:56 +01:00
"explanation": "code du module, ressource ou SAÉ. Exemple M1203, R2.01, ou SAÉ 3.4. Ce code doit être unique dans la formation.",
2021-11-17 10:28:51 +01:00
"allow_null": False,
2022-02-01 11:37:05 +01:00
"validator": lambda val, field, formation_id=formation_id: check_module_code_unicity(
2021-11-17 10:28:51 +01:00
val, field, formation_id
),
},
),
2021-11-18 00:24:56 +01:00
(
"titre",
{
"size": 30,
"explanation": "nom du module. Exemple: <em>Introduction à la démarche ergonomique</em>",
},
),
(
"abbrev",
{
"size": 20,
"explanation": "nom abrégé (pour les bulletins). Exemple: <em>Intro. à l'ergonomie</em>",
},
),
]
2022-02-01 11:37:05 +01:00
if is_apc:
module_types = scu.ModuleType # tous les types
else:
# ne propose pas SAE et Ressources:
module_types = set(scu.ModuleType) - {
scu.ModuleType.RESSOURCE,
scu.ModuleType.SAE,
}
2021-11-18 00:24:56 +01:00
descr += [
2021-11-17 10:28:51 +01:00
(
"module_type",
{
"input_type": "menu",
"title": "Type",
"explanation": "",
2022-02-01 11:37:05 +01:00
"labels": [x.name.capitalize() for x in module_types],
"allowed_values": [str(int(x)) for x in module_types],
2021-11-17 10:28:51 +01:00
},
),
(
"heures_cours",
2021-11-18 00:24:56 +01:00
{
"title": "Heures de cours",
"size": 4,
"type": "float",
"explanation": "nombre d'heures de cours (optionnel)",
},
2021-11-17 10:28:51 +01:00
),
(
"heures_td",
{
2021-11-18 00:24:56 +01:00
"title": "Heures de TD",
2021-11-17 10:28:51 +01:00
"size": 4,
"type": "float",
2021-11-18 00:24:56 +01:00
"explanation": "nombre d'heures de Travaux Dirigés (optionnel)",
2021-11-17 10:28:51 +01:00
},
),
(
"heures_tp",
{
2021-11-18 00:24:56 +01:00
"title": "Heures de TP",
2021-11-17 10:28:51 +01:00
"size": 4,
"type": "float",
2021-11-18 00:24:56 +01:00
"explanation": "nombre d'heures de Travaux Pratiques (optionnel)",
2021-11-17 10:28:51 +01:00
},
),
]
if is_apc:
descr += [
2020-09-26 16:19:37 +02:00
(
2021-11-17 10:28:51 +01:00
"sep_ue_coefs",
2020-09-26 16:19:37 +02:00
{
2021-11-17 10:28:51 +01:00
"input_type": "separator",
"title": """
<div>(<em>les coefficients vers les UE se fixent sur la page dédiée</em>)
</div>""",
2020-09-26 16:19:37 +02:00
},
),
2021-11-17 10:28:51 +01:00
]
else:
descr += [
2020-09-26 16:19:37 +02:00
(
"coefficient",
{
"size": 4,
"type": "float",
"explanation": "coefficient dans la formation (PPN)",
"allow_null": False,
},
),
2021-11-17 10:28:51 +01:00
]
2021-11-18 00:24:56 +01:00
2022-02-01 11:37:05 +01:00
if matiere_id:
descr += [
("ue_id", {"default": ue.id, "input_type": "hidden"}),
("matiere_id", {"default": matiere_id, "input_type": "hidden"}),
]
else:
# choix de l'UE de rattachement
descr += [
(
"ue_id",
{
"input_type": "menu",
"type": "int",
"title": "UE de rattachement",
"explanation": "utilisée notamment pour les malus",
"labels": [
f"S{u.semestre_idx if u.semestre_idx is not None else '.'} / {u.acronyme} {u.titre}"
for u in ues
],
2022-02-01 11:37:05 +01:00
"allowed_values": [u.id for u in ues],
},
),
]
2021-11-17 10:28:51 +01:00
descr += [
# ('ects', { 'size' : 4, 'type' : 'float', 'title' : 'ECTS', 'explanation' : 'nombre de crédits ECTS (inutilisés: les crédits sont associés aux UE)' }),
2022-02-01 11:37:05 +01:00
("formation_id", {"default": formation.id, "input_type": "hidden"}),
2021-11-17 10:28:51 +01:00
(
"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",
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
2021-11-17 10:28:51 +01:00
},
),
(
"numero",
{
"size": 2,
"explanation": "numéro (1,2,3,4...) pour ordre d'affichage",
"type": "int",
"default": default_num,
},
2020-09-26 16:19:37 +02:00
),
2021-11-17 10:28:51 +01:00
]
args = scu.get_request_args()
tf = TrivialFormulator(
request.base_url,
args,
descr,
2020-09-26 16:19:37 +02:00
submitlabel="Créer ce module",
)
if tf[0] == 0:
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
2020-09-26 16:19:37 +02:00
else:
2022-02-01 11:37:05 +01:00
if not matiere_id:
# formulaire avec choix UE de rattachement
ue = UniteEns.query.get(tf[2]["ue_id"])
if ue is None:
raise ValueError("UE invalide")
matiere = ue.matieres.first()
if matiere:
tf[2]["matiere_id"] = matiere.id
else:
matiere_id = sco_edit_matiere.do_matiere_create(
{"ue_id": ue.id, "titre": ue.titre, "numero": 1},
)
tf[2]["matiere_id"] = matiere_id
tf[2]["semestre_id"] = ue.semestre_idx
2021-12-11 20:27:58 +01:00
_ = do_module_create(tf[2])
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-02-01 11:37:05 +01:00
formation_id=formation.id,
2021-12-11 20:27:58 +01:00
semestre_idx=tf[2]["semestre_id"],
2021-07-31 18:01:10 +02:00
)
2020-09-26 16:19:37 +02:00
)
2022-01-04 20:03:38 +01:00
def can_delete_module(module):
"True si le module n'est pas utilisée dans des formsemestre"
return len(module.modimpls.all()) == 0
2021-08-20 01:09:55 +02:00
def do_module_delete(oid):
2021-06-16 18:18:32 +02:00
"delete module"
2022-01-04 20:03:38 +01:00
module = Module.query.get_or_404(oid)
mod = module_list({"module_id": oid})[0] # sco7
if module_is_locked(module.id):
2021-06-16 18:18:32 +02:00
raise ScoLockedFormError()
2022-01-04 20:03:38 +01:00
if not can_delete_module(module):
raise ScoNonEmptyFormationObject(
"Module",
msg=module.titre,
dest_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=module.formation_id,
semestre_idx=module.ue.semestre_idx,
),
)
2021-06-16 18:18:32 +02:00
# S'il y a des moduleimpls, on ne peut pas detruire le module !
mods = sco_moduleimpl.moduleimpl_list(module_id=oid)
2021-06-16 18:18:32 +02:00
if mods:
err_page = f"""<h3>Destruction du module impossible car il est utilisé dans des semestres existants !</h3>
2022-01-04 20:03:38 +01:00
<p class="help">Il faut d'abord supprimer le semestre (ou en retirer ce module). Mais il est peut être préférable de
laisser ce programme intact et d'en créer une nouvelle version pour la modifier sans affecter les semestres déjà en place.
</p>
2021-10-17 23:19:26 +02:00
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
formation_id=mod["formation_id"])}">reprendre</a>
"""
2021-06-16 18:18:32 +02:00
raise ScoGenError(err_page)
# delete
cnx = ndb.GetDBConnexion()
_moduleEditor.delete(cnx, oid)
# news
formation = module.formation
2022-04-12 17:12:51 +02:00
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=mod["formation_id"],
text=f"Modification de la formation {formation.acronyme}",
2022-04-13 00:32:31 +02:00
max_frequency=10 * 60,
2021-06-16 18:18:32 +02:00
)
formation.invalidate_cached_sems()
2021-06-16 18:18:32 +02:00
def module_delete(module_id=None):
2020-09-26 16:19:37 +02:00
"""Delete a module"""
2022-01-04 20:03:38 +01:00
module = Module.query.get_or_404(module_id)
mod = module_list(args={"module_id": module_id})[0] # sco7
if not can_delete_module(module):
raise ScoNonEmptyFormationObject(
"Module",
msg=module.titre,
dest_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=module.formation_id,
semestre_idx=module.ue.semestre_idx,
),
)
2020-09-26 16:19:37 +02:00
H = [
html_sco_header.sco_header(page_title="Suppression d'un module"),
f"""<h2>Suppression du module {module.titre or "<em>sans titre</em>"} ({module.code})</h2>""",
2020-09-26 16:19:37 +02:00
]
dest_url = url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
2022-05-10 11:04:24 +02:00
formation_id=module.formation_id,
semestre_idx=module.ue.semestre_idx,
)
2020-09-26 16:19:37 +02:00
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
2020-09-26 16:19:37 +02:00
(("module_id", {"input_type": "hidden"}),),
initvalues=mod,
2020-09-26 16:19:37 +02:00
submitlabel="Confirmer la suppression",
cancelbutton="Annuler",
)
if tf[0] == 0:
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
2020-09-26 16:19:37 +02:00
elif tf[0] == -1:
2021-07-31 18:01:10 +02:00
return flask.redirect(dest_url)
2020-09-26 16:19:37 +02:00
else:
2021-08-20 01:09:55 +02:00
do_module_delete(module_id)
2021-07-31 18:01:10 +02:00
return flask.redirect(dest_url)
2020-09-26 16:19:37 +02:00
def do_module_edit(vals: dict) -> None:
2021-06-16 18:18:32 +02:00
"edit a module"
# check
mod = module_list({"module_id": vals["module_id"]})[0]
2021-08-20 01:09:55 +02:00
if module_is_locked(mod["module_id"]):
2021-06-16 18:18:32 +02:00
# formation verrouillée: empeche de modifier certains champs:
protected_fields = ("coefficient", "ue_id", "matiere_id", "semestre_id")
for f in protected_fields:
if f in vals:
del vals[f]
2021-06-16 18:18:32 +02:00
# edit
cnx = ndb.GetDBConnexion()
_moduleEditor.edit(cnx, vals)
Formation.query.get(mod["formation_id"]).invalidate_cached_sems()
2021-06-16 18:18:32 +02:00
2021-08-20 01:09:55 +02:00
def check_module_code_unicity(code, field, formation_id, module_id=None):
2020-09-26 16:19:37 +02:00
"true si code module unique dans la formation"
2021-10-16 19:20:36 +02:00
Mods = module_list(args={"code": code, "formation_id": formation_id})
2020-09-26 16:19:37 +02:00
if module_id: # edition: supprime le module en cours
Mods = [m for m in Mods if m["module_id"] != module_id]
return len(Mods) == 0
def module_edit(module_id=None):
2020-09-26 16:19:37 +02:00
"""Edit a module"""
from app.scodoc import sco_formations
from app.scodoc import sco_tag_module
2020-09-26 16:19:37 +02:00
if not module_id:
raise ScoValueError("invalid module !")
modules = module_list(args={"module_id": module_id})
if not modules:
2020-09-26 16:19:37 +02:00
raise ScoValueError("invalid module !")
module = modules[0]
2021-11-17 10:28:51 +01:00
a_module = models.Module.query.get(module_id)
2021-08-20 01:09:55 +02:00
unlocked = not module_is_locked(module_id)
2021-11-13 12:09:30 +01:00
formation_id = module["formation_id"]
formation = sco_formations.formation_list(args={"formation_id": formation_id})[0]
parcours = sco_codes_parcours.get_parcours_from_code(formation["type_parcours"])
is_apc = parcours.APC_SAE # BUT
in_use = len(a_module.modimpls.all()) > 0 # il y a des modimpls
matieres = Matiere.query.filter(
Matiere.ue_id == UniteEns.id, UniteEns.formation_id == formation_id
).order_by(UniteEns.semestre_idx, UniteEns.numero, Matiere.numero)
if in_use:
# restreint aux matières du même semestre
matieres = matieres.filter(UniteEns.semestre_idx == a_module.ue.semestre_idx)
if is_apc:
# ne conserve que la 1ere matière de chaque UE,
# et celle à laquelle ce module est rattaché
matieres = [
mat
for mat in matieres
if a_module.matiere.id == mat.id or mat.id == mat.ue.matieres.first().id
]
mat_names = [
"S%s / %s" % (mat.ue.semestre_idx, mat.ue.acronyme) for mat in matieres
]
else:
mat_names = ["%s / %s" % (mat.ue.acronyme, mat.titre or "") for mat in matieres]
2022-02-20 15:10:15 +01:00
ue_mat_ids = ["%s!%s" % (mat.ue.id, mat.id) for mat in matieres]
module["ue_matiere_id"] = "%s!%s" % (module["ue_id"], module["matiere_id"])
2020-09-26 16:19:37 +02:00
2021-07-09 17:47:06 +02:00
semestres_indices = list(range(1, parcours.NB_SEM + 1))
2020-09-26 16:19:37 +02:00
H = [
2021-06-13 18:29:53 +02:00
html_sco_header.sco_header(
2022-03-28 09:50:40 +02:00
page_title=f"Modification du module {a_module.code or a_module.titre or ''}",
2020-09-26 16:19:37 +02:00
cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css"],
javascripts=[
"libjs/jQuery-tagEditor/jquery.tag-editor.min.js",
"libjs/jQuery-tagEditor/jquery.caret.min.js",
"js/module_tag_editor.js",
],
),
2022-03-28 09:50:40 +02:00
f"""<h2>Modification du module {a_module.code or ''} {a_module.titre or ''}""",
""" (formation %(acronyme)s, version %(version)s)</h2>""" % formation,
render_template(
"scodoc/help/modules.html",
is_apc=is_apc,
formsemestres=FormSemestre.query.filter(
ModuleImpl.formsemestre_id == FormSemestre.id,
ModuleImpl.module_id == module_id,
)
.order_by(FormSemestre.date_debut)
.all(),
),
2020-09-26 16:19:37 +02:00
]
if not unlocked:
H.append(
"""<div class="ue_warning"><span>Formation verrouillée, seuls certains éléments peuvent être modifiés</span></div>"""
)
2022-02-01 11:37:05 +01:00
if is_apc:
module_types = scu.ModuleType # tous les types
else:
# ne propose pas SAE et Ressources, sauf si déjà de ce type...
module_types = (
set(scu.ModuleType) - {scu.ModuleType.RESSOURCE, scu.ModuleType.SAE}
2022-02-26 20:35:34 +01:00
) | {
scu.ModuleType(a_module.module_type)
if a_module.module_type
else scu.ModuleType.STANDARD
}
2020-09-26 16:19:37 +02:00
2021-11-13 12:09:30 +01:00
descr = [
2020-09-26 16:19:37 +02:00
(
2021-11-13 12:09:30 +01:00
"code",
{
"size": 10,
2022-03-01 19:27:03 +01:00
"explanation": "code du module (issu du programme, exemple M1203 ou R2.01. Doit être unique dans la formation)",
2021-11-13 12:09:30 +01:00
"allow_null": False,
"validator": lambda val, field, formation_id=formation_id: check_module_code_unicity(
val, field, formation_id, module_id=module_id
),
},
),
("titre", {"size": 30, "explanation": "nom du module"}),
("abbrev", {"size": 20, "explanation": "nom abrégé (pour bulletins)"}),
(
"module_type",
{
"input_type": "menu",
"title": "Type",
"explanation": "",
2022-02-01 11:37:05 +01:00
"labels": [x.name.capitalize() for x in module_types],
"allowed_values": [str(int(x)) for x in module_types],
2021-11-13 12:09:30 +01:00
"enabled": unlocked,
},
),
(
"heures_cours",
{
"title": "Heures CM :",
"size": 4,
"type": "float",
"explanation": "nombre d'heures de cours",
},
2021-11-13 12:09:30 +01:00
),
(
"heures_td",
{
"title": "Heures TD :",
2021-11-13 12:09:30 +01:00
"size": 4,
"type": "float",
"explanation": "nombre d'heures de Travaux Dirigés",
},
),
(
"heures_tp",
{
"title": "Heures TP :",
2021-11-13 12:09:30 +01:00
"size": 4,
"type": "float",
"explanation": "nombre d'heures de Travaux Pratiques",
},
),
]
if is_apc:
coefs_lst = a_module.ue_coefs_list()
if coefs_lst:
coefs_descr_txt = ", ".join(
[f"{ue.acronyme}: {c}" for (ue, c) in coefs_lst]
)
2021-11-13 12:09:30 +01:00
else:
coefs_descr_txt = """<span class="missing_value">non définis</span>"""
descr += [
2020-09-26 16:19:37 +02:00
(
2021-11-13 12:09:30 +01:00
"ue_coefs",
2020-09-26 16:19:37 +02:00
{
2021-11-13 12:09:30 +01:00
"readonly": True,
"title": "Coefficients vers les UE ",
2021-11-13 12:09:30 +01:00
"default": coefs_descr_txt,
"explanation": " <br>(passer par la page d'édition de la formation pour modifier les coefficients)",
2020-09-26 16:19:37 +02:00
},
2021-11-13 12:09:30 +01:00
)
]
else: # Module classique avec coef scalaire:
descr += [
2020-09-26 16:19:37 +02:00
(
"coefficient",
{
"size": 4,
"type": "float",
"explanation": "coefficient dans la formation (PPN)",
"allow_null": False,
"enabled": unlocked,
},
),
2021-11-13 12:09:30 +01:00
]
descr += [
("formation_id", {"input_type": "hidden"}),
("ue_id", {"input_type": "hidden"}),
("module_id", {"input_type": "hidden"}),
(
"ue_matiere_id",
{
"input_type": "menu",
"title": "Rattachement :" if is_apc else "Matière :",
"explanation": (
"UE de rattachement"
+ (
" module utilisé, ne peut pas être changé de semestre"
if in_use
else ""
)
)
if is_apc
else "un module appartient à une seule matière.",
"labels": mat_names,
"allowed_values": ue_mat_ids,
"enabled": unlocked,
},
),
2021-11-17 10:28:51 +01:00
]
if is_apc:
# le semestre du module est toujours celui de son UE
descr += [
(
"semestre_id",
{
"input_type": "hidden",
"type": "int",
"readonly": True,
},
)
]
else:
descr += [
(
"semestre_id",
{
"input_type": "menu",
"type": "int",
"title": parcours.SESSION_NAME.capitalize(),
"explanation": "%s de début du module dans la formation standard"
% parcours.SESSION_NAME,
"labels": [str(x) for x in semestres_indices],
"allowed_values": semestres_indices,
"enabled": unlocked,
},
)
]
descr += [
2021-11-13 12:09:30 +01:00
(
"code_apogee",
{
"title": "Code Apogée",
"size": 25,
2022-03-01 19:27:03 +01:00
"explanation": """(optionnel) code élément pédagogique Apogée ou liste de codes ELP
séparés par des virgules (ce code est propre à chaque établissement, se rapprocher
du référent Apogée).
""",
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
2021-11-13 12:09:30 +01:00
},
),
(
"numero",
{
"size": 2,
"explanation": "numéro (1,2,3,4...) pour ordre d'affichage",
"type": "int",
},
2020-09-26 16:19:37 +02:00
),
2021-11-13 12:09:30 +01:00
]
2021-11-17 10:28:51 +01:00
# force module semestre_idx to its UE
if a_module.ue.semestre_idx:
module["semestre_id"] = a_module.ue.semestre_idx
# Filet de sécurité si jamais l'UE n'a pas non plus de semestre:
if not module["semestre_id"]:
module["semestre_id"] = 1
2021-11-13 12:09:30 +01:00
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
descr,
2020-09-26 16:19:37 +02:00
html_foot_markup="""<div style="width: 90%;"><span class="sco_tag_edit"><textarea data-module_id="{}" class="module_tag_editor">{}</textarea></span></div>""".format(
2021-08-20 01:09:55 +02:00
module_id, ",".join(sco_tag_module.module_tag_list(module_id))
2020-09-26 16:19:37 +02:00
),
initvalues=module,
2020-09-26 16:19:37 +02:00
submitlabel="Modifier ce module",
)
2022-01-30 09:05:51 +01:00
#
2020-09-26 16:19:37 +02:00
if tf[0] == 0:
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
2020-09-26 16:19:37 +02:00
elif tf[0] == -1:
return flask.redirect(
url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=formation_id,
semestre_idx=module["semestre_id"],
)
)
2020-09-26 16:19:37 +02:00
else:
2022-01-30 08:25:22 +01:00
# l'UE de rattachement peut changer
2020-09-26 16:19:37 +02:00
tf[2]["ue_id"], tf[2]["matiere_id"] = tf[2]["ue_matiere_id"].split("!")
2022-02-20 15:10:15 +01:00
x, y = tf[2]["ue_matiere_id"].split("!")
tf[2]["ue_id"] = int(x)
tf[2]["matiere_id"] = int(y)
old_ue_id = a_module.ue.id
2022-02-20 15:10:15 +01:00
new_ue_id = tf[2]["ue_id"]
if (old_ue_id != new_ue_id) and in_use:
2022-01-30 08:25:22 +01:00
new_ue = UniteEns.query.get_or_404(new_ue_id)
if new_ue.semestre_idx != a_module.ue.semestre_idx:
# pas changer de semestre un module utilisé !
raise ScoValueError(
"Module utilisé: il ne peut pas être changé de semestre !"
)
2021-12-11 20:27:58 +01:00
# En APC, force le semestre égal à celui de l'UE
if is_apc:
selected_ue = UniteEns.query.get(tf[2]["ue_id"])
if selected_ue is None:
raise ValueError("UE invalide")
tf[2]["semestre_id"] = selected_ue.semestre_idx
2020-09-26 16:19:37 +02:00
# Check unicité code module dans la formation
2021-08-20 01:09:55 +02:00
do_module_edit(tf[2])
return flask.redirect(
url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=formation_id,
2021-12-11 20:27:58 +01:00
semestre_idx=tf[2]["semestre_id"],
)
)
2020-09-26 16:19:37 +02:00
# Edition en ligne du code Apogee
def edit_module_set_code_apogee(id=None, value=None):
2021-06-24 23:09:06 +02:00
"Set UE code apogee"
2020-09-26 16:19:37 +02:00
module_id = id
2021-11-17 10:28:51 +01:00
value = str(value).strip("-_ \t")
2020-09-26 16:19:37 +02:00
log("edit_module_set_code_apogee: module_id=%s code_apogee=%s" % (module_id, value))
2021-10-16 19:20:36 +02:00
modules = module_list(args={"module_id": module_id})
2020-09-26 16:19:37 +02:00
if not modules:
2021-06-24 23:09:06 +02:00
return "module invalide" # should not occur
2020-09-26 16:19:37 +02:00
2021-08-20 01:09:55 +02:00
do_module_edit({"module_id": module_id, "code_apogee": value})
2020-09-26 16:19:37 +02:00
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-10-16 19:20:36 +02:00
def module_table(formation_id):
2020-09-26 16:19:37 +02:00
"""Liste des modules de la formation
(XXX inutile ou a revoir)
"""
from app.scodoc import sco_formations
2020-09-26 16:19:37 +02:00
if not formation_id:
raise ScoValueError("invalid formation !")
2021-08-19 10:28:35 +02:00
F = sco_formations.formation_list(args={"formation_id": formation_id})[0]
2020-09-26 16:19:37 +02:00
H = [
html_sco_header.sco_header(page_title="Liste des modules de %(titre)s" % F),
2020-09-26 16:19:37 +02:00
"""<h2>Listes des modules dans la formation %(titre)s (%(acronyme)s)</h2>"""
% F,
'<ul class="notes_module_list">',
]
editable = current_user.has_permission(Permission.ScoChangeFormation)
2020-09-26 16:19:37 +02:00
2021-10-16 19:20:36 +02:00
for Mod in module_list(args={"formation_id": formation_id}):
2020-09-26 16:19:37 +02:00
H.append('<li class="notes_module_list">%s' % Mod)
if editable:
H.append('<a href="module_edit?module_id=%(module_id)s">modifier</a>' % Mod)
H.append(
'<a href="module_delete?module_id=%(module_id)s">supprimer</a>' % Mod
)
H.append("</li>")
H.append("</ul>")
H.append(html_sco_header.sco_footer())
2020-09-26 16:19:37 +02:00
return "\n".join(H)
2021-08-20 01:09:55 +02:00
def module_is_locked(module_id):
2021-06-13 18:29:53 +02:00
"""True if module should not be modified
(used in a locked formsemestre)
"""
r = ndb.SimpleDictFetch(
"""SELECT mi.id
FROM notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi
WHERE mi.module_id = mod.id
AND mi.formsemestre_id = sem.id
AND mi.module_id = %(module_id)s
AND sem.etat = false
2021-06-13 18:29:53 +02:00
""",
{"module_id": module_id},
)
return len(r) > 0
2021-08-20 01:09:55 +02:00
def module_count_moduleimpls(module_id):
2021-06-13 18:29:53 +02:00
"Number of moduleimpls using this module"
mods = sco_moduleimpl.moduleimpl_list(module_id=module_id)
2021-06-13 18:29:53 +02:00
return len(mods)
2020-09-26 16:19:37 +02:00
def formation_add_malus_modules(formation_id, titre=None, redirect=True):
2021-01-01 18:40:47 +01:00
"""Création d'un module de "malus" dans chaque UE d'une formation"""
formation = Formation.query.get_or_404(formation_id)
for ue in formation.ues:
ue_add_malus_module(ue, titre=titre)
formation.invalidate_cached_sems()
2020-09-26 16:19:37 +02:00
if redirect:
2021-10-21 06:32:03 +02:00
return flask.redirect(
url_for(
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id
)
)
2020-09-26 16:19:37 +02:00
def ue_add_malus_module(ue: UniteEns, titre=None, code=None) -> int:
"""Add a malus module in this ue.
If already exists, do nothing.
Returns id of malus module.
"""
modules_malus = [m for m in ue.modules if m.module_type == scu.ModuleType.MALUS]
if len(modules_malus) > 0:
return modules_malus[0].id # déjà existant
2020-09-26 16:19:37 +02:00
titre = titre or ""
code = code or f"MALUS{ue.numero}"
2020-09-26 16:19:37 +02:00
# Tout module doit avoir un semestre_id (indice 1, 2, ...)
if ue.semestre_idx is None:
semestre_ids = sorted(list(set([m.semestre_id for m in ue.modules])))
if len(semestre_ids) > 0:
semestre_id = semestre_ids[0]
else:
# c'est ennuyeux: dans ce cas, on pourrait demander à indiquer explicitement
# le semestre ? ou affecter le malus au semestre 1 ???
raise ScoValueError(
"Impossible d'ajouter un malus s'il n'y a pas d'autres modules"
)
2020-09-26 16:19:37 +02:00
else:
semestre_id = ue.semestre_idx
2020-09-26 16:19:37 +02:00
# Matiere pour placer le module malus
titre_matiere_malus = "Malus"
2020-09-26 16:19:37 +02:00
matieres_malus = [mat for mat in ue.matieres if mat.titre == titre_matiere_malus]
if len(matieres_malus) > 0:
# matière Malus déjà existante, l'utilise
matiere = matieres_malus[0]
else:
if ue.matieres.count() > 0:
numero = max([mat.numero for mat in ue.matieres]) + 10
else:
numero = 0
matiere = Matiere(ue_id=ue.id, titre=titre_matiere_malus, numero=numero)
db.session.add(matiere)
module = Module(
titre=titre,
code=code,
coefficient=0.0,
ue=ue,
matiere=matiere,
formation=ue.formation,
semestre_id=semestre_id,
module_type=scu.ModuleType.MALUS,
2020-09-26 16:19:37 +02:00
)
db.session.add(module)
db.session.commit()
2020-09-26 16:19:37 +02:00
return module.id