2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2022-01-01 14:49:42 +01:00
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
2020-09-26 16:19:37 +02:00
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
2021-06-16 18:18:32 +02:00
""" Ajout/Modification/Suppression UE
2020-09-26 16:19:37 +02:00
"""
2021-07-31 18:01:10 +02:00
import flask
2022-02-27 10:19:25 +01:00
from flask import flash , render_template , url_for
2021-11-18 00:24:56 +01:00
from flask import g , request
2021-07-31 18:01:10 +02:00
from flask_login import current_user
2022-01-27 00:18:50 +01:00
from app import db
from app import log
2022-03-18 19:55:46 +01:00
from app . models import APO_CODE_STR_LEN , SHORT_STR_LEN
2021-12-13 23:44:13 +01:00
from app . models import Formation , UniteEns , ModuleImpl , Module
2022-04-12 17:12:51 +02:00
from app . models import ScolarNews
2022-01-27 00:18:50 +01:00
from app . models . formations import Matiere
2021-06-19 23:21:37 +02:00
import app . scodoc . notesdb as ndb
import app . scodoc . sco_utils as scu
2021-11-12 22:17:46 +01:00
from app . scodoc . sco_utils import ModuleType
2022-01-27 00:18:50 +01:00
from app . scodoc . TrivialFormulator import TrivialFormulator
2021-06-19 23:21:37 +02:00
from app . scodoc . gen_tables import GenTable
from app . scodoc . sco_permissions import Permission
2022-01-04 20:03:38 +01:00
from app . scodoc . sco_exceptions import (
ScoGenError ,
ScoValueError ,
ScoLockedFormError ,
ScoNonEmptyFormationObject ,
)
2021-06-19 23:21:37 +02:00
from app . scodoc import html_sco_header
2021-07-19 19:53:01 +02:00
from app . scodoc import sco_cache
2021-06-19 23:21:37 +02:00
from app . scodoc import sco_codes_parcours
2021-11-17 10:28:51 +01:00
from app . scodoc import sco_edit_apc
2021-06-19 23:21:37 +02:00
from app . scodoc import sco_edit_matiere
from app . scodoc import sco_edit_module
from app . scodoc import sco_formsemestre
from app . scodoc import sco_groups
from app . scodoc import sco_moduleimpl
from app . scodoc import sco_preferences
from app . scodoc import sco_tag_module
2020-09-26 16:19:37 +02:00
2021-06-16 18:18:32 +02:00
_ueEditor = ndb . EditableTable (
" notes_ue " ,
" ue_id " ,
(
" ue_id " ,
" formation_id " ,
" acronyme " ,
" numero " ,
" titre " ,
2021-11-14 18:09:20 +01:00
" semestre_idx " ,
2021-06-16 18:18:32 +02:00
" type " ,
" ue_code " ,
" ects " ,
" is_external " ,
" code_apogee " ,
" coefficient " ,
2022-01-25 10:45:13 +01:00
" color " ,
2021-06-16 18:18:32 +02:00
) ,
sortkey = " numero " ,
2021-08-11 00:36:07 +02:00
input_formators = {
" type " : ndb . int_null_is_zero ,
2021-10-22 23:09:15 +02:00
" is_external " : ndb . bool_or_str ,
2022-02-28 11:00:24 +01:00
" ects " : ndb . float_null_is_null ,
2021-08-11 00:36:07 +02:00
} ,
2021-06-16 18:18:32 +02:00
output_formators = {
" numero " : ndb . int_null_is_zero ,
" ects " : ndb . float_null_is_null ,
" coefficient " : ndb . float_null_is_zero ,
2021-11-14 18:09:20 +01:00
" semestre_idx " : ndb . int_null_is_null ,
2021-06-16 18:18:32 +02:00
} ,
)
2021-10-17 23:19:26 +02:00
def ue_list ( * args , * * kw ) :
2021-06-16 18:18:32 +02:00
" list UEs "
cnx = ndb . GetDBConnexion ( )
return _ueEditor . list ( cnx , * args , * * kw )
2021-08-20 01:09:55 +02:00
def do_ue_create ( args ) :
2021-06-16 18:18:32 +02:00
" create an ue "
cnx = ndb . GetDBConnexion ( )
# check duplicates
2021-10-17 23:19:26 +02:00
ues = ue_list ( { " formation_id " : args [ " formation_id " ] , " acronyme " : args [ " acronyme " ] } )
2021-06-16 18:18:32 +02:00
if ues :
2021-12-18 17:39:03 +01:00
raise ScoValueError (
f """ Acronyme d ' UE " { args [ ' acronyme ' ] } " déjà utilisé !
( chaque UE doit avoir un acronyme unique dans la formation ) """
)
2022-02-27 10:19:25 +01:00
if not " ue_code " in args :
# évite les conflits de code
while True :
cursor = db . session . execute ( " select notes_newid_ucod(); " )
code = cursor . fetchone ( ) [ 0 ]
if UniteEns . query . filter_by ( ue_code = code ) . count ( ) == 0 :
break
args [ " ue_code " ] = code
2021-06-16 18:18:32 +02:00
# create
2021-12-13 23:44:13 +01:00
ue_id = _ueEditor . create ( cnx , args )
2021-06-16 18:18:32 +02:00
2021-12-13 23:44:13 +01:00
# Invalidate cache: vire les poids de toutes les évals de la formation
for modimpl in ModuleImpl . query . filter (
ModuleImpl . module_id == Module . id , Module . formation_id == args [ " formation_id " ]
) :
modimpl . invalidate_evaluations_poids ( )
formation = Formation . query . get ( args [ " formation_id " ] )
formation . invalidate_module_coefs ( )
2021-06-16 18:18:32 +02:00
# news
2022-02-27 10:19:25 +01:00
ue = UniteEns . query . get ( ue_id )
flash ( f " UE créée (code { ue . ue_code } ) " )
2022-02-14 18:33:36 +01:00
formation = Formation . query . get ( args [ " formation_id " ] )
2022-04-12 17:12:51 +02:00
ScolarNews . add (
typ = ScolarNews . NEWS_FORM ,
obj = args [ " formation_id " ] ,
2022-03-15 10:57:52 +01:00
text = f " Modification de la formation { formation . acronyme } " ,
2022-04-13 00:32:31 +02:00
max_frequency = 10 * 60 ,
2021-06-16 18:18:32 +02:00
)
2022-02-14 18:33:36 +01:00
formation . invalidate_cached_sems ( )
2021-12-13 23:44:13 +01:00
return ue_id
2021-06-16 18:18:32 +02:00
2022-01-04 20:03:38 +01:00
def can_delete_ue ( ue : UniteEns ) - > bool :
""" True si l ' UE n ' est pas utilisée dans des formsemestre
et n ' a pas de module rattachés
"""
# "pas un seul module de cette UE n'a de modimpl...""
return ( not len ( ue . modules . all ( ) ) ) and not any ( m . modimpls . all ( ) for m in ue . modules )
2021-08-30 23:28:15 +02:00
def do_ue_delete ( ue_id , delete_validations = False , force = False ) :
2021-06-19 23:21:37 +02:00
" delete UE and attached matieres (but not modules) "
from app . scodoc import sco_formations
from app . scodoc import sco_parcours_dut
2022-01-04 20:03:38 +01:00
ue = UniteEns . query . get_or_404 ( ue_id )
if not can_delete_ue ( ue ) :
raise ScoNonEmptyFormationObject (
" UE " ,
msg = ue . titre ,
dest_url = url_for (
" notes.ue_table " ,
scodoc_dept = g . scodoc_dept ,
formation_id = ue . formation_id ,
semestre_idx = ue . semestre_idx ,
) ,
)
2021-06-16 18:18:32 +02:00
cnx = ndb . GetDBConnexion ( )
2022-01-04 20:03:38 +01:00
log ( " do_ue_delete: ue_id= %s , delete_validations= %s " % ( ue . id , delete_validations ) )
2021-06-16 18:18:32 +02:00
# check
2022-01-04 20:03:38 +01:00
# if ue_is_locked(ue.id):
# raise ScoLockedFormError()
2021-06-16 18:18:32 +02:00
# Il y a-t-il des etudiants ayant validé cette UE ?
# si oui, propose de supprimer les validations
validations = sco_parcours_dut . scolar_formsemestre_validation_list (
2022-01-04 20:03:38 +01:00
cnx , args = { " ue_id " : ue . id }
2021-06-16 18:18:32 +02:00
)
if validations and not delete_validations and not force :
return scu . confirm_dialog (
" <p> %d étudiants ont validé l ' UE %s ( %s )</p><p>Si vous supprimez cette UE, ces validations vont être supprimées !</p> "
2022-01-04 20:03:38 +01:00
% ( len ( validations ) , ue . acronyme , ue . titre ) ,
2021-06-16 18:18:32 +02:00
dest_url = " " ,
target_variable = " delete_validations " ,
2021-10-22 23:09:15 +02:00
cancel_url = url_for (
" notes.ue_table " ,
scodoc_dept = g . scodoc_dept ,
2022-01-04 20:03:38 +01:00
formation_id = ue . formation_id ,
semestre_idx = ue . semestre_idx ,
2021-10-22 23:09:15 +02:00
) ,
2022-01-04 20:03:38 +01:00
parameters = { " ue_id " : ue . id , " dialog_confirmed " : 1 } ,
2021-06-16 18:18:32 +02:00
)
if delete_validations :
2022-01-04 20:03:38 +01:00
log ( " deleting all validations of UE %s " % ue . id )
2021-06-16 18:18:32 +02:00
ndb . SimpleQuery (
" DELETE FROM scolar_formsemestre_validation WHERE ue_id= %(ue_id)s " ,
2022-01-04 20:03:38 +01:00
{ " ue_id " : ue . id } ,
2021-06-16 18:18:32 +02:00
)
# delete all matiere in this UE
2022-01-04 20:03:38 +01:00
mats = sco_edit_matiere . matiere_list ( { " ue_id " : ue . id } )
2021-06-16 18:18:32 +02:00
for mat in mats :
2021-08-20 01:09:55 +02:00
sco_edit_matiere . do_matiere_delete ( mat [ " matiere_id " ] )
2021-06-16 18:18:32 +02:00
# delete uecoef and events
ndb . SimpleQuery (
" DELETE FROM notes_formsemestre_uecoef WHERE ue_id= %(ue_id)s " ,
2022-01-04 20:03:38 +01:00
{ " ue_id " : ue . id } ,
2021-06-16 18:18:32 +02:00
)
2022-01-04 20:03:38 +01:00
ndb . SimpleQuery ( " DELETE FROM scolar_events WHERE ue_id= %(ue_id)s " , { " ue_id " : ue . id } )
2021-06-16 18:18:32 +02:00
cnx = ndb . GetDBConnexion ( )
2022-01-04 20:03:38 +01:00
_ueEditor . delete ( cnx , ue . id )
# > UE delete + supr. validations associées etudiants (cas compliqué, mais rarement
# utilisé: acceptable de tout invalider):
2021-07-19 19:53:01 +02:00
sco_cache . invalidate_formsemestre ( )
2021-06-16 18:18:32 +02:00
# news
2022-01-04 20:03:38 +01:00
F = sco_formations . formation_list ( args = { " formation_id " : ue . formation_id } ) [ 0 ]
2022-04-12 17:12:51 +02:00
ScolarNews . add (
typ = ScolarNews . NEWS_FORM ,
obj = ue . formation_id ,
2021-06-16 18:18:32 +02:00
text = " Modification de la formation %(acronyme)s " % F ,
2022-04-13 00:32:31 +02:00
max_frequency = 10 * 60 ,
2021-06-16 18:18:32 +02:00
)
#
if not force :
2021-07-31 18:01:10 +02:00
return flask . redirect (
url_for (
2021-10-17 23:19:26 +02:00
" notes.ue_table " ,
2021-07-31 18:01:10 +02:00
scodoc_dept = g . scodoc_dept ,
2022-01-04 20:03:38 +01:00
formation_id = ue . formation_id ,
semestre_idx = ue . semestre_idx ,
2021-07-31 18:01:10 +02:00
)
2021-06-16 18:18:32 +02:00
)
2022-01-04 20:03:38 +01:00
return None
2021-06-16 18:18:32 +02:00
2020-09-26 16:19:37 +02:00
2022-02-02 10:23:40 +01:00
def ue_create ( formation_id = None , default_semestre_idx = None ) :
""" Formulaire création d ' une UE """
return ue_edit (
create = True ,
formation_id = formation_id ,
default_semestre_idx = default_semestre_idx ,
)
2020-09-26 16:19:37 +02:00
2022-02-02 10:23:40 +01:00
def ue_edit ( ue_id = None , create = False , formation_id = None , default_semestre_idx = None ) :
""" Formulaire modification ou création d ' une UE """
2020-09-26 16:19:37 +02:00
create = int ( create )
if not create :
2022-02-02 21:13:39 +01:00
ue : UniteEns = UniteEns . query . get_or_404 ( ue_id )
ue_dict = ue . to_dict ( )
formation_id = ue . formation_id
title = f " Modification de l ' UE { ue . acronyme } { ue . titre } "
initvalues = ue_dict
2020-09-26 16:19:37 +02:00
submitlabel = " Modifier les valeurs "
2022-02-11 09:43:53 +01:00
can_change_semestre_id = ( ue . modules . count ( ) == 0 ) or ( ue . semestre_idx is None )
2020-09-26 16:19:37 +02:00
else :
2022-02-02 21:13:39 +01:00
ue = None
2020-09-26 16:19:37 +02:00
title = " Création d ' une UE "
2022-02-02 22:04:18 +01:00
initvalues = {
" semestre_idx " : default_semestre_idx ,
" color " : ue_guess_color_default ( formation_id , default_semestre_idx ) ,
}
2020-09-26 16:19:37 +02:00
submitlabel = " Créer cette UE "
2022-02-02 21:13:39 +01:00
can_change_semestre_id = True
2021-11-14 18:09:20 +01:00
formation = Formation . query . get ( formation_id )
if not formation :
raise ScoValueError ( f " Formation inexistante ! (id= { formation_id } ) " )
parcours = formation . get_parcours ( )
is_apc = parcours . APC_SAE
semestres_indices = list ( range ( 1 , parcours . NB_SEM + 1 ) )
2020-09-26 16:19:37 +02:00
H = [
2021-07-29 16:31:15 +02:00
html_sco_header . sco_header ( page_title = title , javascripts = [ " js/edit_ue.js " ] ) ,
2020-09-26 16:19:37 +02:00
" <h2> " + title ,
2021-11-14 18:09:20 +01:00
f " (formation { formation . acronyme } , version { formation . version } )</h2> " ,
2020-09-26 16:19:37 +02:00
"""
2021-11-14 18:09:20 +01:00
< p class = " help " > Les UE sont des groupes de modules dans une formation donnée ,
utilisés pour la validation ( on calcule des moyennes par UE et applique des
seuils ( " barres " ) ) .
< / p >
< p class = " help " > Note : sauf exception , l ' UE n ' a pas de coefficient associé .
Seuls les < em > modules < / em > ont des coefficients .
< / p > """ ,
2020-09-26 16:19:37 +02:00
]
ue_types = parcours . ALLOWED_UE_TYPES
ue_types . sort ( )
2021-02-03 22:00:41 +01:00
ue_types_names = [ sco_codes_parcours . UE_TYPE_NAME [ k ] for k in ue_types ]
2020-09-26 16:19:37 +02:00
ue_types = [ str ( x ) for x in ue_types ]
2022-02-02 21:13:39 +01:00
form_descr = [
2020-09-26 16:19:37 +02:00
( " ue_id " , { " input_type " : " hidden " } ) ,
( " create " , { " input_type " : " hidden " , " default " : create } ) ,
( " formation_id " , { " input_type " : " hidden " , " default " : formation_id } ) ,
( " titre " , { " size " : 30 , " explanation " : " nom de l ' UE " } ) ,
( " acronyme " , { " size " : 8 , " explanation " : " abbréviation " , " allow_null " : False } ) ,
(
" numero " ,
{
2022-03-01 23:25:42 +01:00
" size " : 4 ,
2020-09-26 16:19:37 +02:00
" explanation " : " numéro (1,2,3,4) de l ' UE pour l ' ordre d ' affichage " ,
" type " : " int " ,
} ,
) ,
2022-02-02 21:13:39 +01:00
]
if can_change_semestre_id :
form_descr + = [
(
" semestre_idx " ,
{
" input_type " : " menu " ,
" type " : " int " ,
" allow_null " : False ,
" title " : parcours . SESSION_NAME . capitalize ( ) ,
" explanation " : " %s de l ' UE dans la formation "
% parcours . SESSION_NAME ,
" labels " : [ " non spécifié " ] + [ str ( x ) for x in semestres_indices ] ,
" allowed_values " : [ " " ] + semestres_indices ,
} ,
) ,
]
else :
form_descr + = [
( " semestre_idx " , { " default " : ue . semestre_idx , " input_type " : " hidden " } ) ,
]
form_descr + = [
2020-09-26 16:19:37 +02:00
(
" type " ,
{
" explanation " : " type d ' UE " ,
" input_type " : " menu " ,
" allowed_values " : ue_types ,
" labels " : ue_types_names ,
} ,
) ,
(
" ects " ,
{
" size " : 4 ,
" type " : " float " ,
" title " : " ECTS " ,
2022-03-19 14:21:27 +01:00
" explanation " : " nombre de crédits ECTS (indiquer 0 si UE bonus) " ,
2022-02-28 11:00:24 +01:00
" allow_null " : not is_apc , # ects requis en APC
2020-09-26 16:19:37 +02:00
} ,
) ,
(
" coefficient " ,
{
" size " : 4 ,
" type " : " float " ,
" title " : " Coefficient " ,
2021-11-14 18:09:20 +01:00
" explanation " : """ les coefficients d ' UE ne sont utilisés que
lorsque l ' option <em>Utiliser les coefficients d ' UE pour calculer
la moyenne générale < / em > est activée . Par défaut , le coefficient
d ' une UE est simplement la somme des coefficients des modules dans
lesquels l ' étudiant a des notes.
2022-01-30 08:17:25 +01:00
Jamais utilisé en BUT .
2021-11-14 18:09:20 +01:00
""" ,
2022-01-30 08:17:25 +01:00
" enabled " : not is_apc ,
2020-09-26 16:19:37 +02:00
} ,
) ,
(
" ue_code " ,
{
" size " : 12 ,
" title " : " Code UE " ,
2022-03-18 19:55:46 +01:00
" max_length " : SHORT_STR_LEN ,
" explanation " : """ code interne (non vide). Toutes les UE partageant le même code
( et le même code de formation ) sont compatibles ( compensation de semestres , capitalisation d ' UE).
Voir liste ci - dessous . """ ,
2020-09-26 16:19:37 +02:00
} ,
) ,
(
" code_apogee " ,
{
" title " : " Code Apogée " ,
2021-06-24 23:09:06 +02:00
" size " : 25 ,
" explanation " : " (optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules " ,
2022-03-18 19:55:46 +01:00
" max_length " : APO_CODE_STR_LEN ,
2020-09-26 16:19:37 +02:00
} ,
) ,
2021-10-22 23:09:15 +02:00
(
" is_external " ,
{
" input_type " : " boolcheckbox " ,
" title " : " UE externe " ,
" explanation " : " réservé pour les capitalisations d ' UE effectuées à l ' extérieur de l ' établissement " ,
} ,
) ,
2022-01-25 10:45:13 +01:00
(
" color " ,
{
" input_type " : " color " ,
" title " : " Couleur " ,
" explanation " : " pour affichages " ,
} ,
) ,
2020-09-26 16:19:37 +02:00
]
2021-11-18 00:24:56 +01:00
if create and not parcours . UE_IS_MODULE and not is_apc :
2022-02-02 21:13:39 +01:00
form_descr . append (
2020-09-26 16:19:37 +02:00
(
" create_matiere " ,
{
" input_type " : " boolcheckbox " ,
2021-11-14 18:09:20 +01:00
" default " : True ,
2020-09-26 16:19:37 +02:00
" title " : " Créer matière identique " ,
" explanation " : " créer immédiatement une matière dans cette UE (utile si on n ' utilise pas de matières) " ,
} ,
)
)
tf = TrivialFormulator (
2021-09-18 10:10:02 +02:00
request . base_url ,
2021-09-27 16:42:14 +02:00
scu . get_request_args ( ) ,
2022-02-02 21:13:39 +01:00
form_descr ,
2021-09-18 10:10:02 +02:00
initvalues = initvalues ,
submitlabel = submitlabel ,
2020-09-26 16:19:37 +02:00
)
if tf [ 0 ] == 0 :
2022-02-11 09:43:53 +01:00
if ue and ue . modules . count ( ) and ue . semestre_idx is not None :
2022-02-02 21:13:39 +01:00
modules_div = f """ <div id= " ue_list_modules " >
< div > < b > { ue . modules . count ( ) } modules sont rattachés
à cette UE < / b > du semestre S { ue . semestre_idx } ,
elle ne peut donc pas être changée de semestre . < / div >
< ul > """
for m in ue . modules :
modules_div + = f """ <li><a class= " stdlink " href= " { url_for (
" notes.module_edit " , scodoc_dept = g . scodoc_dept , module_id = m . id ) } " > {m.code} {m.titre} </a></li> " " "
modules_div + = """ </ul></div> """
else :
modules_div = " "
2022-01-27 18:12:40 +01:00
bonus_div = """ <div id= " bonus_description " ></div> """
2022-02-02 21:13:39 +01:00
ue_div = """ <div id= " ue_list_code " ></div> """
return (
" \n " . join ( H )
+ tf [ 1 ]
+ modules_div
+ bonus_div
+ ue_div
+ html_sco_header . sco_footer ( )
)
2020-09-26 16:19:37 +02:00
else :
if create :
if not tf [ 2 ] [ " ue_code " ] :
del tf [ 2 ] [ " ue_code " ]
if not tf [ 2 ] [ " numero " ] :
# numero regroupant par semestre ou année:
tf [ 2 ] [ " numero " ] = next_ue_numero (
2021-11-14 18:09:20 +01:00
formation_id , int ( tf [ 2 ] [ " semestre_idx " ] )
2020-09-26 16:19:37 +02:00
)
2021-08-20 01:09:55 +02:00
ue_id = do_ue_create ( tf [ 2 ] )
2021-11-18 00:24:56 +01:00
if is_apc or parcours . UE_IS_MODULE or tf [ 2 ] [ " create_matiere " ] :
# rappel: en APC, toutes les UE ont une matière, créée ici
# (inutilisée mais à laquelle les modules sont rattachés)
2021-06-17 00:08:37 +02:00
matiere_id = sco_edit_matiere . do_matiere_create (
{ " ue_id " : ue_id , " titre " : tf [ 2 ] [ " titre " ] , " numero " : 1 } ,
2020-09-26 16:19:37 +02:00
)
if parcours . UE_IS_MODULE :
# dans ce mode, crée un (unique) module dans l'UE:
2021-06-17 00:08:37 +02:00
_ = sco_edit_module . do_module_create (
2020-09-26 16:19:37 +02:00
{
" titre " : tf [ 2 ] [ " titre " ] ,
" code " : tf [ 2 ] [ " acronyme " ] ,
" coefficient " : 1.0 , # tous les modules auront coef 1, et on utilisera les ECTS
" ue_id " : ue_id ,
" matiere_id " : matiere_id ,
" formation_id " : formation_id ,
2021-11-14 18:09:20 +01:00
" semestre_id " : tf [ 2 ] [ " semestre_idx " ] ,
2020-09-26 16:19:37 +02:00
} ,
)
2022-02-28 11:47:39 +01:00
flash ( " UE créée " )
2020-09-26 16:19:37 +02:00
else :
2021-08-20 01:09:55 +02:00
do_ue_edit ( tf [ 2 ] )
2022-02-28 11:47:39 +01:00
flash ( " UE modifiée " )
2021-07-31 18:01:10 +02:00
return flask . redirect (
url_for (
2021-12-12 23:03:36 +01:00
" notes.ue_table " ,
scodoc_dept = g . scodoc_dept ,
formation_id = formation_id ,
semestre_idx = tf [ 2 ] [ " semestre_idx " ] ,
2021-07-31 18:01:10 +02:00
)
2020-09-26 16:19:37 +02:00
)
2021-11-17 10:28:51 +01:00
def _add_ue_semestre_id ( ues : list [ dict ] , is_apc ) :
2021-11-14 18:09:20 +01:00
""" ajoute semestre_id dans les ue, en regardant
2021-11-17 10:28:51 +01:00
semestre_idx ou à défaut , pour les formations non APC , le premier module
de chacune .
Les UE sans modules se voient attribuer le numero UE_SEM_DEFAULT ( 1000000 ) ,
2020-09-26 16:19:37 +02:00
qui les place à la fin de la liste .
"""
2021-10-21 06:32:03 +02:00
for ue in ues :
2021-11-14 18:09:20 +01:00
if ue [ " semestre_idx " ] is not None :
ue [ " semestre_id " ] = ue [ " semestre_idx " ]
2021-11-17 10:28:51 +01:00
elif is_apc :
ue [ " semestre_id " ] = sco_codes_parcours . UE_SEM_DEFAULT
2020-09-26 16:19:37 +02:00
else :
2021-11-17 10:28:51 +01:00
# était le comportement ScoDoc7
2021-11-14 18:09:20 +01:00
modules = sco_edit_module . module_list ( args = { " ue_id " : ue [ " ue_id " ] } )
if modules :
ue [ " semestre_id " ] = modules [ 0 ] [ " semestre_id " ]
else :
2021-11-17 10:28:51 +01:00
ue [ " semestre_id " ] = sco_codes_parcours . UE_SEM_DEFAULT
2020-09-26 16:19:37 +02:00
2021-08-20 01:09:55 +02:00
def next_ue_numero ( formation_id , semestre_id = None ) :
2020-09-26 16:19:37 +02:00
""" Numero d ' une nouvelle UE dans cette formation.
Si le semestre est specifie , cherche les UE ayant des modules de ce semestre
"""
2021-11-17 10:28:51 +01:00
formation = Formation . query . get ( formation_id )
2021-10-21 06:32:03 +02:00
ues = ue_list ( args = { " formation_id " : formation_id } )
if not ues :
2020-09-26 16:19:37 +02:00
return 0
if semestre_id is None :
2021-10-21 06:32:03 +02:00
return ues [ - 1 ] [ " numero " ] + 1000
2020-09-26 16:19:37 +02:00
else :
# Avec semestre: (prend le semestre du 1er module de l'UE)
2021-11-17 10:28:51 +01:00
_add_ue_semestre_id ( ues , formation . get_parcours ( ) . APC_SAE )
2021-10-21 06:32:03 +02:00
ue_list_semestre = [ ue for ue in ues if ue [ " semestre_id " ] == semestre_id ]
2020-09-26 16:19:37 +02:00
if ue_list_semestre :
return ue_list_semestre [ - 1 ] [ " numero " ] + 10
else :
2021-10-21 06:32:03 +02:00
return ues [ - 1 ] [ " numero " ] + 1000
2020-09-26 16:19:37 +02:00
2021-08-31 08:21:30 +02:00
def ue_delete ( ue_id = None , delete_validations = False , dialog_confirmed = False ) :
2020-09-26 16:19:37 +02:00
""" Delete an UE """
2022-01-04 20:03:38 +01:00
ue = UniteEns . query . get_or_404 ( ue_id )
if ue . modules . all ( ) :
raise ScoValueError (
f """ Suppression de l ' UE { ue . titre } impossible car
2022-01-09 21:48:58 +01:00
des modules ( ou SAÉ ou ressources ) lui sont rattachés . """ ,
dest_url = url_for (
" notes.ue_table " ,
scodoc_dept = g . scodoc_dept ,
formation_id = ue . formation . id ,
semestre_idx = ue . semestre_idx ,
) ,
2022-01-04 20:03:38 +01:00
)
if not can_delete_ue ( ue ) :
raise ScoNonEmptyFormationObject (
" UE " ,
msg = ue . titre ,
dest_url = url_for (
" notes.ue_table " ,
scodoc_dept = g . scodoc_dept ,
formation_id = ue . formation_id ,
semestre_idx = ue . semestre_idx ,
) ,
)
2020-09-26 16:19:37 +02:00
if not dialog_confirmed :
2021-06-02 22:40:34 +02:00
return scu . confirm_dialog (
2022-01-04 20:03:38 +01:00
f " <h2>Suppression de l ' UE { ue . titre } ( { ue . acronyme } )</h2> " ,
2020-09-26 16:19:37 +02:00
dest_url = " " ,
2022-01-04 20:03:38 +01:00
parameters = { " ue_id " : ue . id } ,
2021-10-22 23:09:15 +02:00
cancel_url = url_for (
" notes.ue_table " ,
scodoc_dept = g . scodoc_dept ,
2022-01-04 20:03:38 +01:00
formation_id = ue . formation_id ,
semestre_idx = ue . semestre_idx ,
2021-10-22 23:09:15 +02:00
) ,
2020-09-26 16:19:37 +02:00
)
2022-01-04 20:03:38 +01:00
return do_ue_delete ( ue . id , delete_validations = delete_validations )
2020-09-26 16:19:37 +02:00
2021-11-18 00:24:56 +01:00
def ue_table ( formation_id = None , semestre_idx = 1 , msg = " " ) : # was ue_list
2021-01-01 18:40:47 +01:00
""" Liste des matières et modules d ' une formation, avec liens pour
2021-10-17 23:19:26 +02:00
éditer ( si non verrouillée ) .
2020-09-26 16:19:37 +02:00
"""
2021-06-19 23:21:37 +02:00
from app . scodoc import sco_formsemestre_validation
2022-01-08 14:01:16 +01:00
formation : Formation = Formation . query . get ( formation_id )
2021-11-17 10:28:51 +01:00
if not formation :
2020-09-26 16:19:37 +02:00
raise ScoValueError ( " invalid formation_id " )
2021-11-17 10:28:51 +01:00
parcours = formation . get_parcours ( )
2021-11-13 12:09:30 +01:00
is_apc = parcours . APC_SAE
2021-12-17 22:53:34 +01:00
locked = formation . has_locked_sems ( )
2021-11-18 00:24:56 +01:00
if semestre_idx == " all " :
semestre_idx = None
else :
semestre_idx = int ( semestre_idx )
semestre_ids = range ( 1 , parcours . NB_SEM + 1 )
2021-11-22 00:31:53 +01:00
# transition: on requete ici via l'ORM mais on utilise les fonctions ScoDoc7
# basées sur des dicts
2022-02-10 23:14:33 +01:00
ues_obj = UniteEns . query . filter_by (
formation_id = formation_id , is_external = False
) . order_by ( UniteEns . semestre_idx , UniteEns . numero )
2021-11-22 00:31:53 +01:00
ues_externes_obj = UniteEns . query . filter_by (
formation_id = formation_id , is_external = True
)
if is_apc :
2022-02-02 22:04:18 +01:00
# Pour faciliter la transition des anciens programmes non APC
2021-11-22 00:31:53 +01:00
for ue in ues_obj :
ue . guess_semestre_idx ( )
2022-02-02 22:04:18 +01:00
# vérifie qu'on a bien au moins une matière dans chaque UE
2022-01-27 00:18:50 +01:00
if ue . matieres . count ( ) < 1 :
mat = Matiere ( ue_id = ue . id )
db . session . add ( mat )
2022-02-02 22:04:18 +01:00
# donne des couleurs aux UEs crées avant
colorie_anciennes_ues ( ues_obj )
2022-01-27 00:18:50 +01:00
db . session . commit ( )
2021-11-22 00:31:53 +01:00
ues = [ ue . to_dict ( ) for ue in ues_obj ]
ues_externes = [ ue . to_dict ( ) for ue in ues_externes_obj ]
2020-09-26 16:19:37 +02:00
# tri par semestre et numero:
2021-11-17 10:28:51 +01:00
_add_ue_semestre_id ( ues , is_apc )
_add_ue_semestre_id ( ues_externes , is_apc )
2021-10-21 06:32:03 +02:00
ues . sort ( key = lambda u : ( u [ " semestre_id " ] , u [ " numero " ] ) )
2021-10-22 23:09:15 +02:00
ues_externes . sort ( key = lambda u : ( u [ " semestre_id " ] , u [ " numero " ] ) )
2022-02-19 16:16:52 +01:00
# Codes dupliqués (pour aider l'utilisateur)
seen = set ( )
duplicated_codes = {
ue [ " ue_code " ] for ue in ues if ue [ " ue_code " ] in seen or seen . add ( ue [ " ue_code " ] )
}
ues_with_duplicated_code = [ ue for ue in ues if ue [ " ue_code " ] in duplicated_codes ]
2020-09-26 16:19:37 +02:00
2021-10-22 23:09:15 +02:00
has_perm_change = current_user . has_permission ( Permission . ScoChangeFormation )
# editable = (not locked) and has_perm_change
2021-11-17 10:28:51 +01:00
# On autorise maintenant la modification des formations qui ont
# des semestres verrouillés, sauf si cela affect les notes passées
# (verrouillées):
2020-09-26 16:19:37 +02:00
# - pas de modif des modules utilisés dans des semestres verrouillés
# - pas de changement des codes d'UE utilisés dans des semestres verrouillés
2021-10-22 23:09:15 +02:00
editable = has_perm_change
2021-06-12 22:43:22 +02:00
tag_editable = (
2021-10-22 23:09:15 +02:00
current_user . has_permission ( Permission . ScoEditFormationTags ) or has_perm_change
2021-06-12 22:43:22 +02:00
)
2020-09-26 16:19:37 +02:00
if locked :
2021-02-04 20:02:44 +01:00
lockicon = scu . icontag ( " lock32_img " , title = " verrouillé " )
2020-09-26 16:19:37 +02:00
else :
lockicon = " "
2021-10-12 16:05:50 +02:00
arrow_up , arrow_down , arrow_none = sco_groups . get_arrow_icons_tags ( )
2021-02-04 20:02:44 +01:00
delete_icon = scu . icontag (
2020-09-26 16:19:37 +02:00
" delete_small_img " , title = " Supprimer (module inutilisé) " , alt = " supprimer "
)
2021-02-04 20:02:44 +01:00
delete_disabled_icon = scu . icontag (
2020-09-26 16:19:37 +02:00
" delete_small_dis_img " , title = " Suppression impossible (module utilisé) "
)
H = [
2021-06-13 19:12:20 +02:00
html_sco_header . sco_header (
2020-09-26 16:19:37 +02:00
cssstyles = [ " libjs/jQuery-tagEditor/jquery.tag-editor.css " ] ,
javascripts = [
" libjs/jinplace-1.2.1.min.js " ,
" js/ue_list.js " ,
" libjs/jQuery-tagEditor/jquery.tag-editor.min.js " ,
" libjs/jQuery-tagEditor/jquery.caret.min.js " ,
" js/module_tag_editor.js " ,
] ,
2021-11-17 10:28:51 +01:00
page_title = f " Programme { formation . acronyme } " ,
2020-09-26 16:19:37 +02:00
) ,
2021-11-17 10:28:51 +01:00
f """ <h2>Formation { formation . titre } ( { formation . acronyme } )
[ version { formation . version } ] code { formation . formation_code }
{ lockicon }
< / h2 >
""" ,
2020-09-26 16:19:37 +02:00
]
if locked :
H . append (
2021-08-30 23:28:15 +02:00
f """ <p class= " help " >Cette formation est verrouillée car
2021-12-17 22:53:34 +01:00
des semestres verrouillés s ' y réferent.
2021-08-30 23:28:15 +02:00
Si vous souhaitez modifier cette formation ( par exemple pour y ajouter un module ) ,
vous devez :
2020-09-26 16:19:37 +02:00
< / p >
< ul class = " help " >
2021-08-30 23:28:15 +02:00
< li > soit créer une nouvelle version de cette formation pour pouvoir l ' éditer
librement ( vous pouvez passer par la fonction " Associer à une nouvelle version
du programme " (menu " Semestre " ) si vous avez un semestre en cours);
< / li >
< li > soit déverrouiller le ou les semestres qui s ' y réfèrent (attention, en
principe ces semestres sont archivés et ne devraient pas être modifiés ) .
< / li >
2020-09-26 16:19:37 +02:00
< / ul > """
)
if msg :
H . append ( ' <p class= " msg " > ' + msg + " </p> " )
2022-02-19 16:16:52 +01:00
if ues_with_duplicated_code :
2020-09-26 16:19:37 +02:00
H . append (
2022-02-19 16:16:52 +01:00
f """ <div class= " ue_warning " ><span>Attention: plusieurs UE de cette
formation ont le même code : < tt > {
' , ' . join ( [
' <a class= " stdlink " href= " ' + url_for ( " notes.ue_edit " , scodoc_dept = g . scodoc_dept , ue_id = ue [ " ue_id " ] )
+ ' " > ' + ue [ " acronyme " ] + " (code " + ue [ " ue_code " ] + " )</a> "
for ue in ues_with_duplicated_code ] )
} < / tt > .
Il faut corriger cela , sinon les capitalisations et ECTS seront
erronés ! < / span > < / div > """
2020-09-26 16:19:37 +02:00
)
# Description de la formation
H . append (
2021-11-18 00:24:56 +01:00
render_template (
" pn/form_descr.html " ,
formation = formation ,
parcours = parcours ,
editable = editable ,
2020-09-26 16:19:37 +02:00
)
2021-11-18 00:24:56 +01:00
)
2021-11-17 10:28:51 +01:00
2021-11-13 12:09:30 +01:00
# Formation APC (BUT) ?
if is_apc :
H . append (
f """ <div class= " formation_apc_infos " >
2021-11-18 00:24:56 +01:00
< div class = " ue_list_tit " > Formation par compétences ( BUT )
- Semestre { _html_select_semestre_idx ( formation_id , semestre_ids , semestre_idx ) }
< / form >
< / div >
"""
)
2021-12-03 11:03:33 +01:00
if formation . referentiel_competence is None :
descr_refcomp = " "
msg_refcomp = " associer à un référentiel de compétences "
else :
2022-01-08 14:35:02 +01:00
descr_refcomp = f """ Référentiel de compétences:
< a href = " { url_for( ' notes.refcomp_show ' ,
scodoc_dept = g . scodoc_dept , refcomp_id = formation . referentiel_competence . id ) } " >
{ formation . referentiel_competence . type_titre } { formation . referentiel_competence . specialite_long }
2022-03-01 23:25:42 +01:00
< / a > & nbsp ; """
2021-12-03 11:03:33 +01:00
msg_refcomp = " changer "
2022-03-15 23:09:41 +01:00
H . append ( f """ <ul><li> { descr_refcomp } """ )
if current_user . has_permission ( Permission . ScoChangeFormation ) :
H . append (
f """ <a class= " stdlink " href= " { url_for ( ' notes.refcomp_assoc_formation ' ,
2021-12-03 11:03:33 +01:00
scodoc_dept = g . scodoc_dept , formation_id = formation_id )
2022-03-15 23:09:41 +01:00
} " > {msg_refcomp} </a> " " "
)
H . append (
f """ </li>
2022-03-01 23:25:42 +01:00
< li > < a class = " stdlink " href = " {
2021-11-18 00:24:56 +01:00
url_for ( ' notes.edit_modules_ue_coefs ' , scodoc_dept = g . scodoc_dept , formation_id = formation_id , semestre_idx = semestre_idx )
2021-12-03 11:03:33 +01:00
} " >éditer les coefficients des ressources et SAÉs</a>
< / li >
2021-11-13 12:09:30 +01:00
< / ul >
2021-11-18 00:24:56 +01:00
"""
2021-11-13 12:09:30 +01:00
)
2020-09-26 16:19:37 +02:00
# Description des UE/matières/modules
2021-10-22 23:09:15 +02:00
H . append (
2021-11-17 10:28:51 +01:00
"""
< div class = " formation_ue_list " >
< div class = " ue_list_tit " > Programme pédagogique : < / div >
< form >
< input type = " checkbox " class = " sco_tag_checkbox " > montrer les tags < / input >
< / form >
"""
2021-10-22 23:09:15 +02:00
)
2021-11-17 10:28:51 +01:00
if is_apc :
2020-09-26 16:19:37 +02:00
H . append (
2021-11-17 10:28:51 +01:00
sco_edit_apc . html_edit_formation_apc (
2021-11-18 00:24:56 +01:00
formation ,
semestre_idx = semestre_idx ,
editable = editable ,
tag_editable = tag_editable ,
2021-11-17 10:28:51 +01:00
)
2020-09-26 16:19:37 +02:00
)
2021-11-17 10:28:51 +01:00
else :
2022-02-27 17:49:39 +01:00
H . append ( ' <div class= " formation_classic_infos " > ' )
2020-09-26 16:19:37 +02:00
H . append (
2021-11-17 10:28:51 +01:00
_ue_table_ues (
parcours ,
ues ,
editable ,
tag_editable ,
has_perm_change ,
arrow_up ,
arrow_down ,
arrow_none ,
delete_icon ,
delete_disabled_icon ,
)
2020-09-26 16:19:37 +02:00
)
2021-11-17 10:28:51 +01:00
if editable :
H . append (
f """ <ul>
< li > < a class = " stdlink " href = " {
url_for ( ' notes.ue_create ' , scodoc_dept = g . scodoc_dept , formation_id = formation_id )
} " >Ajouter une UE</a>
< / li >
< li > < a href = " {
url_for ( ' notes.formation_add_malus_modules ' ,
scodoc_dept = g . scodoc_dept , formation_id = formation_id )
} " class= " stdlink " >Ajouter des modules de malus dans chaque UE</a>
< / li >
< / ul >
"""
)
2022-02-27 17:49:39 +01:00
H . append ( " </div> " )
2020-09-26 16:19:37 +02:00
H . append ( " </div> " ) # formation_ue_list
2021-10-22 23:09:15 +02:00
if ues_externes :
H . append (
2021-11-17 10:28:51 +01:00
f """
< div class = " formation_ue_list formation_ue_list_externes " >
< div class = " ue_list_tit " > UE externes déclarées ( pour information ) :
< / div >
{ _ue_table_ues (
2021-10-22 23:09:15 +02:00
parcours ,
ues_externes ,
editable ,
tag_editable ,
has_perm_change ,
arrow_up ,
arrow_down ,
arrow_none ,
delete_icon ,
delete_disabled_icon ,
2021-11-17 10:28:51 +01:00
) }
< / div >
"""
2021-10-22 23:09:15 +02:00
)
2020-09-26 16:19:37 +02:00
H . append ( " <p><ul> " )
if editable :
H . append (
2021-11-17 10:28:51 +01:00
f """
< li > < a class = " stdlink " href = " {
url_for ( ' notes.formation_create_new_version ' ,
scodoc_dept = g . scodoc_dept , formation_id = formation_id
)
} " >Créer une nouvelle version (non verrouillée)</a>
< / li >
"""
2020-09-26 16:19:37 +02:00
)
H . append (
2021-11-17 10:28:51 +01:00
f """
< li > < a class = " stdlink " href = " {
url_for ( ' notes.formation_table_recap ' , scodoc_dept = g . scodoc_dept ,
formation_id = formation_id )
} " >Table récapitulative de la formation</a>
< / li >
< li > < a class = " stdlink " href = " {
url_for ( ' notes.formation_export ' , scodoc_dept = g . scodoc_dept ,
formation_id = formation_id , format = ' xml ' )
} " >Export XML de la formation</a>
( permet de la sauvegarder pour l ' échanger avec un autre site)
< / li >
2020-09-26 16:19:37 +02:00
2021-11-17 10:28:51 +01:00
< li > < a class = " stdlink " href = " {
url_for ( ' notes.formation_export ' , scodoc_dept = g . scodoc_dept ,
formation_id = formation_id , format = ' json ' )
} " >Export JSON de la formation</a>
< / li >
2020-09-26 16:19:37 +02:00
2021-11-17 10:28:51 +01:00
< li > < a class = " stdlink " href = " {
url_for ( ' notes.module_table ' , scodoc_dept = g . scodoc_dept ,
formation_id = formation_id )
} " >Liste détaillée des modules de la formation</a> (debug)
< / li >
< / ul >
< / p > """
2020-09-26 16:19:37 +02:00
)
2021-10-22 23:09:15 +02:00
if has_perm_change :
2020-09-26 16:19:37 +02:00
H . append (
"""
< h3 > < a name = " sems " > Semestres ou sessions de cette formation < / a > < / h3 >
< p > < ul > """
)
for sem in sco_formsemestre . do_formsemestre_list (
2021-08-19 10:28:35 +02:00
args = { " formation_id " : formation_id }
2020-09-26 16:19:37 +02:00
) :
H . append (
' <li><a class= " stdlink " href= " formsemestre_status?formsemestre_id= %(formsemestre_id)s " > %(titremois)s </a> '
% sem
)
2021-08-10 12:57:38 +02:00
if not sem [ " etat " ] :
2020-09-26 16:19:37 +02:00
H . append ( " [verrouillé] " )
else :
H . append (
2021-05-11 11:48:32 +02:00
' <a class= " stdlink " href= " formsemestre_editwithmodules?formation_id= %(formation_id)s &formsemestre_id= %(formsemestre_id)s " >Modifier</a> '
2020-09-26 16:19:37 +02:00
% sem
)
H . append ( " </li> " )
H . append ( " </ul> " )
2021-07-31 18:01:10 +02:00
if current_user . has_permission ( Permission . ScoImplement ) :
2020-09-26 16:19:37 +02:00
H . append (
2021-11-17 10:28:51 +01:00
f """ <ul>
< li > < a class = " stdlink " href = " {
url_for ( ' notes.formsemestre_createwithmodules ' , scodoc_dept = g . scodoc_dept ,
formation_id = formation_id , semestre_id = 1 )
2021-11-18 00:24:56 +01:00
} " >Mettre en place un nouveau semestre de formation {formation.acronyme} </a>
2021-11-17 10:28:51 +01:00
< / li >
< / ul > """
2020-09-26 16:19:37 +02:00
)
# <li>(debug) <a class="stdlink" href="check_form_integrity?formation_id=%(formation_id)s">Vérifier cohérence</a></li>
2021-08-20 01:09:55 +02:00
warn , _ = sco_formsemestre_validation . check_formation_ues ( formation_id )
2020-09-26 16:19:37 +02:00
H . append ( warn )
2021-07-29 10:19:00 +02:00
H . append ( html_sco_header . sco_footer ( ) )
2020-09-26 16:19:37 +02:00
return " " . join ( H )
2021-11-18 00:24:56 +01:00
def _html_select_semestre_idx ( formation_id , semestre_ids , semestre_idx ) :
htm = """ <form method= " get " >Semestre:
< select onchange = " this.form.submit() " name = " semestre_idx " id = " semestre_idx " >
"""
for i in list ( semestre_ids ) + [ " all " ] :
if i == " all " :
label = " tous "
else :
label = f " S { i } "
htm + = f """ <option value= " { i } " {
' selected '
if ( semestre_idx == i )
or ( i == " all " and semestre_idx is None )
else ' '
} > { label } < / option >
"""
htm + = f """
< / select >
< input type = " hidden " name = " formation_id " value = " {formation_id} " > < / input >
< / form > """
return htm
2021-10-22 23:09:15 +02:00
def _ue_table_ues (
parcours ,
ues ,
editable ,
tag_editable ,
has_perm_change ,
arrow_up ,
arrow_down ,
arrow_none ,
delete_icon ,
delete_disabled_icon ,
) :
2021-11-17 10:28:51 +01:00
""" Édition de programme: liste des UEs (avec leurs matières et modules).
Pour les formations classiques ( non APC / BUT )
"""
2021-10-22 23:09:15 +02:00
H = [ ]
cur_ue_semestre_id = None
iue = 0
for ue in ues :
2022-02-28 11:00:24 +01:00
if ue [ " ects " ] is None :
2021-10-22 23:09:15 +02:00
ue [ " ects_str " ] = " "
2022-02-28 11:00:24 +01:00
else :
ue [ " ects_str " ] = " , %g ECTS " % ue [ " ects " ]
2021-10-22 23:09:15 +02:00
if editable :
klass = " span_apo_edit "
else :
klass = " "
ue [ " code_apogee_str " ] = (
""" , Apo: <span class= " %s " data-url= " edit_ue_set_code_apogee " id= " %s " data-placeholder= " %s " > """
% ( klass , ue [ " ue_id " ] , scu . APO_MISSING_CODE_STR )
+ ( ue [ " code_apogee " ] or " " )
+ " </span> "
)
if cur_ue_semestre_id != ue [ " semestre_id " ] :
cur_ue_semestre_id = ue [ " semestre_id " ]
if ue [ " semestre_id " ] == sco_codes_parcours . UE_SEM_DEFAULT :
lab = " Pas d ' indication de semestre: "
else :
lab = " Semestre %s : " % ue [ " semestre_id " ]
2022-02-20 15:10:15 +01:00
H . append (
' <div class= " ue_list_div " ><div class= " ue_list_tit_sem " > %s </div> ' % lab
)
2021-10-22 23:09:15 +02:00
H . append ( ' <ul class= " notes_ue_list " > ' )
H . append ( ' <li class= " notes_ue_list " > ' )
if iue != 0 and editable :
H . append (
' <a href= " ue_move?ue_id= %s &after=0 " class= " aud " > %s </a> '
% ( ue [ " ue_id " ] , arrow_up )
)
else :
H . append ( arrow_none )
if iue < len ( ues ) - 1 and editable :
H . append (
' <a href= " ue_move?ue_id= %s &after=1 " class= " aud " > %s </a> '
% ( ue [ " ue_id " ] , arrow_down )
)
else :
H . append ( arrow_none )
ue [ " acro_titre " ] = str ( ue [ " acronyme " ] )
if ue [ " titre " ] != ue [ " acronyme " ] :
ue [ " acro_titre " ] + = " " + str ( ue [ " titre " ] )
H . append (
""" %(acro_titre)s <span class= " ue_code " >(code %(ue_code)s %(ects_str)s , coef. %(coefficient)3.2f %(code_apogee_str)s )</span>
< span class = " ue_coef " > < / span >
"""
% ue
)
if ue [ " type " ] != sco_codes_parcours . UE_STANDARD :
H . append (
' <span class= " ue_type " > %s </span> '
% sco_codes_parcours . UE_TYPE_NAME [ ue [ " type " ] ]
)
if ue [ " is_external " ] :
# Cas spécial: si l'UE externe a plus d'un module, c'est peut être une UE
# qui a été déclarée externe par erreur (ou suite à un bug d'import/export xml)
# Dans ce cas, propose de changer le type (même si verrouillée)
if len ( sco_moduleimpl . moduleimpls_in_external_ue ( ue [ " ue_id " ] ) ) > 1 :
H . append ( ' <span class= " ue_is_external " > ' )
if has_perm_change :
H . append (
f """ <a class= " stdlink " href= " {
url_for ( " notes.ue_set_internal " , scodoc_dept = g . scodoc_dept , ue_id = ue [ " ue_id " ] )
} " >transformer en UE ordinaire</a> " " "
)
H . append ( " </span> " )
ue_editable = editable and not ue_is_locked ( ue [ " ue_id " ] )
if ue_editable :
H . append (
' <a class= " stdlink " href= " ue_edit?ue_id= %(ue_id)s " >modifier</a> ' % ue
)
else :
H . append ( ' <span class= " locked " >[verrouillé]</span> ' )
H . append (
2021-11-18 00:24:56 +01:00
_ue_table_matieres (
2021-10-22 23:09:15 +02:00
parcours ,
ue ,
editable ,
tag_editable ,
arrow_up ,
arrow_down ,
arrow_none ,
delete_icon ,
delete_disabled_icon ,
)
)
2022-02-19 16:16:52 +01:00
if ( iue > = len ( ues ) - 1 ) or ue [ " semestre_id " ] != ues [ iue + 1 ] [ " semestre_id " ] :
H . append (
f """ </ul><ul><li><a href= " { url_for ( ' notes.ue_create ' , scodoc_dept = g . scodoc_dept ,
formation_id = ue [ ' formation_id ' ] , semestre_idx = ue [ ' semestre_id ' ] )
2022-02-20 15:10:15 +01:00
} " >Ajouter une UE dans le semestre { ue[ ' semestre_id ' ] or ' ' }</a></li></ul>
< / div >
"""
2022-02-19 16:16:52 +01:00
)
iue + = 1
2021-10-22 23:09:15 +02:00
return " \n " . join ( H )
def _ue_table_matieres (
parcours ,
ue ,
editable ,
tag_editable ,
arrow_up ,
arrow_down ,
arrow_none ,
delete_icon ,
delete_disabled_icon ,
) :
""" Édition de programme: liste des matières (et leurs modules) d ' une UE. """
H = [ ]
if not parcours . UE_IS_MODULE :
H . append ( ' <ul class= " notes_matiere_list " > ' )
matieres = sco_edit_matiere . matiere_list ( args = { " ue_id " : ue [ " ue_id " ] } )
for mat in matieres :
if not parcours . UE_IS_MODULE :
H . append ( ' <li class= " notes_matiere_list " > ' )
if editable and not sco_edit_matiere . matiere_is_locked ( mat [ " matiere_id " ] ) :
H . append (
f """ <a class= " stdlink " href= " {
url_for ( " notes.matiere_edit " ,
scodoc_dept = g . scodoc_dept , matiere_id = mat [ " matiere_id " ] )
} " >
"""
)
H . append ( " %(titre)s " % mat )
if editable and not sco_edit_matiere . matiere_is_locked ( mat [ " matiere_id " ] ) :
H . append ( " </a> " )
modules = sco_edit_module . module_list ( args = { " matiere_id " : mat [ " matiere_id " ] } )
H . append (
_ue_table_modules (
parcours ,
2022-02-01 11:37:05 +01:00
ue ,
2021-10-22 23:09:15 +02:00
mat ,
modules ,
editable ,
tag_editable ,
arrow_up ,
arrow_down ,
arrow_none ,
delete_icon ,
delete_disabled_icon ,
)
)
2021-11-08 19:44:25 +01:00
if not parcours . UE_IS_MODULE :
H . append ( " </li> " )
2021-10-22 23:09:15 +02:00
if not matieres :
H . append ( " <li>Aucune matière dans cette UE ! " )
if editable :
H . append (
""" <a class= " stdlink " href= " ue_delete?ue_id= %(ue_id)s " >supprimer l ' UE</a> """
% ue
)
H . append ( " </li> " )
if editable and not parcours . UE_IS_MODULE :
H . append (
' <li><a class= " stdlink " href= " matiere_create?ue_id= %(ue_id)s " >créer une matière</a> </li> '
% ue
)
if not parcours . UE_IS_MODULE :
H . append ( " </ul> " )
return " \n " . join ( H )
def _ue_table_modules (
parcours ,
2022-02-01 11:37:05 +01:00
ue ,
2021-10-22 23:09:15 +02:00
mat ,
modules ,
editable ,
tag_editable ,
arrow_up ,
arrow_down ,
arrow_none ,
delete_icon ,
delete_disabled_icon ,
2021-11-08 19:44:25 +01:00
unit_name = " matière " ,
add_suppress_link = True , # lien "supprimer cette matière"
empty_list_msg = " Aucun élément dans cette matière " ,
create_element_msg = " créer un module " ,
2021-10-22 23:09:15 +02:00
) :
""" Édition de programme: liste des modules d ' une matière d ' une UE """
H = [ ' <ul class= " notes_module_list " > ' ]
im = 0
for mod in modules :
mod [ " nb_moduleimpls " ] = sco_edit_module . module_count_moduleimpls (
mod [ " module_id " ]
)
klass = " notes_module_list "
2021-11-12 22:17:46 +01:00
if mod [ " module_type " ] == ModuleType . MALUS :
2021-10-22 23:09:15 +02:00
klass + = " module_malus "
H . append ( ' <li class= " %s " > ' % klass )
H . append ( ' <span class= " notes_module_list_buts " > ' )
if im != 0 and editable :
H . append (
' <a href= " module_move?module_id= %s &after=0 " class= " aud " > %s </a> '
% ( mod [ " module_id " ] , arrow_up )
)
else :
H . append ( arrow_none )
if im < len ( modules ) - 1 and editable :
H . append (
' <a href= " module_move?module_id= %s &after=1 " class= " aud " > %s </a> '
% ( mod [ " module_id " ] , arrow_down )
)
else :
H . append ( arrow_none )
im + = 1
if mod [ " nb_moduleimpls " ] == 0 and editable :
2022-01-04 20:03:38 +01:00
icon = delete_icon
2021-10-22 23:09:15 +02:00
else :
2022-01-04 20:03:38 +01:00
icon = delete_disabled_icon
H . append (
' <a class= " smallbutton " href= " module_delete?module_id= %s " > %s </a> '
% ( mod [ " module_id " ] , icon )
)
2021-10-22 23:09:15 +02:00
H . append ( " </span> " )
mod_editable = (
editable # and not sco_edit_module.module_is_locked( Mod['module_id'])
)
if mod_editable :
H . append (
' <a class= " discretelink " title= " Modifier le module numéro %(numero)s , utilisé par %(nb_moduleimpls)d sessions " href= " module_edit?module_id= %(module_id)s " > '
% mod
)
H . append (
' <span class= " formation_module_tit " > %s </span> '
% scu . join_words ( mod [ " code " ] , mod [ " titre " ] )
)
if mod_editable :
H . append ( " </a> " )
heurescoef = (
" %(heures_cours)s / %(heures_td)s / %(heures_tp)s , coef. %(coefficient)s " % mod
)
if mod_editable :
klass = " span_apo_edit "
else :
klass = " "
heurescoef + = (
' , Apo: <span class= " %s " data-url= " edit_module_set_code_apogee " id= " %s " data-placeholder= " %s " > '
% ( klass , mod [ " module_id " ] , scu . APO_MISSING_CODE_STR )
+ ( mod [ " code_apogee " ] or " " )
+ " </span> "
)
if tag_editable :
tag_cls = " module_tag_editor "
else :
tag_cls = " module_tag_editor_ro "
tag_mk = """ <span class= " sco_tag_edit " ><form><textarea data-module_id= " {} " class= " {} " > {} </textarea></form></span> """
tag_edit = tag_mk . format (
mod [ " module_id " ] ,
tag_cls ,
" , " . join ( sco_tag_module . module_tag_list ( mod [ " module_id " ] ) ) ,
)
2022-02-01 11:37:05 +01:00
if ue [ " semestre_idx " ] is not None and mod [ " semestre_id " ] != ue [ " semestre_idx " ] :
warning_semestre = ' <span class= " red " >incohérent ?</span> '
else :
warning_semestre = " "
2021-10-22 23:09:15 +02:00
H . append (
2022-02-01 11:37:05 +01:00
" %s %s %s " % ( parcours . SESSION_NAME , mod [ " semestre_id " ] , warning_semestre )
2021-10-22 23:09:15 +02:00
+ " ( %s ) " % heurescoef
+ tag_edit
)
H . append ( " </li> " )
if not modules :
2021-11-08 19:44:25 +01:00
H . append ( f " <li> { empty_list_msg } ! " )
if editable and add_suppress_link :
2021-10-22 23:09:15 +02:00
H . append (
f """ <a class= " stdlink " href= " {
url_for ( " notes.matiere_delete " ,
scodoc_dept = g . scodoc_dept , matiere_id = mat [ " matiere_id " ] ) } "
> la supprimer < / a >
"""
)
H . append ( " </li> " )
if editable : # and ((not parcours.UE_IS_MODULE) or len(Modlist) == 0):
H . append (
f """ <li> <a class= " stdlink " href= " {
url_for ( " notes.module_create " ,
scodoc_dept = g . scodoc_dept , matiere_id = mat [ " matiere_id " ] ) } "
2021-11-08 19:44:25 +01:00
> { create_element_msg } < / a > < / li >
2021-10-22 23:09:15 +02:00
"""
)
H . append ( " </ul> " )
return " \n " . join ( H )
2021-08-20 01:09:55 +02:00
def ue_sharing_code ( ue_code = None , ue_id = None , hide_ue_id = None ) :
2020-09-26 16:19:37 +02:00
""" HTML list of UE sharing this code
Either ue_code or ue_id may be specified .
2021-08-09 07:43:41 +02:00
hide_ue_id spécifie un id à retirer de la liste .
2020-09-26 16:19:37 +02:00
"""
2021-06-19 23:21:37 +02:00
from app . scodoc import sco_formations
2021-09-30 17:50:37 +02:00
ue_code = str ( ue_code )
2020-09-26 16:19:37 +02:00
if ue_id :
2021-10-17 23:19:26 +02:00
ue = ue_list ( args = { " ue_id " : ue_id } ) [ 0 ]
2020-09-26 16:19:37 +02:00
if not ue_code :
ue_code = ue [ " ue_code " ]
2021-08-19 10:28:35 +02:00
F = sco_formations . formation_list ( args = { " formation_id " : ue [ " formation_id " ] } ) [ 0 ]
2020-09-26 16:19:37 +02:00
formation_code = F [ " formation_code " ]
2021-09-27 17:18:43 +02:00
# UE du même code, code formation et departement:
2021-09-26 11:28:13 +02:00
q_ues = (
2021-11-12 22:17:46 +01:00
UniteEns . query . filter_by ( ue_code = ue_code )
. join ( UniteEns . formation , aliased = True )
2021-09-27 17:18:43 +02:00
. filter_by ( dept_id = g . scodoc_dept_id , formation_code = formation_code )
2021-09-26 11:28:13 +02:00
)
2020-09-26 16:19:37 +02:00
else :
2021-09-27 17:18:43 +02:00
# Toutes les UE du departement avec ce code:
2021-09-26 11:28:13 +02:00
q_ues = (
2021-11-12 22:17:46 +01:00
UniteEns . query . filter_by ( ue_code = ue_code )
. join ( UniteEns . formation , aliased = True )
2021-09-27 17:18:43 +02:00
. filter_by ( dept_id = g . scodoc_dept_id )
2021-09-26 11:28:13 +02:00
)
2020-09-26 16:19:37 +02:00
if hide_ue_id : # enlève l'ue de depart
2021-11-12 22:17:46 +01:00
q_ues = q_ues . filter ( UniteEns . id != hide_ue_id )
2020-09-26 16:19:37 +02:00
2021-09-26 11:28:13 +02:00
ues = q_ues . all ( )
if not ues :
2020-09-26 16:19:37 +02:00
if ue_id :
return """ <span class= " ue_share " >Seule UE avec code %s </span> """ % ue_code
else :
return """ <span class= " ue_share " >Aucune UE avec code %s </span> """ % ue_code
H = [ ]
if ue_id :
H . append ( ' <span class= " ue_share " >Autres UE avec le code %s :</span> ' % ue_code )
else :
H . append ( ' <span class= " ue_share " >UE avec le code %s :</span> ' % ue_code )
H . append ( " <ul> " )
2021-09-26 11:28:13 +02:00
for ue in ues :
2020-09-26 16:19:37 +02:00
H . append (
2021-09-26 11:28:13 +02:00
f """ <li> { ue . acronyme } ( { ue . titre } ) dans <a class= " stdlink "
2021-10-17 23:19:26 +02:00
href = " { url_for( " notes . ue_table " , scodoc_dept=g.scodoc_dept, formation_id=ue.formation.id)} "
2021-09-26 11:28:13 +02:00
> { ue . formation . acronyme } ( { ue . formation . titre } ) < / a > , version { ue . formation . version }
< / li >
"""
2020-09-26 16:19:37 +02:00
)
H . append ( " </ul> " )
return " \n " . join ( H )
2021-08-20 01:09:55 +02:00
def do_ue_edit ( args , bypass_lock = False , dont_invalidate_cache = False ) :
2020-09-26 16:19:37 +02:00
" edit an UE "
# check
ue_id = args [ " ue_id " ]
2021-10-17 23:19:26 +02:00
ue = ue_list ( { " ue_id " : ue_id } ) [ 0 ]
2021-08-20 01:09:55 +02:00
if ( not bypass_lock ) and ue_is_locked ( ue [ " ue_id " ] ) :
2020-09-26 16:19:37 +02:00
raise ScoLockedFormError ( )
# check: acronyme unique dans cette formation
2021-07-09 17:47:06 +02:00
if " acronyme " in args :
2020-09-26 16:19:37 +02:00
new_acro = args [ " acronyme " ]
2021-10-17 23:19:26 +02:00
ues = ue_list ( { " formation_id " : ue [ " formation_id " ] , " acronyme " : new_acro } )
2020-09-26 16:19:37 +02:00
if ues and ues [ 0 ] [ " ue_id " ] != ue_id :
2021-12-18 17:39:03 +01:00
raise ScoValueError (
f """ Acronyme d ' UE " { args [ ' acronyme ' ] } " déjà utilisé !
( chaque UE doit avoir un acronyme unique dans la formation ) """
)
2020-09-26 16:19:37 +02:00
# On ne peut pas supprimer le code UE:
2021-07-09 17:47:06 +02:00
if " ue_code " in args and not args [ " ue_code " ] :
2020-09-26 16:19:37 +02:00
del args [ " ue_code " ]
2021-06-15 13:59:56 +02:00
cnx = ndb . GetDBConnexion ( )
2021-06-17 00:08:37 +02:00
_ueEditor . edit ( cnx , args )
2020-09-26 16:19:37 +02:00
2021-12-16 16:27:35 +01:00
formation = Formation . query . get ( ue [ " formation_id " ] )
2020-09-26 16:19:37 +02:00
if not dont_invalidate_cache :
# Invalide les semestres utilisant cette formation:
2021-12-16 16:27:35 +01:00
formation . invalidate_cached_sems ( )
2020-09-26 16:19:37 +02:00
# essai edition en ligne:
2021-08-30 23:28:15 +02:00
def edit_ue_set_code_apogee ( id = None , value = None ) :
2020-09-26 16:19:37 +02:00
" set UE code apogee "
ue_id = id
2022-01-19 23:02:15 +01:00
value = value . strip ( " -_ \t " ) [ : APO_CODE_STR_LEN ] # tronque
2020-09-26 16:19:37 +02:00
log ( " edit_ue_set_code_apogee: ue_id= %s code_apogee= %s " % ( ue_id , value ) )
2021-10-17 23:19:26 +02:00
ues = ue_list ( args = { " ue_id " : ue_id } )
2020-09-26 16:19:37 +02:00
if not ues :
return " ue invalide "
do_ue_edit (
{ " ue_id " : ue_id , " code_apogee " : value } ,
bypass_lock = True ,
dont_invalidate_cache = False ,
)
if not value :
2021-02-04 20:02:44 +01:00
value = scu . APO_MISSING_CODE_STR
2020-09-26 16:19:37 +02:00
return value
2021-08-20 01:09:55 +02:00
def ue_is_locked ( ue_id ) :
2021-06-13 18:29:53 +02:00
""" True if UE should not be modified
( contains modules used in a locked formsemestre )
"""
r = ndb . SimpleDictFetch (
2021-08-08 17:38:46 +02:00
""" SELECT ue.id
FROM notes_ue ue , notes_modules mod , notes_formsemestre sem , notes_moduleimpl mi
WHERE ue . id = mod . ue_id
AND mi . module_id = mod . id AND mi . formsemestre_id = sem . id
AND ue . id = % ( ue_id ) s AND sem . etat = false
2021-06-13 18:29:53 +02:00
""" ,
{ " ue_id " : ue_id } ,
)
return len ( r ) > 0
2020-09-26 16:19:37 +02:00
# ---- Table recap formation
2021-09-27 10:20:10 +02:00
def formation_table_recap ( formation_id , format = " html " ) :
2021-01-16 11:49:02 +01:00
""" Table recapitulant formation. """
2021-06-19 23:21:37 +02:00
from app . scodoc import sco_formations
2021-08-19 10:28:35 +02:00
F = sco_formations . formation_list ( args = { " formation_id " : formation_id } )
2020-09-26 16:19:37 +02:00
if not F :
raise ScoValueError ( " invalid formation_id " )
F = F [ 0 ]
T = [ ]
2021-10-21 06:32:03 +02:00
ues = ue_list ( args = { " formation_id " : formation_id } )
for ue in ues :
Matlist = sco_edit_matiere . matiere_list ( args = { " ue_id " : ue [ " ue_id " ] } )
2020-09-26 16:19:37 +02:00
for Mat in Matlist :
2021-10-16 19:20:36 +02:00
Modlist = sco_edit_module . module_list (
2021-08-20 01:09:55 +02:00
args = { " matiere_id " : Mat [ " matiere_id " ] }
2021-06-17 00:08:37 +02:00
)
2020-09-26 16:19:37 +02:00
for Mod in Modlist :
2021-06-13 19:12:20 +02:00
Mod [ " nb_moduleimpls " ] = sco_edit_module . module_count_moduleimpls (
2021-08-20 01:09:55 +02:00
Mod [ " module_id " ]
2020-09-26 16:19:37 +02:00
)
#
T . append (
{
2021-10-21 06:32:03 +02:00
" UE_acro " : ue [ " acronyme " ] ,
2020-09-26 16:19:37 +02:00
" Mat_tit " : Mat [ " titre " ] ,
" Mod_tit " : Mod [ " abbrev " ] or Mod [ " titre " ] ,
" Mod_code " : Mod [ " code " ] ,
" Mod_coef " : Mod [ " coefficient " ] ,
" Mod_sem " : Mod [ " semestre_id " ] ,
" nb_moduleimpls " : Mod [ " nb_moduleimpls " ] ,
" heures_cours " : Mod [ " heures_cours " ] ,
" heures_td " : Mod [ " heures_td " ] ,
" heures_tp " : Mod [ " heures_tp " ] ,
" ects " : Mod [ " ects " ] ,
}
)
columns_ids = [
" UE_acro " ,
" Mat_tit " ,
" Mod_tit " ,
" Mod_code " ,
" Mod_coef " ,
" Mod_sem " ,
" nb_moduleimpls " ,
" heures_cours " ,
" heures_td " ,
" heures_tp " ,
" ects " ,
]
titles = {
" UE_acro " : " UE " ,
" Mat_tit " : " Matière " ,
" Mod_tit " : " Module " ,
" Mod_code " : " Code " ,
" Mod_coef " : " Coef. " ,
" Mod_sem " : " Sem. " ,
" nb_moduleimpls " : " Nb utilisé " ,
" heures_cours " : " Cours (h) " ,
" heures_td " : " TD (h) " ,
" heures_tp " : " TP (h) " ,
" ects " : " ECTS " ,
}
title = (
""" Formation %(titre)s ( %(acronyme)s ) [version %(version)s ] code %(formation_code)s """
% F
)
tab = GenTable (
columns_ids = columns_ids ,
rows = T ,
titles = titles ,
2021-08-21 17:07:44 +02:00
origin = " Généré par %s le " % scu . sco_version . SCONAME
2021-02-05 21:35:21 +01:00
+ scu . timedate_human_repr ( )
+ " " ,
2020-09-26 16:19:37 +02:00
caption = title ,
html_caption = title ,
html_class = " table_leftalign " ,
2021-09-18 10:10:02 +02:00
base_url = " %s ?formation_id= %s " % ( request . base_url , formation_id ) ,
2020-09-26 16:19:37 +02:00
page_title = title ,
html_title = " <h2> " + title + " </h2> " ,
pdf_title = title ,
2021-07-28 17:03:54 +02:00
preferences = sco_preferences . SemPreferences ( ) ,
2020-09-26 16:19:37 +02:00
)
2021-09-16 00:15:10 +02:00
return tab . make_page ( format = format )
2020-09-26 16:19:37 +02:00
2022-02-02 22:04:18 +01:00
def ue_list_semestre_ids ( ue : dict ) :
2020-09-26 16:19:37 +02:00
""" Liste triée des numeros de semestres des modules dans cette UE
Il est recommandable que tous les modules d ' une UE aient le même indice de semestre.
Mais cela n ' a pas toujours été le cas dans les programmes pédagogiques officiels,
aussi ScoDoc laisse le choix .
"""
2021-11-17 10:28:51 +01:00
modules = sco_edit_module . module_list ( args = { " ue_id " : ue [ " ue_id " ] } )
return sorted ( list ( set ( [ mod [ " semestre_id " ] for mod in modules ] ) ) )
2022-02-02 22:04:18 +01:00
UE_PALETTE = [
2022-02-10 23:14:33 +01:00
" #B80004 " , # rouge
" #F97B3D " , # Orange Crayola
" #FEB40B " , # Honey Yellow
" #80CB3F " , # Yellow Green
" #05162E " , # Oxford Blue
" #548687 " , # Steel Teal
" #444054 " , # Independence
" #889696 " , # Spanish Gray
" #0CA4A5 " , # Viridian Green
2022-02-02 22:04:18 +01:00
]
def colorie_anciennes_ues ( ues : list [ UniteEns ] ) - > None :
""" Avant ScoDoc 9.2, les ue n ' avaient pas de couleurs
Met des défauts raisonnables
"""
nb_colors = len ( UE_PALETTE )
index = 0
last_sem_idx = 0
for ue in ues :
if ue . semestre_idx != last_sem_idx :
index = 0
2022-02-10 23:14:33 +01:00
last_sem_idx = ue . semestre_idx
2022-02-02 22:04:18 +01:00
if ue . color is None :
ue . color = UE_PALETTE [ index % nb_colors ]
index + = 1
db . session . add ( ue )
def ue_guess_color_default ( formation_id : int , default_semestre_idx : int ) - > str :
""" Un code couleur pour une nouvelle UE dans ce semestre """
nb_colors = len ( UE_PALETTE )
# UE existantes dans ce semestre:
nb_ues = UniteEns . query . filter (
UniteEns . formation_id == formation_id ,
UniteEns . semestre_idx == default_semestre_idx ,
) . count ( )
index = nb_ues
return UE_PALETTE [ index % nb_colors ]