# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # ScoDoc # # Copyright (c) 1999 - 2021 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 # ############################################################################## """ Module notes: issu de ScoDoc7 / ZNotes.py Emmanuel Viennet, 2021 """ import sys import time import datetime import jaxml import pprint from flask import g from flask import current_app from config import Config from scodoc_manager import sco_mgr from app.decorators import ( scodoc7func, ScoDoc7Context, permission_required, admin_required, login_required, ) from app.auth.models import Permission from app.views import notes_bp as bp # --------------- from app.scodoc import sco_utils as scu from app.scodoc import notesdb as ndb from app.scodoc.notes_log import log, sendAlarm from app.scodoc import scolog from app.scodoc.scolog import logdb from app.scodoc.sco_exceptions import ( ScoValueError, ScoLockedFormError, ScoGenError, AccessDenied, ) from app.scodoc.sco_pdf import PDFLOCK import app.scodoc.VERSION as VERSION from app.scodoc.sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC from app.scodoc.gen_tables import GenTable from app.scodoc.sco_permissions import Permission, ScoImplement from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc import sco_core from app.scodoc import htmlutils from app.scodoc import html_sco_header from app.scodoc import notes_table from app.scodoc import pe_view from app.scodoc import sco_apogee_compare from app.scodoc import sco_apogee_csv from app.scodoc import sco_archives from app.scodoc import sco_bulletins from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_cache from app.scodoc import sco_codes_parcours from app.scodoc import sco_compute_moy from app.scodoc import sco_cost_formation from app.scodoc import sco_debouche from app.scodoc import sco_edit_formation from app.scodoc import sco_edit_matiere from app.scodoc import sco_edit_module from app.scodoc import sco_edit_ue from app.scodoc import sco_etape_apogee_view from app.scodoc import sco_evaluations from app.scodoc import sco_excel from app.scodoc import sco_export_results from app.scodoc import sco_formations from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_custommenu from app.scodoc import sco_formsemestre_edit from app.scodoc import sco_formsemestre_exterieurs from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_formsemestre_status from app.scodoc import sco_formsemestre_validation from app.scodoc import sco_groups from app.scodoc import sco_inscr_passage from app.scodoc import sco_liste_notes from app.scodoc import sco_lycee from app.scodoc import sco_moduleimpl from app.scodoc import sco_moduleimpl_inscriptions from app.scodoc import sco_moduleimpl_status from app.scodoc import sco_news from app.scodoc import sco_parcours_dut from app.scodoc import sco_placement from app.scodoc import sco_poursuite_dut from app.scodoc import sco_prepajury from app.scodoc import sco_pvjury from app.scodoc import sco_pvpdf from app.scodoc import sco_recapcomplet from app.scodoc import sco_report from app.scodoc import sco_saisie_notes from app.scodoc import sco_semset from app.scodoc import sco_synchro_etuds from app.scodoc import sco_tag_module from app.scodoc import sco_ue_external from app.scodoc import sco_undo_notes from app.scodoc import scolars context = ScoDoc7Context("notes") def sco_publish(route, function, permission): """Declare a route for a python function, protected by permission and called following ScoDoc 7 Zope standards. """ # f = bp.route(route)(permission_required(permission)(scodoc7func(context)(function))) # setattr(sys.modules[__name__], f.__name__, f) # --------------------- Quelques essais élémentaires: @bp.route("/essai") @permission_required(Permission.ScoView) @scodoc7func(context) def essai(context, REQUEST=None): return essai_(context, REQUEST) def essai_(context, REQUEST): return "

essai !

%s

%s

" % ( context, REQUEST, ) def essai2(context): return essai_(context, "sans request") sco_publish("/essai2", essai2, Permission.ScoImplement) # --------------------- # --------------- @bp.route("/clearcache") @permission_required(Permission.ScoView) @scodoc7func(context) def clearcache(context, REQUEST=None): "Efface les caches de notes (utile pendant developpement slt)" log("*** clearcache request") # Debugging code: compare results before and after cache reconstruction # (_should_ be identicals !) # Compare XML representation cache = sco_core.get_notes_cache(context) formsemestre_ids = cache.get_cached_formsemestre_ids() docs_before = [] for formsemestre_id in formsemestre_ids: docs_before.append( sco_recapcomplet.do_formsemestre_recapcomplet( context, REQUEST, formsemestre_id, format="xml", xml_nodate=True ) ) # cache.inval_cache(context) # > # Rebuild cache (useful only to debug) docs_after = [] for formsemestre_id in formsemestre_ids: docs_after.append( sco_recapcomplet.do_formsemestre_recapcomplet( context, REQUEST, formsemestre_id, format="xml", xml_nodate=True ) ) if docs_before != docs_after: log("clearcache: inconsistency !") txt = "before=" + repr(docs_before) + "\n\nafter=" + repr(docs_after) + "\n" log(txt) sendAlarm(context, "clearcache: inconsistency !", txt) # -------------------------------------------------------------------- # # Notes/ methods # # -------------------------------------------------------------------- sco_publish( "/formsemestre_status", sco_formsemestre_status.formsemestre_status, Permission.ScoView, ) sco_publish( "/formsemestre_createwithmodules", sco_formsemestre_edit.formsemestre_createwithmodules, Permission.ScoImplement, ) # controle d'acces specifique pour dir. etud: sco_publish( "/formsemestre_editwithmodules", sco_formsemestre_edit.formsemestre_editwithmodules, Permission.ScoView, ) sco_publish( "/formsemestre_clone", sco_formsemestre_edit.formsemestre_clone, Permission.ScoImplement, ) sco_publish( "/formsemestre_associate_new_version", sco_formsemestre_edit.formsemestre_associate_new_version, Permission.ScoChangeFormation, ) sco_publish( "/formsemestre_delete", sco_formsemestre_edit.formsemestre_delete, Permission.ScoImplement, ) sco_publish( "/formsemestre_delete2", sco_formsemestre_edit.formsemestre_delete2, Permission.ScoImplement, ) sco_publish( "/formsemestre_recapcomplet", sco_recapcomplet.formsemestre_recapcomplet, Permission.ScoView, ) sco_publish( "/formsemestres_bulletins", sco_recapcomplet.formsemestres_bulletins, Permission.ScoObservateur, ) sco_publish( "/moduleimpl_status", sco_moduleimpl_status.moduleimpl_status, Permission.ScoView ) sco_publish( "/formsemestre_description", sco_formsemestre_status.formsemestre_description, Permission.ScoView, ) sco_publish( "/formsemestre_status_menubar", sco_formsemestre_status.formsemestre_status_menubar, Permission.ScoView, ) sco_publish( "/formation_create", sco_edit_formation.formation_create, Permission.ScoChangeFormation, ) sco_publish( "/formation_delete", sco_edit_formation.formation_delete, Permission.ScoChangeFormation, ) sco_publish( "/formation_edit", sco_edit_formation.formation_edit, Permission.ScoChangeFormation ) sco_publish( "/formsemestre_bulletinetud", sco_bulletins.formsemestre_bulletinetud, Permission.ScoView, ) sco_publish( "/formsemestre_evaluations_cal", sco_evaluations.formsemestre_evaluations_cal, Permission.ScoView, ) sco_publish( "/formsemestre_evaluations_delai_correction", sco_evaluations.formsemestre_evaluations_delai_correction, Permission.ScoView, ) sco_publish( "/module_evaluation_renumber", sco_evaluations.module_evaluation_renumber, Permission.ScoView, ) sco_publish( "/module_evaluation_move", sco_evaluations.module_evaluation_move, Permission.ScoView, ) sco_publish( "/formsemestre_list_saisies_notes", sco_undo_notes.formsemestre_list_saisies_notes, Permission.ScoView, ) sco_publish("/ue_create", sco_edit_ue.ue_create, Permission.ScoChangeFormation) sco_publish("/ue_delete", sco_edit_ue.ue_delete, Permission.ScoChangeFormation) sco_publish("/ue_edit", sco_edit_ue.ue_edit, Permission.ScoChangeFormation) sco_publish("/ue_list", sco_edit_ue.ue_list, Permission.ScoView) sco_publish("/ue_sharing_code", sco_edit_ue.ue_sharing_code, Permission.ScoView) sco_publish( "/edit_ue_set_code_apogee", sco_edit_ue.edit_ue_set_code_apogee, Permission.ScoChangeFormation, ) sco_publish( "/formation_table_recap", sco_edit_ue.formation_table_recap, Permission.ScoView ) sco_publish( "/formation_add_malus_modules", sco_edit_module.formation_add_malus_modules, Permission.ScoChangeFormation, ) sco_publish( "/matiere_create", sco_edit_matiere.matiere_create, Permission.ScoChangeFormation ) sco_publish( "/matiere_delete", sco_edit_matiere.matiere_delete, Permission.ScoChangeFormation ) sco_publish( "/matiere_edit", sco_edit_matiere.matiere_edit, Permission.ScoChangeFormation ) sco_publish( "/module_create", sco_edit_module.module_create, Permission.ScoChangeFormation ) sco_publish( "/module_delete", sco_edit_module.module_delete, Permission.ScoChangeFormation ) sco_publish("/module_edit", sco_edit_module.module_edit, Permission.ScoChangeFormation) sco_publish( "/edit_module_set_code_apogee", sco_edit_module.edit_module_set_code_apogee, Permission.ScoChangeFormation, ) sco_publish("/module_list", sco_edit_module.module_list, Permission.ScoView) sco_publish("/module_tag_search", sco_tag_module.module_tag_search, Permission.ScoView) sco_publish( "/module_tag_set", sco_tag_module.module_tag_set, Permission.ScoEditFormationTags ) # @bp.route("/") @bp.route("/index_html") @permission_required(Permission.ScoView) @scodoc7func(context) def index_html(context, REQUEST=None): "Page accueil formations" editable = REQUEST.AUTHENTICATED_USER.has_permission( Permission.ScoChangeFormation, g.scodoc_dept ) H = [ html_sco_header.sco_header( context, REQUEST, page_title="Programmes formations" ), """

