API: unifie traitement errors, messages JSON.

This commit is contained in:
Emmanuel Viennet 2022-08-07 19:56:25 +02:00
parent a053afeba6
commit f7a2c1e8e7
24 changed files with 159 additions and 185 deletions

View File

@ -3,11 +3,18 @@
from flask import Blueprint
from flask import request
from app.scodoc import sco_utils as scu
api_bp = Blueprint("api", __name__)
api_web_bp = Blueprint("apiweb", __name__)
@api_bp.errorhandler(404)
def api_error_handler(e):
"erreurs API => json"
return scu.json_error(404, message=str(e))
def requested_format(default_format="json", allowed_formats=None):
"""Extract required format from query string.
* default value is json. A list of allowed formats may be provided

View File

@ -9,7 +9,7 @@
from flask import jsonify
from app.api import api_bp as bp
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.decorators import scodoc, permission_required
from app.models import Identite
@ -53,7 +53,7 @@ def absences(etudid: int = None):
"""
etud = Identite.query.get(etudid)
if etud is None:
return error_response(404, message="etudiant inexistant")
return json_error(404, message="etudiant inexistant")
# Absences de l'étudiant
ndb.open_db_connection()
abs_list = sco_abs.list_abs_date(etud.id)
@ -97,7 +97,7 @@ def absences_just(etudid: int = None):
"""
etud = Identite.query.get(etudid)
if etud is None:
return error_response(404, message="etudiant inexistant")
return json_error(404, message="etudiant inexistant")
# Absences justifiées de l'étudiant
abs_just = [

View File

@ -15,7 +15,7 @@ import app
from app import db
from app.api import api_bp as bp, api_web_bp
from app.decorators import scodoc, permission_required
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.models import BilletAbsence
from app.models.etudiants import Identite
from app.scodoc import sco_abs_billets
@ -47,7 +47,7 @@ def billets_absence_create():
description = data.get("description", "")
justified = data.get("justified", False)
if None in (etudid, abs_begin, abs_end):
return error_response(
return json_error(
404, message="Paramètre manquant: etudid, abs_bein, abs_end requis"
)
query = Identite.query.filter_by(etudid=etudid)

View File

@ -17,7 +17,7 @@ from flask_login import login_required
import app
from app import db, log
from app.api import api_bp as bp
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.decorators import scodoc, permission_required
from app.models import Departement, FormSemestre
from app.models import departements
@ -103,12 +103,12 @@ def departement_create():
data = request.get_json(force=True) # may raise 400 Bad Request
acronym = str(data.get("acronym", ""))
if not acronym:
return error_response(404, "missing acronym")
return json_error(404, "missing acronym")
visible = bool(data.get("visible", True))
try:
dept = departements.create_dept(acronym, visible=visible)
except ScoValueError as exc:
return error_response(404, exc.args[0] if exc.args else "")
return json_error(404, exc.args[0] if exc.args else "")
return jsonify(dept.to_dict())
@ -128,7 +128,7 @@ def departement_edit(acronym):
data = request.get_json(force=True) # may raise 400 Bad Request
visible = bool(data.get("visible", None))
if visible is None:
return error_response(404, "missing argument: visible")
return json_error(404, "missing argument: visible")
visible = bool(visible)
dept.visible = visible
db.session.add(dept)

View File

@ -1,41 +0,0 @@
# Authentication code borrowed from Miguel Grinberg's Mega Tutorial
# (see https://github.com/miguelgrinberg/microblog)
# Under The MIT License (MIT)
# Copyright (c) 2017 Miguel Grinberg
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from flask import jsonify
from werkzeug.http import HTTP_STATUS_CODES
def error_response(status_code, message=None):
"""Réponse sur erreur"""
payload = {"error": HTTP_STATUS_CODES.get(status_code, "Unknown error")}
if message:
payload["message"] = message
response = jsonify(payload)
response.status_code = status_code
return response
def bad_request(message):
"400 Bad Request response"
return error_response(400, message)

View File

@ -15,7 +15,7 @@ from sqlalchemy import desc, or_
import app
from app.api import api_bp as bp, api_web_bp
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.api import tools
from app.decorators import scodoc, permission_required
from app.models import (
@ -116,7 +116,7 @@ def etudiant(etudid: int = None, nip: str = None, ine: str = None):
etud = tools.get_etud(etudid, nip, ine)
if etud is None:
return error_response(
return json_error(
404,
message="étudiant inconnu",
)
@ -148,7 +148,7 @@ def etudiants(etudid: int = None, nip: str = None, ine: str = None):
elif ine is not None:
query = Identite.query.filter_by(code_ine=ine)
else:
return error_response(
return json_error(
404,
message="parametre manquant",
)
@ -185,12 +185,12 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
elif ine is not None:
q_etud = Identite.query.filter_by(code_ine=ine)
else:
return error_response(404, message="parametre manquant")
return json_error(404, message="parametre manquant")
if g.scodoc_dept is not None:
q_etud = q_etud.filter_by(dept_id=g.scodoc_dept_id)
etud = q_etud.join(Admission).order_by(desc(Admission.annee)).first()
if etud is None:
return error_response(404, message="etudiant inexistant")
return json_error(404, message="etudiant inexistant")
query = FormSemestre.query.filter(
FormSemestreInscription.etudid == etud.id,
FormSemestreInscription.formsemestre_id == FormSemestre.id,
@ -328,7 +328,7 @@ def etudiant_bulletin_semestre(
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
dept = Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
if g.scodoc_dept and dept != g.scodoc_dept:
return error_response(404, "formsemestre non trouve")
return json_error(404, "formsemestre non trouve")
if etudid is not None:
query = Identite.query.filter_by(id=etudid)
elif nip is not None:
@ -336,11 +336,11 @@ def etudiant_bulletin_semestre(
elif ine is not None:
query = Identite.query.filter_by(code_ine=ine, dept_id=dept.id)
else:
return error_response(404, message="parametre manquant")
return json_error(404, message="parametre manquant")
etud = query.first()
if etud is None:
return error_response(404, message="etudiant inexistant")
return json_error(404, message="etudiant inexistant")
app.set_sco_dept(dept.acronym)
@ -400,7 +400,7 @@ def etudiant_groups(formsemestre_id: int, etudid: int = None):
query = query.filter_by(dept_id=g.scodoc_dept_id)
formsemestre = query.first()
if formsemestre is None:
return error_response(
return json_error(
404,
message="formsemestre inconnu",
)

View File

@ -15,7 +15,7 @@ import app
from app.api import api_bp as bp, api_web_bp
from app.decorators import scodoc, permission_required
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.models import Evaluation, ModuleImpl, FormSemestre
from app.scodoc import sco_evaluation_db
from app.scodoc.sco_permissions import Permission

View File

@ -13,7 +13,7 @@ from flask_login import login_required
import app
from app.api import api_bp as bp, api_web_bp
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.decorators import scodoc, permission_required
from app.models.formations import Formation
from app.models.formsemestre import FormSemestre
@ -210,7 +210,7 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False):
try:
data = sco_formations.formation_export(formation_id, export_ids)
except ValueError:
return error_response(500, message="Erreur inconnue")
return json_error(500, message="Erreur inconnue")
return jsonify(data)

View File

@ -13,7 +13,7 @@ from flask_login import login_required
import app
from app.api import api_bp as bp, api_web_bp
from app.decorators import scodoc, permission_required
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.comp import res_sem
from app.comp.moy_mod import ModuleImplResults
from app.comp.res_compat import NotesTableCompat
@ -107,7 +107,7 @@ def formsemestres_query():
try:
annee_scolaire_int = int(annee_scolaire)
except ValueError:
return error_response(404, "invalid annee_scolaire: not int")
return json_error(404, "invalid annee_scolaire: not int")
debut_annee = scu.date_debut_anne_scolaire(annee_scolaire_int)
fin_annee = scu.date_fin_anne_scolaire(annee_scolaire_int)
formsemestres = formsemestres.filter(
@ -119,7 +119,7 @@ def formsemestres_query():
try:
dept_id = int(dept_id)
except ValueError:
return error_response(404, "invalid dept_id: not int")
return json_error(404, "invalid dept_id: not int")
formsemestres = formsemestres.filter_by(dept_id=dept_id)
if etape_apo is not None:
formsemestres = formsemestres.join(FormSemestreEtape).filter(
@ -417,7 +417,7 @@ def formsemestre_resultat(formsemestre_id: int):
"""
format_spec = request.args.get("format", None)
if format_spec is not None and format_spec != "raw":
return error_response(404, "invalid format specification")
return json_error(404, "invalid format specification")
convert_values = format_spec != "raw"
query = FormSemestre.query.filter_by(id=formsemestre_id)

