3461 lines
112 KiB
Python
3461 lines
112 KiB
Python
# -*- 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,
|
|
cursus_but,
|
|
jury_edit_manual,
|
|
jury_but,
|
|
jury_but_pv,
|
|
jury_but_validation_auto,
|
|
jury_but_view,
|
|
)
|
|
from app.but.forms import jury_but_forms
|
|
|
|
|
|
from app.comp import jury, res_sem
|
|
from app.comp.res_compat import NotesTableCompat
|
|
from app.models import (
|
|
Formation,
|
|
ScolarFormSemestreValidation,
|
|
ScolarAutorisationInscription,
|
|
ScolarNews,
|
|
Scolog,
|
|
)
|
|
from app.models.but_refcomp import ApcNiveau
|
|
from app.models.config import ScoDocSiteConfig
|
|
from app.models.etudiants import Identite
|
|
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
|
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, ScoPermissionDenied
|
|
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_formation_versions
|
|
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_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_pv_forms
|
|
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.gen_tables import GenTable
|
|
from app.scodoc.sco_pv_dict import descr_autorisations
|
|
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_formation_versions.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 = Identite.get_etud(etudid)
|
|
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.j2",
|
|
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"
|
|
|
|
response = 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": # ne renvoie rien dans ce cas (mails envoyés)
|
|
return redirect(
|
|
url_for(
|
|
"notes.formsemestre_bulletinetud",
|
|
scodoc_dept=g.scodoc_dept,
|
|
etudid=etud.id,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
return response
|
|
|
|
|
|
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("/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")
|
|
if ue_id is None:
|
|
log("WARNING: get_ue_niveaux_options_html missing ue_id arg")
|
|
return "???"
|
|
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/<int:ue_id>")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def ue_infos(ue_id):
|
|
ue = UniteEns.query.get_or_404(ue_id)
|
|
return sco_edit_apc.html_ue_infos(ue)
|
|
|
|
|
|
@bp.route("/ue_set_internal", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoChangeFormation)
|
|
@scodoc7func
|
|
def ue_set_internal(ue_id):
|
|
""""""
|
|
ue = models.UniteEns.query.get(ue_id)
|
|
if not ue:
|
|
raise ScoValueError("invalid ue_id")
|
|
ue.is_external = False
|
|
db.session.add(ue)
|
|
db.session.commit()
|
|
# Invalide les semestres de cette formation
|
|
ue.formation.invalidate_cached_sems()
|
|
|
|
return redirect(
|
|
url_for(
|
|
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=ue.formation_id
|
|
)
|
|
)
|
|
|
|
|
|
@bp.route("/ue_sharing_code")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def ue_sharing_code():
|
|
ue_code = request.args.get("ue_code")
|
|
ue_id = request.args.get("ue_id")
|
|
hide_ue_id = request.args.get("hide_ue_id")
|
|
return sco_edit_ue.ue_sharing_code(
|
|
ue_code=ue_code,
|
|
ue_id=None if ue_id is None else int(ue_id),
|
|
hide_ue_id=None if hide_ue_id is None else int(hide_ue_id),
|
|
)
|
|
|
|
|
|
sco_publish(
|
|
"/edit_ue_set_code_apogee",
|
|
sco_edit_ue.edit_ue_set_code_apogee,
|
|
Permission.ScoChangeFormation,
|
|
methods=["POST"],
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_edit_uecoefs",
|
|
sco_formsemestre_edit.formsemestre_edit_uecoefs,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
|
|
@bp.route("/formation_table_recap")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formation_table_recap(formation_id, format="html"):
|
|
return sco_formation_recap.formation_table_recap(formation_id, format=format)
|
|
|
|
|
|
sco_publish(
|
|
"/export_recap_formations_annee_scolaire",
|
|
sco_formation_recap.export_recap_formations_annee_scolaire,
|
|
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,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/matiere_delete",
|
|
sco_edit_matiere.matiere_delete,
|
|
Permission.ScoChangeFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/matiere_edit",
|
|
sco_edit_matiere.matiere_edit,
|
|
Permission.ScoChangeFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/module_create",
|
|
sco_edit_module.module_create,
|
|
Permission.ScoChangeFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/module_delete",
|
|
sco_edit_module.module_delete,
|
|
Permission.ScoChangeFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/module_edit",
|
|
sco_edit_module.module_edit,
|
|
Permission.ScoChangeFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/edit_module_set_code_apogee",
|
|
sco_edit_module.edit_module_set_code_apogee,
|
|
Permission.ScoChangeFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish("/module_list", sco_edit_module.module_table, Permission.ScoView)
|
|
sco_publish("/module_tag_search", sco_tag_module.module_tag_search, Permission.ScoView)
|
|
|
|
|
|
@bp.route("/module_tag_set", methods=["POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoEditFormationTags)
|
|
def module_tag_set():
|
|
"""Set tags on module"""
|
|
module_id = int(request.form.get("module_id"))
|
|
taglist = request.form.get("taglist")
|
|
return sco_tag_module.module_tag_set(module_id, taglist)
|
|
|
|
|
|
@bp.route("/module_clone", methods=["POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoChangeFormation)
|
|
def module_clone():
|
|
"""Clone existing module"""
|
|
module_id = int(request.form.get("module_id"))
|
|
module = Module.query.get_or_404(module_id)
|
|
module2 = module.clone()
|
|
db.session.add(module2)
|
|
db.session.commit()
|
|
flash(f"Module {module.code} dupliqué")
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.ue_table",
|
|
scodoc_dept=g.scodoc_dept,
|
|
semestre_idx=module.semestre_id,
|
|
formation_id=module.formation_id,
|
|
)
|
|
)
|
|
|
|
|
|
#
|
|
@bp.route("/")
|
|
@bp.route("/index_html")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def index_html():
|
|
"Page accueil formations"
|
|
|
|
editable = current_user.has_permission(Permission.ScoChangeFormation)
|
|
|
|
H = [
|
|
html_sco_header.sco_header(page_title="Programmes formations"),
|
|
"""<h2>Programmes pédagogiques</h2>
|
|
""",
|
|
]
|
|
T = sco_formations.formation_list_table()
|
|
|
|
H.append(T.html())
|
|
|
|
if editable:
|
|
H.append(
|
|
f"""
|
|
<p class="help">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.</p>
|
|
|
|
<ul>
|
|
<li><a class="stdlink" href="formation_create" id="link-create-formation">Créer une
|
|
formation</a>
|
|
</li>
|
|
<li><a class="stdlink" href="formation_import_xml_form">Importer une formation (xml)</a>
|
|
</li>
|
|
<li><a class="stdlink" href="{
|
|
url_for("notes.export_recap_formations_annee_scolaire",
|
|
scodoc_dept=g.scodoc_dept, annee_scolaire=scu.annee_scolaire()-1)
|
|
}">exporter les formations de l'année scolaire
|
|
{scu.annee_scolaire()-1} - {scu.annee_scolaire()}
|
|
</a>
|
|
</li>
|
|
<li><a class="stdlink" href="{
|
|
url_for("notes.export_recap_formations_annee_scolaire",
|
|
scodoc_dept=g.scodoc_dept, annee_scolaire=scu.annee_scolaire())
|
|
}">exporter les formations de l'année scolaire
|
|
{scu.annee_scolaire()} - {scu.annee_scolaire()+1}
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
<h3>Référentiels de compétences</h3>
|
|
<ul>
|
|
<li><a class="stdlink" href="{
|
|
url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept)
|
|
}">Liste des référentiels chargés</a>
|
|
</li>
|
|
</ul>
|
|
"""
|
|
)
|
|
|
|
H.append(html_sco_header.sco_footer())
|
|
return "\n".join(H)
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
#
|
|
# Notes Methods
|
|
#
|
|
# --------------------------------------------------------------------
|
|
|
|
# --- Formations
|
|
|
|
|
|
@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") }
|
|
<h2>Import d'une formation</h2>
|
|
<p>Création d'une formation (avec UE, matières, modules)
|
|
à partir un fichier XML (réservé aux utilisateurs avertis).
|
|
</p>
|
|
<p>S'il s'agit d'une formation par compétence (BUT), assurez-vous d'avoir
|
|
chargé le référentiel de compétences AVANT d'importer le fichier formation
|
|
(voir <a class="stdlink" href="{
|
|
url_for("notes.refcomp_table", scodoc_dept=g.scodoc_dept)
|
|
}">page des référentiels</a>).
|
|
</p>
|
|
{ tf[1] }
|
|
{ html_sco_header.sco_footer() }
|
|
"""
|
|
elif tf[0] == -1:
|
|
return flask.redirect(scu.NotesURL())
|
|
else:
|
|
formation_id, _, _ = sco_formations.formation_import_xml(
|
|
tf[2]["xmlfile"].read()
|
|
)
|
|
|
|
return f"""
|
|
{ html_sco_header.sco_header(page_title="Import d'une formation") }
|
|
<h2>Import effectué !</h2>
|
|
<ul>
|
|
<li><a class="stdlink" href="{
|
|
url_for("notes.ue_list", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
|
)}">Voir la formation</a>
|
|
</li>
|
|
<li><a class="stdlink" href="{
|
|
url_for("notes.formation_delete", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
|
)}">Supprimer cette formation</a>
|
|
(en cas d'erreur, par exemple pour charger auparavant le référentiel de compétences)
|
|
</li>
|
|
</ul>
|
|
{ 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_flip_lock", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView) # acces vérifié dans la vue
|
|
@scodoc7func
|
|
def formsemestre_flip_lock(formsemestre_id, dialog_confirmed=False):
|
|
"Changement de l'état de verrouillage du semestre"
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
dest_url = url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
)
|
|
if not formsemestre.est_chef_or_diretud():
|
|
raise ScoPermissionDenied("opération non autorisée", dest_url=dest_url)
|
|
if not dialog_confirmed:
|
|
msg = "verrouillage" if formsemestre.etat else "déverrouillage"
|
|
return scu.confirm_dialog(
|
|
f"<h2>Confirmer le {msg} du semestre ?</h2>",
|
|
help_msg="""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).
|
|
<br>
|
|
Le programme d'une formation qui a un semestre verrouillé ne peut plus être modifié.
|
|
""",
|
|
dest_url="",
|
|
cancel_url=dest_url,
|
|
parameters={"formsemestre_id": formsemestre_id},
|
|
)
|
|
|
|
formsemestre.flip_lock()
|
|
db.session.commit()
|
|
|
|
return flask.redirect(dest_url)
|
|
|
|
|
|
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 <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
|
|
% (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 = [
|
|
"<ul><li><b>%s</b> (responsable)</li>"
|
|
% uid2display.get(M["responsable_id"], M["responsable_id"])
|
|
]
|
|
for ens in M["ens"]:
|
|
u = User.query.get(ens["ens_id"])
|
|
if u:
|
|
nom = u.get_nomcomplet()
|
|
else:
|
|
nom = "? (compte inconnu)"
|
|
H.append(
|
|
f"""
|
|
<li>{nom} (<a class="stdlink" href="{
|
|
url_for('notes.edit_enseignants_form_delete',
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id,
|
|
ens_id=ens["ens_id"])
|
|
}">supprimer</a>)
|
|
</li>"""
|
|
)
|
|
H.append("</ul>")
|
|
F = f"""<p class="help">Les enseignants d'un module ont le droit de
|
|
saisir et modifier toutes les notes des évaluations de ce module.
|
|
</p>
|
|
<p class="help">Pour changer le responsable du module, passez par la
|
|
page "<a class="stdlink" href="{
|
|
url_for("notes.formsemestre_editwithmodules", scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=M["formsemestre_id"])
|
|
}">Modification du semestre</a>",
|
|
accessible uniquement au responsable de la formation (chef de département)
|
|
</p>
|
|
"""
|
|
|
|
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": url_for(
|
|
"users.get_user_list_xml", scodoc_dept=g.scodoc_dept
|
|
)
|
|
+ "?",
|
|
"varname": "start",
|
|
"json": False,
|
|
"noresults": "Valeur invalide !",
|
|
"timeout": 60000,
|
|
},
|
|
},
|
|
),
|
|
]
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
modform,
|
|
submitlabel="Ajouter enseignant",
|
|
cancelbutton="Annuler",
|
|
)
|
|
if tf[0] == 0:
|
|
return header + "\n".join(H) + tf[1] + F + footer
|
|
elif tf[0] == -1:
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=moduleimpl_id,
|
|
)
|
|
)
|
|
else:
|
|
ens_id = User.get_user_id_from_nomplogin(tf[2]["ens_id"])
|
|
if not ens_id:
|
|
H.append(
|
|
'<p class="help">Pour ajouter un enseignant, choisissez un nom dans le menu</p>'
|
|
)
|
|
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(
|
|
f"""<p class="help">Enseignant {ens_id} déjà dans la liste !</p>"""
|
|
)
|
|
else:
|
|
sco_moduleimpl.do_ens_create(
|
|
{"moduleimpl_id": moduleimpl_id, "ens_id": ens_id}
|
|
)
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.edit_enseignants_form",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=moduleimpl_id,
|
|
)
|
|
)
|
|
return header + "\n".join(H) + tf[1] + F + footer
|
|
|
|
|
|
@bp.route("/edit_moduleimpl_resp", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def edit_moduleimpl_resp(moduleimpl_id: int):
|
|
"""Changement d'un enseignant responsable de module
|
|
Accessible par Admin et dir des etud si flag resp_can_change_ens
|
|
"""
|
|
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
|
modimpl.can_change_ens_by(current_user, raise_exc=True) # access control
|
|
H = [
|
|
html_sco_header.html_sem_header(
|
|
f"""Modification du responsable du <a href="{
|
|
url_for("notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
|
}">module {modimpl.module.titre or ""}</a>""",
|
|
javascripts=["libjs/AutoSuggest.js"],
|
|
cssstyles=["css/autosuggest_inquisitor.css"],
|
|
bodyOnLoad="init_tf_form('')",
|
|
)
|
|
]
|
|
help_str = """<p class="help">Taper le début du nom de l'enseignant.</p>"""
|
|
# Liste des enseignants avec forme pour affichage / saisie avec suggestion
|
|
userlist = [sco_users.user_info(user=u) for u in sco_users.get_user_list()]
|
|
uid2display = {} # uid : forme pour affichage = "NOM Prenom (login)"
|
|
for u in userlist:
|
|
uid2display[u["id"]] = u["nomplogin"]
|
|
allowed_user_names = list(uid2display.values())
|
|
|
|
initvalues = modimpl.to_dict(with_module=False)
|
|
initvalues["responsable_id"] = uid2display.get(
|
|
modimpl.responsable_id, modimpl.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": url_for(
|
|
"users.get_user_list_xml", scodoc_dept=g.scodoc_dept
|
|
)
|
|
+ "?",
|
|
"varname": "start",
|
|
"json": False,
|
|
"noresults": "Valeur invalide !",
|
|
"timeout": 60000,
|
|
},
|
|
},
|
|
),
|
|
]
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
form,
|
|
submitlabel="Changer responsable",
|
|
cancelbutton="Annuler",
|
|
initvalues=initvalues,
|
|
)
|
|
if tf[0] == 0:
|
|
return "\n".join(H) + tf[1] + help_str + 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:
|
|
responsable_id = User.get_user_id_from_nomplogin(tf[2]["responsable_id"])
|
|
if not responsable_id:
|
|
# presque impossible: tf verifie les valeurs (mais qui peuvent changer entre temps)
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=moduleimpl_id,
|
|
)
|
|
)
|
|
|
|
modimpl.responsable_id = responsable_id
|
|
db.session.add(modimpl)
|
|
db.session.commit()
|
|
flash("Responsable modifié")
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=moduleimpl_id,
|
|
)
|
|
)
|
|
|
|
|
|
_EXPR_HELP = """<p class="help">Expérimental: formule de calcul de la moyenne %(target)s</p>
|
|
<p class="help">Attention: l'utilisation de formules ralentit considérablement
|
|
les traitements. A utiliser uniquement dans les cas ne pouvant pas être traités autrement.</p>
|
|
<p class="help">Dans la formule, les variables suivantes sont définies:</p>
|
|
<ul class="help">
|
|
<li><tt>moy</tt> la moyenne, calculée selon la règle standard (moyenne pondérée)</li>
|
|
<li><tt>moy_is_valid</tt> vrai si la moyenne est valide (numérique)</li>
|
|
<li><tt>moy_val</tt> la valeur de la moyenne (nombre, valant 0 si invalide)</li>
|
|
<li><tt>notes</tt> vecteur des notes (/20) aux %(objs)s</li>
|
|
<li><tt>coefs</tt> vecteur des coefficients des %(objs)s, les coefs des %(objs)s sans notes (ATT, EXC) étant mis à zéro</li>
|
|
<li><tt>cmask</tt> vecteur de 0/1, 0 si le coef correspondant a été annulé</li>
|
|
<li>Nombre d'absences: <tt>nb_abs</tt>, <tt>nb_abs_just</tt>, <tt>nb_abs_nojust</tt> (en demi-journées)</li>
|
|
</ul>
|
|
<p class="help">Les éléments des vecteurs sont ordonnés dans l'ordre des %(objs)s%(ordre)s.</p>
|
|
<p class="help">Les fonctions suivantes sont utilisables: <tt>abs, cmp, dot, len, map, max, min, pow, reduce, round, sum, ifelse</tt>.</p>
|
|
<p class="help">La notation <tt>V(1,2,3)</tt> représente un vecteur <tt>(1,2,3)</tt>.</p>
|
|
<p class="help"></p>Pour indiquer que la note calculée n'existe pas, utiliser la chaîne <tt>'NA'</tt>.</p>
|
|
<p class="help">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 <tt>#</tt></p>
|
|
"""
|
|
|
|
|
|
@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 <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
|
|
% (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
|
|
flash("règle de calcul modifiée")
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=moduleimpl_id,
|
|
)
|
|
)
|
|
|
|
|
|
@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 <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
|
|
% (moduleimpl_id, M["module"]["titre"]),
|
|
page_title="Absences du module %s" % (M["module"]["titre"]),
|
|
)
|
|
]
|
|
if not T and format == "html":
|
|
return (
|
|
"\n".join(H)
|
|
+ "<p>Aucune absence signalée</p>"
|
|
+ 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/<int:formsemestre_id>/<int:ue_id>", methods=["GET", "POST"])
|
|
@scodoc
|
|
def delete_ue_expr(formsemestre_id: int, ue_id: int):
|
|
"""Efface une expression de calcul d'UE"""
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
if not formsemestre.can_be_edited_by(current_user):
|
|
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
|
|
expr = FormSemestreUEComputationExpr.query.filter_by(
|
|
formsemestre_id=formsemestre_id, ue_id=ue_id
|
|
).first()
|
|
if expr is not None:
|
|
db.session.delete(expr)
|
|
db.session.commit()
|
|
flash("formule supprimée")
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
|
|
|
|
@bp.route("/formsemestre_enseignants_list")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_enseignants_list(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(formsemestre_id)
|
|
# resp. de modules:
|
|
mods = sco_moduleimpl.moduleimpl_withmodule_list(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:
|
|
u = User.query.filter_by(id=ens).first()
|
|
if not u:
|
|
continue
|
|
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": u.user_name,
|
|
"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"] or "?" for x in sem_ens[ens]["mods"]]
|
|
)
|
|
|
|
# ajoute infos sur enseignant:
|
|
for ens in sem_ens:
|
|
sem_ens[ens].update(sco_users.user_info(ens))
|
|
if sem_ens[ens]["email"]:
|
|
sem_ens[ens]["_email_target"] = "mailto:%s" % sem_ens[ens]["email"]
|
|
|
|
sem_ens_list = list(sem_ens.values())
|
|
sem_ens_list.sort(key=itemgetter("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(
|
|
"Enseignants du semestre", with_page_header=False
|
|
),
|
|
base_url="%s?formsemestre_id=%s" % (request.base_url, 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(formsemestre_id),
|
|
)
|
|
return T.make_page(page_title=title, title=title, format=format)
|
|
|
|
|
|
@bp.route("/edit_enseignants_form_delete", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def edit_enseignants_form_delete(moduleimpl_id, ens_id: int):
|
|
"""remove ens from this modueimpl
|
|
|
|
ens_id: user.id
|
|
"""
|
|
M, _ = sco_moduleimpl.can_change_ens(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(f"invalid ens_id ({ens_id})")
|
|
ndb.SimpleQuery(
|
|
"""DELETE FROM notes_modules_enseignants
|
|
WHERE moduleimpl_id = %(moduleimpl_id)s
|
|
AND ens_id = %(ens_id)s
|
|
""",
|
|
{"moduleimpl_id": moduleimpl_id, "ens_id": ens_id},
|
|
)
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.edit_enseignants_form",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=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")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def do_formsemestre_inscription_listinscrits(formsemestre_id, format=None):
|
|
"""Liste les inscrits (état I) à ce semestre et cache le résultat"""
|
|
r = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
|
|
formsemestre_id
|
|
)
|
|
return scu.sendResult(r, format=format, name="inscrits")
|
|
|
|
|
|
@bp.route("/formsemestre_desinscription", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoImplement)
|
|
@scodoc7func
|
|
def formsemestre_desinscription(etudid, formsemestre_id, dialog_confirmed=False):
|
|
"""désinscrit 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é.
|
|
"""
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
sem = formsemestre.to_dict() # compat
|
|
# -- check lock
|
|
if not formsemestre.etat:
|
|
raise ScoValueError("desinscription impossible: semestre verrouille")
|
|
|
|
# -- Si décisions de jury, désinscription interdite
|
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
|
if nt.etud_has_decision(etudid):
|
|
raise ScoValueError(
|
|
f"""Désinscription impossible: l'étudiant a une décision de jury
|
|
(la supprimer avant si nécessaire:
|
|
<a href="{
|
|
url_for("notes.formsemestre_validation_suppress_etud",
|
|
scodoc_dept=g.scodoc_dept, etudid=etudid,
|
|
formsemestre_id=formsemestre_id)
|
|
}">supprimer décision jury</a>
|
|
)
|
|
"""
|
|
)
|
|
if not dialog_confirmed:
|
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
|
if formsemestre.modalite != "EXT":
|
|
msg_ext = """
|
|
<p>%s sera désinscrit de tous les modules du semestre %s (%s - %s).</p>
|
|
<p>Cette opération ne doit être utilisée que pour corriger une <b>erreur</b> !
|
|
Un étudiant réellement inscrit doit le rester, le faire éventuellement <b>démissionner<b>.
|
|
</p>
|
|
""" % (
|
|
etud["nomprenom"],
|
|
sem["titre_num"],
|
|
sem["date_debut"],
|
|
sem["date_fin"],
|
|
)
|
|
else: # semestre extérieur
|
|
msg_ext = """
|
|
<p>%s sera désinscrit du semestre extérieur %s (%s - %s).</p>
|
|
""" % (
|
|
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 = """<p class="warning">Attention: le semestre extérieur
|
|
sera supprimé car il n'a pas d'autre étudiant inscrit.
|
|
</p>
|
|
"""
|
|
return scu.confirm_dialog(
|
|
"""<h2>Confirmer la demande de désinscription ?</h2>""" + msg_ext,
|
|
dest_url="",
|
|
cancel_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
),
|
|
parameters={"etudid": etudid, "formsemestre_id": formsemestre_id},
|
|
)
|
|
|
|
sco_formsemestre_inscriptions.do_formsemestre_desinscription(
|
|
etudid, formsemestre_id
|
|
)
|
|
|
|
flash("Étudiant désinscrit")
|
|
return redirect(
|
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
|
)
|
|
|
|
|
|
sco_publish(
|
|
"/do_formsemestre_desinscription",
|
|
sco_formsemestre_inscriptions.do_formsemestre_desinscription,
|
|
Permission.ScoEtudInscrit,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
|
|
@bp.route(
|
|
"/etud_desinscrit_ue/<int:etudid>/<int:formsemestre_id>/<int:ue_id>",
|
|
methods=["GET", "POST"],
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.ScoEtudInscrit)
|
|
def etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
|
|
"""
|
|
- En classique: désinscrit l'etudiant de tous les modules de cette UE dans ce semestre.
|
|
- En APC: dispense de l'UE indiquée.
|
|
"""
|
|
etud = Identite.get_etud(etudid)
|
|
ue = UniteEns.query.get_or_404(ue_id)
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
if ue.formation.is_apc():
|
|
if (
|
|
DispenseUE.query.filter_by(
|
|
formsemestre_id=formsemestre_id, etudid=etudid, ue_id=ue_id
|
|
).count()
|
|
== 0
|
|
):
|
|
disp = DispenseUE(
|
|
formsemestre_id=formsemestre_id, ue_id=ue_id, etudid=etudid
|
|
)
|
|
db.session.add(disp)
|
|
db.session.commit()
|
|
log(f"etud_desinscrit_ue {etud} {ue}")
|
|
Scolog.logdb(
|
|
method="etud_desinscrit_ue",
|
|
etudid=etud.id,
|
|
msg=f"Désinscription de l'UE {ue.acronyme} de {formsemestre.titre_annee()}",
|
|
commit=True,
|
|
)
|
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
|
else:
|
|
sco_moduleimpl_inscriptions.do_etud_desinscrit_ue_classic(
|
|
etudid, formsemestre_id, ue_id
|
|
)
|
|
flash(f"{etud.nomprenom} déinscrit de {ue.acronyme}")
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.moduleimpl_inscriptions_stats",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
|
|
|
|
@bp.route(
|
|
"/etud_inscrit_ue/<int:etudid>/<int:formsemestre_id>/<int:ue_id>",
|
|
methods=["GET", "POST"],
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.ScoEtudInscrit)
|
|
def etud_inscrit_ue(etudid, formsemestre_id, ue_id):
|
|
"""
|
|
En classic: inscrit l'étudiant à tous les modules de cette UE dans ce semestre.
|
|
En APC: enlève la dispense de cette UE s'il y en a une.
|
|
"""
|
|
formsemestre = FormSemestre.query.filter_by(
|
|
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
etud = Identite.get_etud(etudid)
|
|
ue = UniteEns.query.get_or_404(ue_id)
|
|
if ue.formation.is_apc():
|
|
for disp in DispenseUE.query.filter_by(
|
|
formsemestre_id=formsemestre_id, etudid=etud.id, ue_id=ue_id
|
|
):
|
|
db.session.delete(disp)
|
|
log(f"etud_inscrit_ue {etud} {ue}")
|
|
Scolog.logdb(
|
|
method="etud_inscrit_ue",
|
|
etudid=etud.id,
|
|
msg=f"Inscription à l'UE {ue.acronyme} de {formsemestre.titre_annee()}",
|
|
commit=True,
|
|
)
|
|
db.session.commit()
|
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
|
else:
|
|
# Formations classiques: joue sur les inscriptions aux modules
|
|
sco_moduleimpl_inscriptions.do_etud_inscrit_ue(etud.id, formsemestre_id, ue_id)
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.moduleimpl_inscriptions_stats",
|
|
scodoc_dept=g.scodoc_dept,
|
|
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,
|
|
methods=["GET", "POST"],
|
|
)
|
|
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,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/moduleimpl_inscriptions_stats",
|
|
sco_moduleimpl_inscriptions.moduleimpl_inscriptions_stats,
|
|
Permission.ScoView,
|
|
)
|
|
|
|
|
|
# --- Evaluations
|
|
|
|
|
|
@bp.route("/evaluation_delete", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoEnsView)
|
|
@scodoc7func
|
|
def evaluation_delete(evaluation_id):
|
|
"""Form delete evaluation"""
|
|
El = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": evaluation_id})
|
|
if not El:
|
|
raise ScoValueError("Evaluation inexistante ! (%s)" % evaluation_id)
|
|
E = El[0]
|
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
|
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
|
tit = "Suppression de l'évaluation %(description)s (%(jour)s)" % E
|
|
etat = sco_evaluations.do_evaluation_etat(evaluation_id)
|
|
H = [
|
|
html_sco_header.html_sem_header(tit, with_h2=False),
|
|
"""<h2 class="formsemestre">Module <tt>%(code)s</tt> %(titre)s</h2>""" % Mod,
|
|
"""<h3>%s</h3>""" % tit,
|
|
"""<p class="help">Opération <span class="redboldtext">irréversible</span>. Si vous supprimez l'évaluation, vous ne pourrez pas retrouver les notes associées.</p>""",
|
|
]
|
|
warning = False
|
|
if etat["nb_notes_total"]:
|
|
warning = True
|
|
nb_desinscrits = etat["nb_notes_total"] - etat["nb_notes"]
|
|
H.append(
|
|
"""<div class="ue_warning"><span>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</span>""")
|
|
if etat["nb_notes"] == 0:
|
|
H.append(
|
|
"""<p>Vous pouvez quand même supprimer l'évaluation, les notes des étudiants désincrits seront effacées.</p>"""
|
|
)
|
|
|
|
if etat["nb_notes"]:
|
|
H.append(
|
|
"""<p>Suppression impossible (effacer les notes d'abord)</p><p><a class="stdlink" href="moduleimpl_status?moduleimpl_id=%s">retour au tableau de bord du module</a></p></div>"""
|
|
% E["moduleimpl_id"]
|
|
)
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
|
if warning:
|
|
H.append("""</div>""")
|
|
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
(("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()
|
|
elif tf[0] == -1:
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=E["moduleimpl_id"],
|
|
)
|
|
)
|
|
else:
|
|
sco_evaluation_db.do_evaluation_delete(E["evaluation_id"])
|
|
return (
|
|
"\n".join(H)
|
|
+ f"""<p>OK, évaluation supprimée.</p>
|
|
<p><a class="stdlink" href="{
|
|
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=E["moduleimpl_id"])
|
|
}">Continuer</a></p>"""
|
|
+ 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(
|
|
"/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:
|
|
<ul>
|
|
<li><bf>courte</bf>: moyennes des modules (en BUT: seulement les moyennes d'UE)</li>
|
|
<li><bf>intermédiaire</bf>: moyennes des modules et notes des évaluations sélectionnées</li>
|
|
<li><bf>complète</bf>: toutes les notes</li>
|
|
</ul>"""
|
|
|
|
|
|
@bp.route("/formsemestre_bulletins_pdf_choice")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_bulletins_pdf_choice(formsemestre_id, version=None):
|
|
"""Choix version puis envois classeur bulletins pdf"""
|
|
if version:
|
|
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
|
formsemestre_id, version=version
|
|
)
|
|
return scu.sendPDFFile(pdfdoc, filename)
|
|
return formsemestre_bulletins_choice(
|
|
formsemestre_id,
|
|
title="Choisir la version des bulletins à générer",
|
|
explanation=_EXPL_BULL,
|
|
)
|
|
|
|
|
|
@bp.route("/etud_bulletins_pdf")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def etud_bulletins_pdf(etudid, version="selectedevals"):
|
|
"Publie tous les bulletins d'un etudiants dans un classeur PDF"
|
|
pdfdoc, filename = sco_bulletins_pdf.get_etud_bulletins_pdf(etudid, version=version)
|
|
return scu.sendPDFFile(pdfdoc, filename)
|
|
|
|
|
|
@bp.route("/formsemestre_bulletins_mailetuds_choice")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_bulletins_mailetuds_choice(
|
|
formsemestre_id,
|
|
version=None,
|
|
dialog_confirmed=False,
|
|
prefer_mail_perso=0,
|
|
):
|
|
"""Choix version puis envoi classeur bulletins pdf"""
|
|
if version:
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_bulletins_mailetuds",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
version=version,
|
|
dialog_confirmed=dialog_confirmed,
|
|
prefer_mail_perso=prefer_mail_perso,
|
|
)
|
|
)
|
|
|
|
expl_bull = """Versions des bulletins:
|
|
<ul>
|
|
<li><b>courte</b>: moyennes des modules</li>
|
|
<li><b>intermédiaire</b>: moyennes des modules et notes des évaluations sélectionnées</li>
|
|
<li><b>complète</b>: toutes les notes</li>
|
|
<ul>"""
|
|
return formsemestre_bulletins_choice(
|
|
formsemestre_id,
|
|
title="Choisir la version des bulletins à envoyer par mail",
|
|
explanation="""Chaque étudiant (non démissionnaire ni défaillant)
|
|
ayant une adresse mail connue de ScoDoc
|
|
recevra une copie PDF de son bulletin de notes, dans la version choisie.
|
|
</p><p>"""
|
|
+ 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"""
|
|
<form name="f" method="GET" action="{request.base_url}">
|
|
<input type="hidden" name="formsemestre_id" value="{formsemestre_id}"></input>
|
|
""",
|
|
]
|
|
H.append("""<select name="version" class="noprint">""")
|
|
for version, description in (
|
|
("short", "Version courte"),
|
|
("selectedevals", "Version intermédiaire"),
|
|
("long", "Version complète"),
|
|
):
|
|
H.append(f"""<option value="{version}">{description}</option>""")
|
|
|
|
H.append("""</select> <input type="submit" value="Générer"/>""")
|
|
if choose_mail:
|
|
H.append(
|
|
"""<div>
|
|
<input type="checkbox" name="prefer_mail_perso" value="1"
|
|
/>Utiliser si possible les adresses personnelles
|
|
</div>"""
|
|
)
|
|
|
|
H.append(f"""<p class="help">{explanation}</p>""")
|
|
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
|
|
|
|
|
@bp.route("/formsemestre_bulletins_mailetuds")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_bulletins_mailetuds(
|
|
formsemestre_id,
|
|
version="long",
|
|
dialog_confirmed=False,
|
|
prefer_mail_perso=0,
|
|
):
|
|
"""Envoie à chaque etudiant son bulletin
|
|
(inscrit non démissionnaire ni défaillant et ayant un mail renseigné dans ScoDoc)
|
|
"""
|
|
prefer_mail_perso = int(prefer_mail_perso)
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
inscriptions = [
|
|
inscription
|
|
for inscription in formsemestre.inscriptions
|
|
if inscription.etat == scu.INSCRIT
|
|
]
|
|
#
|
|
if not sco_bulletins.can_send_bulletin_by_mail(formsemestre_id):
|
|
raise AccessDenied("vous n'avez pas le droit d'envoyer les bulletins")
|
|
# Confirmation dialog
|
|
if not dialog_confirmed:
|
|
return scu.confirm_dialog(
|
|
f"<h2>Envoyer les {len(inscriptions)} bulletins par e-mail aux étudiants inscrits ?",
|
|
dest_url="",
|
|
cancel_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
),
|
|
parameters={
|
|
"version": version,
|
|
"formsemestre_id": formsemestre_id,
|
|
"prefer_mail_perso": prefer_mail_perso,
|
|
},
|
|
)
|
|
|
|
# Make each bulletin
|
|
nb_sent = 0
|
|
for inscription in inscriptions:
|
|
sent, _ = sco_bulletins.do_formsemestre_bulletinetud(
|
|
formsemestre,
|
|
inscription.etud,
|
|
version=version,
|
|
prefer_mail_perso=prefer_mail_perso,
|
|
format="pdfmail",
|
|
)
|
|
if sent:
|
|
nb_sent += 1
|
|
#
|
|
return f"""
|
|
{html_sco_header.sco_header()}
|
|
<p>{nb_sent} bulletins sur {len(inscriptions)} envoyés par mail !</p>
|
|
<p><a class="stdlink" href="{url_for('notes.formsemestre_status',
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id)
|
|
}">continuer</a></p>
|
|
{html_sco_header.sco_footer()}
|
|
"""
|
|
|
|
|
|
sco_publish(
|
|
"/external_ue_create_form",
|
|
sco_ue_external.external_ue_create_form,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
|
|
@bp.route("/appreciation_add_form", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoEnsView)
|
|
@scodoc7func
|
|
def appreciation_add_form(
|
|
etudid=None,
|
|
formsemestre_id=None,
|
|
id=None, # si id, edit
|
|
suppress=False, # si true, supress id
|
|
):
|
|
"form ajout ou edition d'une appreciation"
|
|
cnx = ndb.GetDBConnexion()
|
|
if id: # edit mode
|
|
apps = sco_etud.appreciations_list(cnx, args={"id": id})
|
|
if not apps:
|
|
raise ScoValueError("id d'appreciation invalide !")
|
|
app = apps[0]
|
|
formsemestre_id = app["formsemestre_id"]
|
|
etudid = app["etudid"]
|
|
vals = scu.get_request_args()
|
|
if "edit" in vals:
|
|
edit = int(vals["edit"])
|
|
elif id:
|
|
edit = 1
|
|
else:
|
|
edit = 0
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
# check custom access permission
|
|
can_edit_app = (current_user.id in sem["responsables"]) or (
|
|
current_user.has_permission(Permission.ScoEtudInscrit)
|
|
)
|
|
if not can_edit_app:
|
|
raise AccessDenied("vous n'avez pas le droit d'ajouter une appreciation")
|
|
#
|
|
bull_url = "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" % (
|
|
formsemestre_id,
|
|
etudid,
|
|
)
|
|
if suppress:
|
|
sco_etud.appreciations_delete(cnx, id)
|
|
logdb(cnx, method="appreciation_suppress", etudid=etudid, msg="")
|
|
return flask.redirect(bull_url)
|
|
#
|
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
|
if id:
|
|
a = "Edition"
|
|
else:
|
|
a = "Ajout"
|
|
H = [
|
|
html_sco_header.sco_header()
|
|
+ "<h2>%s d'une appréciation sur %s</h2>" % (a, etud["nomprenom"])
|
|
]
|
|
F = html_sco_header.sco_footer()
|
|
descr = [
|
|
("edit", {"input_type": "hidden", "default": edit}),
|
|
("etudid", {"input_type": "hidden"}),
|
|
("formsemestre_id", {"input_type": "hidden"}),
|
|
("id", {"input_type": "hidden"}),
|
|
("comment", {"title": "", "input_type": "textarea", "rows": 4, "cols": 60}),
|
|
]
|
|
if id:
|
|
initvalues = {
|
|
"etudid": etudid,
|
|
"formsemestre_id": formsemestre_id,
|
|
"comment": app["comment"],
|
|
}
|
|
else:
|
|
initvalues = {}
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
descr,
|
|
initvalues=initvalues,
|
|
cancelbutton="Annuler",
|
|
submitlabel="Ajouter appréciation",
|
|
)
|
|
if tf[0] == 0:
|
|
return "\n".join(H) + "\n" + tf[1] + F
|
|
elif tf[0] == -1:
|
|
return flask.redirect(bull_url)
|
|
else:
|
|
args = {
|
|
"etudid": etudid,
|
|
"formsemestre_id": formsemestre_id,
|
|
"author": current_user.user_name,
|
|
"comment": tf[2]["comment"],
|
|
}
|
|
if edit:
|
|
args["id"] = id
|
|
sco_etud.appreciations_edit(cnx, args)
|
|
else: # nouvelle
|
|
sco_etud.appreciations_create(cnx, args)
|
|
# log
|
|
logdb(
|
|
cnx,
|
|
method="appreciation_add",
|
|
etudid=etudid,
|
|
msg=tf[2]["comment"],
|
|
)
|
|
# ennuyeux mais necessaire (pour le PDF seulement)
|
|
sco_cache.invalidate_formsemestre(
|
|
pdfonly=True, formsemestre_id=formsemestre_id
|
|
) # > appreciation_add
|
|
return flask.redirect(bull_url)
|
|
|
|
|
|
# --- FORMULAIRE POUR VALIDATION DES UE ET SEMESTRES
|
|
|
|
|
|
@bp.route("/formsemestre_validation_etud_form")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_validation_etud_form(
|
|
formsemestre_id,
|
|
etudid=None,
|
|
etud_index=None,
|
|
check=0,
|
|
desturl="",
|
|
sortcol=None,
|
|
):
|
|
"Formulaire choix jury pour un étudiant"
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
read_only = not formsemestre.can_edit_jury()
|
|
if formsemestre.formation.is_apc():
|
|
return redirect(
|
|
url_for(
|
|
"notes.formsemestre_validation_but",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
etudid=etudid,
|
|
)
|
|
)
|
|
return sco_formsemestre_validation.formsemestre_validation_etud_form(
|
|
formsemestre_id,
|
|
etudid=etudid,
|
|
etud_index=etud_index,
|
|
check=check,
|
|
read_only=read_only,
|
|
dest_url=desturl,
|
|
sortcol=sortcol,
|
|
)
|
|
|
|
|
|
@bp.route("/formsemestre_validation_etud")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_validation_etud(
|
|
formsemestre_id,
|
|
etudid=None,
|
|
codechoice=None,
|
|
desturl="",
|
|
sortcol=None,
|
|
):
|
|
"Enregistre choix jury pour un étudiant"
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
if not formsemestre.can_edit_jury():
|
|
raise ScoPermissionDenied(
|
|
dest_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
|
|
return sco_formsemestre_validation.formsemestre_validation_etud(
|
|
formsemestre_id,
|
|
etudid=etudid,
|
|
codechoice=codechoice,
|
|
desturl=desturl,
|
|
sortcol=sortcol,
|
|
)
|
|
|
|
|
|
@bp.route("/formsemestre_validation_etud_manu")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_validation_etud_manu(
|
|
formsemestre_id,
|
|
etudid=None,
|
|
code_etat="",
|
|
new_code_prev="",
|
|
devenir="",
|
|
assidu=False,
|
|
desturl="",
|
|
sortcol=None,
|
|
):
|
|
"Enregistre choix jury pour un étudiant"
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
if not formsemestre.can_edit_jury():
|
|
raise ScoPermissionDenied(
|
|
dest_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
|
|
return sco_formsemestre_validation.formsemestre_validation_etud_manu(
|
|
formsemestre_id,
|
|
etudid=etudid,
|
|
code_etat=code_etat,
|
|
new_code_prev=new_code_prev,
|
|
devenir=devenir,
|
|
assidu=assidu,
|
|
desturl=desturl,
|
|
sortcol=sortcol,
|
|
)
|
|
|
|
|
|
# --- Jurys BUT
|
|
@bp.route(
|
|
"/formsemestre_validation_but/<int:formsemestre_id>/<etudid>",
|
|
methods=["GET", "POST"],
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def formsemestre_validation_but(
|
|
formsemestre_id: int,
|
|
etudid: int,
|
|
):
|
|
"Form. saisie décision jury semestre BUT"
|
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
|
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
etud = Identite.get_etud(etudid)
|
|
nb_etuds = formsemestre.etuds.count()
|
|
# la route ne donne pas le type d'etudid pour pouvoir construire des URLs
|
|
# provisoires avec NEXT et PREV
|
|
try:
|
|
etudid = int(etudid)
|
|
except ValueError:
|
|
abort(404, "invalid etudid")
|
|
read_only = not formsemestre.can_edit_jury()
|
|
|
|
# --- Navigation
|
|
prev_lnk = (
|
|
f"""{scu.EMO_PREV_ARROW} <a href="{url_for(
|
|
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id, etudid="PREV"
|
|
)}" class="stdlink"">précédent</a>
|
|
"""
|
|
if nb_etuds > 1
|
|
else ""
|
|
)
|
|
next_lnk = (
|
|
f"""<a href="{url_for(
|
|
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id, etudid="NEXT"
|
|
)}" class="stdlink"">suivant</a> {scu.EMO_NEXT_ARROW}
|
|
"""
|
|
if nb_etuds > 1
|
|
else ""
|
|
)
|
|
navigation_div = f"""
|
|
<div class="but_navigation">
|
|
<div class="prev">
|
|
{prev_lnk}
|
|
</div>
|
|
<div class="back_list">
|
|
<a href="{
|
|
url_for(
|
|
"notes.formsemestre_recapcomplet",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
mode_jury=1,
|
|
selected_etudid=etud.id
|
|
)}" class="stdlink">retour à la liste</a>
|
|
</div>
|
|
<div class="next">
|
|
{next_lnk}
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
H = [
|
|
html_sco_header.sco_header(
|
|
page_title=f"Validation BUT S{formsemestre.semestre_id}",
|
|
formsemestre_id=formsemestre_id,
|
|
etudid=etudid,
|
|
cssstyles=[
|
|
"css/jury_but.css",
|
|
"css/cursus_but.css",
|
|
],
|
|
javascripts=("js/jury_but.js",),
|
|
),
|
|
"""<div class="jury_but">
|
|
""",
|
|
]
|
|
|
|
if formsemestre.etuds_inscriptions[etudid].etat != scu.INSCRIT:
|
|
return (
|
|
"\n".join(H)
|
|
+ f"""
|
|
<div>
|
|
<div class="bull_head">
|
|
<div>
|
|
<div class="titre_parcours">Jury BUT</div>
|
|
<div class="nom_etud">{etud.nomprenom}</div>
|
|
</div>
|
|
<div class="bull_photo"><a href="{
|
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
|
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
|
</div>
|
|
</div>
|
|
<div class="warning">Impossible de statuer sur cet étudiant:
|
|
il est démissionnaire ou défaillant (voir <a class="stdlink" href="{
|
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
|
}">sa fiche</a>)
|
|
</div>
|
|
</div>
|
|
{navigation_div}
|
|
</div>
|
|
"""
|
|
+ html_sco_header.sco_footer()
|
|
)
|
|
|
|
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
|
if len(deca.rcues_annee) == 0:
|
|
return jury_but_view.jury_but_semestriel(
|
|
formsemestre, etud, read_only, navigation_div=navigation_div
|
|
)
|
|
if request.method == "POST":
|
|
if not read_only:
|
|
deca.record_form(request.form)
|
|
ScolarNews.add(
|
|
typ=ScolarNews.NEWS_JURY,
|
|
obj=formsemestre.id,
|
|
text=f"""Saisie décision jury dans {formsemestre.html_link_status()}""",
|
|
url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
),
|
|
)
|
|
flash("codes enregistrés")
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_validation_but",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
etudid=etudid,
|
|
)
|
|
)
|
|
|
|
warning = ""
|
|
if len(deca.niveaux_competences) != len(deca.decisions_rcue_by_niveau):
|
|
if deca.a_cheval:
|
|
warning += """<div class="warning">Attention: regroupements RCUE
|
|
entre années (redoublement).</div>"""
|
|
else:
|
|
warning += f"""<div class="warning">Attention: {len(deca.niveaux_competences)}
|
|
niveaux mais {len(deca.decisions_rcue_by_niveau)} regroupements RCUE.</div>"""
|
|
if (deca.parcour is None) and len(formsemestre.parcours) > 0:
|
|
warning += (
|
|
"""<div class="warning">L'étudiant n'est pas inscrit à un parcours.</div>"""
|
|
)
|
|
if deca.formsemestre_impair and deca.inscription_etat_impair != scu.INSCRIT:
|
|
etat_ins = scu.ETATS_INSCRIPTION.get(deca.inscription_etat_impair, "inconnu?")
|
|
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_impair.semestre_id}</div>"""
|
|
if deca.formsemestre_pair and deca.inscription_etat_pair != scu.INSCRIT:
|
|
etat_ins = scu.ETATS_INSCRIPTION.get(deca.inscription_etat_pair, "inconnu?")
|
|
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_pair.semestre_id}</div>"""
|
|
if deca.has_notes_en_attente():
|
|
warning += f"""<div class="warning">{etud.nomprenom} a des notes en ATTente.
|
|
Vous devriez régler cela avant de statuer en jury !</div>"""
|
|
H.append(
|
|
f"""
|
|
<div>
|
|
<div class="bull_head">
|
|
<div>
|
|
<div class="titre_parcours">Jury BUT{deca.annee_but}
|
|
- Parcours {(deca.parcour.libelle if deca.parcour else False) or "non spécifié"}
|
|
- {deca.annee_scolaire_str()}</div>
|
|
<div class="nom_etud">{etud.nomprenom}</div>
|
|
</div>
|
|
<div class="bull_photo"><a href="{
|
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
|
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
|
</div>
|
|
</div>
|
|
{warning}
|
|
</div>
|
|
|
|
<form method="post" class="jury_but_box" id="jury_but">
|
|
"""
|
|
)
|
|
|
|
H.append(jury_but_view.show_etud(deca, read_only=read_only))
|
|
|
|
autorisations_idx = deca.get_autorisations_passage()
|
|
div_autorisations_passage = (
|
|
f"""
|
|
<div class="but_autorisations_passage">
|
|
<span>Autorisé à passer en :</span>
|
|
{ ", ".join( ["S" + str(i) for i in autorisations_idx ] )}
|
|
</div>
|
|
"""
|
|
if autorisations_idx
|
|
else """<div class="but_autorisations_passage but_explanation">
|
|
pas d'autorisations de passage enregistrées.
|
|
</div>
|
|
"""
|
|
)
|
|
H.append(div_autorisations_passage)
|
|
|
|
if read_only:
|
|
H.append(
|
|
f"""
|
|
<div class="but_explanation">
|
|
{"Vous n'avez pas la permission de modifier ces décisions."
|
|
if formsemestre.etat
|
|
else "Semestre verrouillé."}
|
|
Les champs entourés en vert sont enregistrés.
|
|
</div>"""
|
|
)
|
|
else:
|
|
erase_span = f"""<a href="{
|
|
url_for("notes.formsemestre_jury_but_erase",
|
|
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id,
|
|
etudid=deca.etud.id)}" class="stdlink"
|
|
title="efface décisions issues des jurys de cette année"
|
|
>effacer décisions de ce jury</a>
|
|
|
|
<a style="margin-left: 16px;" class="stdlink"
|
|
href="{
|
|
url_for("notes.erase_decisions_annee_formation",
|
|
scodoc_dept=g.scodoc_dept, formation_id=deca.formsemestre.formation.id,
|
|
etudid=deca.etud.id, annee=deca.annee_but)}"
|
|
title="efface toutes décisions concernant le BUT{deca.annee_but}
|
|
de cet étudiant (même extérieures ou issues d'un redoublement)"
|
|
>effacer toutes ses décisions de BUT{deca.annee_but}</a>
|
|
"""
|
|
H.append(
|
|
f"""<div class="but_settings">
|
|
<input type="checkbox" onchange="enable_manual_codes(this)">
|
|
<em>permettre la saisie manuelles des codes
|
|
{"d'année et " if deca.jury_annuel else ""}
|
|
de niveaux.
|
|
Dans ce cas, assurez-vous de la cohérence entre les codes d'UE/RCUE/Année !
|
|
</em>
|
|
</input>
|
|
</div>
|
|
|
|
<div class="but_buttons">
|
|
<span><input type="submit" value="Enregistrer ces décisions"></span>
|
|
<span>{erase_span}</span>
|
|
</div>
|
|
"""
|
|
)
|
|
H.append(navigation_div)
|
|
H.append("</form>")
|
|
if deca.a_cheval:
|
|
H.append(
|
|
f"""<div class="but_doc_codes but_warning_rcue_cap">
|
|
{scu.EMO_WARNING} Rappel: pour les redoublants, seules les UE <b>capitalisées</b> (note > 10)
|
|
lors d'une année précédente peuvent être prise en compte pour former
|
|
un RCUE (associé à un niveau de compétence du BUT).
|
|
</div>
|
|
"""
|
|
)
|
|
# Affichage cursus BUT
|
|
but_cursus = cursus_but.EtudCursusBUT(etud, deca.formsemestre.formation)
|
|
H += [
|
|
"""<div class="jury_but_box">
|
|
<div class="jury_but_box_title"><b>Niveaux de compétences enregistrés :</b></div>
|
|
""",
|
|
render_template(
|
|
"but/cursus_etud.j2",
|
|
cursus=but_cursus,
|
|
scu=scu,
|
|
),
|
|
"</div>",
|
|
]
|
|
H.append(
|
|
render_template(
|
|
"but/documentation_codes_jury.j2",
|
|
nom_univ=f"""Export {sco_preferences.get_preference("InstituteName")
|
|
or sco_preferences.get_preference("UnivName")
|
|
or "Apogée"}""",
|
|
codes=ScoDocSiteConfig.get_codes_apo_dict(),
|
|
)
|
|
)
|
|
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
|
|
|
|
|
@bp.route(
|
|
"/formsemestre_validation_auto_but/<int:formsemestre_id>", methods=["GET", "POST"]
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
|
"Saisie automatique des décisions de jury BUT"
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
if not formsemestre.can_edit_jury():
|
|
raise ScoPermissionDenied(
|
|
dest_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
form = jury_but_forms.FormSemestreValidationAutoBUTForm()
|
|
if request.method == "POST":
|
|
if not form.cancel.data:
|
|
nb_etud_modif = jury_but_validation_auto.formsemestre_validation_auto_but(
|
|
formsemestre
|
|
)
|
|
flash(f"Décisions enregistrées ({nb_etud_modif} étudiants modifiés)")
|
|
return redirect(
|
|
url_for(
|
|
"notes.formsemestre_recapcomplet",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
mode_jury=1,
|
|
)
|
|
)
|
|
return render_template(
|
|
"but/formsemestre_validation_auto_but.j2",
|
|
form=form,
|
|
sco=ScoData(formsemestre=formsemestre),
|
|
title="Calcul automatique jury BUT",
|
|
)
|
|
|
|
|
|
@bp.route(
|
|
"/formsemestre_validate_previous_ue/<int:formsemestre_id>/<int:etudid>",
|
|
methods=["GET", "POST"],
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def formsemestre_validate_previous_ue(formsemestre_id, etudid=None):
|
|
"Form. saisie UE validée hors ScoDoc"
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
if not formsemestre.can_edit_jury():
|
|
raise ScoPermissionDenied(
|
|
dest_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
etud: Identite = (
|
|
Identite.query.filter_by(id=etudid)
|
|
.join(FormSemestreInscription)
|
|
.filter_by(formsemestre_id=formsemestre_id)
|
|
.first_or_404()
|
|
)
|
|
|
|
return sco_formsemestre_validation.formsemestre_validate_previous_ue(
|
|
formsemestre, etud
|
|
)
|
|
|
|
|
|
sco_publish(
|
|
"/formsemestre_ext_create_form",
|
|
sco_formsemestre_exterieurs.formsemestre_ext_create_form,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
|
|
@bp.route("/formsemestre_ext_edit_ue_validations", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid=None):
|
|
"Form. edition UE semestre extérieur"
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
if not formsemestre.can_edit_jury():
|
|
raise ScoPermissionDenied(
|
|
dest_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
|
|
return sco_formsemestre_exterieurs.formsemestre_ext_edit_ue_validations(
|
|
formsemestre_id, etudid
|
|
)
|
|
|
|
|
|
@bp.route("/formsemestre_validation_auto")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_validation_auto(formsemestre_id):
|
|
"Formulaire saisie automatisee des decisions d'un semestre"
|
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
|
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
if not formsemestre.can_edit_jury():
|
|
raise ScoPermissionDenied(
|
|
dest_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
|
|
if formsemestre.formation.is_apc():
|
|
return redirect(
|
|
url_for(
|
|
"notes.formsemestre_validation_auto_but",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
)
|
|
)
|
|
return sco_formsemestre_validation.formsemestre_validation_auto(formsemestre_id)
|
|
|
|
|
|
@bp.route("/do_formsemestre_validation_auto")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def do_formsemestre_validation_auto(formsemestre_id):
|
|
"Formulaire saisie automatisee des decisions d'un semestre"
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
if not formsemestre.can_edit_jury():
|
|
raise ScoPermissionDenied(
|
|
dest_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
|
|
return sco_formsemestre_validation.do_formsemestre_validation_auto(formsemestre_id)
|
|
|
|
|
|
@bp.route("/formsemestre_validation_suppress_etud", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_validation_suppress_etud(
|
|
formsemestre_id, etudid, dialog_confirmed=False
|
|
):
|
|
"""Suppression des décisions de jury pour un étudiant."""
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
if not formsemestre.can_edit_jury():
|
|
raise ScoPermissionDenied(
|
|
dest_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
etud = Identite.get_etud(etudid)
|
|
if formsemestre.formation.is_apc():
|
|
next_url = url_for(
|
|
"scolar.ficheEtud",
|
|
scodoc_dept=g.scodoc_dept,
|
|
etudid=etudid,
|
|
)
|
|
else:
|
|
next_url = url_for(
|
|
"notes.formsemestre_validation_etud_form",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
etudid=etudid,
|
|
)
|
|
if not dialog_confirmed:
|
|
d = sco_bulletins_json.dict_decision_jury(
|
|
etud, formsemestre, with_decisions=True
|
|
)
|
|
|
|
descr_ues = [f"{u['acronyme']}: {u['code']}" for u in d.get("decision_ue", [])]
|
|
dec_annee = d.get("decision_annee")
|
|
if dec_annee:
|
|
descr_annee = dec_annee.get("code", "-")
|
|
else:
|
|
descr_annee = "-"
|
|
|
|
existing = f"""
|
|
<ul>
|
|
<li>Semestre : {d.get("decision", {"code":"-"})['code'] or "-"}</li>
|
|
<li>Année BUT: {descr_annee}</li>
|
|
<li>UEs : {", ".join(descr_ues)}</li>
|
|
<li>RCUEs: {len(d.get("decision_rcue", []))} décisions</li>
|
|
<li>Autorisations: {descr_autorisations(ScolarAutorisationInscription.query.filter_by(origin_formsemestre_id=formsemestre_id,
|
|
etudid=etudid))}
|
|
</ul>
|
|
"""
|
|
return scu.confirm_dialog(
|
|
f"""<h2>Confirmer la suppression des décisions du semestre
|
|
{formsemestre.titre_mois()} pour {etud.nomprenom}
|
|
</h2>
|
|
<p>Cette opération est irréversible.</p>
|
|
<div>
|
|
{existing}
|
|
</div>
|
|
""",
|
|
OK="Supprimer",
|
|
dest_url="",
|
|
cancel_url=next_url,
|
|
parameters={"etudid": etudid, "formsemestre_id": formsemestre_id},
|
|
)
|
|
|
|
sco_formsemestre_validation.formsemestre_validation_suppress_etud(
|
|
formsemestre_id, etudid
|
|
)
|
|
flash("Décisions supprimées")
|
|
return flask.redirect(next_url)
|
|
|
|
|
|
# ------------- PV de JURY et archives
|
|
sco_publish(
|
|
"/formsemestre_pvjury", sco_pv_forms.formsemestre_pvjury, Permission.ScoView
|
|
)
|
|
|
|
sco_publish("/pvjury_page_but", jury_but_pv.pvjury_page_but, Permission.ScoView)
|
|
|
|
|
|
@bp.route("/formsemestre_saisie_jury")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_saisie_jury(formsemestre_id: int, selected_etudid: int = None):
|
|
"""Page de saisie: liste des étudiants et lien vers page jury
|
|
sinon, redirect vers page recap en mode jury
|
|
"""
|
|
return redirect(
|
|
url_for(
|
|
"notes.formsemestre_recapcomplet",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
mode_jury=1,
|
|
)
|
|
)
|
|
|
|
|
|
@bp.route(
|
|
"/formsemestre_jury_but_erase/<int:formsemestre_id>",
|
|
methods=["GET", "POST"],
|
|
defaults={"etudid": None},
|
|
)
|
|
@bp.route(
|
|
"/formsemestre_jury_but_erase/<int:formsemestre_id>/<int:etudid>",
|
|
methods=["GET", "POST"],
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def formsemestre_jury_but_erase(formsemestre_id: int, etudid: int = None):
|
|
"""Supprime la décision de jury BUT pour cette année.
|
|
Si l'étudiant n'est pas spécifié, efface les décisions de tous les inscrits.
|
|
Si only_one_sem, n'efface que pour le formsemestre indiqué, pas les deux de l'année.
|
|
"""
|
|
only_one_sem = int(request.args.get("only_one_sem") or False)
|
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
|
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
if not formsemestre.can_edit_jury():
|
|
raise ScoPermissionDenied(
|
|
dest_url=url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
if not formsemestre.formation.is_apc():
|
|
raise ScoValueError("semestre non BUT")
|
|
if etudid is None:
|
|
etud = None
|
|
etuds = formsemestre.get_inscrits(include_demdef=True)
|
|
dest_url = url_for(
|
|
"notes.formsemestre_recapcomplet",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
mode_jury=1,
|
|
)
|
|
else:
|
|
etud = Identite.get_etud(etudid)
|
|
etuds = [etud]
|
|
dest_url = url_for(
|
|
"notes.formsemestre_validation_but",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
etudid=etudid,
|
|
)
|
|
if request.method == "POST":
|
|
with sco_cache.DeferredSemCacheManager():
|
|
for etud in etuds:
|
|
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
|
deca.erase(only_one_sem=only_one_sem)
|
|
log(f"formsemestre_jury_but_erase({formsemestre_id}, {etudid})")
|
|
flash(
|
|
(
|
|
"décisions de jury du semestre effacées"
|
|
if only_one_sem
|
|
else "décisions de jury des semestres de l'année BUT effacées"
|
|
)
|
|
+ f" pour {len(etuds)} étudiant{'s' if len(etuds) > 1 else ''}"
|
|
)
|
|
return redirect(dest_url)
|
|
|
|
return render_template(
|
|
"confirm_dialog.j2",
|
|
title=f"""Effacer les validations de jury {
|
|
("de " + etud.nomprenom)
|
|
if etud
|
|
else ("des " + str(len(etuds)) + " étudiants inscrits dans ce semestre")
|
|
} ?""",
|
|
explanation=(
|
|
f"""Les validations d'UE et autorisations de passage
|
|
du semestre S{formsemestre.semestre_id} seront effacées."""
|
|
if only_one_sem
|
|
else """Les validations de toutes les UE, RCUE (compétences) et année
|
|
issues de cette année scolaire seront effacées.
|
|
"""
|
|
)
|
|
+ """
|
|
<p>Les décisions des années scolaires précédentes ne seront pas modifiées.</p>
|
|
<p>Efface aussi toutes les validations concernant l'année BUT de ce semestre,
|
|
même si elles ont été acquises ailleurs.
|
|
</p>
|
|
<div class="warning">Cette opération est irréversible !
|
|
A n'utiliser que dans des cas exceptionnels, vérifiez bien tous les étudiants ensuite.
|
|
</div>
|
|
""",
|
|
cancel_url=dest_url,
|
|
)
|
|
|
|
|
|
@bp.route(
|
|
"/erase_decisions_annee_formation/<int:etudid>/<int:formation_id>/<int:annee>",
|
|
methods=["GET", "POST"],
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.ScoEtudInscrit)
|
|
def erase_decisions_annee_formation(etudid: int, formation_id: int, annee: int):
|
|
"""Efface toute les décisions d'une année pour cet étudiant"""
|
|
etud: Identite = Identite.query.get_or_404(etudid)
|
|
formation: Formation = Formation.query.filter_by(
|
|
id=formation_id, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
if request.method == "POST":
|
|
jury.erase_decisions_annee_formation(etud, formation, annee, delete=True)
|
|
flash("Décisions de jury effacées")
|
|
return redirect(
|
|
url_for(
|
|
"scolar.ficheEtud",
|
|
scodoc_dept=g.scodoc_dept,
|
|
etudid=etud.id,
|
|
)
|
|
)
|
|
validations = jury.erase_decisions_annee_formation(etud, formation, annee)
|
|
return render_template(
|
|
"jury/erase_decisions_annee_formation.j2",
|
|
annee=annee,
|
|
cancel_url=url_for(
|
|
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
|
),
|
|
etud=etud,
|
|
formation=formation,
|
|
validations=validations,
|
|
sco=ScoData(),
|
|
title=f"Effacer décisions de jury {etud.nom} - année {annee}",
|
|
)
|
|
|
|
|
|
@bp.route(
|
|
"/jury_delete_manual/<int:etudid>",
|
|
methods=["GET", "POST"],
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.ScoEtudInscrit)
|
|
def jury_delete_manual(etudid: int):
|
|
"""Efface toute les décisions d'une année pour cet étudiant"""
|
|
etud: Identite = Identite.query.get_or_404(etudid)
|
|
return jury_edit_manual.jury_delete_manual(etud)
|
|
|
|
|
|
sco_publish(
|
|
"/formsemestre_lettres_individuelles",
|
|
sco_pv_forms.formsemestre_lettres_individuelles,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_pvjury_pdf",
|
|
sco_pv_forms.formsemestre_pvjury_pdf,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/feuille_preparation_jury",
|
|
sco_prepajury.feuille_preparation_jury,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_archive",
|
|
sco_archives.formsemestre_archive,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_delete_archive",
|
|
sco_archives.formsemestre_delete_archive,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_list_archives",
|
|
sco_archives.formsemestre_list_archives,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_get_archived_file",
|
|
sco_archives.formsemestre_get_archived_file,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish("/view_apo_csv", sco_etape_apogee_view.view_apo_csv, Permission.ScoEditApo)
|
|
sco_publish(
|
|
"/view_apo_csv_store",
|
|
sco_etape_apogee_view.view_apo_csv_store,
|
|
Permission.ScoEditApo,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/view_apo_csv_download_and_store",
|
|
sco_etape_apogee_view.view_apo_csv_download_and_store,
|
|
Permission.ScoEditApo,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/view_apo_csv_delete",
|
|
sco_etape_apogee_view.view_apo_csv_delete,
|
|
Permission.ScoEditApo,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/view_scodoc_etuds", sco_etape_apogee_view.view_scodoc_etuds, Permission.ScoEditApo
|
|
)
|
|
sco_publish(
|
|
"/view_apo_etuds", sco_etape_apogee_view.view_apo_etuds, Permission.ScoEditApo
|
|
)
|
|
sco_publish(
|
|
"/apo_semset_maq_status",
|
|
sco_etape_apogee_view.apo_semset_maq_status,
|
|
Permission.ScoEditApo,
|
|
)
|
|
sco_publish(
|
|
"/apo_csv_export_results",
|
|
sco_etape_apogee_view.apo_csv_export_results,
|
|
Permission.ScoEditApo,
|
|
)
|
|
|
|
|
|
@bp.route("/formsemestre_set_apo_etapes", methods=["POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoEditApo)
|
|
def formsemestre_set_apo_etapes():
|
|
"""Change les codes étapes du semestre indiqué.
|
|
Args: oid=formsemestre_id, value=chaine "V1RT, V1RT2", codes séparés par des virgules
|
|
"""
|
|
formsemestre_id = int(request.form.get("oid"))
|
|
etapes_apo_str = request.form.get("value")
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
current_etapes = {e.etape_apo for e in formsemestre.etapes}
|
|
new_etapes = {s.strip() for s in etapes_apo_str.split(",")}
|
|
|
|
if new_etapes != current_etapes:
|
|
formsemestre.etapes = []
|
|
for etape_apo in new_etapes:
|
|
etape = models.FormSemestreEtape(
|
|
formsemestre_id=formsemestre_id, etape_apo=etape_apo
|
|
)
|
|
formsemestre.etapes.append(etape)
|
|
db.session.add(formsemestre)
|
|
db.session.commit()
|
|
ScolarNews.add(
|
|
typ=ScolarNews.NEWS_APO,
|
|
text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})",
|
|
)
|
|
return ("", 204)
|
|
|
|
|
|
@bp.route("/formsemestre_set_elt_annee_apo", methods=["POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoEditApo)
|
|
def formsemestre_set_elt_annee_apo():
|
|
"""Change les codes étapes du semestre indiqué.
|
|
Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
|
"""
|
|
oid = int(request.form.get("oid"))
|
|
value = (request.form.get("value") or "").strip()
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid)
|
|
if value != formsemestre.elt_annee_apo:
|
|
formsemestre.elt_annee_apo = value
|
|
db.session.add(formsemestre)
|
|
db.session.commit()
|
|
ScolarNews.add(
|
|
typ=ScolarNews.NEWS_APO,
|
|
text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})",
|
|
)
|
|
return ("", 204)
|
|
|
|
|
|
@bp.route("/formsemestre_set_elt_sem_apo", methods=["POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoEditApo)
|
|
def formsemestre_set_elt_sem_apo():
|
|
"""Change les codes étapes du semestre indiqué.
|
|
Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
|
"""
|
|
oid = int(request.form.get("oid"))
|
|
value = (request.form.get("value") or "").strip()
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid)
|
|
if value != formsemestre.elt_sem_apo:
|
|
formsemestre.elt_sem_apo = value
|
|
db.session.add(formsemestre)
|
|
db.session.commit()
|
|
ScolarNews.add(
|
|
typ=ScolarNews.NEWS_APO,
|
|
text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})",
|
|
)
|
|
return ("", 204)
|
|
|
|
|
|
@bp.route("/ue_set_apo", methods=["POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoEditApo)
|
|
def ue_set_apo():
|
|
"""Change le code APO de l'UE
|
|
Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE)
|
|
"""
|
|
ue_id = int(request.form.get("oid"))
|
|
code_apo = (request.form.get("value") or "").strip()
|
|
ue = UniteEns.query.get_or_404(ue_id)
|
|
if code_apo != ue.code_apogee:
|
|
ue.code_apogee = code_apo
|
|
db.session.add(ue)
|
|
db.session.commit()
|
|
ScolarNews.add(
|
|
typ=ScolarNews.NEWS_FORM,
|
|
text=f"Modification code Apogée d'UE dans la formation {ue.formation.titre} ({ue.formation.acronyme})",
|
|
)
|
|
return ("", 204)
|
|
|
|
|
|
@bp.route("/module_set_apo", methods=["POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoEditApo)
|
|
def module_set_apo():
|
|
"""Change le code APO du module
|
|
Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE)
|
|
"""
|
|
oid = int(request.form.get("oid"))
|
|
code_apo = (request.form.get("value") or "").strip()
|
|
mod = Module.query.get_or_404(oid)
|
|
if code_apo != mod.code_apogee:
|
|
mod.code_apogee = code_apo
|
|
db.session.add(mod)
|
|
db.session.commit()
|
|
ScolarNews.add(
|
|
typ=ScolarNews.NEWS_FORM,
|
|
text=f"""Modification code Apogée d'UE dans la formation {
|
|
mod.formation.titre} ({mod.formation.acronyme})""",
|
|
)
|
|
return ("", 204)
|
|
|
|
|
|
# sco_semset
|
|
sco_publish("/semset_page", sco_semset.semset_page, Permission.ScoEditApo)
|
|
sco_publish(
|
|
"/do_semset_create",
|
|
sco_semset.do_semset_create,
|
|
Permission.ScoEditApo,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/do_semset_delete",
|
|
sco_semset.do_semset_delete,
|
|
Permission.ScoEditApo,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/edit_semset_set_title",
|
|
sco_semset.edit_semset_set_title,
|
|
Permission.ScoEditApo,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/do_semset_add_sem",
|
|
sco_semset.do_semset_add_sem,
|
|
Permission.ScoEditApo,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/do_semset_remove_sem",
|
|
sco_semset.do_semset_remove_sem,
|
|
Permission.ScoEditApo,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
# sco_export_result
|
|
sco_publish(
|
|
"/scodoc_table_results",
|
|
sco_export_results.scodoc_table_results,
|
|
Permission.ScoEditApo,
|
|
)
|
|
|
|
sco_publish(
|
|
"/apo_compare_csv_form",
|
|
sco_apogee_compare.apo_compare_csv_form,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/apo_compare_csv",
|
|
sco_apogee_compare.apo_compare_csv,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
# ------------- INSCRIPTIONS: PASSAGE D'UN SEMESTRE A UN AUTRE
|
|
sco_publish(
|
|
"/formsemestre_inscr_passage",
|
|
sco_inscr_passage.formsemestre_inscr_passage,
|
|
Permission.ScoEtudInscrit,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_synchro_etuds",
|
|
sco_synchro_etuds.formsemestre_synchro_etuds,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
# ------------- RAPPORTS STATISTIQUES
|
|
sco_publish(
|
|
"/formsemestre_report_counts",
|
|
sco_report.formsemestre_report_counts,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_suivi_cohorte",
|
|
sco_report.formsemestre_suivi_cohorte,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_suivi_cursus",
|
|
sco_report.formsemestre_suivi_cursus,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_etuds_lycees",
|
|
sco_lycee.formsemestre_etuds_lycees,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/scodoc_table_etuds_lycees",
|
|
sco_lycee.scodoc_table_etuds_lycees,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_graph_cursus",
|
|
sco_report.formsemestre_graph_cursus,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_but_indicateurs",
|
|
sco_report_but.formsemestre_but_indicateurs,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_poursuite_report",
|
|
sco_poursuite_dut.formsemestre_poursuite_report,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/pe_view_sem_recap",
|
|
pe_view.pe_view_sem_recap,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/report_debouche_date", sco_debouche.report_debouche_date, Permission.ScoView
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_estim_cost",
|
|
sco_cost_formation.formsemestre_estim_cost,
|
|
Permission.ScoView,
|
|
)
|
|
|
|
# --------------------------------------------------------------------
|
|
# DEBUG
|
|
|
|
|
|
@bp.route("/check_sem_integrity")
|
|
@scodoc
|
|
@permission_required(Permission.ScoImplement)
|
|
@scodoc7func
|
|
def check_sem_integrity(formsemestre_id, fix=False):
|
|
"""Debug.
|
|
Check that ue and module formations are consistents
|
|
"""
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
|
|
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
|
bad_ue = []
|
|
bad_sem = []
|
|
formations_set = set() # les formations mentionnées dans les UE et modules
|
|
for modimpl in modimpls:
|
|
mod = sco_edit_module.module_list({"module_id": modimpl["module_id"]})[0]
|
|
formations_set.add(mod["formation_id"])
|
|
ue = sco_edit_ue.ue_list({"ue_id": mod["ue_id"]})[0]
|
|
formations_set.add(ue["formation_id"])
|
|
if ue["formation_id"] != mod["formation_id"]:
|
|
modimpl["mod"] = mod
|
|
modimpl["ue"] = ue
|
|
bad_ue.append(modimpl)
|
|
if sem["formation_id"] != mod["formation_id"]:
|
|
bad_sem.append(modimpl)
|
|
modimpl["mod"] = mod
|
|
|
|
H = [
|
|
html_sco_header.sco_header(),
|
|
"<p>formation_id=%s" % sem["formation_id"],
|
|
]
|
|
if bad_ue:
|
|
H += [
|
|
"<h2>Modules d'une autre formation que leur UE:</h2>",
|
|
"<br>".join([str(x) for x in bad_ue]),
|
|
]
|
|
if bad_sem:
|
|
H += [
|
|
"<h2>Module du semestre dans une autre formation:</h2>",
|
|
"<br>".join([str(x) for x in bad_sem]),
|
|
]
|
|
if not bad_ue and not bad_sem:
|
|
H.append("<p>Aucun problème à signaler !</p>")
|
|
else:
|
|
log("check_sem_integrity: problem detected: formations_set=%s" % formations_set)
|
|
if sem["formation_id"] in formations_set:
|
|
formations_set.remove(sem["formation_id"])
|
|
if len(formations_set) == 1:
|
|
if fix:
|
|
log("check_sem_integrity: trying to fix %s" % formsemestre_id)
|
|
formation_id = formations_set.pop()
|
|
if sem["formation_id"] != formation_id:
|
|
sem["formation_id"] = formation_id
|
|
sco_formsemestre.do_formsemestre_edit(sem)
|
|
H.append("""<p class="alert">Problème réparé: vérifiez</p>""")
|
|
else:
|
|
H.append(
|
|
"""
|
|
<p class="alert">Problème détecté réparable:
|
|
<a href="check_sem_integrity?formsemestre_id=%s&fix=1">réparer maintenant</a></p>
|
|
"""
|
|
% (formsemestre_id,)
|
|
)
|
|
else:
|
|
H.append("""<p class="alert">Problème détecté !</p>""")
|
|
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
|
|
|
|
|
@bp.route("/check_form_integrity")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def check_form_integrity(formation_id, fix=False):
|
|
"debug"
|
|
log("check_form_integrity: formation_id=%s fix=%s" % (formation_id, fix))
|
|
ues = sco_edit_ue.ue_list(args={"formation_id": formation_id})
|
|
bad = []
|
|
for ue in ues:
|
|
mats = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
|
|
for mat in mats:
|
|
mods = sco_edit_module.module_list({"matiere_id": mat["matiere_id"]})
|
|
for mod in mods:
|
|
if mod["ue_id"] != ue["ue_id"]:
|
|
if fix:
|
|
# fix mod.ue_id
|
|
log(
|
|
"fix: mod.ue_id = %s (was %s)" % (ue["ue_id"], mod["ue_id"])
|
|
)
|
|
mod["ue_id"] = ue["ue_id"]
|
|
sco_edit_module.do_module_edit(mod)
|
|
bad.append(mod)
|
|
if mod["formation_id"] != formation_id:
|
|
bad.append(mod)
|
|
if bad:
|
|
txth = "<br>".join([str(x) for x in bad])
|
|
txt = "\n".join([str(x) for x in bad])
|
|
log("check_form_integrity: formation_id=%s\ninconsistencies:" % formation_id)
|
|
log(txt)
|
|
# Notify by e-mail
|
|
send_scodoc_alarm("Notes: formation incoherente !", txt)
|
|
else:
|
|
txth = "OK"
|
|
log("ok")
|
|
return html_sco_header.sco_header() + txth + html_sco_header.sco_footer()
|
|
|
|
|
|
@bp.route("/check_formsemestre_integrity")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def check_formsemestre_integrity(formsemestre_id):
|
|
"debug"
|
|
log("check_formsemestre_integrity: formsemestre_id=%s" % (formsemestre_id))
|
|
# verifie que tous les moduleimpl d'un formsemestre
|
|
# se réfèrent à un module dont l'UE appartient a la même formation
|
|
# Ancien bug: les ue_id étaient mal copiés lors des création de versions
|
|
# de formations
|
|
diag = []
|
|
|
|
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
|
for mod in Mlist:
|
|
if mod["module"]["ue_id"] != mod["matiere"]["ue_id"]:
|
|
diag.append(
|
|
"moduleimpl %s: module.ue_id=%s != matiere.ue_id=%s"
|
|
% (
|
|
mod["moduleimpl_id"],
|
|
mod["module"]["ue_id"],
|
|
mod["matiere"]["ue_id"],
|
|
)
|
|
)
|
|
if mod["ue"]["formation_id"] != mod["module"]["formation_id"]:
|
|
diag.append(
|
|
"moduleimpl %s: ue.formation_id=%s != mod.formation_id=%s"
|
|
% (
|
|
mod["moduleimpl_id"],
|
|
mod["ue"]["formation_id"],
|
|
mod["module"]["formation_id"],
|
|
)
|
|
)
|
|
if diag:
|
|
send_scodoc_alarm(
|
|
"Notes: formation incoherente dans semestre %s !" % formsemestre_id,
|
|
"\n".join(diag),
|
|
)
|
|
log("check_formsemestre_integrity: formsemestre_id=%s" % formsemestre_id)
|
|
log("inconsistencies:\n" + "\n".join(diag))
|
|
else:
|
|
diag = ["OK"]
|
|
log("ok")
|
|
return (
|
|
html_sco_header.sco_header() + "<br>".join(diag) + html_sco_header.sco_footer()
|
|
)
|