Programmes pédagogiques

""", ] T = sco_formations.formation_list_table(context, REQUEST=REQUEST) H.append(T.html()) if editable: H.append( """

Créer une formation

Importer une formation (xml)

Une "formation" est un programme pédagogique structuré en UE, matières et modules. Chaque semestre se réfère à une formation. La modification d'une formation affecte tous les semestres qui s'y réfèrent.

""" ) H.append(html_sco_header.sco_footer(context, REQUEST)) return "\n".join(H) # -------------------------------------------------------------------- # # Notes Methods # # -------------------------------------------------------------------- # --- Formations sco_publish( "/do_formation_create", sco_edit_formation.do_formation_create, Permission.ScoChangeFormation, ) sco_publish( "/do_formation_delete", sco_edit_formation.do_formation_delete, Permission.ScoChangeFormation, ) @bp.route("/formation_list") @permission_required(Permission.ScoView) @scodoc7func(context) def formation_list(context, format=None, REQUEST=None, formation_id=None, args={}): """List formation(s) with given id, or matching args (when args is given, formation_id is ignored). """ r = sco_formation.formation_list(context, formation_id=formation_id, args=args) return scu.sendResult(REQUEST, r, name="formation", format=format) @bp.route("/formation_export") @permission_required(Permission.ScoView) @scodoc7func(context) def formation_export( context, formation_id, export_ids=False, format=None, REQUEST=None ): "Export de la formation au format indiqué (xml ou json)" return sco_formations.formation_export( context, formation_id, export_ids=export_ids, format=format, REQUEST=REQUEST ) @bp.route("/formation_import_xml") @permission_required(Permission.ScoChangeFormation) @scodoc7func(context) def formation_import_xml(context, file, REQUEST): "import d'une formation en XML" log("formation_import_xml") doc = file.read() return sco_formations.formation_import_xml(context, REQUEST, doc) @bp.route("/formation_import_xml_form") @permission_required(Permission.ScoChangeFormation) @scodoc7func(context) def formation_import_xml_form(context, REQUEST): "form import d'une formation en XML" H = [ html_sco_header.sco_header( context, page_title="Import d'une formation", REQUEST=REQUEST ), """

Import d'une formation

Création d'une formation (avec UE, matières, modules) à partir un fichier XML (réservé aux utilisateurs avertis)

""", ] footer = html_sco_header.sco_footer(context, REQUEST) tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, (("xmlfile", {"input_type": "file", "title": "Fichier XML", "size": 30}),), submitlabel="Importer", cancelbutton="Annuler", ) if tf[0] == 0: return "\n".join(H) + tf[1] + footer elif tf[0] == -1: return REQUEST.RESPONSE.redirect(scu.NotesURL()) else: formation_id, _, _ = sco_formations.formation_import_xml( context, tf[2]["xmlfile"], REQUEST ) return ( "\n".join(H) + """

Import effectué !

Voir la formation

