357 lines
12 KiB
Python
357 lines
12 KiB
Python
##############################################################################
|
|
# ScoDoc
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
|
# See LICENSE
|
|
##############################################################################
|
|
|
|
"""
|
|
ScoDoc 9 API : jury WIP à compléter avec enregistrement décisions
|
|
"""
|
|
|
|
import datetime
|
|
|
|
from flask import g, request, url_for
|
|
from flask_json import as_json
|
|
from flask_login import current_user, login_required
|
|
|
|
import app
|
|
from app import db, log
|
|
from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR, tools
|
|
from app.decorators import scodoc, permission_required
|
|
from app.scodoc.sco_exceptions import ScoException
|
|
from app.but import jury_but_results
|
|
from app.models import (
|
|
ApcParcours,
|
|
ApcValidationAnnee,
|
|
ApcValidationRCUE,
|
|
Formation,
|
|
FormSemestre,
|
|
Identite,
|
|
ScolarAutorisationInscription,
|
|
ScolarFormSemestreValidation,
|
|
ScolarNews,
|
|
Scolog,
|
|
UniteEns,
|
|
)
|
|
from app.scodoc import codes_cursus
|
|
from app.scodoc import sco_cache
|
|
from app.scodoc.sco_permissions import Permission
|
|
from app.scodoc.sco_utils import json_error
|
|
|
|
|
|
@bp.route("/formsemestre/<int:formsemestre_id>/decisions_jury")
|
|
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/decisions_jury")
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def decisions_jury(formsemestre_id: int):
|
|
"""Décisions du jury des étudiants du formsemestre."""
|
|
# APC, pair:
|
|
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
|
if formsemestre is None:
|
|
return json_error(
|
|
404,
|
|
message="formsemestre inconnu",
|
|
)
|
|
if formsemestre.formation.is_apc():
|
|
app.set_sco_dept(formsemestre.departement.acronym)
|
|
rows = jury_but_results.get_jury_but_results(formsemestre)
|
|
return rows
|
|
else:
|
|
raise ScoException("non implemente")
|
|
|
|
|
|
def _news_delete_jury_etud(etud: Identite):
|
|
"génère news sur effacement décision"
|
|
# n'utilise pas g.scodoc_dept, pas toujours dispo en mode API
|
|
url = url_for(
|
|
"scolar.ficheEtud", scodoc_dept=etud.departement.acronym, etudid=etud.id
|
|
)
|
|
ScolarNews.add(
|
|
typ=ScolarNews.NEWS_JURY,
|
|
obj=etud.id,
|
|
text=f"""Suppression décision jury pour <a href="{url}">{etud.nomprenom}</a>""",
|
|
url=url,
|
|
)
|
|
|
|
|
|
@bp.route(
|
|
"/etudiant/<int:etudid>/jury/validation_ue/<int:validation_id>/delete",
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/etudiant/<int:etudid>/jury/validation_ue/<int:validation_id>/delete",
|
|
methods=["POST"],
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def validation_ue_delete(etudid: int, validation_id: int):
|
|
"Efface cette validation"
|
|
return _validation_ue_delete(etudid, validation_id)
|
|
|
|
|
|
@bp.route(
|
|
"/etudiant/<int:etudid>/jury/validation_formsemestre/<int:validation_id>/delete",
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/etudiant/<int:etudid>/jury/validation_formsemestre/<int:validation_id>/delete",
|
|
methods=["POST"],
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def validation_formsemestre_delete(etudid: int, validation_id: int):
|
|
"Efface cette validation"
|
|
# c'est la même chose (formations classiques)
|
|
return _validation_ue_delete(etudid, validation_id)
|
|
|
|
|
|
def _validation_ue_delete(etudid: int, validation_id: int):
|
|
"Efface cette validation (semestres classiques ou UEs)"
|
|
etud = tools.get_etud(etudid)
|
|
if etud is None:
|
|
return "étudiant inconnu", 404
|
|
validation = ScolarFormSemestreValidation.query.filter_by(
|
|
id=validation_id, etudid=etudid
|
|
).first_or_404()
|
|
# Vérification de la permission:
|
|
# A le droit de supprimer cette validation: le chef de dept ou quelqu'un ayant
|
|
# le droit de saisir des décisions de jury dans le formsemestre concerné s'il y en a un
|
|
# (c'est le cas pour les validations de jury, mais pas pour les "antérieures" non
|
|
# rattachées à un formsemestre)
|
|
if not g.scodoc_dept: # accès API
|
|
if not current_user.has_permission(Permission.EtudInscrit):
|
|
return json_error(403, "opération non autorisée (117)")
|
|
else:
|
|
if validation.formsemestre:
|
|
if (
|
|
validation.formsemestre.dept_id != g.scodoc_dept_id
|
|
) or not validation.formsemestre.can_edit_jury():
|
|
return json_error(403, "opération non autorisée (123)")
|
|
elif not current_user.has_permission(Permission.EtudInscrit):
|
|
# Validation non rattachée à un semestre: on doit être chef
|
|
return json_error(403, "opération non autorisée (126)")
|
|
|
|
log(f"validation_ue_delete: etuid={etudid} {validation}")
|
|
db.session.delete(validation)
|
|
sco_cache.invalidate_formsemestre_etud(etud)
|
|
db.session.commit()
|
|
_news_delete_jury_etud(etud)
|
|
return "ok"
|
|
|
|
|
|
@bp.route(
|
|
"/etudiant/<int:etudid>/jury/autorisation_inscription/<int:validation_id>/delete",
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/etudiant/<int:etudid>/jury/autorisation_inscription/<int:validation_id>/delete",
|
|
methods=["POST"],
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@as_json
|
|
def autorisation_inscription_delete(etudid: int, validation_id: int):
|
|
"Efface cette validation"
|
|
etud = tools.get_etud(etudid)
|
|
if etud is None:
|
|
return "étudiant inconnu", 404
|
|
validation = ScolarAutorisationInscription.query.filter_by(
|
|
id=validation_id, etudid=etudid
|
|
).first_or_404()
|
|
log(f"autorisation_inscription_delete: etuid={etudid} {validation}")
|
|
db.session.delete(validation)
|
|
sco_cache.invalidate_formsemestre_etud(etud)
|
|
db.session.commit()
|
|
_news_delete_jury_etud(etud)
|
|
return "ok"
|
|
|
|
|
|
@bp.route(
|
|
"/etudiant/<int:etudid>/jury/validation_rcue/record",
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/etudiant/<int:etudid>/jury/validation_rcue/record",
|
|
methods=["POST"],
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@as_json
|
|
def validation_rcue_record(etudid: int):
|
|
"""Enregistre une validation de RCUE.
|
|
Si une validation existe déjà pour ce RCUE, la remplace.
|
|
The request content type should be "application/json":
|
|
{
|
|
"code" : str,
|
|
"ue1_id" : int,
|
|
"ue2_id" : int,
|
|
// Optionnel:
|
|
"formsemestre_id" : int,
|
|
"date" : date_iso, // si non spécifié, now()
|
|
"parcours_id" :int,
|
|
}
|
|
"""
|
|
etud = tools.get_etud(etudid)
|
|
if etud is None:
|
|
return json_error(404, "étudiant inconnu")
|
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
|
code = data.get("code")
|
|
if code is None:
|
|
return json_error(API_CLIENT_ERROR, "missing argument: code")
|
|
if code not in codes_cursus.CODES_JURY_RCUE:
|
|
return json_error(API_CLIENT_ERROR, "invalid code value")
|
|
ue1_id = data.get("ue1_id")
|
|
if ue1_id is None:
|
|
return json_error(API_CLIENT_ERROR, "missing argument: ue1_id")
|
|
try:
|
|
ue1_id = int(ue1_id)
|
|
except ValueError:
|
|
return json_error(API_CLIENT_ERROR, "invalid value for ue1_id")
|
|
ue2_id = data.get("ue2_id")
|
|
if ue2_id is None:
|
|
return json_error(API_CLIENT_ERROR, "missing argument: ue2_id")
|
|
try:
|
|
ue2_id = int(ue2_id)
|
|
except ValueError:
|
|
return json_error(API_CLIENT_ERROR, "invalid value for ue2_id")
|
|
formsemestre_id = data.get("formsemestre_id")
|
|
date_validation_str = data.get("date", datetime.datetime.now().isoformat())
|
|
parcours_id = data.get("parcours_id")
|
|
#
|
|
query = UniteEns.query.filter_by(id=ue1_id)
|
|
if g.scodoc_dept:
|
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
|
ue1: UniteEns = query.first_or_404()
|
|
query = UniteEns.query.filter_by(id=ue2_id)
|
|
if g.scodoc_dept:
|
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
|
ue2: UniteEns = query.first_or_404()
|
|
if ue1.niveau_competence_id != ue2.niveau_competence_id:
|
|
return json_error(
|
|
API_CLIENT_ERROR, "UEs non associees au meme niveau de competence"
|
|
)
|
|
if formsemestre_id is not None:
|
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
|
if g.scodoc_dept:
|
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
formsemestre: FormSemestre = query.first_or_404()
|
|
if (formsemestre.formation_id != ue1.formation_id) or (
|
|
formsemestre.formation_id != ue2.formation_id
|
|
):
|
|
return json_error(
|
|
API_CLIENT_ERROR, "ues et semestre ne sont pas de la meme formation"
|
|
)
|
|
else:
|
|
formsemestre = None
|
|
try:
|
|
date_validation = datetime.datetime.fromisoformat(date_validation_str)
|
|
except ValueError:
|
|
return json_error(API_CLIENT_ERROR, "invalid date string")
|
|
if parcours_id is not None:
|
|
parcours: ApcParcours = ApcParcours.query.get_or_404(parcours_id)
|
|
if parcours.referentiel_id != ue1.niveau_competence.competence.referentiel_id:
|
|
return json_error(API_CLIENT_ERROR, "niveau et parcours incompatibles")
|
|
|
|
# Une validation pour ce niveau de compétence existe-elle ?
|
|
validation = (
|
|
ApcValidationRCUE.query.filter_by(etudid=etudid)
|
|
.join(UniteEns, UniteEns.id == ApcValidationRCUE.ue2_id)
|
|
.filter_by(niveau_competence_id=ue2.niveau_competence_id)
|
|
.first()
|
|
)
|
|
if validation:
|
|
validation.code = code
|
|
validation.date = date_validation
|
|
validation.formsemestre_id = formsemestre_id
|
|
validation.parcours_id = parcours_id
|
|
validation.ue1_id = ue1_id
|
|
validation.ue2_id = ue2_id
|
|
operation = "update"
|
|
else:
|
|
validation = ApcValidationRCUE(
|
|
code=code,
|
|
date=date_validation,
|
|
etudid=etudid,
|
|
formsemestre_id=formsemestre_id,
|
|
parcours_id=parcours_id,
|
|
ue1_id=ue1_id,
|
|
ue2_id=ue2_id,
|
|
)
|
|
operation = "record"
|
|
db.session.add(validation)
|
|
# invalider bulletins (les autres résultats ne dépendent pas des RCUEs):
|
|
sco_cache.invalidate_formsemestre_etud(etud)
|
|
db.session.commit()
|
|
Scolog.logdb(
|
|
method="validation_rcue_record",
|
|
etudid=etudid,
|
|
msg=f"Enregistrement {validation}",
|
|
commit=True,
|
|
)
|
|
log(f"{operation} {validation}")
|
|
return validation.to_dict()
|
|
|
|
|
|
@bp.route(
|
|
"/etudiant/<int:etudid>/jury/validation_rcue/<int:validation_id>/delete",
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/etudiant/<int:etudid>/jury/validation_rcue/<int:validation_id>/delete",
|
|
methods=["POST"],
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@as_json
|
|
def validation_rcue_delete(etudid: int, validation_id: int):
|
|
"Efface cette validation"
|
|
etud = tools.get_etud(etudid)
|
|
if etud is None:
|
|
return "étudiant inconnu", 404
|
|
validation = ApcValidationRCUE.query.filter_by(
|
|
id=validation_id, etudid=etudid
|
|
).first_or_404()
|
|
log(f"validation_ue_delete: etuid={etudid} {validation}")
|
|
db.session.delete(validation)
|
|
sco_cache.invalidate_formsemestre_etud(etud)
|
|
db.session.commit()
|
|
_news_delete_jury_etud(etud)
|
|
return "ok"
|
|
|
|
|
|
@bp.route(
|
|
"/etudiant/<int:etudid>/jury/validation_annee_but/<int:validation_id>/delete",
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/etudiant/<int:etudid>/jury/validation_annee_but/<int:validation_id>/delete",
|
|
methods=["POST"],
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.EtudInscrit)
|
|
@as_json
|
|
def validation_annee_but_delete(etudid: int, validation_id: int):
|
|
"Efface cette validation"
|
|
etud = tools.get_etud(etudid)
|
|
if etud is None:
|
|
return "étudiant inconnu", 404
|
|
validation = ApcValidationAnnee.query.filter_by(
|
|
id=validation_id, etudid=etudid
|
|
).first_or_404()
|
|
log(f"validation_annee_but: etuid={etudid} {validation}")
|
|
db.session.delete(validation)
|
|
sco_cache.invalidate_formsemestre_etud(etud)
|
|
db.session.commit()
|
|
_news_delete_jury_etud(etud)
|
|
return "ok"
|