forked from ScoDoc/ScoDoc
3514 lines
113 KiB
Python
3514 lines
113 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# ScoDoc
|
|
#
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
|
#
|
|
##############################################################################
|
|
|
|
"""
|
|
Module notes: issu de ScoDoc7 / ZNotes.py
|
|
|
|
Emmanuel Viennet, 2021
|
|
"""
|
|
import datetime
|
|
import html
|
|
from operator import itemgetter
|
|
import time
|
|
|
|
import flask
|
|
from flask import 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 import bulletin_but_court # ne pas enlever: ajoute des routes !
|
|
|
|
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 (
|
|
ApcNiveau,
|
|
Assiduite,
|
|
BulAppreciations,
|
|
DispenseUE,
|
|
Evaluation,
|
|
Formation,
|
|
FormSemestre,
|
|
FormSemestreInscription,
|
|
FormSemestreUEComputationExpr,
|
|
Identite,
|
|
Module,
|
|
ModuleImpl,
|
|
ScolarAutorisationInscription,
|
|
ScolarNews,
|
|
Scolog,
|
|
ScoDocSiteConfig,
|
|
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.pe import pe_view # ne pas enlever, ajoute des vues
|
|
from app.scodoc import sco_bulletins_json, sco_utils as scu, sco_prepajury_lille
|
|
from app import log, send_scodoc_alarm
|
|
|
|
from app.scodoc.sco_exceptions import (
|
|
AccessDenied,
|
|
ScoValueError,
|
|
ScoInvalidIdType,
|
|
)
|
|
from app.scodoc import (
|
|
html_sco_header,
|
|
sco_apogee_compare,
|
|
sco_archives_formsemestre,
|
|
sco_assiduites,
|
|
sco_bulletins,
|
|
sco_bulletins_pdf,
|
|
sco_cache,
|
|
sco_cost_formation,
|
|
sco_debouche,
|
|
sco_edit_apc,
|
|
sco_edit_formation,
|
|
sco_edit_matiere,
|
|
sco_edit_module,
|
|
sco_edit_ue,
|
|
sco_etape_apogee_view,
|
|
sco_etud,
|
|
sco_evaluations,
|
|
sco_evaluation_check_abs,
|
|
sco_evaluation_db,
|
|
sco_evaluation_edit,
|
|
sco_evaluation_recap,
|
|
sco_export_results,
|
|
sco_formations,
|
|
sco_formation_recap,
|
|
sco_formation_versions,
|
|
sco_formsemestre,
|
|
sco_formsemestre_custommenu,
|
|
sco_formsemestre_edit,
|
|
sco_formsemestre_exterieurs,
|
|
sco_formsemestre_inscriptions,
|
|
sco_formsemestre_status,
|
|
sco_formsemestre_validation,
|
|
sco_groups_view,
|
|
sco_inscr_passage,
|
|
sco_liste_notes,
|
|
sco_lycee,
|
|
sco_moduleimpl,
|
|
sco_moduleimpl_inscriptions,
|
|
sco_moduleimpl_status,
|
|
sco_placement,
|
|
sco_poursuite_dut,
|
|
sco_preferences,
|
|
sco_prepajury,
|
|
sco_pv_forms,
|
|
sco_recapcomplet,
|
|
sco_report,
|
|
sco_report_but,
|
|
sco_saisie_notes,
|
|
sco_semset,
|
|
sco_synchro_etuds,
|
|
sco_tag_module,
|
|
sco_ue_external,
|
|
sco_undo_notes,
|
|
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.EditFormSemestre,
|
|
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.EditFormSemestre,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_associate_new_version",
|
|
sco_formation_versions.formsemestre_associate_new_version,
|
|
Permission.EditFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_delete",
|
|
sco_formsemestre_edit.formsemestre_delete,
|
|
Permission.EditFormSemestre,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_delete2",
|
|
sco_formsemestre_edit.formsemestre_delete2,
|
|
Permission.EditFormSemestre,
|
|
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.Observateur,
|
|
)
|
|
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.EditFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/formation_delete",
|
|
sco_edit_formation.formation_delete,
|
|
Permission.EditFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/formation_edit",
|
|
sco_edit_formation.formation_edit,
|
|
Permission.EditFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
|
|
@bp.route("/formsemestre_bulletinetud")
|
|
@scodoc
|
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_bulletinetud(
|
|
etudid=None,
|
|
formsemestre_id=None,
|
|
fmt=None,
|
|
version="long",
|
|
xml_with_decisions=False,
|
|
force_publishing=False,
|
|
prefer_mail_perso=False,
|
|
code_nip=None,
|
|
code_ine=None,
|
|
):
|
|
fmt = fmt or "html"
|
|
if version not in scu.BULLETINS_VERSIONS_BUT:
|
|
raise ScoValueError(
|
|
"formsemestre_bulletinetud: version de bulletin demandée invalide"
|
|
)
|
|
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 version == "butcourt":
|
|
return redirect(
|
|
url_for(
|
|
"notes.bulletin_but_pdf" if fmt == "pdf" else "notes.bulletin_but_html",
|
|
scodoc_dept=g.scodoc_dept,
|
|
etudid=etud.id,
|
|
formsemestre_id=formsemestre_id,
|
|
)
|
|
)
|
|
if fmt == "json":
|
|
return sco_bulletins.get_formsemestre_bulletin_etud_json(
|
|
formsemestre, etud, version=version, force_publishing=force_publishing
|
|
)
|
|
if formsemestre.formation.is_apc() and fmt == "html":
|
|
return render_template(
|
|
"but/bulletin.j2",
|
|
appreciations=BulAppreciations.get_appreciations_list(
|
|
formsemestre.id, etud.id
|
|
),
|
|
bul_url=url_for(
|
|
"notes.formsemestre_bulletinetud",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
etudid=etud.id,
|
|
fmt="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.EtudInscrit)),
|
|
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 fmt == "oldjson":
|
|
fmt = "json"
|
|
|
|
response = sco_bulletins.formsemestre_bulletinetud(
|
|
etud,
|
|
formsemestre_id=formsemestre_id,
|
|
fmt=fmt,
|
|
version=version,
|
|
xml_with_decisions=xml_with_decisions,
|
|
force_publishing=force_publishing,
|
|
prefer_mail_perso=prefer_mail_perso,
|
|
)
|
|
|
|
if fmt == "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,
|
|
)
|
|
|
|
|
|
@bp.route("/moduleimpl_evaluation_renumber", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
|
@scodoc7func
|
|
def moduleimpl_evaluation_renumber(moduleimpl_id):
|
|
"Renumérote les évaluations, triant par date"
|
|
modimpl: ModuleImpl = (
|
|
ModuleImpl.query.filter_by(id=moduleimpl_id)
|
|
.join(FormSemestre)
|
|
.filter_by(dept_id=g.scodoc_dept_id)
|
|
.first_or_404()
|
|
)
|
|
if not modimpl.can_edit_evaluation(current_user):
|
|
raise ScoPermissionDenied(
|
|
dest_url=url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=modimpl.id,
|
|
)
|
|
)
|
|
Evaluation.moduleimpl_evaluation_renumber(modimpl)
|
|
# redirect to moduleimpl page:
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=moduleimpl_id,
|
|
)
|
|
)
|
|
|
|
|
|
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.EditFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/ue_delete",
|
|
sco_edit_ue.ue_delete,
|
|
Permission.EditFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
|
|
@bp.route("/ue_edit/<int:ue_id>", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EditFormation)
|
|
def ue_edit(ue_id: int):
|
|
"Edition de l'UE"
|
|
return sco_edit_ue.ue_edit(ue_id)
|
|
|
|
|
|
@bp.route("/set_ue_niveau_competence", methods=["POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EditFormation)
|
|
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_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: int):
|
|
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.EditFormation)
|
|
@scodoc7func
|
|
def ue_set_internal(ue_id):
|
|
""""""
|
|
ue = db.session.get(UniteEns, 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) or ue_id == "") else int(ue_id),
|
|
hide_ue_id=(
|
|
None if ((hide_ue_id is None) or hide_ue_id == "") else int(hide_ue_id)
|
|
),
|
|
)
|
|
|
|
|
|
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, fmt="html"):
|
|
return sco_formation_recap.formation_table_recap(formation_id, fmt=fmt)
|
|
|
|
|
|
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.EditFormation,
|
|
)
|
|
sco_publish(
|
|
"/matiere_create",
|
|
sco_edit_matiere.matiere_create,
|
|
Permission.EditFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/matiere_delete",
|
|
sco_edit_matiere.matiere_delete,
|
|
Permission.EditFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/matiere_edit",
|
|
sco_edit_matiere.matiere_edit,
|
|
Permission.EditFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/module_create",
|
|
sco_edit_module.module_create,
|
|
Permission.EditFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/module_delete",
|
|
sco_edit_module.module_delete,
|
|
Permission.EditFormation,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/module_edit",
|
|
sco_edit_module.module_edit,
|
|
Permission.EditFormation,
|
|
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("/formation_tag_modules_by_type/<int:formation_id>/<int:semestre_idx>")
|
|
@scodoc
|
|
@permission_required(Permission.EditFormationTags)
|
|
def formation_tag_modules_by_type(formation_id: int, semestre_idx: int):
|
|
"""Taggue tous les modules de la formation en fonction de leur type : 'res', 'sae', 'malus'
|
|
Ne taggue pas les modules standards.
|
|
"""
|
|
formation = Formation.query.filter_by(
|
|
id=formation_id, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
sco_tag_module.formation_tag_modules_by_type(formation)
|
|
flash("Formation tagguée")
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.ue_table",
|
|
scodoc_dept=g.scodoc_dept,
|
|
semestre_idx=semestre_idx,
|
|
formation_id=formation.id,
|
|
show_tags=1,
|
|
)
|
|
)
|
|
|
|
|
|
@bp.route("/module_tag_set", methods=["POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EditFormationTags)
|
|
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.EditFormation)
|
|
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", alias=True)
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def index_html():
|
|
"Page accueil formations"
|
|
fmt = request.args.get("fmt", "html")
|
|
detail = scu.to_bool(request.args.get("detail", False))
|
|
|
|
editable = current_user.has_permission(Permission.EditFormation)
|
|
table = sco_formations.formation_list_table(detail=detail)
|
|
|
|
if fmt != "html":
|
|
return table.make_page(fmt=fmt, filename=f"Formations-{g.scodoc_dept}")
|
|
|
|
H = [
|
|
html_sco_header.sco_header(page_title="Formations (programmes)"),
|
|
f"""<h2>Formations (programmes pédagogiques)</h2>
|
|
<form>
|
|
<input type="checkbox" id="detailCheckbox" name="detail"
|
|
onchange="this.form.submit();"
|
|
{'checked' if detail else ''}>
|
|
<label for="detailCheckbox">Informations détaillées</label>
|
|
</form>
|
|
""",
|
|
table.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, fmt=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,
|
|
fmt=fmt,
|
|
export_codes_apo=export_codes_apo,
|
|
)
|
|
|
|
|
|
@bp.route("/formation_import_xml_form", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EditFormation)
|
|
@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(url_for("notes.index_html", scodoc_dept=g.scodoc_dept))
|
|
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_table", 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("/module_move", sco_edit_formation.module_move, Permission.EditFormation)
|
|
sco_publish("/ue_move", sco_edit_formation.ue_move, Permission.EditFormation)
|
|
|
|
|
|
@bp.route("/ue_clone", methods=["POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EditFormation)
|
|
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.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(
|
|
fmt="json",
|
|
formsemestre_id=None,
|
|
formation_id=None,
|
|
etape_apo=None,
|
|
):
|
|
"""List formsemestres in given format.
|
|
kw can specify some conditions: examples:
|
|
formsemestre_list( fmt='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", fmt=fmt)
|
|
|
|
|
|
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"
|
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
|
modimpl.can_change_ens(raise_exc=True)
|
|
# --
|
|
header = html_sco_header.html_sem_header(
|
|
f"""Enseignants du <a href="{
|
|
url_for("notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
|
}">module {modimpl.module.titre or modimpl.module.code}</a>""",
|
|
page_title=f"Enseignants du module {modimpl.module.titre or modimpl.module.code}",
|
|
javascripts=["libjs/AutoSuggest.js"],
|
|
cssstyles=["css/autosuggest_inquisitor.css"],
|
|
bodyOnLoad="init_tf_form('')",
|
|
)
|
|
footer = html_sco_header.sco_footer()
|
|
|
|
# Liste des enseignants avec forme pour affichage / saisie avec suggestion
|
|
userlist = sco_users.get_user_list()
|
|
uid2display = {} # uid : forme pour affichage = "NOM Prenom (login)"(login)"
|
|
for u in userlist:
|
|
uid2display[u.id] = u.get_nomplogin()
|
|
allowed_user_names = list(uid2display.values())
|
|
|
|
H = [
|
|
f"""<ul><li><b>{
|
|
uid2display.get(modimpl.responsable_id, modimpl.responsable_id)
|
|
}</b> (responsable)</li>"""
|
|
]
|
|
u: User
|
|
for u in modimpl.enseignants:
|
|
H.append(
|
|
f"""
|
|
<li>{u.get_nomcomplet()} (<a class="stdlink" href="{
|
|
url_for('notes.edit_enseignants_form_delete',
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id,
|
|
ens_id=u.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=modimpl.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 = User.get_user_from_nomplogin(tf[2]["ens_id"])
|
|
if ens is None:
|
|
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.id for x in modimpl.enseignants)
|
|
or ens.id == modimpl.responsable_id
|
|
):
|
|
H.append(
|
|
f"""<p class="help">Enseignant {ens.user_name} déjà dans la liste !</p>"""
|
|
)
|
|
else:
|
|
modimpl.enseignants.append(ens)
|
|
db.session.add(modimpl)
|
|
db.session.commit()
|
|
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_responsable(current_user, raise_exc=True) # access control
|
|
H = [
|
|
html_sco_header.html_sem_header(
|
|
f"""Modification du responsable du <a class="stdlink" 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 = User.get_user_from_nomplogin(tf[2]["responsable_id"])
|
|
if not responsable:
|
|
# 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 = responsable
|
|
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,
|
|
)
|
|
)
|
|
|
|
|
|
@bp.route("/view_module_abs")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def view_module_abs(moduleimpl_id, fmt="html"):
|
|
"""Visualisation des absences a un module"""
|
|
modimpl: ModuleImpl = (
|
|
ModuleImpl.query.filter_by(id=moduleimpl_id)
|
|
.join(FormSemestre)
|
|
.filter_by(dept_id=g.scodoc_dept_id)
|
|
).first_or_404()
|
|
|
|
inscrits: list[Identite] = sorted(
|
|
[i.etud for i in modimpl.inscriptions], key=lambda e: e.sort_key
|
|
)
|
|
|
|
rows = []
|
|
for etud in inscrits:
|
|
(
|
|
nb_abs_nj,
|
|
nb_abs_just,
|
|
nb_abs,
|
|
) = sco_assiduites.formsemestre_get_assiduites_count(
|
|
etud.id, modimpl.formsemestre, moduleimpl_id=modimpl.id
|
|
)
|
|
rows.append(
|
|
{
|
|
"nomprenom": etud.nomprenom,
|
|
"just": nb_abs_just,
|
|
"nojust": nb_abs_nj,
|
|
"total": nb_abs,
|
|
"_nomprenom_target": url_for(
|
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
|
),
|
|
}
|
|
)
|
|
|
|
H = [
|
|
html_sco_header.html_sem_header(
|
|
f"""Absences du <a href="{
|
|
url_for("notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id
|
|
)}">module {modimpl.module.titre_str()}</a>""",
|
|
page_title=f"Absences du module {modimpl.module.titre_str()}",
|
|
)
|
|
]
|
|
if not rows and fmt == "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=rows,
|
|
html_class="table_leftalign",
|
|
base_url=f"{request.base_url}?moduleimpl_id={moduleimpl_id}",
|
|
filename="absmodule_" + scu.make_filename(modimpl.module.titre_str()),
|
|
caption=f"Absences dans le module {modimpl.module.titre_str()}",
|
|
preferences=sco_preferences.SemPreferences(),
|
|
table_id="view_module_abs",
|
|
)
|
|
|
|
if fmt != "html":
|
|
return tab.make_page(fmt=fmt)
|
|
|
|
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, fmt="html"):
|
|
"""Liste les enseignants intervenants dans le semestre (resp. modules et chargés de TD)
|
|
et indique les absences saisies par chacun.
|
|
"""
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
# resp. de modules et charges de TD
|
|
# uid : { "mods" : liste des modimpls, ... }
|
|
sem_ens: dict[int, list[ModuleImpl]] = {}
|
|
modimpls = formsemestre.modimpls_sorted
|
|
for modimpl in modimpls:
|
|
if not modimpl.responsable_id in sem_ens:
|
|
sem_ens[modimpl.responsable_id] = {"mods": [modimpl]}
|
|
else:
|
|
sem_ens[modimpl.responsable_id]["mods"].append(modimpl)
|
|
|
|
for enseignant in modimpl.enseignants:
|
|
if not enseignant.id in sem_ens:
|
|
sem_ens[enseignant.id] = {"mods": [modimpl]}
|
|
else:
|
|
sem_ens[enseignant.id]["mods"].append(modimpl)
|
|
# compte les absences ajoutées par chacun dans tout le semestre
|
|
for uid, info in sem_ens.items():
|
|
# Note : avant 9.6, on utilisait Scolog pour compter les opérations AddAbsence
|
|
# ici on compte directement les assiduités
|
|
info["nbabsadded"] = (
|
|
Assiduite.query.filter_by(user_id=uid, etat=scu.EtatAssiduite.ABSENT)
|
|
.filter(
|
|
Assiduite.date_debut >= formsemestre.date_debut,
|
|
Assiduite.date_debut <= formsemestre.date_fin,
|
|
)
|
|
.join(Identite)
|
|
.join(FormSemestreInscription)
|
|
.filter_by(formsemestre_id=formsemestre.id)
|
|
.count()
|
|
)
|
|
# description textuelle des modules
|
|
for uid, info in sem_ens.items():
|
|
info["descr_mods"] = ", ".join(
|
|
[modimpl.module.code for modimpl in sem_ens[uid]["mods"]]
|
|
)
|
|
|
|
# ajoute infos sur enseignant:
|
|
for uid, info in sem_ens.items():
|
|
user: User = db.session.get(User, uid)
|
|
if user:
|
|
if user.email:
|
|
info["email"] = user.email
|
|
info["_email_target"] = f"mailto:{user.email}"
|
|
info["nom_fmt"] = user.get_nom_fmt()
|
|
info["prenom_fmt"] = user.get_prenom_fmt()
|
|
info["sort_key"] = user.sort_key()
|
|
|
|
sem_ens_list = list(sem_ens.values())
|
|
sem_ens_list.sort(key=itemgetter("sort_key"))
|
|
|
|
# --- Generate page with table
|
|
title = f"Enseignants de {formsemestre.titre_mois()}"
|
|
table = 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 formsemestre_enseignants_list",
|
|
html_with_td_classes=True,
|
|
filename=scu.make_filename(f"Enseignants-{formsemestre.titre_annee()}"),
|
|
html_title=html_sco_header.html_sem_header(
|
|
"Enseignants du semestre", with_page_header=False
|
|
),
|
|
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}",
|
|
caption="""Tous les enseignants (responsables ou associés aux modules de
|
|
ce semestre) apparaissent. Le nombre de saisies d'absences est indicatif.""",
|
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
|
table_id="formsemestre_enseignants_list",
|
|
)
|
|
return table.make_page(page_title=title, title=title, fmt=fmt)
|
|
|
|
|
|
@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
|
|
"""
|
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
|
modimpl.can_change_ens(raise_exc=True)
|
|
# search ens_id
|
|
ens: User | None = None
|
|
for ens in modimpl.enseignants:
|
|
if ens.id == ens_id:
|
|
break
|
|
if ens is None:
|
|
raise ScoValueError(f"invalid ens_id ({ens_id})")
|
|
modimpl.enseignants.remove(ens)
|
|
db.session.commit()
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.edit_enseignants_form",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=moduleimpl_id,
|
|
)
|
|
)
|
|
|
|
|
|
# --- Gestion des inscriptions aux semestres
|
|
|
|
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, fmt=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, fmt=fmt, name="inscrits")
|
|
|
|
|
|
@bp.route("/formsemestre_desinscription", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EditFormSemestre)
|
|
@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 avec
|
|
<a class="stdlink" href="{
|
|
url_for("notes.formsemestre_validation_suppress_etud",
|
|
scodoc_dept=g.scodoc_dept, etudid=etudid,
|
|
formsemestre_id=formsemestre_id)
|
|
}">supprimer décision jury</a>)
|
|
""",
|
|
safe=True,
|
|
)
|
|
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.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
|
)
|
|
|
|
|
|
sco_publish(
|
|
"/do_formsemestre_desinscription",
|
|
sco_formsemestre_inscriptions.do_formsemestre_desinscription,
|
|
Permission.EtudInscrit,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
|
|
@bp.route(
|
|
"/etud_desinscrit_ue/<int:etudid>/<int:formsemestre_id>/<int:ue_id>",
|
|
methods=["GET", "POST"],
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
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.EtudInscrit)
|
|
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.EtudInscrit,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_inscription_with_modules_etud",
|
|
sco_formsemestre_inscriptions.formsemestre_inscription_with_modules_etud,
|
|
Permission.EtudInscrit,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_inscription_with_modules",
|
|
sco_formsemestre_inscriptions.formsemestre_inscription_with_modules,
|
|
Permission.EtudInscrit,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_inscription_option",
|
|
sco_formsemestre_inscriptions.formsemestre_inscription_option,
|
|
Permission.EtudInscrit,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/do_moduleimpl_incription_options",
|
|
sco_formsemestre_inscriptions.do_moduleimpl_incription_options,
|
|
Permission.EtudInscrit,
|
|
)
|
|
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.ScoView,
|
|
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.EnsView)
|
|
@scodoc7func
|
|
def evaluation_delete(evaluation_id):
|
|
"""Form delete evaluation"""
|
|
evaluation: Evaluation = (
|
|
Evaluation.query.filter_by(id=evaluation_id)
|
|
.join(ModuleImpl)
|
|
.join(FormSemestre)
|
|
.filter_by(dept_id=g.scodoc_dept_id)
|
|
.first_or_404()
|
|
)
|
|
|
|
tit = f"""Suppression de l'évaluation {evaluation.description} ({evaluation.descr_date()})"""
|
|
etat = sco_evaluations.do_evaluation_etat(evaluation.id)
|
|
H = [
|
|
f"""
|
|
{html_sco_header.html_sem_header(tit, with_h2=False)}
|
|
<h2 class="formsemestre">Module <tt>{evaluation.moduleimpl.module.code}</tt>
|
|
{evaluation.moduleimpl.module.titre_str()}</h2>
|
|
<h3>{tit}</h3>
|
|
<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(
|
|
f"""<div class="ue_warning"><span>Il y a {etat["nb_notes_total"]} notes"""
|
|
)
|
|
if nb_desinscrits:
|
|
H.append(
|
|
""" (dont {nb_desinscrits} d'étudiants qui ne sont plus inscrits)"""
|
|
)
|
|
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(
|
|
f"""<p>Suppression impossible (effacer les notes d'abord)</p>
|
|
<p><a class="stdlink" href="{
|
|
url_for("notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=evaluation.moduleimpl_id)
|
|
}">retour au tableau de bord du module</a>
|
|
</p>
|
|
</div>"""
|
|
)
|
|
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={"evaluation_id": evaluation.id},
|
|
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=evaluation.moduleimpl_id,
|
|
)
|
|
)
|
|
else:
|
|
evaluation.delete()
|
|
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=evaluation.moduleimpl_id)
|
|
}">Continuer</a></p>"""
|
|
+ html_sco_header.sco_footer()
|
|
)
|
|
|
|
|
|
@bp.route("/evaluation_edit", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EnsView)
|
|
@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.EnsView)
|
|
@scodoc7func
|
|
def evaluation_create(moduleimpl_id):
|
|
"form create evaluation"
|
|
modimpl = db.session.get(ModuleImpl, 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.
|
|
Si evaluation_id non spécifié, toutes les notes des évaluations de ce modimpl.
|
|
"""
|
|
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
|
|
|
|
fmt = vals.get("fmt", "html")
|
|
html_content, page_title = sco_liste_notes.do_evaluation_listenotes(
|
|
evaluation_id=evaluation_id, moduleimpl_id=moduleimpl_id, fmt=fmt
|
|
)
|
|
if fmt == "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,
|
|
)
|
|
|
|
|
|
@bp.route("/evaluation_check_absences_html/<int:evaluation_id>")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def evaluation_check_absences_html(evaluation_id: int):
|
|
"Check absences sur une évaluation"
|
|
evaluation: Evaluation = (
|
|
Evaluation.query.filter_by(id=evaluation_id)
|
|
.join(ModuleImpl)
|
|
.join(FormSemestre)
|
|
.filter_by(dept_id=g.scodoc_dept_id)
|
|
.first_or_404()
|
|
)
|
|
return sco_evaluation_check_abs.evaluation_check_absences_html(evaluation)
|
|
|
|
|
|
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.EnsView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
# --- Saisie des notes
|
|
sco_publish(
|
|
"/saisie_notes_tableur",
|
|
sco_saisie_notes.saisie_notes_tableur,
|
|
Permission.EnsView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/feuille_saisie_notes",
|
|
sco_saisie_notes.feuille_saisie_notes,
|
|
Permission.EnsView,
|
|
)
|
|
sco_publish("/saisie_notes", sco_saisie_notes.saisie_notes, Permission.EnsView)
|
|
sco_publish(
|
|
"/do_evaluation_set_missing",
|
|
sco_saisie_notes.do_evaluation_set_missing,
|
|
Permission.EnsView,
|
|
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,
|
|
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
|
version="selectedevals",
|
|
):
|
|
"Publie les bulletins dans un classeur PDF"
|
|
# Informations sur les groupes à utiliser:
|
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
|
group_ids,
|
|
formsemestre_id=formsemestre_id,
|
|
select_all_when_unspecified=True,
|
|
)
|
|
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
|
formsemestre_id, groups_infos=groups_infos, 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,
|
|
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
|
):
|
|
"""Choix version puis envoi classeur bulletins pdf"""
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
# Informations sur les groupes à utiliser:
|
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
|
group_ids,
|
|
formsemestre_id=formsemestre_id,
|
|
select_all_when_unspecified=True,
|
|
)
|
|
if version:
|
|
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
|
formsemestre_id, groups_infos=groups_infos, version=version
|
|
)
|
|
return scu.sendPDFFile(pdfdoc, filename)
|
|
return _formsemestre_bulletins_choice(
|
|
formsemestre,
|
|
explanation=_EXPL_BULL,
|
|
groups_infos=groups_infos,
|
|
title="Choisir la version des bulletins à générer",
|
|
)
|
|
|
|
|
|
@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"
|
|
if version not in scu.BULLETINS_VERSIONS:
|
|
raise ScoValueError("etud_bulletins_pdf: version de bulletin demandée invalide")
|
|
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,
|
|
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
|
):
|
|
"""Choix version puis envoi classeur bulletins pdf"""
|
|
# Informations sur les groupes à utiliser:
|
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
|
group_ids,
|
|
formsemestre_id=formsemestre_id,
|
|
select_all_when_unspecified=True,
|
|
)
|
|
if version:
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.formsemestre_bulletins_mailetuds",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
version=version,
|
|
dialog_confirmed=int(dialog_confirmed),
|
|
prefer_mail_perso=prefer_mail_perso,
|
|
group_ids=groups_infos.group_ids,
|
|
)
|
|
)
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
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>
|
|
"""
|
|
if formsemestre.formation.is_apc():
|
|
expl_bull += """
|
|
<li><b>courte spéciale BUT</b>: un résumé en une page pour les BUTs</li>
|
|
"""
|
|
expl_bull += "</ul>"
|
|
return _formsemestre_bulletins_choice(
|
|
formsemestre,
|
|
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,
|
|
groups_infos=groups_infos,
|
|
)
|
|
|
|
|
|
# not published
|
|
def _formsemestre_bulletins_choice(
|
|
formsemestre: FormSemestre,
|
|
title="",
|
|
explanation="",
|
|
choose_mail=False,
|
|
groups_infos=None,
|
|
):
|
|
"""Choix d'une version de bulletin
|
|
(pour envois mail ou génération classeur pdf)
|
|
"""
|
|
versions_bulletins = (
|
|
scu.BULLETINS_VERSIONS_BUT
|
|
if formsemestre.formation.is_apc()
|
|
else scu.BULLETINS_VERSIONS
|
|
)
|
|
|
|
return render_template(
|
|
"formsemestre/bulletins_choice.j2",
|
|
explanation=explanation,
|
|
choose_mail=choose_mail,
|
|
formsemestre=formsemestre,
|
|
menu_groups_choice=sco_groups_view.menu_groups_choice(groups_infos),
|
|
sco=ScoData(formsemestre=formsemestre),
|
|
sco_groups_view=sco_groups_view,
|
|
title=title,
|
|
versions_bulletins=versions_bulletins,
|
|
)
|
|
|
|
|
|
@bp.route("/formsemestre_bulletins_mailetuds", methods=["GET", "POST"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@scodoc7func
|
|
def formsemestre_bulletins_mailetuds(
|
|
formsemestre_id,
|
|
version="long",
|
|
dialog_confirmed=False,
|
|
prefer_mail_perso=0,
|
|
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
|
):
|
|
"""Envoie à chaque etudiant son bulletin
|
|
(inscrit non démissionnaire ni défaillant et ayant un mail renseigné dans ScoDoc)
|
|
"""
|
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
|
group_ids,
|
|
formsemestre_id=formsemestre_id,
|
|
select_all_when_unspecified=True,
|
|
)
|
|
etudids = {m["etudid"] for m in groups_infos.members}
|
|
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 and inscription.etudid in etudids
|
|
]
|
|
#
|
|
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 sélectionnés ?",
|
|
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,
|
|
fmt="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.EnsView)
|
|
@scodoc7func
|
|
def appreciation_add_form(
|
|
etudid=None,
|
|
formsemestre_id=None,
|
|
appreciation_id=None, # si id, edit
|
|
suppress=False, # si true, supress id
|
|
):
|
|
"form ajout ou edition d'une appreciation"
|
|
if appreciation_id: # edit mode
|
|
appreciation = db.session.get(BulAppreciations, appreciation_id)
|
|
if appreciation is None:
|
|
raise ScoValueError("id d'appreciation invalide !")
|
|
formsemestre_id = appreciation.formsemestre_id
|
|
etudid = appreciation.etudid
|
|
etud: Identite = Identite.query.filter_by(
|
|
id=etudid, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
vals = scu.get_request_args()
|
|
if "edit" in vals:
|
|
edit = int(vals["edit"])
|
|
elif appreciation_id:
|
|
edit = 1
|
|
else:
|
|
edit = 0
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
# check custom access permission
|
|
can_edit_app = formsemestre.est_responsable(current_user) or (
|
|
current_user.has_permission(Permission.EtudInscrit)
|
|
)
|
|
if not can_edit_app:
|
|
raise AccessDenied("vous n'avez pas le droit d'ajouter une appreciation")
|
|
#
|
|
bul_url = url_for(
|
|
"notes.formsemestre_bulletinetud",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
etudid=etudid,
|
|
)
|
|
|
|
if suppress:
|
|
db.session.delete(appreciation)
|
|
Scolog.logdb(
|
|
method="appreciation_suppress",
|
|
etudid=etudid,
|
|
)
|
|
db.session.commit()
|
|
flash("appréciation supprimée")
|
|
return flask.redirect(bul_url)
|
|
#
|
|
if appreciation_id:
|
|
action = "Édition"
|
|
else:
|
|
action = "Ajout"
|
|
H = [
|
|
html_sco_header.sco_header(),
|
|
f"""<h2>{action} d'une appréciation sur {etud.nomprenom}</h2>""",
|
|
]
|
|
F = html_sco_header.sco_footer()
|
|
descr = [
|
|
("edit", {"input_type": "hidden", "default": edit}),
|
|
("etudid", {"input_type": "hidden"}),
|
|
("formsemestre_id", {"input_type": "hidden"}),
|
|
("appreciation_id", {"input_type": "hidden"}),
|
|
("comment", {"title": "", "input_type": "textarea", "rows": 4, "cols": 60}),
|
|
]
|
|
if appreciation_id:
|
|
initvalues = {
|
|
"etudid": etudid,
|
|
"formsemestre_id": formsemestre_id,
|
|
"comment": appreciation.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(bul_url)
|
|
else:
|
|
if edit:
|
|
appreciation.author = (current_user.user_name,)
|
|
appreciation.comment = tf[2]["comment"].strip()
|
|
flash("appréciation modifiée")
|
|
else: # nouvelle
|
|
appreciation = BulAppreciations(
|
|
etudid=etudid,
|
|
formsemestre_id=formsemestre_id,
|
|
author=current_user.user_name,
|
|
comment=tf[2]["comment"].strip(),
|
|
)
|
|
flash("appréciation ajoutée")
|
|
db.session.add(appreciation)
|
|
# log
|
|
Scolog.logdb(
|
|
method="appreciation_add",
|
|
etudid=etudid,
|
|
msg=appreciation.comment_safe(),
|
|
)
|
|
db.session.commit()
|
|
# ennuyeux mais necessaire (pour le PDF seulement)
|
|
sco_cache.invalidate_formsemestre(
|
|
pdfonly=True, formsemestre_id=formsemestre_id
|
|
) # > appreciation_add
|
|
return flask.redirect(bul_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()
|
|
# 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 as exc:
|
|
raise ScoValueError("adresse invalide") from exc
|
|
etud = Identite.get_etud(etudid)
|
|
nb_etuds = formsemestre.etuds.count()
|
|
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.html_link_fiche()}</div>
|
|
</div>
|
|
<div class="bull_photo"><a href="{
|
|
etud.url_fiche()
|
|
}">{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="{
|
|
etud.url_fiche()}">sa fiche</a>)
|
|
</div>
|
|
</div>
|
|
{navigation_div}
|
|
</div>
|
|
"""
|
|
+ html_sco_header.sco_footer()
|
|
)
|
|
|
|
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
|
has_notes_en_attente = deca.has_notes_en_attente()
|
|
evaluations_a_debloquer = Evaluation.get_evaluations_blocked_for_etud(
|
|
formsemestre, etud
|
|
)
|
|
if has_notes_en_attente or evaluations_a_debloquer:
|
|
read_only = True
|
|
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):
|
|
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 formsemestre.date_fin - datetime.date.today() > datetime.timedelta(days=12):
|
|
# encore loin de la fin du semestre de départ de ce jury ?
|
|
warning += f"""<div class="warning">Le semestre S{formsemestre.semestre_id}
|
|
terminera le {formsemestre.date_fin.strftime(scu.DATE_FMT)} :
|
|
êtes-vous certain de vouloir enregistrer une décision de jury ?
|
|
</div>"""
|
|
|
|
if deca.formsemestre_impair:
|
|
inscription = deca.formsemestre_impair.etuds_inscriptions.get(etud.id)
|
|
if (not inscription) or inscription.etat != scu.INSCRIT:
|
|
etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
|
|
warning += f"""<div class="warning">{etat_ins}
|
|
en S{deca.formsemestre_impair.semestre_id}</div>"""
|
|
|
|
if deca.formsemestre_pair:
|
|
inscription = deca.formsemestre_pair.etuds_inscriptions.get(etud.id)
|
|
if (not inscription) or inscription.etat != scu.INSCRIT:
|
|
etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
|
|
warning += f"""<div class="warning">{etat_ins}
|
|
en S{deca.formsemestre_pair.semestre_id}</div>"""
|
|
|
|
if has_notes_en_attente:
|
|
warning += f"""<div class="warning-bloquant">{etud.html_link_fiche()
|
|
} a des notes en ATTente dans les modules suivants.
|
|
Vous devez régler cela avant de statuer en jury !
|
|
<ul class="modimpls_att">
|
|
"""
|
|
for modimpl in deca.get_modimpls_attente():
|
|
warning += f"""<li><a href="{
|
|
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
|
}" class="stdlink">{modimpl.module.code} {modimpl.module.titre_str()}</a></li>"""
|
|
warning += "</ul></div>"
|
|
if evaluations_a_debloquer:
|
|
links_evals = [
|
|
f"""<a class="stdlink" href="{url_for(
|
|
'notes.evaluation_listenotes', scodoc_dept=g.scodoc_dept, evaluation_id=e.id
|
|
)}">{e.description} en {e.moduleimpl.module.code}</a>"""
|
|
for e in evaluations_a_debloquer
|
|
]
|
|
warning += f"""<div class="warning-bloquant">Impossible de statuer sur cet étudiant:
|
|
il a des notes dans des évaluations qui seront débloquées plus tard:
|
|
voir {", ".join(links_evals)}
|
|
"""
|
|
|
|
if warning:
|
|
warning = f"""<div class="jury_but_warning jury_but_box">{warning}</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.html_link_fiche()}</div>
|
|
</div>
|
|
<div class="bull_photo"><a href="{
|
|
etud.url_fiche()}">{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 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, formsemestre_id=formsemestre_id)}"
|
|
>effacer des décisions de jury</a>
|
|
|
|
<a style="margin-left: 16px;" class="stdlink"
|
|
href="{
|
|
url_for("notes.formsemestre_validate_previous_ue",
|
|
scodoc_dept=g.scodoc_dept,
|
|
etudid=deca.etud.id, formsemestre_id=formsemestre_id)}"
|
|
>enregistrer des UEs antérieures</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>")
|
|
|
|
# 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(),
|
|
)
|
|
)
|
|
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>
|
|
"""
|
|
)
|
|
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,
|
|
)
|
|
)
|
|
if not formsemestre.formation.is_apc():
|
|
raise ScoValueError(
|
|
"formsemestre_validation_auto_but est réservé aux formations APC"
|
|
)
|
|
|
|
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,
|
|
)
|
|
)
|
|
# Avertissement si formsemestre impair
|
|
formsemestres_suspects = {}
|
|
if formsemestre.semestre_id % 2:
|
|
_, decas = jury_but_validation_auto.formsemestre_validation_auto_but(
|
|
formsemestre, dry_run=True
|
|
)
|
|
# regarde si il y a des semestres pairs postérieurs qui ne soient pas bloqués
|
|
formsemestres_suspects = {
|
|
deca.formsemestre_pair.id: deca.formsemestre_pair
|
|
for deca in decas
|
|
if deca.formsemestre_pair
|
|
and deca.formsemestre_pair.date_debut > formsemestre.date_debut
|
|
and not deca.formsemestre_pair.block_moyennes
|
|
}
|
|
|
|
return render_template(
|
|
"but/formsemestre_validation_auto_but.j2",
|
|
form=form,
|
|
formsemestres_suspects=formsemestres_suspects,
|
|
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.fiche_etud",
|
|
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_erase/<int:formsemestre_id>",
|
|
methods=["GET", "POST"],
|
|
defaults={"etudid": None},
|
|
)
|
|
@bp.route(
|
|
"/formsemestre_jury_erase/<int:formsemestre_id>/<int:etudid>",
|
|
methods=["GET", "POST"],
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def formsemestre_jury_erase(formsemestre_id: int, etudid: int = None):
|
|
"""Supprime la décision de jury (classique ou BUT) pour cette année.
|
|
Si l'étudiant n'est pas spécifié, efface les décisions de tous les inscrits.
|
|
En BUT, si only_one_sem n'efface que pour le formsemestre indiqué, pas les deux de l'année.
|
|
En classique, n'affecte que les décisions issues de ce formsemestre.
|
|
"""
|
|
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,
|
|
)
|
|
)
|
|
is_apc = formsemestre.formation.is_apc()
|
|
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]
|
|
endpoint = (
|
|
"notes.formsemestre_validation_but"
|
|
if is_apc
|
|
else "notes.formsemestre_validation_etud_form"
|
|
)
|
|
dest_url = url_for(
|
|
endpoint,
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre_id,
|
|
etudid=etudid,
|
|
)
|
|
if request.method == "POST":
|
|
with sco_cache.DeferredSemCacheManager():
|
|
for etud in etuds:
|
|
if is_apc:
|
|
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
|
deca.erase(only_one_sem=only_one_sem)
|
|
else:
|
|
sco_formsemestre_validation.formsemestre_validation_suppress_etud(
|
|
formsemestre.id, etud.id
|
|
)
|
|
log(f"formsemestre_jury_erase({formsemestre_id}, {etud.id})")
|
|
flash(
|
|
(
|
|
"décisions de jury du semestre effacées"
|
|
if (only_one_sem or is_apc)
|
|
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 or is_apc)
|
|
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>"""
|
|
if is_apc
|
|
else ""
|
|
+ """
|
|
<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.EtudInscrit)
|
|
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.fiche_etud",
|
|
scodoc_dept=g.scodoc_dept,
|
|
etudid=etud.id,
|
|
)
|
|
)
|
|
validations = jury.erase_decisions_annee_formation(etud, formation, annee)
|
|
formsemestre_origine_id = request.args.get("formsemestre_id")
|
|
formsemestre_origine = (
|
|
FormSemestre.query.get_or_404(formsemestre_origine_id)
|
|
if formsemestre_origine_id
|
|
else None
|
|
)
|
|
return render_template(
|
|
"jury/erase_decisions_annee_formation.j2",
|
|
annee=annee,
|
|
cancel_url=url_for(
|
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
|
),
|
|
etud=etud,
|
|
formation=formation,
|
|
formsemestre_origine=formsemestre_origine,
|
|
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.EtudInscrit)
|
|
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(
|
|
"/feuille_preparation_lille",
|
|
sco_prepajury_lille.feuille_preparation_lille,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_archive",
|
|
sco_archives_formsemestre.formsemestre_archive,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_delete_archive",
|
|
sco_archives_formsemestre.formsemestre_delete_archive,
|
|
Permission.ScoView,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_list_archives",
|
|
sco_archives_formsemestre.formsemestre_list_archives,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish(
|
|
"/formsemestre_get_archived_file",
|
|
sco_archives_formsemestre.formsemestre_get_archived_file,
|
|
Permission.ScoView,
|
|
)
|
|
sco_publish("/view_apo_csv", sco_etape_apogee_view.view_apo_csv, Permission.EditApogee)
|
|
sco_publish(
|
|
"/view_apo_csv_store",
|
|
sco_etape_apogee_view.view_apo_csv_store,
|
|
Permission.EditApogee,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/view_apo_csv_download_and_store",
|
|
sco_etape_apogee_view.view_apo_csv_download_and_store,
|
|
Permission.EditApogee,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/view_apo_csv_delete",
|
|
sco_etape_apogee_view.view_apo_csv_delete,
|
|
Permission.EditApogee,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/view_scodoc_etuds", sco_etape_apogee_view.view_scodoc_etuds, Permission.EditApogee
|
|
)
|
|
sco_publish(
|
|
"/view_apo_etuds", sco_etape_apogee_view.view_apo_etuds, Permission.EditApogee
|
|
)
|
|
sco_publish(
|
|
"/apo_semset_maq_status",
|
|
sco_etape_apogee_view.apo_semset_maq_status,
|
|
Permission.EditApogee,
|
|
)
|
|
sco_publish(
|
|
"/apo_csv_export_results",
|
|
sco_etape_apogee_view.apo_csv_export_results,
|
|
Permission.EditApogee,
|
|
)
|
|
|
|
|
|
@bp.route("/formsemestre_set_apo_etapes", methods=["POST"])
|
|
@scodoc
|
|
@permission_required(Permission.EditApogee)
|
|
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.EditApogee)
|
|
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.EditApogee)
|
|
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
|
|
"""
|
|
try:
|
|
oid = int(request.form.get("oid"))
|
|
except (TypeError, ValueError) as exc:
|
|
raise ScoValueError("paramètre invalide") from exc
|
|
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.EditApogee)
|
|
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.EditApogee)
|
|
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.EditApogee)
|
|
sco_publish(
|
|
"/do_semset_create",
|
|
sco_semset.do_semset_create,
|
|
Permission.EditApogee,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/do_semset_delete",
|
|
sco_semset.do_semset_delete,
|
|
Permission.EditApogee,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/edit_semset_set_title",
|
|
sco_semset.edit_semset_set_title,
|
|
Permission.EditApogee,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/do_semset_add_sem",
|
|
sco_semset.do_semset_add_sem,
|
|
Permission.EditApogee,
|
|
methods=["GET", "POST"],
|
|
)
|
|
sco_publish(
|
|
"/do_semset_remove_sem",
|
|
sco_semset.do_semset_remove_sem,
|
|
Permission.EditApogee,
|
|
methods=["GET", "POST"],
|
|
)
|
|
|
|
# sco_export_result
|
|
sco_publish(
|
|
"/scodoc_table_results",
|
|
sco_export_results.scodoc_table_results,
|
|
Permission.EditApogee,
|
|
)
|
|
|
|
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.EtudInscrit,
|
|
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.ViewEtudData,
|
|
)
|
|
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(
|
|
"/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.EditFormSemestre)
|
|
@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 = UniteEns.query.get_or_404(mod["ue_id"])
|
|
ue_dict = ue.to_dict()
|
|
formations_set.add(ue_dict["formation_id"])
|
|
if ue_dict["formation_id"] != mod["formation_id"]:
|
|
modimpl["mod"] = mod
|
|
modimpl["ue"] = ue_dict
|
|
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 (obsolete)"
|
|
log(f"check_form_integrity: formation_id={formation_id} fix={fix}")
|
|
formation: Formation = Formation.query.filter_by(
|
|
dept_id=g.scodoc_dept_id, formation_id=formation_id
|
|
).first_or_404()
|
|
bad = []
|
|
for ue in formation.ues:
|
|
for matiere in ue.matieres:
|
|
for mod in matiere.modules:
|
|
if mod.ue_id != ue.id:
|
|
if fix:
|
|
# fix mod.ue_id
|
|
log(f"fix: mod.ue_id = {ue.id} (was {mod.ue_id})")
|
|
mod.ue_id = ue.id
|
|
db.session.add(mod)
|
|
bad.append(mod)
|
|
if mod.formation_id != formation_id:
|
|
bad.append(mod)
|
|
if bad:
|
|
txth = "<br>".join([html.escape(str(x)) for x in bad])
|
|
txt = "\n".join([str(x) for x in bad])
|
|
log(f"check_form_integrity: formation_id={formation_id}\ninconsistencies:")
|
|
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(f"check_formsemestre_integrity: formsemestre_id={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 = []
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
for modimpl in formsemestre.modimpls:
|
|
if modimpl.module.ue_id != modimpl.module.matiere.ue_id:
|
|
diag.append(
|
|
f"""moduleimpl {modimpl.id}: module.ue_id={modimpl.module.ue_id
|
|
} != matiere.ue_id={modimpl.module.matiere.ue_id}"""
|
|
)
|
|
if modimpl.module.ue.formation_id != modimpl.module.formation_id:
|
|
diag.append(
|
|
f"""moduleimpl {modimpl.id}: ue.formation_id={
|
|
modimpl.module.ue.formation_id} != mod.formation_id={
|
|
modimpl.module.formation_id}"""
|
|
)
|
|
if diag:
|
|
send_scodoc_alarm(
|
|
f"Notes: formation incoherente dans semestre {formsemestre_id} !",
|
|
"\n".join(diag),
|
|
)
|
|
log(f"check_formsemestre_integrity: formsemestre_id={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()
|
|
)
|