forked from ScoDoc/ScoDoc
API: modimpl and formsemestre inscription/desinscription
This commit is contained in:
parent
0533ad59fd
commit
03ba057a87
@ -7,7 +7,7 @@ from flask_json import as_json
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import current_app, g, request
|
from flask import current_app, g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from app import db
|
from app import db, log
|
||||||
from app.decorators import permission_required
|
from app.decorators import permission_required
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoException
|
from app.scodoc.sco_exceptions import AccessDenied, ScoException
|
||||||
@ -47,6 +47,7 @@ def api_permission_required(permission):
|
|||||||
@api_bp.errorhandler(404)
|
@api_bp.errorhandler(404)
|
||||||
def api_error_handler(e):
|
def api_error_handler(e):
|
||||||
"erreurs API => json"
|
"erreurs API => json"
|
||||||
|
log(f"api_error_handler: {e}")
|
||||||
return scu.json_error(404, message=str(e))
|
return scu.json_error(404, message=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,6 +41,10 @@ from app.models import (
|
|||||||
from app.models.formsemestre import GROUPS_AUTO_ASSIGNMENT_DATA_MAX
|
from app.models.formsemestre import GROUPS_AUTO_ASSIGNMENT_DATA_MAX
|
||||||
from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json
|
from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json
|
||||||
from app.scodoc import sco_edt_cal
|
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
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
@ -64,10 +68,7 @@ def formsemestre_get(formsemestre_id: int):
|
|||||||
-------
|
-------
|
||||||
/formsemestre/1
|
/formsemestre/1
|
||||||
"""
|
"""
|
||||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
if g.scodoc_dept:
|
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
|
||||||
return formsemestre.to_dict_api()
|
return formsemestre.to_dict_api()
|
||||||
|
|
||||||
|
|
||||||
@ -400,12 +401,7 @@ def bulletins(formsemestre_id: int, version: str = "long"):
|
|||||||
-------
|
-------
|
||||||
/formsemestre/1/bulletins
|
/formsemestre/1/bulletins
|
||||||
"""
|
"""
|
||||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
if g.scodoc_dept:
|
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
formsemestre: FormSemestre = query.first()
|
|
||||||
if formsemestre is None:
|
|
||||||
return json_error(404, "formsemestre non trouve")
|
|
||||||
app.set_sco_dept(formsemestre.departement.acronym)
|
app.set_sco_dept(formsemestre.departement.acronym)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
@ -432,10 +428,7 @@ def formsemestre_programme(formsemestre_id: int):
|
|||||||
-------
|
-------
|
||||||
/formsemestre/1/programme
|
/formsemestre/1/programme
|
||||||
"""
|
"""
|
||||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
if g.scodoc_dept:
|
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
|
||||||
ues = formsemestre.get_ues()
|
ues = formsemestre.get_ues()
|
||||||
m_list = {
|
m_list = {
|
||||||
ModuleType.RESSOURCE: [],
|
ModuleType.RESSOURCE: [],
|
||||||
@ -508,10 +501,7 @@ def formsemestre_etudiants(
|
|||||||
/formsemestre/1/etudiants/query;
|
/formsemestre/1/etudiants/query;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
if g.scodoc_dept:
|
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
|
||||||
if with_query:
|
if with_query:
|
||||||
etat = request.args.get("etat")
|
etat = request.args.get("etat")
|
||||||
if etat is not None:
|
if etat is not None:
|
||||||
@ -543,6 +533,63 @@ def formsemestre_etudiants(
|
|||||||
return sorted(etuds, key=itemgetter("sort_key"))
|
return sorted(etuds, key=itemgetter("sort_key"))
|
||||||
|
|
||||||
|
|
||||||
|
@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")
|
@bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||||
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||||
@login_required
|
@login_required
|
||||||
@ -649,10 +696,7 @@ def formsemestre_resultat(formsemestre_id: int):
|
|||||||
return json_error(API_CLIENT_ERROR, "invalid format specification")
|
return json_error(API_CLIENT_ERROR, "invalid format specification")
|
||||||
convert_values = format_spec != "raw"
|
convert_values = format_spec != "raw"
|
||||||
|
|
||||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
if g.scodoc_dept:
|
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
|
||||||
app.set_sco_dept(formsemestre.departement.acronym)
|
app.set_sco_dept(formsemestre.departement.acronym)
|
||||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
# Ajoute le groupe de chaque partition,
|
# Ajoute le groupe de chaque partition,
|
||||||
@ -690,10 +734,7 @@ def formsemestre_resultat(formsemestre_id: int):
|
|||||||
@as_json
|
@as_json
|
||||||
def groups_get_auto_assignment(formsemestre_id: int):
|
def groups_get_auto_assignment(formsemestre_id: int):
|
||||||
"""Rend les données stockées par `groups_save_auto_assignment`."""
|
"""Rend les données stockées par `groups_save_auto_assignment`."""
|
||||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
if g.scodoc_dept:
|
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
|
||||||
response = make_response(formsemestre.groups_auto_assignment_data or b"")
|
response = make_response(formsemestre.groups_auto_assignment_data or b"")
|
||||||
response.headers["Content-Type"] = scu.JSON_MIMETYPE
|
response.headers["Content-Type"] = scu.JSON_MIMETYPE
|
||||||
return response
|
return response
|
||||||
@ -713,11 +754,7 @@ def groups_save_auto_assignment(formsemestre_id: int):
|
|||||||
"""Enregistre les données, associées à ce formsemestre.
|
"""Enregistre les données, associées à ce formsemestre.
|
||||||
Usage réservé aux fonctions de gestion des groupes, ne pas utiliser ailleurs.
|
Usage réservé aux fonctions de gestion des groupes, ne pas utiliser ailleurs.
|
||||||
"""
|
"""
|
||||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
if g.scodoc_dept:
|
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
|
||||||
|
|
||||||
if not formsemestre.can_change_groups():
|
if not formsemestre.can_change_groups():
|
||||||
return json_error(403, "non autorisé (can_change_groups)")
|
return json_error(403, "non autorisé (can_change_groups)")
|
||||||
|
|
||||||
@ -726,6 +763,7 @@ def groups_save_auto_assignment(formsemestre_id: int):
|
|||||||
formsemestre.groups_auto_assignment_data = request.data
|
formsemestre.groups_auto_assignment_data = request.data
|
||||||
db.session.add(formsemestre)
|
db.session.add(formsemestre)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>/edt")
|
@bp.route("/formsemestre/<int:formsemestre_id>/edt")
|
||||||
@ -746,10 +784,7 @@ def formsemestre_edt(formsemestre_id: int):
|
|||||||
group_ids : string (optionnel) filtre sur les groupes ScoDoc.
|
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.
|
show_modules_titles: show_modules_titles affiche le titre complet du module (défaut), sinon juste le code.
|
||||||
"""
|
"""
|
||||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
if g.scodoc_dept:
|
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
|
||||||
group_ids = request.args.getlist("group_ids", int)
|
group_ids = request.args.getlist("group_ids", int)
|
||||||
show_modules_titles = scu.to_bool(request.args.get("show_modules_titles", False))
|
show_modules_titles = scu.to_bool(request.args.get("show_modules_titles", False))
|
||||||
return sco_edt_cal.formsemestre_edt_dict(
|
return sco_edt_cal.formsemestre_edt_dict(
|
||||||
|
@ -16,12 +16,15 @@ from flask_json import as_json
|
|||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
|
|
||||||
import app
|
import app
|
||||||
|
from app import db
|
||||||
from app.api import api_bp as bp, api_web_bp
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.api import api_permission_required as permission_required
|
from app.api import api_permission_required as permission_required
|
||||||
from app.decorators import scodoc
|
from app.decorators import scodoc
|
||||||
from app.models import ModuleImpl
|
from app.models import Identite, ModuleImpl, ModuleImplInscription
|
||||||
from app.scodoc import sco_liste_notes
|
from app.scodoc import sco_cache, sco_liste_notes
|
||||||
|
from app.scodoc.sco_moduleimpl import do_moduleimpl_inscrit_etuds
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
from app.scodoc.sco_utils import json_error
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/moduleimpl/<int:moduleimpl_id>")
|
@bp.route("/moduleimpl/<int:moduleimpl_id>")
|
||||||
@ -63,6 +66,60 @@ def moduleimpl_inscriptions(moduleimpl_id: int):
|
|||||||
return [i.to_dict() for i in modimpl.inscriptions]
|
return [i.to_dict() for i in modimpl.inscriptions]
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post("/moduleimpl/<int:moduleimpl_id>/etudid/<int:etudid>/inscrit")
|
||||||
|
@api_web_bp.post("/moduleimpl/<int:moduleimpl_id>/etudid/<int:etudid>/inscrit")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
@as_json
|
||||||
|
def moduleimpl_etud_inscrit(moduleimpl_id: int, etudid: int):
|
||||||
|
"""Inscrit l'étudiant à ce moduleimpl.
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/moduleimpl/1/etudid/2/inscrit
|
||||||
|
"""
|
||||||
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||||
|
if not modimpl.can_change_inscriptions():
|
||||||
|
return json_error(403, "opération non autorisée")
|
||||||
|
etud = Identite.get_etud(etudid)
|
||||||
|
do_moduleimpl_inscrit_etuds(modimpl.id, modimpl.formsemestre_id, [etud.id])
|
||||||
|
app.log(f"moduleimpl_etud_inscrit: {etud} inscrit à {modimpl}")
|
||||||
|
return (
|
||||||
|
ModuleImplInscription.query.filter_by(moduleimpl_id=modimpl.id, etudid=etud.id)
|
||||||
|
.first()
|
||||||
|
.to_dict()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post("/moduleimpl/<int:moduleimpl_id>/etudid/<int:etudid>/desinscrit")
|
||||||
|
@api_web_bp.post("/moduleimpl/<int:moduleimpl_id>/etudid/<int:etudid>/desinscrit")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
@as_json
|
||||||
|
def moduleimpl_etud_desinscrit(moduleimpl_id: int, etudid: int):
|
||||||
|
"""Désinscrit l'étudiant de ce moduleimpl.
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/moduleimpl/1/etudid/2/desinscrit
|
||||||
|
"""
|
||||||
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||||
|
if not modimpl.can_change_inscriptions():
|
||||||
|
return json_error(403, "opération non autorisée")
|
||||||
|
etud = Identite.get_etud(etudid)
|
||||||
|
inscription = ModuleImplInscription.query.filter_by(
|
||||||
|
etudid=etud.id, moduleimpl_id=modimpl.id
|
||||||
|
).first()
|
||||||
|
if inscription:
|
||||||
|
db.session.delete(inscription)
|
||||||
|
db.session.commit()
|
||||||
|
sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id)
|
||||||
|
app.log(f"moduleimpl_etud_desinscrit: {etud} inscrit à {modimpl}")
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/moduleimpl/<int:moduleimpl_id>/notes")
|
@bp.route("/moduleimpl/<int:moduleimpl_id>/notes")
|
||||||
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>/notes")
|
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>/notes")
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -111,6 +111,12 @@ class ScoDocModel(db.Model):
|
|||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
return modified
|
return modified
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"dict"
|
||||||
|
d = dict(self.__dict__)
|
||||||
|
d.pop("_sa_instance_state", None)
|
||||||
|
return d
|
||||||
|
|
||||||
def edit_from_form(self, form) -> bool:
|
def edit_from_form(self, form) -> bool:
|
||||||
"""Generic edit method for updating model instance.
|
"""Generic edit method for updating model instance.
|
||||||
True if modification.
|
True if modification.
|
||||||
|
@ -1318,7 +1318,7 @@ notes_formsemestre_responsables = db.Table(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FormSemestreEtape(db.Model):
|
class FormSemestreEtape(models.ScoDocModel):
|
||||||
"""Étape Apogée associée au semestre"""
|
"""Étape Apogée associée au semestre"""
|
||||||
|
|
||||||
__tablename__ = "notes_formsemestre_etapes"
|
__tablename__ = "notes_formsemestre_etapes"
|
||||||
@ -1349,7 +1349,7 @@ class FormSemestreEtape(db.Model):
|
|||||||
return ApoEtapeVDI(self.etape_apo)
|
return ApoEtapeVDI(self.etape_apo)
|
||||||
|
|
||||||
|
|
||||||
class FormationModalite(db.Model):
|
class FormationModalite(models.ScoDocModel):
|
||||||
"""Modalités de formation, utilisées pour la présentation
|
"""Modalités de formation, utilisées pour la présentation
|
||||||
(grouper les semestres, générer des codes, etc.)
|
(grouper les semestres, générer des codes, etc.)
|
||||||
"""
|
"""
|
||||||
@ -1400,7 +1400,7 @@ class FormationModalite(db.Model):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
class FormSemestreUECoef(db.Model):
|
class FormSemestreUECoef(models.ScoDocModel):
|
||||||
"""Coef des UE capitalisees arrivant dans ce semestre"""
|
"""Coef des UE capitalisees arrivant dans ce semestre"""
|
||||||
|
|
||||||
__tablename__ = "notes_formsemestre_uecoef"
|
__tablename__ = "notes_formsemestre_uecoef"
|
||||||
@ -1441,7 +1441,7 @@ class FormSemestreUEComputationExpr(db.Model):
|
|||||||
computation_expr = db.Column(db.Text())
|
computation_expr = db.Column(db.Text())
|
||||||
|
|
||||||
|
|
||||||
class FormSemestreCustomMenu(db.Model):
|
class FormSemestreCustomMenu(models.ScoDocModel):
|
||||||
"""Menu custom associe au semestre"""
|
"""Menu custom associe au semestre"""
|
||||||
|
|
||||||
__tablename__ = "notes_formsemestre_custommenu"
|
__tablename__ = "notes_formsemestre_custommenu"
|
||||||
@ -1457,7 +1457,7 @@ class FormSemestreCustomMenu(db.Model):
|
|||||||
idx = db.Column(db.Integer, default=0, server_default="0") # rang dans le menu
|
idx = db.Column(db.Integer, default=0, server_default="0") # rang dans le menu
|
||||||
|
|
||||||
|
|
||||||
class FormSemestreInscription(db.Model):
|
class FormSemestreInscription(models.ScoDocModel):
|
||||||
"""Inscription à un semestre de formation"""
|
"""Inscription à un semestre de formation"""
|
||||||
|
|
||||||
__tablename__ = "notes_formsemestre_inscription"
|
__tablename__ = "notes_formsemestre_inscription"
|
||||||
@ -1503,7 +1503,7 @@ class FormSemestreInscription(db.Model):
|
|||||||
} {('etape="'+self.etape+'"') if self.etape else ''}>"""
|
} {('etape="'+self.etape+'"') if self.etape else ''}>"""
|
||||||
|
|
||||||
|
|
||||||
class NotesSemSet(db.Model):
|
class NotesSemSet(models.ScoDocModel):
|
||||||
"""semsets: ensemble de formsemestres pour exports Apogée"""
|
"""semsets: ensemble de formsemestres pour exports Apogée"""
|
||||||
|
|
||||||
__tablename__ = "notes_semset"
|
__tablename__ = "notes_semset"
|
||||||
|
@ -325,7 +325,7 @@ notes_modules_enseignants = db.Table(
|
|||||||
# XXX il manque probablement une relation pour gérer cela
|
# XXX il manque probablement une relation pour gérer cela
|
||||||
|
|
||||||
|
|
||||||
class ModuleImplInscription(db.Model):
|
class ModuleImplInscription(ScoDocModel):
|
||||||
"""Inscription à un module (etudiants,moduleimpl)"""
|
"""Inscription à un module (etudiants,moduleimpl)"""
|
||||||
|
|
||||||
__tablename__ = "notes_moduleimpl_inscription"
|
__tablename__ = "notes_moduleimpl_inscription"
|
||||||
|
@ -56,7 +56,7 @@ class BulAppreciations(models.ScoDocModel):
|
|||||||
return safehtml.html_to_safe_html(self.comment or "")
|
return safehtml.html_to_safe_html(self.comment or "")
|
||||||
|
|
||||||
|
|
||||||
class NotesNotes(db.Model):
|
class NotesNotes(models.ScoDocModel):
|
||||||
"""Une note"""
|
"""Une note"""
|
||||||
|
|
||||||
__tablename__ = "notes_notes"
|
__tablename__ = "notes_notes"
|
||||||
@ -75,12 +75,6 @@ class NotesNotes(db.Model):
|
|||||||
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
uid = db.Column(db.Integer, db.ForeignKey("user.id"))
|
uid = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
|
||||||
"dict"
|
|
||||||
d = dict(self.__dict__)
|
|
||||||
d.pop("_sa_instance_state", None)
|
|
||||||
return d
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"pour debug"
|
"pour debug"
|
||||||
from app.models.evaluations import Evaluation
|
from app.models.evaluations import Evaluation
|
||||||
|
@ -287,6 +287,8 @@ def do_formsemestre_inscription_with_modules(
|
|||||||
group_ids = group_ids or []
|
group_ids = group_ids or []
|
||||||
if isinstance(group_ids, int):
|
if isinstance(group_ids, int):
|
||||||
group_ids = [group_ids]
|
group_ids = [group_ids]
|
||||||
|
# Check that all groups exist before creating the inscription
|
||||||
|
groups = [GroupDescr.query.get_or_404(group_id) for group_id in group_ids]
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id, dept_id=dept_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id, dept_id=dept_id)
|
||||||
# inscription au semestre
|
# inscription au semestre
|
||||||
args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
|
args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
|
||||||
@ -303,14 +305,13 @@ def do_formsemestre_inscription_with_modules(
|
|||||||
# 1- inscrit au groupe 'tous'
|
# 1- inscrit au groupe 'tous'
|
||||||
group_id = sco_groups.get_default_group(formsemestre_id)
|
group_id = sco_groups.get_default_group(formsemestre_id)
|
||||||
sco_groups.set_group(etudid, group_id)
|
sco_groups.set_group(etudid, group_id)
|
||||||
gdone = {group_id: 1} # empeche doublons
|
gdone = {group_id} # empeche doublons
|
||||||
|
|
||||||
# 2- inscrit aux groupes
|
# 2- inscrit aux groupes
|
||||||
for group_id in group_ids:
|
for group in groups:
|
||||||
if group_id and group_id not in gdone:
|
if group.id not in gdone:
|
||||||
_ = GroupDescr.query.get_or_404(group_id)
|
|
||||||
sco_groups.set_group(etudid, group_id)
|
sco_groups.set_group(etudid, group_id)
|
||||||
gdone[group_id] = 1
|
gdone.add(group_id)
|
||||||
|
|
||||||
# Inscription à tous les modules de ce semestre
|
# Inscription à tous les modules de ce semestre
|
||||||
for modimpl in formsemestre.modimpls:
|
for modimpl in formsemestre.modimpls:
|
||||||
|
@ -133,14 +133,15 @@ def moduleimpl_inscriptions_edit(
|
|||||||
|
|
||||||
if (partitionIdx==-1) {
|
if (partitionIdx==-1) {
|
||||||
for (var i =nb_inputs_to_skip; i < elems.length; i++) {
|
for (var i =nb_inputs_to_skip; i < elems.length; i++) {
|
||||||
elems[i].checked=check;
|
elems[i].checked=check;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (var i =nb_inputs_to_skip; i < elems.length; i++) {
|
for (var i =nb_inputs_to_skip; i < elems.length; i++) {
|
||||||
var cells = elems[i].parentNode.parentNode.getElementsByTagName("td")[partitionIdx].childNodes;
|
let tds = elems[i].parentNode.parentNode.getElementsByTagName("td");
|
||||||
if (cells.length && cells[0].nodeValue == groupName) {
|
var cells = tds[partitionIdx].childNodes;
|
||||||
elems[i].checked=check;
|
if (cells.length && cells[0].nodeValue == groupName) {
|
||||||
}
|
elems[i].checked=check;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,19 +180,19 @@ def moduleimpl_inscriptions_edit(
|
|||||||
else:
|
else:
|
||||||
checked = ""
|
checked = ""
|
||||||
H.append(
|
H.append(
|
||||||
f"""<tr><td class="etud"><input type="checkbox" name="etudids:list" value="{etud['etudid']}" {checked}>"""
|
f"""<tr><td class="etud">
|
||||||
|
<input type="checkbox" name="etudids:list" value="{etud['etudid']}" {checked}>
|
||||||
|
<a class="discretelink etudinfo" href="{
|
||||||
|
url_for(
|
||||||
|
"scolar.fiche_etud",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
etudid=etud["etudid"],
|
||||||
|
)
|
||||||
|
}" id="{etud['etudid']}">{etud['nomprenom']}</a>
|
||||||
|
</input>
|
||||||
|
</td>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
H.append(
|
|
||||||
f"""<a class="discretelink etudinfo" href="{
|
|
||||||
url_for(
|
|
||||||
"scolar.fiche_etud",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
etudid=etud["etudid"],
|
|
||||||
)
|
|
||||||
}" id="{etud['etudid']}">{etud['nomprenom']}</a>"""
|
|
||||||
)
|
|
||||||
H.append("""</input></td>""")
|
|
||||||
|
|
||||||
groups = sco_groups.get_etud_groups(etud["etudid"], formsemestre.id)
|
groups = sco_groups.get_etud_groups(etud["etudid"], formsemestre.id)
|
||||||
for partition in partitions:
|
for partition in partitions:
|
||||||
if partition["partition_name"]:
|
if partition["partition_name"]:
|
||||||
|
@ -56,6 +56,9 @@ class APIError(Exception):
|
|||||||
self.message = message
|
self.message = message
|
||||||
self.payload = payload or {}
|
self.payload = payload or {}
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"APIError: {self.message} payload={self.payload}"
|
||||||
|
|
||||||
|
|
||||||
def get_auth_headers(user, password) -> dict:
|
def get_auth_headers(user, password) -> dict:
|
||||||
"Demande de jeton, dict à utiliser dans les en-têtes de requêtes http"
|
"Demande de jeton, dict à utiliser dans les en-têtes de requêtes http"
|
||||||
@ -130,11 +133,17 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None, raw=False):
|
|||||||
|
|
||||||
|
|
||||||
def POST(
|
def POST(
|
||||||
path: str, data: dict = {}, headers: dict = None, errmsg=None, dept=None, raw=False
|
path: str,
|
||||||
|
data: dict = None,
|
||||||
|
headers: dict = None,
|
||||||
|
errmsg=None,
|
||||||
|
dept=None,
|
||||||
|
raw=False,
|
||||||
):
|
):
|
||||||
"""Post
|
"""Post
|
||||||
Decode réponse en json, sauf si raw.
|
Decode réponse en json, sauf si raw.
|
||||||
"""
|
"""
|
||||||
|
data = data or {}
|
||||||
if dept:
|
if dept:
|
||||||
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
|
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
|
||||||
else:
|
else:
|
||||||
@ -147,7 +156,13 @@ def POST(
|
|||||||
timeout=SCO_TEST_API_TIMEOUT,
|
timeout=SCO_TEST_API_TIMEOUT,
|
||||||
)
|
)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise APIError(errmsg or f"erreur status={r.status_code} !", r.json())
|
try:
|
||||||
|
payload = r.json()
|
||||||
|
except requests.exceptions.JSONDecodeError:
|
||||||
|
payload = r.text
|
||||||
|
raise APIError(
|
||||||
|
errmsg or f"erreur url={url} status={r.status_code} !", payload=payload
|
||||||
|
)
|
||||||
return r if raw else r.json() # decode la reponse JSON
|
return r if raw else r.json() # decode la reponse JSON
|
||||||
|
|
||||||
|
|
||||||
|
51
tests/api/test_api_exceptions.py
Normal file
51
tests/api/test_api_exceptions.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""Test API exceptions
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from tests.api.setup_test_api import (
|
||||||
|
API_URL,
|
||||||
|
CHECK_CERTIFICATE,
|
||||||
|
api_headers,
|
||||||
|
)
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
|
def test_exceptions(api_headers):
|
||||||
|
"""
|
||||||
|
Vérifie que les exceptions de l'API sont toutes en JSON.
|
||||||
|
"""
|
||||||
|
# Une requete sur une url inexistante ne passe pas par les blueprints API
|
||||||
|
# et est donc en HTML
|
||||||
|
r = requests.get(
|
||||||
|
f"{API_URL}/mmm/non/existant/mmm",
|
||||||
|
headers=api_headers,
|
||||||
|
verify=CHECK_CERTIFICATE,
|
||||||
|
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||||
|
)
|
||||||
|
assert r.status_code == 404
|
||||||
|
assert r.headers["Content-Type"] == "text/html; charset=utf-8"
|
||||||
|
|
||||||
|
# Une requete d'un objet non existant est en JSON
|
||||||
|
r = requests.get(
|
||||||
|
f"{API_URL}/formsemestre/999999",
|
||||||
|
headers=api_headers,
|
||||||
|
verify=CHECK_CERTIFICATE,
|
||||||
|
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||||
|
)
|
||||||
|
assert r.status_code == 404
|
||||||
|
assert r.headers["Content-Type"] == "application/json"
|
||||||
|
assert r.json()
|
||||||
|
|
||||||
|
# Une requête API sans autorisation est en JSON
|
||||||
|
r = requests.post(
|
||||||
|
f"{API_URL}/formsemestre/1/etudid/1/inscrit",
|
||||||
|
headers=api_headers,
|
||||||
|
verify=CHECK_CERTIFICATE,
|
||||||
|
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||||
|
)
|
||||||
|
assert r.status_code == 401
|
||||||
|
assert r.headers["Content-Type"] == "application/json"
|
||||||
|
assert r.json()
|
@ -29,6 +29,7 @@ from tests.api.setup_test_api import (
|
|||||||
CHECK_CERTIFICATE,
|
CHECK_CERTIFICATE,
|
||||||
GET,
|
GET,
|
||||||
api_headers,
|
api_headers,
|
||||||
|
api_admin_headers,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.api.tools_test_api import (
|
from tests.api.tools_test_api import (
|
||||||
@ -585,6 +586,46 @@ def test_formsemestre_etudiants(api_headers):
|
|||||||
assert r_error_defaillants.status_code == 404
|
assert r_error_defaillants.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_formsemestre_inscriptions(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Route: /formsemestre/<int:formsemestre_id>/etudid/<int:etudid>/inscrit
|
||||||
|
"""
|
||||||
|
dept_id = 1
|
||||||
|
formsemestre_id = 1
|
||||||
|
etudid = 20 # pas déjà inscrit au semestre 1
|
||||||
|
# -- Inscription
|
||||||
|
r = requests.post(
|
||||||
|
f"{API_URL}/formsemestre/{formsemestre_id}/etudid/{etudid}/inscrit",
|
||||||
|
data=json.dumps({"dept_id": dept_id}),
|
||||||
|
headers=api_admin_headers,
|
||||||
|
verify=CHECK_CERTIFICATE,
|
||||||
|
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
inscription = r.json()
|
||||||
|
assert inscription["formsemestre_id"] == formsemestre_id
|
||||||
|
assert inscription["etudid"] == etudid
|
||||||
|
assert inscription["etat"] == "I"
|
||||||
|
# -- Désincription
|
||||||
|
r = requests.post(
|
||||||
|
f"{API_URL}/formsemestre/{formsemestre_id}/etudid/{etudid}/desinscrit",
|
||||||
|
headers=api_admin_headers,
|
||||||
|
verify=CHECK_CERTIFICATE,
|
||||||
|
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
### ERROR ###
|
||||||
|
etudid_inexistant = 165165165165165165165
|
||||||
|
r_error = requests.post(
|
||||||
|
f"{API_URL}/formsemestre/{formsemestre_id}/etudid/{etudid_inexistant}/inscrit",
|
||||||
|
headers=api_admin_headers,
|
||||||
|
verify=CHECK_CERTIFICATE,
|
||||||
|
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||||
|
)
|
||||||
|
assert r_error.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_formsemestre_programme(api_headers):
|
def test_formsemestre_programme(api_headers):
|
||||||
"""
|
"""
|
||||||
Route: /formsemestre/1/programme
|
Route: /formsemestre/1/programme
|
||||||
|
@ -968,6 +968,8 @@ def gen_api_doc(app, endpoint_start="api."):
|
|||||||
with open(fname, "w", encoding="utf-8") as f:
|
with open(fname, "w", encoding="utf-8") as f:
|
||||||
f.write(mdpage)
|
f.write(mdpage)
|
||||||
print(
|
print(
|
||||||
"La documentation API a été générée avec succès. "
|
f"""La documentation API a été générée avec succès.
|
||||||
f"Vous pouvez la consulter à l'adresse suivante : {fname}"
|
Vous pouvez la consulter à l'adresse suivante : {fname}.
|
||||||
|
Vous pouvez maintenant générer les samples avec `tools/test_api.sh --make-samples`.
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user