""" % formation_id + footer ) @bp.route("/formation_create_new_version") @permission_required(Permission.ScoChangeFormation) @scodoc7func(context) def formation_create_new_version(context, formation_id, redirect=True, REQUEST=None): "duplicate formation, with new version number" xml = sco_formations.formation_export( context, formation_id, export_ids=True, format="xml" ) new_id, modules_old2new, ues_old2new = sco_formations.formation_import_xml( context, REQUEST, xml ) # news F = sco_formations.formation_list(context, args={"formation_id": new_id})[0] sco_news.add( context, REQUEST, typ=NEWS_FORM, object=new_id, text="Nouvelle version de la formation %(acronyme)s" % F, ) if redirect: return REQUEST.RESPONSE.redirect( "ue_list?formation_id=" + new_id + "&msg=Nouvelle version !" ) else: return new_id, modules_old2new, ues_old2new # --- UE sco_publish( "/do_ue_create", sco_edit_ue.do_ue_create, Permission.ScoChangeFormation, ) sco_publish( "/do_ue_list", sco_edit_ue.do_ue_list, Permission.ScoView, ) # --- Matieres sco_publish( "/do_matiere_create", sco_edit_matiere.do_matiere_create, Permission.ScoChangeFormation, ) sco_publish( "/do_matiere_delete", sco_edit_matiere.do_matiere_delete, Permission.ScoChangeFormation, ) # --- Modules sco_publish( "/do_module_delete", sco_edit_module.do_module_delete, Permission.ScoChangeFormation, ) @bp.route("/formation_count_sems") @permission_required(Permission.ScoView) @scodoc7func(context) def formation_count_sems(context, formation_id): "Number of formsemestre in this formation (locked or not)" sems = sco_formsemestre.do_formsemestre_list( context, args={"formation_id": formation_id} ) return len(sems) sco_publish( "/module_count_moduleimpls", sco_edit_module.module_count_moduleimpls, Permission.ScoView, ) sco_publish("/module_is_locked", sco_edit_module.module_is_locked, Permission.ScoView) sco_publish( "/matiere_is_locked", sco_edit_matiere.matiere_is_locked, Permission.ScoView ) @bp.route("/module_move") @permission_required(Permission.ScoChangeFormation) @scodoc7func(context) def module_move(context, module_id, after=0, REQUEST=None, redirect=1): """Move before/after previous one (decrement/increment numero)""" module = sco_edit_module.do_module_list(context, {"module_id": module_id})[0] redirect = int(redirect) after = int(after) # 0: deplace avant, 1 deplace apres if after not in (0, 1): raise ValueError('invalid value for "after"') formation_id = module["formation_id"] others = sco_edit_module.do_module_list( context, {"matiere_id": module["matiere_id"]} ) # log('others=%s' % others) if len(others) > 1: idx = [p["module_id"] for p in others].index(module_id) # log('module_move: after=%s idx=%s' % (after, idx)) 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 # log('moving module %s' % module_id) cnx = ndb.GetDBConnexion() module["numero"], neigh["numero"] = neigh["numero"], module["numero"] if module["numero"] == neigh["numero"]: neigh["numero"] -= 2 * after - 1 _moduleEditor.edit(cnx, module) _moduleEditor.edit(cnx, neigh) # redirect to ue_list page: if redirect: return REQUEST.RESPONSE.redirect("ue_list?formation_id=" + formation_id) @bp.route("/ue_move") @permission_required(Permission.ScoChangeFormation) @scodoc7func(context) def ue_move(context, ue_id, after=0, REQUEST=None, redirect=1): """Move UE before/after previous one (decrement/increment numero)""" o = sco_edit_ue.do_ue_list(context, {"ue_id": ue_id})[0] # log('ue_move %s (#%s) after=%s' % (ue_id, o['numero'], after)) redirect = int(redirect) after = int(after) # 0: deplace avant, 1 deplace apres if after not in (0, 1): raise ValueError('invalid value for "after"') formation_id = o["formation_id"] others = sco_edit_ue.do_ue_list(context, {"formation_id": formation_id}) if len(others) > 1: idx = [p["ue_id"] for p in others].index(ue_id) 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 # log('moving ue %s (neigh #%s)' % (ue_id, neigh['numero'])) cnx = ndb.GetDBConnexion() o["numero"], neigh["numero"] = neigh["numero"], o["numero"] if o["numero"] == neigh["numero"]: neigh["numero"] -= 2 * after - 1 context._ueEditor.edit(cnx, o) context._ueEditor.edit(cnx, neigh) # redirect to ue_list page if redirect: return REQUEST.RESPONSE.redirect("ue_list?formation_id=" + o["formation_id"]) # --- Semestres de formation @bp.route("/do_formsemestre_create") @permission_required(Permission.ScoImplement) @scodoc7func(context) def do_formsemestre_create(context, args, REQUEST, silent=False): "create a formsemestre" cnx = ndb.GetDBConnexion() formsemestre_id = sco_formsemestre._formsemestreEditor.create(cnx, args) if args["etapes"]: args["formsemestre_id"] = formsemestre_id sco_formsemestre.write_formsemestre_etapes(context, args) if args["responsables"]: args["formsemestre_id"] = formsemestre_id sco_formsemestre.write_formsemestre_responsables(context, args) # create default partition partition_id = sco_groups.partition_create( context, formsemestre_id, default=True, redirect=0, REQUEST=REQUEST ) _group_id = sco_groups.createGroup( context, partition_id, default=True, REQUEST=REQUEST ) # news if not args.has_key("titre"): args["titre"] = "sans titre" args["formsemestre_id"] = formsemestre_id args["url"] = "Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s" % args if not silent: sco_news.add( context, REQUEST, typ=NEWS_SEM, text='Création du semestre %(titre)s' % args, url=args["url"], ) return formsemestre_id @bp.route("/formsemestre_list") @permission_required(Permission.ScoView) @scodoc7func(context) def formsemestre_list( context, format=None, REQUEST=None, formsemestre_id=None, formation_id=None, etape_apo=None, ): """List formsemestres in given format. kw can specify some conditions: examples: formsemestre_list( format='json', formation_id='F777', REQUEST=REQUEST) """ # XAPI: new json api args = {} L = locals() for argname in ("formsemestre_id", "formation_id", "etape_apo"): if L[argname] is not None: args[argname] = L[argname] sems = sco_formsemestre.do_formsemestre_list(context, args=args) # log('formsemestre_list: format="%s", %s semestres found' % (format,len(sems))) return scu.sendResult(REQUEST, sems, name="formsemestre", format=format) @bp.route("/XMLgetFormsemestres") @permission_required(Permission.ScoView) @scodoc7func(context) def XMLgetFormsemestres(context, etape_apo=None, formsemestre_id=None, REQUEST=None): """List all formsemestres matching etape, XML format DEPRECATED: use formsemestre_list() """ log("Warning: calling deprecated XMLgetFormsemestres") args = {} if etape_apo: args["etape_apo"] = etape_apo if formsemestre_id: args["formsemestre_id"] = formsemestre_id if REQUEST: REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE) doc = jaxml.XML_document(encoding=scu.SCO_ENCODING) doc.formsemestrelist() for sem in sco_formsemestre.do_formsemestre_list(context, args=args): doc._push() doc.formsemestre(sem) doc._pop() return repr(doc) sco_publish( "/do_formsemestre_edit", sco_formsemestre.do_formsemestre_edit, Permission.ScoImplement, ) sco_publish( "/formsemestre_edit_options", sco_formsemestre_edit.formsemestre_edit_options, Permission.ScoView, ) sco_publish( "/formsemestre_change_lock", sco_formsemestre_edit.formsemestre_change_lock, Permission.ScoView, ) sco_publish( "/formsemestre_change_publication_bul", sco_formsemestre_edit.formsemestre_change_publication_bul, Permission.ScoView, ) sco_publish( "/view_formsemestre_by_etape", sco_formsemestre.view_formsemestre_by_etape, Permission.ScoView, ) def _check_access_diretud( context, formsemestre_id, REQUEST, required_permission=ScoImplement ): """Check if access granted: responsable or ScoImplement Return True|False, HTML_error_page """ authuser = REQUEST.AUTHENTICATED_USER sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) header = html_sco_header.sco_header( context, page_title="Accès interdit", REQUEST=REQUEST ) footer = html_sco_header.sco_footer(context, REQUEST) if (str(authuser) not in sem["responsables"]) and not authuser.has_permission( required_permission, context ): return ( False, "\n".join( [ header, "

Opération non autorisée pour %s

" % authuser, "

Responsable de ce semestre : %s

" % ", ".join(sem["responsables"]), footer, ] ), ) else: return True, "" @bp.route("/formsemestre_custommenu_edit") @permission_required(Permission.ScoView) @scodoc7func(context) def formsemestre_custommenu_edit(context, REQUEST, formsemestre_id): "Dialogue modif menu" # accessible à tous ! return sco_formsemestre_custommenu.formsemestre_custommenu_edit( context, formsemestre_id, REQUEST=REQUEST ) # --- dialogue modif enseignants/moduleimpl @bp.route("/edit_enseignants_form") @permission_required(Permission.ScoView) @scodoc7func(context) def edit_enseignants_form(context, REQUEST, moduleimpl_id): "modif liste enseignants/moduleimpl" M, sem = sco_moduleimpl.can_change_ens(context, REQUEST, moduleimpl_id) # -- header = html_sco_header.html_sem_header( context, REQUEST, 'Enseignants du module %s' % (moduleimpl_id, M["module"]["titre"]), page_title="Enseignants du module %s" % M["module"]["titre"], javascripts=["libjs/AutoSuggest.js"], cssstyles=["css/autosuggest_inquisitor.css"], bodyOnLoad="init_tf_form('')", ) footer = html_sco_header.sco_footer(context, REQUEST) # Liste des enseignants avec forme pour affichage / saisie avec suggestion userlist = context.Users.get_userlist() login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)" for u in userlist: login2display[u["user_name"]] = u["nomplogin"] allowed_user_names = login2display.values() H = [ "") F = """

Les enseignants d'un module ont le droit de saisir et modifier toutes les notes des évaluations de ce module.

Pour changer le responsable du module, passez par la page "Modification du semestre", accessible uniquement au responsable de la formation (chef de département)

