# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## """Form choix modules / responsables et creation formsemestre """ import flask from flask import url_for, flash from flask import g, request from flask_login import current_user from app import db from app.auth.models import User from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN from app.models import Module, ModuleImpl, Evaluation, EvaluationUEPoids, UniteEns from app.models import ScolarNews from app.models.formations import Formation from app.models.formsemestre import FormSemestre from app.models.but_refcomp import ApcParcours import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu from app.scodoc import sco_cache from app.scodoc import sco_groups from app import log from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc.sco_permissions import Permission from app.scodoc.sco_vdi import ApoEtapeVDI from app.scodoc import html_sco_header from app.scodoc import sco_codes_parcours from app.scodoc import sco_compute_moy from app.scodoc import sco_edit_module from app.scodoc import sco_etud from app.scodoc import sco_evaluation_db from app.scodoc import sco_formations from app.scodoc import sco_formsemestre from app.scodoc import sco_groups_copy from app.scodoc import sco_modalites from app.scodoc import sco_moduleimpl from app.scodoc import sco_cursus_dut from app.scodoc import sco_permissions_check from app.scodoc import sco_portal_apogee from app.scodoc import sco_preferences from app.scodoc import sco_users def _default_sem_title(formation): """Default title for a semestre in formation""" return formation.titre def formsemestre_createwithmodules(): """Page création d'un semestre""" H = [ html_sco_header.sco_header( page_title="Création d'un semestre", javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"], cssstyles=["css/autosuggest_inquisitor.css"], bodyOnLoad="init_tf_form('')", ), """

Mise en place d'un semestre de formation

""", ] r = do_formsemestre_createwithmodules() if isinstance(r, str): H.append(r) else: return r # response redirect return "\n".join(H) + html_sco_header.sco_footer() def formsemestre_editwithmodules(formsemestre_id): """Page modification semestre""" formsemestre: FormSemestre = FormSemestre.query.filter_by( id=formsemestre_id, dept_id=g.scodoc_dept_id ).first_or_404() H = [ html_sco_header.html_sem_header( "Modification du semestre", javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"], cssstyles=["css/autosuggest_inquisitor.css"], bodyOnLoad="init_tf_form('')", ) ] if not formsemestre.etat: H.append( f"""

{scu.icontag( "lock_img", border="0", title="Semestre verrouillé") }Ce semestre est verrouillé.

""" ) 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( """

Seuls les modules cochés font partie de ce semestre. Pour les retirer, les décocher et appuyer sur le bouton "modifier".

Attention : s'il y a déjà des évaluations dans un module, il ne peut pas être supprimé !

Les modules ont toujours un responsable. Par défaut, c'est le directeur des études.

Un semestre ne peut comporter qu'une seule UE "bonus sport/culture"