View File

@ -15,7 +15,7 @@ import app
from app import db, log
from app.api import api_bp as bp, api_web_bp
from app.decorators import scodoc, permission_required
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.but import jury_but_recap
from app.models import FormSemestre, FormSemestreInscription, Identite
from app.scodoc.sco_permissions import Permission

View File

@ -35,7 +35,7 @@ from flask_login import login_required
from app.api import api_bp as bp, api_web_bp
from app.api import requested_format
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.models import Departement
from app.scodoc.sco_logos import list_logos, find_logo
from app.decorators import scodoc, permission_required
@ -49,10 +49,10 @@ from app.scodoc.sco_permissions import Permission
@permission_required(Permission.ScoView)
def api_get_glob_logos():
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
return error_response(401, message="accès interdit")
return json_error(403, message="accès interdit")
required_format = requested_format() # json only
if required_format is None:
return error_response(400, "Illegal format")
return json_error(400, "Illegal format")
logos = list_logos()[None]
return jsonify(list(logos.keys()))
@ -62,10 +62,10 @@ def api_get_glob_logos():
@permission_required(Permission.ScoView)
def api_get_glob_logo(logoname):
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
return error_response(401, message="accès interdit")
return json_error(403, message="accès interdit")
logo = find_logo(logoname=logoname)
if logo is None:
return error_response(404, message="logo not found")
return json_error(404, message="logo not found")
logo.select()
return send_file(
logo.filepath,
@ -80,7 +80,7 @@ def api_get_glob_logo(logoname):
def api_get_local_logos(departement):
dept_id = Departement.from_acronym(departement).id
if not g.current_user.has_permission(Permission.ScoChangePreferences, departement):
return error_response(401, message="accès interdit")
return json_error(403, message="accès interdit")
logos = list_logos().get(dept_id, dict())
return jsonify(list(logos.keys()))
@ -92,10 +92,10 @@ def api_get_local_logo(departement, logoname):
# format = requested_format("jpg", ['png', 'jpg']) XXX ?
dept_id = Departement.from_acronym(departement).id
if not g.current_user.has_permission(Permission.ScoChangePreferences, departement):
return error_response(401, message="accès interdit")
return json_error(403, message="accès interdit")
logo = find_logo(logoname=logoname, dept_id=dept_id)
if logo is None:
return error_response(404, message="logo not found")
return json_error(404, message="logo not found")
logo.select()
return send_file(
logo.filepath,

View File

@ -14,7 +14,7 @@ import app
from app import db, log
from app.api import api_bp as bp, api_web_bp
from app.decorators import scodoc, permission_required
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.models import FormSemestre, FormSemestreInscription, Identite
from app.models import GroupDescr, Partition
from app.models.groups import group_membership
@ -137,7 +137,7 @@ def etud_in_group_query(group_id: int):
"""Etudiants du groupe, filtrés par état"""
etat = request.args.get("etat")
if etat not in {scu.INSCRIT, scu.DEMISSION, scu.DEF}:
return error_response(404, "etat: valeur invalide")
return json_error(404, "etat: valeur invalide")
query = GroupDescr.query.filter_by(id=group_id)
if g.scodoc_dept:
query = (
@ -169,7 +169,7 @@ def set_etud_group(etudid: int, group_id: int):
)
group = query.first_or_404()
if etud.id not in {e.id for e in group.partition.formsemestre.etuds}:
return error_response(404, "etud non inscrit au formsemestre du groupe")
return json_error(404, "etud non inscrit au formsemestre du groupe")
groups = (
GroupDescr.query.filter_by(partition_id=group.partition.id)
.join(group_membership)
@ -261,13 +261,13 @@ def group_create(partition_id: int):
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
partition: Partition = query.first_or_404()
if not partition.groups_editable:
return error_response(404, "partition non editable")
return json_error(404, "partition non editable")
data = request.get_json(force=True) # may raise 400 Bad Request
group_name = data.get("group_name")
if group_name is None:
return error_response(404, "missing group name or invalid data format")
return json_error(404, "missing group name or invalid data format")
if not GroupDescr.check_name(partition, group_name):
return error_response(404, "invalid group_name")
return json_error(404, "invalid group_name")
group_name = group_name.strip()
group = GroupDescr(group_name=group_name, partition_id=partition_id)
@ -293,7 +293,7 @@ def group_delete(group_id: int):
)
group: GroupDescr = query.first_or_404()
if not group.partition.groups_editable:
return error_response(404, "partition non editable")
return json_error(404, "partition non editable")
formsemestre_id = group.partition.formsemestre_id
log(f"deleting {group}")
db.session.delete(group)
@ -317,12 +317,12 @@ def group_edit(group_id: int):
)
group: GroupDescr = query.first_or_404()
if not group.partition.groups_editable:
return error_response(404, "partition non editable")
return json_error(404, "partition non editable")
data = request.get_json(force=True) # may raise 400 Bad Request
group_name = data.get("group_name")
if group_name is not None:
if not GroupDescr.check_name(group.partition, group_name, existing=True):
return error_response(404, "invalid group_name")
return json_error(404, "invalid group_name")
group.group_name = group_name.strip()
db.session.add(group)
db.session.commit()
@ -358,14 +358,14 @@ def partition_create(formsemestre_id: int):
data = request.get_json(force=True) # may raise 400 Bad Request
partition_name = data.get("partition_name")
if partition_name is None:
return error_response(404, "missing partition_name or invalid data format")
return json_error(404, "missing partition_name or invalid data format")
if partition_name == scu.PARTITION_PARCOURS:
return error_response(404, f"invalid partition_name {scu.PARTITION_PARCOURS}")
return json_error(404, f"invalid partition_name {scu.PARTITION_PARCOURS}")
if not Partition.check_name(formsemestre, partition_name):
return error_response(404, "invalid partition_name")
return json_error(404, "invalid partition_name")
numero = data.get("numero", 0)
if not isinstance(numero, int):
return error_response(404, "invalid type for numero")
return json_error(404, "invalid type for numero")
args = {
"formsemestre_id": formsemestre_id,
"partition_name": partition_name.strip(),
@ -376,7 +376,7 @@ def partition_create(formsemestre_id: int):
boolean_field, False if boolean_field != "groups_editable" else True
)
if not isinstance(value, bool):
return error_response(404, f"invalid type for {boolean_field}")
return json_error(404, f"invalid type for {boolean_field}")
args[boolean_field] = value
partition = Partition(**args)
@ -407,7 +407,7 @@ def formsemestre_order_partitions(formsemestre_id: int):
if not isinstance(partition_ids, int) and not all(
isinstance(x, int) for x in partition_ids
):
return error_response(
return json_error(
404,
message="paramètre liste des partitions invalide",
)
@ -444,7 +444,7 @@ def partition_order_groups(partition_id: int):
if not isinstance(group_ids, int) and not all(
isinstance(x, int) for x in group_ids
):
return error_response(
return json_error(
404,
message="paramètre liste de groupe invalide",
)
@ -487,18 +487,18 @@ def partition_edit(partition_id: int):
#
if partition_name is not None and partition_name != partition.partition_name:
if partition.is_parcours():
return error_response(404, f"can't rename {scu.PARTITION_PARCOURS}")
return json_error(404, f"can't rename {scu.PARTITION_PARCOURS}")
if not Partition.check_name(
partition.formsemestre, partition_name, existing=True
):
return error_response(404, "invalid partition_name")
return json_error(404, "invalid partition_name")
partition.partition_name = partition_name.strip()
modified = True
numero = data.get("numero")
if numero is not None and numero != partition.numero:
if not isinstance(numero, int):
return error_response(404, "invalid type for numero")
return json_error(404, "invalid type for numero")
partition.numero = numero
modified = True
@ -506,9 +506,9 @@ def partition_edit(partition_id: int):
value = data.get(boolean_field)
if value is not None and value != getattr(partition, boolean_field):
if not isinstance(value, bool):
return error_response(404, f"invalid type for {boolean_field}")
return json_error(404, f"invalid type for {boolean_field}")
if boolean_field == "groups_editable" and partition.is_parcours():
return error_response(404, f"can't change {scu.PARTITION_PARCOURS}")
return json_error(404, f"can't change {scu.PARTITION_PARCOURS}")
setattr(partition, boolean_field, value)
modified = True
@ -540,7 +540,7 @@ def partition_delete(partition_id: int):
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
partition: Partition = query.first_or_404()
if not partition.partition_name:
return error_response(404, "ne peut pas supprimer la partition par défaut")
return json_error(404, "ne peut pas supprimer la partition par défaut")
is_parcours = partition.is_parcours()
formsemestre: FormSemestre = partition.formsemestre
log(f"deleting partition {partition}")

View File

@ -10,7 +10,7 @@ from flask_login import current_user
from sqlalchemy import desc, or_
from app import models
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.models import Departement, Identite, Admission
from app.scodoc.sco_permissions import Permission
@ -39,7 +39,7 @@ def get_etud(etudid=None, nip=None, ine=None) -> models.Identite:
elif ine is not None:
query = Identite.query.filter_by(code_ine=ine)
else:
return error_response(
return json_error(
404,
message="parametre manquant",
)

View File

@ -12,10 +12,10 @@
from flask import g, jsonify, request
from flask_login import current_user, login_required
import app
from app import db, log
from app.api import api_bp as bp, api_web_bp
from app.api.errors import error_response
from app.models.etudiants import Identite
from app.scodoc.sco_utils import json_error
from app.auth.models import User, Role, UserRole
from app.decorators import scodoc, permission_required
from app.models import Departement
@ -35,11 +35,11 @@ def user_info(uid: int):
"""
user: User = User.query.get(uid)
if user is None:
return error_response(404, "user not found")
return json_error(404, "user not found")
if g.scodoc_dept:
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersView)
if user.dept not in allowed_depts:
return error_response(404, "user not found")
return json_error(404, "user not found")
return jsonify(user.to_dict())
@ -101,20 +101,20 @@ def user_create():
data = request.get_json(force=True) # may raise 400 Bad Request
user_name = data.get("user_name")
if not user_name:
return error_response(404, "empty user_name")
return json_error(404, "empty user_name")
user = User.query.filter_by(user_name=user_name).first()
if user:
return error_response(404, f"user_create: user {user} already exists\n")
return json_error(404, f"user_create: user {user} already exists\n")
dept = data.get("dept")
if dept == "@all":
dept = None
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersAdmin)
if dept not in allowed_depts:
return error_response(403, "user_create: departement non autorise")
return json_error(403, "user_create: departement non autorise")
if (dept is not None) and (
Departement.query.filter_by(acronym=dept).first() is None
):
return error_response(404, "user_create: departement inexistant")
return json_error(404, "user_create: departement inexistant")
nom = data.get("nom")
prenom = data.get("prenom")
active = scu.to_bool(data.get("active", True))
@ -151,12 +151,12 @@ def user_edit(uid: int):
if (None not in allowed_depts) and (
(orig_dept not in allowed_depts) or (dest_dept not in allowed_depts)
):
return error_response(403, "user_edit: departement non autorise")
return json_error(403, "user_edit: departement non autorise")
if dest_dept != orig_dept:
if (dest_dept is not None) and (
Departement.query.filter_by(acronym=dest_dept).first() is None
):
return error_response(404, "user_edit: departement inexistant")
return json_error(404, "user_edit: departement inexistant")
user.dept = dest_dept
user.nom = data.get("nom", user.nom)
@ -189,7 +189,7 @@ def user_role_add(uid: int, role_name: str, dept: str = None):
_ = Departement.query.filter_by(acronym=dept).first_or_404()
allowed_depts = current_user.get_depts_with_permission(Permission.ScoSuperAdmin)
if (None not in allowed_depts) and (dept not in allowed_depts):
return error_response(403, "user_role_add: departement non autorise")
return json_error(403, "user_role_add: departement non autorise")
user.add_role(role, dept)
db.session.add(user)
db.session.commit()
@ -217,7 +217,7 @@ def user_role_remove(uid: int, role_name: str, dept: str = None):
_ = Departement.query.filter_by(acronym=dept).first_or_404()
allowed_depts = current_user.get_depts_with_permission(Permission.ScoSuperAdmin)
if (None not in allowed_depts) and (dept not in allowed_depts):
return error_response(403, "user_role_remove: departement non autorise")
return json_error(403, "user_role_remove: departement non autorise")
query = UserRole.query.filter(UserRole.role == role, UserRole.user == user)
if dept is not None:
@ -276,7 +276,7 @@ def role_permission_add(role_name: str, perm_name: str):
role: Role = Role.query.filter_by(name=role_name).first_or_404()
permission = Permission.get_by_name(perm_name)
if permission is None:
return error_response(404, "role_permission_add: permission inconnue")
return json_error(404, "role_permission_add: permission inconnue")
role.add_permission(permission)
db.session.add(role)
db.session.commit()
@ -299,7 +299,7 @@ def role_permission_remove(role_name: str, perm_name: str):
role: Role = Role.query.filter_by(name=role_name).first_or_404()
permission = Permission.get_by_name(perm_name)
if permission is None:
return error_response(404, "role_permission_remove: permission inconnue")
return json_error(404, "role_permission_remove: permission inconnue")
role.remove_permission(permission)
db.session.add(role)
db.session.commit()
@ -319,7 +319,7 @@ def role_create(role_name: str):
"""
role: Role = Role.query.filter_by(name=role_name).first()
if role:
return error_response(404, "role_create: role already exists")
return json_error(404, "role_create: role already exists")
role = Role(name=role_name)
data = request.get_json(force=True) # may raise 400 Bad Request
permissions = data.get("permissions")
@ -327,7 +327,7 @@ def role_create(role_name: str):
try:
role.set_named_permissions(permissions)
except ScoValueError:
return error_response(404, "role_create: invalid permissions")
return json_error(404, "role_create: invalid permissions")
db.session.add(role)
db.session.commit()
return jsonify(role.to_dict())
@ -352,12 +352,12 @@ def role_edit(role_name: str):
try:
role.set_named_permissions(permissions)
except ScoValueError:
return error_response(404, "role_create: invalid permissions")
return json_error(404, "role_create: invalid permissions")
role_name = data.get("role_name")
if role_name and role_name != role.name:
existing_role: Role = Role.query.filter_by(name=role_name).first()
if existing_role:
return error_response(404, "role_edit: role name already exists")
return json_error(404, "role_edit: role name already exists")
role.name = role_name
db.session.add(role)
db.session.commit()

View File

@ -9,7 +9,7 @@ from flask import g, redirect, request, url_for
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth
import flask_login
from app import login
from app.api.errors import error_response
from app.scodoc.sco_utils import json_error
from app.auth.models import User
basic_auth = HTTPBasicAuth()
@ -80,8 +80,6 @@ def load_user_from_request(req: flask.Request) -> User:
@login.unauthorized_handler
def unauthorized():
"flask-login: si pas autorisé, redirige vers page login, sauf si API"
from app.api.errors import error_response as api_error_response
if request.blueprint == "api" or request.blueprint == "apiweb":
return api_error_response(http.HTTPStatus.UNAUTHORIZED, "Non autorise (logic)")
return json_error(http.HTTPStatus.UNAUTHORIZED, "Non autorise (logic)")
return redirect(url_for("auth.login"))

View File

@ -10,11 +10,10 @@
import collections
import datetime
import numpy as np
from flask import url_for, g
from flask import g, has_request_context, url_for
from app.comp.res_but import ResultatsSemestreBUT
from app.models import FormSemestre, Identite
from app.models import but_validations
from app.models.groups import GroupDescr
from app.models.ues import UniteEns
from app.scodoc import sco_bulletins, sco_utils as scu
@ -170,7 +169,9 @@ class BulletinBUT:
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl.id,
),
)
if has_request_context()
else "na",
"moyenne": {
# # moyenne indicative de module: moyenne des UE,
# # ignorant celles sans notes (nan)
@ -228,7 +229,9 @@ class BulletinBUT:
"notes.evaluation_listenotes",
scodoc_dept=g.scodoc_dept,
evaluation_id=e.id,
),
)
if has_request_context()
else "na",
}
return d