""" % ( sem["formation_id"], M["formsemestre_id"], ) modform = [ ("moduleimpl_id", {"input_type": "hidden"}), ( "ens_id", { "input_type": "text_suggest", "size": 50, "title": "Ajouter un enseignant", "allowed_values": allowed_user_names, "allow_null": False, "text_suggest_options": { "script": "Users/get_userlist_xml?", "varname": "start", "json": False, "noresults": "Valeur invalide !", "timeout": 60000, }, }, ), ] tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, modform, submitlabel="Ajouter enseignant", cancelbutton="Annuler", ) if tf[0] == 0: return header + "\n".join(H) + tf[1] + F + footer elif tf[0] == -1: return REQUEST.RESPONSE.redirect( "moduleimpl_status?moduleimpl_id=" + moduleimpl_id ) else: ens_id = context.Users.get_user_name_from_nomplogin(tf[2]["ens_id"]) if not ens_id: H.append( '

Pour ajouter un enseignant, choisissez un nom dans le menu

' ) else: # et qu'il n'est pas deja: if ( ens_id in [x["ens_id"] for x in M["ens"]] or ens_id == M["responsable_id"] ): H.append( '

Enseignant %s déjà dans la liste !

' % ens_id ) else: sco_moduleimpl.do_ens_create( context, {"moduleimpl_id": moduleimpl_id, "ens_id": ens_id} ) return REQUEST.RESPONSE.redirect( "edit_enseignants_form?moduleimpl_id=%s" % moduleimpl_id ) return header + "\n".join(H) + tf[1] + F + footer @bp.route("/edit_moduleimpl_resp") @permission_required(Permission.ScoView) @scodoc7func(context) def edit_moduleimpl_resp(context, REQUEST, moduleimpl_id): """Changement d'un enseignant responsable de module Accessible par Admin et dir des etud si flag resp_can_change_ens """ M, sem = sco_moduleimpl.can_change_module_resp(context, REQUEST, moduleimpl_id) H = [ html_sco_header.html_sem_header( context, REQUEST, 'Modification du responsable du module %s' % (moduleimpl_id, M["module"]["titre"]), sem, javascripts=["libjs/AutoSuggest.js"], cssstyles=["css/autosuggest_inquisitor.css"], bodyOnLoad="init_tf_form('')", ) ] help = """

Taper le début du nom de l'enseignant.

""" # Liste des enseignants avec forme pour affichage / saisie avec suggestion userlist = context.Users.get_userlist() login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)" for u in userlist: login2display[u["user_name"]] = u["nomplogin"] allowed_user_names = login2display.values() initvalues = M initvalues["responsable_id"] = login2display.get( M["responsable_id"], M["responsable_id"] ) form = [ ("moduleimpl_id", {"input_type": "hidden"}), ( "responsable_id", { "input_type": "text_suggest", "size": 50, "title": "Responsable du module", "allowed_values": allowed_user_names, "allow_null": False, "text_suggest_options": { "script": "Users/get_userlist_xml?", "varname": "start", "json": False, "noresults": "Valeur invalide !", "timeout": 60000, }, }, ), ] tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, form, submitlabel="Changer responsable", cancelbutton="Annuler", initvalues=initvalues, ) if tf[0] == 0: return ( "\n".join(H) + tf[1] + help + html_sco_header.sco_footer(context, REQUEST) ) elif tf[0] == -1: return REQUEST.RESPONSE.redirect( "moduleimpl_status?moduleimpl_id=" + moduleimpl_id ) else: responsable_id = context.Users.get_user_name_from_nomplogin( tf[2]["responsable_id"] ) if ( not responsable_id ): # presque impossible: tf verifie les valeurs (mais qui peuvent changer entre temps) return REQUEST.RESPONSE.redirect( "moduleimpl_status?moduleimpl_id=" + moduleimpl_id ) sco_moduleimpl.do_moduleimpl_edit( context, {"moduleimpl_id": moduleimpl_id, "responsable_id": responsable_id}, formsemestre_id=sem["formsemestre_id"], ) return REQUEST.RESPONSE.redirect( "moduleimpl_status?moduleimpl_id=" + moduleimpl_id + "&head_message=responsable%20modifié" ) _expr_help = """

Expérimental: formule de calcul de la moyenne %(target)s

Attention: l'utilisation de formules ralenti considérablement les traitements. A utiliser uniquement dans els cas ne pouvant pas être traités autrement.

Dans la formule, les variables suivantes sont définies:

Les éléments des vecteurs sont ordonnés dans l'ordre des %(objs)s%(ordre)s.

Les fonctions suivantes sont utilisables: abs, cmp, dot, len, map, max, min, pow, reduce, round, sum, ifelse.

La notation V(1,2,3) représente un vecteur (1,2,3).

Pour indiquer que la note calculée n'existe pas, utiliser la chaîne 'NA'.

Vous pouvez désactiver la formule (et revenir au mode de calcul "classique") en supprimant le texte ou en faisant précéder la première ligne par #

