forked from ScoDoc/ScoDoc
1970 lines
72 KiB
Python
1970 lines
72 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2024 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 flash, g, request, redirect, render_template, url_for
|
|
from flask_login import current_user
|
|
import sqlalchemy as sa
|
|
|
|
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 (
|
|
ApcValidationAnnee,
|
|
ApcValidationRCUE,
|
|
Evaluation,
|
|
FormationModalite,
|
|
FormSemestreUECoef,
|
|
Module,
|
|
ModuleImpl,
|
|
ScoDocSiteConfig,
|
|
ScolarAutorisationInscription,
|
|
ScolarFormSemestreValidation,
|
|
ScolarNews,
|
|
UniteEns,
|
|
)
|
|
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 codes_cursus
|
|
from app.scodoc import sco_edit_module
|
|
from app.scodoc import sco_formsemestre
|
|
from app.scodoc import sco_groups_copy
|
|
from app.scodoc import sco_moduleimpl
|
|
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.acronyme
|
|
|
|
|
|
def formsemestre_createwithmodules():
|
|
"""Page création d'un semestre"""
|
|
H = ["""<h2>Mise en place d'un semestre de formation</h2>"""]
|
|
r = do_formsemestre_createwithmodules()
|
|
if not isinstance(r, str):
|
|
return r # response redirect
|
|
|
|
H.append(r)
|
|
|
|
return render_template(
|
|
"sco_page_dept.j2",
|
|
title="Création d'un semestre",
|
|
content="\n".join(H),
|
|
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
|
cssstyles=["css/autosuggest_inquisitor.css"],
|
|
)
|
|
|
|
|
|
def formsemestre_editwithmodules(formsemestre_id: int):
|
|
"""Page modification semestre"""
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
H = []
|
|
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 render_template(
|
|
"sco_page.j2",
|
|
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
|
cssstyles=["css/autosuggest_inquisitor.css"],
|
|
title="Modification du semestre",
|
|
content="<h2>Modification du semestre</h2>" + "\n".join(H),
|
|
)
|
|
|
|
|
|
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.EditFormSemestre): # 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.EditFormSemestre):
|
|
if not edit:
|
|
# il faut EditFormSemestre 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)}
|
|
try:
|
|
semestre_id = int(vals["semestre_id"])
|
|
except ValueError as exc:
|
|
raise ScoValueError("valeur invalide pour l'indice de semestre") from exc
|
|
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)
|
|
group_tous = formsemestre.get_default_group()
|
|
if group_tous:
|
|
initvalues["edt_promo_id"] = group_tous.edt_id or ""
|
|
|
|
# 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()
|
|
)
|
|
# Elimine les ressources et SAE sauf si déjà dans le semestre
|
|
modules = [
|
|
m
|
|
for m in modules
|
|
if (m.module_type not in (scu.ModuleType.RESSOURCE, scu.ModuleType.SAE))
|
|
or m.id in module_ids_set
|
|
]
|
|
# Pour regroupement des modules par semestres:
|
|
semestre_ids = sorted({mod.semestre_id for mod in modules})
|
|
# Liste des modalités
|
|
modalites_abbrv_titres = {
|
|
modalite.modalite: modalite.titre
|
|
for modalite in FormationModalite.query.order_by(FormationModalite.numero)
|
|
}
|
|
#
|
|
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_titres.keys(),
|
|
"labels": modalites_abbrv_titres.values(),
|
|
},
|
|
),
|
|
(
|
|
"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 "",
|
|
},
|
|
),
|
|
(
|
|
"capacite_accueil",
|
|
{
|
|
"title": "Capacité d'accueil",
|
|
"size": 4,
|
|
"explanation": "nombre max d'inscrits (hors démissionnaires). Laisser vide si pas de limite.",
|
|
"type": "int",
|
|
"allow_null": True,
|
|
},
|
|
),
|
|
]
|
|
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 sem.:",
|
|
"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")
|
|
or (formsemestre and formsemestre.formation.is_apc())
|
|
),
|
|
},
|
|
)
|
|
)
|
|
modform.append(
|
|
(
|
|
"elt_annee_apo",
|
|
{
|
|
"size": 32,
|
|
"title": "Element(s) Apogée anné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"),
|
|
},
|
|
)
|
|
)
|
|
modform.append(
|
|
(
|
|
"elt_passage_apo",
|
|
{
|
|
"size": 32,
|
|
"title": "Element(s) Apogée passage:",
|
|
"explanation": "associé(s) au passage. Séparés par des virgules.",
|
|
"allow_null": True, # toujours optionnel car rarement utilisé
|
|
},
|
|
)
|
|
)
|
|
if ScoDocSiteConfig.get("edt_ics_path"):
|
|
modform.append(
|
|
(
|
|
"edt_id",
|
|
{
|
|
"size": 32,
|
|
"title": "Identifiant EDT",
|
|
"explanation": "optionnel, identifiant sur le logiciel emploi du temps (par défaut, utilise la première étape Apogée).",
|
|
"allow_null": True,
|
|
},
|
|
)
|
|
)
|
|
modform.append(
|
|
(
|
|
"edt_promo_id",
|
|
{
|
|
"size": 32,
|
|
"title": "Identifiant EDT promo",
|
|
"explanation": """optionnel, identifiant du groupe "tous"
|
|
(promotion complète) dans l'emploi du temps.""",
|
|
"allow_null": True,
|
|
},
|
|
)
|
|
)
|
|
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>
|
|
<p><a class="stdlink" href="{url_for("notes.formsemestre_edit_modimpls_codes",
|
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
|
}">Modifier les codes Apogée et emploi du temps des modules</a>
|
|
</p>
|
|
|
|
<p><a class="stdlink" href="{url_for("notes.edit_formsemestre_description",
|
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
|
}">Éditer la description externe du semestre</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.EditFormSemestre):
|
|
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 la passerelle étudiants",
|
|
"labels": [""],
|
|
},
|
|
),
|
|
(
|
|
"block_moyennes",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"title": "Bloquer moyennes",
|
|
"explanation": "empêcher le calcul des moyennes d'UE et générale (en BUT, empèche sa prise en compte dans les jurys annuels)",
|
|
},
|
|
),
|
|
(
|
|
"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:
|
|
# convert and check dates
|
|
tf[2]["date_debut"] = scu.convert_fr_date(tf[2]["date_debut"])
|
|
tf[2]["date_fin"] = scu.convert_fr_date(tf[2]["date_fin"])
|
|
if tf[2]["date_debut"] > 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>'
|
|
|
|
# check capacité accueil si indiquée
|
|
if edit and isinstance(tf[2]["capacite_accueil"], int):
|
|
new_capacite_accueil = tf[2]["capacite_accueil"]
|
|
inscriptions = formsemestre.get_inscrits(etats={scu.INSCRIT, scu.DEF})
|
|
if len(inscriptions) > new_capacite_accueil:
|
|
msg = f"""<ul class="tf-msg"><li class="tf-msg">Capacité d'accueil insuffisante
|
|
(il y a {len(inscriptions)} inscrits non démissionaires)</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:
|
|
if formsemestre:
|
|
return redirect(
|
|
url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
)
|
|
)
|
|
else:
|
|
return redirect(url_for("notes.index_html", scodoc_dept=g.scodoc_dept))
|
|
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:
|
|
resp = User.get_user_from_nomplogin(tf[2][field])
|
|
tf[2][field] = resp.id if resp else -1
|
|
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 = User.get_user_from_nomplogin(tf[2][module_id])
|
|
if mod_resp 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"]
|
|
else:
|
|
mod_resp_id = mod_resp.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.get_formsemestre(formsemestre_id)
|
|
if "parcours" in tf[2]:
|
|
formsemestre.parcours = [
|
|
db.session.get(ApcParcours, int(parcour_id_str))
|
|
for parcour_id_str in tf[2]["parcours"]
|
|
]
|
|
# --- Id edt du groupe par défault
|
|
if "edt_promo_id" in tf[2]:
|
|
group_tous = formsemestre.get_default_group()
|
|
if group_tous:
|
|
group_tous.edt_id = tf[2]["edt_promo_id"]
|
|
db.session.add(group_tous)
|
|
|
|
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 = db.session.get(Module, 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 = [
|
|
"""<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 render_template(
|
|
"sco_page.j2",
|
|
javascripts=["libjs/AutoSuggest.js"],
|
|
cssstyles=["css/autosuggest_inquisitor.css"],
|
|
title="Copie du semestre",
|
|
content="".join(H) + msg + tf[1],
|
|
)
|
|
elif tf[0] == -1: # cancel
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
else:
|
|
resp = User.get_user_from_nomplogin(tf[2]["responsable_id"])
|
|
if not resp:
|
|
raise ScoValueError("id responsable invalide")
|
|
new_formsemestre_id = do_formsemestre_clone(
|
|
formsemestre_id,
|
|
resp,
|
|
tf[2]["date_debut"],
|
|
tf[2]["date_fin"],
|
|
clone_evaluations=tf[2]["clone_evaluations"],
|
|
clone_partitions=tf[2]["clone_partitions"],
|
|
)
|
|
flash("Nouveau semestre créé")
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=new_formsemestre_id,
|
|
)
|
|
)
|
|
|
|
|
|
def do_formsemestre_clone(
|
|
orig_formsemestre_id,
|
|
responsable: User, # 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.
|
|
Clone description.
|
|
New dates, responsable_id
|
|
"""
|
|
log(f"do_formsemestre_clone: {orig_formsemestre_id}")
|
|
formsemestre_orig: FormSemestre = FormSemestre.query.get_or_404(
|
|
orig_formsemestre_id
|
|
)
|
|
# 1- create sem
|
|
args = formsemestre_orig.to_dict()
|
|
del args["formsemestre_id"]
|
|
del args["id"]
|
|
del args["parcours"] # copiés ensuite
|
|
args["responsables"] = [responsable]
|
|
args["date_debut"] = date_debut
|
|
args["date_fin"] = date_fin
|
|
args["etat"] = 1 # non verrouillé
|
|
|
|
formsemestre = FormSemestre.create_formsemestre(args)
|
|
log(f"created formsemestre {formsemestre}")
|
|
# 2- create moduleimpls
|
|
modimpl_orig: ModuleImpl
|
|
for modimpl_orig in formsemestre_orig.modimpls:
|
|
assert isinstance(modimpl_orig, ModuleImpl)
|
|
assert isinstance(modimpl_orig.id, int)
|
|
log(f"cloning {modimpl_orig}")
|
|
args = modimpl_orig.to_dict(with_module=False)
|
|
args["formsemestre_id"] = formsemestre.id
|
|
modimpl_new = ModuleImpl.create_from_dict(args)
|
|
log(f"created ModuleImpl from {args}")
|
|
db.session.flush()
|
|
# copy enseignants
|
|
for ens in modimpl_orig.enseignants:
|
|
modimpl_new.enseignants.append(ens)
|
|
db.session.add(modimpl_new)
|
|
db.session.flush()
|
|
log(f"new moduleimpl.id = {modimpl_new.id}")
|
|
# optionally, copy evaluations
|
|
if clone_evaluations:
|
|
e: Evaluation
|
|
for e in Evaluation.query.filter_by(moduleimpl_id=modimpl_orig.id):
|
|
log(f"cloning evaluation {e.id}")
|
|
# copie en enlevant la date
|
|
args = dict(e.__dict__)
|
|
args.pop("_sa_instance_state")
|
|
args.pop("id")
|
|
args.pop("date_debut", None)
|
|
args.pop("date_fin", None)
|
|
args["moduleimpl_id"] = modimpl_new.id
|
|
new_eval = Evaluation(**args)
|
|
db.session.add(new_eval)
|
|
db.session.commit()
|
|
# Copie les poids APC de l'évaluation
|
|
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
|
|
db.session.commit()
|
|
if clone_evaluations:
|
|
flash(
|
|
"Attention: les évaluations n'ont plus de dates: n'oubliez pas de les indiquer"
|
|
)
|
|
# 3- copy uecoefs
|
|
for ue_coef in FormSemestreUECoef.query.filter_by(
|
|
formsemestre_id=formsemestre_orig.id
|
|
):
|
|
new_ue_coef = FormSemestreUECoef(
|
|
formsemestre_id=formsemestre.id,
|
|
ue_id=ue_coef.ue_id,
|
|
coefficient=ue_coef.coefficient,
|
|
)
|
|
db.session.add(new_ue_coef)
|
|
db.session.flush()
|
|
|
|
# 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(
|
|
f"""do_formsemestre_clone: ignoring old preference {
|
|
pname}={pvalue} for {formsemestre}"""
|
|
)
|
|
|
|
# 5- Copie les parcours
|
|
formsemestre.parcours = formsemestre_orig.parcours
|
|
|
|
# 6- Copy description
|
|
if formsemestre_orig.description:
|
|
formsemestre.description = formsemestre_orig.description.clone()
|
|
|
|
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_delete(formsemestre_id: int) -> str | flask.Response:
|
|
"""Delete a formsemestre (affiche avertissements)"""
|
|
formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
H = [
|
|
"""<h2>Suppression du semestre</h2>
|
|
<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>""",
|
|
]
|
|
evaluations = (
|
|
Evaluation.query.join(ModuleImpl)
|
|
.filter_by(formsemestre_id=formsemestre.id)
|
|
.all()
|
|
)
|
|
if evaluations:
|
|
H.append(
|
|
f"""<p class="warning">Attention: il y a {len(evaluations)} é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(evaluations)} é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:
|
|
has_decisions, message = formsemestre_has_decisions_or_compensations(
|
|
formsemestre
|
|
)
|
|
if has_decisions:
|
|
H.append(
|
|
f"""<p><b>Ce semestre ne peut pas être supprimé !</b></p>
|
|
<p>il y a des décisions de jury ou des compensations par d'autres semestres:
|
|
</p>
|
|
<ul>
|
|
<li>{message}</li>
|
|
</ul>
|
|
"""
|
|
)
|
|
else:
|
|
H.append(tf[1])
|
|
return render_template(
|
|
"sco_page.j2", title="Suppression du semestre", content="\n".join(H)
|
|
)
|
|
|
|
if tf[0] == -1: # cancel
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
return flask.redirect(
|
|
"formsemestre_delete2?formsemestre_id=" + str(formsemestre_id)
|
|
)
|
|
|
|
|
|
def formsemestre_delete2(formsemestre_id, dialog_confirmed=False):
|
|
"""Delete a formsemestre (confirmation)"""
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
# 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=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
),
|
|
parameters={"formsemestre_id": formsemestre.id},
|
|
)
|
|
# Bon, s'il le faut...
|
|
do_formsemestre_delete(formsemestre.id)
|
|
flash("Semestre supprimé !")
|
|
return flask.redirect(url_for("scolar.index_html", scodoc_dept=g.scodoc_dept))
|
|
|
|
|
|
def formsemestre_has_decisions_or_compensations(
|
|
formsemestre: FormSemestre,
|
|
) -> tuple[bool, str]:
|
|
"""True if decision de jury (sem. UE, RCUE, année) émanant de ce semestre
|
|
ou compensation de ce semestre par d'autres semestres
|
|
ou autorisations de passage.
|
|
"""
|
|
# Validations de semestre ou d'UEs
|
|
nb_validations = ScolarFormSemestreValidation.query.filter_by(
|
|
formsemestre_id=formsemestre.id
|
|
).count()
|
|
if nb_validations:
|
|
return True, f"{nb_validations} validations de semestre ou d'UE"
|
|
nb_validations = ScolarFormSemestreValidation.query.filter_by(
|
|
compense_formsemestre_id=formsemestre.id
|
|
).count()
|
|
if nb_validations:
|
|
return True, f"{nb_validations} compensations utilisées dans d'autres semestres"
|
|
# Autorisations d'inscription:
|
|
nb_validations = ScolarAutorisationInscription.query.filter_by(
|
|
origin_formsemestre_id=formsemestre.id
|
|
).count()
|
|
if nb_validations:
|
|
return (
|
|
True,
|
|
f"{nb_validations} autorisations d'inscriptions émanant de ce semestre",
|
|
)
|
|
# Validations d'années BUT
|
|
nb_validations = ApcValidationAnnee.query.filter_by(
|
|
formsemestre_id=formsemestre.id
|
|
).count()
|
|
if nb_validations:
|
|
return True, f"{nb_validations} validations d'année BUT utilisant ce semestre"
|
|
# Validations de RCUEs
|
|
nb_validations = ApcValidationRCUE.query.filter_by(
|
|
formsemestre_id=formsemestre.id
|
|
).count()
|
|
if nb_validations:
|
|
return True, f"{nb_validations} validations de RCUE utilisant ce semestre"
|
|
return False, ""
|
|
|
|
|
|
def do_formsemestre_delete(formsemestre_id: int):
|
|
"""delete formsemestre, and all its moduleimpls.
|
|
No checks, no warnings: erase all !
|
|
"""
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
sco_cache.EvaluationCache.invalidate_sem(formsemestre.id)
|
|
titre_sem = formsemestre.titre_annee()
|
|
# --- Destruction des modules de ce semestre
|
|
for modimpl in formsemestre.modimpls:
|
|
# evaluations
|
|
for e in modimpl.evaluations:
|
|
db.session.execute(
|
|
sa.text(
|
|
"""DELETE FROM notes_notes WHERE evaluation_id=:evaluation_id"""
|
|
),
|
|
{"evaluation_id": e.id},
|
|
)
|
|
db.session.execute(
|
|
sa.text(
|
|
"""DELETE FROM notes_notes_log WHERE evaluation_id=:evaluation_id"""
|
|
),
|
|
{"evaluation_id": e.id},
|
|
)
|
|
|
|
db.session.delete(e)
|
|
db.session.delete(modimpl)
|
|
# --- Desinscription des etudiants
|
|
db.session.execute(
|
|
sa.text(
|
|
"DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=:formsemestre_id"
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
|
|
# --- Suppression des evenements
|
|
db.session.execute(
|
|
sa.text("DELETE FROM scolar_events WHERE formsemestre_id=:formsemestre_id"),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Suppression des appreciations
|
|
db.session.execute(
|
|
sa.text(
|
|
"DELETE FROM notes_appreciations WHERE formsemestre_id=:formsemestre_id"
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Supression des validations (!!!)
|
|
db.session.execute(
|
|
sa.text(
|
|
"DELETE FROM scolar_formsemestre_validation WHERE formsemestre_id=:formsemestre_id"
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Supression des references a ce semestre dans les compensations:
|
|
db.session.execute(
|
|
sa.text(
|
|
"""UPDATE scolar_formsemestre_validation
|
|
SET compense_formsemestre_id=NULL
|
|
WHERE compense_formsemestre_id=:formsemestre_id"""
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Suppression des autorisations
|
|
db.session.execute(
|
|
sa.text(
|
|
"DELETE FROM scolar_autorisation_inscription WHERE origin_formsemestre_id=:formsemestre_id"
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Suppression des coefs d'UE capitalisées
|
|
db.session.execute(
|
|
sa.text(
|
|
"DELETE FROM notes_formsemestre_uecoef WHERE formsemestre_id=:formsemestre_id"
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Suppression des item du menu custom
|
|
db.session.execute(
|
|
sa.text(
|
|
"DELETE FROM notes_formsemestre_custommenu WHERE formsemestre_id=:formsemestre_id"
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Suppression des formules
|
|
db.session.execute(
|
|
sa.text(
|
|
"DELETE FROM notes_formsemestre_ue_computation_expr WHERE formsemestre_id=:formsemestre_id"
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Suppression des preferences
|
|
db.session.execute(
|
|
sa.text("DELETE FROM sco_prefs WHERE formsemestre_id=:formsemestre_id"),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Suppression des groupes et partitions
|
|
db.session.execute(
|
|
sa.text(
|
|
"""
|
|
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)
|
|
"""
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
|
|
db.session.execute(
|
|
sa.text(
|
|
"""
|
|
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)
|
|
"""
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
db.session.execute(
|
|
sa.text("DELETE FROM partition WHERE formsemestre_id=:formsemestre_id"),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Responsables
|
|
db.session.execute(
|
|
sa.text(
|
|
"DELETE FROM notes_formsemestre_responsables WHERE formsemestre_id=:formsemestre_id"
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Etapes
|
|
db.session.execute(
|
|
sa.text(
|
|
"DELETE FROM notes_formsemestre_etapes WHERE formsemestre_id=:formsemestre_id"
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- SemSets
|
|
db.session.execute(
|
|
sa.text(
|
|
"DELETE FROM notes_semset_formsemestre WHERE formsemestre_id=:formsemestre_id"
|
|
),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Dispenses d'UE
|
|
db.session.execute(
|
|
sa.text("""DELETE FROM "dispenseUE" WHERE formsemestre_id=:formsemestre_id"""),
|
|
{"formsemestre_id": formsemestre_id},
|
|
)
|
|
# --- Destruction du semestre
|
|
db.session.delete(formsemestre)
|
|
|
|
# news
|
|
ScolarNews.add(
|
|
typ=ScolarNews.NEWS_SEM,
|
|
obj=formsemestre_id,
|
|
text=f"Suppression du semestre {titre_sem}",
|
|
max_frequency=0,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
def formsemestre_edit_options(formsemestre_id):
|
|
"""dialog to change formsemestre options
|
|
(accessible par EditFormSemestre ou dir. etudes)
|
|
"""
|
|
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
|
if not ok:
|
|
return err
|
|
return sco_preferences.SemPreferences(formsemestre_id).edit(
|
|
categories=["bul", "bul_but_pdf"]
|
|
)
|
|
|
|
|
|
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,
|
|
help_msg="""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."""
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
|
if not ok:
|
|
return err
|
|
|
|
help_msg = """<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 = [
|
|
help_msg,
|
|
]
|
|
#
|
|
ues, modimpls = _get_sem_ues_modimpls(formsemestre)
|
|
sum_coefs_by_ue_id = {}
|
|
for ue in ues:
|
|
sum_coefs_by_ue_id[ue.id] = sum(
|
|
modimpl.module.coefficient or 0.0
|
|
for modimpl in modimpls
|
|
if modimpl.module.ue_id == 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.id}
|
|
)
|
|
if coefs:
|
|
initvalues["ue_" + str(ue.id)] = coefs[0]["coefficient"]
|
|
else:
|
|
initvalues["ue_" + str(ue.id)] = "auto"
|
|
descr = {
|
|
"size": 10,
|
|
"title": ue.acronyme,
|
|
"explanation": f"somme coefs modules = {sum_coefs_by_ue_id[ue.id]}",
|
|
}
|
|
if ue.id == err_ue_id:
|
|
descr["dom_id"] = "erroneous_ue"
|
|
form.append(("ue_" + str(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 render_template(
|
|
"sco_page.j2",
|
|
title="Coefficients des UE du semestre",
|
|
content="<h2>Coefficients des UE du semestre</h2>" + "\n".join(H) + tf[1],
|
|
)
|
|
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: list[tuple[UniteEns, float]] = []
|
|
msg = []
|
|
for ue in ues:
|
|
val = tf[2]["ue_" + str(ue.id)]
|
|
coefs = sco_formsemestre.formsemestre_uecoef_list(
|
|
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": 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_modified.append((ue, val))
|
|
except ValueError:
|
|
ok = False
|
|
msg.append(
|
|
f"valeur invalide ({val}) pour le coefficient de l'UE {ue.acronyme}"
|
|
)
|
|
|
|
if not ok:
|
|
render_template(
|
|
"sco_page.j2",
|
|
title="Coefficients des UE du semestre",
|
|
content="<h2>Coefficients des UE du semestre</h2>"
|
|
+ "\n".join(H)
|
|
+ "<p><ul><li>%s</li></ul></p>" % "</li><li>".join(msg)
|
|
+ tf[1],
|
|
)
|
|
|
|
# apply modifications
|
|
for ue, val in ue_modified:
|
|
sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
|
|
cnx, formsemestre_id, ue.id, val
|
|
)
|
|
for ue in ue_deleted:
|
|
sco_formsemestre.do_formsemestre_uecoef_delete(cnx, formsemestre_id, 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, val in ue_modified:
|
|
message.append(f"<li>{ue.acronyme} : {val}</li>")
|
|
message.append("</ul>")
|
|
if ue_deleted:
|
|
message.append("""<h4>Coefs supprimés dans les UE:<h4><ul>""")
|
|
for ue in ue_deleted:
|
|
message.append(f"<li>{ue.acronyme}</li>")
|
|
message.append("</ul>")
|
|
sco_cache.invalidate_formsemestre(
|
|
formsemestre_id=formsemestre_id
|
|
) # > modif coef UE cap (modifs notes de _certains_ etudiants)
|
|
else:
|
|
message = ["""<h3>Aucune modification</h3>"""]
|
|
|
|
return render_template(
|
|
"sco_page.j2",
|
|
title="Coefficients des UE du semestre",
|
|
content=f"""
|
|
<h2>Coefficients des UE du semestre</h2>
|
|
{" ".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>
|
|
""",
|
|
)
|
|
|
|
|
|
def _get_sem_ues_modimpls(
|
|
formsemestre: FormSemestre,
|
|
) -> tuple[list[UniteEns], list[ModuleImpl]]:
|
|
"""Get liste des UE du semestre (à partir des moduleimpls)
|
|
(utilisé quand on ne peut pas construire nt et faire nt.get_ues_stat_dict())
|
|
"""
|
|
uedict = {}
|
|
modimpls = formsemestre.modimpls.all()
|
|
for modimpl in modimpls:
|
|
if not modimpl.module.ue_id in uedict:
|
|
uedict[modimpl.module.ue.id] = modimpl.module.ue
|
|
ues = list(uedict.values())
|
|
ues.sort(key=lambda u: u.numero)
|
|
return ues, modimpls
|
|
|
|
|
|
# ----- identification externe des sessions (pour SOJA et autres logiciels)
|
|
def get_formsemestre_session_id(sem, code_specialite, 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 = 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 or "", annee_sco)
|
|
)
|
|
)
|