""" ) return "\n".join(H) + html_sco_header.sco_footer() def can_edit_sem(formsemestre_id: int = None, sem=None): """Return sem if user can edit it, False otherwise""" sem = sem or sco_formsemestre.get_formsemestre(formsemestre_id) if not current_user.has_permission(Permission.ScoImplement): # pas chef if not sem["resp_can_edit"] or current_user.id not in sem["responsables"]: return False return sem resp_fields = [ "responsable_id", "responsable_id2", "responsable_id3", "responsable_id4", ] def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = None): "Form choix modules / responsables et création formsemestre" vals = scu.get_request_args() # Fonction accessible à tous, contrôle d'acces à la main: if not current_user.has_permission(Permission.ScoImplement): if not edit: # il faut ScoImplement pour créer un semestre raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération") else: if not formsemestre.resp_can_edit or current_user.id not in ( u.id for u in formsemestre.responsables ): raise AccessDenied( "vous n'avez pas le droit d'effectuer cette opération" ) # Liste des enseignants avec form pour affichage / saisie avec suggestion # attention: il faut prendre ici tous les utilisateurs, même inactifs, car # les responsables de modules d'anciens semestres peuvent ne plus être actifs. # Mais la suggestion utilise get_user_list_xml() qui ne suggérera que les actifs. user_list = sco_users.get_user_list(with_inactives=True) uid2display = {} # user_name : forme pour affichage = "NOM Prenom (login)" for u in user_list: uid2display[u.id] = u.get_nomplogin() allowed_user_names = list(uid2display.values()) + [""] # formation_id = int(vals["formation_id"]) formation = Formation.query.get(formation_id) if formation is None: raise ScoValueError("Formation inexistante !") is_apc = formation.is_apc() if not edit: initvalues = {"titre": _default_sem_title(formation)} semestre_id = int(vals["semestre_id"]) module_ids_set = set() else: # setup form init values initvalues = formsemestre.to_dict() semestre_id = formsemestre.semestre_id # add associated modules to tf-checked: module_ids_existing = [modimpl.module.id for modimpl in formsemestre.modimpls] module_ids_set = set(module_ids_existing) initvalues["tf-checked"] = ["MI" + str(x) for x in module_ids_existing] for modimpl in formsemestre.modimpls: initvalues[f"MI{modimpl.module.id}"] = uid2display.get( modimpl.responsable_id, f"inconnu numéro {modimpl.responsable_id} resp. de {modimpl.id} !", ) for index, resp in enumerate(formsemestre.responsables): initvalues[resp_fields[index]] = uid2display.get(resp.id) # Liste des ID de semestres if formation.type_parcours is not None: parcours = sco_codes_parcours.get_parcours_from_code(formation.type_parcours) nb_sem = parcours.NB_SEM else: nb_sem = 10 # fallback, max 10 semestres if nb_sem == 1: semestre_id_list = [-1] else: if edit and is_apc: # en APC, ne permet pas de changer de semestre semestre_id_list = [formsemestre.semestre_id] else: semestre_id_list = list(range(1, nb_sem + 1)) if not is_apc: # propose "pas de semestre" seulement en classique semestre_id_list.insert(0, -1) semestre_id_labels = [] for sid in semestre_id_list: if sid == -1: semestre_id_labels.append("pas de semestres") else: semestre_id_labels.append(f"S{sid}") # Liste des modules dans cette formation if is_apc: # BUT: trie par type (res, sae), parcours, numéro modules = sorted( formation.modules, key=lambda m: m.sort_key_apc(), ) else: modules = ( Module.query.filter( Module.formation_id == formation_id, UniteEns.id == Module.ue_id ) .order_by(Module.module_type, UniteEns.numero, Module.numero) .all() ) # Pour regroupement des modules par semestres: semestre_ids = {} for mod in modules: semestre_ids[mod.semestre_id] = 1 semestre_ids = list(semestre_ids.keys()) semestre_ids.sort() modalites = sco_modalites.do_modalite_list() modalites_abbrv = [m["modalite"] for m in modalites] modalites_titles = [m["titre"] for m in modalites] # modform = [ ("formsemestre_id", {"input_type": "hidden"}), ("formation_id", {"input_type": "hidden", "default": formation_id}), ( "date_debut", { "title": "Date de début", # j/m/a "input_type": "datedmy", "explanation": "j/m/a", "size": 9, "allow_null": False, }, ), ( "date_fin", { "title": "Date de fin", # j/m/a "input_type": "datedmy", "explanation": "j/m/a", "size": 9, "allow_null": False, }, ), *[ ( field, { "input_type": "text_suggest", "size": 50, "title": "(Co-)Directeur(s) des études" if index else "Directeur des études", "explanation": "(facultatif) taper le début du nom et choisir dans le menu" if index else "(obligatoire) taper le début du nom et choisir dans le menu", "allowed_values": allowed_user_names, "allow_null": index, # > 0, # il faut au moins un responsable de semestre "text_suggest_options": { "script": url_for( "users.get_user_list_xml", scodoc_dept=g.scodoc_dept ) + "?", # "Users/get_user_list_xml?", "varname": "start", "json": False, "noresults": "Valeur invalide !", "timeout": 60000, }, }, ) for index, field in enumerate(resp_fields) ], ( "titre", { "size": 40, "title": "Nom de ce semestre", "explanation": f"""n'indiquez pas les dates, ni le semestre, ni la modalité dans le titre: ils seront automatiquement ajoutés """, }, ), ( "modalite", { "input_type": "menu", "title": "Modalité", "allowed_values": modalites_abbrv, "labels": modalites_titles, }, ), ] modform.append( ( "semestre_id", { "input_type": "menu", "title": "Semestre dans la formation", "allowed_values": semestre_id_list, "labels": semestre_id_labels, "explanation": "en BUT, on ne peut pas modifier le semestre après création" if is_apc else "", "attributes": ['onchange="change_semestre_id();"'] if is_apc else "", }, ), ) etapes = sco_portal_apogee.get_etapes_apogee_dept() # Propose les etapes renvoyées par le portail # et ajoute les étapes du semestre qui ne sont pas dans la liste (soit la liste a changé, soit l'étape a été ajoutée manuellement) etapes_set = {et[0] for et in etapes} if edit: for etape_vdi in formsemestre.etapes_apo_vdi(): if etape_vdi.etape not in etapes_set: etapes.append((etape_vdi.etape, "inconnue")) modform.append( ( "elt_help_apo", { "title": "Codes Apogée nécessaires pour inscrire les étudiants et exporter les notes en fin de semestre:", "input_type": "separator", }, ) ) mf_manual = { "size": 12, "template": '%(label)s%(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": '%(label)s%(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": '%(label)s%(elem)s', }, ) ) # 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": '%(label)s%(elem)s', "explanation": "saisie manuelle si votre étape n'est pas dans le menu", }, ) ) modform.append( ( "elt_sem_apo", { "size": 32, "title": "Element(s) Apogée:", "explanation": "associé(s) au résultat du semestre (ex: VRTW1). Inutile en BUT. Séparés par des virgules.", "allow_null": not sco_preferences.get_preference( "always_require_apo_sem_codes" ) or (formsemestre and formsemestre.modalite == "EXT"), }, ) ) modform.append( ( "elt_annee_apo", { "size": 32, "title": "Element(s) Apogée:", "explanation": "associé(s) au résultat de l'année (ex: VRT1A). Séparés par des virgules.", "allow_null": not sco_preferences.get_preference( "always_require_apo_sem_codes" ) or (formsemestre and formsemestre.modalite == "EXT"), }, ) ) if edit: formtit = f"""

Modifier les coefficients des UE capitalisées

Sélectionner les modules, leurs responsables et les étudiants à inscrire:

""" else: formtit = """

Sélectionner les modules et leurs responsables

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.

""" modform += [ ( "gestion_compensation_lst", { "input_type": "checkbox", "title": "Jurys", "allowed_values": ["X"], "explanation": "proposer compensations de semestres (parcours DUT)", "labels": [""], }, ), ( "gestion_semestrielle_lst", { "input_type": "checkbox", "title": "", "allowed_values": ["X"], "explanation": "formation semestrialisée (jurys avec semestres décalés)", "labels": [""], }, ), ] if current_user.has_permission(Permission.ScoImplement): modform += [ ( "resp_can_edit", { "input_type": "boolcheckbox", "title": "Autorisations", "explanation": "Autoriser le directeur des études à modifier ce semestre", }, ) ] modform += [ ( "resp_can_change_ens", { "input_type": "boolcheckbox", "title": "", "explanation": "Autoriser le directeur des études à modifier les enseignants", }, ), ( "ens_can_edit_eval", { "input_type": "boolcheckbox", "title": "", "explanation": """Autoriser tous les enseignants associés à un module à y créer des évaluations""", }, ), ( "bul_bgcolor", { "size": 8, "title": "Couleur fond des bulletins", "explanation": "version web seulement (ex: #ffeeee)", "validator": lambda val, _: len(val) < SHORT_STR_LEN, }, ), ( "bul_publish_xml_lst", { "input_type": "checkbox", "title": "Publication", "allowed_values": ["X"], "explanation": "publier le bulletin sur le portail étudiants", "labels": [""], }, ), ( "block_moyennes", { "input_type": "boolcheckbox", "title": "Bloquer moyennes", "explanation": "empêcher le calcul des moyennes d'UE et générale.", }, ), ( "block_moyenne_generale", { "input_type": "boolcheckbox", "title": "Pas de moyenne générale", "explanation": "ne pas calculer la moyenne générale indicative (en BUT)", }, ), ] # Choix des parcours if is_apc: ref_comp = formation.referentiel_competence if ref_comp: modform += [ ( "parcours", { "input_type": "checkbox", "vertical": True, "dom_id": "tf_module_parcours", "labels": [parcour.libelle for parcour in ref_comp.parcours], "allowed_values": [ str(parcour.id) for parcour in ref_comp.parcours ], "explanation": """Parcours proposés dans ce semestre. S'il s'agit d'un semestre de "tronc commun", ne pas indiquer de parcours.""", }, ) ] if edit: initvalues["parcours"] = [ str(parcour.id) for parcour in formsemestre.parcours ] else: modform += [ ( "parcours", { "input_type": "separator", "title": f"""{scu.EMO_WARNING } Pas de parcours: vérifier la formation """, }, ) ] # Choix des modules modform += [ ( "sep", { "input_type": "separator", "title": "", "template": f"{formtit}", }, ), ] 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"""""" else: templ_sep = ( f"""""" ) modform.append( ( "sep", { "input_type": "separator", "title": f"Semestre {semestre_id}", "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'" itemtemplate = f"""""" else: itemtemplate = f"""""" modform.append( ( "MI" + str(mod.id), { "input_type": "text_suggest", "size": 50, "withcheckbox": True, "title": "%s %s" % (mod.code or "", mod.titre or ""), "allowed_values": allowed_user_names, "template": itemtemplate, "text_suggest_options": { "script": url_for( "users.get_user_list_xml", scodoc_dept=g.scodoc_dept ) + "?", "varname": "start", "json": False, "noresults": "Valeur invalide !", "timeout": 60000, }, }, ) ) if nbmod == 0: modform.append( ( "sep", { "input_type": "separator", "title": "aucun module dans cette formation !!!", }, ) ) if edit: submitlabel = "Modifier ce semestre" else: submitlabel = "Créer ce semestre de formation" # # Etapes: if edit: n = 1 for etape_vdi in formsemestre.etapes_apo_vdi(): initvalues["etape_apo" + str(n)] = etape_vdi.etape initvalues["vdi_apo" + str(n)] = etape_vdi.vdi n += 1 # initvalues["gestion_compensation"] = initvalues.get("gestion_compensation", False) if initvalues["gestion_compensation"]: initvalues["gestion_compensation_lst"] = ["X"] else: initvalues["gestion_compensation_lst"] = [] if vals.get("tf_submitted", False) and "gestion_compensation_lst" not in vals: vals["gestion_compensation_lst"] = [] initvalues["gestion_semestrielle"] = initvalues.get("gestion_semestrielle", False) if initvalues["gestion_semestrielle"]: initvalues["gestion_semestrielle_lst"] = ["X"] else: initvalues["gestion_semestrielle_lst"] = [] if vals.get("tf_submitted", False) and "gestion_semestrielle_lst" not in vals: vals["gestion_semestrielle_lst"] = [] initvalues["bul_hide_xml"] = initvalues.get("bul_hide_xml", False) if not initvalues["bul_hide_xml"]: initvalues["bul_publish_xml_lst"] = ["X"] else: initvalues["bul_publish_xml_lst"] = [] if vals.get("tf_submitted", False) and "bul_publish_xml_lst" not in vals: vals["bul_publish_xml_lst"] = [] # tf = TrivialFormulator( request.base_url, vals, modform, submitlabel=submitlabel, cancelbutton="Annuler", top_buttons=True, initvalues=initvalues, ) msg = "" if tf[0] == 1: # check dates if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]): msg = '' 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 = '' if tf[0] == 0 or msg: return f"""