""" @bp.route("/edit_moduleimpl_expr") @permission_required(Permission.ScoView) @scodoc7func(context) def edit_moduleimpl_expr(context, REQUEST, moduleimpl_id): """Edition formule calcul moyenne module Accessible par Admin, dir des etud et responsable module """ M, sem = sco_moduleimpl.can_change_ens(context, REQUEST, moduleimpl_id) H = [ html_sco_header.html_sem_header( context, REQUEST, 'Modification règle de calcul du module %s' % (moduleimpl_id, M["module"]["titre"]), sem, ), context._expr_help % { "target": "du module", "objs": "évaluations", "ordre": " (le premier élément est la plus ancienne évaluation)", }, ] initvalues = M form = [ ("moduleimpl_id", {"input_type": "hidden"}), ( "computation_expr", { "title": "Formule de calcul", "input_type": "textarea", "rows": 4, "cols": 60, "explanation": "formule de calcul (expérimental)", }, ), ] tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, form, submitlabel="Modifier formule de calcul", cancelbutton="Annuler", initvalues=initvalues, ) if tf[0] == 0: return "\n".join(H) + tf[1] + html_sco_header.sco_footer(context, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect( "moduleimpl_status?moduleimpl_id=" + moduleimpl_id ) else: sco_moduleimpl.do_moduleimpl_edit( context, { "moduleimpl_id": moduleimpl_id, "computation_expr": tf[2]["computation_expr"], }, formsemestre_id=sem["formsemestre_id"], ) sco_core.inval_cache( context, formsemestre_id=sem["formsemestre_id"] ) # > modif regle calcul return REQUEST.RESPONSE.redirect( "moduleimpl_status?moduleimpl_id=" + moduleimpl_id + "&head_message=règle%20de%20calcul%20modifiée" ) @bp.route("/view_module_abs") @permission_required(Permission.ScoView) @scodoc7func(context) def view_module_abs(context, REQUEST, moduleimpl_id, format="html"): """Visualisation des absences a un module""" M = sco_moduleimpl.do_moduleimpl_withmodule_list( context, moduleimpl_id=moduleimpl_id )[0] sem = sco_formsemestre.get_formsemestre(context, M["formsemestre_id"]) debut_sem = ndb.DateDMYtoISO(sem["date_debut"]) fin_sem = ndb.DateDMYtoISO(sem["date_fin"]) list_insc = sco_moduleimpl.do_moduleimpl_listeetuds(context, moduleimpl_id) T = [] for etudid in list_insc: nb_abs = context.Absences.CountAbs( etudid=etudid, debut=debut_sem, fin=fin_sem, moduleimpl_id=moduleimpl_id ) if nb_abs: nb_abs_just = context.Absences.CountAbsJust( etudid=etudid, debut=debut_sem, fin=fin_sem, moduleimpl_id=moduleimpl_id, ) etud = scolars.get_etud_info(etudid=etudid, filled=True)[0] T.append( { "nomprenom": etud["nomprenom"], "just": nb_abs_just, "nojust": nb_abs - nb_abs_just, "total": nb_abs, "_nomprenom_target": "ficheEtud?etudid=%s" % etudid, } ) H = [ html_sco_header.html_sem_header( context, REQUEST, 'Absences du module %s' % (moduleimpl_id, M["module"]["titre"]), page_title="Absences du module %s" % (M["module"]["titre"]), sem=sem, ) ] if not T and format == "html": return ( "\n".join(H) + "

Aucune absence signalée

" + html_sco_header.sco_footer(context, REQUEST) ) tab = GenTable( titles={ "nomprenom": "Nom", "just": "Just.", "nojust": "Non Just.", "total": "Total", }, columns_ids=("nomprenom", "just", "nojust", "total"), rows=T, html_class="table_leftalign", base_url="%s?moduleimpl_id=%s" % (REQUEST.URL0, moduleimpl_id), filename="absmodule_" + scu.make_filename(M["module"]["titre"]), caption="Absences dans le module %s" % M["module"]["titre"], preferences=sco_preferences.SemPreferences( context, ), ) if format != "html": return tab.make_page(context, format=format, REQUEST=REQUEST) return "\n".join(H) + tab.html() + html_sco_header.sco_footer(context, REQUEST) @bp.route("/edit_ue_expr") @permission_required(Permission.ScoView) @scodoc7func(context) def edit_ue_expr(context, REQUEST, formsemestre_id, ue_id): """Edition formule calcul moyenne UE""" # Check access sem = sco_formsemestre_edit.can_edit_sem(context, REQUEST, formsemestre_id) if not sem: raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération") cnx = ndb.GetDBConnexion() # ue = sco_edit_ue.do_ue_list(context, {"ue_id": ue_id})[0] H = [ html_sco_header.html_sem_header( context, REQUEST, "Modification règle de calcul de l'UE %s (%s)" % (ue["acronyme"], ue["titre"]), sem, ), context._expr_help % {"target": "de l'UE", "objs": "modules", "ordre": ""}, ] el = sco_compute_moy.formsemestre_ue_computation_expr_list( cnx, {"formsemestre_id": formsemestre_id, "ue_id": ue_id} ) if el: initvalues = el[0] else: initvalues = {} form = [ ("ue_id", {"input_type": "hidden"}), ("formsemestre_id", {"input_type": "hidden"}), ( "computation_expr", { "title": "Formule de calcul", "input_type": "textarea", "rows": 4, "cols": 60, "explanation": "formule de calcul (expérimental)", }, ), ] tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, form, submitlabel="Modifier formule de calcul", cancelbutton="Annuler", initvalues=initvalues, ) if tf[0] == 0: return "\n".join(H) + tf[1] + html_sco_header.sco_footer(context, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect( "formsemestre_status?formsemestre_id=" + formsemestre_id ) else: if el: el[0]["computation_expr"] = tf[2]["computation_expr"] sco_compute_moy.formsemestre_ue_computation_expr_edit(cnx, el[0]) else: sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, tf[2]) sco_core.inval_cache( context, formsemestre_id=formsemestre_id ) # > modif regle calcul return REQUEST.RESPONSE.redirect( "formsemestre_status?formsemestre_id=" + formsemestre_id + "&head_message=règle%20de%20calcul%20modifiée" ) @bp.route("/formsemestre_enseignants_list") @permission_required(Permission.ScoView) @scodoc7func(context) def formsemestre_enseignants_list(context, REQUEST, formsemestre_id, format="html"): """Liste les enseignants intervenants dans le semestre (resp. modules et chargés de TD) et indique les absences saisies par chacun. """ sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) # resp. de modules: mods = sco_moduleimpl.do_moduleimpl_withmodule_list( context, formsemestre_id=formsemestre_id ) sem_ens = {} for mod in mods: if not mod["responsable_id"] in sem_ens: sem_ens[mod["responsable_id"]] = {"mods": [mod]} else: sem_ens[mod["responsable_id"]]["mods"].append(mod) # charges de TD: for mod in mods: for ensd in mod["ens"]: if not ensd["ens_id"] in sem_ens: sem_ens[ensd["ens_id"]] = {"mods": [mod]} else: sem_ens[ensd["ens_id"]]["mods"].append(mod) # compte les absences ajoutées par chacun dans tout le semestre cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) for ens in sem_ens: cursor.execute( "select * from scolog L, notes_formsemestre_inscription I where method='AddAbsence' and authenticated_user=%(authenticated_user)s and L.etudid = I.etudid and I.formsemestre_id=%(formsemestre_id)s and date > %(date_debut)s and date < %(date_fin)s", { "authenticated_user": ens, "formsemestre_id": formsemestre_id, "date_debut": ndb.DateDMYtoISO(sem["date_debut"]), "date_fin": ndb.DateDMYtoISO(sem["date_fin"]), }, ) events = cursor.dictfetchall() sem_ens[ens]["nbabsadded"] = len(events) # description textuelle des modules for ens in sem_ens: sem_ens[ens]["descr_mods"] = ", ".join( [x["module"]["code"] for x in sem_ens[ens]["mods"]] ) # ajoute infos sur enseignant: for ens in sem_ens: sem_ens[ens].update(context.Users.user_info(ens)) if sem_ens[ens]["email"]: sem_ens[ens]["_email_target"] = "mailto:%s" % sem_ens[ens]["email"] sem_ens_list = sem_ens.values() sem_ens_list.sort(lambda x, y: cmp(x["nomprenom"], y["nomprenom"])) # --- Generate page with table title = "Enseignants de " + sem["titremois"] T = GenTable( columns_ids=["nom_fmt", "prenom_fmt", "descr_mods", "nbabsadded", "email"], titles={ "nom_fmt": "Nom", "prenom_fmt": "Prénom", "email": "Mail", "descr_mods": "Modules", "nbabsadded": "Saisies Abs.", }, rows=sem_ens_list, html_sortable=True, html_class="table_leftalign", filename=scu.make_filename("Enseignants-" + sem["titreannee"]), html_title=html_sco_header.html_sem_header( context, REQUEST, "Enseignants du semestre", sem, with_page_header=False ), base_url="%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id), caption="Tous les enseignants (responsables ou associés aux modules de ce semestre) apparaissent. Le nombre de saisies d'absences est le nombre d'opérations d'ajout effectuées sur ce semestre, sans tenir compte des annulations ou double saisies.", preferences=sco_preferences.SemPreferences(context, formsemestre_id), ) return T.make_page( context, page_title=title, title=title, REQUEST=REQUEST, format=format ) @bp.route("/edit_enseignants_form_delete") @permission_required(Permission.ScoView) @scodoc7func(context) def edit_enseignants_form_delete(context, REQUEST, moduleimpl_id, ens_id): "remove ens" M, _ = sco_moduleimpl.can_change_ens(context, REQUEST, moduleimpl_id) # search ens_id ok = False for ens in M["ens"]: if ens["ens_id"] == ens_id: ok = True break if not ok: raise ScoValueError("invalid ens_id (%s)" % ens_id) sco_moduleimpl.do_ens_delete(context, ens["modules_enseignants_id"]) return REQUEST.RESPONSE.redirect( "edit_enseignants_form?moduleimpl_id=%s" % moduleimpl_id ) # --- Gestion des inscriptions aux semestres # Ancienne API, pas certain de la publier en ScoDoc8 # sco_publish( # "/do_formsemestre_inscription_create", # sco_formsemestre_inscriptions.do_formsemestre_inscription_create, # Permission.ScoEtudInscrit, # ) # sco_publish( # "/do_formsemestre_inscription_edit", # sco_formsemestre_inscriptions.do_formsemestre_inscription_edit, # Permission.ScoEtudInscrit, # ) sco_publish( "/do_formsemestre_inscription_list", sco_formsemestre_inscriptions.do_formsemestre_inscription_list, Permission.ScoView, ) @bp.route("/do_formsemestre_inscription_listinscrits") @permission_required(Permission.ScoView) @scodoc7func(context) def do_formsemestre_inscription_listinscrits( context, formsemestre_id, format=None, REQUEST=None ): """Liste les inscrits (état I) à ce semestre et cache le résultat""" cache = context.get_formsemestre_inscription_cache() r = cache.get(formsemestre_id) if r is None: # retreive list r = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( context, args={"formsemestre_id": formsemestre_id, "etat": "I"} ) cache.set(formsemestre_id, r) return scu.sendResult(REQUEST, r, format=format, name="inscrits") @bp.route("/formsemestre_desinscription") @permission_required(Permission.ScoImplement) @scodoc7func(context) def formsemestre_desinscription( context, etudid, formsemestre_id, REQUEST=None, dialog_confirmed=False ): """desinscrit l'etudiant de ce semestre (et donc de tous les modules). A n'utiliser qu'en cas d'erreur de saisie. S'il s'agit d'un semestre extérieur et qu'il n'y a plus d'inscrit, le semestre sera supprimé. """ sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) # -- check lock if sem["etat"] != "1": raise ScoValueError("desinscription impossible: semestre verrouille") # -- Si décisions de jury, désinscription interdite nt = sco_core.get_notes_cache(context).get_NotesTable(context, formsemestre_id) if nt.etud_has_decision(etudid): raise ScoValueError( """Désinscription impossible: l'étudiant a une décision de jury (la supprimer avant si nécessaire: supprimer décision jury ) """ % (etudid, formsemestre_id) ) if not dialog_confirmed: etud = scolars.get_etud_info(etudid=etudid, filled=1)[0] if sem["modalite"] != "EXT": msg_ext = """

