2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2022-01-01 14:49:42 +01:00
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
2020-09-26 16:19:37 +02:00
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
""" Ajout/Modification/Supression formations
( portage from DTML )
"""
2021-08-01 10:16:16 +02:00
import flask
2021-09-18 10:10:02 +02:00
from flask import g , url_for , request
2021-08-01 10:16:16 +02:00
2021-12-09 11:52:46 +01:00
from app import db
from app import log
2021-12-29 19:30:49 +01:00
from app . models import SHORT_STR_LEN
2021-12-16 16:27:35 +01:00
from app . models . formations import Formation
2021-12-09 11:52:46 +01:00
from app . models . modules import Module
2022-01-06 21:23:29 +01:00
from app . models . ues import UniteEns
2022-04-12 17:12:51 +02:00
from app . models import ScolarNews
2021-12-09 11:52:46 +01:00
2021-06-19 23:21:37 +02:00
import app . scodoc . notesdb as ndb
import app . scodoc . sco_utils as scu
from app . scodoc . TrivialFormulator import TrivialFormulator , tf_error_message
2022-07-13 18:52:07 +02:00
from app . scodoc . sco_exceptions import ScoValueError , ScoNonEmptyFormationObject
2021-07-19 19:53:01 +02:00
2021-06-19 23:21:37 +02:00
from app . scodoc import html_sco_header
2022-10-23 23:28:24 +02:00
from app . scodoc import sco_cache
2021-06-19 23:21:37 +02:00
from app . scodoc import sco_codes_parcours
from app . scodoc import sco_edit_ue
from app . scodoc import sco_formations
from app . scodoc import sco_formsemestre
2020-09-26 16:19:37 +02:00
2021-09-24 20:35:50 +02:00
def formation_delete ( formation_id = None , dialog_confirmed = False ) :
2021-01-01 18:40:47 +01:00
""" Delete a formation """
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 ( " formation inexistante ! " )
F = F [ 0 ]
H = [
2021-07-29 16:31:15 +02:00
html_sco_header . sco_header ( page_title = " Suppression d ' une formation " ) ,
2020-09-26 16:19:37 +02:00
""" <h2>Suppression de la formation %(titre)s ( %(acronyme)s )</h2> """ % F ,
]
2021-08-19 10:28:35 +02:00
sems = sco_formsemestre . do_formsemestre_list ( { " formation_id " : formation_id } )
2020-09-26 16:19:37 +02:00
if sems :
H . append (
2022-06-08 17:42:52 +02:00
""" <p class= " warning " >Impossible de supprimer cette formation,
car les sessions suivantes l ' utilisent:</p>
< ul > """
2020-09-26 16:19:37 +02:00
)
for sem in sems :
H . append (
' <li><a href= " formsemestre_status?formsemestre_id= %(formsemestre_id)s " > %(titremois)s </a></li> '
% sem
)
2021-06-15 12:34:33 +02:00
H . append ( ' </ul><p><a href= " %s " >Revenir</a></p> ' % scu . NotesURL ( ) )
2020-09-26 16:19:37 +02:00
else :
if not dialog_confirmed :
2021-06-02 22:40:34 +02:00
return scu . confirm_dialog (
2020-09-26 16:19:37 +02:00
""" <h2>Confirmer la suppression de la formation %(titre)s ( %(acronyme)s ) ?</h2>
< p > < b > Attention : < / b > la suppression d ' une formation est <b>irréversible</b> et implique la supression de toutes les UE, matières et modules de la formation !
< / p >
"""
% F ,
OK = " Supprimer cette formation " ,
2021-06-15 12:34:33 +02:00
cancel_url = scu . NotesURL ( ) ,
2020-09-26 16:19:37 +02:00
parameters = { " formation_id " : formation_id } ,
)
else :
2021-08-20 01:09:55 +02:00
do_formation_delete ( F [ " formation_id " ] )
2020-09-26 16:19:37 +02:00
H . append (
""" <p>OK, formation supprimée.</p>
< p > < a class = " stdlink " href = " %s " > continuer < / a > < / p > """
2021-06-15 12:34:33 +02:00
% scu . NotesURL ( )
2020-09-26 16:19:37 +02:00
)
2021-07-29 10:19:00 +02:00
H . append ( html_sco_header . sco_footer ( ) )
2020-09-26 16:19:37 +02:00
return " \n " . join ( H )
2022-07-13 18:52:07 +02:00
def do_formation_delete ( formation_id ) :
2021-06-16 18:18:32 +02:00
""" delete a formation (and all its UE, matieres, modules)
2022-07-13 18:52:07 +02:00
Warning : delete all ues , will ask if there are validations !
2021-06-16 18:18:32 +02:00
"""
2022-07-13 18:52:07 +02:00
formation : Formation = Formation . query . get ( formation_id )
if formation is None :
return
acronyme = formation . acronyme
if formation . formsemestres . count ( ) :
raise ScoNonEmptyFormationObject (
type_objet = " formation " ,
msg = formation . titre ,
dest_url = url_for (
" notes.ue_table " , scodoc_dept = g . scodoc_dept , formation_id = formation . id
) ,
)
2022-10-23 23:28:24 +02:00
with sco_cache . DeferredSemCacheManager ( ) :
# Suppression des modules
for module in formation . modules :
db . session . delete ( module )
db . session . flush ( )
# Suppression des UEs
for ue in formation . ues :
sco_edit_ue . do_ue_delete ( ue , force = True )
2021-06-16 18:18:32 +02:00
2022-10-23 23:28:24 +02:00
db . session . delete ( formation )
2021-06-16 18:18:32 +02:00
# news
2022-04-12 17:12:51 +02:00
ScolarNews . add (
typ = ScolarNews . NEWS_FORM ,
2022-07-13 18:52:07 +02:00
obj = formation_id ,
text = f " Suppression de la formation { acronyme } " ,
2021-06-16 18:18:32 +02:00
)
2021-09-27 10:20:10 +02:00
def formation_create ( ) :
2021-01-01 18:40:47 +01:00
""" Creation d ' une formation """
2021-09-27 10:20:10 +02:00
return formation_edit ( create = True )
2020-09-26 16:19:37 +02:00
2021-09-27 10:20:10 +02:00
def formation_edit ( formation_id = None , create = False ) :
2021-01-01 18:40:47 +01:00
""" Edit or create a formation """
2020-09-26 16:19:37 +02:00
if create :
H = [
2021-07-29 16:31:15 +02:00
html_sco_header . sco_header ( page_title = " Création d ' une formation " ) ,
2020-09-26 16:19:37 +02:00
""" <h2>Création d ' une formation</h2>
< p class = " help " > Une " formation " décrit une filière , comme un DUT ou une Licence . La formation se subdivise en unités pédagogiques ( UE , matières , modules ) . Elle peut se diviser en plusieurs semestres ( ou sessions ) , qui seront mis en place séparément .
< / p >
< p > Le < tt > titre < / tt > est le nom complet , parfois adapté pour mieux distinguer les modalités ou versions de programme pédagogique . Le < tt > titre_officiel < / tt > est le nom complet du diplôme , qui apparaitra sur certains PV de jury de délivrance du diplôme .
< / p >
""" ,
]
submitlabel = " Créer cette formation "
initvalues = { " type_parcours " : sco_codes_parcours . DEFAULT_TYPE_PARCOURS }
is_locked = False
else :
# edit an existing formation
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 ( " formation inexistante ! " )
initvalues = F [ 0 ]
2021-08-20 01:09:55 +02:00
is_locked = sco_formations . formation_has_locked_sems ( formation_id )
2020-09-26 16:19:37 +02:00
submitlabel = " Modifier les valeurs "
H = [
2021-07-29 16:31:15 +02:00
html_sco_header . sco_header ( page_title = " Modification d ' une formation " ) ,
2020-09-26 16:19:37 +02:00
""" <h2>Modification de la formation %(acronyme)s </h2> """ % initvalues ,
]
if is_locked :
H . append (
' <p class= " warning " >Attention: Formation verrouillée, le type de parcours ne peut être modifié.</p> '
)
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 ( ) ,
2020-09-26 16:19:37 +02:00
(
( " formation_id " , { " default " : formation_id , " input_type " : " hidden " } ) ,
(
" acronyme " ,
{
" size " : 12 ,
" explanation " : " identifiant de la formation (par ex. DUT R&T) " ,
" allow_null " : False ,
} ,
) ,
(
" titre " ,
{
" size " : 80 ,
" explanation " : " nom complet de la formation (ex: DUT Réseaux et Télécommunications " ,
" allow_null " : False ,
} ,
) ,
(
" titre_officiel " ,
{
" size " : 80 ,
" explanation " : " nom officiel (pour les PV de jury) " ,
" allow_null " : False ,
} ,
) ,
(
" type_parcours " ,
{
" input_type " : " menu " ,
" title " : " Type de parcours " ,
" type " : " int " ,
" allowed_values " : sco_codes_parcours . FORMATION_PARCOURS_TYPES ,
" labels " : sco_codes_parcours . FORMATION_PARCOURS_DESCRS ,
" explanation " : " détermine notamment le nombre de semestres et les règles de validation d ' UE et de semestres (barres) " ,
" readonly " : is_locked ,
} ,
) ,
(
" formation_code " ,
{
" size " : 12 ,
" title " : " Code formation " ,
" explanation " : " code interne. Toutes les formations partageant le même code sont compatibles (compensation de semestres, capitalisation d ' UE). Laisser vide si vous ne savez pas, ou entrer le code d ' une formation existante. " ,
2021-12-29 19:30:49 +01:00
" validator " : lambda val , _ : len ( val ) < SHORT_STR_LEN ,
2020-09-26 16:19:37 +02:00
} ,
) ,
(
" code_specialite " ,
{
" size " : 12 ,
" title " : " Code spécialité " ,
" explanation " : " optionel: code utilisé pour échanger avec d ' autres logiciels et identifiant la filière ou spécialité (exemple: ASUR). N ' est utilisé que s ' il n ' y a pas de numéro de semestre. " ,
} ,
) ,
) ,
initvalues = initvalues ,
submitlabel = submitlabel ,
)
if tf [ 0 ] == 0 :
2021-07-29 10:19:00 +02:00
return " \n " . join ( H ) + tf [ 1 ] + html_sco_header . sco_footer ( )
2020-09-26 16:19:37 +02:00
elif tf [ 0 ] == - 1 :
2021-07-31 18:01:10 +02:00
return flask . redirect ( scu . NotesURL ( ) )
2020-09-26 16:19:37 +02:00
else :
# check unicity : constraint UNIQUE(acronyme,titre,version)
if create :
version = 1
else :
version = initvalues [ " version " ]
args = {
" acronyme " : tf [ 2 ] [ " acronyme " ] ,
" titre " : tf [ 2 ] [ " titre " ] ,
" version " : version ,
}
2021-02-03 22:00:41 +01:00
ndb . quote_dict ( args )
2021-08-19 10:28:35 +02:00
others = sco_formations . formation_list ( args = args )
2020-09-26 16:19:37 +02:00
if others and ( ( len ( others ) > 1 ) or others [ 0 ] [ " formation_id " ] != formation_id ) :
return (
" \n " . join ( H )
+ tf_error_message (
2022-05-26 03:55:03 +02:00
f """ Valeurs incorrectes: il existe déjà <a href= " {
url_for ( ' notes.ue_table ' , scodoc_dept = g . scodoc_dept , formation_id = others [ 0 ] [ " id " ] )
} " >une formation</a> avec même titre,
acronyme et version .
"""
2020-09-26 16:19:37 +02:00
)
+ tf [ 1 ]
2021-07-29 10:19:00 +02:00
+ html_sco_header . sco_footer ( )
2020-09-26 16:19:37 +02:00
)
#
if create :
2021-08-20 01:09:55 +02:00
formation_id = do_formation_create ( tf [ 2 ] )
2020-09-26 16:19:37 +02:00
else :
2021-08-20 01:09:55 +02:00
do_formation_edit ( tf [ 2 ] )
2021-08-09 10:09:04 +02:00
return flask . redirect (
url_for (
2021-10-17 23:19:26 +02:00
" notes.ue_table " , scodoc_dept = g . scodoc_dept , formation_id = formation_id
2021-08-09 10:09:04 +02:00
)
)
2020-09-26 16:19:37 +02:00
2021-08-20 01:09:55 +02:00
def do_formation_create ( args ) :
2021-06-12 22:43:22 +02:00
" create a formation "
2021-06-15 13:59:56 +02:00
cnx = ndb . GetDBConnexion ( )
2021-06-12 22:43:22 +02:00
# check unique acronyme/titre/version
a = args . copy ( )
2021-07-09 17:47:06 +02:00
if " formation_id " in a :
2021-06-12 22:43:22 +02:00
del a [ " formation_id " ]
2021-08-19 10:28:35 +02:00
F = sco_formations . formation_list ( args = a )
2021-06-12 22:43:22 +02:00
if len ( F ) > 0 :
log ( " do_formation_create: error: %d formations matching args= %s " % ( len ( F ) , a ) )
raise ScoValueError ( " Formation non unique ( %s ) ! " % str ( a ) )
# Si pas de formation_code, l'enleve (default SQL)
2021-07-09 17:47:06 +02:00
if " formation_code " in args and not args [ " formation_code " ] :
2021-06-12 22:43:22 +02:00
del args [ " formation_code " ]
#
2021-06-16 10:15:46 +02:00
r = sco_formations . _formationEditor . create ( cnx , args )
2021-06-12 22:43:22 +02:00
2022-04-12 17:12:51 +02:00
ScolarNews . add (
typ = ScolarNews . NEWS_FORM ,
2021-06-12 22:43:22 +02:00
text = " Création de la formation %(titre)s ( %(acronyme)s ) " % args ,
)
return r
2021-08-20 01:09:55 +02:00
def do_formation_edit ( args ) :
2020-09-26 16:19:37 +02:00
" edit a formation "
2022-07-08 23:58:27 +02:00
# On ne peut jamais supprimer le code formation:
if " formation_code " in args and not args [ " formation_code " ] :
del args [ " formation_code " ]
formation : Formation = Formation . query . get_or_404 ( args [ " formation_id " ] )
2020-09-26 16:19:37 +02:00
# On autorise la modif de la formation meme si elle est verrouillee
# car cela ne change que du cosmetique, (sauf eventuellement le code formation ?)
# mais si verrouillée on ne peut changer le type de parcours
2022-07-08 23:58:27 +02:00
if formation . has_locked_sems ( ) :
2021-07-09 17:47:06 +02:00
if " type_parcours " in args :
2020-09-26 16:19:37 +02:00
del args [ " type_parcours " ]
2022-07-08 23:58:27 +02:00
for field in formation . __dict__ :
if field and field [ 0 ] != " _ " and field in args :
setattr ( formation , field , args [ field ] )
db . session . add ( formation )
db . session . commit ( )
2021-12-16 16:27:35 +01:00
formation . invalidate_cached_sems ( )
2021-06-17 00:08:37 +02:00
2021-12-09 11:52:46 +01:00
def module_move ( module_id , after = 0 , redirect = True ) :
2021-06-17 00:08:37 +02:00
""" Move before/after previous one (decrement/increment numero) """
2021-12-09 11:52:46 +01:00
redirect = bool ( redirect )
module = Module . query . get_or_404 ( module_id )
2021-06-17 00:08:37 +02:00
after = int ( after ) # 0: deplace avant, 1 deplace apres
if after not in ( 0 , 1 ) :
2021-12-09 11:52:46 +01:00
raise ValueError ( f ' invalid value for " after " ( { after } ) ' )
if module . formation . is_apc ( ) :
# pas de matières, mais on prend tous les modules de même type de la formation
query = Module . query . filter_by (
semestre_id = module . semestre_id ,
formation = module . formation ,
module_type = module . module_type ,
)
else :
query = Module . query . filter_by ( matiere = module . matiere )
modules = query . order_by ( Module . numero , Module . code ) . all ( )
if len ( { o . numero for o in modules } ) != len ( modules ) :
# il y a des numeros identiques !
2021-12-17 13:42:39 +01:00
scu . objects_renumber ( db , modules )
2021-12-09 11:52:46 +01:00
if len ( modules ) > 1 :
idx = [ m . id for m in modules ] . index ( module . id )
2021-06-17 00:08:37 +02:00
neigh = None # object to swap with
if after == 0 and idx > 0 :
2021-12-09 11:52:46 +01:00
neigh = modules [ idx - 1 ]
elif after == 1 and idx < len ( modules ) - 1 :
neigh = modules [ idx + 1 ]
if neigh : # échange les numéros
module . numero , neigh . numero = neigh . numero , module . numero
db . session . add ( module )
db . session . add ( neigh )
db . session . commit ( )
2022-01-06 21:23:29 +01:00
module . formation . invalidate_cached_sems ( )
2021-06-17 00:08:37 +02:00
# redirect to ue_list page:
if redirect :
2021-08-09 10:09:04 +02:00
return flask . redirect (
url_for (
2021-12-09 11:52:46 +01:00
" notes.ue_table " ,
scodoc_dept = g . scodoc_dept ,
formation_id = module . formation . id ,
2021-12-17 14:30:38 +01:00
semestre_idx = module . ue . semestre_idx ,
2021-08-09 10:09:04 +02:00
)
)
2021-06-17 00:08:37 +02:00
2021-08-30 23:28:15 +02:00
def ue_move ( ue_id , after = 0 , redirect = 1 ) :
2021-06-17 00:08:37 +02:00
""" Move UE before/after previous one (decrement/increment numero) """
2022-01-06 21:23:29 +01:00
ue = UniteEns . query . get_or_404 ( ue_id )
2021-06-17 00:08:37 +02:00
redirect = int ( redirect )
after = int ( after ) # 0: deplace avant, 1 deplace apres
if after not in ( 0 , 1 ) :
raise ValueError ( ' invalid value for " after " ' )
2022-09-29 22:39:54 +02:00
others_q = ue . formation . ues . order_by ( UniteEns . numero )
if ue . formation . is_apc ( ) :
others_q = others_q . filter_by ( semestre_idx = ue . semestre_idx )
others = others_q . all ( )
2022-01-06 21:23:29 +01:00
if len ( { o . numero for o in others } ) != len ( others ) :
# il y a des numeros identiques !
scu . objects_renumber ( db , others )
2022-02-14 18:33:36 +01:00
ue . formation . invalidate_cached_sems ( )
2021-06-17 00:08:37 +02:00
if len ( others ) > 1 :
2022-01-06 21:23:29 +01:00
idx = [ u . id for u in others ] . index ( ue . id )
2021-06-17 00:08:37 +02:00
neigh = None # object to swap with
if after == 0 and idx > 0 :
neigh = others [ idx - 1 ]
elif after == 1 and idx < len ( others ) - 1 :
neigh = others [ idx + 1 ]
if neigh : #
# swap numero between partition and its neighbor
2022-01-06 21:23:29 +01:00
ue . numero , neigh . numero = neigh . numero , ue . numero
db . session . add ( ue )
db . session . add ( neigh )
db . session . commit ( )
ue . formation . invalidate_cached_sems ( )
2021-06-17 00:08:37 +02:00
# redirect to ue_list page
if redirect :
2021-08-09 10:09:04 +02:00
return flask . redirect (
url_for (
2021-10-17 23:19:26 +02:00
" notes.ue_table " ,
2021-08-09 10:09:04 +02:00
scodoc_dept = g . scodoc_dept ,
2022-01-06 21:23:29 +01:00
formation_id = ue . formation_id ,
semestre_idx = ue . semestre_idx ,
2021-08-09 10:09:04 +02:00
)
)