1
0
forked from ScoDoc/ScoDoc
ScoDoc/app/api/formsemestres.py

889 lines
29 KiB
Python
Raw Normal View History

##############################################################################
# ScoDoc
2023-12-31 23:04:06 +01:00
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""
ScoDoc 9 API : accès aux formsemestres
2024-07-24 17:34:30 +02:00
CATEGORY
--------
FormSemestre
"""
import base64
import io
from operator import attrgetter, itemgetter
2023-05-15 23:39:08 +02:00
from flask import g, make_response, request
from flask_json import as_json
2024-01-20 17:37:24 +01:00
from flask_login import current_user, login_required
import PIL
import sqlalchemy as sa
import app
from app import db, log
from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR
from app.api import api_permission_required as permission_required
from app.decorators import scodoc
from app.scodoc.sco_utils import json_error
from app.comp import res_sem
from app.comp.moy_mod import ModuleImplResults
from app.comp.res_compat import NotesTableCompat
from app.models import (
Departement,
Evaluation,
FormSemestre,
2024-08-14 10:28:26 +02:00
FormSemestreDescription,
FormSemestreEtape,
FormSemestreInscription,
Identite,
ModuleImpl,
NotesNotes,
)
2023-05-15 23:39:08 +02:00
from app.models.formsemestre import GROUPS_AUTO_ASSIGNMENT_DATA_MAX
from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json
from app.scodoc import sco_edt_cal
from app.scodoc.sco_formsemestre_inscriptions import (
do_formsemestre_inscription_with_modules,
do_formsemestre_desinscription,
)
from app.scodoc import sco_groups
2022-03-04 17:16:08 +01:00
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_utils import ModuleType
import app.scodoc.sco_utils as scu
from app.tables.recap import TableRecap, RowRecap
@bp.route("/formsemestre/<int:formsemestre_id>")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestre_get(formsemestre_id: int):
"""
Information sur le formsemestre indiqué.
formsemestre_id : l'id du formsemestre
SAMPLES
-------
/formsemestre/1
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
return formsemestre.to_dict_api()
@bp.route("/formsemestres/query")
@api_web_bp.route("/formsemestres/query")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
2022-07-21 08:53:44 +02:00
def formsemestres_query():
2022-04-27 09:24:20 +02:00
"""
2024-07-24 17:34:30 +02:00
Retourne les formsemestres filtrés par étape Apogée ou année scolaire
ou département (acronyme ou id) ou état ou code étudiant.
2022-04-27 09:24:20 +02:00
2024-07-23 16:49:11 +02:00
PARAMS
------
2022-05-03 13:35:17 +02:00
etape_apo : un code étape apogée
2022-07-21 08:53:44 +02:00
annee_scolaire : année de début de l'année scolaire
dept_acronym : acronyme du département (eg "RT")
dept_id : id du département
ine ou nip: code d'un étudiant: ramène alors tous les semestres auxquels il est inscrit.
etat: 0 si verrouillé, 1 sinon
QUERY
-----
etape_apo:<string:etape_apo>
annee_scolaire:<string:annee_scolaire>
dept_acronym:<string:dept_acronym>
dept_id:<int:dept_id>
etat:<int:etat>
nip:<string:nip>
ine:<string:ine>
2022-04-27 09:24:20 +02:00
"""
2022-07-21 08:53:44 +02:00
etape_apo = request.args.get("etape_apo")
annee_scolaire = request.args.get("annee_scolaire")
dept_acronym = request.args.get("dept_acronym")
dept_id = request.args.get("dept_id")
etat = request.args.get("etat")
nip = request.args.get("nip")
2022-10-28 15:48:27 +02:00
ine = request.args.get("ine")
2022-07-21 08:53:44 +02:00
formsemestres = FormSemestre.query
if g.scodoc_dept:
formsemestres = formsemestres.filter_by(dept_id=g.scodoc_dept_id)
2022-07-21 08:53:44 +02:00
if annee_scolaire is not None:
try:
annee_scolaire_int = int(annee_scolaire)
except ValueError:
return json_error(API_CLIENT_ERROR, "invalid annee_scolaire: not int")
2023-12-29 13:57:44 +01:00
debut_annee = scu.date_debut_annee_scolaire(annee_scolaire_int)
fin_annee = scu.date_fin_annee_scolaire(annee_scolaire_int)
2022-07-21 08:53:44 +02:00
formsemestres = formsemestres.filter(
FormSemestre.date_fin >= debut_annee, FormSemestre.date_debut <= fin_annee
)
if etat is not None:
try:
etat = bool(int(etat))
except ValueError:
return json_error(404, "invalid etat: integer expected")
formsemestres = formsemestres.filter_by(etat=etat)
2022-07-21 08:53:44 +02:00
if dept_acronym is not None:
formsemestres = formsemestres.join(Departement).filter_by(acronym=dept_acronym)
2022-07-21 08:53:44 +02:00
if dept_id is not None:
try:
dept_id = int(dept_id)
except ValueError:
return json_error(404, "invalid dept_id: integer expected")
2022-07-21 08:53:44 +02:00
formsemestres = formsemestres.filter_by(dept_id=dept_id)
if etape_apo is not None:
formsemestres = formsemestres.join(FormSemestreEtape).filter(
FormSemestreEtape.etape_apo == etape_apo
)
2022-10-28 15:48:27 +02:00
inscr_joined = False
if nip is not None:
formsemestres = (
formsemestres.join(FormSemestreInscription)
.join(Identite)
.filter_by(code_nip=nip)
)
2022-10-28 15:48:27 +02:00
inscr_joined = True
if ine is not None:
2022-10-28 15:48:27 +02:00
if not inscr_joined:
formsemestres = formsemestres.join(FormSemestreInscription).join(Identite)
formsemestres = formsemestres.filter_by(code_ine=ine)
return [
formsemestre.to_dict_api()
for formsemestre in formsemestres.order_by(
FormSemestre.date_debut.desc(),
FormSemestre.modalite,
FormSemestre.semestre_id,
FormSemestre.titre,
)
]
2022-04-27 09:24:20 +02:00
@bp.route("/formsemestre/<int:formsemestre_id>/edit", methods=["POST"])
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/edit", methods=["POST"])
@scodoc
@permission_required(Permission.EditFormSemestre)
@as_json
def formsemestre_edit(formsemestre_id: int):
2024-07-24 17:34:30 +02:00
"""Modifie les champs d'un formsemestre.
On peut spécifier un ou plusieurs champs.
DATA
---
```json
{
"semestre_id" : string,
"titre" : string,
"date_debut" : date iso,
"date_fin" : date iso,
"edt_id" : string,
"etat" : string,
"modalite" : string,
"gestion_compensation" : bool,
"bul_hide_xml" : bool,
"block_moyennes" : bool,
"block_moyenne_generale" : bool,
"mode_calcul_moyennes" : string,
"gestion_semestrielle" : string,
"bul_bgcolor" : string,
"resp_can_edit" : bool,
"resp_can_change_ens" : bool,
"ens_can_edit_eval" : bool,
"elt_sem_apo" : string,
"elt_annee_apo : string,
}
```
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
args = request.get_json(force=True) # may raise 400 Bad Request
editable_keys = {
"semestre_id",
"titre",
"date_debut",
"date_fin",
"edt_id",
"etat",
"modalite",
"gestion_compensation",
"bul_hide_xml",
"block_moyennes",
"block_moyenne_generale",
"mode_calcul_moyennes",
"gestion_semestrielle",
"bul_bgcolor",
"resp_can_edit",
"resp_can_change_ens",
"ens_can_edit_eval",
"elt_sem_apo",
"elt_annee_apo",
}
formsemestre.from_dict({k: v for (k, v) in args.items() if k in editable_keys})
try:
db.session.commit()
except sa.exc.StatementError as exc:
return json_error(404, f"invalid argument(s): {exc.args[0]}")
return formsemestre.to_dict_api()
@bp.route("/formsemestre/apo/set_etapes", methods=["POST"])
@api_web_bp.route("/formsemestre/apo/set_etapes", methods=["POST"])
@scodoc
@permission_required(Permission.EditApogee)
def formsemestre_set_apo_etapes():
"""Change les codes étapes du semestre indiqué.
2024-07-24 17:34:30 +02:00
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
par des virgules.
Ce changement peut être fait sur un semestre verrouillé.
2024-07-24 17:34:30 +02:00
DATA
----
```json
{
oid : int, le formsemestre_id
value : string, eg "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.get_formsemestre(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 = FormSemestreEtape(
formsemestre_id=formsemestre_id, etape_apo=etape_apo
)
formsemestre.etapes.append(etape)
db.session.add(formsemestre)
db.session.commit()
log(
f"""API formsemestre_set_apo_etapes: formsemestre_id={
formsemestre.id} code_apogee={etapes_apo_str}"""
)
return ("", 204)
@bp.route("/formsemestre/apo/set_elt_sem", methods=["POST"])
@api_web_bp.route("/formsemestre/apo/set_elt_sem", methods=["POST"])
@scodoc
@permission_required(Permission.EditApogee)
def formsemestre_set_elt_sem_apo():
"""Change les codes étapes du semestre indiqué.
2024-07-24 17:34:30 +02:00
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
par des virgules.
2024-07-24 17:34:30 +02:00
Ce changement peut être fait sur un semestre verrouillé.
DATA
----
```json
{
oid : int, le formsemestre_id
value : string, eg "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.get_formsemestre(oid)
if value != formsemestre.elt_sem_apo:
formsemestre.elt_sem_apo = value
db.session.add(formsemestre)
db.session.commit()
log(
f"""API formsemestre_set_elt_sem_apo: formsemestre_id={
formsemestre.id} code_apogee={value}"""
)
return ("", 204)
@bp.route("/formsemestre/apo/set_elt_annee", methods=["POST"])
@api_web_bp.route("/formsemestre/apo/set_elt_annee", methods=["POST"])
@scodoc
@permission_required(Permission.EditApogee)
def formsemestre_set_elt_annee_apo():
"""Change les codes étapes du semestre indiqué (par le champ oid).
2024-07-24 17:34:30 +02:00
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
par des virgules.
2024-07-24 17:34:30 +02:00
Ce changement peut être fait sur un semestre verrouillé.
DATA
----
```json
{
oid : int, le formsemestre_id
value : string, eg "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.get_formsemestre(oid)
if value != formsemestre.elt_annee_apo:
formsemestre.elt_annee_apo = value
db.session.add(formsemestre)
db.session.commit()
log(
f"""API formsemestre_set_elt_annee_apo: formsemestre_id={
formsemestre.id} code_apogee={value}"""
)
return ("", 204)
@bp.route("/formsemestre/apo/set_elt_passage", methods=["POST"])
@api_web_bp.route("/formsemestre/apo/set_elt_passage", methods=["POST"])
@scodoc
@permission_required(Permission.EditApogee)
def formsemestre_set_elt_passage_apo():
"""Change les codes Apogée de passage du semestre indiqué (par le champ oid).
2024-07-24 17:34:30 +02:00
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
par des virgules.
2024-07-24 17:34:30 +02:00
Ce changement peut être fait sur un semestre verrouillé.
DATA
----
```json
{
oid : int, le formsemestre_id
value : string, eg "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.get_formsemestre(oid)
if value != formsemestre.elt_annee_apo:
formsemestre.elt_passage_apo = value
db.session.add(formsemestre)
db.session.commit()
log(
f"""API formsemestre_set_elt_passage_apo: formsemestre_id={
formsemestre.id} code_apogee={value}"""
)
return ("", 204)
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins/<string:version>")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/bulletins/<string:version>")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def bulletins(formsemestre_id: int, version: str = "long"):
"""
2024-07-24 17:34:30 +02:00
Retourne les bulletins d'un formsemestre.
2024-07-24 17:34:30 +02:00
PARAMS
------
formsemestre_id : int
version : string ("long", "short", "selectedevals")
SAMPLES
-------
/formsemestre/1/bulletins
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
app.set_sco_dept(formsemestre.departement.acronym)
data = []
2022-05-03 13:35:17 +02:00
for etu in formsemestre.etuds:
bul_etu = get_formsemestre_bulletin_etud_json(
formsemestre, etu, version=version
)
data.append(bul_etu.json)
return data
@bp.route("/formsemestre/<int:formsemestre_id>/programme")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/programme")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestre_programme(formsemestre_id: int):
"""
Retourne la liste des UEs, ressources et SAEs d'un semestre
SAMPLES
-------
/formsemestre/1/programme
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
ues = formsemestre.get_ues()
m_list = {
ModuleType.RESSOURCE: [],
ModuleType.SAE: [],
ModuleType.STANDARD: [],
ModuleType.MALUS: [],
}
for modimpl in formsemestre.modimpls_sorted:
d = modimpl.to_dict(convert_objects=True)
m_list[modimpl.module.module_type].append(d)
return {
"ues": [ue.to_dict(convert_objects=True) for ue in ues],
"ressources": m_list[ModuleType.RESSOURCE],
"saes": m_list[ModuleType.SAE],
"modules": m_list[ModuleType.STANDARD],
"malus": m_list[ModuleType.MALUS],
}
@bp.route(
"/formsemestre/<int:formsemestre_id>/etudiants",
defaults={"with_query": False, "long": False},
)
@bp.route(
"/formsemestre/<int:formsemestre_id>/etudiants/long",
defaults={"with_query": False, "long": True},
)
@bp.route(
"/formsemestre/<int:formsemestre_id>/etudiants/query",
defaults={"with_query": True, "long": False},
)
@bp.route(
"/formsemestre/<int:formsemestre_id>/etudiants/long/query",
defaults={"with_query": True, "long": True},
)
@api_web_bp.route(
"/formsemestre/<int:formsemestre_id>/etudiants",
defaults={"with_query": False, "long": False},
)
@api_web_bp.route(
"/formsemestre/<int:formsemestre_id>/etudiants/long",
defaults={"with_query": False, "long": True},
)
@api_web_bp.route(
"/formsemestre/<int:formsemestre_id>/etudiants/query",
defaults={"with_query": True, "long": False},
)
@api_web_bp.route(
"/formsemestre/<int:formsemestre_id>/etudiants/long/query",
defaults={"with_query": True, "long": True},
)
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestre_etudiants(
formsemestre_id: int, with_query: bool = False, long: bool = False
):
"""Étudiants d'un formsemestre.
Si l'état est spécifié, ne renvoie que les inscrits (`I`), les
démissionnaires (`D`) ou les défaillants (`DEF`)
QUERY
-----
etat:<string:etat>
SAMPLES
-------
/formsemestre/1/etudiants/query;
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
if with_query:
etat = request.args.get("etat")
if etat is not None:
etat = {
"actifs": scu.INSCRIT,
"demissionnaires": scu.DEMISSION,
"defaillants": scu.DEF,
}.get(etat, etat)
inscriptions = [
ins for ins in formsemestre.inscriptions if ins.etat == etat
]
else:
inscriptions = formsemestre.inscriptions
else:
inscriptions = formsemestre.inscriptions
if long:
2024-01-20 17:37:24 +01:00
restrict = not current_user.has_permission(Permission.ViewEtudData)
etuds = [ins.etud.to_dict_api(restrict=restrict) for ins in inscriptions]
else:
etuds = [ins.etud.to_dict_short() for ins in inscriptions]
# Ajout des groupes de chaque étudiants
# XXX A REVOIR: trop inefficace !
for etud in etuds:
etud["groups"] = sco_groups.get_etud_groups(
etud["id"], formsemestre_id, exclude_default=True
)
return sorted(etuds, key=itemgetter("sort_key"))
2022-05-30 16:08:04 +02:00
@bp.post("/formsemestre/<int:formsemestre_id>/etudid/<int:etudid>/inscrit")
@api_web_bp.post("/formsemestre/<int:formsemestre_id>/etudid/<int:etudid>/inscrit")
@login_required
@scodoc
@permission_required(Permission.EtudInscrit)
@as_json
def formsemestre_etud_inscrit(formsemestre_id: int, etudid: int):
"""Inscrit l'étudiant à ce formsemestre et TOUS ses modules STANDARDS
(donc sauf les modules bonus sport).
DATA
----
```json
{
"dept_id" : int, # le département
"etape" : string, # optionnel: l'étape Apogée d'inscription
"group_ids" : [int], # optionnel: liste des groupes où inscrire l'étudiant (doivent exister)
}
```
"""
data = request.get_json(force=True) if request.data else {}
dept_id = data.get("dept_id", g.scodoc_dept_id)
formsemestre = FormSemestre.get_formsemestre(formsemestre_id, dept_id=dept_id)
app.set_sco_dept(formsemestre.departement.acronym)
etud = Identite.get_etud(etudid)
group_ids = data.get("group_ids", [])
etape = data.get("etape", None)
do_formsemestre_inscription_with_modules(
formsemestre.id, etud.id, dept_id=dept_id, etape=etape, group_ids=group_ids
)
app.log(f"formsemestre_etud_inscrit: {etud} inscrit à {formsemestre}")
return (
FormSemestreInscription.query.filter_by(
formsemestre_id=formsemestre.id, etudid=etud.id
)
.first()
.to_dict()
)
@bp.post("/formsemestre/<int:formsemestre_id>/etudid/<int:etudid>/desinscrit")
@api_web_bp.post("/formsemestre/<int:formsemestre_id>/etudid/<int:etudid>/desinscrit")
@login_required
@scodoc
@permission_required(Permission.EtudInscrit)
@as_json
def formsemestre_etud_desinscrit(formsemestre_id: int, etudid: int):
"""Désinscrit l'étudiant de ce formsemestre et TOUS ses modules"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
app.set_sco_dept(formsemestre.departement.acronym)
etud = Identite.get_etud(etudid)
do_formsemestre_desinscription(etud.id, formsemestre.id)
app.log(f"formsemestre_etud_desinscrit: {etud} désinscrit de {formsemestre}")
return {"status": "ok"}
@bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
2024-07-17 14:58:49 +02:00
def formsemestre_etat_evaluations(formsemestre_id: int):
"""
Informations sur l'état des évaluations d'un formsemestre.
SAMPLES
-------
/formsemestre/1/etat_evals
"""
2024-07-17 14:58:49 +02:00
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
app.set_sco_dept(formsemestre.departement.acronym)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
result = []
for modimpl_id in nt.modimpls_results:
modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl_id]
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(modimpl_id)
modimpl_dict = modimpl.to_dict(convert_objects=True, with_module=False)
list_eval = []
for evaluation_id in modimpl_results.evaluations_etat:
eval_etat = modimpl_results.evaluations_etat[evaluation_id]
evaluation = Evaluation.query.get_or_404(evaluation_id)
eval_dict = evaluation.to_dict_api()
eval_dict["etat"] = eval_etat.to_dict()
eval_dict["nb_inscrits"] = modimpl_results.nb_inscrits_module
eval_dict["nb_notes_manquantes"] = len(
modimpl_results.evals_etudids_sans_note[evaluation.id]
)
eval_dict["nb_notes_abs"] = sum(
modimpl_results.evals_notes[evaluation.id] == scu.NOTES_ABSENCE
)
eval_dict["nb_notes_att"] = eval_etat.nb_attente
eval_dict["nb_notes_exc"] = sum(
modimpl_results.evals_notes[evaluation.id] == scu.NOTES_NEUTRALISE
)
# Récupération de toutes les notes de l'évaluation
# eval["notes"] = modimpl_results.get_eval_notes_dict(evaluation_id)
notes = NotesNotes.query.filter_by(evaluation_id=evaluation.id).all()
date_debut = None
date_fin = None
date_mediane = None
# Si il y a plus d'une note saisie pour l'évaluation
if len(notes) >= 1:
# Tri des notes en fonction de leurs dates
notes_sorted = sorted(notes, key=attrgetter("date"))
date_debut = notes_sorted[0].date
date_fin = notes_sorted[-1].date
# Note médiane
date_mediane = notes_sorted[len(notes_sorted) // 2].date
eval_dict["saisie_notes"] = {
"datetime_debut": (
date_debut.isoformat() if date_debut is not None else None
),
"datetime_fin": date_fin.isoformat() if date_fin is not None else None,
"datetime_mediane": (
date_mediane.isoformat() if date_mediane is not None else None
),
}
list_eval.append(eval_dict)
modimpl_dict["evaluations"] = list_eval
result.append(modimpl_dict)
return result
@bp.route("/formsemestre/<int:formsemestre_id>/resultats")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/resultats")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestre_resultat(formsemestre_id: int):
2024-07-24 17:34:30 +02:00
"""Tableau récapitulatif des résultats.
Pour chaque étudiant, son état, ses groupes, ses moyennes d'UE et de modules.
Si `format=raw`, ne converti pas les valeurs.
QUERY
-----
format:<string:format>
SAMPLES
-------
/formsemestre/1/resultats;
"""
format_spec = request.args.get("format", None)
if format_spec is not None and format_spec != "raw":
return json_error(API_CLIENT_ERROR, "invalid format specification")
convert_values = format_spec != "raw"
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
app.set_sco_dept(formsemestre.departement.acronym)
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
# Ajoute le groupe de chaque partition,
etud_groups = sco_groups.get_formsemestre_etuds_groups(formsemestre_id)
class RowRecapAPI(RowRecap):
"""Pour table avec partitions et sort_key"""
def add_etud_cols(self):
"""Ajoute colonnes étudiant: codes, noms"""
super().add_etud_cols()
self.add_cell("partitions", "partitions", etud_groups.get(self.etud.id, {}))
self.add_cell("sort_key", "sort_key", self.etud.sort_key)
table = TableRecap(
res,
convert_values=convert_values,
include_evaluations=False,
mode_jury=False,
row_class=RowRecapAPI,
)
2023-01-29 21:52:39 +01:00
rows = table.to_list()
# for row in rows:
# row["partitions"] = etud_groups.get(row["etudid"], {})
return rows
2023-05-15 23:39:08 +02:00
2024-07-17 14:58:49 +02:00
@bp.route("/formsemestre/<int:formsemestre_id>/groups_get_auto_assignment")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/groups_get_auto_assignment")
2023-05-15 23:39:08 +02:00
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
2024-07-17 14:58:49 +02:00
def groups_get_auto_assignment(formsemestre_id: int):
2024-07-24 17:34:30 +02:00
"""Rend les données stockées par `groups_save_auto_assignment`."""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
2023-05-15 23:39:08 +02:00
response = make_response(formsemestre.groups_auto_assignment_data or b"")
response.headers["Content-Type"] = scu.JSON_MIMETYPE
return response
@bp.route(
2024-07-17 14:58:49 +02:00
"/formsemestre/<int:formsemestre_id>/groups_save_auto_assignment", methods=["POST"]
2023-05-15 23:39:08 +02:00
)
@api_web_bp.route(
2024-07-17 14:58:49 +02:00
"/formsemestre/<int:formsemestre_id>/groups_save_auto_assignment", methods=["POST"]
2023-05-15 23:39:08 +02:00
)
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
2024-07-17 14:58:49 +02:00
def groups_save_auto_assignment(formsemestre_id: int):
2024-07-24 17:34:30 +02:00
"""Enregistre les données, associées à ce formsemestre.
Usage réservé aux fonctions de gestion des groupes, ne pas utiliser ailleurs.
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
2024-07-24 17:34:30 +02:00
if not formsemestre.can_change_groups():
return json_error(403, "non autorisé (can_change_groups)")
2023-05-15 23:39:08 +02:00
if len(request.data) > GROUPS_AUTO_ASSIGNMENT_DATA_MAX:
return json_error(413, "data too large")
formsemestre.groups_auto_assignment_data = request.data
db.session.add(formsemestre)
db.session.commit()
return {"status": "ok"}
@bp.route("/formsemestre/<int:formsemestre_id>/edt")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/edt")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestre_edt(formsemestre_id: int):
2024-07-24 17:34:30 +02:00
"""L'emploi du temps du semestre.
Si ok, une liste d'évènements. Sinon, une chaine indiquant un message d'erreur.
2023-11-16 23:34:47 +01:00
2024-07-24 17:34:30 +02:00
Expérimental, ne pas utiliser hors ScoDoc.
QUERY
-----
2024-07-24 17:34:30 +02:00
group_ids : string (optionnel) filtre sur les groupes ScoDoc.
show_modules_titles: show_modules_titles affiche le titre complet du module (défaut), sinon juste le code.
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
2023-11-16 23:34:47 +01:00
group_ids = request.args.getlist("group_ids", int)
show_modules_titles = scu.to_bool(request.args.get("show_modules_titles", False))
return sco_edt_cal.formsemestre_edt_dict(
formsemestre, group_ids=group_ids, show_modules_titles=show_modules_titles
)
@bp.route("/formsemestre/<int:formsemestre_id>/description")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/description")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestre_get_description(formsemestre_id: int):
"""Description externe du formsemestre. Peut être vide.
formsemestre_id : l'id du formsemestre
SAMPLES
-------
/formsemestre/1/description
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
return formsemestre.description.to_dict() if formsemestre.description else {}
2024-08-14 10:28:26 +02:00
@bp.post("/formsemestre/<int:formsemestre_id>/description/edit")
@api_web_bp.post("/formsemestre/<int:formsemestre_id>/description/edit")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def formsemestre_edit_description(formsemestre_id: int):
"""Modifie description externe du formsemestre.
Les images peuvent êtres passées dans el json, encodées en base64.
2024-08-14 10:28:26 +02:00
formsemestre_id : l'id du formsemestre
SAMPLES
-------
/formsemestre/<int:formsemestre_id>/description/edit;{""description"":""descriptif du semestre"", ""dispositif"":1}
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
args = request.get_json(force=True) # may raise 400 Bad Request
if not formsemestre.description:
formsemestre.description = FormSemestreDescription()
# Decode images (base64)
for key in ["image", "photo_ens"]:
if key in args:
args[key] = base64.b64decode(args[key])
2024-08-14 10:28:26 +02:00
formsemestre.description.from_dict(args)
db.session.commit()
return formsemestre.description.to_dict()
@bp.route("/formsemestre/<int:formsemestre_id>/description/image")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/description/image")
@login_required
@scodoc
@permission_required(Permission.ScoView)
def formsemestre_get_description_image(formsemestre_id: int):
"""Image de la description externe du formsemestre. Peut être vide.
formsemestre_id : l'id du formsemestre
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
if not formsemestre.description or not formsemestre.description.image:
return make_response("", 204) # 204 No Content status
return _image_response(formsemestre.description.image)
@bp.route("/formsemestre/<int:formsemestre_id>/description/photo_ens")
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/description/photo_ens")
@login_required
@scodoc
@permission_required(Permission.ScoView)
def formsemestre_get_photo_ens(formsemestre_id: int):
"""Photo du responsable, ou illustration du formsemestre. Peut être vide."""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
if not formsemestre.description or not formsemestre.description.photo_ens:
return make_response("", 204) # 204 No Content status
return _image_response(formsemestre.description.photo_ens)
def _image_response(image_data: bytes):
# Guess the mimetype based on the image data
try:
image = PIL.Image.open(io.BytesIO(image_data))
mimetype = image.get_format_mimetype()
except PIL.UnidentifiedImageError:
# Default to binary stream if mimetype cannot be determined
mimetype = "application/octet-stream"
response = make_response(image_data)
response.headers["Content-Type"] = mimetype
return response