%s sera désinscrit de tous les modules du semestre %s (%s - %s).

Cette opération ne doit être utilisée que pour corriger une erreur ! Un étudiant réellement inscrit doit le rester, le faire éventuellement démissionner.

""" % ( etud["nomprenom"], sem["titre_num"], sem["date_debut"], sem["date_fin"], ) else: # semestre extérieur msg_ext = """

%s sera désinscrit du semestre extérieur %s (%s - %s).

""" % ( etud["nomprenom"], sem["titre_num"], sem["date_debut"], sem["date_fin"], ) inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( context, args={"formsemestre_id": formsemestre_id} ) nbinscrits = len(inscrits) if nbinscrits <= 1: msg_ext = """

Attention: le semestre extérieur sera supprimé car il n'a pas d'autre étudiant inscrit.

""" return scu.confirm_dialog( context, """

Confirmer la demande de desinscription ?

""" + msg_ext, dest_url="", REQUEST=REQUEST, cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id, parameters={"etudid": etudid, "formsemestre_id": formsemestre_id}, ) context.do_formsemestre_desinscription(etudid, formsemestre_id, REQUEST=REQUEST) return ( html_sco_header.sco_header(context, REQUEST) + '

Etudiant désinscrit !

retour à la fiche' % (scu.ScoURL(), etudid) + html_sco_header.sco_footer(context, REQUEST) ) sco_publish( "/do_formsemestre_desinscription", sco_formsemestre_inscriptions.do_formsemestre_desinscription, Permission.ScoEtudInscrit, ) @bp.route("/etud_desinscrit_ue") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def etud_desinscrit_ue(context, etudid, formsemestre_id, ue_id, REQUEST=None): """Desinscrit l'etudiant de tous les modules de cette UE dans ce semestre.""" sco_moduleimpl_inscriptions.do_etud_desinscrit_ue( context, etudid, formsemestre_id, ue_id, REQUEST=REQUEST ) return REQUEST.RESPONSE.redirect( scu.ScoURL() + "/Notes/moduleimpl_inscriptions_stats?formsemestre_id=" + formsemestre_id ) @bp.route("/etud_inscrit_ue") @permission_required(Permission.ScoEtudInscrit) @scodoc7func(context) def etud_inscrit_ue(context, etudid, formsemestre_id, ue_id, REQUEST=None): """Inscrit l'etudiant de tous les modules de cette UE dans ce semestre.""" sco_moduleimpl_inscriptions.do_etud_inscrit_ue( context, etudid, formsemestre_id, ue_id, REQUEST=REQUEST ) return REQUEST.RESPONSE.redirect( scu.ScoURL() + "/Notes/moduleimpl_inscriptions_stats?formsemestre_id=" + formsemestre_id ) # --- Inscriptions sco_publish( "/formsemestre_inscription_with_modules_form", sco_formsemestre_inscriptions.formsemestre_inscription_with_modules_form, Permission.ScoEtudInscrit, ) sco_publish( "/formsemestre_inscription_with_modules_etud", sco_formsemestre_inscriptions.formsemestre_inscription_with_modules_etud, Permission.ScoEtudInscrit, ) sco_publish( "/formsemestre_inscription_with_modules", sco_formsemestre_inscriptions.formsemestre_inscription_with_modules, Permission.ScoEtudInscrit, ) sco_publish( "/formsemestre_inscription_option", sco_formsemestre_inscriptions.formsemestre_inscription_option, Permission.ScoEtudInscrit, ) sco_publish( "/do_moduleimpl_incription_options", sco_formsemestre_inscriptions.do_moduleimpl_incription_options, Permission.ScoEtudInscrit, ) sco_publish( "/formsemestre_inscrits_ailleurs", sco_formsemestre_inscriptions.formsemestre_inscrits_ailleurs, Permission.ScoView, ) sco_publish( "/moduleimpl_inscriptions_edit", sco_moduleimpl_inscriptions.moduleimpl_inscriptions_edit, Permission.ScoEtudInscrit, ) sco_publish( "/moduleimpl_inscriptions_stats", sco_moduleimpl_inscriptions.moduleimpl_inscriptions_stats, Permission.ScoView, ) # --- Evaluations _evaluationEditor = ndb.EditableTable( "notes_evaluation", "evaluation_id", ( "evaluation_id", "moduleimpl_id", "jour", "heure_debut", "heure_fin", "description", "note_max", "coefficient", "visibulletin", "publish_incomplete", "evaluation_type", "numero", ), sortkey="numero desc, jour desc, heure_debut desc", # plus recente d'abord output_formators={ "jour": ndb.DateISOtoDMY, "visibulletin": str, "publish_incomplete": str, "numero": ndb.int_null_is_zero, }, input_formators={ "jour": ndb.DateDMYtoISO, "heure_debut": ndb.TimetoISO8601, # converti par do_evaluation_list "heure_fin": ndb.TimetoISO8601, # converti par do_evaluation_list "visibulletin": int, "publish_incomplete": int, }, ) def _evaluation_check_write_access(context, REQUEST, moduleimpl_id=None): """Vérifie que l'on a le droit de modifier, créer ou détruire une évaluation dans ce module. Sinon, lance une exception. (nb: n'implique pas le droit de saisir ou modifier des notes) """ # acces pour resp. moduleimpl et resp. form semestre (dir etud) if moduleimpl_id is None: raise ValueError("no moduleimpl specified") # bug authuser = REQUEST.AUTHENTICATED_USER uid = str(authuser) M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)[0] sem = sco_formsemestre.get_formsemestre(context, M["formsemestre_id"]) if ( (not authuser.has_permission(Permission.ScoEditAllEvals, context)) and uid != M["responsable_id"] and uid not in sem["responsables"] ): if sem["ens_can_edit_eval"]: for ens in M["ens"]: if ens["ens_id"] == uid: return # ok raise AccessDenied("Modification évaluation impossible pour %s" % (uid,)) @bp.route("/do_evaluation_create") @permission_required(Permission.ScoEnsView) @scodoc7func(context) def do_evaluation_create( context, moduleimpl_id=None, jour=None, heure_debut=None, heure_fin=None, description=None, note_max=None, coefficient=None, visibulletin=None, publish_incomplete=None, evaluation_type=None, numero=None, REQUEST=None, **kw ): """Create an evaluation""" args = locals() log("do_evaluation_create: args=" + str(args)) context._evaluation_check_write_access(REQUEST, moduleimpl_id=moduleimpl_id) context._check_evaluation_args(args) # Check numeros sco_evaluations.module_evaluation_renumber( context, moduleimpl_id, REQUEST=REQUEST, only_if_unumbered=True ) if not "numero" in args or args["numero"] is None: n = None # determine le numero avec la date # Liste des eval existantes triees par date, la plus ancienne en tete ModEvals = context.do_evaluation_list( args={"moduleimpl_id": moduleimpl_id}, sortkey="jour asc, heure_debut asc", ) if args["jour"]: next_eval = None t = ( ndb.DateDMYtoISO(args["jour"]), ndb.TimetoISO8601(args["heure_debut"]), ) for e in ModEvals: if ( ndb.DateDMYtoISO(e["jour"]), ndb.TimetoISO8601(e["heure_debut"]), ) > t: next_eval = e break if next_eval: n = sco_evaluations.module_evaluation_insert_before( context, ModEvals, next_eval, REQUEST ) else: n = None # a placer en fin if n is None: # pas de date ou en fin: if ModEvals: log(pprint.pformat(ModEvals[-1])) n = ModEvals[-1]["numero"] + 1 else: n = 0 # the only one # log("creating with numero n=%d" % n) args["numero"] = n # cnx = ndb.GetDBConnexion() r = context._evaluationEditor.create(cnx, args) # news M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)[0] mod = sco_edit_module.do_module_list(context, args={"module_id": M["module_id"]})[0] mod["moduleimpl_id"] = M["moduleimpl_id"] mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod sco_news.add( context, REQUEST, typ=NEWS_NOTE, object=moduleimpl_id, text='Création d\'une évaluation dans %(titre)s' % mod, url=mod["url"], ) return r def _check_evaluation_args(context, args): "Check coefficient, dates and duration, raises exception if invalid" moduleimpl_id = args["moduleimpl_id"] # check bareme note_max = args.get("note_max", None) if note_max is None: raise ScoValueError("missing note_max") try: note_max = float(note_max) except ValueError: raise ScoValueError("Invalid note_max value") if note_max < 0: raise ScoValueError("Invalid note_max value (must be positive or null)") # check coefficient coef = args.get("coefficient", None) if coef is None: raise ScoValueError("missing coefficient") try: coef = float(coef) except ValueError: raise ScoValueError("Invalid coefficient value") if coef < 0: raise ScoValueError("Invalid coefficient value (must be positive or null)") # check date jour = args.get("jour", None) args["jour"] = jour if jour: M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)[0] sem = sco_formsemestre.get_formsemestre(context, M["formsemestre_id"]) d, m, y = [int(x) for x in sem["date_debut"].split("/")] date_debut = datetime.date(y, m, d) d, m, y = [int(x) for x in sem["date_fin"].split("/")] date_fin = datetime.date(y, m, d) # passe par ndb.DateDMYtoISO pour avoir date pivot y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")] jour = datetime.date(y, m, d) if (jour > date_fin) or (jour < date_debut): raise ScoValueError( "La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !" % (d, m, y) ) heure_debut = args.get("heure_debut", None) args["heure_debut"] = heure_debut heure_fin = args.get("heure_fin", None) args["heure_fin"] = heure_fin if jour and ((not heure_debut) or (not heure_fin)): raise ScoValueError("Les heures doivent être précisées") d = ndb.TimeDuration(heure_debut, heure_fin) if d and ((d < 0) or (d > 60 * 12)): raise ScoValueError("Heures de l'évaluation incohérentes !") @bp.route("/evaluation_delete") @permission_required(Permission.ScoEnsView) @scodoc7func(context) def evaluation_delete(context, REQUEST, evaluation_id): """Form delete evaluation""" El = context.do_evaluation_list(args={"evaluation_id": evaluation_id}) if not El: raise ValueError("Evalution inexistante ! (%s)" % evaluation_id) E = El[0] M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=E["moduleimpl_id"])[0] Mod = sco_edit_module.do_module_list(context, args={"module_id": M["module_id"]})[0] tit = "Suppression de l'évaluation %(description)s (%(jour)s)" % E etat = sco_evaluations.do_evaluation_etat(context, evaluation_id) H = [ html_sco_header.html_sem_header(context, REQUEST, tit, with_h2=False), """