Formation {formation.titre} ({formation.acronyme}), version { formation.version}, code {formation.formation_code}

{msg} {tf[1]} """ elif tf[0] == -1: return "

annulation

" else: if tf[2]["gestion_compensation_lst"]: tf[2]["gestion_compensation"] = True else: tf[2]["gestion_compensation"] = False if tf[2]["gestion_semestrielle_lst"]: tf[2]["gestion_semestrielle"] = True else: tf[2]["gestion_semestrielle"] = False if tf[2]["bul_publish_xml_lst"]: tf[2]["bul_hide_xml"] = False else: tf[2]["bul_hide_xml"] = True # remap les identifiants de responsables: for field in resp_fields: tf[2][field] = User.get_user_id_from_nomplogin(tf[2][field]) tf[2]["responsables"] = [] for field in resp_fields: if tf[2][field]: tf[2]["responsables"].append(tf[2][field]) for module_id in tf[2]["tf-checked"]: mod_resp_id = User.get_user_id_from_nomplogin(tf[2][module_id]) if mod_resp_id is None: # Si un module n'a pas de responsable (ou inconnu), # l'affecte au 1er directeur des etudes: mod_resp_id = tf[2]["responsable_id"] tf[2][module_id] = mod_resp_id # etapes: tf[2]["etapes"] = [] if etapes: # menus => case supplementaire pour saisie manuelle, indicée 0 start_i = 0 else: start_i = 1 for n in range(start_i, scu.EDIT_NB_ETAPES + 1): tf[2]["etapes"].append( ApoEtapeVDI( etape=tf[2]["etape_apo" + str(n)], vdi=tf[2]["vdi_apo" + str(n)] ) ) # Modules sélectionnés: # (retire le "MI" du début du nom de champs) module_ids_checked = [int(x[2:]) for x in tf[2]["tf-checked"]] _formsemestre_check_ue_bonus_unicity(module_ids_checked) if not edit: if is_apc: _formsemestre_check_module_list( module_ids_checked, tf[2]["semestre_id"] ) # création du semestre formsemestre_id = sco_formsemestre.do_formsemestre_create(tf[2]) # création des modules for module_id in module_ids_checked: modargs = { "module_id": module_id, "formsemestre_id": formsemestre_id, "responsable_id": tf[2][f"MI{module_id}"], } _ = sco_moduleimpl.do_moduleimpl_create(modargs) else: # Modification du semestre: # on doit creer les modules nouvellement selectionnés # modifier ceux à modifier, et DETRUIRE ceux qui ne sont plus selectionnés. # Note: la destruction échouera s'il y a des objets dépendants # (eg des évaluations définies) module_ids_tocreate = [ x for x in module_ids_checked if not x in module_ids_existing ] if is_apc: _formsemestre_check_module_list( module_ids_tocreate, tf[2]["semestre_id"] ) # modules existants à modifier module_ids_toedit = [ x for x in module_ids_checked if x in module_ids_existing ] # modules à détruire module_ids_todelete = [ x for x in module_ids_existing if not x in module_ids_checked ] # sco_formsemestre.do_formsemestre_edit(tf[2]) # msg = [] for module_id in module_ids_tocreate: modargs = { "module_id": module_id, "formsemestre_id": formsemestre.id, "responsable_id": tf[2]["MI" + str(module_id)], } moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(modargs) mod = sco_edit_module.module_list({"module_id": module_id})[0] msg += [ "création de %s (%s)" % (mod["code"] or "?", mod["titre"] or "?") ] # INSCRIPTIONS DES ETUDIANTS log( 'inscription module: %s = "%s"' % ("%s!group_id" % module_id, tf[2]["%s!group_id" % module_id]) ) group_id = tf[2]["%s!group_id" % module_id] if group_id: etudids = [ x["etudid"] for x in sco_groups.get_group_members(group_id) ] log( "inscription module:module_id=%s,moduleimpl_id=%s: %s" % (module_id, moduleimpl_id, etudids) ) sco_moduleimpl.do_moduleimpl_inscrit_etuds( moduleimpl_id, formsemestre.id, etudids, ) msg += [ "inscription de %d étudiants au module %s" % (len(etudids), mod["code"] or "(module sans code)") ] else: log( "inscription module:module_id=%s,moduleimpl_id=%s: aucun etudiant inscrit" % (module_id, moduleimpl_id) ) # ok, diag = formsemestre_delete_moduleimpls( formsemestre.id, module_ids_todelete ) msg += diag for module_id in module_ids_toedit: moduleimpl_id = sco_moduleimpl.moduleimpl_list( formsemestre_id=formsemestre.id, module_id=module_id )[0]["moduleimpl_id"] modargs = { "moduleimpl_id": moduleimpl_id, "module_id": module_id, "formsemestre_id": formsemestre.id, "responsable_id": tf[2]["MI" + str(module_id)], } sco_moduleimpl.do_moduleimpl_edit( modargs, formsemestre_id=formsemestre.id ) mod = sco_edit_module.module_list({"module_id": module_id})[0] # --- Association des parcours if formsemestre is None: formsemestre = FormSemestre.query.get(formsemestre_id) if "parcours" in tf[2]: formsemestre.parcours = [ ApcParcours.query.get(int(parcour_id_str)) for parcour_id_str in tf[2]["parcours"] ] db.session.add(formsemestre) db.session.commit() # --- Crée ou met à jour les groupes de parcours BUT formsemestre.setup_parcours_groups() # peut être nécessaire dans certains cas: formsemestre.update_inscriptions_parcours_from_groups() # --- Fin if edit: if msg: return f"""
Attention !
  • {"
  • ".join(msg)}
{"

Modification effectuée

" if ok else "

Modules non modifiés

" } retour au tableau de bord """ else: flash("Semestre modifié") return flask.redirect( url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, ) ) 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 == sco_codes_parcours.UE_SPORT} if len(ues_bonus) > 1: raise ScoValueError( """Les modules de bonus sélectionnés ne sont pas tous dans la même UE bonus. Changez la sélection ou modifiez la structure du programme de formation.""", dest_url="javascript:history.back();", ) def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del): """Delete moduleimpls module_ids_to_del: list of module_id (warning: not moduleimpl) Moduleimpls must have no associated evaluations. """ ok = True msg = [] for module_id in module_ids_to_del: module = Module.query.get(module_id) if module is None: continue # ignore invalid ids modimpls = ModuleImpl.query.filter_by( formsemestre_id=formsemestre_id, module_id=module_id ) for modimpl in modimpls: nb_evals = modimpl.evaluations.count() if nb_evals > 0: msg += [ f"""impossible de supprimer {module.code} ({module.titre or ""}) car il y a {nb_evals} évaluations définies (supprimez-les d\'abord)""" ] ok = False else: msg += [f"""suppression de {module.code} ({module.titre or ""})"""] db.session.delete(modimpl) if ok: db.session.commit() else: db.session.rollback() return ok, msg def formsemestre_clone(formsemestre_id): """ Formulaire clonage d'un semestre """ sem = sco_formsemestre.get_formsemestre(formsemestre_id) # Liste des enseignants avec forme pour affichage / saisie avec suggestion user_list = sco_users.get_user_list() uid2display = {} # user_name : forme pour affichage = "NOM Prenom (login)" for u in user_list: uid2display[u.id] = u.get_nomplogin() allowed_user_names = list(uid2display.values()) + [""] initvalues = { "formsemestre_id": sem["formsemestre_id"], "responsable_id": uid2display.get( sem["responsables"][0], sem["responsables"][0] ), } H = [ html_sco_header.html_sem_header( "Copie du semestre", javascripts=["libjs/AutoSuggest.js"], cssstyles=["css/autosuggest_inquisitor.css"], bodyOnLoad="init_tf_form('')", ), """

