1894 lines
73 KiB
Python
1894 lines
73 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2023 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
|
|
#
|
|
##############################################################################
|
|
|
|
"""Form choix modules / responsables et creation formsemestre
|
|
"""
|
|
import flask
|
|
from flask import url_for, flash, redirect
|
|
from flask import g, request
|
|
from flask_login import current_user
|
|
|
|
from app import db
|
|
from app.auth.models import User
|
|
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
|
|
from app.models import Module, ModuleImpl, Evaluation, EvaluationUEPoids, UniteEns
|
|
from app.models import ScolarNews
|
|
from app.models.formations import Formation
|
|
from app.models.formsemestre import FormSemestre
|
|
from app.models.but_refcomp import ApcParcours
|
|
import app.scodoc.notesdb as ndb
|
|
import app.scodoc.sco_utils as scu
|
|
from app.scodoc import sco_cache
|
|
from app.scodoc import sco_groups
|
|
from app import log
|
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
|
from app.scodoc.sco_permissions import Permission
|
|
from app.scodoc.sco_vdi import ApoEtapeVDI
|
|
from app.scodoc import html_sco_header
|
|
from app.scodoc import codes_cursus
|
|
from app.scodoc import sco_compute_moy
|
|
from app.scodoc import sco_edit_module
|
|
from app.scodoc import sco_etud
|
|
from app.scodoc import sco_evaluation_db
|
|
from app.scodoc import sco_formations
|
|
from app.scodoc import sco_formsemestre
|
|
from app.scodoc import sco_groups_copy
|
|
from app.scodoc import sco_modalites
|
|
from app.scodoc import sco_moduleimpl
|
|
from app.scodoc import sco_cursus_dut
|
|
from app.scodoc import sco_permissions_check
|
|
from app.scodoc import sco_portal_apogee
|
|
from app.scodoc import sco_preferences
|
|
from app.scodoc import sco_users
|
|
|
|
|
|
def _default_sem_title(formation):
|
|
"""Default title for a semestre in formation"""
|
|
return formation.titre
|
|
|
|
|
|
def formsemestre_createwithmodules():
|
|
"""Page création d'un semestre"""
|
|
H = [
|
|
html_sco_header.sco_header(
|
|
page_title="Création d'un semestre",
|
|
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
|
cssstyles=["css/autosuggest_inquisitor.css"],
|
|
bodyOnLoad="init_tf_form('')",
|
|
),
|
|
"""<h2>Mise en place d'un semestre de formation</h2>""",
|
|
]
|
|
r = do_formsemestre_createwithmodules()
|
|
if isinstance(r, str):
|
|
H.append(r)
|
|
else:
|
|
return r # response redirect
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
|
|
|
|
|
def formsemestre_editwithmodules(formsemestre_id):
|
|
"""Page modification semestre"""
|
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
|
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
H = [
|
|
html_sco_header.html_sem_header(
|
|
"Modification du semestre",
|
|
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
|
cssstyles=["css/autosuggest_inquisitor.css"],
|
|
bodyOnLoad="init_tf_form('')",
|
|
)
|
|
]
|
|
if not formsemestre.etat:
|
|
H.append(
|
|
f"""<p>{scu.icontag(
|
|
"lock_img", border="0", title="Semestre verrouillé")
|
|
}<b>Ce semestre est verrouillé.</b></p>"""
|
|
)
|
|
else:
|
|
r = do_formsemestre_createwithmodules(edit=True, formsemestre=formsemestre)
|
|
if isinstance(r, str):
|
|
H.append(r)
|
|
else:
|
|
return r # response redirect
|
|
vals = scu.get_request_args()
|
|
if not vals.get("tf_submitted", False):
|
|
H.append(
|
|
"""<p class="help">Seuls les modules cochés font partie de ce semestre.
|
|
Pour les retirer, les décocher et appuyer sur le bouton "modifier".
|
|
</p>
|
|
<p class="help">Attention : s'il y a déjà des évaluations dans un module,
|
|
il ne peut pas être supprimé !</p>
|
|
<p class="help">Les modules ont toujours un responsable.
|
|
Par défaut, c'est le directeur des études.</p>
|
|
<p class="help">Un semestre ne peut comporter qu'une seule UE "bonus
|
|
sport/culture"</p>
|
|
"""
|
|
)
|
|
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
|
|
|
|
|
def can_edit_sem(formsemestre_id: int = None, sem=None):
|
|
"""Return sem if user can edit it, False otherwise"""
|
|
sem = sem or sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
if not current_user.has_permission(Permission.ScoImplement): # pas chef
|
|
if not sem["resp_can_edit"] or current_user.id not in sem["responsables"]:
|
|
return False
|
|
return sem
|
|
|
|
|
|
resp_fields = [
|
|
"responsable_id",
|
|
"responsable_id2",
|
|
"responsable_id3",
|
|
"responsable_id4",
|
|
]
|
|
|
|
|
|
def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = None):
|
|
"Form choix modules / responsables et création formsemestre"
|
|
vals = scu.get_request_args()
|
|
# Fonction accessible à tous, contrôle d'acces à la main:
|
|
if not current_user.has_permission(Permission.ScoImplement):
|
|
if not edit:
|
|
# il faut ScoImplement pour créer un semestre
|
|
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
|
|
else:
|
|
if not formsemestre.resp_can_edit or current_user.id not in (
|
|
u.id for u in formsemestre.responsables
|
|
):
|
|
raise AccessDenied(
|
|
"vous n'avez pas le droit d'effectuer cette opération"
|
|
)
|
|
|
|
# Liste des enseignants avec form pour affichage / saisie avec suggestion
|
|
# attention: il faut prendre ici tous les utilisateurs, même inactifs, car
|
|
# les responsables de modules d'anciens semestres peuvent ne plus être actifs.
|
|
# Mais la suggestion utilise get_user_list_xml() qui ne suggérera que les actifs.
|
|
user_list = sco_users.get_user_list(with_inactives=True)
|
|
uid2display = {} # user_name : forme pour affichage = "NOM Prenom (login)"
|
|
for u in user_list:
|
|
uid2display[u.id] = u.get_nomplogin()
|
|
allowed_user_names = list(uid2display.values()) + [""]
|
|
#
|
|
if formsemestre:
|
|
formation = formsemestre.formation
|
|
else:
|
|
formation_id = int(vals["formation_id"])
|
|
formation = Formation.query.get_or_404(formation_id)
|
|
|
|
is_apc = formation.is_apc()
|
|
if not edit:
|
|
initvalues = {"titre": _default_sem_title(formation)}
|
|
semestre_id = int(vals["semestre_id"])
|
|
module_ids_set = set()
|
|
else:
|
|
# setup form init values
|
|
initvalues = formsemestre.to_dict()
|
|
semestre_id = formsemestre.semestre_id
|
|
# add associated modules to tf-checked:
|
|
module_ids_existing = [modimpl.module.id for modimpl in formsemestre.modimpls]
|
|
module_ids_set = set(module_ids_existing)
|
|
initvalues["tf-checked"] = ["MI" + str(x) for x in module_ids_existing]
|
|
for modimpl in formsemestre.modimpls:
|
|
initvalues[f"MI{modimpl.module.id}"] = uid2display.get(
|
|
modimpl.responsable_id,
|
|
f"inconnu numéro {modimpl.responsable_id} resp. de {modimpl.id} !",
|
|
)
|
|
for index, resp in enumerate(formsemestre.responsables):
|
|
initvalues[resp_fields[index]] = uid2display.get(resp.id)
|
|
|
|
# Liste des ID de semestres
|
|
if formation.type_parcours is not None:
|
|
parcours = codes_cursus.get_cursus_from_code(formation.type_parcours)
|
|
nb_sem = parcours.NB_SEM
|
|
else:
|
|
nb_sem = 10 # fallback, max 10 semestres
|
|
if nb_sem == 1:
|
|
semestre_id_list = [-1]
|
|
else:
|
|
if edit and is_apc:
|
|
# en APC, ne permet pas de changer de semestre
|
|
semestre_id_list = [formsemestre.semestre_id]
|
|
else:
|
|
semestre_id_list = list(range(1, nb_sem + 1))
|
|
if not is_apc:
|
|
# propose "pas de semestre" seulement en classique
|
|
semestre_id_list.insert(0, -1)
|
|
|
|
semestre_id_labels = []
|
|
for sid in semestre_id_list:
|
|
if sid == -1:
|
|
semestre_id_labels.append("pas de semestres")
|
|
else:
|
|
semestre_id_labels.append(f"S{sid}")
|
|
# Liste des modules dans cette formation
|
|
if is_apc:
|
|
# BUT: trie par type (res, sae), parcours, numéro
|
|
modules = sorted(
|
|
formation.modules,
|
|
key=lambda m: m.sort_key_apc(),
|
|
)
|
|
else:
|
|
modules = (
|
|
Module.query.filter(
|
|
Module.formation_id == formation.id, UniteEns.id == Module.ue_id
|
|
)
|
|
.order_by(Module.module_type, UniteEns.numero, Module.numero)
|
|
.all()
|
|
)
|
|
# Pour regroupement des modules par semestres:
|
|
semestre_ids = {}
|
|
for mod in modules:
|
|
semestre_ids[mod.semestre_id] = 1
|
|
semestre_ids = list(semestre_ids.keys())
|
|
semestre_ids.sort()
|
|
|
|
modalites = sco_modalites.do_modalite_list()
|
|
modalites_abbrv = [m["modalite"] for m in modalites]
|
|
modalites_titles = [m["titre"] for m in modalites]
|
|
#
|
|
modform = [
|
|
("formsemestre_id", {"input_type": "hidden"}),
|
|
("formation_id", {"input_type": "hidden", "default": formation.id}),
|
|
(
|
|
"date_debut",
|
|
{
|
|
"title": "Date de début", # j/m/a
|
|
"input_type": "datedmy",
|
|
"explanation": "j/m/a",
|
|
"size": 9,
|
|
"allow_null": False,
|
|
},
|
|
),
|
|
(
|
|
"date_fin",
|
|
{
|
|
"title": "Date de fin", # j/m/a
|
|
"input_type": "datedmy",
|
|
"explanation": "j/m/a",
|
|
"size": 9,
|
|
"allow_null": False,
|
|
},
|
|
),
|
|
*[
|
|
(
|
|
field,
|
|
{
|
|
"input_type": "text_suggest",
|
|
"size": 50,
|
|
"title": "(Co-)Directeur(s) des études"
|
|
if index
|
|
else "Directeur des études",
|
|
"explanation": "(facultatif) taper le début du nom et choisir dans le menu"
|
|
if index
|
|
else "(obligatoire) taper le début du nom et choisir dans le menu",
|
|
"allowed_values": allowed_user_names,
|
|
"allow_null": index, # > 0, # il faut au moins un responsable de semestre
|
|
"text_suggest_options": {
|
|
"script": url_for(
|
|
"users.get_user_list_xml", scodoc_dept=g.scodoc_dept
|
|
)
|
|
+ "?", # "Users/get_user_list_xml?",
|
|
"varname": "start",
|
|
"json": False,
|
|
"noresults": "Valeur invalide !",
|
|
"timeout": 60000,
|
|
},
|
|
},
|
|
)
|
|
for index, field in enumerate(resp_fields)
|
|
],
|
|
(
|
|
"titre",
|
|
{
|
|
"size": 40,
|
|
"title": "Nom de ce semestre",
|
|
"explanation": f"""n'indiquez pas les dates, ni le semestre, ni la modalité dans
|
|
le titre: ils seront automatiquement ajoutés <input type="button"
|
|
value="remettre titre par défaut" onClick="document.tf.titre.value='{
|
|
_default_sem_title(formation)}';"/>""",
|
|
"allow_null": False,
|
|
},
|
|
),
|
|
(
|
|
"modalite",
|
|
{
|
|
"input_type": "menu",
|
|
"title": "Modalité",
|
|
"allowed_values": modalites_abbrv,
|
|
"labels": modalites_titles,
|
|
},
|
|
),
|
|
]
|
|
modform.append(
|
|
(
|
|
"semestre_id",
|
|
{
|
|
"input_type": "menu",
|
|
"title": "Semestre dans la formation",
|
|
"allowed_values": semestre_id_list,
|
|
"labels": semestre_id_labels,
|
|
"explanation": "en BUT, on ne peut pas modifier le semestre après création"
|
|
if is_apc
|
|
else "",
|
|
"attributes": ['onchange="change_semestre_id();"'] if is_apc else "",
|
|
},
|
|
),
|
|
)
|
|
etapes = sco_portal_apogee.get_etapes_apogee_dept()
|
|
# Propose les etapes renvoyées par le portail
|
|
# et ajoute les étapes du semestre qui ne sont pas dans la liste (soit la liste a changé, soit l'étape a été ajoutée manuellement)
|
|
etapes_set = {et[0] for et in etapes}
|
|
if edit:
|
|
for etape_vdi in formsemestre.etapes_apo_vdi():
|
|
if etape_vdi.etape not in etapes_set:
|
|
etapes.append((etape_vdi.etape, "inconnue"))
|
|
modform.append(
|
|
(
|
|
"elt_help_apo",
|
|
{
|
|
"title": "Codes Apogée nécessaires pour inscrire les étudiants et exporter les notes en fin de semestre:",
|
|
"input_type": "separator",
|
|
},
|
|
)
|
|
)
|
|
|
|
mf_manual = {
|
|
"size": 12,
|
|
"template": '<tr%(item_dom_attr)s><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s',
|
|
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
|
|
}
|
|
if etapes:
|
|
mf = {
|
|
"input_type": "menu",
|
|
"allowed_values": [""] + [e[0] for e in etapes],
|
|
"labels": ["(aucune)"] + ["%s (%s)" % (e[1], e[0]) for e in etapes],
|
|
"template": '<tr%(item_dom_attr)s><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s',
|
|
}
|
|
else:
|
|
# fallback: code etape libre
|
|
mf = mf_manual
|
|
|
|
for n in range(1, scu.EDIT_NB_ETAPES + 1):
|
|
mf["title"] = f"Etape Apogée ({n})"
|
|
modform.append(("etape_apo" + str(n), mf.copy()))
|
|
modform.append(
|
|
(
|
|
"vdi_apo" + str(n),
|
|
{
|
|
"size": 7,
|
|
"title": "Version (VDI): ",
|
|
"template": '<span class="vdi_label">%(label)s</span><span class="tf-field">%(elem)s</span></td></tr>',
|
|
},
|
|
)
|
|
)
|
|
# Saisie manuelle de l'étape: (seulement si menus)
|
|
if etapes:
|
|
n = 0
|
|
mf = mf_manual
|
|
mf["title"] = "Etape Apogée (+)"
|
|
modform.append(("etape_apo" + str(n), mf.copy()))
|
|
modform.append(
|
|
(
|
|
"vdi_apo" + str(n),
|
|
{
|
|
"size": 7,
|
|
"title": "Version (VDI): ",
|
|
"template": '<span class="vdi_label">%(label)s</span><span class="tf-field">%(elem)s</span></td></tr>',
|
|
"explanation": "saisie manuelle si votre étape n'est pas dans le menu",
|
|
},
|
|
)
|
|
)
|
|
|
|
modform.append(
|
|
(
|
|
"elt_sem_apo",
|
|
{
|
|
"size": 32,
|
|
"title": "Element(s) Apogée:",
|
|
"explanation": "associé(s) au résultat du semestre (ex: VRTW1). Inutile en BUT. Séparés par des virgules.",
|
|
"allow_null": not sco_preferences.get_preference(
|
|
"always_require_apo_sem_codes"
|
|
)
|
|
or (formsemestre and formsemestre.modalite == "EXT"),
|
|
},
|
|
)
|
|
)
|
|
modform.append(
|
|
(
|
|
"elt_annee_apo",
|
|
{
|
|
"size": 32,
|
|
"title": "Element(s) Apogée:",
|
|
"explanation": "associé(s) au résultat de l'année (ex: VRT1A). Séparés par des virgules.",
|
|
"allow_null": not sco_preferences.get_preference(
|
|
"always_require_apo_sem_codes"
|
|
)
|
|
or (formsemestre and formsemestre.modalite == "EXT"),
|
|
},
|
|
)
|
|
)
|
|
if edit:
|
|
formtit = f"""
|
|
<p><a class="stdlink" href="{url_for("notes.formsemestre_edit_uecoefs",
|
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
|
}">Modifier les coefficients des UE capitalisées</a>
|
|
</p>
|
|
<h3>Sélectionner les modules, leurs responsables et les étudiants
|
|
à inscrire:</h3>
|
|
"""
|
|
else:
|
|
formtit = """<h3>Sélectionner les modules et leurs responsables</h3>
|
|
<p class="help">
|
|
Si vous avez des parcours (options), dans un premier temps
|
|
ne sélectionnez que les modules du tronc commun, puis après inscriptions,
|
|
revenez ajouter les modules de parcours en sélectionnant les groupes d'étudiants
|
|
à y inscrire.
|
|
</p>"""
|
|
|
|
modform += [
|
|
(
|
|
"gestion_compensation_lst",
|
|
{
|
|
"input_type": "checkbox",
|
|
"title": "Jurys",
|
|
"allowed_values": ["X"],
|
|
"explanation": "proposer compensations de semestres (parcours DUT)",
|
|
"labels": [""],
|
|
},
|
|
),
|
|
(
|
|
"gestion_semestrielle_lst",
|
|
{
|
|
"input_type": "checkbox",
|
|
"title": "",
|
|
"allowed_values": ["X"],
|
|
"explanation": "formation semestrialisée (jurys avec semestres décalés)",
|
|
"labels": [""],
|
|
},
|
|
),
|
|
]
|
|
if current_user.has_permission(Permission.ScoImplement):
|
|
modform += [
|
|
(
|
|
"resp_can_edit",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"title": "Autorisations",
|
|
"explanation": "Autoriser le directeur des études à modifier ce semestre",
|
|
},
|
|
)
|
|
]
|
|
modform += [
|
|
(
|
|
"resp_can_change_ens",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"title": "",
|
|
"explanation": "Autoriser le directeur des études à modifier les enseignants",
|
|
},
|
|
),
|
|
(
|
|
"ens_can_edit_eval",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"title": "",
|
|
"explanation": """Autoriser tous les enseignants associés
|
|
à un module à y créer des évaluations""",
|
|
},
|
|
),
|
|
(
|
|
"bul_bgcolor",
|
|
{
|
|
"size": 8,
|
|
"title": "Couleur fond des bulletins",
|
|
"explanation": "version web seulement (ex: #ffeeee)",
|
|
"validator": lambda val, _: len(val) < SHORT_STR_LEN,
|
|
},
|
|
),
|
|
(
|
|
"bul_publish_xml_lst",
|
|
{
|
|
"input_type": "checkbox",
|
|
"title": "Publication",
|
|
"allowed_values": ["X"],
|
|
"explanation": "publier le bulletin sur le portail étudiants",
|
|
"labels": [""],
|
|
},
|
|
),
|
|
(
|
|
"block_moyennes",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"title": "Bloquer moyennes",
|
|
"explanation": "empêcher le calcul des moyennes d'UE et générale.",
|
|
},
|
|
),
|
|
(
|
|
"block_moyenne_generale",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"title": "Pas de moyenne générale",
|
|
"explanation": "ne pas calculer la moyenne générale indicative (en BUT)",
|
|
},
|
|
),
|
|
]
|
|
# Choix des parcours
|
|
if is_apc:
|
|
ref_comp = formation.referentiel_competence
|
|
if ref_comp:
|
|
modform += [
|
|
(
|
|
"parcours",
|
|
{
|
|
"input_type": "checkbox",
|
|
"vertical": True,
|
|
"dom_id": "tf_module_parcours",
|
|
"labels": [parcour.libelle for parcour in ref_comp.parcours],
|
|
"allowed_values": [
|
|
str(parcour.id) for parcour in ref_comp.parcours
|
|
],
|
|
"explanation": """Parcours proposés dans ce semestre.
|
|
Cocher tous les parcours est exactement équivalent à n'en cocher aucun:
|
|
par exemple, pour un semestre de "tronc commun", on peut ne pas indiquer de parcours.
|
|
Si aucun parcours n'est coché, toutes les UEs du
|
|
programme seront donc considérées, quel que soit leur parcours.
|
|
""",
|
|
},
|
|
)
|
|
]
|
|
if edit:
|
|
initvalues["parcours"] = [
|
|
str(parcour.id) for parcour in formsemestre.get_parcours_apc()
|
|
]
|
|
else:
|
|
modform += [
|
|
(
|
|
"parcours",
|
|
{
|
|
"input_type": "separator",
|
|
"title": f"""<span class="fontred">{scu.EMO_WARNING }
|
|
Pas de parcours:
|
|
<a class="stdlink" href="{ url_for('notes.ue_table',
|
|
scodoc_dept=g.scodoc_dept, formation_id=formation.id)
|
|
}">vérifier la formation</a>
|
|
</span>""",
|
|
},
|
|
)
|
|
]
|
|
|
|
# Choix des modules
|
|
modform += [
|
|
(
|
|
"sep",
|
|
{
|
|
"input_type": "separator",
|
|
"title": "",
|
|
"template": f"</table>{formtit}<table>",
|
|
},
|
|
),
|
|
]
|
|
|
|
nbmod = 0
|
|
|
|
for semestre_id in semestre_ids:
|
|
if is_apc:
|
|
# pour restreindre l'édition aux modules du semestre sélectionné
|
|
tr_class = f'class="sem{semestre_id}"'
|
|
else:
|
|
tr_class = ""
|
|
if edit:
|
|
templ_sep = f"""<tr {tr_class}><td>%(label)s</td><td><b>Responsable</b></td><td><b>Inscrire</b></td></tr>"""
|
|
else:
|
|
templ_sep = (
|
|
f"""<tr {tr_class}><td>%(label)s</td><td><b>Responsable</b></td></tr>"""
|
|
)
|
|
modform.append(
|
|
(
|
|
"sep",
|
|
{
|
|
"input_type": "separator",
|
|
"title": f"<b>Semestre {semestre_id}</b>",
|
|
"template": templ_sep,
|
|
},
|
|
)
|
|
)
|
|
for mod in modules:
|
|
if mod.semestre_id == semestre_id and (
|
|
(not edit) # creation => tous modules
|
|
or (not is_apc) # pas BUT, on peut mixer les semestres
|
|
or (semestre_id == formsemestre.semestre_id) # module du semestre
|
|
or (mod.id in module_ids_set) # module déjà présent
|
|
):
|
|
nbmod += 1
|
|
if edit:
|
|
select_name = f"{mod.id}!group_id"
|
|
|
|
def opt_selected(gid):
|
|
if gid == vals.get(select_name):
|
|
return "selected"
|
|
else:
|
|
return ""
|
|
|
|
if mod.id in module_ids_set:
|
|
# pas de menu inscription si le module est déjà présent
|
|
disabled = "disabled"
|
|
else:
|
|
disabled = ""
|
|
fcg = f'<select name="{select_name}" {disabled}>'
|
|
default_group_id = sco_groups.get_default_group(formsemestre.id)
|
|
fcg += f"""<option value="{default_group_id}" {
|
|
opt_selected(default_group_id)}>Tous</option>"""
|
|
|
|
fcg += f'<option value="" {opt_selected("")}>Aucun</option>'
|
|
for partition in formsemestre.partitions:
|
|
if partition.partition_name is not None:
|
|
for group in partition.groups:
|
|
# Si le module n'est associé qu'à un parcours, propose d'y inscrire les étudiants directement
|
|
if (
|
|
partition.partition_name == scu.PARTITION_PARCOURS
|
|
and len(mod.parcours) == 1
|
|
and group.group_name == mod.parcours[0].code
|
|
):
|
|
selected = "selected"
|
|
else:
|
|
selected = opt_selected(group.id)
|
|
# print(
|
|
# f"{partition.partition_name} {group.group_name} {selected}"
|
|
# )
|
|
fcg += f"""<option value="{group.id}" {selected
|
|
}>{partition.partition_name} {group.group_name}</option>"""
|
|
fcg += "</select>"
|
|
itemtemplate = f"""<tr {tr_class}>
|
|
<td class="tf-fieldlabel">%(label)s</td>
|
|
<td class="tf-field">%(elem)s</td>
|
|
<td>{fcg}</td>
|
|
</tr>"""
|
|
else:
|
|
itemtemplate = f"""<tr {tr_class}>
|
|
<td class="tf-fieldlabel">%(label)s</td>
|
|
<td class="tf-field">%(elem)s</td>
|
|
</tr>"""
|
|
modform.append(
|
|
(
|
|
"MI" + str(mod.id),
|
|
{
|
|
"input_type": "text_suggest",
|
|
"size": 50,
|
|
"withcheckbox": True,
|
|
"title": "%s %s" % (mod.code or "", mod.titre or ""),
|
|
"allowed_values": allowed_user_names,
|
|
"template": itemtemplate,
|
|
"text_suggest_options": {
|
|
"script": url_for(
|
|
"users.get_user_list_xml", scodoc_dept=g.scodoc_dept
|
|
)
|
|
+ "?",
|
|
"varname": "start",
|
|
"json": False,
|
|
"noresults": "Valeur invalide !",
|
|
"timeout": 60000,
|
|
},
|
|
},
|
|
)
|
|
)
|
|
if nbmod == 0:
|
|
modform.append(
|
|
(
|
|
"sep",
|
|
{
|
|
"input_type": "separator",
|
|
"title": "aucun module dans cette formation !!!",
|
|
},
|
|
)
|
|
)
|
|
if edit:
|
|
submitlabel = "Modifier ce semestre"
|
|
else:
|
|
submitlabel = "Créer ce semestre de formation"
|
|
#
|
|
# Etapes:
|
|
if edit:
|
|
n = 1
|
|
for etape_vdi in formsemestre.etapes_apo_vdi():
|
|
initvalues["etape_apo" + str(n)] = etape_vdi.etape
|
|
initvalues["vdi_apo" + str(n)] = etape_vdi.vdi
|
|
n += 1
|
|
#
|
|
initvalues["gestion_compensation"] = initvalues.get("gestion_compensation", False)
|
|
if initvalues["gestion_compensation"]:
|
|
initvalues["gestion_compensation_lst"] = ["X"]
|
|
else:
|
|
initvalues["gestion_compensation_lst"] = []
|
|
if vals.get("tf_submitted", False) and "gestion_compensation_lst" not in vals:
|
|
vals["gestion_compensation_lst"] = []
|
|
|
|
initvalues["gestion_semestrielle"] = initvalues.get("gestion_semestrielle", False)
|
|
if initvalues["gestion_semestrielle"]:
|
|
initvalues["gestion_semestrielle_lst"] = ["X"]
|
|
else:
|
|
initvalues["gestion_semestrielle_lst"] = []
|
|
if vals.get("tf_submitted", False) and "gestion_semestrielle_lst" not in vals:
|
|
vals["gestion_semestrielle_lst"] = []
|
|
|
|
initvalues["bul_hide_xml"] = initvalues.get("bul_hide_xml", False)
|
|
if not initvalues["bul_hide_xml"]:
|
|
initvalues["bul_publish_xml_lst"] = ["X"]
|
|
else:
|
|
initvalues["bul_publish_xml_lst"] = []
|
|
if vals.get("tf_submitted", False) and "bul_publish_xml_lst" not in vals:
|
|
vals["bul_publish_xml_lst"] = []
|
|
|
|
#
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
vals,
|
|
modform,
|
|
submitlabel=submitlabel,
|
|
cancelbutton="Annuler",
|
|
top_buttons=True,
|
|
initvalues=initvalues,
|
|
)
|
|
msg = ""
|
|
if tf[0] == 1:
|
|
# check dates
|
|
if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
|
|
msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
|
|
|
|
if (
|
|
sco_preferences.get_preference("always_require_apo_sem_codes")
|
|
and not any(
|
|
[tf[2]["etape_apo" + str(n)] for n in range(0, scu.EDIT_NB_ETAPES + 1)]
|
|
)
|
|
# n'impose pas d'Apo pour les sem. extérieurs
|
|
and ((formsemestre is None) or formsemestre.modalite != "EXT")
|
|
):
|
|
msg = '<ul class="tf-msg"><li class="tf-msg">Code étape Apogée manquant</li></ul>'
|
|
|
|
if tf[0] == 0 or msg:
|
|
return f"""<p>Formation <a class="discretelink" href="{
|
|
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept,
|
|
formation_id=formation.id)
|
|
}"><em>{formation.titre}</em> ({formation.acronyme}), version {
|
|
formation.version}, code {formation.formation_code}</a>
|
|
</p>
|
|
{msg}
|
|
{tf[1]}
|
|
"""
|
|
elif tf[0] == -1:
|
|
return "<h4>annulation</h4>"
|
|
else:
|
|
if tf[2]["gestion_compensation_lst"]:
|
|
tf[2]["gestion_compensation"] = True
|
|
else:
|
|
tf[2]["gestion_compensation"] = False
|
|
if tf[2]["gestion_semestrielle_lst"]:
|
|
tf[2]["gestion_semestrielle"] = True
|
|
else:
|
|
tf[2]["gestion_semestrielle"] = False
|
|
if tf[2]["bul_publish_xml_lst"]:
|
|
tf[2]["bul_hide_xml"] = False
|
|
else:
|
|
tf[2]["bul_hide_xml"] = True
|
|
# remap les identifiants de responsables:
|
|
for field in resp_fields:
|
|
tf[2][field] = User.get_user_id_from_nomplogin(tf[2][field])
|
|
tf[2]["responsables"] = []
|
|
for field in resp_fields:
|
|
if tf[2][field]:
|
|
tf[2]["responsables"].append(tf[2][field])
|
|
for module_id in tf[2]["tf-checked"]:
|
|
mod_resp_id = User.get_user_id_from_nomplogin(tf[2][module_id])
|
|
if mod_resp_id is None:
|
|
# Si un module n'a pas de responsable (ou inconnu),
|
|
# l'affecte au 1er directeur des etudes:
|
|
mod_resp_id = tf[2]["responsable_id"]
|
|
tf[2][module_id] = mod_resp_id
|
|
|
|
# etapes:
|
|
tf[2]["etapes"] = []
|
|
if etapes: # menus => case supplementaire pour saisie manuelle, indicée 0
|
|
start_i = 0
|
|
else:
|
|
start_i = 1
|
|
for n in range(start_i, scu.EDIT_NB_ETAPES + 1):
|
|
tf[2]["etapes"].append(
|
|
ApoEtapeVDI(
|
|
etape=tf[2]["etape_apo" + str(n)], vdi=tf[2]["vdi_apo" + str(n)]
|
|
)
|
|
)
|
|
# Modules sélectionnés:
|
|
# (retire le "MI" du début du nom de champs)
|
|
module_ids_checked = [int(x[2:]) for x in tf[2]["tf-checked"]]
|
|
_formsemestre_check_ue_bonus_unicity(module_ids_checked)
|
|
if not edit:
|
|
if is_apc:
|
|
_formsemestre_check_module_list(
|
|
module_ids_checked, tf[2]["semestre_id"]
|
|
)
|
|
# création du semestre
|
|
formsemestre_id = sco_formsemestre.do_formsemestre_create(tf[2])
|
|
# création des modules
|
|
for module_id in module_ids_checked:
|
|
modargs = {
|
|
"module_id": module_id,
|
|
"formsemestre_id": formsemestre_id,
|
|
"responsable_id": tf[2][f"MI{module_id}"],
|
|
}
|
|
_ = sco_moduleimpl.do_moduleimpl_create(modargs)
|
|
else:
|
|
# Modification du semestre:
|
|
# on doit creer les modules nouvellement selectionnés
|
|
# modifier ceux à modifier, et DETRUIRE ceux qui ne sont plus selectionnés.
|
|
# Note: la destruction échouera s'il y a des objets dépendants
|
|
# (eg des évaluations définies)
|
|
module_ids_tocreate = [
|
|
x for x in module_ids_checked if not x in module_ids_existing
|
|
]
|
|
if is_apc:
|
|
_formsemestre_check_module_list(
|
|
module_ids_tocreate, tf[2]["semestre_id"]
|
|
)
|
|
# modules existants à modifier
|
|
module_ids_toedit = [
|
|
x for x in module_ids_checked if x in module_ids_existing
|
|
]
|
|
# modules à détruire
|
|
module_ids_todelete = [
|
|
x for x in module_ids_existing if not x in module_ids_checked
|
|
]
|
|
#
|
|
sco_formsemestre.do_formsemestre_edit(tf[2])
|
|
#
|
|
msg = []
|
|
for module_id in module_ids_tocreate:
|
|
modargs = {
|
|
"module_id": module_id,
|
|
"formsemestre_id": formsemestre.id,
|
|
"responsable_id": tf[2]["MI" + str(module_id)],
|
|
}
|
|
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(modargs)
|
|
mod = sco_edit_module.module_list({"module_id": module_id})[0]
|
|
msg += [
|
|
"création de %s (%s)" % (mod["code"] or "?", mod["titre"] or "?")
|
|
]
|
|
# INSCRIPTIONS DES ETUDIANTS
|
|
log(
|
|
'inscription module: %s = "%s"'
|
|
% ("%s!group_id" % module_id, tf[2]["%s!group_id" % module_id])
|
|
)
|
|
group_id = tf[2]["%s!group_id" % module_id]
|
|
if group_id:
|
|
etudids = [
|
|
x["etudid"] for x in sco_groups.get_group_members(group_id)
|
|
]
|
|
log(
|
|
"inscription module:module_id=%s,moduleimpl_id=%s: %s"
|
|
% (module_id, moduleimpl_id, etudids)
|
|
)
|
|
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
|
|
moduleimpl_id,
|
|
formsemestre.id,
|
|
etudids,
|
|
)
|
|
msg += [
|
|
"inscription de %d étudiants au module %s"
|
|
% (len(etudids), mod["code"] or "(module sans code)")
|
|
]
|
|
else:
|
|
log(
|
|
"inscription module:module_id=%s,moduleimpl_id=%s: aucun etudiant inscrit"
|
|
% (module_id, moduleimpl_id)
|
|
)
|
|
#
|
|
ok, diag = formsemestre_delete_moduleimpls(
|
|
formsemestre.id, module_ids_todelete
|
|
)
|
|
msg += diag
|
|
for module_id in module_ids_toedit:
|
|
moduleimpl_id = sco_moduleimpl.moduleimpl_list(
|
|
formsemestre_id=formsemestre.id, module_id=module_id
|
|
)[0]["moduleimpl_id"]
|
|
modargs = {
|
|
"moduleimpl_id": moduleimpl_id,
|
|
"module_id": module_id,
|
|
"formsemestre_id": formsemestre.id,
|
|
"responsable_id": tf[2]["MI" + str(module_id)],
|
|
}
|
|
sco_moduleimpl.do_moduleimpl_edit(
|
|
modargs, formsemestre_id=formsemestre.id
|
|
)
|
|
mod = sco_edit_module.module_list({"module_id": module_id})[0]
|
|
# --- Association des parcours
|
|
if formsemestre is None:
|
|
formsemestre = FormSemestre.query.get(formsemestre_id)
|
|
if "parcours" in tf[2]:
|
|
formsemestre.parcours = [
|
|
ApcParcours.query.get(int(parcour_id_str))
|
|
for parcour_id_str in tf[2]["parcours"]
|
|
]
|
|
db.session.add(formsemestre)
|
|
db.session.commit()
|
|
# --- Crée ou met à jour les groupes de parcours BUT
|
|
formsemestre.setup_parcours_groups()
|
|
# peut être nécessaire dans certains cas:
|
|
formsemestre.update_inscriptions_parcours_from_groups()
|
|
# --- Fin
|
|
if edit:
|
|
if msg:
|
|
return f"""
|
|
<div class="ue_warning"><span>Attention !<ul>
|
|
<li>
|
|
{"</li><li>".join(msg)}
|
|
</li>
|
|
</ul></span>
|
|
</div>
|
|
{"<p>Modification effectuée</p>" if ok
|
|
else "<p>Modules non modifiés</p>"
|
|
}
|
|
<a class="stdlink" href="{
|
|
url_for('notes.formsemestre_status',
|
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
|
}">retour au tableau de bord</a>
|
|
"""
|
|
else:
|
|
flash("Semestre modifié")
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
check_parcours=0,
|
|
)
|
|
)
|
|
else:
|
|
flash("Nouveau semestre créé")
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
)
|
|
)
|
|
|
|
|
|
def _formsemestre_check_module_list(module_ids, semestre_idx):
|
|
"""En APC: Vérifie que tous les modules de la liste
|
|
sont dans le semestre indiqué.
|
|
Sinon, raise ScoValueError.
|
|
"""
|
|
# vérification de la cohérence / modules / semestre
|
|
mod_sems_idx = {
|
|
Module.query.get_or_404(module_id).ue.semestre_idx for module_id in module_ids
|
|
}
|
|
if mod_sems_idx and mod_sems_idx != {semestre_idx}:
|
|
modules = [Module.query.get_or_404(module_id) for module_id in module_ids]
|
|
log(
|
|
f"""_formsemestre_check_module_list:
|
|
{chr(10).join( str(module) + " " + str(module.ue) for module in modules )}
|
|
"""
|
|
)
|
|
for module in modules:
|
|
log(
|
|
f"{module.code}\tsemestre_id={module.semestre_id}\tue.semestre_idx={module.ue.semestre_idx}"
|
|
)
|
|
raise ScoValueError(
|
|
f"Les modules sélectionnés ne sont pas tous dans le semestre choisi (S{semestre_idx}) !",
|
|
dest_url="javascript:history.back();",
|
|
)
|
|
|
|
|
|
def _formsemestre_check_ue_bonus_unicity(module_ids):
|
|
"""Vérifie qu'il n'y a qu'une seule UE bonus associée aux modules choisis"""
|
|
ues = [Module.query.get_or_404(module_id).ue for module_id in module_ids]
|
|
ues_bonus = {ue.id for ue in ues if ue.type == codes_cursus.UE_SPORT}
|
|
if len(ues_bonus) > 1:
|
|
raise ScoValueError(
|
|
"""Les modules de bonus sélectionnés ne sont pas tous dans la même UE bonus.
|
|
Changez la sélection ou modifiez la structure du programme de formation.""",
|
|
dest_url="javascript:history.back();",
|
|
)
|
|
|
|
|
|
def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
|
|
"""Delete moduleimpls
|
|
module_ids_to_del: list of module_id (warning: not moduleimpl)
|
|
Moduleimpls must have no associated evaluations.
|
|
"""
|
|
ok = True
|
|
msg = []
|
|
for module_id in module_ids_to_del:
|
|
module = Module.query.get(module_id)
|
|
if module is None:
|
|
continue # ignore invalid ids
|
|
modimpls = ModuleImpl.query.filter_by(
|
|
formsemestre_id=formsemestre_id, module_id=module_id
|
|
)
|
|
for modimpl in modimpls:
|
|
nb_evals = modimpl.evaluations.count()
|
|
if nb_evals > 0:
|
|
msg += [
|
|
f"""<b>impossible de supprimer {module.code} ({module.titre or ""})
|
|
car il y a {nb_evals} évaluations définies
|
|
(<a href="{
|
|
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
|
}" class="stdlink">supprimez-les d\'abord</a>)</b>"""
|
|
]
|
|
ok = False
|
|
else:
|
|
msg += [f"""suppression de {module.code} ({module.titre or ""})"""]
|
|
db.session.delete(modimpl)
|
|
if ok:
|
|
db.session.commit()
|
|
else:
|
|
db.session.rollback()
|
|
return ok, msg
|
|
|
|
|
|
def formsemestre_clone(formsemestre_id):
|
|
"""
|
|
Formulaire clonage d'un semestre
|
|
"""
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
# Liste des enseignants avec forme pour affichage / saisie avec suggestion
|
|
user_list = sco_users.get_user_list()
|
|
uid2display = {} # user_name : forme pour affichage = "NOM Prenom (login)"
|
|
for u in user_list:
|
|
uid2display[u.id] = u.get_nomplogin()
|
|
allowed_user_names = list(uid2display.values()) + [""]
|
|
|
|
initvalues = {
|
|
"formsemestre_id": sem["formsemestre_id"],
|
|
"responsable_id": uid2display.get(
|
|
sem["responsables"][0], sem["responsables"][0]
|
|
),
|
|
}
|
|
|
|
H = [
|
|
html_sco_header.html_sem_header(
|
|
"Copie du semestre",
|
|
javascripts=["libjs/AutoSuggest.js"],
|
|
cssstyles=["css/autosuggest_inquisitor.css"],
|
|
bodyOnLoad="init_tf_form('')",
|
|
),
|
|
"""<p class="help">Cette opération duplique un semestre: on reprend les mêmes modules et responsables. Aucun étudiant n'est inscrit.</p>""",
|
|
]
|
|
|
|
descr = [
|
|
("formsemestre_id", {"input_type": "hidden"}),
|
|
(
|
|
"date_debut",
|
|
{
|
|
"title": "Date de début", # j/m/a
|
|
"input_type": "datedmy",
|
|
"explanation": "j/m/a",
|
|
"size": 9,
|
|
"allow_null": False,
|
|
},
|
|
),
|
|
(
|
|
"date_fin",
|
|
{
|
|
"title": "Date de fin", # j/m/a
|
|
"input_type": "datedmy",
|
|
"explanation": "j/m/a",
|
|
"size": 9,
|
|
"allow_null": False,
|
|
},
|
|
),
|
|
(
|
|
"responsable_id",
|
|
{
|
|
"input_type": "text_suggest",
|
|
"size": 50,
|
|
"title": "Directeur des études",
|
|
"explanation": "taper le début du nom et choisir dans le menu",
|
|
"allowed_values": allowed_user_names,
|
|
"allow_null": False,
|
|
"text_suggest_options": {
|
|
"script": url_for(
|
|
"users.get_user_list_xml", scodoc_dept=g.scodoc_dept
|
|
)
|
|
+ "?",
|
|
"varname": "start",
|
|
"json": False,
|
|
"noresults": "Valeur invalide !",
|
|
"timeout": 60000,
|
|
},
|
|
},
|
|
),
|
|
(
|
|
"clone_evaluations",
|
|
{
|
|
"title": "Copier aussi les évaluations",
|
|
"input_type": "boolcheckbox",
|
|
"explanation": "copie toutes les évaluations, sans les dates (ni les notes!)",
|
|
},
|
|
),
|
|
(
|
|
"clone_partitions",
|
|
{
|
|
"title": "Copier aussi les partitions",
|
|
"input_type": "boolcheckbox",
|
|
"explanation": "copie toutes les partitions (sans les étudiants!)",
|
|
},
|
|
),
|
|
]
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
descr,
|
|
submitlabel="Dupliquer ce semestre",
|
|
cancelbutton="Annuler",
|
|
initvalues=initvalues,
|
|
)
|
|
msg = ""
|
|
if tf[0] == 1:
|
|
# check dates
|
|
if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
|
|
msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
|
|
if tf[0] == 0 or msg:
|
|
return "".join(H) + msg + tf[1] + html_sco_header.sco_footer()
|
|
elif tf[0] == -1: # cancel
|
|
return flask.redirect(
|
|
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
|
)
|
|
else:
|
|
new_formsemestre_id = do_formsemestre_clone(
|
|
formsemestre_id,
|
|
User.get_user_id_from_nomplogin(tf[2]["responsable_id"]),
|
|
tf[2]["date_debut"],
|
|
tf[2]["date_fin"],
|
|
clone_evaluations=tf[2]["clone_evaluations"],
|
|
clone_partitions=tf[2]["clone_partitions"],
|
|
)
|
|
return flask.redirect(
|
|
"formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé"
|
|
% new_formsemestre_id
|
|
)
|
|
|
|
|
|
def do_formsemestre_clone(
|
|
orig_formsemestre_id,
|
|
responsable_id, # new resp.
|
|
date_debut,
|
|
date_fin, # 'dd/mm/yyyy'
|
|
clone_evaluations=False,
|
|
clone_partitions=False,
|
|
):
|
|
"""Clone a semestre: make copy, same modules, same options, same resps, same partitions.
|
|
New dates, responsable_id
|
|
"""
|
|
log(f"cloning orig_formsemestre_id")
|
|
formsemestre_orig: FormSemestre = FormSemestre.query.get_or_404(
|
|
orig_formsemestre_id
|
|
)
|
|
orig_sem = sco_formsemestre.get_formsemestre(orig_formsemestre_id)
|
|
cnx = ndb.GetDBConnexion()
|
|
# 1- create sem
|
|
args = orig_sem.copy()
|
|
del args["formsemestre_id"]
|
|
args["responsables"] = [responsable_id]
|
|
args["date_debut"] = date_debut
|
|
args["date_fin"] = date_fin
|
|
args["etat"] = 1 # non verrouillé
|
|
formsemestre_id = sco_formsemestre.do_formsemestre_create(args)
|
|
log(f"created formsemestre {formsemestre_id}")
|
|
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
|
# 2- create moduleimpls
|
|
mods_orig = sco_moduleimpl.moduleimpl_list(formsemestre_id=orig_formsemestre_id)
|
|
for mod_orig in mods_orig:
|
|
args = mod_orig.copy()
|
|
args["formsemestre_id"] = formsemestre_id
|
|
mid = sco_moduleimpl.do_moduleimpl_create(args)
|
|
# copy notes_modules_enseignants
|
|
ens = sco_moduleimpl.do_ens_list(
|
|
args={"moduleimpl_id": mod_orig["moduleimpl_id"]}
|
|
)
|
|
for e in ens:
|
|
args = e.copy()
|
|
args["moduleimpl_id"] = mid
|
|
sco_moduleimpl.do_ens_create(args)
|
|
# optionally, copy evaluations
|
|
if clone_evaluations:
|
|
for e in Evaluation.query.filter_by(
|
|
moduleimpl_id=mod_orig["moduleimpl_id"]
|
|
):
|
|
# copie en enlevant la date
|
|
new_eval = e.clone(not_copying=("jour", "moduleimpl_id"))
|
|
new_eval.moduleimpl_id = mid
|
|
# Copie les poids APC de l'évaluation
|
|
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
|
|
db.session.commit()
|
|
|
|
# 3- copy uecoefs
|
|
objs = sco_formsemestre.formsemestre_uecoef_list(
|
|
cnx, args={"formsemestre_id": orig_formsemestre_id}
|
|
)
|
|
for obj in objs:
|
|
args = obj.copy()
|
|
args["formsemestre_id"] = formsemestre_id
|
|
_ = sco_formsemestre.formsemestre_uecoef_create(cnx, args)
|
|
|
|
# NB: don't copy notes_formsemestre_custommenu (usually specific)
|
|
|
|
# 4- Copy new style preferences
|
|
prefs = sco_preferences.SemPreferences(orig_formsemestre_id)
|
|
|
|
if orig_formsemestre_id in prefs.base_prefs.prefs:
|
|
for pname in prefs.base_prefs.prefs[orig_formsemestre_id]:
|
|
if not prefs.is_global(pname):
|
|
pvalue = prefs[pname]
|
|
try:
|
|
prefs.base_prefs.set(formsemestre_id, pname, pvalue)
|
|
except ValueError:
|
|
log(
|
|
"do_formsemestre_clone: ignoring old preference %s=%s for %s"
|
|
% (pname, pvalue, formsemestre_id)
|
|
)
|
|
|
|
# 5- Copy formules utilisateur
|
|
objs = sco_compute_moy.formsemestre_ue_computation_expr_list(
|
|
cnx, args={"formsemestre_id": orig_formsemestre_id}
|
|
)
|
|
for obj in objs:
|
|
args = obj.copy()
|
|
args["formsemestre_id"] = formsemestre_id
|
|
_ = sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, args)
|
|
|
|
# 6- Copie les parcours
|
|
formsemestre.parcours = formsemestre_orig.parcours
|
|
db.session.add(formsemestre)
|
|
db.session.commit()
|
|
|
|
# 7- Copy partitions and groups
|
|
if clone_partitions:
|
|
sco_groups_copy.clone_partitions_and_groups(
|
|
orig_formsemestre_id, formsemestre_id
|
|
)
|
|
|
|
return formsemestre_id
|
|
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
|
|
def formsemestre_associate_new_version(
|
|
formsemestre_id,
|
|
other_formsemestre_ids=[],
|
|
dialog_confirmed=False,
|
|
):
|
|
"""Formulaire changement formation d'un semestre"""
|
|
formsemestre_id = int(formsemestre_id)
|
|
other_formsemestre_ids = [int(x) for x in other_formsemestre_ids]
|
|
if not dialog_confirmed:
|
|
# dresse le liste des semestres de la meme formation et version
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
othersems = sco_formsemestre.do_formsemestre_list(
|
|
args={
|
|
"formation_id": formsemestre.formation.id,
|
|
"version": formsemestre.formation.version,
|
|
"etat": "1",
|
|
},
|
|
)
|
|
H = []
|
|
for s in othersems:
|
|
if (
|
|
s["formsemestre_id"] == formsemestre_id
|
|
or s["formsemestre_id"] in other_formsemestre_ids
|
|
):
|
|
checked = 'checked="checked"'
|
|
else:
|
|
checked = ""
|
|
if s["formsemestre_id"] == formsemestre_id:
|
|
disabled = 'disabled="1"'
|
|
else:
|
|
disabled = ""
|
|
H.append(
|
|
f"""<div><input type="checkbox" name="other_formsemestre_ids:list"
|
|
value="{s['formsemestre_id']}" {checked} {disabled}
|
|
>{s['titremois']}</input></div>"""
|
|
)
|
|
|
|
return scu.confirm_dialog(
|
|
f"""<h2>Associer à une nouvelle version de formation non verrouillée ?</h2>
|
|
<p class="help">Le programme pédagogique ("formation") va être dupliqué
|
|
pour que vous puissiez le modifier sans affecter les autres
|
|
semestres. Les autres paramètres (étudiants, notes...) du
|
|
semestre seront inchangés.
|
|
</p>
|
|
<p class="help">Veillez à ne pas abuser de cette possibilité, car créer
|
|
trop de versions de formations va vous compliquer la gestion
|
|
(à vous de garder trace des différences et à ne pas vous
|
|
tromper par la suite...).
|
|
</p>
|
|
<p class="help">Si vous souhaitez créer un programme pour de futurs semestres,
|
|
utilisez plutôt <a class="stdlink" href="{
|
|
url_for('notes.formation_create_new_version',
|
|
scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id
|
|
)}">Créer une nouvelle version</a>.
|
|
</p>
|
|
<div class="othersemlist">
|
|
<p>Si vous voulez associer aussi d'autres semestres à la nouvelle
|
|
version, cochez-les:
|
|
</p>"""
|
|
+ "".join(H)
|
|
+ "</div>",
|
|
OK="Associer ces semestres à une nouvelle version",
|
|
dest_url="",
|
|
cancel_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
),
|
|
parameters={"formsemestre_id": formsemestre_id},
|
|
)
|
|
else:
|
|
do_formsemestres_associate_new_version(
|
|
[formsemestre_id] + other_formsemestre_ids
|
|
)
|
|
flash("Semestre associé à une nouvelle version de la formation")
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
|
|
|
|
def do_formsemestres_associate_new_version(formsemestre_ids):
|
|
"""Cree une nouvelle version de la formation du semestre, et y rattache les semestres.
|
|
Tous les moduleimpl sont ré-associés à la nouvelle formation, ainsi que les decisions de jury
|
|
si elles existent (codes d'UE validées).
|
|
Les semestre doivent tous appartenir à la meme version de la formation
|
|
"""
|
|
log(f"do_formsemestres_associate_new_version {formsemestre_ids}")
|
|
if not formsemestre_ids:
|
|
return
|
|
# Check: tous de la même formation
|
|
assert isinstance(formsemestre_ids[0], int)
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_ids[0])
|
|
formation_id = sem["formation_id"]
|
|
for formsemestre_id in formsemestre_ids[1:]:
|
|
assert isinstance(formsemestre_id, int)
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
if formation_id != sem["formation_id"]:
|
|
raise ScoValueError("les semestres ne sont pas tous de la même formation !")
|
|
|
|
cnx = ndb.GetDBConnexion()
|
|
# New formation:
|
|
(
|
|
formation_id,
|
|
modules_old2new,
|
|
ues_old2new,
|
|
) = sco_formations.formation_create_new_version(formation_id, redirect=False)
|
|
# Log new ues:
|
|
for ue_id in ues_old2new:
|
|
ue = UniteEns.query.get(ue_id)
|
|
new_ue = UniteEns.query.get(ues_old2new[ue_id])
|
|
assert ue.semestre_idx == new_ue.semestre_idx
|
|
log(f"{ue} -> {new_ue}")
|
|
# Log new modules
|
|
for module_id in modules_old2new:
|
|
mod = Module.query.get(module_id)
|
|
new_mod = Module.query.get(modules_old2new[module_id])
|
|
assert mod.semestre_id == new_mod.semestre_id
|
|
log(f"{mod} -> {new_mod}")
|
|
# re-associate
|
|
for formsemestre_id in formsemestre_ids:
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
sem["formation_id"] = formation_id
|
|
sco_formsemestre.do_formsemestre_edit(sem, cnx=cnx, html_quote=False)
|
|
_reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new)
|
|
|
|
cnx.commit()
|
|
|
|
|
|
def _reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new):
|
|
"""Associe les moduleimpls d'un semestre existant à un autre programme
|
|
et met à jour les décisions de jury (validations d'UE).
|
|
"""
|
|
# re-associate moduleimpls to new modules:
|
|
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
|
for mod in modimpls:
|
|
mod["module_id"] = modules_old2new[mod["module_id"]]
|
|
sco_moduleimpl.do_moduleimpl_edit(mod, formsemestre_id=formsemestre_id)
|
|
# Update poids des évaluations
|
|
# les poids associent les évaluations aux UE (qui ont changé d'id)
|
|
for poids in EvaluationUEPoids.query.filter(
|
|
EvaluationUEPoids.evaluation_id == Evaluation.id,
|
|
Evaluation.moduleimpl_id == ModuleImpl.id,
|
|
ModuleImpl.formsemestre_id == formsemestre_id,
|
|
):
|
|
poids.ue_id = ues_old2new[poids.ue_id]
|
|
db.session.add(poids)
|
|
db.session.commit()
|
|
|
|
# update decisions:
|
|
events = sco_etud.scolar_events_list(cnx, args={"formsemestre_id": formsemestre_id})
|
|
for e in events:
|
|
if e["ue_id"]:
|
|
e["ue_id"] = ues_old2new[e["ue_id"]]
|
|
sco_etud.scolar_events_edit(cnx, e)
|
|
validations = sco_cursus_dut.scolar_formsemestre_validation_list(
|
|
cnx, args={"formsemestre_id": formsemestre_id}
|
|
)
|
|
for e in validations:
|
|
if e["ue_id"]:
|
|
e["ue_id"] = ues_old2new[e["ue_id"]]
|
|
# log('e=%s' % e )
|
|
sco_cursus_dut.scolar_formsemestre_validation_edit(cnx, e)
|
|
|
|
|
|
def formsemestre_delete(formsemestre_id):
|
|
"""Delete a formsemestre (affiche avertissements)"""
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
H = [
|
|
html_sco_header.html_sem_header("Suppression du semestre"),
|
|
"""<div class="ue_warning"><span>Attention !</span>
|
|
<p class="help">A n'utiliser qu'en cas d'erreur lors de la saisie d'une formation. Normalement,
|
|
<b>un semestre ne doit jamais être supprimé</b>
|
|
(on perd la mémoire des notes et de tous les événements liés à ce semestre !).
|
|
</p>
|
|
|
|
<p class="help">Tous les modules de ce semestre seront supprimés.
|
|
Ceci n'est possible que si :
|
|
</p>
|
|
<ol>
|
|
<li>aucune décision de jury n'a été entrée dans ce semestre;</li>
|
|
<li>et aucun étudiant de ce semestre ne le compense avec un autre semestre.</li>
|
|
</ol>
|
|
</div>""",
|
|
]
|
|
|
|
evals = sco_evaluation_db.do_evaluation_list_in_formsemestre(formsemestre_id)
|
|
if evals:
|
|
H.append(
|
|
f"""<p class="warning">Attention: il y a {len(evals)} évaluations
|
|
dans ce semestre
|
|
(sa suppression entrainera l'effacement définif des notes) !</p>"""
|
|
)
|
|
submit_label = (
|
|
f"Confirmer la suppression (du semestre et des {len(evals)} évaluations !)"
|
|
)
|
|
else:
|
|
submit_label = "Confirmer la suppression du semestre"
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
(("formsemestre_id", {"input_type": "hidden"}),),
|
|
initvalues=formsemestre.to_dict(),
|
|
submitlabel=submit_label,
|
|
cancelbutton="Annuler",
|
|
)
|
|
if tf[0] == 0:
|
|
if formsemestre_has_decisions_or_compensations(formsemestre_id):
|
|
H.append(
|
|
"""<p><b>Ce semestre ne peut pas être supprimé ! (il y a des décisions de jury ou des compensations par d'autres semestres)</b></p>"""
|
|
)
|
|
else:
|
|
H.append(tf[1])
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
|
elif tf[0] == -1: # cancel
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
else:
|
|
return flask.redirect(
|
|
"formsemestre_delete2?formsemestre_id=" + str(formsemestre_id)
|
|
)
|
|
|
|
|
|
def formsemestre_delete2(formsemestre_id, dialog_confirmed=False):
|
|
"""Delete a formsemestre (confirmation)"""
|
|
# Confirmation dialog
|
|
if not dialog_confirmed:
|
|
return scu.confirm_dialog(
|
|
"""<h2>Vous voulez vraiment supprimer ce semestre ???</h2><p>(opération irréversible)</p>""",
|
|
dest_url="",
|
|
cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id,
|
|
parameters={"formsemestre_id": formsemestre_id},
|
|
)
|
|
# Bon, s'il le faut...
|
|
do_formsemestre_delete(formsemestre_id)
|
|
return flask.redirect(scu.ScoURL() + "?head_message=Semestre%20supprimé")
|
|
|
|
|
|
def formsemestre_has_decisions_or_compensations(formsemestre_id):
|
|
"""True if decision de jury dans ce semestre
|
|
ou bien compensation de ce semestre par d'autre ssemestres.
|
|
"""
|
|
r = ndb.SimpleDictFetch(
|
|
"""SELECT v.id AS formsemestre_validation_id, v.*
|
|
FROM scolar_formsemestre_validation v
|
|
WHERE v.formsemestre_id = %(formsemestre_id)s
|
|
OR v.compense_formsemestre_id = %(formsemestre_id)s""",
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
return r
|
|
|
|
|
|
def do_formsemestre_delete(formsemestre_id):
|
|
"""delete formsemestre, and all its moduleimpls.
|
|
No checks, no warnings: erase all !
|
|
"""
|
|
cnx = ndb.GetDBConnexion()
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
|
|
sco_cache.EvaluationCache.invalidate_sem(formsemestre_id)
|
|
|
|
# --- Destruction des modules de ce semestre
|
|
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
|
for mod in mods:
|
|
# evaluations
|
|
evals = sco_evaluation_db.do_evaluation_list(
|
|
args={"moduleimpl_id": mod["moduleimpl_id"]}
|
|
)
|
|
for e in evals:
|
|
ndb.SimpleQuery(
|
|
"DELETE FROM notes_notes WHERE evaluation_id=%(evaluation_id)s",
|
|
e,
|
|
)
|
|
ndb.SimpleQuery(
|
|
"DELETE FROM notes_notes_log WHERE evaluation_id=%(evaluation_id)s",
|
|
e,
|
|
)
|
|
ndb.SimpleQuery(
|
|
"DELETE FROM notes_evaluation WHERE id=%(evaluation_id)s",
|
|
e,
|
|
)
|
|
|
|
sco_moduleimpl.do_moduleimpl_delete(
|
|
mod["moduleimpl_id"], formsemestre_id=formsemestre_id
|
|
)
|
|
# --- Desinscription des etudiants
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
|
req = "DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=%(formsemestre_id)s"
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Suppression des evenements
|
|
req = "DELETE FROM scolar_events WHERE formsemestre_id=%(formsemestre_id)s"
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Suppression des appreciations
|
|
req = "DELETE FROM notes_appreciations WHERE formsemestre_id=%(formsemestre_id)s"
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Supression des validations (!!!)
|
|
req = "DELETE FROM scolar_formsemestre_validation WHERE formsemestre_id=%(formsemestre_id)s"
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Supression des references a ce semestre dans les compensations:
|
|
req = "UPDATE scolar_formsemestre_validation SET compense_formsemestre_id=NULL WHERE compense_formsemestre_id=%(formsemestre_id)s"
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Suppression des autorisations
|
|
req = "DELETE FROM scolar_autorisation_inscription WHERE origin_formsemestre_id=%(formsemestre_id)s"
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Suppression des coefs d'UE capitalisées
|
|
req = "DELETE FROM notes_formsemestre_uecoef WHERE formsemestre_id=%(formsemestre_id)s"
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Suppression des item du menu custom
|
|
req = "DELETE FROM notes_formsemestre_custommenu WHERE formsemestre_id=%(formsemestre_id)s"
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Suppression des formules
|
|
req = "DELETE FROM notes_formsemestre_ue_computation_expr WHERE formsemestre_id=%(formsemestre_id)s"
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Suppression des preferences
|
|
req = "DELETE FROM sco_prefs WHERE formsemestre_id=%(formsemestre_id)s"
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Suppression des groupes et partitions
|
|
req = """DELETE FROM group_membership
|
|
WHERE group_id IN
|
|
(SELECT gm.group_id FROM group_membership gm, partition p, group_descr gd
|
|
WHERE gm.group_id = gd.id AND gd.partition_id = p.id
|
|
AND p.formsemestre_id=%(formsemestre_id)s)
|
|
"""
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
req = """DELETE FROM group_descr
|
|
WHERE id IN
|
|
(SELECT gd.id FROM group_descr gd, partition p
|
|
WHERE gd.partition_id = p.id
|
|
AND p.formsemestre_id=%(formsemestre_id)s)
|
|
"""
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
req = "DELETE FROM partition WHERE formsemestre_id=%(formsemestre_id)s"
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Responsables
|
|
req = """DELETE FROM notes_formsemestre_responsables
|
|
WHERE formsemestre_id=%(formsemestre_id)s"""
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Etapes
|
|
req = """DELETE FROM notes_formsemestre_etapes
|
|
WHERE formsemestre_id=%(formsemestre_id)s"""
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Dispenses d'UE
|
|
req = """DELETE FROM "dispenseUE" WHERE formsemestre_id=%(formsemestre_id)s"""
|
|
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
|
# --- Destruction du semestre
|
|
sco_formsemestre._formsemestreEditor.delete(cnx, formsemestre_id)
|
|
|
|
# news
|
|
ScolarNews.add(
|
|
typ=ScolarNews.NEWS_SEM,
|
|
obj=formsemestre_id,
|
|
text="Suppression du semestre %(titre)s" % sem,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
def formsemestre_edit_options(formsemestre_id):
|
|
"""dialog to change formsemestre options
|
|
(accessible par ScoImplement ou dir. etudes)
|
|
"""
|
|
log("formsemestre_edit_options")
|
|
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
|
if not ok:
|
|
return err
|
|
return sco_preferences.SemPreferences(formsemestre_id).edit(categories=["bul"])
|
|
|
|
|
|
def formsemestre_change_lock(formsemestre_id) -> None:
|
|
"""Change etat (verrouille si ouvert, déverrouille si fermé)
|
|
nota: etat (1 ouvert, 0 fermé)
|
|
"""
|
|
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
|
if not ok:
|
|
return err
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
etat = not sem["etat"]
|
|
|
|
args = {"formsemestre_id": formsemestre_id, "etat": etat}
|
|
sco_formsemestre.do_formsemestre_edit(args)
|
|
|
|
|
|
def formsemestre_change_publication_bul(
|
|
formsemestre_id, dialog_confirmed=False, redirect=True
|
|
):
|
|
"""Change etat publication bulletins sur portail"""
|
|
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
|
if not ok:
|
|
return err
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
etat = not sem["bul_hide_xml"]
|
|
|
|
if not dialog_confirmed:
|
|
if etat:
|
|
msg = "non"
|
|
else:
|
|
msg = ""
|
|
return scu.confirm_dialog(
|
|
"<h2>Confirmer la %s publication des bulletins ?</h2>" % msg,
|
|
helpmsg="""Il est parfois utile de désactiver la diffusion des bulletins,
|
|
par exemple pendant la tenue d'un jury ou avant harmonisation des notes.
|
|
<br>
|
|
Ce réglage n'a d'effet que si votre établissement a interfacé ScoDoc et un portail étudiant.
|
|
""",
|
|
dest_url="",
|
|
cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id,
|
|
parameters={"bul_hide_xml": etat, "formsemestre_id": formsemestre_id},
|
|
)
|
|
|
|
args = {"formsemestre_id": formsemestre_id, "bul_hide_xml": etat}
|
|
sco_formsemestre.do_formsemestre_edit(args)
|
|
if redirect:
|
|
return flask.redirect(
|
|
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
|
)
|
|
return None
|
|
|
|
|
|
def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
|
"""Changement manuel des coefficients des UE capitalisées."""
|
|
from app.scodoc import notes_table
|
|
|
|
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
|
if not ok:
|
|
return err
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
|
|
footer = html_sco_header.sco_footer()
|
|
help = """<p class="help">
|
|
Seuls les modules ont un coefficient. Cependant, il est nécessaire d'affecter un coefficient aux UE capitalisée pour pouvoir les prendre en compte dans la moyenne générale.
|
|
</p>
|
|
<p class="help">ScoDoc calcule normalement le coefficient d'une UE comme la somme des
|
|
coefficients des modules qui la composent.
|
|
</p>
|
|
<p class="help">Dans certains cas, on n'a pas les mêmes modules dans le semestre antérieur
|
|
(capitalisé) et dans le semestre courant, et le coefficient d'UE est alors variable.
|
|
Il est alors possible de forcer la valeur du coefficient d'UE.
|
|
</p>
|
|
<p class="help">
|
|
Indiquez "auto" (ou laisser vide) pour que ScoDoc calcule automatiquement le coefficient,
|
|
ou bien entrez une valeur (nombre réel).
|
|
</p>
|
|
<p class="help">Dans le doute, si le mode auto n'est pas applicable et que tous les étudiants sont inscrits aux mêmes modules de ce semestre, prenez comme coefficient la somme indiquée.
|
|
Sinon, référez vous au programme pédagogique. Les lignes en <font color="red">rouge</font>
|
|
sont à changer.
|
|
</p>
|
|
<p class="warning">Les coefficients indiqués ici ne s'appliquent que pour le traitement des UE capitalisées.
|
|
</p>
|
|
"""
|
|
H = [
|
|
html_sco_header.html_sem_header("Coefficients des UE du semestre"),
|
|
help,
|
|
]
|
|
#
|
|
ues, modimpls = notes_table.get_sem_ues_modimpls(formsemestre_id)
|
|
for ue in ues:
|
|
ue["sum_coefs"] = sum(
|
|
[
|
|
mod["module"]["coefficient"]
|
|
for mod in modimpls
|
|
if mod["module"]["ue_id"] == ue["ue_id"]
|
|
]
|
|
)
|
|
|
|
cnx = ndb.GetDBConnexion()
|
|
|
|
initvalues = {"formsemestre_id": formsemestre_id}
|
|
form = [("formsemestre_id", {"input_type": "hidden"})]
|
|
for ue in ues:
|
|
coefs = sco_formsemestre.formsemestre_uecoef_list(
|
|
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]}
|
|
)
|
|
if coefs:
|
|
initvalues["ue_" + str(ue["ue_id"])] = coefs[0]["coefficient"]
|
|
else:
|
|
initvalues["ue_" + str(ue["ue_id"])] = "auto"
|
|
descr = {
|
|
"size": 10,
|
|
"title": ue["acronyme"],
|
|
"explanation": "somme coefs modules = %s" % ue["sum_coefs"],
|
|
}
|
|
if ue["ue_id"] == err_ue_id:
|
|
descr["dom_id"] = "erroneous_ue"
|
|
form.append(("ue_" + str(ue["ue_id"]), descr))
|
|
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
form,
|
|
submitlabel="Changer les coefficients",
|
|
cancelbutton="Annuler",
|
|
initvalues=initvalues,
|
|
)
|
|
if tf[0] == 0:
|
|
return "\n".join(H) + tf[1] + footer
|
|
elif tf[0] == -1:
|
|
return redirect(
|
|
url_for(
|
|
"notes.formsemestre_editwithmodules",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
else:
|
|
# change values
|
|
# 1- supprime les coef qui ne sont plus forcés
|
|
# 2- modifie ou cree les coefs
|
|
ue_deleted = []
|
|
ue_modified = []
|
|
msg = []
|
|
for ue in ues:
|
|
val = tf[2]["ue_" + str(ue["ue_id"])]
|
|
coefs = sco_formsemestre.formsemestre_uecoef_list(
|
|
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]}
|
|
)
|
|
if val == "" or val == "auto":
|
|
# supprime ce coef (il sera donc calculé automatiquement)
|
|
if coefs:
|
|
ue_deleted.append(ue)
|
|
else:
|
|
try:
|
|
val = float(val)
|
|
if (not coefs) or (coefs[0]["coefficient"] != val):
|
|
ue["coef"] = val
|
|
ue_modified.append(ue)
|
|
except:
|
|
ok = False
|
|
msg.append(
|
|
"valeur invalide (%s) pour le coefficient de l'UE %s"
|
|
% (val, ue["acronyme"])
|
|
)
|
|
|
|
if not ok:
|
|
return (
|
|
"\n".join(H)
|
|
+ "<p><ul><li>%s</li></ul></p>" % "</li><li>".join(msg)
|
|
+ tf[1]
|
|
+ footer
|
|
)
|
|
|
|
# apply modifications
|
|
for ue in ue_modified:
|
|
sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
|
|
cnx, formsemestre_id, ue["ue_id"], ue["coef"]
|
|
)
|
|
for ue in ue_deleted:
|
|
sco_formsemestre.do_formsemestre_uecoef_delete(
|
|
cnx, formsemestre_id, ue["ue_id"]
|
|
)
|
|
|
|
if ue_modified or ue_deleted:
|
|
message = ["""<h3>Modification effectuées</h3>"""]
|
|
if ue_modified:
|
|
message.append("""<h4>Coefs modifiés dans les UE:<h4><ul>""")
|
|
for ue in ue_modified:
|
|
message.append("<li>%(acronyme)s : %(coef)s</li>" % ue)
|
|
message.append("</ul>")
|
|
if ue_deleted:
|
|
message.append("""<h4>Coefs supprimés dans les UE:<h4><ul>""")
|
|
for ue in ue_deleted:
|
|
message.append("<li>%(acronyme)s</li>" % ue)
|
|
message.append("</ul>")
|
|
else:
|
|
message = ["""<h3>Aucune modification</h3>"""]
|
|
sco_cache.invalidate_formsemestre(
|
|
formsemestre_id=formsemestre_id
|
|
) # > modif coef UE cap (modifs notes de _certains_ etudiants)
|
|
|
|
return f"""{html_sco_header.html_sem_header("Coefficients des UE du semestre")}
|
|
{" ".join(message)}
|
|
<p><a class="stdlink" href="{url_for("notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
|
}">Revenir au tableau de bord</a>
|
|
</p>
|
|
{footer}
|
|
"""
|
|
|
|
|
|
# ----- identification externe des sessions (pour SOJA et autres logiciels)
|
|
def get_formsemestre_session_id(sem, F, parcours):
|
|
"""Identifiant de session pour ce semestre
|
|
Obsolete: vooir FormSemestre.session_id() #sco7
|
|
"""
|
|
imputation_dept = sco_preferences.get_preference(
|
|
"ImputationDept", sem["formsemestre_id"]
|
|
)
|
|
if not imputation_dept:
|
|
imputation_dept = sco_preferences.get_preference("DeptName") or ""
|
|
imputation_dept = imputation_dept.upper()
|
|
parcours_type = parcours.NAME
|
|
modalite = sem["modalite"]
|
|
modalite = (
|
|
(modalite or "").replace("FAP", "FA").replace("APP", "FA")
|
|
) # exception pour code Apprentissage
|
|
if sem["semestre_id"] > 0:
|
|
decale = scu.sem_decale_str(sem)
|
|
semestre_id = "S%d" % sem["semestre_id"] + decale
|
|
else:
|
|
semestre_id = F["code_specialite"]
|
|
annee_sco = str(scu.annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"]))
|
|
|
|
return scu.sanitize_string(
|
|
"-".join((imputation_dept, parcours_type, modalite, semestre_id, annee_sco))
|
|
)
|