Module %(code)s %(titre)s

""" % Mod, """

%s

""" % tit, """

Opération irréversible. Si vous supprimez l'évaluation, vous ne pourrez pas retrouver les notes associées.

""", ] warning = False if etat["nb_notes_total"]: warning = True nb_desinscrits = etat["nb_notes_total"] - etat["nb_notes"] H.append( """
Il y a %s notes""" % etat["nb_notes_total"] ) if nb_desinscrits: H.append( """ (dont %s d'étudiants qui ne sont plus inscrits)""" % nb_desinscrits ) H.append(""" dans l'évaluation""") if etat["nb_notes"] == 0: H.append( """

Vous pouvez quand même supprimer l'évaluation, les notes des étudiants désincrits seront effacées.

""" ) if etat["nb_notes"]: H.append( """

Suppression impossible (effacer les notes d'abord)

retour au tableau de bord du module

""" % E["moduleimpl_id"] ) return "\n".join(H) + html_sco_header.sco_footer(context, REQUEST) if warning: H.append("""""") tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, (("evaluation_id", {"input_type": "hidden"}),), initvalues=E, submitlabel="Confirmer la suppression", cancelbutton="Annuler", ) if tf[0] == 0: return "\n".join(H) + tf[1] + html_sco_header.sco_footer(context, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect( scu.ScoURL() + "/Notes/moduleimpl_status?moduleimpl_id=" + E["moduleimpl_id"] ) else: sco_evaluations.do_evaluation_delete(context, REQUEST, E["evaluation_id"]) return ( "\n".join(H) + """

OK, évaluation supprimée.

Continuer

""" % ( scu.ScoURL() + "/Notes/moduleimpl_status?moduleimpl_id=" + E["moduleimpl_id"] ) + html_sco_header.sco_footer(context, REQUEST) ) @bp.route("/do_evaluation_list") @permission_required(Permission.ScoView) @scodoc7func(context) def do_evaluation_list(context, args, sortkey=None): """List evaluations, sorted by numero (or most recent date first). Ajoute les champs: 'duree' : '2h30' 'matin' : 1 (commence avant 12:00) ou 0 'apresmidi' : 1 (termine après 12:00) ou 0 'descrheure' : ' de 15h00 à 16h30' """ cnx = ndb.GetDBConnexion() evals = context._evaluationEditor.list(cnx, args, sortkey=sortkey) # calcule duree (chaine de car.) de chaque evaluation et ajoute jouriso, matin, apresmidi for e in evals: heure_debut_dt = e["heure_debut"] or datetime.time( 8, 00 ) # au cas ou pas d'heure (note externe?) heure_fin_dt = e["heure_fin"] or datetime.time(8, 00) e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"]) e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"]) e["jouriso"] = ndb.DateDMYtoISO(e["jour"]) heure_debut, heure_fin = e["heure_debut"], e["heure_fin"] d = ndb.TimeDuration(heure_debut, heure_fin) if d is not None: m = d % 60 e["duree"] = "%dh" % (d / 60) if m != 0: e["duree"] += "%02d" % m else: e["duree"] = "" if heure_debut and (not heure_fin or heure_fin == heure_debut): e["descrheure"] = " à " + heure_debut elif heure_debut and heure_fin: e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin) else: e["descrheure"] = "" # matin, apresmidi: utile pour se referer aux absences: if heure_debut_dt < datetime.time(12, 00): e["matin"] = 1 else: e["matin"] = 0 if heure_fin_dt > datetime.time(12, 00): e["apresmidi"] = 1 else: e["apresmidi"] = 0 return evals @bp.route("/do_evaluation_list_in_formsemestre") @permission_required(Permission.ScoView) @scodoc7func(context) def do_evaluation_list_in_formsemestre(context, formsemestre_id): "list evaluations in this formsemestre" mods = sco_moduleimpl.do_moduleimpl_list(context, formsemestre_id=formsemestre_id) evals = [] for mod in mods: evals += context.do_evaluation_list( args={"moduleimpl_id": mod["moduleimpl_id"]} ) return evals @bp.route("/do_evaluation_edit") @permission_required(Permission.ScoEnsView) @scodoc7func(context) def do_evaluation_edit(context, REQUEST, args): "edit a evaluation" evaluation_id = args["evaluation_id"] the_evals = context.do_evaluation_list({"evaluation_id": evaluation_id}) if not the_evals: raise ValueError("evaluation inexistante !") moduleimpl_id = the_evals[0]["moduleimpl_id"] args["moduleimpl_id"] = moduleimpl_id context._check_evaluation_args(args) context._evaluation_check_write_access(REQUEST, moduleimpl_id=moduleimpl_id) cnx = ndb.GetDBConnexion() context._evaluationEditor.edit(cnx, args) # inval cache pour ce semestre M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)[0] sco_core.inval_cache( context, formsemestre_id=M["formsemestre_id"] ) # > evaluation_edit (coef, ...) @bp.route("/evaluation_edit") @permission_required(Permission.ScoEnsView) @scodoc7func(context) def evaluation_edit(context, evaluation_id, REQUEST): "form edit evaluation" return sco_evaluations.evaluation_create_form( context, evaluation_id=evaluation_id, REQUEST=REQUEST, edit=True ) @bp.route("/evaluation_create") @permission_required(Permission.ScoEnsView) @scodoc7func(context) def evaluation_create(context, moduleimpl_id, REQUEST): "form create evaluation" return sco_evaluations.evaluation_create_form( context, moduleimpl_id=moduleimpl_id, REQUEST=REQUEST, edit=False ) @bp.route("/evaluation_listenotes") @permission_required(Permission.ScoView) @scodoc7func(context) def evaluation_listenotes(context, REQUEST=None): """Affichage des notes d'une évaluation""" if REQUEST.form.get("format", "html") == "html": H = html_sco_header.sco_header( context, REQUEST, cssstyles=["css/verticalhisto.css"], javascripts=["js/etud_info.js"], init_qtip=True, ) F = html_sco_header.sco_footer(context, REQUEST) else: H, F = "", "" B = context.do_evaluation_listenotes(REQUEST) return H + B + F sco_publish( "/do_evaluation_listenotes", sco_liste_notes.do_evaluation_listenotes, Permission.ScoView, ) sco_publish( "/evaluation_list_operations", sco_undo_notes.evaluation_list_operations, Permission.ScoView, ) sco_publish( "/evaluation_check_absences_html", sco_liste_notes.evaluation_check_absences_html, Permission.ScoView, ) sco_publish( "/formsemestre_check_absences_html", sco_liste_notes.formsemestre_check_absences_html, Permission.ScoView, ) # --- Placement des étudiants pour l'évaluation sco_publish( "/placement_eval_selectetuds", sco_placement.placement_eval_selectetuds, Permission.ScoEnsView, ) sco_publish("/do_placement", sco_placement.do_placement, Permission.ScoEnsView) # --- Saisie des notes sco_publish( "/saisie_notes_tableur", sco_saisie_notes.saisie_notes_tableur, Permission.ScoEnsView, ) sco_publish( "/feuille_saisie_notes", sco_saisie_notes.feuille_saisie_notes, Permission.ScoEnsView, ) sco_publish("/saisie_notes", sco_saisie_notes.saisie_notes, Permission.ScoEnsView) sco_publish("/save_note", sco_saisie_notes.save_note, Permission.ScoEnsView) sco_publish( "/do_evaluation_set_missing", sco_saisie_notes.do_evaluation_set_missing, Permission.ScoEnsView, ) sco_publish( "/evaluation_suppress_alln", sco_saisie_notes.evaluation_suppress_alln, Permission.ScoView, ) def _notes_getall( context, evaluation_id, table="notes_notes", filter_suppressed=True, by_uid=None ): """get tt les notes pour une evaluation: { etudid : { 'value' : value, 'date' : date ... }} Attention: inclue aussi les notes des étudiants qui ne sont plus inscrits au module. """ # log('_notes_getall( e=%s fs=%s )' % (evaluation_id, filter_suppressed)) do_cache = ( filter_suppressed and table == "notes_notes" and (by_uid is None) ) # pas de cache pour (rares) appels via undo_notes ou specifiant un enseignant if do_cache: cache = sco_core.get_evaluations_cache(context) r = cache.get(evaluation_id) if r != None: return r cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cond = " where evaluation_id=%(evaluation_id)s" if by_uid: cond += " and uid=%(by_uid)s" cursor.execute( "select * from " + table + cond, {"evaluation_id": evaluation_id, "by_uid": by_uid}, ) res = cursor.dictfetchall() d = {} if filter_suppressed: for x in res: if x["value"] != scu.NOTES_SUPPRESS: d[x["etudid"]] = x else: for x in res: d[x["etudid"]] = x if do_cache: cache.set(evaluation_id, d) return d # --- Bulletins @bp.route("/formsemestre_bulletins_pdf") @permission_required(Permission.ScoView) @scodoc7func(context) def formsemestre_bulletins_pdf( context, formsemestre_id, REQUEST, version="selectedevals" ): "Publie les bulletins dans un classeur PDF" pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( context, formsemestre_id, REQUEST, version=version ) return scu.sendPDFFile(REQUEST, pdfdoc, filename) @bp.route("/formsemestre_bulletins_pdf_choice") @permission_required(Permission.ScoView) @scodoc7func(context) def formsemestre_bulletins_pdf_choice(context, REQUEST, formsemestre_id, version=None): """Choix version puis envois classeur bulletins pdf""" if version: pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( context, formsemestre_id, REQUEST, version=version ) return scu.sendPDFFile(REQUEST, pdfdoc, filename) return formsemestre_bulletins_choice( context, REQUEST, formsemestre_id, title="Choisir la version des bulletins à générer", explanation=expl_bull, ) @bp.route("/etud_bulletins_pdf") @permission_required(Permission.ScoView) @scodoc7func(context) def etud_bulletins_pdf(context, etudid, REQUEST, version="selectedevals"): "Publie tous les bulletins d'un etudiants dans un classeur PDF" pdfdoc, filename = sco_bulletins_pdf.get_etud_bulletins_pdf( context, etudid, REQUEST, version=version ) return scu.sendPDFFile(REQUEST, pdfdoc, filename) @bp.route("/formsemestre_bulletins_mailetuds_choice") @permission_required(Permission.ScoView) @scodoc7func(context) def formsemestre_bulletins_mailetuds_choice( context, REQUEST, formsemestre_id, version=None, dialog_confirmed=False, prefer_mail_perso=0, ): """Choix version puis envois classeur bulletins pdf""" if version: return context.formsemestre_bulletins_mailetuds( formsemestre_id, REQUEST, version=version, dialog_confirmed=dialog_confirmed, prefer_mail_perso=prefer_mail_perso, ) expl_bull = """Versions des bulletins: