forked from ScoDoc/ScoDoc
Compare commits
10 Commits
af7b5b01fb
...
6b8667522b
Author | SHA1 | Date | |
---|---|---|---|
6b8667522b | |||
37dcdca65b | |||
7c7d128b57 | |||
646c30bb21 | |||
055839757b | |||
6eb3921774 | |||
b2443a2c69 | |||
0df77b20fb | |||
6d922adb7e | |||
bf6cad9d77 |
@ -28,6 +28,9 @@ from flask_migrate import Migrate
|
|||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
from jinja2 import select_autoescape
|
from jinja2 import select_autoescape
|
||||||
|
import numpy as np
|
||||||
|
import psycopg2
|
||||||
|
from psycopg2.extensions import AsIs as psycopg2_AsIs
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import werkzeug.debug
|
import werkzeug.debug
|
||||||
from wtforms.fields import HiddenField
|
from wtforms.fields import HiddenField
|
||||||
@ -68,6 +71,19 @@ cache = Cache(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# NumPy & Psycopg2 (necessary with Numpy 2.0)
|
||||||
|
# probablement à changer quand on passera à psycopg3.2
|
||||||
|
def adapt_numpy_scalar(numpy_scalar):
|
||||||
|
"""Adapt numeric types for psycopg2"""
|
||||||
|
return psycopg2_AsIs(numpy_scalar if not np.isnan(numpy_scalar) else "'NaN'")
|
||||||
|
|
||||||
|
|
||||||
|
psycopg2.extensions.register_adapter(np.float32, adapt_numpy_scalar)
|
||||||
|
psycopg2.extensions.register_adapter(np.float64, adapt_numpy_scalar)
|
||||||
|
psycopg2.extensions.register_adapter(np.int32, adapt_numpy_scalar)
|
||||||
|
psycopg2.extensions.register_adapter(np.int64, adapt_numpy_scalar)
|
||||||
|
|
||||||
|
|
||||||
def handle_sco_value_error(exc):
|
def handle_sco_value_error(exc):
|
||||||
"page d'erreur avec message"
|
"page d'erreur avec message"
|
||||||
return render_template("sco_value_error.j2", exc=exc), 404
|
return render_template("sco_value_error.j2", exc=exc), 404
|
||||||
|
@ -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(
|
||||||
|
@ -195,7 +195,7 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération du département et des étudiants du département
|
# Récupération du département et des étudiants du département
|
||||||
dept: Departement = Departement.query.get(dept_id)
|
dept: Departement = db.session.get(Departement, dept_id)
|
||||||
if dept is None:
|
if dept is None:
|
||||||
return json_error(404, "Assiduité non existante")
|
return json_error(404, "Assiduité non existante")
|
||||||
etuds: list[int] = [etud.id for etud in dept.etudiants]
|
etuds: list[int] = [etud.id for etud in dept.etudiants]
|
||||||
@ -765,7 +765,7 @@ def justif_export(justif_id: int | None = None, filename: str | None = None):
|
|||||||
current_user.has_permission(Permission.AbsJustifView)
|
current_user.has_permission(Permission.AbsJustifView)
|
||||||
or justificatif_unique.user_id == current_user.id
|
or justificatif_unique.user_id == current_user.id
|
||||||
):
|
):
|
||||||
return json_error(401, "non autorisé à voir ce fichier")
|
return json_error(403, "non autorisé à voir ce fichier")
|
||||||
|
|
||||||
# On récupère l'archive concernée
|
# On récupère l'archive concernée
|
||||||
archive_name: str = justificatif_unique.fichier
|
archive_name: str = justificatif_unique.fichier
|
||||||
|
@ -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
|
||||||
|
@ -169,7 +169,7 @@ def group_set_etudiant(group_id: int, etudid: int):
|
|||||||
if not group.partition.formsemestre.etat:
|
if not group.partition.formsemestre.etat:
|
||||||
return json_error(403, "formsemestre verrouillé")
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not group.partition.formsemestre.can_change_groups():
|
if not group.partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
if etud.id not in {e.id for e in group.partition.formsemestre.etuds}:
|
if etud.id not in {e.id for e in group.partition.formsemestre.etuds}:
|
||||||
return json_error(404, "etud non inscrit au formsemestre du groupe")
|
return json_error(404, "etud non inscrit au formsemestre du groupe")
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ def group_remove_etud(group_id: int, etudid: int):
|
|||||||
if not group.partition.formsemestre.etat:
|
if not group.partition.formsemestre.etat:
|
||||||
return json_error(403, "formsemestre verrouillé")
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not group.partition.formsemestre.can_change_groups():
|
if not group.partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
|
|
||||||
group.remove_etud(etud)
|
group.remove_etud(etud)
|
||||||
|
|
||||||
@ -232,7 +232,7 @@ def partition_remove_etud(partition_id: int, etudid: int):
|
|||||||
if not partition.formsemestre.etat:
|
if not partition.formsemestre.etat:
|
||||||
return json_error(403, "formsemestre verrouillé")
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not partition.formsemestre.can_change_groups():
|
if not partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
db.session.execute(
|
db.session.execute(
|
||||||
sa.text(
|
sa.text(
|
||||||
"""DELETE FROM group_membership
|
"""DELETE FROM group_membership
|
||||||
@ -289,7 +289,7 @@ def group_create(partition_id: int): # partition-group-create
|
|||||||
if not partition.groups_editable:
|
if not partition.groups_editable:
|
||||||
return json_error(403, "partition non editable")
|
return json_error(403, "partition non editable")
|
||||||
if not partition.formsemestre.can_change_groups():
|
if not partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
|
|
||||||
args = request.get_json(force=True) # may raise 400 Bad Request
|
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
group_name = args.get("group_name")
|
group_name = args.get("group_name")
|
||||||
@ -337,7 +337,7 @@ def group_delete(group_id: int):
|
|||||||
if not group.partition.groups_editable:
|
if not group.partition.groups_editable:
|
||||||
return json_error(403, "partition non editable")
|
return json_error(403, "partition non editable")
|
||||||
if not group.partition.formsemestre.can_change_groups():
|
if not group.partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
formsemestre_id = group.partition.formsemestre_id
|
formsemestre_id = group.partition.formsemestre_id
|
||||||
log(f"deleting {group}")
|
log(f"deleting {group}")
|
||||||
db.session.delete(group)
|
db.session.delete(group)
|
||||||
@ -378,7 +378,7 @@ def group_edit(group_id: int):
|
|||||||
if not group.partition.groups_editable:
|
if not group.partition.groups_editable:
|
||||||
return json_error(403, "partition non editable")
|
return json_error(403, "partition non editable")
|
||||||
if not group.partition.formsemestre.can_change_groups():
|
if not group.partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
|
|
||||||
args = request.get_json(force=True) # may raise 400 Bad Request
|
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
if "group_name" in args:
|
if "group_name" in args:
|
||||||
@ -423,7 +423,7 @@ def group_set_edt_id(group_id: int, edt_id: str):
|
|||||||
)
|
)
|
||||||
group: GroupDescr = query.first_or_404()
|
group: GroupDescr = query.first_or_404()
|
||||||
if not group.partition.formsemestre.can_change_groups():
|
if not group.partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
log(f"group_set_edt_id( {group_id}, '{edt_id}' )")
|
log(f"group_set_edt_id( {group_id}, '{edt_id}' )")
|
||||||
group.edt_id = edt_id
|
group.edt_id = edt_id
|
||||||
db.session.add(group)
|
db.session.add(group)
|
||||||
@ -461,7 +461,7 @@ def partition_create(formsemestre_id: int):
|
|||||||
if not formsemestre.etat:
|
if not formsemestre.etat:
|
||||||
return json_error(403, "formsemestre verrouillé")
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not formsemestre.can_change_groups():
|
if not formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
partition_name = data.get("partition_name")
|
partition_name = data.get("partition_name")
|
||||||
if partition_name is None:
|
if partition_name is None:
|
||||||
@ -523,7 +523,7 @@ def formsemestre_set_partitions_order(formsemestre_id: int):
|
|||||||
if not formsemestre.etat:
|
if not formsemestre.etat:
|
||||||
return json_error(403, "formsemestre verrouillé")
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not formsemestre.can_change_groups():
|
if not formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
partition_ids = request.get_json(force=True) # may raise 400 Bad Request
|
partition_ids = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
if not isinstance(partition_ids, list) and not all(
|
if not isinstance(partition_ids, list) and not all(
|
||||||
isinstance(x, int) for x in partition_ids
|
isinstance(x, int) for x in partition_ids
|
||||||
@ -569,7 +569,7 @@ def partition_order_groups(partition_id: int):
|
|||||||
if not partition.formsemestre.etat:
|
if not partition.formsemestre.etat:
|
||||||
return json_error(403, "formsemestre verrouillé")
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not partition.formsemestre.can_change_groups():
|
if not partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
group_ids = request.get_json(force=True) # may raise 400 Bad Request
|
group_ids = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
if not isinstance(group_ids, list) and not all(
|
if not isinstance(group_ids, list) and not all(
|
||||||
isinstance(x, int) for x in group_ids
|
isinstance(x, int) for x in group_ids
|
||||||
@ -623,7 +623,7 @@ def partition_edit(partition_id: int):
|
|||||||
if not partition.formsemestre.etat:
|
if not partition.formsemestre.etat:
|
||||||
return json_error(403, "formsemestre verrouillé")
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not partition.formsemestre.can_change_groups():
|
if not partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
modified = False
|
modified = False
|
||||||
partition_name = data.get("partition_name")
|
partition_name = data.get("partition_name")
|
||||||
@ -689,7 +689,7 @@ def partition_delete(partition_id: int):
|
|||||||
if not partition.formsemestre.etat:
|
if not partition.formsemestre.etat:
|
||||||
return json_error(403, "formsemestre verrouillé")
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not partition.formsemestre.can_change_groups():
|
if not partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(403, "opération non autorisée")
|
||||||
if not partition.partition_name:
|
if not partition.partition_name:
|
||||||
return json_error(
|
return json_error(
|
||||||
API_CLIENT_ERROR, "ne peut pas supprimer la partition par défaut"
|
API_CLIENT_ERROR, "ne peut pas supprimer la partition par défaut"
|
||||||
|
@ -445,9 +445,10 @@ class User(UserMixin, ScoDocModel):
|
|||||||
|
|
||||||
def set_roles(self, roles, dept):
|
def set_roles(self, roles, dept):
|
||||||
"set roles in the given dept"
|
"set roles in the given dept"
|
||||||
self.user_roles = [
|
self.user_roles = []
|
||||||
UserRole(user=self, role=r, dept=dept) for r in roles if isinstance(r, Role)
|
for r in roles:
|
||||||
]
|
if isinstance(r, Role):
|
||||||
|
self.add_role(r, dept)
|
||||||
|
|
||||||
def get_roles(self):
|
def get_roles(self):
|
||||||
"iterator on my roles"
|
"iterator on my roles"
|
||||||
|
@ -44,7 +44,9 @@ def formation_change_referentiel(
|
|||||||
ue.niveau_competence_id = niveaux_map[ue.niveau_competence_id]
|
ue.niveau_competence_id = niveaux_map[ue.niveau_competence_id]
|
||||||
db.session.add(ue)
|
db.session.add(ue)
|
||||||
if ue.parcours:
|
if ue.parcours:
|
||||||
new_list = [ApcParcours.query.get(parcours_map[p.id]) for p in ue.parcours]
|
new_list = [
|
||||||
|
db.session.get(ApcParcours, parcours_map[p.id]) for p in ue.parcours
|
||||||
|
]
|
||||||
ue.parcours.clear()
|
ue.parcours.clear()
|
||||||
ue.parcours.extend(new_list)
|
ue.parcours.extend(new_list)
|
||||||
db.session.add(ue)
|
db.session.add(ue)
|
||||||
@ -52,7 +54,7 @@ def formation_change_referentiel(
|
|||||||
for module in formation.modules:
|
for module in formation.modules:
|
||||||
if module.parcours:
|
if module.parcours:
|
||||||
new_list = [
|
new_list = [
|
||||||
ApcParcours.query.get(parcours_map[p.id]) for p in module.parcours
|
db.session.get(ApcParcours, parcours_map[p.id]) for p in module.parcours
|
||||||
]
|
]
|
||||||
module.parcours.clear()
|
module.parcours.clear()
|
||||||
module.parcours.extend(new_list)
|
module.parcours.extend(new_list)
|
||||||
@ -76,7 +78,8 @@ def formation_change_referentiel(
|
|||||||
# FormSemestre / parcours_formsemestre
|
# FormSemestre / parcours_formsemestre
|
||||||
for formsemestre in formation.formsemestres:
|
for formsemestre in formation.formsemestres:
|
||||||
new_list = [
|
new_list = [
|
||||||
ApcParcours.query.get(parcours_map[p.id]) for p in formsemestre.parcours
|
db.session.get(ApcParcours, parcours_map[p.id])
|
||||||
|
for p in formsemestre.parcours
|
||||||
]
|
]
|
||||||
formsemestre.parcours.clear()
|
formsemestre.parcours.clear()
|
||||||
formsemestre.parcours.extend(new_list)
|
formsemestre.parcours.extend(new_list)
|
||||||
|
@ -1557,8 +1557,8 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
res: ResultatsSemestreBUT = (
|
res: ResultatsSemestreBUT = (
|
||||||
self.rcue.res_pair if paire else self.rcue.res_impair
|
self.rcue.res_pair if paire else self.rcue.res_impair
|
||||||
)
|
)
|
||||||
self.moy_ue = np.NaN
|
self.moy_ue = np.nan
|
||||||
self.moy_ue_with_cap = np.NaN
|
self.moy_ue_with_cap = np.nan
|
||||||
self.ue_status = {}
|
self.ue_status = {}
|
||||||
|
|
||||||
if self.ue.type != sco_codes.UE_STANDARD:
|
if self.ue.type != sco_codes.UE_STANDARD:
|
||||||
|
@ -541,17 +541,16 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
|||||||
ue_ids = [ue.id for ue in ues]
|
ue_ids = [ue.id for ue in ues]
|
||||||
evaluation_ids = [evaluation.id for evaluation in modimpl.evaluations]
|
evaluation_ids = [evaluation.id for evaluation in modimpl.evaluations]
|
||||||
evals_poids = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
evals_poids = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
||||||
if (
|
if modimpl.module.module_type in (ModuleType.RESSOURCE, ModuleType.SAE):
|
||||||
modimpl.module.module_type == ModuleType.RESSOURCE
|
|
||||||
or modimpl.module.module_type == ModuleType.SAE
|
|
||||||
):
|
|
||||||
for ue_poids in EvaluationUEPoids.query.join(
|
for ue_poids in EvaluationUEPoids.query.join(
|
||||||
EvaluationUEPoids.evaluation
|
EvaluationUEPoids.evaluation
|
||||||
).filter_by(moduleimpl_id=moduleimpl_id):
|
).filter_by(moduleimpl_id=moduleimpl_id):
|
||||||
try:
|
if (
|
||||||
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
ue_poids.evaluation_id in evals_poids.index
|
||||||
except KeyError:
|
and ue_poids.ue_id in evals_poids.columns
|
||||||
pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre...
|
):
|
||||||
|
evals_poids.at[ue_poids.evaluation_id, ue_poids.ue_id] = ue_poids.poids
|
||||||
|
# ignore poids vers des UEs qui n'existent plus ou sont dans un autre semestre...
|
||||||
|
|
||||||
# Initialise poids non enregistrés:
|
# Initialise poids non enregistrés:
|
||||||
default_poids = (
|
default_poids = (
|
||||||
@ -564,7 +563,7 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
|||||||
if np.isnan(evals_poids.values.flat).any():
|
if np.isnan(evals_poids.values.flat).any():
|
||||||
ue_coefs = modimpl.module.get_ue_coef_dict()
|
ue_coefs = modimpl.module.get_ue_coef_dict()
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
evals_poids[ue.id][evals_poids[ue.id].isna()] = (
|
evals_poids.loc[evals_poids[ue.id].isna(), ue.id] = (
|
||||||
1 if ue_coefs.get(ue.id, default_poids) > 0 else 0
|
1 if ue_coefs.get(ue.id, default_poids) > 0 else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ def compute_sem_moys_apc_using_ects(
|
|||||||
moy_gen = (etud_moy_ue_df * ects).sum(axis=1) / ects.sum(axis=1)
|
moy_gen = (etud_moy_ue_df * ects).sum(axis=1) / ects.sum(axis=1)
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
# peut arriver si aucun module... on ignore
|
# peut arriver si aucun module... on ignore
|
||||||
moy_gen = pd.Series(np.NaN, index=etud_moy_ue_df.index)
|
moy_gen = pd.Series(np.nan, index=etud_moy_ue_df.index)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
if None in ects:
|
if None in ects:
|
||||||
formation = db.session.get(Formation, formation_id)
|
formation = db.session.get(Formation, formation_id)
|
||||||
@ -93,7 +93,7 @@ def compute_sem_moys_apc_using_ects(
|
|||||||
scodoc_dept=g.scodoc_dept, formation_id=formation_id)}">{formation.get_titre_version()}</a>)"""
|
scodoc_dept=g.scodoc_dept, formation_id=formation_id)}">{formation.get_titre_version()}</a>)"""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
moy_gen = pd.Series(np.NaN, index=etud_moy_ue_df.index)
|
moy_gen = pd.Series(np.nan, index=etud_moy_ue_df.index)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
return moy_gen
|
return moy_gen
|
||||||
|
@ -92,7 +92,7 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
|||||||
|
|
||||||
for mod_coef in query:
|
for mod_coef in query:
|
||||||
if mod_coef.module_id in module_coefs_df:
|
if mod_coef.module_id in module_coefs_df:
|
||||||
module_coefs_df[mod_coef.module_id][mod_coef.ue_id] = mod_coef.coef
|
module_coefs_df.at[mod_coef.ue_id, mod_coef.module_id] = mod_coef.coef
|
||||||
# silently ignore coefs associated to other modules (ie when module_type is changed)
|
# silently ignore coefs associated to other modules (ie when module_type is changed)
|
||||||
|
|
||||||
# Initialisation des poids non fixés:
|
# Initialisation des poids non fixés:
|
||||||
@ -138,14 +138,16 @@ def df_load_modimpl_coefs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
for mod_coef in mod_coefs:
|
for mod_coef in mod_coefs:
|
||||||
try:
|
if (
|
||||||
modimpl_coefs_df[mod2impl[mod_coef.module_id]][
|
mod_coef.ue_id in modimpl_coefs_df.index
|
||||||
mod_coef.ue_id
|
and mod2impl[mod_coef.module_id] in modimpl_coefs_df.columns
|
||||||
] = mod_coef.coef
|
):
|
||||||
except IndexError:
|
modimpl_coefs_df.at[mod_coef.ue_id, mod2impl[mod_coef.module_id]] = (
|
||||||
|
mod_coef.coef
|
||||||
|
)
|
||||||
# il peut y avoir en base des coefs sur des modules ou UE
|
# il peut y avoir en base des coefs sur des modules ou UE
|
||||||
# qui ont depuis été retirés de la formation
|
# qui ont depuis été retirés de la formation : on ignore ces coefs
|
||||||
pass
|
|
||||||
# Initialisation des poids non fixés:
|
# Initialisation des poids non fixés:
|
||||||
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
|
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
|
||||||
# sur toutes les UE)
|
# sur toutes les UE)
|
||||||
@ -178,7 +180,7 @@ def notes_sem_assemble_cube(modimpls_notes: list[pd.DataFrame]) -> np.ndarray:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
app.critical_error(
|
app.critical_error(
|
||||||
f"""notes_sem_assemble_cube: shapes {
|
f"""notes_sem_assemble_cube: shapes {
|
||||||
", ".join([x.shape for x in modimpls_notes_arr])}"""
|
", ".join([str(x.shape) for x in modimpls_notes_arr])}"""
|
||||||
)
|
)
|
||||||
return modimpls_notes.swapaxes(0, 1)
|
return modimpls_notes.swapaxes(0, 1)
|
||||||
|
|
||||||
@ -299,7 +301,11 @@ def compute_ue_moys_apc(
|
|||||||
)
|
)
|
||||||
# Les "dispenses" sont très peu nombreuses et traitées en python:
|
# Les "dispenses" sont très peu nombreuses et traitées en python:
|
||||||
for dispense_ue in dispense_ues:
|
for dispense_ue in dispense_ues:
|
||||||
etud_moy_ue_df[dispense_ue[1]][dispense_ue[0]] = 0.0
|
if (
|
||||||
|
dispense_ue[0] in etud_moy_ue_df.columns
|
||||||
|
and dispense_ue[1] in etud_moy_ue_df.index
|
||||||
|
):
|
||||||
|
etud_moy_ue_df.at[dispense_ue[1], dispense_ue[0]] = 0.0
|
||||||
|
|
||||||
return etud_moy_ue_df
|
return etud_moy_ue_df
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -297,7 +297,7 @@ class Assiduite(ScoDocModel):
|
|||||||
moduleimpl_id = int(moduleimpl_id)
|
moduleimpl_id = int(moduleimpl_id)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ScoValueError("Module non reconnu") from exc
|
raise ScoValueError("Module non reconnu") from exc
|
||||||
moduleimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||||
|
|
||||||
# ici moduleimpl est None si non spécifié
|
# ici moduleimpl est None si non spécifié
|
||||||
|
|
||||||
@ -352,8 +352,8 @@ class Assiduite(ScoDocModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if self.moduleimpl_id is not None:
|
if self.moduleimpl_id is not None:
|
||||||
modimpl: ModuleImpl = ModuleImpl.query.get(self.moduleimpl_id)
|
modimpl: ModuleImpl = db.session.get(ModuleImpl, self.moduleimpl_id)
|
||||||
mod: Module = Module.query.get(modimpl.module_id)
|
mod: Module = db.session.get(Module, modimpl.module_id)
|
||||||
if traduire:
|
if traduire:
|
||||||
return f"{mod.code} {mod.titre}"
|
return f"{mod.code} {mod.titre}"
|
||||||
return mod
|
return mod
|
||||||
|
@ -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
|
||||||
|
@ -333,7 +333,7 @@ class SxTag(pe_tabletags.TableTag):
|
|||||||
etud_moy = np.max(set_cube_no_nan, axis=2)
|
etud_moy = np.max(set_cube_no_nan, axis=2)
|
||||||
|
|
||||||
# Fix les max non calculé -1 -> NaN
|
# Fix les max non calculé -1 -> NaN
|
||||||
etud_moy[etud_moy < 0] = np.NaN
|
etud_moy[etud_moy < 0] = np.nan
|
||||||
|
|
||||||
# Le dataFrame
|
# Le dataFrame
|
||||||
etud_moy_tag_df = pd.DataFrame(
|
etud_moy_tag_df = pd.DataFrame(
|
||||||
|
@ -196,7 +196,7 @@ def check_if_has_decision_jury(
|
|||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
if nt.etud_has_decision(etudid):
|
if nt.etud_has_decision(etudid):
|
||||||
etud = Identite.query.get(etudid)
|
etud = db.session.get(Identite, etudid)
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
f"""désinscription impossible: l'étudiant {etud.nomprenom} a
|
f"""désinscription impossible: l'étudiant {etud.nomprenom} a
|
||||||
une décision de jury (la supprimer avant si nécessaire)"""
|
une décision de jury (la supprimer avant si nécessaire)"""
|
||||||
@ -287,6 +287,12 @@ 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
|
||||||
|
if group_id != ""
|
||||||
|
]
|
||||||
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 +309,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.add(group.id)
|
||||||
gdone[group_id] = 1
|
|
||||||
|
|
||||||
# Inscription à tous les modules de ce semestre
|
# Inscription à tous les modules de ce semestre
|
||||||
for modimpl in formsemestre.modimpls:
|
for modimpl in formsemestre.modimpls:
|
||||||
|
@ -376,7 +376,7 @@ def formsemestre_inscr_passage(
|
|||||||
if a_desinscrire:
|
if a_desinscrire:
|
||||||
H.append("<h3>Étudiants à désinscrire</h3><ol>")
|
H.append("<h3>Étudiants à désinscrire</h3><ol>")
|
||||||
a_desinscrire_ident = sorted(
|
a_desinscrire_ident = sorted(
|
||||||
(Identite.query.get(eid) for eid in a_desinscrire),
|
(db.session.get(Identite, eid) for eid in a_desinscrire),
|
||||||
key=lambda x: x.sort_key,
|
key=lambda x: x.sort_key,
|
||||||
)
|
)
|
||||||
for etud in a_desinscrire_ident:
|
for etud in a_desinscrire_ident:
|
||||||
|
@ -136,7 +136,8 @@ def moduleimpl_inscriptions_edit(
|
|||||||
}
|
}
|
||||||
} 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");
|
||||||
|
var cells = tds[partitionIdx].childNodes;
|
||||||
if (cells.length && cells[0].nodeValue == groupName) {
|
if (cells.length && cells[0].nodeValue == groupName) {
|
||||||
elems[i].checked=check;
|
elems[i].checked=check;
|
||||||
}
|
}
|
||||||
@ -178,19 +179,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}>
|
||||||
H.append(
|
<a class="discretelink etudinfo" href="{
|
||||||
f"""<a class="discretelink etudinfo" href="{
|
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.fiche_etud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud["etudid"],
|
etudid=etud["etudid"],
|
||||||
)
|
)
|
||||||
}" id="{etud['etudid']}">{etud['nomprenom']}</a>"""
|
}" id="{etud['etudid']}">{etud['nomprenom']}</a>
|
||||||
|
</input>
|
||||||
|
</td>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
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"]:
|
||||||
|
@ -483,6 +483,8 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
}" title="Charger toutles les notes via tableur">Importer les notes</a>
|
}" title="Charger toutles les notes via tableur">Importer les notes</a>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
bot_table_links = top_table_links
|
||||||
if nb_evaluations > 0:
|
if nb_evaluations > 0:
|
||||||
H.append(
|
H.append(
|
||||||
'<div class="moduleimpl_evaluations_top_links">'
|
'<div class="moduleimpl_evaluations_top_links">'
|
||||||
|
@ -292,7 +292,7 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem) -> list[dict]:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
ue = UniteEns.query.get(ue_id)
|
ue = db.session.get(UniteEns, ue_id)
|
||||||
assert ue
|
assert ue
|
||||||
# note modernisation code: on utilise des dict tant que get_etud_ue_status renvoie des dicts
|
# note modernisation code: on utilise des dict tant que get_etud_ue_status renvoie des dicts
|
||||||
uelist.append(ue.to_dict())
|
uelist.append(ue.to_dict())
|
||||||
|
@ -408,7 +408,7 @@ def _check_inscription(
|
|||||||
elif etudid not in etudids_inscrits_mod:
|
elif etudid not in etudids_inscrits_mod:
|
||||||
msg_err = "non inscrit au module"
|
msg_err = "non inscrit au module"
|
||||||
if msg_err:
|
if msg_err:
|
||||||
etud = Identite.query.get(etudid) if isinstance(etudid, int) else None
|
etud = db.session.get(Identite, etudid) if isinstance(etudid, int) else None
|
||||||
msg = f"étudiant {etud.nomprenom if etud else etudid} {msg_err}"
|
msg = f"étudiant {etud.nomprenom if etud else etudid} {msg_err}"
|
||||||
log(f"notes_add: {etudid} {msg}: aborting")
|
log(f"notes_add: {etudid} {msg}: aborting")
|
||||||
raise NoteProcessError(msg)
|
raise NoteProcessError(msg)
|
||||||
@ -454,7 +454,7 @@ def notes_add(
|
|||||||
|
|
||||||
if (value is not None) and not isinstance(value, float):
|
if (value is not None) and not isinstance(value, float):
|
||||||
log(f"notes_add: {etudid} valeur de note invalide ({value}): aborting")
|
log(f"notes_add: {etudid} valeur de note invalide ({value}): aborting")
|
||||||
etud = Identite.query.get(etudid) if isinstance(etudid, int) else None
|
etud = db.session.get(Identite, etudid) if isinstance(etudid, int) else None
|
||||||
raise NoteProcessError(
|
raise NoteProcessError(
|
||||||
f"etudiant {etud.nomprenom if etud else etudid}: valeur de note invalide ({value})"
|
f"etudiant {etud.nomprenom if etud else etudid}: valeur de note invalide ({value})"
|
||||||
)
|
)
|
||||||
@ -491,7 +491,9 @@ def notes_add(
|
|||||||
# si change sur DEM/DEF ajoute message warning aux messages
|
# si change sur DEM/DEF ajoute message warning aux messages
|
||||||
if etudid not in etudids_actifs: # DEM ou DEF
|
if etudid not in etudids_actifs: # DEM ou DEF
|
||||||
etud = (
|
etud = (
|
||||||
Identite.query.get(etudid) if isinstance(etudid, int) else None
|
db.session.get(Identite, etudid)
|
||||||
|
if isinstance(etudid, int)
|
||||||
|
else None
|
||||||
)
|
)
|
||||||
messages.append(
|
messages.append(
|
||||||
f"""étudiant {etud.nomprenom if etud else etudid
|
f"""étudiant {etud.nomprenom if etud else etudid
|
||||||
@ -801,7 +803,7 @@ def get_sorted_etuds_notes(
|
|||||||
notes_db[etudid]["value"], fixed_precision_str=False
|
notes_db[etudid]["value"], fixed_precision_str=False
|
||||||
)
|
)
|
||||||
user = (
|
user = (
|
||||||
User.query.get(notes_db[etudid]["uid"])
|
db.session.get(User, notes_db[etudid]["uid"])
|
||||||
if notes_db[etudid]["uid"]
|
if notes_db[etudid]["uid"]
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
@ -144,7 +144,7 @@ def external_ue_create(
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
modimpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||||
assert modimpl
|
assert modimpl
|
||||||
return modimpl
|
return modimpl
|
||||||
|
|
||||||
@ -206,7 +206,7 @@ def get_external_moduleimpl(formsemestre_id: int, ue_id: int) -> ModuleImpl:
|
|||||||
)
|
)
|
||||||
if r:
|
if r:
|
||||||
modimpl_id = r[0]["moduleimpl_id"]
|
modimpl_id = r[0]["moduleimpl_id"]
|
||||||
modimpl = ModuleImpl.query.get(modimpl_id)
|
modimpl = db.session.get(ModuleImpl, modimpl_id)
|
||||||
assert modimpl
|
assert modimpl
|
||||||
return modimpl
|
return modimpl
|
||||||
else:
|
else:
|
||||||
|
@ -553,7 +553,7 @@ class RowAssiJusti(tb.Row):
|
|||||||
)
|
)
|
||||||
if self.table.options.show_module:
|
if self.table.options.show_module:
|
||||||
if self.ligne["type"] == "assiduite":
|
if self.ligne["type"] == "assiduite":
|
||||||
assi: Assiduite = Assiduite.query.get(self.ligne["obj_id"])
|
assi: Assiduite = db.session.get(Assiduite, self.ligne["obj_id"])
|
||||||
# Gestion des colonnes concernant le module
|
# Gestion des colonnes concernant le module
|
||||||
mod: Module = assi.get_module(False)
|
mod: Module = assi.get_module(False)
|
||||||
code = mod.code if isinstance(mod, Module) else ""
|
code = mod.code if isinstance(mod, Module) else ""
|
||||||
@ -607,7 +607,9 @@ class RowAssiJusti(tb.Row):
|
|||||||
|
|
||||||
def _utilisateur(self) -> None:
|
def _utilisateur(self) -> None:
|
||||||
utilisateur: User = (
|
utilisateur: User = (
|
||||||
User.query.get(self.ligne["user_id"]) if self.ligne["user_id"] else None
|
db.session.get(User, self.ligne["user_id"])
|
||||||
|
if self.ligne["user_id"] is not None
|
||||||
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
self.add_cell(
|
self.add_cell(
|
||||||
|
@ -270,6 +270,9 @@ Pour uniformiser les résultats des exemples, ceux sont soumis à quelques post-
|
|||||||
|
|
||||||
Voir exemples d'utilisation de l'API en Python, dans `tests/api/`.
|
Voir exemples d'utilisation de l'API en Python, dans `tests/api/`.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
Cette page a été générée par la commande `flask gen-api-doc`, et les exemples de résultats
|
||||||
|
sont créés par `tools/test_api.sh --make-samples`.
|
||||||
|
|
||||||
!!! note "Voir aussi"
|
!!! note "Voir aussi"
|
||||||
|
|
||||||
|
@ -24,6 +24,9 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
"""Vues assiduité
|
||||||
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
@ -508,7 +511,7 @@ def _record_assiduite_etud(
|
|||||||
case None:
|
case None:
|
||||||
moduleimpl = None
|
moduleimpl = None
|
||||||
case _:
|
case _:
|
||||||
moduleimpl = ModuleImpl.query.get(moduleimpl_id)
|
moduleimpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||||
try:
|
try:
|
||||||
assi_etat: scu.EtatAssiduite = scu.EtatAssiduite.get(form.assi_etat.data)
|
assi_etat: scu.EtatAssiduite = scu.EtatAssiduite.get(form.assi_etat.data)
|
||||||
|
|
||||||
@ -1678,7 +1681,7 @@ def _preparer_objet(
|
|||||||
if not sans_gros_objet:
|
if not sans_gros_objet:
|
||||||
justificatifs: list[int] = get_assiduites_justif(objet.assiduite_id, False)
|
justificatifs: list[int] = get_assiduites_justif(objet.assiduite_id, False)
|
||||||
for justi_id in justificatifs:
|
for justi_id in justificatifs:
|
||||||
justi: Justificatif = Justificatif.query.get(justi_id)
|
justi: Justificatif = db.session.get(Justificatif, justi_id)
|
||||||
objet_prepare["justification"]["justificatifs"].append(
|
objet_prepare["justification"]["justificatifs"].append(
|
||||||
_preparer_objet("justificatif", justi, sans_gros_objet=True)
|
_preparer_objet("justificatif", justi, sans_gros_objet=True)
|
||||||
)
|
)
|
||||||
@ -1717,7 +1720,7 @@ def _preparer_objet(
|
|||||||
objet_prepare["etud_nom"] = objet.etudiant.nomprenom
|
objet_prepare["etud_nom"] = objet.etudiant.nomprenom
|
||||||
|
|
||||||
if objet.user_id is not None:
|
if objet.user_id is not None:
|
||||||
user: User = User.query.get(objet.user_id)
|
user: User = db.session.get(User, objet.user_id)
|
||||||
objet_prepare["saisie_par"] = user.get_nomprenom()
|
objet_prepare["saisie_par"] = user.get_nomprenom()
|
||||||
else:
|
else:
|
||||||
objet_prepare["saisie_par"] = "Inconnu"
|
objet_prepare["saisie_par"] = "Inconnu"
|
||||||
@ -2925,7 +2928,7 @@ def _module_selector_multiple(
|
|||||||
)
|
)
|
||||||
choices = OrderedDict()
|
choices = OrderedDict()
|
||||||
for formsemestre_id in modimpls_by_formsemestre:
|
for formsemestre_id in modimpls_by_formsemestre:
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||||
if only_form is not None and formsemestre != only_form:
|
if only_form is not None and formsemestre != only_form:
|
||||||
continue
|
continue
|
||||||
# indique le nom du semestre dans le menu (optgroup)
|
# indique le nom du semestre dans le menu (optgroup)
|
||||||
|
@ -2112,7 +2112,7 @@ def check_group_apogee(group_id, etat=None, fix=False, fixmail=False):
|
|||||||
def export_etudiants_courants():
|
def export_etudiants_courants():
|
||||||
"""Table export de tous les étudiants des formsemestres en cours."""
|
"""Table export de tous les étudiants des formsemestres en cours."""
|
||||||
fmt = request.args.get("fmt", "html")
|
fmt = request.args.get("fmt", "html")
|
||||||
departement = Departement.query.get(g.scodoc_dept_id)
|
departement = db.session.get(Departement, g.scodoc_dept_id)
|
||||||
if not departement:
|
if not departement:
|
||||||
raise ScoValueError("département invalide")
|
raise ScoValueError("département invalide")
|
||||||
formsemestres = FormSemestre.get_dept_formsemestres_courants(departement).all()
|
formsemestres = FormSemestre.get_dept_formsemestres_courants(departement).all()
|
||||||
@ -2462,7 +2462,7 @@ def formsemestre_import_etud_admission(
|
|||||||
Si tous_courants, le fait pour tous les formsemestres courants du département
|
Si tous_courants, le fait pour tous les formsemestres courants du département
|
||||||
"""
|
"""
|
||||||
if tous_courants:
|
if tous_courants:
|
||||||
departement = Departement.query.get(g.scodoc_dept_id)
|
departement = db.session.get(Departement, g.scodoc_dept_id)
|
||||||
formsemestres = FormSemestre.get_dept_formsemestres_courants(departement)
|
formsemestres = FormSemestre.get_dept_formsemestres_courants(departement)
|
||||||
else:
|
else:
|
||||||
formsemestres = [FormSemestre.get_formsemestre(formsemestre_id)]
|
formsemestres = [FormSemestre.get_formsemestre(formsemestre_id)]
|
||||||
@ -2557,13 +2557,9 @@ def sco_dump_and_send_db(message="", request_url="", traceback_str_base64=""):
|
|||||||
try:
|
try:
|
||||||
r_msg = r.json()["message"]
|
r_msg = r.json()["message"]
|
||||||
except (requests.exceptions.JSONDecodeError, KeyError):
|
except (requests.exceptions.JSONDecodeError, KeyError):
|
||||||
r_msg = "Erreur: code <tt>"
|
r_msg = f"""Erreur: code <tt>{status_code}</tt>
|
||||||
+status_code
|
Merci de contacter <a href="mailto:{scu.SCO_DEV_MAIL}">{scu.SCO_DEV_MAIL}</a>
|
||||||
+'</tt> Merci de contacter <a href="mailto:'
|
"""
|
||||||
+scu.SCO_DEV_MAIL
|
|
||||||
+'">'
|
|
||||||
+scu.SCO_DEV_MAIL
|
|
||||||
+"</a>"
|
|
||||||
|
|
||||||
H = [html_sco_header.sco_header(page_title="Assistance technique")]
|
H = [html_sco_header.sco_header(page_title="Assistance technique")]
|
||||||
if status_code == requests.codes.OK: # pylint: disable=no-member
|
if status_code == requests.codes.OK: # pylint: disable=no-member
|
||||||
|
@ -4,10 +4,10 @@ Tableau de bord utilisateur
|
|||||||
Emmanuel Viennet, 2023
|
Emmanuel Viennet, 2023
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from flask import flash, redirect, render_template, url_for
|
from flask import flash, redirect, render_template, url_for
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
|
from app import db
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
scodoc,
|
scodoc,
|
||||||
@ -31,7 +31,8 @@ def user_board(user_name: str):
|
|||||||
modimpls_by_formsemestre,
|
modimpls_by_formsemestre,
|
||||||
) = FormSemestre.get_user_formsemestres_annee_by_dept(user)
|
) = FormSemestre.get_user_formsemestres_annee_by_dept(user)
|
||||||
depts = {
|
depts = {
|
||||||
dept_id: Departement.query.get(dept_id) for dept_id in formsemestres_by_dept
|
dept_id: db.session.get(Departement, dept_id)
|
||||||
|
for dept_id in formsemestres_by_dept
|
||||||
}
|
}
|
||||||
dept_names = {
|
dept_names = {
|
||||||
dept_id: sco_preferences.get_preference("DeptName", dept_id=dept_id)
|
dept_id: sco_preferences.get_preference("DeptName", dept_id=dept_id)
|
||||||
|
@ -1,114 +1,116 @@
|
|||||||
alembic==1.13.0
|
alembic==1.13.2
|
||||||
astroid==3.0.1
|
astroid==3.2.4
|
||||||
async-timeout==4.0.3
|
async-timeout==4.0.3
|
||||||
attrs==23.1.0
|
attrs==23.2.0
|
||||||
Babel==2.13.1
|
Babel==2.15.0
|
||||||
black==23.11.0
|
black==24.4.2
|
||||||
blinker==1.7.0
|
blinker==1.8.2
|
||||||
Brotli==1.1.0
|
Brotli==1.1.0
|
||||||
cachelib==0.9.0
|
cachelib==0.9.0
|
||||||
certifi==2023.11.17
|
certifi==2024.7.4
|
||||||
cffi==1.16.0
|
cffi==1.16.0
|
||||||
chardet==5.2.0
|
chardet==5.2.0
|
||||||
charset-normalizer==3.3.2
|
charset-normalizer==3.3.2
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
cracklib==2.9.6
|
cracklib==2.9.6
|
||||||
cryptography==41.0.7
|
cryptography==43.0.0
|
||||||
cssselect2==0.7.0
|
cssselect2==0.7.0
|
||||||
|
deepdiff==6.7.1
|
||||||
Deprecated==1.2.14
|
Deprecated==1.2.14
|
||||||
dill==0.3.7
|
dill==0.3.8
|
||||||
dnspython==2.4.2
|
dnspython==2.6.1
|
||||||
dominate==2.9.0
|
dominate==2.9.1
|
||||||
email-validator==2.1.0.post1
|
email_validator==2.2.0
|
||||||
ERAlchemy==1.2.10
|
ERAlchemy==1.2.10
|
||||||
et-xmlfile==1.1.0
|
et-xmlfile==1.1.0
|
||||||
exceptiongroup==1.2.0
|
exceptiongroup==1.2.2
|
||||||
execnet==2.0.2
|
execnet==2.1.1
|
||||||
flake8==6.1.0
|
flake8==7.1.0
|
||||||
Flask==3.0.0
|
Flask==3.0.3
|
||||||
flask-babel==4.0.0
|
flask-babel==4.0.0
|
||||||
Flask-Caching==2.1.0
|
Flask-Caching==2.3.0
|
||||||
Flask-HTTPAuth==4.8.0
|
Flask-HTTPAuth==4.8.0
|
||||||
Flask-JSON==0.4.0
|
Flask-JSON==0.4.0
|
||||||
Flask-Login==0.6.3
|
Flask-Login==0.6.3
|
||||||
Flask-Mail==0.9.1
|
Flask-Mail==0.10.0
|
||||||
Flask-Migrate==4.0.5
|
Flask-Migrate==4.0.7
|
||||||
Flask-SQLAlchemy==3.1.1
|
Flask-SQLAlchemy==3.1.1
|
||||||
Flask-WTF==1.2.1
|
Flask-WTF==1.2.1
|
||||||
fonttools==4.46.0
|
fonttools==4.53.1
|
||||||
gprof2dot==2022.7.29
|
gprof2dot==2024.6.6
|
||||||
greenlet==3.0.1
|
greenlet==3.0.3
|
||||||
gunicorn==21.2.0
|
gunicorn==22.0.0
|
||||||
html5lib==1.1
|
html5lib==1.1
|
||||||
icalendar==5.0.11
|
icalendar==5.0.13
|
||||||
idna==3.6
|
idna==3.7
|
||||||
importlib-metadata==7.0.0
|
importlib_metadata==8.2.0
|
||||||
iniconfig==2.0.0
|
iniconfig==2.0.0
|
||||||
isort==5.12.0
|
isort==5.13.2
|
||||||
itsdangerous==2.1.2
|
itsdangerous==2.2.0
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.4
|
||||||
lazy-object-proxy==1.9.0
|
lazy-object-proxy==1.10.0
|
||||||
lxml==4.9.3
|
lxml==5.2.2
|
||||||
Mako==1.3.0
|
Mako==1.3.5
|
||||||
MarkupSafe==2.1.3
|
MarkupSafe==2.1.5
|
||||||
mccabe==0.7.0
|
mccabe==0.7.0
|
||||||
mypy==1.7.1
|
mypy==1.11.0
|
||||||
mypy-extensions==1.0.0
|
mypy-extensions==1.0.0
|
||||||
numpy==1.26.2
|
numpy==2.0.1
|
||||||
openpyxl==3.1.2
|
openpyxl==3.1.5
|
||||||
packaging==23.2
|
ordered-set==4.1.0
|
||||||
pandas==2.1.3
|
packaging==24.1
|
||||||
pathspec==0.11.2
|
pandas==2.2.2
|
||||||
Pillow==10.1.0
|
pathspec==0.12.1
|
||||||
platformdirs==4.1.0
|
pillow==10.4.0
|
||||||
pluggy==1.3.0
|
platformdirs==4.2.2
|
||||||
|
pluggy==1.5.0
|
||||||
psycopg2==2.9.9
|
psycopg2==2.9.9
|
||||||
puremagic==1.15
|
puremagic==1.26
|
||||||
py==1.11.0
|
py==1.11.0
|
||||||
pycodestyle==2.11.1
|
pycodestyle==2.12.0
|
||||||
pycparser==2.21
|
pycparser==2.22
|
||||||
pydot==1.4.2
|
pydot==3.0.1
|
||||||
pydyf==0.8.0
|
pydyf==0.11.0
|
||||||
pyflakes==3.1.0
|
pyflakes==3.2.0
|
||||||
pygraphviz==1.11
|
pygraphviz==1.13
|
||||||
PyJWT==2.8.0
|
PyJWT==2.8.0
|
||||||
pylint==3.0.2
|
pylint==3.2.6
|
||||||
pylint-flask==0.6
|
pylint-flask==0.6
|
||||||
pylint-flask-sqlalchemy==0.2.0
|
pylint-flask-sqlalchemy==0.2.0
|
||||||
pylint-plugin-utils==0.8.2
|
pylint-plugin-utils==0.8.2
|
||||||
pyOpenSSL==23.3.0
|
pyOpenSSL==24.2.1
|
||||||
pyparsing==3.1.1
|
pyparsing==3.1.2
|
||||||
pyphen==0.14.0
|
pyphen==0.16.0
|
||||||
pytest==7.4.3
|
pytest==8.3.2
|
||||||
pytest-xdist==3.5.0
|
pytest-xdist==3.6.1
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.9.0.post0
|
||||||
python-docx==1.1.0
|
python-docx==1.1.2
|
||||||
python-dotenv==1.0.0
|
python-dotenv==1.0.1
|
||||||
python-editor==1.0.4
|
python-editor==1.0.4
|
||||||
pytz==2023.3.post1
|
pytz==2024.1
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
redis==5.0.1
|
redis==5.0.8
|
||||||
reportlab==4.0.7
|
reportlab==4.2.2
|
||||||
requests==2.31.0
|
requests==2.32.3
|
||||||
rq==1.15.1
|
rq==1.16.2
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
snakeviz==2.2.0
|
snakeviz==2.2.0
|
||||||
SQLAlchemy==2.0.23
|
SQLAlchemy==2.0.31
|
||||||
tinycss2==1.2.1
|
tinycss2==1.3.0
|
||||||
toml==0.10.2
|
toml==0.10.2
|
||||||
tomli==2.0.1
|
tomli==2.0.1
|
||||||
tomlkit==0.12.3
|
tomlkit==0.13.0
|
||||||
tornado==6.4
|
tornado==6.4.1
|
||||||
tuna==0.5.11
|
tuna==0.5.11
|
||||||
typing_extensions==4.8.0
|
typing_extensions==4.12.2
|
||||||
tzdata==2023.3
|
tzdata==2024.1
|
||||||
urllib3==2.1.0
|
urllib3==2.2.2
|
||||||
visitor==0.1.3
|
visitor==0.1.3
|
||||||
weasyprint==60.1
|
weasyprint==62.3
|
||||||
webencodings==0.5.1
|
webencodings==0.5.1
|
||||||
Werkzeug==3.0.1
|
Werkzeug==3.0.3
|
||||||
wrapt==1.16.0
|
wrapt==1.16.0
|
||||||
WTForms==3.1.1
|
WTForms==3.1.2
|
||||||
xmltodict==0.13.0
|
xmltodict==0.13.0
|
||||||
zipp==3.17.0
|
zipp==3.19.2
|
||||||
zopfli==0.2.3
|
zopfli==0.2.3
|
||||||
|
@ -52,16 +52,20 @@ print(f"API_USER={API_USER}")
|
|||||||
|
|
||||||
|
|
||||||
class APIError(Exception):
|
class APIError(Exception):
|
||||||
def __init__(self, message: str = "", payload=None):
|
def __init__(self, message: str = "", payload=None, status_code=None):
|
||||||
self.message = message
|
self.message = message
|
||||||
self.payload = payload or {}
|
self.payload = payload or {}
|
||||||
|
self.status_code = status_code
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"APIError: {self.message} payload={self.payload} status_code={self.status_code}"
|
||||||
|
|
||||||
|
|
||||||
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"
|
||||||
ans = requests.post(API_URL + "/tokens", auth=(user, password), timeout=5)
|
ans = requests.post(API_URL + "/tokens", auth=(user, password), timeout=5)
|
||||||
if ans.status_code != 200:
|
if ans.status_code != 200:
|
||||||
raise APIError(f"Echec demande jeton par {user}")
|
raise APIError(f"Echec demande jeton par {user}", status_code=ans.status_code)
|
||||||
token = ans.json()["token"]
|
token = ans.json()["token"]
|
||||||
return {"Authorization": f"Bearer {token}"}
|
return {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
@ -106,11 +110,12 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None, raw=False):
|
|||||||
timeout=SCO_TEST_API_TIMEOUT,
|
timeout=SCO_TEST_API_TIMEOUT,
|
||||||
)
|
)
|
||||||
if reply.status_code != 200:
|
if reply.status_code != 200:
|
||||||
print("url", SCODOC_URL)
|
|
||||||
print("url", url)
|
print("url", url)
|
||||||
print("reply", reply.text)
|
print("reply", reply.text)
|
||||||
raise APIError(
|
raise APIError(
|
||||||
errmsg or f"""erreur status={reply.status_code} !""", reply.json()
|
errmsg or f"""erreur get {url} !""",
|
||||||
|
reply.json(),
|
||||||
|
status_code=reply.status_code,
|
||||||
)
|
)
|
||||||
if raw:
|
if raw:
|
||||||
return reply
|
return reply
|
||||||
@ -126,15 +131,24 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None, raw=False):
|
|||||||
"Content-Disposition": reply.headers.get("Content-Disposition", None),
|
"Content-Disposition": reply.headers.get("Content-Disposition", None),
|
||||||
}
|
}
|
||||||
return retval
|
return retval
|
||||||
raise APIError("Unknown returned content {r.headers.get('Content-Type', None} !\n")
|
raise APIError(
|
||||||
|
"Unknown returned content {r.headers.get('Content-Type', None} !\n",
|
||||||
|
status_code=reply.status_code,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
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 +161,15 @@ 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,
|
||||||
|
status_code=r.status_code,
|
||||||
|
)
|
||||||
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
|
||||||
|
@ -185,7 +185,7 @@ def test_modif_users_depts(api_admin_headers):
|
|||||||
headers=admin_h,
|
headers=admin_h,
|
||||||
)
|
)
|
||||||
except APIError as exc:
|
except APIError as exc:
|
||||||
if exc.args[1]["status"] == 400:
|
if exc.status_code == 400:
|
||||||
ok = True
|
ok = True
|
||||||
assert ok
|
assert ok
|
||||||
# Un "vrai" mot de passe:
|
# Un "vrai" mot de passe:
|
||||||
@ -234,7 +234,7 @@ def test_modif_users_depts(api_admin_headers):
|
|||||||
dept=dept3["acronym"],
|
dept=dept3["acronym"],
|
||||||
)
|
)
|
||||||
except APIError as exc:
|
except APIError as exc:
|
||||||
if exc.args[1]["status"] == 401:
|
if exc.status_code == 401:
|
||||||
ok = True
|
ok = True
|
||||||
assert ok
|
assert ok
|
||||||
# Nettoyage:
|
# Nettoyage:
|
||||||
|
@ -250,7 +250,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
|||||||
|
|
||||||
|
|
||||||
def _signal_absences_justificatifs(etudid: int):
|
def _signal_absences_justificatifs(etudid: int):
|
||||||
etud: Identite = Identite.query.get(etudid)
|
etud: Identite = db.session.get(Identite, etudid)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
for i in range(15, 18):
|
for i in range(15, 18):
|
||||||
db.session.add(
|
db.session.add(
|
||||||
|
@ -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`.
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
|
@ -11,13 +11,17 @@
|
|||||||
#
|
#
|
||||||
# Toutes les autres options sont passées telles qu'elles à pytest
|
# Toutes les autres options sont passées telles qu'elles à pytest
|
||||||
#
|
#
|
||||||
|
# Utilisation pour générer des exemples de documentation:
|
||||||
|
# tools/test_api.sh --make-samples
|
||||||
|
#
|
||||||
# Exemples:
|
# Exemples:
|
||||||
# - lancer tous les tests API: tools/test_api.sh
|
# - lancer tous les tests API: tools/test_api.sh
|
||||||
# - lancer tous les tests, en mode debug (arrêt pdb sur le 1er):
|
# - lancer tous les tests, en mode debug (arrêt pdb sur le 1er):
|
||||||
# tools/test_api.sh -x --pdb tests/api
|
# tools/test_api.sh -x --pdb tests/api
|
||||||
# - lancer un module de test, en utilisant un server dev existant:
|
# - lancer un module de test, en utilisant un server dev existant:
|
||||||
# tools/test_api.sh --dont-start-server -x --pdb tests/api/test_api_evaluations.py
|
# tools/test_api.sh --dont-start-server -x --pdb tests/api/test_api_evaluations.py
|
||||||
#
|
# - Générer les samples pour la doc:
|
||||||
|
# tools/test_api.sh --make-samples
|
||||||
#
|
#
|
||||||
# E. Viennet, Fev 2023
|
# E. Viennet, Fev 2023
|
||||||
|
|
||||||
@ -70,10 +74,16 @@ if [ "$#" -eq 0 ]
|
|||||||
then
|
then
|
||||||
echo "Starting pytest tests/api"
|
echo "Starting pytest tests/api"
|
||||||
pytest tests/api
|
pytest tests/api
|
||||||
|
else
|
||||||
|
if [ "$1" = "--make-samples" ]
|
||||||
|
then
|
||||||
|
echo "Generating API documentation samples"
|
||||||
|
python tests/api/make_samples.py -i /tmp/samples.csv
|
||||||
else
|
else
|
||||||
echo "Starting pytest $@"
|
echo "Starting pytest $@"
|
||||||
pytest "$@"
|
pytest "$@"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# ------------------
|
# ------------------
|
||||||
if [ "$START_SERVER" -eq 1 ]
|
if [ "$START_SERVER" -eq 1 ]
|
||||||
|
Loading…
Reference in New Issue
Block a user