View File

@ -6,7 +6,7 @@
import datetime
from functools import cached_property
from flask import abort, url_for
from flask import abort, has_request_context, url_for
from flask import g, request
import sqlalchemy
from sqlalchemy import desc, text
@ -196,7 +196,8 @@ class Identite(db.Model):
"nationalite": self.nationalite or "",
"boursier": self.boursier or "",
}
if include_urls:
if include_urls and has_request_context():
# test request context so we can use this func in tests under the flask shell
d["fiche_url"] = url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=self.id
)

View File

@ -37,6 +37,7 @@ from flask_login import current_user
from app import email
from app import log
from app.scodoc.sco_utils import json_error
from app.but import bulletin_but
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
@ -74,9 +75,13 @@ def get_formsemestre_bulletin_etud_json(
) -> str:
"""Le JSON du bulletin d'un étudiant, quel que soit le type de formation."""
if formsemestre.formation.is_apc():
r = bulletin_but.BulletinBUT(formsemestre)
bul = bulletin_but.BulletinBUT(formsemestre)
if not etud.id in bul.res.identdict:
return error_response(
404, "get_formsemestre_bulletin_etud_json: invalid etud"
)
return jsonify(
r.bulletin_etud(
bul.bulletin_etud(
etud,
formsemestre,
force_publishing=force_publishing,

View File

@ -53,6 +53,7 @@ import requests
import flask
from flask import g, request
from flask import flash, url_for, make_response, jsonify
from werkzeug.http import HTTP_STATUS_CODES
from config import Config
from app import log
@ -819,15 +820,26 @@ def get_request_args():
return vals
def json_error(message, success=False, status=404):
def json_error(status_code, message=None):
"""Simple JSON response, for errors"""
response = {
"success": success,
"status": status,
"message": message,
payload = {
"error": HTTP_STATUS_CODES.get(status_code, "Unknown error"),
"status": status_code,
}
if message:
payload["message"] = message
response = jsonify(payload)
response.status_code = status_code
log(f"Error: {response}")
return jsonify(response), status
return response
def json_ok_response(status_code=200, payload=None):
"""Simple JSON respons for "success" """
payload = payload or {"OK": True}
response = jsonify(payload)
response.status_code = status_code
return response
def get_scodoc_version():

View File

@ -36,17 +36,22 @@ import time
from xml.etree import ElementTree
import flask
from flask import abort, flash, jsonify, redirect, render_template, url_for
from flask import abort, flash, redirect, render_template, url_for
from flask import current_app, g, request
from flask_login import current_user
from app import db
from app import models
from app.auth.models import User
from app.but import apc_edit_ue, jury_but_recap
from app.but import jury_but, jury_but_validation_auto
from app.but.forms import jury_but_forms
from app.but import jury_but_pv
from app.but import jury_but_view
from app.comp import res_sem
from app.comp.res_but import ResultatsSemestreBUT
from app.comp.res_compat import NotesTableCompat
from app.models import ScolarNews
from app.models.config import ScoDocSiteConfig
from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre
@ -54,13 +59,8 @@ from app.models.formsemestre import FormSemestreUEComputationExpr
from app.models.moduleimpls import ModuleImpl
from app.models.modules import Module
from app.models.ues import UniteEns
from app.views import notes_bp as bp
from app import api
from app import db
from app import models
from app.models import ScolarNews, but_validations
from app.auth.models import User
from app.but import apc_edit_ue, jury_but_recap
from app.decorators import (
scodoc,
scodoc7func,
@ -68,7 +68,6 @@ from app.decorators import (
permission_required_compat_scodoc7,
)
from app.views import notes_bp as bp
# ---------------
@ -775,7 +774,7 @@ def formsemestre_list(
formsemestre_id = int(formsemestre_id) if formsemestre_id is not None else None
formation_id = int(formation_id) if formation_id is not None else None
except ValueError:
return api.errors.error_response(404, "invalid id")
return scu.json_error(404, "invalid id")
# XAPI: new json api
args = {}
L = locals()

View File

@ -128,26 +128,26 @@ def set_module_ue_coef():
try:
module_id = int(request.form["module_id"])
except ValueError:
return scu.json_error("invalid module_id", 400)
return scu.json_error(404, "invalid module_id")
try:
ue_id = int(request.form["ue_id"])
except ValueError:
return scu.json_error("invalid ue_id", 400)
return scu.json_error(404, "invalid ue_id")
try:
coef = float(request.form["coef"].replace(",", "."))
except ValueError:
return scu.json_error("invalid coef", 400)
return scu.json_error(404, "invalid coef")
module = models.Module.query.get(module_id)
if module is None:
return scu.json_error(f"module not found ({module_id})", 404)
return scu.json_error(404, f"module not found ({module_id})")
ue = models.UniteEns.query.get(ue_id)
if not ue:
return scu.json_error(f"UE not found ({ue_id})", 404)
return scu.json_error(404, f"UE not found ({ue_id})")
module.set_ue_coef(ue, coef)
db.session.commit()
module.formation.invalidate_cached_sems()
return scu.json_error("ok", success=True, status=201)
return scu.json_ok_response(201)
@bp.route("/edit_modules_ue_coefs")

View File

@ -55,24 +55,24 @@ class APIError(Exception):
pass
def GET(path: str, headers={}, errmsg=None, dept=None):
def GET(path: str, headers: dict = None, errmsg=None, dept=None):
"""Get and returns as JSON"""
if dept:
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
else:
url = API_URL + path
r = requests.get(url, headers=headers or CUR_HEADERS, verify=CHECK_CERTIFICATE)
r = requests.get(url, headers=headers or {}, verify=CHECK_CERTIFICATE)
if r.status_code != 200:
raise APIError(errmsg or f"""erreur status={r.status_code} !\n{r.text}""")
return r.json() # decode la reponse JSON
def POST_JSON(path: str, data: dict = {}, headers={}, errmsg=None):
def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None):
"""Post"""
r = requests.post(
API_URL + path,
json=data,
headers=headers,
headers=headers or {},
verify=CHECK_CERTIFICATE,
)
if r.status_code != 200:

View File

@ -46,7 +46,7 @@ def test_admin_access(create_admin_token):
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client:
response = client.get(API_URL + "/logos", headers=headers)
assert response.status_code == 401
assert response.status_code == 403
def test_lambda_access(create_lambda_token):
@ -57,7 +57,7 @@ def test_lambda_access(create_lambda_token):
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client:
response = client.get(API_URL + "/logos", headers=headers)
assert response.status_code == 401
assert response.status_code == 403
def test_initial_with_header_and_footer(create_super_token):

View File

@ -201,33 +201,23 @@ def create_formsemestre(
def inscrit_etudiants(etuds: list, formsemestre: FormSemestre):
"""Inscrit les etudiants aux semestres et à tous ses modules"""
for etud in etuds:
aleatoire = random.randint(0, 10)
if aleatoire <= 3:
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
formsemestre.id,
etud.id,
group_ids=[],
etat="I",
method="init db test",
)
elif 3 < aleatoire <= 6:
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
formsemestre.id,
etud.id,
group_ids=[],
etat="D",
method="init db test",
)
"""Inscrit les étudiants au semestre et à tous ses modules.
1/5 DEF, 1/5 DEF
"""
for i, etud in enumerate(etuds):
if (i + 1) % 5 == 0:
etat = "D"
elif (i + 2) % 5 == 0:
etat = "DEF"
else:
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
formsemestre.id,
etud.id,
group_ids=[],
etat="DEF",
method="init db test",
)
etat = "I"
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
formsemestre.id,
etud.id,
group_ids=[],
etat=etat,
method="init db test",
)
def create_evaluations(formsemestre: FormSemestre):
@ -368,11 +358,11 @@ def init_test_database():
mapp.set_sco_dept(dept.acronym)
user_lecteur, user_autre = create_users(depts)
with sco_cache.DeferredSemCacheManager():
etuds = create_etuds(dept)
etuds = create_etuds(dept, nb=20)
formation = import_formation(dept.id)
formsemestre = create_formsemestre(formation, user_lecteur)
create_evaluations(formsemestre)
inscrit_etudiants(etuds, formsemestre)
inscrit_etudiants(etuds[:16], formsemestre)
saisie_notes_evaluations(formsemestre, user_lecteur)
add_absences(formsemestre)
create_etape_apo(formsemestre)