# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# ScoDoc
#
# Copyright (c) 1999 - 2023 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
"""
from operator import itemgetter
import time
import flask
from flask import abort, flash, redirect, render_template, url_for
from flask import g, request
from flask_login import current_user
from app import db
from app import models
from app.auth.models import User
from app.but import apc_edit_ue, jury_but_recap
from app.but import jury_but, jury_but_validation_auto
from app.but.forms import jury_but_forms
from app.but import jury_but_pv
from app.but import jury_but_view
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import ScolarNews, Scolog
from app.models.but_refcomp import ApcNiveau, ApcParcours
from app.models.config import ScoDocSiteConfig
from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre
from app.models.formsemestre import FormSemestreUEComputationExpr
from app.models.moduleimpls import ModuleImpl
from app.models.modules import Module
from app.models.ues import DispenseUE, UniteEns
from app.scodoc.sco_exceptions import ScoFormationConflict
from app.views import notes_bp as bp
from app.decorators import (
scodoc,
scodoc7func,
permission_required,
permission_required_compat_scodoc7,
)
# ---------------
from app.scodoc import sco_bulletins_json, sco_utils as scu
from app.scodoc import notesdb as ndb
from app import log, send_scodoc_alarm
from app.scodoc.scolog import logdb
from app.scodoc.sco_exceptions import (
AccessDenied,
ScoValueError,
ScoInvalidIdType,
)
from app.scodoc import html_sco_header
from app.pe import pe_view
from app.scodoc import sco_abs
from app.scodoc import sco_apogee_compare
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_cost_formation
from app.scodoc import sco_debouche
from app.scodoc import sco_edit_apc
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_etud
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_check_abs
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_evaluation_edit
from app.scodoc import sco_evaluation_recap
from app.scodoc import sco_export_results
from app.scodoc import sco_formations
from app.scodoc import sco_formation_recap
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_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_permissions_check
from app.scodoc import sco_placement
from app.scodoc import sco_poursuite_dut
from app.scodoc import sco_preferences
from app.scodoc import sco_prepajury
from app.scodoc import sco_pvjury
from app.scodoc import sco_recapcomplet
from app.scodoc import sco_report
from app.scodoc import sco_report_but
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 sco_users
from app.scodoc import sco_xml
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_permissions import Permission
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.views import ScoData
def sco_publish(route, function, permission, methods=("GET",)):
"""Declare a route for a python function,
protected by permission and called following ScoDoc 7 Zope standards.
"""
return bp.route(route, methods=methods)(
scodoc(permission_required(permission)(scodoc7func(function)))
)
# --------------------------------------------------------------------
#
# Notes/ methods
#
# --------------------------------------------------------------------
sco_publish(
"/formsemestre_status",
sco_formsemestre_status.formsemestre_status,
Permission.ScoView,
)
sco_publish(
"/formsemestre_createwithmodules",
sco_formsemestre_edit.formsemestre_createwithmodules,
Permission.ScoImplement,
methods=["GET", "POST"],
)
# controle d'acces specifique pour dir. etud:
sco_publish(
"/formsemestre_editwithmodules",
sco_formsemestre_edit.formsemestre_editwithmodules,
Permission.ScoView,
methods=["GET", "POST"],
)
sco_publish(
"/formsemestre_clone",
sco_formsemestre_edit.formsemestre_clone,
Permission.ScoImplement,
methods=["GET", "POST"],
)
sco_publish(
"/formsemestre_associate_new_version",
sco_formsemestre_edit.formsemestre_associate_new_version,
Permission.ScoChangeFormation,
methods=["GET", "POST"],
)
sco_publish(
"/formsemestre_delete",
sco_formsemestre_edit.formsemestre_delete,
Permission.ScoImplement,
methods=["GET", "POST"],
)
sco_publish(
"/formsemestre_delete2",
sco_formsemestre_edit.formsemestre_delete2,
Permission.ScoImplement,
methods=["GET", "POST"],
)
sco_publish(
"/formsemestre_note_etuds_sans_note",
sco_formsemestre_status.formsemestre_note_etuds_sans_notes,
Permission.ScoView,
methods=["GET", "POST"],
)
sco_publish(
"/formsemestre_recapcomplet",
sco_recapcomplet.formsemestre_recapcomplet,
Permission.ScoView,
)
sco_publish(
"/evaluations_recap",
sco_evaluation_recap.evaluations_recap,
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(
"/formation_create",
sco_edit_formation.formation_create,
Permission.ScoChangeFormation,
methods=["GET", "POST"],
)
sco_publish(
"/formation_delete",
sco_edit_formation.formation_delete,
Permission.ScoChangeFormation,
methods=["GET", "POST"],
)
sco_publish(
"/formation_edit",
sco_edit_formation.formation_edit,
Permission.ScoChangeFormation,
methods=["GET", "POST"],
)
@bp.route(
"/formsemestre_bulletinetud", methods=["GET", "POST"]
) # POST pour compat anciens clients PHP (deprecated)
@scodoc
@permission_required_compat_scodoc7(Permission.ScoView)
@scodoc7func
def formsemestre_bulletinetud(
etudid=None,
formsemestre_id=None,
format=None,
version="long",
xml_with_decisions=False,
force_publishing=False,
prefer_mail_perso=False,
code_nip=None,
code_ine=None,
):
format = format or "html"
if not isinstance(etudid, int):
raise ScoInvalidIdType("formsemestre_bulletinetud: etudid must be an integer !")
if formsemestre_id is not None and not isinstance(formsemestre_id, int):
raise ScoInvalidIdType(
"formsemestre_bulletinetud: formsemestre_id must be an integer !"
)
formsemestre = FormSemestre.query.filter_by(
formsemestre_id=formsemestre_id, dept_id=g.scodoc_dept_id
).first_or_404()
if etudid:
etud = models.Identite.query.filter_by(
etudid=etudid, dept_id=formsemestre.dept_id
).first_or_404()
elif code_nip:
etud = models.Identite.query.filter_by(
code_nip=str(code_nip), dept_id=formsemestre.dept_id
).first_or_404()
elif code_ine:
etud = models.Identite.query.filter_by(
code_ine=str(code_ine), dept_id=formsemestre.dept_id
).first_or_404()
else:
raise ScoValueError(
"Paramètre manquant: spécifier etudid, code_nip ou code_ine"
)
if format == "json":
return sco_bulletins.get_formsemestre_bulletin_etud_json(
formsemestre, etud, version=version, force_publishing=force_publishing
)
if formsemestre.formation.is_apc() and format == "html":
return render_template(
"but/bulletin.html",
appreciations=models.BulAppreciations.query.filter_by(
etudid=etudid, formsemestre_id=formsemestre.id
).order_by(models.BulAppreciations.date),
bul_url=url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
etudid=etudid,
format="json",
force_publishing=1, # pour ScoDoc lui même
version=version,
),
can_edit_appreciations=formsemestre.est_responsable(current_user)
or (current_user.has_permission(Permission.ScoEtudInscrit)),
etud=etud,
formsemestre=formsemestre,
inscription_courante=etud.inscription_courante(),
inscription_str=etud.inscription_descr()["inscription_str"],
is_apc=formsemestre.formation.is_apc(),
menu_autres_operations=sco_bulletins.make_menu_autres_operations(
formsemestre, etud, "notes.formsemestre_bulletinetud", version
),
sco=ScoData(etud=etud),
scu=scu,
time=time,
title=f"Bul. {etud.nom} - BUT",
version=version,
)
if format == "oldjson":
format = "json"
r = sco_bulletins.formsemestre_bulletinetud(
etud,
formsemestre_id=formsemestre_id,
format=format,
version=version,
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
prefer_mail_perso=prefer_mail_perso,
)
if format == "pdfmail":
return redirect(
url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
etudid=etud.id,
formsemestre_id=formsemestre_id,
)
)
return r
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(
"/moduleimpl_evaluation_renumber",
sco_evaluation_db.moduleimpl_evaluation_renumber,
Permission.ScoView,
)
sco_publish(
"/moduleimpl_evaluation_move",
sco_evaluation_db.moduleimpl_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,
methods=["GET", "POST"],
)
sco_publish(
"/ue_delete",
sco_edit_ue.ue_delete,
Permission.ScoChangeFormation,
methods=["GET", "POST"],
)
sco_publish(
"/ue_edit",
sco_edit_ue.ue_edit,
Permission.ScoChangeFormation,
methods=["GET", "POST"],
)
@bp.route("/set_ue_niveau_competence", methods=["POST"])
@scodoc
@permission_required(Permission.ScoChangeFormation)
def set_ue_niveau_competence():
"""Associe UE et niveau.
Si le niveau_id est "", désassocie."""
ue_id = request.form.get("ue_id")
niveau_id = request.form.get("niveau_id")
if niveau_id == "":
niveau_id = None
ue: UniteEns = UniteEns.query.get_or_404(ue_id)
niveau = None if niveau_id is None else ApcNiveau.query.get_or_404(niveau_id)
try:
ue.set_niveau_competence(niveau)
except ScoFormationConflict:
return "", 409 # conflict
return "", 204
@bp.route("/set_ue_parcours", methods=["POST"])
@scodoc
@permission_required(Permission.ScoChangeFormation)
def set_ue_parcours():
"""Associe UE et parcours BUT.
Si le parcour_id est "", désassocie."""
ue_id = request.form.get("ue_id")
parcour_id = request.form.get("parcour_id")
if parcour_id == "":
parcour_id = None
ue: UniteEns = UniteEns.query.get_or_404(ue_id)
parcour = None if parcour_id is None else ApcParcours.query.get_or_404(parcour_id)
try:
ue.set_parcour(parcour)
except ScoFormationConflict:
return "", 409 # conflict
return "", 204
@bp.route("/get_ue_niveaux_options_html")
@scodoc
@permission_required(Permission.ScoView)
def get_ue_niveaux_options_html():
"""fragment html avec les options du menu de sélection du
niveau de compétences associé à une UE
"""
ue_id = request.args.get("ue_id")
ue: UniteEns = UniteEns.query.get_or_404(ue_id)
return apc_edit_ue.get_ue_niveaux_options_html(ue)
@bp.route("/ue_list") # backward compat
@bp.route("/ue_table")
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def ue_table(formation_id=None, semestre_idx=1, msg=""):
return sco_edit_ue.ue_table(
formation_id=formation_id, semestre_idx=semestre_idx, msg=msg
)
@bp.route("/ue_infos/ 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. Création d'une formation (avec UE, matières, modules)
à partir un fichier XML (réservé aux utilisateurs avertis)
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)
Pour ajouter un enseignant, choisissez un nom dans le menu Enseignant {ens_id} déjà dans la liste ! Taper le début du nom de l'enseignant. Expérimental: formule de calcul de la moyenne %(target)s Attention: l'utilisation de formules ralentit considérablement
les traitements. A utiliser uniquement dans les 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).Programmes pédagogiques
""",
]
T = sco_formations.formation_list_table()
H.append(T.html())
if editable:
H.append(
f"""
Référentiels de compétences
"""
)
H.append(html_sco_header.sco_footer())
return "\n".join(H)
# --------------------------------------------------------------------
#
# Notes Methods
#
# --------------------------------------------------------------------
# --- Formations
sco_publish(
"/do_formation_create",
sco_edit_formation.do_formation_create,
Permission.ScoChangeFormation,
methods=["GET", "POST"],
)
sco_publish(
"/do_formation_delete",
sco_edit_formation.do_formation_delete,
Permission.ScoChangeFormation,
methods=["GET", "POST"],
)
@bp.route("/formation_list")
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def formation_list(format=None, formation_id=None, args={}):
"""List formation(s) with given id, or matching args
(when args is given, formation_id is ignored).
"""
r = sco_formations.formation_list(formation_id=formation_id, args=args)
return scu.sendResult(r, name="formation", format=format)
@bp.route("/formation_export")
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def formation_export(
formation_id, export_ids=False, format=None, export_codes_apo=True
):
"Export de la formation au format indiqué (xml ou json)"
return sco_formations.formation_export(
formation_id,
export_ids=export_ids,
format=format,
export_codes_apo=export_codes_apo,
)
@bp.route("/formation_import_xml_form", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoChangeFormation)
@scodoc7func
def formation_import_xml_form():
"form import d'une formation en XML"
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
(("xmlfile", {"input_type": "file", "title": "Fichier XML", "size": 30}),),
submitlabel="Importer",
cancelbutton="Annuler",
)
if tf[0] == 0:
return f"""
{ html_sco_header.sco_header(page_title="Import d'une formation") }
Import d'une formation
Import effectué !
{ html_sco_header.sco_footer() }
"""
sco_publish(
"/formation_create_new_version",
sco_formations.formation_create_new_version,
Permission.ScoChangeFormation,
)
# --- UE
sco_publish(
"/ue_list",
sco_edit_ue.ue_list,
Permission.ScoView,
)
sco_publish(
"/module_move", sco_edit_formation.module_move, Permission.ScoChangeFormation
)
sco_publish("/ue_move", sco_edit_formation.ue_move, Permission.ScoChangeFormation)
@bp.route("/ue_clone", methods=["POST"])
@scodoc
@permission_required(Permission.ScoChangeFormation)
def ue_clone():
"""Clone existing UE"""
ue_id = int(request.form.get("ue_id"))
ue = UniteEns.query.get_or_404(ue_id)
ue2 = ue.clone()
db.session.add(ue2)
db.session.commit()
flash(f"UE {ue.acronyme} dupliquée")
return flask.redirect(
url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
semestre_idx=ue.semestre_idx,
formation_id=ue.formation_id,
)
)
# --- Semestres de formation
@bp.route(
"/formsemestre_list", methods=["GET", "POST"]
) # pour compat anciens clients PHP
@scodoc
@permission_required_compat_scodoc7(Permission.ScoView)
@scodoc7func
def formsemestre_list(
format="json",
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')
"""
log("Warning: calling deprecated view formsemestre_list")
try:
formsemestre_id = int(formsemestre_id) if formsemestre_id is not None else None
formation_id = int(formation_id) if formation_id is not None else None
except ValueError:
return scu.json_error(404, "invalid id")
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(args=args)
return scu.sendResult(sems, name="formsemestre", format=format)
sco_publish(
"/formsemestre_edit_options",
sco_formsemestre_edit.formsemestre_edit_options,
Permission.ScoView,
methods=["GET", "POST"],
)
@bp.route("/formsemestre_change_lock", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoView) # acces vérifié dans la fonction
@scodoc7func
def formsemestre_change_lock(formsemestre_id, dialog_confirmed=False):
"Changement de l'état de verrouillage du semestre"
if not dialog_confirmed:
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etat = not sem["etat"]
if etat:
msg = "déverrouillage"
else:
msg = "verrouillage"
return scu.confirm_dialog(
f"Confirmer le {msg} du semestre ?
",
helpmsg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées.
Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment
(par son responsable ou un administrateur).
Le programme d'une formation qui a un semestre verrouillé ne peut plus être modifié.
""",
dest_url="",
cancel_url=url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
),
parameters={"formsemestre_id": formsemestre_id},
)
sco_formsemestre_edit.formsemestre_change_lock(formsemestre_id)
return flask.redirect(
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
)
sco_publish(
"/formsemestre_change_publication_bul",
sco_formsemestre_edit.formsemestre_change_publication_bul,
Permission.ScoView,
methods=["GET", "POST"],
)
sco_publish(
"/view_formsemestre_by_etape",
sco_formsemestre.view_formsemestre_by_etape,
Permission.ScoView,
)
@bp.route("/formsemestre_custommenu_edit", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def formsemestre_custommenu_edit(formsemestre_id):
"Dialogue modif menu"
# accessible à tous !
return sco_formsemestre_custommenu.formsemestre_custommenu_edit(formsemestre_id)
# --- dialogue modif enseignants/moduleimpl
@bp.route("/edit_enseignants_form", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def edit_enseignants_form(moduleimpl_id):
"modif liste enseignants/moduleimpl"
M, sem = sco_moduleimpl.can_change_ens(moduleimpl_id)
# --
header = html_sco_header.html_sem_header(
'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()
# Liste des enseignants avec forme pour affichage / saisie avec suggestion
userlist = sco_users.get_user_list()
uid2display = {} # uid : forme pour affichage = "NOM Prenom (login)"(login)"
for u in userlist:
uid2display[u.id] = u.get_nomplogin()
allowed_user_names = list(uid2display.values())
H = [
"
")
F = f"""
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", methods=["GET", "POST"]) @scodoc @permission_required(Permission.ScoView) @scodoc7func def edit_moduleimpl_expr(moduleimpl_id): """Edition formule calcul moyenne module Accessible par Admin, dir des etud et responsable module Inutilisé en ScoDoc 9. """ M, sem = sco_moduleimpl.can_change_ens(moduleimpl_id) H = [ html_sco_header.html_sem_header( 'Modification règle de calcul du module %s' % (moduleimpl_id, M["module"]["titre"]), ), _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.base_url, scu.get_request_args(), 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() elif tf[0] == -1: return flask.redirect( url_for( "notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id, ) ) else: sco_moduleimpl.do_moduleimpl_edit( { "moduleimpl_id": moduleimpl_id, "computation_expr": tf[2]["computation_expr"], }, formsemestre_id=sem["formsemestre_id"], ) sco_cache.invalidate_formsemestre( formsemestre_id=sem["formsemestre_id"] ) # > modif regle calcul return flask.redirect( url_for( "notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id, head_message="règle%20de%20calcul%20modifiée", ) ) @bp.route("/delete_moduleimpl_expr", methods=["GET", "POST"]) @scodoc @permission_required(Permission.ScoView) @scodoc7func def delete_moduleimpl_expr(moduleimpl_id): """Suppression formule calcul moyenne module Accessible par Admin, dir des etud et responsable module """ modimpl = ModuleImpl.query.get_or_404(moduleimpl_id) sco_moduleimpl.can_change_ens(moduleimpl_id) modimpl.computation_expr = None db.session.add(modimpl) db.session.commit() flash("Ancienne formule supprimée") return flask.redirect( url_for( "notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id, ) ) @bp.route("/view_module_abs") @scodoc @permission_required(Permission.ScoView) @scodoc7func def view_module_abs(moduleimpl_id, format="html"): """Visualisation des absences a un module""" M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0] sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"]) debut_sem = ndb.DateDMYtoISO(sem["date_debut"]) fin_sem = ndb.DateDMYtoISO(sem["date_fin"]) list_insc = sco_moduleimpl.moduleimpl_listeetuds(moduleimpl_id) T = [] for etudid in list_insc: nb_abs = sco_abs.count_abs( etudid=etudid, debut=debut_sem, fin=fin_sem, moduleimpl_id=moduleimpl_id, ) if nb_abs: nb_abs_just = sco_abs.count_abs_just( etudid=etudid, debut=debut_sem, fin=fin_sem, moduleimpl_id=moduleimpl_id, ) etud = sco_etud.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": url_for( "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid ), } ) H = [ html_sco_header.html_sem_header( 'Absences du module %s' % (moduleimpl_id, M["module"]["titre"]), page_title="Absences du module %s" % (M["module"]["titre"]), ) ] if not T and format == "html": return ( "\n".join(H) + "Aucune absence signalée
" + html_sco_header.sco_footer() ) 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.base_url, moduleimpl_id), filename="absmodule_" + scu.make_filename(M["module"]["titre"]), caption="Absences dans le module %s" % M["module"]["titre"], preferences=sco_preferences.SemPreferences(), ) if format != "html": return tab.make_page(format=format) return "\n".join(H) + tab.html() + html_sco_header.sco_footer() @bp.route("/delete_ue_expr/%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( 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( """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( """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)
OK, évaluation supprimée.
""" + html_sco_header.sco_footer() ) @bp.route("/evaluation_edit", methods=["GET", "POST"]) @scodoc @permission_required(Permission.ScoEnsView) @scodoc7func def evaluation_edit(evaluation_id): "form edit evaluation" return sco_evaluation_edit.evaluation_create_form( evaluation_id=evaluation_id, edit=True ) @bp.route("/evaluation_create", methods=["GET", "POST"]) @scodoc @permission_required(Permission.ScoEnsView) @scodoc7func def evaluation_create(moduleimpl_id): "form create evaluation" modimpl = ModuleImpl.query.get(moduleimpl_id) if modimpl is None: raise ScoValueError("Ce module n'existe pas ou plus !") return sco_evaluation_edit.evaluation_create_form( moduleimpl_id=moduleimpl_id, edit=False ) @bp.route("/evaluation_listenotes", methods=["GET", "POST"]) # API ScoDoc 7 compat @scodoc @permission_required_compat_scodoc7(Permission.ScoView) @scodoc7func def evaluation_listenotes(): """Affichage des notes d'une évaluation""" evaluation_id = None moduleimpl_id = None vals = scu.get_request_args() try: if "evaluation_id" in vals: evaluation_id = int(vals["evaluation_id"]) if "moduleimpl_id" in vals and vals["moduleimpl_id"]: moduleimpl_id = int(vals["moduleimpl_id"]) except ValueError as exc: raise ScoValueError("evaluation_listenotes: id invalides !") from exc format = vals.get("format", "html") html_content, page_title = sco_liste_notes.do_evaluation_listenotes( evaluation_id=evaluation_id, moduleimpl_id=moduleimpl_id, format=format ) if format == "html": H = html_sco_header.sco_header( page_title=page_title, cssstyles=["css/verticalhisto.css"], javascripts=["js/etud_info.js"], init_qtip=True, ) F = html_sco_header.sco_footer() return H + html_content + F else: return html_content sco_publish( "/evaluation_list_operations", sco_undo_notes.evaluation_list_operations, Permission.ScoView, ) sco_publish( "/evaluation_check_absences_html", sco_evaluation_check_abs.evaluation_check_absences_html, Permission.ScoView, ) sco_publish( "/formsemestre_check_absences_html", sco_evaluation_check_abs.formsemestre_check_absences_html, Permission.ScoView, ) # --- Placement des étudiants pour l'évaluation sco_publish( "/placement_eval_selectetuds", sco_placement.placement_eval_selectetuds, Permission.ScoEnsView, methods=["GET", "POST"], ) # --- Saisie des notes sco_publish( "/saisie_notes_tableur", sco_saisie_notes.saisie_notes_tableur, Permission.ScoEnsView, methods=["GET", "POST"], ) 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, methods=["GET", "POST"], ) sco_publish( "/do_evaluation_set_missing", sco_saisie_notes.do_evaluation_set_missing, Permission.ScoEnsView, methods=["GET", "POST"], ) sco_publish( "/evaluation_suppress_alln", sco_saisie_notes.evaluation_suppress_alln, Permission.ScoView, methods=["GET", "POST"], ) # --- Bulletins @bp.route("/formsemestre_bulletins_pdf") @scodoc @permission_required(Permission.ScoView) @scodoc7func def formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"): "Publie les bulletins dans un classeur PDF" pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( formsemestre_id, version=version ) return scu.sendPDFFile(pdfdoc, filename) _EXPL_BULL = """Versions des bulletins:""" + expl_bull, choose_mail=True, ) # not published def formsemestre_bulletins_choice( formsemestre_id, title="", explanation="", choose_mail=False ): """Choix d'une version de bulletin""" H = [ html_sco_header.html_sem_header(title), f"""