Cette opération duplique un semestre: on reprend les mêmes modules et responsables. Aucun étudiant n'est inscrit.

""", ] 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 = '' if tf[0] == 0 or msg: return "".join(H) + msg + tf[1] + html_sco_header.sco_footer() elif tf[0] == -1: # cancel return flask.redirect( "formsemestre_status?formsemestre_id=%s" % formsemestre_id ) else: new_formsemestre_id = do_formsemestre_clone( formsemestre_id, User.get_user_id_from_nomplogin(tf[2]["responsable_id"]), tf[2]["date_debut"], tf[2]["date_fin"], clone_evaluations=tf[2]["clone_evaluations"], clone_partitions=tf[2]["clone_partitions"], ) return flask.redirect( "formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé" % new_formsemestre_id ) def do_formsemestre_clone( orig_formsemestre_id, responsable_id, # new resp. date_debut, date_fin, # 'dd/mm/yyyy' clone_evaluations=False, clone_partitions=False, ): """Clone a semestre: make copy, same modules, same options, same resps, same partitions. New dates, responsable_id """ log("cloning %s" % orig_formsemestre_id) orig_sem = sco_formsemestre.get_formsemestre(orig_formsemestre_id) cnx = ndb.GetDBConnexion() # 1- create sem args = orig_sem.copy() del args["formsemestre_id"] args["responsables"] = [responsable_id] args["date_debut"] = date_debut args["date_fin"] = date_fin args["etat"] = 1 # non verrouillé formsemestre_id = sco_formsemestre.do_formsemestre_create(args) log("created formsemestre %s" % formsemestre_id) # 2- create moduleimpls mods_orig = sco_moduleimpl.moduleimpl_list(formsemestre_id=orig_formsemestre_id) for mod_orig in mods_orig: args = mod_orig.copy() args["formsemestre_id"] = formsemestre_id mid = sco_moduleimpl.do_moduleimpl_create(args) # copy notes_modules_enseignants ens = sco_moduleimpl.do_ens_list( args={"moduleimpl_id": mod_orig["moduleimpl_id"]} ) for e in ens: args = e.copy() args["moduleimpl_id"] = mid sco_moduleimpl.do_ens_create(args) # optionally, copy evaluations if clone_evaluations: for e in Evaluation.query.filter_by( moduleimpl_id=mod_orig["moduleimpl_id"] ): # copie en enlevant la date new_eval = e.clone(not_copying=("jour", "moduleimpl_id")) new_eval.moduleimpl_id = mid # Copie les poids APC de l'évaluation new_eval.set_ue_poids_dict(e.get_ue_poids_dict()) db.session.commit() # 3- copy uecoefs objs = sco_formsemestre.formsemestre_uecoef_list( cnx, args={"formsemestre_id": orig_formsemestre_id} ) for obj in objs: args = obj.copy() args["formsemestre_id"] = formsemestre_id _ = sco_formsemestre.formsemestre_uecoef_create(cnx, args) # NB: don't copy notes_formsemestre_custommenu (usually specific) # 4- Copy new style preferences prefs = sco_preferences.SemPreferences(orig_formsemestre_id) if orig_formsemestre_id in prefs.base_prefs.prefs: for pname in prefs.base_prefs.prefs[orig_formsemestre_id]: if not prefs.is_global(pname): pvalue = prefs[pname] try: prefs.base_prefs.set(formsemestre_id, pname, pvalue) except ValueError: log( "do_formsemestre_clone: ignoring old preference %s=%s for %s" % (pname, pvalue, formsemestre_id) ) # 5- Copy formules utilisateur objs = sco_compute_moy.formsemestre_ue_computation_expr_list( cnx, args={"formsemestre_id": orig_formsemestre_id} ) for obj in objs: args = obj.copy() args["formsemestre_id"] = formsemestre_id _ = sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, args) # 5- Copy partitions and groups if clone_partitions: sco_groups_copy.clone_partitions_and_groups( orig_formsemestre_id, formsemestre_id ) return formsemestre_id # --------------------------------------------------------------------------------------- def formsemestre_associate_new_version( formsemestre_id, other_formsemestre_ids=[], dialog_confirmed=False, ): """Formulaire changement formation d'un semestre""" formsemestre_id = int(formsemestre_id) other_formsemestre_ids = [int(x) for x in other_formsemestre_ids] if not dialog_confirmed: # dresse le liste des semestres de la meme formation et version formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) othersems = sco_formsemestre.do_formsemestre_list( args={ "formation_id": formsemestre.formation.id, "version": formsemestre.formation.version, "etat": "1", }, ) H = [] for s in othersems: if ( s["formsemestre_id"] == formsemestre_id or s["formsemestre_id"] in other_formsemestre_ids ): checked = 'checked="checked"' else: checked = "" if s["formsemestre_id"] == formsemestre_id: disabled = 'disabled="1"' else: disabled = "" H.append( f"""
{s['titremois']}
""" ) return scu.confirm_dialog( f"""

Associer à une nouvelle version de formation non verrouillée ?

Le programme pédagogique ("formation") va être dupliqué pour que vous puissiez le modifier sans affecter les autres semestres. Les autres paramètres (étudiants, notes...) du semestre seront inchangés.

Veillez à ne pas abuser de cette possibilité, car créer trop de versions de formations va vous compliquer la gestion (à vous de garder trace des différences et à ne pas vous tromper par la suite...).

Si vous souhaitez créer un programme pour de futurs semestres, utilisez plutôt Créer une nouvelle version.

Si vous voulez associer aussi d'autres semestres à la nouvelle version, cochez-les:

""" + "".join(H) + "
", OK="Associer ces semestres à une nouvelle version", dest_url="", cancel_url=url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ), parameters={"formsemestre_id": formsemestre_id}, ) else: do_formsemestres_associate_new_version( [formsemestre_id] + other_formsemestre_ids ) flash("Semestre associé à une nouvelle version de la formation") return flask.redirect( url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ) ) def do_formsemestres_associate_new_version(formsemestre_ids): """Cree une nouvelle version de la formation du semestre, et y rattache les semestres. Tous les moduleimpl sont ré-associés à la nouvelle formation, ainsi que les decisions de jury si elles existent (codes d'UE validées). Les semestre doivent tous appartenir à la meme version de la formation """ log(f"do_formsemestres_associate_new_version {formsemestre_ids}") if not formsemestre_ids: return # Check: tous de la même formation assert isinstance(formsemestre_ids[0], int) sem = sco_formsemestre.get_formsemestre(formsemestre_ids[0]) formation_id = sem["formation_id"] for formsemestre_id in formsemestre_ids[1:]: assert isinstance(formsemestre_id, int) sem = sco_formsemestre.get_formsemestre(formsemestre_id) if formation_id != sem["formation_id"]: raise ScoValueError("les semestres ne sont pas tous de la même formation !") cnx = ndb.GetDBConnexion() # New formation: ( formation_id, modules_old2new, ues_old2new, ) = sco_formations.formation_create_new_version(formation_id, redirect=False) # Log new ues: for ue_id in ues_old2new: ue = UniteEns.query.get(ue_id) new_ue = UniteEns.query.get(ues_old2new[ue_id]) assert ue.semestre_idx == new_ue.semestre_idx log(f"{ue} -> {new_ue}") # Log new modules for module_id in modules_old2new: mod = Module.query.get(module_id) new_mod = Module.query.get(modules_old2new[module_id]) assert mod.semestre_id == new_mod.semestre_id log(f"{mod} -> {new_mod}") # re-associate for formsemestre_id in formsemestre_ids: sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem["formation_id"] = formation_id sco_formsemestre.do_formsemestre_edit(sem, cnx=cnx, html_quote=False) _reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new) cnx.commit() def _reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new): """Associe les moduleimpls d'un semestre existant à un autre programme et met à jour les décisions de jury (validations d'UE). """ # re-associate moduleimpls to new modules: modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) for mod in modimpls: mod["module_id"] = modules_old2new[mod["module_id"]] sco_moduleimpl.do_moduleimpl_edit(mod, formsemestre_id=formsemestre_id) # Update poids des évaluations # les poids associent les évaluations aux UE (qui ont changé d'id) for poids in EvaluationUEPoids.query.filter( EvaluationUEPoids.evaluation_id == Evaluation.id, Evaluation.moduleimpl_id == ModuleImpl.id, ModuleImpl.formsemestre_id == formsemestre_id, ): poids.ue_id = ues_old2new[poids.ue_id] db.session.add(poids) db.session.commit() # update decisions: events = sco_etud.scolar_events_list(cnx, args={"formsemestre_id": formsemestre_id}) for e in events: if e["ue_id"]: e["ue_id"] = ues_old2new[e["ue_id"]] sco_etud.scolar_events_edit(cnx, e) validations = sco_cursus_dut.scolar_formsemestre_validation_list( cnx, args={"formsemestre_id": formsemestre_id} ) for e in validations: if e["ue_id"]: e["ue_id"] = ues_old2new[e["ue_id"]] # log('e=%s' % e ) sco_cursus_dut.scolar_formsemestre_validation_edit(cnx, e) def formsemestre_delete(formsemestre_id): """Delete a formsemestre (affiche avertissements)""" formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) H = [ html_sco_header.html_sem_header("Suppression du semestre"), """
Attention !

A n'utiliser qu'en cas d'erreur lors de la saisie d'une formation. Normalement, un semestre ne doit jamais être supprimé (on perd la mémoire des notes et de tous les événements liés à ce semestre !).

Tous les modules de ce semestre seront supprimés. Ceci n'est possible que si :

  1. aucune décision de jury n'a été entrée dans ce semestre;
  2. et aucun étudiant de ce semestre ne le compense avec un autre semestre.
""", ] evals = sco_evaluation_db.do_evaluation_list_in_formsemestre(formsemestre_id) if evals: H.append( f"""

Attention: il y a {len(evals)} évaluations dans ce semestre (sa suppression entrainera l'effacement définif des notes) !

""" ) submit_label = ( f"Confirmer la suppression (du semestre et des {len(evals)} évaluations !)" ) else: submit_label = "Confirmer la suppression du semestre" tf = TrivialFormulator( request.base_url, scu.get_request_args(), (("formsemestre_id", {"input_type": "hidden"}),), initvalues=formsemestre.to_dict(), submitlabel=submit_label, cancelbutton="Annuler", ) if tf[0] == 0: if formsemestre_has_decisions_or_compensations(formsemestre_id): H.append( """

Ce semestre ne peut pas être supprimé ! (il y a des décisions de jury ou des compensations par d'autres semestres)

""" ) else: H.append(tf[1]) return "\n".join(H) + html_sco_header.sco_footer() elif tf[0] == -1: # cancel return flask.redirect( url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ) ) else: return flask.redirect( "formsemestre_delete2?formsemestre_id=" + str(formsemestre_id) ) def formsemestre_delete2(formsemestre_id, dialog_confirmed=False): """Delete a formsemestre (confirmation)""" # Confirmation dialog if not dialog_confirmed: return scu.confirm_dialog( """

Vous voulez vraiment supprimer ce semestre ???

(opération irréversible)

""", dest_url="", cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id, parameters={"formsemestre_id": formsemestre_id}, ) # Bon, s'il le faut... do_formsemestre_delete(formsemestre_id) return flask.redirect(scu.ScoURL() + "?head_message=Semestre%20supprimé") def formsemestre_has_decisions_or_compensations(formsemestre_id): """True if decision de jury dans ce semestre ou bien compensation de ce semestre par d'autre ssemestres. """ r = ndb.SimpleDictFetch( """SELECT v.id AS formsemestre_validation_id, v.* FROM scolar_formsemestre_validation v WHERE v.formsemestre_id = %(formsemestre_id)s OR v.compense_formsemestre_id = %(formsemestre_id)s""", {"formsemestre_id": formsemestre_id}, ) return r def do_formsemestre_delete(formsemestre_id): """delete formsemestre, and all its moduleimpls. No checks, no warnings: erase all ! """ cnx = ndb.GetDBConnexion() sem = sco_formsemestre.get_formsemestre(formsemestre_id) sco_cache.EvaluationCache.invalidate_sem(formsemestre_id) # --- Destruction des modules de ce semestre mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) for mod in mods: # evaluations evals = sco_evaluation_db.do_evaluation_list( args={"moduleimpl_id": mod["moduleimpl_id"]} ) for e in evals: ndb.SimpleQuery( "DELETE FROM notes_notes WHERE evaluation_id=%(evaluation_id)s", e, ) ndb.SimpleQuery( "DELETE FROM notes_notes_log WHERE evaluation_id=%(evaluation_id)s", e, ) ndb.SimpleQuery( "DELETE FROM notes_evaluation WHERE id=%(evaluation_id)s", e, ) sco_moduleimpl.do_moduleimpl_delete( mod["moduleimpl_id"], formsemestre_id=formsemestre_id ) # --- Desinscription des etudiants cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) req = "DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Suppression des evenements req = "DELETE FROM scolar_events WHERE formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Suppression des appreciations req = "DELETE FROM notes_appreciations WHERE formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Supression des validations (!!!) req = "DELETE FROM scolar_formsemestre_validation WHERE formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Supression des references a ce semestre dans les compensations: req = "UPDATE scolar_formsemestre_validation SET compense_formsemestre_id=NULL WHERE compense_formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Suppression des autorisations req = "DELETE FROM scolar_autorisation_inscription WHERE origin_formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Suppression des coefs d'UE capitalisées req = "DELETE FROM notes_formsemestre_uecoef WHERE formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Suppression des item du menu custom req = "DELETE FROM notes_formsemestre_custommenu WHERE formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Suppression des formules req = "DELETE FROM notes_formsemestre_ue_computation_expr WHERE formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Suppression des preferences req = "DELETE FROM sco_prefs WHERE formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Suppression des groupes et partitions req = """DELETE FROM group_membership WHERE group_id IN (SELECT gm.group_id FROM group_membership gm, partition p, group_descr gd WHERE gm.group_id = gd.id AND gd.partition_id = p.id AND p.formsemestre_id=%(formsemestre_id)s) """ cursor.execute(req, {"formsemestre_id": formsemestre_id}) req = """DELETE FROM group_descr WHERE id IN (SELECT gd.id FROM group_descr gd, partition p WHERE gd.partition_id = p.id AND p.formsemestre_id=%(formsemestre_id)s) """ cursor.execute(req, {"formsemestre_id": formsemestre_id}) req = "DELETE FROM partition WHERE formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Responsables req = """DELETE FROM notes_formsemestre_responsables WHERE formsemestre_id=%(formsemestre_id)s""" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Etapes req = """DELETE FROM notes_formsemestre_etapes WHERE formsemestre_id=%(formsemestre_id)s""" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Destruction du semestre sco_formsemestre._formsemestreEditor.delete(cnx, formsemestre_id) # news ScolarNews.add( typ=ScolarNews.NEWS_SEM, obj=formsemestre_id, text="Suppression du semestre %(titre)s" % sem, ) # --------------------------------------------------------------------------------------- def formsemestre_edit_options(formsemestre_id): """dialog to change formsemestre options (accessible par ScoImplement ou dir. etudes) """ log("formsemestre_edit_options") ok, err = sco_permissions_check.check_access_diretud(formsemestre_id) if not ok: return err return sco_preferences.SemPreferences(formsemestre_id).edit(categories=["bul"]) def formsemestre_change_lock(formsemestre_id) -> None: """Change etat (verrouille si ouvert, déverrouille si fermé) nota: etat (1 ouvert, 0 fermé) """ ok, err = sco_permissions_check.check_access_diretud(formsemestre_id) if not ok: return err sem = sco_formsemestre.get_formsemestre(formsemestre_id) etat = not sem["etat"] args = {"formsemestre_id": formsemestre_id, "etat": etat} sco_formsemestre.do_formsemestre_edit(args) def formsemestre_change_publication_bul( formsemestre_id, dialog_confirmed=False, redirect=True ): """Change etat publication bulletins sur portail""" ok, err = sco_permissions_check.check_access_diretud(formsemestre_id) if not ok: return err sem = sco_formsemestre.get_formsemestre(formsemestre_id) etat = not sem["bul_hide_xml"] if not dialog_confirmed: if etat: msg = "non" else: msg = "" return scu.confirm_dialog( "

Confirmer la %s publication des bulletins ?

" % msg, helpmsg="""Il est parfois utile de désactiver la diffusion des bulletins, par exemple pendant la tenue d'un jury ou avant harmonisation des notes.
Ce réglage n'a d'effet que si votre établissement a interfacé ScoDoc et un portail étudiant. """, dest_url="", cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id, parameters={"bul_hide_xml": etat, "formsemestre_id": formsemestre_id}, ) args = {"formsemestre_id": formsemestre_id, "bul_hide_xml": etat} sco_formsemestre.do_formsemestre_edit(args) if redirect: return flask.redirect( "formsemestre_status?formsemestre_id=%s" % formsemestre_id ) return None def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): """Changement manuel des coefficients des UE capitalisées.""" from app.scodoc import notes_table ok, err = sco_permissions_check.check_access_diretud(formsemestre_id) if not ok: return err sem = sco_formsemestre.get_formsemestre(formsemestre_id) footer = html_sco_header.sco_footer() help = """

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.

ScoDoc calcule normalement le coefficient d'une UE comme la somme des coefficients des modules qui la composent.

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.

Indiquez "auto" (ou laisser vide) pour que ScoDoc calcule automatiquement le coefficient, ou bien entrez une valeur (nombre réel).

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 rouge sont à changer.

Les coefficients indiqués ici ne s'appliquent que pour le traitement des UE capitalisées.

""" H = [ html_sco_header.html_sem_header("Coefficients des UE du semestre"), help, ] # ues, modimpls = notes_table.get_sem_ues_modimpls(formsemestre_id) for ue in ues: ue["sum_coefs"] = sum( [ mod["module"]["coefficient"] for mod in modimpls if mod["module"]["ue_id"] == ue["ue_id"] ] ) cnx = ndb.GetDBConnexion() initvalues = {"formsemestre_id": formsemestre_id} form = [("formsemestre_id", {"input_type": "hidden"})] for ue in ues: coefs = sco_formsemestre.formsemestre_uecoef_list( cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]} ) if coefs: initvalues["ue_" + str(ue["ue_id"])] = coefs[0]["coefficient"] else: initvalues["ue_" + str(ue["ue_id"])] = "auto" descr = { "size": 10, "title": ue["acronyme"], "explanation": "somme coefs modules = %s" % ue["sum_coefs"], } if ue["ue_id"] == err_ue_id: descr["dom_id"] = "erroneous_ue" form.append(("ue_" + str(ue["ue_id"]), descr)) tf = TrivialFormulator( request.base_url, scu.get_request_args(), form, submitlabel="Changer les coefficients", cancelbutton="Annuler", initvalues=initvalues, ) if tf[0] == 0: return "\n".join(H) + tf[1] + footer elif tf[0] == -1: return "

annulation

" else: # change values # 1- supprime les coef qui ne sont plus forcés # 2- modifie ou cree les coefs ue_deleted = [] ue_modified = [] msg = [] for ue in ues: val = tf[2]["ue_" + str(ue["ue_id"])] coefs = sco_formsemestre.formsemestre_uecoef_list( cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]} ) if val == "" or val == "auto": # supprime ce coef (il sera donc calculé automatiquement) if coefs: ue_deleted.append(ue) else: try: val = float(val) if (not coefs) or (coefs[0]["coefficient"] != val): ue["coef"] = val ue_modified.append(ue) except: ok = False msg.append( "valeur invalide (%s) pour le coefficient de l'UE %s" % (val, ue["acronyme"]) ) if not ok: return ( "\n".join(H) + "

" % "
  • ".join(msg) + tf[1] + footer ) # apply modifications for ue in ue_modified: sco_formsemestre.do_formsemestre_uecoef_edit_or_create( cnx, formsemestre_id, ue["ue_id"], ue["coef"] ) for ue in ue_deleted: sco_formsemestre.do_formsemestre_uecoef_delete( cnx, formsemestre_id, ue["ue_id"] ) if ue_modified or ue_deleted: message = ["""

    Modification effectuées

    """] if ue_modified: message.append("""

    Coefs modifiés dans les UE:

      """) for ue in ue_modified: message.append("
    • %(acronyme)s : %(coef)s
    • " % ue) message.append("
    ") if ue_deleted: message.append("""

    Coefs supprimés dans les UE:

      """) for ue in ue_deleted: message.append("
    • %(acronyme)s
    • " % ue) message.append("
    ") else: message = ["""

    Aucune modification

    """] sco_cache.invalidate_formsemestre( formsemestre_id=formsemestre_id ) # > modif coef UE cap (modifs notes de _certains_ etudiants) return f"""{html_sco_header.html_sem_header("Coefficients des UE du semestre")} {" ".join(message)}

    Revenir au tableau de bord

    {footer} """ # ----- identification externe des sessions (pour SOJA et autres logiciels) def get_formsemestre_session_id(sem, F, parcours): """Identifiant de session pour ce semestre Obsolete: vooir FormSemestre.session_id() #sco7 """ imputation_dept = sco_preferences.get_preference( "ImputationDept", sem["formsemestre_id"] ) if not imputation_dept: imputation_dept = sco_preferences.get_preference("DeptName") or "" imputation_dept = imputation_dept.upper() parcours_type = parcours.NAME modalite = sem["modalite"] modalite = ( (modalite or "").replace("FAP", "FA").replace("APP", "FA") ) # exception pour code Apprentissage if sem["semestre_id"] > 0: decale = scu.sem_decale_str(sem) semestre_id = "S%d" % sem["semestre_id"] + decale else: semestre_id = F["code_specialite"] annee_sco = str(scu.annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"])) return scu.sanitize_string( "-".join((imputation_dept, parcours_type, modalite, semestre_id, annee_sco)) )
  • %(label)sResponsableInscrire
    %(label)sResponsable
    %(label)s %(elem)s {fcg}
    %(label)s %(elem)s