forked from ScoDoc/ScoDoc
Modification contrôle d'accès. Routes API basic+token. Revision routes API.
This commit is contained in:
parent
c9a6fe0743
commit
dcd7cf78fd
@ -41,6 +41,7 @@ migrate = Migrate(compare_type=True)
|
|||||||
login = LoginManager()
|
login = LoginManager()
|
||||||
login.login_view = "auth.login"
|
login.login_view = "auth.login"
|
||||||
login.login_message = "Identifiez-vous pour accéder à cette page."
|
login.login_message = "Identifiez-vous pour accéder à cette page."
|
||||||
|
|
||||||
mail = Mail()
|
mail = Mail()
|
||||||
bootstrap = Bootstrap()
|
bootstrap = Bootstrap()
|
||||||
moment = Moment()
|
moment = Moment()
|
||||||
@ -249,8 +250,8 @@ def create_app(config_class=DevConfig):
|
|||||||
from app.views import notes_bp
|
from app.views import notes_bp
|
||||||
from app.views import users_bp
|
from app.views import users_bp
|
||||||
from app.views import absences_bp
|
from app.views import absences_bp
|
||||||
from app.api import bp as api_bp
|
from app.api import api_bp
|
||||||
from app.api import api_web_bp as api_web_bp
|
from app.api import api_web_bp
|
||||||
|
|
||||||
# https://scodoc.fr/ScoDoc
|
# https://scodoc.fr/ScoDoc
|
||||||
app.register_blueprint(scodoc_bp)
|
app.register_blueprint(scodoc_bp)
|
||||||
@ -265,7 +266,7 @@ def create_app(config_class=DevConfig):
|
|||||||
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
|
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
|
||||||
)
|
)
|
||||||
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
|
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
|
||||||
app.register_blueprint(api_web_bp, url_prefix="/ScoDoc/<scodoc_dept>/apiweb")
|
app.register_blueprint(api_web_bp, url_prefix="/ScoDoc/<scodoc_dept>/api")
|
||||||
|
|
||||||
scodoc_log_formatter = LogRequestFormatter(
|
scodoc_log_formatter = LogRequestFormatter(
|
||||||
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
|
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
bp = Blueprint("api", __name__)
|
api_bp = Blueprint("api", __name__)
|
||||||
api_web_bp = Blueprint("apiweb", __name__)
|
api_web_bp = Blueprint("apiweb", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
from flask import jsonify
|
from flask import jsonify
|
||||||
|
|
||||||
from app.api import bp
|
from app.api import api_bp as bp
|
||||||
from app.api.errors import error_response
|
from app.api.errors import error_response
|
||||||
from app.api.auth import permission_required_api
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import Identite
|
from app.models import Identite
|
||||||
|
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
@ -21,7 +21,8 @@ from app.scodoc.sco_permissions import Permission
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/absences/etudid/<int:etudid>", methods=["GET"])
|
@bp.route("/absences/etudid/<int:etudid>", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def absences(etudid: int = None):
|
def absences(etudid: int = None):
|
||||||
"""
|
"""
|
||||||
Retourne la liste des absences d'un étudiant donné
|
Retourne la liste des absences d'un étudiant donné
|
||||||
@ -65,7 +66,8 @@ def absences(etudid: int = None):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/absences/etudid/<int:etudid>/just", methods=["GET"])
|
@bp.route("/absences/etudid/<int:etudid>/just", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def absences_just(etudid: int = None):
|
def absences_just(etudid: int = None):
|
||||||
"""
|
"""
|
||||||
Retourne la liste des absences justifiées d'un étudiant donné
|
Retourne la liste des absences justifiées d'un étudiant donné
|
||||||
@ -120,7 +122,8 @@ def absences_just(etudid: int = None):
|
|||||||
"/absences/abs_group_etat/group_id/<int:group_id>/date_debut/<string:date_debut>/date_fin/<string:date_fin>",
|
"/absences/abs_group_etat/group_id/<int:group_id>/date_debut/<string:date_debut>/date_fin/<string:date_fin>",
|
||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
)
|
)
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def abs_groupe_etat(group_id: int, date_debut=None, date_fin=None):
|
def abs_groupe_etat(group_id: int, date_debut=None, date_fin=None):
|
||||||
"""
|
"""
|
||||||
Liste des absences d'un groupe (possibilité de choisir entre deux dates)
|
Liste des absences d'un groupe (possibilité de choisir entre deux dates)
|
||||||
|
148
app/api/auth.py
148
app/api/auth.py
@ -1,148 +0,0 @@
|
|||||||
# -*- coding: UTF-8 -*
|
|
||||||
# Authentication code borrowed from Miguel Grinberg's Mega Tutorial
|
|
||||||
# (see https://github.com/miguelgrinberg/microblog)
|
|
||||||
# and modified for ScoDoc
|
|
||||||
|
|
||||||
# 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 functools import wraps
|
|
||||||
|
|
||||||
from flask import g
|
|
||||||
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth
|
|
||||||
import flask_login
|
|
||||||
from flask_login import current_user
|
|
||||||
|
|
||||||
from app import log
|
|
||||||
from app.auth.models import User
|
|
||||||
from app.api import bp, api_web_bp
|
|
||||||
from app.api.errors import error_response
|
|
||||||
from app.decorators import scodoc, permission_required
|
|
||||||
|
|
||||||
basic_auth = HTTPBasicAuth()
|
|
||||||
token_auth = HTTPTokenAuth()
|
|
||||||
|
|
||||||
|
|
||||||
@basic_auth.verify_password
|
|
||||||
def verify_password(username, password):
|
|
||||||
"Verify password for this user"
|
|
||||||
user: User = User.query.filter_by(user_name=username).first()
|
|
||||||
if user and user.check_password(password):
|
|
||||||
g.current_user = user
|
|
||||||
# note: est aussi basic_auth.current_user()
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
@basic_auth.error_handler
|
|
||||||
def basic_auth_error(status):
|
|
||||||
"error response (401 for invalid auth.)"
|
|
||||||
return error_response(status)
|
|
||||||
|
|
||||||
|
|
||||||
@token_auth.verify_token
|
|
||||||
def verify_token(token) -> User:
|
|
||||||
"""Retrouve l'utilisateur à partir du jeton.
|
|
||||||
Si la requête n'a pas de jeton, token == "".
|
|
||||||
"""
|
|
||||||
|
|
||||||
user = User.check_token(token) if token else None
|
|
||||||
if user is not None:
|
|
||||||
flask_login.login_user(user)
|
|
||||||
g.current_user = user
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
@token_auth.error_handler
|
|
||||||
def token_auth_error(status):
|
|
||||||
"Réponse en cas d'erreur d'auth."
|
|
||||||
return error_response(status)
|
|
||||||
|
|
||||||
|
|
||||||
@token_auth.get_user_roles
|
|
||||||
def get_user_roles(user):
|
|
||||||
return user.roles
|
|
||||||
|
|
||||||
|
|
||||||
def token_permission_required(permission):
|
|
||||||
"Décorateur pour les fonctions de l'API ScoDoc"
|
|
||||||
|
|
||||||
def decorator(f):
|
|
||||||
@wraps(f)
|
|
||||||
def decorated_function(*args, **kwargs):
|
|
||||||
current_user = basic_auth.current_user()
|
|
||||||
if not current_user or not current_user.has_permission(permission, None):
|
|
||||||
if current_user:
|
|
||||||
message = f"API permission denied (user {current_user})"
|
|
||||||
else:
|
|
||||||
message = f"API permission denied (no user supplied)"
|
|
||||||
log(message)
|
|
||||||
# raise werkzeug.exceptions.Forbidden(description=message)
|
|
||||||
return error_response(403, message=None)
|
|
||||||
if not hasattr(g, "scodoc_dept"):
|
|
||||||
g.scodoc_dept = None
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
|
|
||||||
# return decorated_function(token_auth.login_required())
|
|
||||||
return decorated_function
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def permission_required_api(permission_web, permission_api):
|
|
||||||
"""Décorateur pour les fonctions de l'API accessibles en mode jeton
|
|
||||||
ou en mode web.
|
|
||||||
Si cookie d'authentification web, utilise pour se logger et calculer les
|
|
||||||
permissions.
|
|
||||||
Sinon, tente le jeton jwt.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(f):
|
|
||||||
@wraps(f)
|
|
||||||
def decorated_function(*args, **kwargs):
|
|
||||||
scodoc_dept = getattr(g, "scodoc_dept", None)
|
|
||||||
if not current_user.has_permission(permission_web, scodoc_dept):
|
|
||||||
# try API
|
|
||||||
return token_auth.login_required(
|
|
||||||
token_permission_required(permission_api)(f)
|
|
||||||
)(*args, **kwargs)
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
|
|
||||||
return decorated_function
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def web_publish(route, function, permission, methods=("GET",)):
|
|
||||||
"""Declare a route for a python function protected by permission
|
|
||||||
using web http cookie
|
|
||||||
"""
|
|
||||||
return api_web_bp.route(route, methods=methods)(
|
|
||||||
scodoc(permission_required(permission)(function))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def api_publish(route, function, permission, methods=("GET",)):
|
|
||||||
"""Declare a route for a python function protected by permission
|
|
||||||
using API token
|
|
||||||
"""
|
|
||||||
return bp.route(route, methods=methods)(
|
|
||||||
token_auth.login_required(token_permission_required(permission)(function))
|
|
||||||
)
|
|
@ -4,8 +4,8 @@ from flask import jsonify
|
|||||||
|
|
||||||
import app
|
import app
|
||||||
from app import models
|
from app import models
|
||||||
from app.api import bp
|
from app.api import api_bp as bp
|
||||||
from app.api.auth import permission_required_api
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import Departement, FormSemestre
|
from app.models import Departement, FormSemestre
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
@ -22,21 +22,24 @@ def get_departement(dept_ident: str) -> Departement:
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/departements", methods=["GET"])
|
@bp.route("/departements", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def departements():
|
def departements():
|
||||||
"""Liste les départements"""
|
"""Liste les départements"""
|
||||||
return jsonify([dept.to_dict() for dept in Departement.query])
|
return jsonify([dept.to_dict() for dept in Departement.query])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/departements_ids", methods=["GET"])
|
@bp.route("/departements_ids", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def departements_ids():
|
def departements_ids():
|
||||||
"""Liste des ids de départements"""
|
"""Liste des ids de départements"""
|
||||||
return jsonify([dept.id for dept in Departement.query])
|
return jsonify([dept.id for dept in Departement.query])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/departement/<string:acronym>", methods=["GET"])
|
@bp.route("/departement/<string:acronym>", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def departement(acronym: str):
|
def departement(acronym: str):
|
||||||
"""
|
"""
|
||||||
Info sur un département. Accès par acronyme.
|
Info sur un département. Accès par acronyme.
|
||||||
@ -55,7 +58,8 @@ def departement(acronym: str):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/departement/id/<int:dept_id>", methods=["GET"])
|
@bp.route("/departement/id/<int:dept_id>", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def departement_by_id(dept_id: int):
|
def departement_by_id(dept_id: int):
|
||||||
"""
|
"""
|
||||||
Info sur un département. Accès par id.
|
Info sur un département. Accès par id.
|
||||||
@ -65,7 +69,8 @@ def departement_by_id(dept_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/departement/<string:acronym>/etudiants", methods=["GET"])
|
@bp.route("/departement/<string:acronym>/etudiants", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def dept_etudiants(acronym: str):
|
def dept_etudiants(acronym: str):
|
||||||
"""
|
"""
|
||||||
Retourne la liste des étudiants d'un département
|
Retourne la liste des étudiants d'un département
|
||||||
@ -93,7 +98,8 @@ def dept_etudiants(acronym: str):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/departement/id/<int:dept_id>/etudiants", methods=["GET"])
|
@bp.route("/departement/id/<int:dept_id>/etudiants", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def dept_etudiants_by_id(dept_id: int):
|
def dept_etudiants_by_id(dept_id: int):
|
||||||
"""
|
"""
|
||||||
Retourne la liste des étudiants d'un département d'id donné.
|
Retourne la liste des étudiants d'un département d'id donné.
|
||||||
@ -103,7 +109,8 @@ def dept_etudiants_by_id(dept_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/departement/<string:acronym>/formsemestres_ids", methods=["GET"])
|
@bp.route("/departement/<string:acronym>/formsemestres_ids", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def dept_formsemestres_ids(acronym: str):
|
def dept_formsemestres_ids(acronym: str):
|
||||||
"""liste des ids formsemestre du département"""
|
"""liste des ids formsemestre du département"""
|
||||||
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||||
@ -111,7 +118,8 @@ def dept_formsemestres_ids(acronym: str):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/departement/id/<int:dept_id>/formsemestres_ids", methods=["GET"])
|
@bp.route("/departement/id/<int:dept_id>/formsemestres_ids", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def dept_formsemestres_ids_by_id(dept_id: int):
|
def dept_formsemestres_ids_by_id(dept_id: int):
|
||||||
"""liste des ids formsemestre du département"""
|
"""liste des ids formsemestre du département"""
|
||||||
dept = Departement.query.get_or_404(dept_id)
|
dept = Departement.query.get_or_404(dept_id)
|
||||||
@ -119,7 +127,8 @@ def dept_formsemestres_ids_by_id(dept_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/departement/<string:acronym>/formsemestres_courants", methods=["GET"])
|
@bp.route("/departement/<string:acronym>/formsemestres_courants", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def dept_formsemestres_courants(acronym: str):
|
def dept_formsemestres_courants(acronym: str):
|
||||||
"""
|
"""
|
||||||
Liste des semestres actifs d'un département d'acronyme donné
|
Liste des semestres actifs d'un département d'acronyme donné
|
||||||
@ -173,7 +182,8 @@ def dept_formsemestres_courants(acronym: str):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/departement/id/<int:dept_id>/formsemestres_courants", methods=["GET"])
|
@bp.route("/departement/id/<int:dept_id>/formsemestres_courants", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def dept_formsemestres_courants_by_id(dept_id: int):
|
def dept_formsemestres_courants_by_id(dept_id: int):
|
||||||
"""
|
"""
|
||||||
Liste des semestres actifs d'un département d'id donné
|
Liste des semestres actifs d'un département d'id donné
|
||||||
|
@ -10,41 +10,46 @@
|
|||||||
|
|
||||||
from flask import g, jsonify
|
from flask import g, jsonify
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
from flask_login import login_required
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
|
|
||||||
import app
|
import app
|
||||||
from app.api import bp
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.api.errors import error_response
|
from app.api.errors import error_response
|
||||||
from app.api.auth import permission_required_api, api_publish, web_publish
|
|
||||||
from app.api import tools
|
from app.api import tools
|
||||||
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import Departement, FormSemestreInscription, FormSemestre, Identite
|
from app.models import Departement, FormSemestreInscription, FormSemestre, Identite
|
||||||
from app.scodoc import sco_bulletins
|
from app.scodoc import sco_bulletins
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud
|
from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
# Un exemple:
|
||||||
|
@bp.route("/api_function/<int:arg>")
|
||||||
|
@api_web_bp.route("/api_function/<int:arg>")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def api_function(arg: int):
|
def api_function(arg: int):
|
||||||
"""Une fonction quelconque de l'API"""
|
"""Une fonction quelconque de l'API"""
|
||||||
# u = current_user
|
return jsonify(
|
||||||
# dept = g.scodoc_dept # peut être None si accès API
|
{"current_user": current_user.to_dict(), "arg": arg, "dept": g.scodoc_dept}
|
||||||
return jsonify({"current_user": current_user.to_dict(), "dept": g.scodoc_dept})
|
)
|
||||||
|
|
||||||
|
|
||||||
api_publish("/api_function/<int:arg>", api_function, Permission.APIView)
|
|
||||||
web_publish("/api_function/<int:arg>", api_function, Permission.ScoView)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/etudiants/courants", defaults={"long": False})
|
@bp.route("/etudiants/courants", defaults={"long": False})
|
||||||
@bp.route("/etudiants/courants/long", defaults={"long": True})
|
@bp.route("/etudiants/courants/long", defaults={"long": True})
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/etudiants/courants", defaults={"long": False})
|
||||||
|
@api_web_bp.route("/etudiants/courants/long", defaults={"long": True})
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def etudiants_courants(long=False):
|
def etudiants_courants(long=False):
|
||||||
"""
|
"""
|
||||||
La liste des étudiants des semestres "courants" (tous département)
|
La liste des étudiants des semestres "courants" (tous départements)
|
||||||
(date du jour comprise dans la période couverte par le sem.)
|
(date du jour comprise dans la période couverte par le sem.)
|
||||||
dans lesquels l'utilisateur a le rôle APIView (donc tous si le dept du
|
dans lesquels l'utilisateur a la permission ScoView
|
||||||
rôle est None).
|
(donc tous si le dept du rôle est None).
|
||||||
|
|
||||||
Exemple de résultat :
|
Exemple de résultat :
|
||||||
[
|
[
|
||||||
@ -89,9 +94,7 @@ def etudiants_courants(long=False):
|
|||||||
"villedomicile": "VALPARAISO",
|
"villedomicile": "VALPARAISO",
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
allowed_depts = current_user.get_depts_with_permission(
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
||||||
Permission.APIView | Permission.ScoView
|
|
||||||
)
|
|
||||||
etuds = Identite.query.filter(
|
etuds = Identite.query.filter(
|
||||||
Identite.id == FormSemestreInscription.etudid,
|
Identite.id == FormSemestreInscription.etudid,
|
||||||
FormSemestreInscription.formsemestre_id == FormSemestre.id,
|
FormSemestreInscription.formsemestre_id == FormSemestre.id,
|
||||||
@ -110,10 +113,15 @@ def etudiants_courants(long=False):
|
|||||||
return jsonify(data)
|
return jsonify(data)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/etudiant/etudid/<int:etudid>", methods=["GET"])
|
@bp.route("/etudiant/etudid/<int:etudid>")
|
||||||
@bp.route("/etudiant/nip/<string:nip>", methods=["GET"])
|
@bp.route("/etudiant/nip/<string:nip>")
|
||||||
@bp.route("/etudiant/ine/<string:ine>", methods=["GET"])
|
@bp.route("/etudiant/ine/<string:ine>")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/etudiant/etudid/<int:etudid>")
|
||||||
|
@api_web_bp.route("/etudiant/nip/<string:nip>")
|
||||||
|
@api_web_bp.route("/etudiant/ine/<string:ine>")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def etudiant(etudid: int = None, nip: str = None, ine: str = None):
|
def etudiant(etudid: int = None, nip: str = None, ine: str = None):
|
||||||
"""
|
"""
|
||||||
Retourne les informations de l'étudiant correspondant, ou 404 si non trouvé.
|
Retourne les informations de l'étudiant correspondant, ou 404 si non trouvé.
|
||||||
@ -167,7 +175,11 @@ def etudiant(etudid: int = None, nip: str = None, ine: str = None):
|
|||||||
@bp.route("/etudiants/etudid/<int:etudid>", methods=["GET"])
|
@bp.route("/etudiants/etudid/<int:etudid>", methods=["GET"])
|
||||||
@bp.route("/etudiants/nip/<string:nip>", methods=["GET"])
|
@bp.route("/etudiants/nip/<string:nip>", methods=["GET"])
|
||||||
@bp.route("/etudiants/ine/<string:ine>", methods=["GET"])
|
@bp.route("/etudiants/ine/<string:ine>", methods=["GET"])
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/etudiants/etudid/<int:etudid>", methods=["GET"])
|
||||||
|
@api_web_bp.route("/etudiants/nip/<string:nip>", methods=["GET"])
|
||||||
|
@api_web_bp.route("/etudiants/ine/<string:ine>", methods=["GET"])
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def etudiants(etudid: int = None, nip: str = None, ine: str = None):
|
def etudiants(etudid: int = None, nip: str = None, ine: str = None):
|
||||||
"""
|
"""
|
||||||
Info sur le ou les étudiants correspondant. Comme /etudiant mais renvoie
|
Info sur le ou les étudiants correspondant. Comme /etudiant mais renvoie
|
||||||
@ -176,9 +188,7 @@ def etudiants(etudid: int = None, nip: str = None, ine: str = None):
|
|||||||
Dans 99% des cas, la liste contient un seul étudiant, mais si l'étudiant a
|
Dans 99% des cas, la liste contient un seul étudiant, mais si l'étudiant a
|
||||||
été inscrit dans plusieurs départements, on a plusieurs objets (1 par dept.).
|
été inscrit dans plusieurs départements, on a plusieurs objets (1 par dept.).
|
||||||
"""
|
"""
|
||||||
allowed_depts = current_user.get_depts_with_permission(
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
||||||
Permission.APIView | Permission.ScoView
|
|
||||||
)
|
|
||||||
if etudid is not None:
|
if etudid is not None:
|
||||||
query = Identite.query.filter_by(id=etudid)
|
query = Identite.query.filter_by(id=etudid)
|
||||||
elif nip is not None:
|
elif nip is not None:
|
||||||
@ -201,14 +211,20 @@ def etudiants(etudid: int = None, nip: str = None, ine: str = None):
|
|||||||
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
||||||
@bp.route("/etudiant/nip/<string:nip>/formsemestres")
|
@bp.route("/etudiant/nip/<string:nip>/formsemestres")
|
||||||
@bp.route("/etudiant/ine/<string:ine>/formsemestres")
|
@bp.route("/etudiant/ine/<string:ine>/formsemestres")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
||||||
|
@api_web_bp.route("/etudiant/nip/<string:nip>/formsemestres")
|
||||||
|
@api_web_bp.route("/etudiant/ine/<string:ine>/formsemestres")
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None):
|
def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None):
|
||||||
"""
|
"""
|
||||||
Liste des semestres qu'un étudiant a suivi, triés par ordre chronologique.
|
Liste des semestres qu'un étudiant a suivi, triés par ordre chronologique.
|
||||||
Attention, si accès via NIP ou INE, les semestres peuvent être de départements différents
|
Accès par etudid, nip ou ine.
|
||||||
(si l'étudiant a changé de département). L'id du département est `dept_id`.
|
|
||||||
|
|
||||||
Accès par etudid, nip ou ine
|
Attention, si accès via NIP ou INE, les semestres peuvent être de départements
|
||||||
|
différents (si l'étudiant a changé de département). L'id du département est `dept_id`.
|
||||||
|
|
||||||
|
Si accès par département, ne retourne que les formsemestre suivis dans le département.
|
||||||
|
|
||||||
Exemple de résultat :
|
Exemple de résultat :
|
||||||
[
|
[
|
||||||
@ -265,6 +281,9 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
|||||||
message="parametre manquant",
|
message="parametre manquant",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if g.scodoc_dept is not None:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
|
||||||
formsemestres = query.order_by(FormSemestre.date_debut)
|
formsemestres = query.order_by(FormSemestre.date_debut)
|
||||||
|
|
||||||
return jsonify(
|
return jsonify(
|
||||||
@ -287,22 +306,12 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
|||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
defaults={"version": "long", "pdf": False},
|
defaults={"version": "long", "pdf": False},
|
||||||
)
|
)
|
||||||
# Version PDF non fonctionnelle
|
# Version PDF non testée
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
defaults={"version": "long", "pdf": True},
|
defaults={"version": "long", "pdf": True},
|
||||||
)
|
)
|
||||||
# @bp.route(
|
|
||||||
# "/etudiant/nip/<string:nip>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
|
||||||
# methods=["GET"],
|
|
||||||
# defaults={"version": "long", "pdf": True},
|
|
||||||
# )
|
|
||||||
# @bp.route(
|
|
||||||
# "/etudiant/ine/<string:ine>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
|
||||||
# methods=["GET"],
|
|
||||||
# defaults={"version": "long", "pdf": True},
|
|
||||||
# )
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/short",
|
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/short",
|
||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
@ -333,8 +342,60 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
|||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
defaults={"version": "short", "pdf": True},
|
defaults={"version": "short", "pdf": True},
|
||||||
)
|
)
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route(
|
||||||
def etudiant_bulletin_semestre( # XXX TODO Ajouter la possibilité de retourner en version pdf
|
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "long", "pdf": False},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/etudiant/nip/<string:nip>/formsemestre/<int:formsemestre_id>/bulletin",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "long", "pdf": False},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/etudiant/ine/<string:ine>/formsemestre/<int:formsemestre_id>/bulletin",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "long", "pdf": False},
|
||||||
|
)
|
||||||
|
# Version PDF non testée
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "long", "pdf": True},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/short",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "short", "pdf": False},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/etudiant/nip/<string:nip>/formsemestre/<int:formsemestre_id>/bulletin/short",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "short", "pdf": False},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/etudiant/ine/<string:ine>/formsemestre/<int:formsemestre_id>/bulletin/short",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "short", "pdf": False},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/short/pdf",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "short", "pdf": True},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/etudiant/nip/<string:nip>/formsemestre/<int:formsemestre_id>/bulletin/short/pdf",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "short", "pdf": True},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/etudiant/ine/<string:ine>/formsemestre/<int:formsemestre_id>/bulletin/short/pdf",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "short", "pdf": True},
|
||||||
|
)
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def etudiant_bulletin_semestre(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etudid: int = None,
|
etudid: int = None,
|
||||||
nip: str = None,
|
nip: str = None,
|
||||||
@ -354,7 +415,8 @@ def etudiant_bulletin_semestre( # XXX TODO Ajouter la possibilité de retourner
|
|||||||
"""
|
"""
|
||||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
|
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
|
||||||
dept = Departement.query.filter_by(id=formsemestre.dept_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")
|
||||||
if etudid is not None:
|
if etudid is not None:
|
||||||
query = Identite.query.filter_by(id=etudid)
|
query = Identite.query.filter_by(id=etudid)
|
||||||
elif nip is not None:
|
elif nip is not None:
|
||||||
@ -391,25 +453,14 @@ def etudiant_bulletin_semestre( # XXX TODO Ajouter la possibilité de retourner
|
|||||||
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/groups",
|
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/groups",
|
||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
)
|
)
|
||||||
@bp.route(
|
@scodoc
|
||||||
"/etudiant/nip/<string:nip>/formsemestre/<int:formsemestre_id>/groups",
|
@permission_required(Permission.ScoView)
|
||||||
methods=["GET"],
|
def etudiant_groups(formsemestre_id: int, etudid: int = None):
|
||||||
)
|
|
||||||
@bp.route(
|
|
||||||
"/etudiant/ine/<string:ine>/formsemestre/<int:formsemestre_id>/groups",
|
|
||||||
methods=["GET"],
|
|
||||||
)
|
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
|
||||||
def etudiant_groups(
|
|
||||||
formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Retourne la liste des groupes auxquels appartient l'étudiant dans le formsemestre indiqué
|
Retourne la liste des groupes auxquels appartient l'étudiant dans le formsemestre indiqué
|
||||||
|
|
||||||
formsemestre_id : l'id d'un formsemestre
|
formsemestre_id : l'id d'un formsemestre
|
||||||
etudid : l'etudid d'un étudiant
|
etudid : l'etudid d'un étudiant
|
||||||
nip : le code nip d'un étudiant
|
|
||||||
ine : le code ine d'un étudiant
|
|
||||||
|
|
||||||
Exemple de résultat :
|
Exemple de résultat :
|
||||||
[
|
[
|
||||||
@ -438,30 +489,18 @@ def etudiant_groups(
|
|||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formsemestre = query.first()
|
||||||
if formsemestre is None:
|
if formsemestre is None:
|
||||||
return error_response(
|
return error_response(
|
||||||
404,
|
404,
|
||||||
message="formsemestre inconnu",
|
message="formsemestre inconnu",
|
||||||
)
|
)
|
||||||
dept = Departement.query.get(formsemestre.dept_id)
|
dept = formsemestre.departement
|
||||||
if etudid is not None:
|
etud = Identite.query.filter_by(id=etudid, dept_id=dept.id).first_or_404(etudid)
|
||||||
query = Identite.query.filter_by(id=etudid)
|
|
||||||
elif nip is not None:
|
|
||||||
query = Identite.query.filter_by(code_nip=nip, dept_id=dept.id)
|
|
||||||
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",
|
|
||||||
)
|
|
||||||
etud = query.first()
|
|
||||||
if etud is None:
|
|
||||||
return error_response(
|
|
||||||
404,
|
|
||||||
message="etudiant inconnu",
|
|
||||||
)
|
|
||||||
app.set_sco_dept(dept.acronym)
|
app.set_sco_dept(dept.acronym)
|
||||||
data = sco_groups.get_etud_groups(etud.id, formsemestre.id)
|
data = sco_groups.get_etud_groups(etud.id, formsemestre.id)
|
||||||
|
|
||||||
|
@ -8,21 +8,24 @@
|
|||||||
ScoDoc 9 API : accès aux évaluations
|
ScoDoc 9 API : accès aux évaluations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import jsonify
|
from flask import g, jsonify
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
import app
|
import app
|
||||||
|
|
||||||
from app import models
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.api import bp
|
from app.decorators import scodoc, permission_required
|
||||||
from app.api.auth import permission_required_api
|
|
||||||
from app.api.errors import error_response
|
from app.api.errors import error_response
|
||||||
from app.models import Evaluation
|
from app.models import Evaluation, ModuleImpl, FormSemestre
|
||||||
from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes
|
from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/evaluations/<int:moduleimpl_id>", methods=["GET"])
|
@bp.route("/evaluations/<int:moduleimpl_id>")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/evaluations/<int:moduleimpl_id>")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def evaluations(moduleimpl_id: int):
|
def evaluations(moduleimpl_id: int):
|
||||||
"""
|
"""
|
||||||
Retourne la liste des évaluations d'un moduleimpl
|
Retourne la liste des évaluations d'un moduleimpl
|
||||||
@ -54,17 +57,21 @@ def evaluations(moduleimpl_id: int):
|
|||||||
...
|
...
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
# Récupération de toutes les évaluations
|
query = Evaluation.query.filter_by(id=moduleimpl_id)
|
||||||
evals = Evaluation.query.filter_by(id=moduleimpl_id)
|
if g.scodoc_dept:
|
||||||
|
query = (
|
||||||
# Mise en forme des données
|
query.join(ModuleImpl)
|
||||||
data = [d.to_dict() for d in evals]
|
.join(FormSemestre)
|
||||||
|
.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
return jsonify(data)
|
)
|
||||||
|
return jsonify([d.to_dict() for d in query])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/evaluation/eval_notes/<int:evaluation_id>", methods=["GET"])
|
@bp.route("/evaluation/<int:evaluation_id>/notes")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/evaluation/<int:evaluation_id>/notes")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def evaluation_notes(evaluation_id: int):
|
def evaluation_notes(evaluation_id: int):
|
||||||
"""
|
"""
|
||||||
Retourne la liste des notes à partir de l'id d'une évaluation donnée
|
Retourne la liste des notes à partir de l'id d'une évaluation donnée
|
||||||
@ -94,7 +101,15 @@ def evaluation_notes(evaluation_id: int):
|
|||||||
...
|
...
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
evaluation = models.Evaluation.query.filter_by(id=evaluation_id).first_or_404()
|
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = (
|
||||||
|
query.join(ModuleImpl)
|
||||||
|
.join(FormSemestre)
|
||||||
|
.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
evaluation = query.first_or_404()
|
||||||
dept = evaluation.moduleimpl.formsemestre.departement
|
dept = evaluation.moduleimpl.formsemestre.departement
|
||||||
app.set_sco_dept(dept.acronym)
|
app.set_sco_dept(dept.acronym)
|
||||||
|
|
||||||
|
@ -8,43 +8,58 @@
|
|||||||
ScoDoc 9 API : accès aux formations
|
ScoDoc 9 API : accès aux formations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import jsonify
|
from flask import g, jsonify
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
import app
|
import app
|
||||||
from app import models
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.api import bp
|
|
||||||
from app.api.errors import error_response
|
from app.api.errors import error_response
|
||||||
from app.api.auth import permission_required_api
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models.formations import Formation
|
from app.models.formations import Formation
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.models.moduleimpls import ModuleImpl
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formations", methods=["GET"])
|
@bp.route("/formations")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/formations")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def formations():
|
def formations():
|
||||||
"""
|
"""
|
||||||
Retourne la liste de toutes les formations (tous départements)
|
Retourne la liste de toutes les formations (tous départements)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
data = [d.to_dict() for d in models.Formation.query]
|
query = Formation.query
|
||||||
return jsonify(data)
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
|
||||||
|
return jsonify([d.to_dict() for d in query])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formations_ids", methods=["GET"])
|
@bp.route("/formations_ids")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/formations_ids")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def formations_ids():
|
def formations_ids():
|
||||||
"""
|
"""
|
||||||
Retourne la liste de toutes les id de formations (tous départements)
|
Retourne la liste de toutes les id de formations (tous départements)
|
||||||
|
|
||||||
Exemple de résultat : [ 17, 99, 32 ]
|
Exemple de résultat : [ 17, 99, 32 ]
|
||||||
"""
|
"""
|
||||||
data = [d.id for d in models.Formation.query]
|
query = Formation.query
|
||||||
return jsonify(data)
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
return jsonify([d.id for d in query])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formation/<int:formation_id>", methods=["GET"])
|
@bp.route("/formation/<int:formation_id>")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/formation/<int:formation_id>")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def formation_by_id(formation_id: int):
|
def formation_by_id(formation_id: int):
|
||||||
"""
|
"""
|
||||||
La formation d'id donné
|
La formation d'id donné
|
||||||
@ -66,21 +81,31 @@ def formation_by_id(formation_id: int):
|
|||||||
"formation_id": 1
|
"formation_id": 1
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
formation = models.Formation.query.get_or_404(formation_id)
|
query = Formation.query.filter_by(id=formation_id)
|
||||||
return jsonify(formation.to_dict())
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
return jsonify(query.first_or_404().to_dict())
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/formation/formation_export/<int:formation_id>",
|
"/formation/<int:formation_id>/export",
|
||||||
methods=["GET"],
|
|
||||||
defaults={"export_ids": False},
|
defaults={"export_ids": False},
|
||||||
)
|
)
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/formation/formation_export/<int:formation_id>/with_ids",
|
"/formation/<int:formation_id>/export/with_ids",
|
||||||
methods=["GET"],
|
|
||||||
defaults={"export_ids": True},
|
defaults={"export_ids": True},
|
||||||
)
|
)
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route(
|
||||||
|
"/formation/<int:formation_id>/export",
|
||||||
|
defaults={"export_ids": False},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/formation/<int:formation_id>/export/with_ids",
|
||||||
|
defaults={"export_ids": True},
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def formation_export_by_formation_id(formation_id: int, export_ids=False):
|
def formation_export_by_formation_id(formation_id: int, export_ids=False):
|
||||||
"""
|
"""
|
||||||
Retourne la formation, avec UE, matières, modules
|
Retourne la formation, avec UE, matières, modules
|
||||||
@ -177,7 +202,10 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
formation = Formation.query.get_or_404(formation_id)
|
query = Formation.query.filter_by(id=formation_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formation = query.first_or_404(formation_id)
|
||||||
app.set_sco_dept(formation.departement.acronym)
|
app.set_sco_dept(formation.departement.acronym)
|
||||||
try:
|
try:
|
||||||
data = sco_formations.formation_export(formation_id, export_ids)
|
data = sco_formations.formation_export(formation_id, export_ids)
|
||||||
@ -187,11 +215,36 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False):
|
|||||||
return jsonify(data)
|
return jsonify(data)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formation/moduleimpl/<int:moduleimpl_id>", methods=["GET"])
|
@bp.route("/formation/<int:formation_id>/referentiel_competences")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/formation/<int:formation_id>/referentiel_competences")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def referentiel_competences(formation_id: int):
|
||||||
|
"""
|
||||||
|
Retourne le référentiel de compétences
|
||||||
|
|
||||||
|
formation_id : l'id d'une formation
|
||||||
|
|
||||||
|
return null si pas de référentiel associé.
|
||||||
|
"""
|
||||||
|
query = Formation.query.filter_by(id=formation_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formation = query.first_or_404(formation_id)
|
||||||
|
if formation.referentiel_competence is None:
|
||||||
|
return jsonify(None)
|
||||||
|
return jsonify(formation.referentiel_competence.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/moduleimpl/<int:moduleimpl_id>")
|
||||||
|
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def moduleimpl(moduleimpl_id: int):
|
def moduleimpl(moduleimpl_id: int):
|
||||||
"""
|
"""
|
||||||
Retourne un module moduleimpl en fonction de son id
|
Retourne un moduleimpl en fonction de son id
|
||||||
|
|
||||||
moduleimpl_id : l'id d'un moduleimpl
|
moduleimpl_id : l'id d'un moduleimpl
|
||||||
|
|
||||||
@ -224,24 +277,8 @@ def moduleimpl(moduleimpl_id: int):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
modimpl = models.ModuleImpl.query.get_or_404(moduleimpl_id)
|
query = ModuleImpl.query.filter_by(id=moduleimpl_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
modimpl = query.first_or_404()
|
||||||
return jsonify(modimpl.to_dict())
|
return jsonify(modimpl.to_dict())
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
|
||||||
"/formation/<int:formation_id>/referentiel_competences",
|
|
||||||
methods=["GET"],
|
|
||||||
)
|
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
|
||||||
def referentiel_competences(formation_id: int):
|
|
||||||
"""
|
|
||||||
Retourne le référentiel de compétences
|
|
||||||
|
|
||||||
formation_id : l'id d'une formation
|
|
||||||
|
|
||||||
return null si pas de référentiel associé.
|
|
||||||
"""
|
|
||||||
formation = models.Formation.query.get_or_404(formation_id)
|
|
||||||
if formation.referentiel_competence is None:
|
|
||||||
return jsonify(None)
|
|
||||||
return jsonify(formation.referentiel_competence.to_dict())
|
|
||||||
|
@ -7,17 +7,23 @@
|
|||||||
"""
|
"""
|
||||||
ScoDoc 9 API : accès aux formsemestres
|
ScoDoc 9 API : accès aux formsemestres
|
||||||
"""
|
"""
|
||||||
from flask import abort, jsonify, request
|
from flask import g, jsonify, request
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
import app
|
import app
|
||||||
from app import models
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.api import bp
|
from app.decorators import scodoc, permission_required
|
||||||
from app.api.auth import permission_required_api
|
|
||||||
from app.api.errors import error_response
|
from app.api.errors import error_response
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.moy_mod import ModuleImplResults
|
from app.comp.moy_mod import ModuleImplResults
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import Evaluation, FormSemestre, FormSemestreEtape, ModuleImpl
|
from app.models import (
|
||||||
|
Departement,
|
||||||
|
Evaluation,
|
||||||
|
FormSemestre,
|
||||||
|
FormSemestreEtape,
|
||||||
|
ModuleImpl,
|
||||||
|
)
|
||||||
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_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
@ -25,8 +31,11 @@ from app.scodoc.sco_utils import ModuleType
|
|||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>", methods=["GET"])
|
@bp.route("/formsemestre/<int:formsemestre_id>")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/formsemestre/<int:formsemestre_id>")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def formsemestre_infos(formsemestre_id: int):
|
def formsemestre_infos(formsemestre_id: int):
|
||||||
"""
|
"""
|
||||||
Information sur le formsemestre indiqué.
|
Information sur le formsemestre indiqué.
|
||||||
@ -64,12 +73,18 @@ def formsemestre_infos(formsemestre_id: int):
|
|||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
formsemestre: FormSemestre = models.FormSemestre.query.get_or_404(formsemestre_id)
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
return jsonify(formsemestre.to_dict_api())
|
return jsonify(formsemestre.to_dict_api())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestres/query", methods=["GET"])
|
@bp.route("/formsemestres/query")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/formsemestres/query")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def formsemestres_query():
|
def formsemestres_query():
|
||||||
"""
|
"""
|
||||||
Retourne les formsemestres filtrés par
|
Retourne les formsemestres filtrés par
|
||||||
@ -85,6 +100,8 @@ def formsemestres_query():
|
|||||||
dept_acronym = request.args.get("dept_acronym")
|
dept_acronym = request.args.get("dept_acronym")
|
||||||
dept_id = request.args.get("dept_id")
|
dept_id = request.args.get("dept_id")
|
||||||
formsemestres = FormSemestre.query
|
formsemestres = FormSemestre.query
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
if etape_apo is not None:
|
if etape_apo is not None:
|
||||||
formsemestres = formsemestres.join(FormSemestreEtape).filter(
|
formsemestres = formsemestres.join(FormSemestreEtape).filter(
|
||||||
FormSemestreEtape.etape_apo == etape_apo
|
FormSemestreEtape.etape_apo == etape_apo
|
||||||
@ -100,9 +117,7 @@ def formsemestres_query():
|
|||||||
FormSemestre.date_fin >= debut_annee, FormSemestre.date_debut <= fin_annee
|
FormSemestre.date_fin >= debut_annee, FormSemestre.date_debut <= fin_annee
|
||||||
)
|
)
|
||||||
if dept_acronym is not None:
|
if dept_acronym is not None:
|
||||||
formsemestres = formsemestres.join(models.Departement).filter_by(
|
formsemestres = formsemestres.join(Departement).filter_by(acronym=dept_acronym)
|
||||||
acronym=dept_acronym
|
|
||||||
)
|
|
||||||
if dept_id is not None:
|
if dept_id is not None:
|
||||||
try:
|
try:
|
||||||
dept_id = int(dept_id)
|
dept_id = int(dept_id)
|
||||||
@ -113,8 +128,11 @@ def formsemestres_query():
|
|||||||
return jsonify([formsemestre.to_dict_api() for formsemestre in formsemestres])
|
return jsonify([formsemestre.to_dict_api() for formsemestre in formsemestres])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins", methods=["GET"])
|
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def bulletins(formsemestre_id: int):
|
def bulletins(formsemestre_id: int):
|
||||||
"""
|
"""
|
||||||
Retourne les bulletins d'un formsemestre donné
|
Retourne les bulletins d'un formsemestre donné
|
||||||
@ -123,7 +141,10 @@ def bulletins(formsemestre_id: int):
|
|||||||
|
|
||||||
Exemple de résultat : liste, voir https://scodoc.org/ScoDoc9API/#bulletin
|
Exemple de résultat : liste, voir https://scodoc.org/ScoDoc9API/#bulletin
|
||||||
"""
|
"""
|
||||||
formsemestre = models.FormSemestre.query.get_or_404(formsemestre_id)
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
app.set_sco_dept(formsemestre.departement.acronym)
|
app.set_sco_dept(formsemestre.departement.acronym)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
@ -134,11 +155,11 @@ def bulletins(formsemestre_id: int):
|
|||||||
return jsonify(data)
|
return jsonify(data)
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
@bp.route("/formsemestre/<int:formsemestre_id>/programme")
|
||||||
"/formsemestre/<int:formsemestre_id>/programme",
|
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/programme")
|
||||||
methods=["GET"],
|
@login_required
|
||||||
)
|
@scodoc
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@permission_required(Permission.ScoView)
|
||||||
def formsemestre_programme(formsemestre_id: int):
|
def formsemestre_programme(formsemestre_id: int):
|
||||||
"""
|
"""
|
||||||
Retourne la liste des Ues, ressources et SAE d'un semestre
|
Retourne la liste des Ues, ressources et SAE d'un semestre
|
||||||
@ -204,7 +225,10 @@ def formsemestre_programme(formsemestre_id: int):
|
|||||||
"modules" : [ ... les modules qui ne sont ni des SAEs ni des ressources ... ]
|
"modules" : [ ... les modules qui ne sont ni des SAEs ni des ressources ... ]
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
formsemestre: FormSemestre = models.FormSemestre.query.get_or_404(formsemestre_id)
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
ues = formsemestre.query_ues()
|
ues = formsemestre.query_ues()
|
||||||
m_list = {
|
m_list = {
|
||||||
ModuleType.RESSOURCE: [],
|
ModuleType.RESSOURCE: [],
|
||||||
@ -226,29 +250,41 @@ def formsemestre_programme(formsemestre_id: int):
|
|||||||
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/formsemestre/<int:formsemestre_id>/etudiants",
|
"/formsemestre/<int:formsemestre_id>/etudiants",
|
||||||
methods=["GET"],
|
|
||||||
defaults={"etat": scu.INSCRIT},
|
defaults={"etat": scu.INSCRIT},
|
||||||
)
|
)
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/formsemestre/<int:formsemestre_id>/etudiants/demissionnaires",
|
"/formsemestre/<int:formsemestre_id>/etudiants/demissionnaires",
|
||||||
methods=["GET"],
|
|
||||||
defaults={"etat": scu.DEMISSION},
|
defaults={"etat": scu.DEMISSION},
|
||||||
)
|
)
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/formsemestre/<int:formsemestre_id>/etudiants/defaillants",
|
"/formsemestre/<int:formsemestre_id>/etudiants/defaillants",
|
||||||
methods=["GET"],
|
|
||||||
defaults={"etat": scu.DEF},
|
defaults={"etat": scu.DEF},
|
||||||
)
|
)
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route(
|
||||||
|
"/formsemestre/<int:formsemestre_id>/etudiants",
|
||||||
|
defaults={"etat": scu.INSCRIT},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/formsemestre/<int:formsemestre_id>/etudiants/demissionnaires",
|
||||||
|
defaults={"etat": scu.DEMISSION},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/formsemestre/<int:formsemestre_id>/etudiants/defaillants",
|
||||||
|
defaults={"etat": scu.DEF},
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def formsemestre_etudiants(formsemestre_id: int, etat: str):
|
def formsemestre_etudiants(formsemestre_id: int, etat: str):
|
||||||
"""
|
"""
|
||||||
Retourne la liste des étudiants d'un formsemestre
|
Retourne la liste des étudiants d'un formsemestre
|
||||||
|
|
||||||
formsemestre_id : l'id d'un formsemestre
|
formsemestre_id : l'id d'un formsemestre
|
||||||
"""
|
"""
|
||||||
formsemestre: FormSemestre = models.FormSemestre.query.filter_by(
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
id=formsemestre_id
|
if g.scodoc_dept:
|
||||||
).first_or_404()
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
|
|
||||||
inscriptions = [ins for ins in formsemestre.inscriptions if ins.etat == etat]
|
inscriptions = [ins for ins in formsemestre.inscriptions if ins.etat == etat]
|
||||||
etuds = [ins.etud.to_dict_short() for ins in inscriptions]
|
etuds = [ins.etud.to_dict_short() for ins in inscriptions]
|
||||||
@ -260,8 +296,11 @@ def formsemestre_etudiants(formsemestre_id: int, etat: str):
|
|||||||
return jsonify(etuds)
|
return jsonify(etuds)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>/etat_evals", methods=["GET"])
|
@bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def etat_evals(formsemestre_id: int):
|
def etat_evals(formsemestre_id: int):
|
||||||
"""
|
"""
|
||||||
Informations sur l'état des évaluations d'un formsemestre.
|
Informations sur l'état des évaluations d'un formsemestre.
|
||||||
@ -297,7 +336,10 @@ def etat_evals(formsemestre_id: int):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
app.set_sco_dept(formsemestre.departement.acronym)
|
app.set_sco_dept(formsemestre.departement.acronym)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
|
||||||
@ -364,8 +406,11 @@ def etat_evals(formsemestre_id: int):
|
|||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>/resultats", methods=["GET"])
|
@bp.route("/formsemestre/<int:formsemestre_id>/resultats")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/resultats")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def formsemestre_resultat(formsemestre_id: int):
|
def formsemestre_resultat(formsemestre_id: int):
|
||||||
"""Tableau récapitulatif des résultats
|
"""Tableau récapitulatif des résultats
|
||||||
Pour chaque étudiant, son état, ses groupes, ses moyennes d'UE et de modules.
|
Pour chaque étudiant, son état, ses groupes, ses moyennes d'UE et de modules.
|
||||||
@ -375,7 +420,10 @@ def formsemestre_resultat(formsemestre_id: int):
|
|||||||
return error_response(404, "invalid format specification")
|
return error_response(404, "invalid format specification")
|
||||||
convert_values = format_spec != "raw"
|
convert_values = format_spec != "raw"
|
||||||
|
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formsemestre: FormSemestre = query.first_or_404(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)
|
||||||
rows, footer_rows, titles, column_ids = res.get_table_recap(
|
rows, footer_rows, titles, column_ids = res.get_table_recap(
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
# from flask import jsonify
|
# from flask import jsonify
|
||||||
|
|
||||||
# from app import models
|
# from app import models
|
||||||
# from app.api import bp
|
# from app.api import api_bp as bp
|
||||||
# from app.api.errors import error_response
|
# from app.api.errors import error_response
|
||||||
# from app.api.auth import permission_required_api
|
|
||||||
# from app.scodoc.sco_prepajury import feuille_preparation_jury
|
# from app.scodoc.sco_prepajury import feuille_preparation_jury
|
||||||
# from app.scodoc.sco_pvjury import formsemestre_pvjury
|
# from app.scodoc.sco_pvjury import formsemestre_pvjury
|
||||||
|
|
||||||
|
@ -31,19 +31,22 @@ Contrib @jmp
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import jsonify, g, send_file
|
from flask import jsonify, g, send_file
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
from app.api import bp
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.api import requested_format
|
from app.api import requested_format
|
||||||
from app.api.auth import token_auth
|
|
||||||
from app.api.errors import error_response
|
from app.api.errors import error_response
|
||||||
from app.models import Departement
|
from app.models import Departement
|
||||||
from app.scodoc.sco_logos import list_logos, find_logo
|
from app.scodoc.sco_logos import list_logos, find_logo
|
||||||
from app.api.auth import permission_required_api
|
from app.decorators import scodoc, permission_required
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
# Note: l'API logos n'est accessible qu'en mode global (avec jeton, sans dept)
|
||||||
|
|
||||||
@bp.route("/logos", methods=["GET"])
|
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@bp.route("/logos")
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def api_get_glob_logos():
|
def api_get_glob_logos():
|
||||||
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
|
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
|
||||||
return error_response(401, message="accès interdit")
|
return error_response(401, message="accès interdit")
|
||||||
@ -54,8 +57,9 @@ def api_get_glob_logos():
|
|||||||
return jsonify(list(logos.keys()))
|
return jsonify(list(logos.keys()))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/logos/<string:logoname>", methods=["GET"])
|
@bp.route("/logos/<string:logoname>")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def api_get_glob_logo(logoname):
|
def api_get_glob_logo(logoname):
|
||||||
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
|
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
|
||||||
return error_response(401, message="accès interdit")
|
return error_response(401, message="accès interdit")
|
||||||
@ -70,8 +74,9 @@ def api_get_glob_logo(logoname):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/departements/<string:departement>/logos", methods=["GET"])
|
@bp.route("/departements/<string:departement>/logos")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def api_get_local_logos(departement):
|
def api_get_local_logos(departement):
|
||||||
dept_id = Departement.from_acronym(departement).id
|
dept_id = Departement.from_acronym(departement).id
|
||||||
if not g.current_user.has_permission(Permission.ScoChangePreferences, departement):
|
if not g.current_user.has_permission(Permission.ScoChangePreferences, departement):
|
||||||
@ -80,8 +85,9 @@ def api_get_local_logos(departement):
|
|||||||
return jsonify(list(logos.keys()))
|
return jsonify(list(logos.keys()))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/departements/<string:departement>/logos/<string:logoname>", methods=["GET"])
|
@bp.route("/departements/<string:departement>/logos/<string:logoname>")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def api_get_local_logo(departement, logoname):
|
def api_get_local_logo(departement, logoname):
|
||||||
# format = requested_format("jpg", ['png', 'jpg']) XXX ?
|
# format = requested_format("jpg", ['png', 'jpg']) XXX ?
|
||||||
dept_id = Departement.from_acronym(departement).id
|
dept_id = Departement.from_acronym(departement).id
|
||||||
|
@ -7,12 +7,14 @@
|
|||||||
"""
|
"""
|
||||||
ScoDoc 9 API : partitions
|
ScoDoc 9 API : partitions
|
||||||
"""
|
"""
|
||||||
from flask import jsonify, request
|
from flask import g, jsonify, request
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
import app
|
import app
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.api import bp
|
from app import api
|
||||||
from app.api.auth import permission_required_api
|
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.api.errors import error_response
|
||||||
from app.models import FormSemestre, FormSemestreInscription, Identite
|
from app.models import FormSemestre, FormSemestreInscription, Identite
|
||||||
from app.models import GroupDescr, Partition
|
from app.models import GroupDescr, Partition
|
||||||
@ -22,8 +24,10 @@ from app.scodoc.sco_permissions import Permission
|
|||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/partition/<int:partition_id>", methods=["GET"])
|
@bp.route("/partition/<int:partition_id>")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def partition_info(partition_id: int):
|
def partition_info(partition_id: int):
|
||||||
"""Info sur une partition.
|
"""Info sur une partition.
|
||||||
|
|
||||||
@ -44,12 +48,18 @@ def partition_info(partition_id: int):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
partition = Partition.query.get_or_404(partition_id)
|
query = Partition.query.filter_by(id=partition_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
partition = query.first_or_404()
|
||||||
return jsonify(partition.to_dict(with_groups=True))
|
return jsonify(partition.to_dict(with_groups=True))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>/partitions", methods=["GET"])
|
@bp.route("/formsemestre/<int:formsemestre_id>/partitions")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/partitions")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def formsemestre_partitions(formsemestre_id: int):
|
def formsemestre_partitions(formsemestre_id: int):
|
||||||
"""Liste de toutes les partitions d'un formsemestre
|
"""Liste de toutes les partitions d'un formsemestre
|
||||||
|
|
||||||
@ -70,7 +80,10 @@ def formsemestre_partitions(formsemestre_id: int):
|
|||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
partitions = sorted(formsemestre.partitions, key=lambda p: p.numero or 0)
|
partitions = sorted(formsemestre.partitions, key=lambda p: p.numero or 0)
|
||||||
return jsonify(
|
return jsonify(
|
||||||
{
|
{
|
||||||
@ -81,8 +94,11 @@ def formsemestre_partitions(formsemestre_id: int):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/group/<int:group_id>/etudiants", methods=["GET"])
|
@bp.route("/group/<int:group_id>/etudiants")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/group/<int:group_id>/etudiants")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def etud_in_group(group_id: int):
|
def etud_in_group(group_id: int):
|
||||||
"""
|
"""
|
||||||
Retourne la liste des étudiants dans un groupe
|
Retourne la liste des étudiants dans un groupe
|
||||||
@ -103,18 +119,31 @@ def etud_in_group(group_id: int):
|
|||||||
...
|
...
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
group = GroupDescr.query.get_or_404(group_id)
|
query = GroupDescr.query.filter_by(id=group_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = (
|
||||||
|
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
)
|
||||||
|
group = query.first_or_404()
|
||||||
return jsonify([etud.to_dict_short() for etud in group.etuds])
|
return jsonify([etud.to_dict_short() for etud in group.etuds])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/group/<int:group_id>/etudiants/query", methods=["GET"])
|
@bp.route("/group/<int:group_id>/etudiants/query")
|
||||||
@permission_required_api(Permission.ScoView, Permission.APIView)
|
@api_web_bp.route("/group/<int:group_id>/etudiants/query")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
def etud_in_group_query(group_id: int):
|
def etud_in_group_query(group_id: int):
|
||||||
"""Etudiants du groupe, filtrés par état"""
|
"""Etudiants du groupe, filtrés par état"""
|
||||||
etat = request.args.get("etat")
|
etat = request.args.get("etat")
|
||||||
if etat not in {scu.INSCRIT, scu.DEMISSION, scu.DEF}:
|
if etat not in {scu.INSCRIT, scu.DEMISSION, scu.DEF}:
|
||||||
return error_response(404, "etat: valeur invalide")
|
return error_response(404, "etat: valeur invalide")
|
||||||
group = GroupDescr.query.get_or_404(group_id)
|
query = GroupDescr.query.filter_by(id=group_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = (
|
||||||
|
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
)
|
||||||
|
group = query.first_or_404() # just tro ckeck that group exists in accessible dept
|
||||||
query = (
|
query = (
|
||||||
Identite.query.join(FormSemestreInscription)
|
Identite.query.join(FormSemestreInscription)
|
||||||
.filter_by(formsemestre_id=group.partition.formsemestre_id, etat=etat)
|
.filter_by(formsemestre_id=group.partition.formsemestre_id, etat=etat)
|
||||||
@ -126,11 +155,21 @@ def etud_in_group_query(group_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/group/<int:group_id>/set_etudiant/<int:etudid>", methods=["POST"])
|
@bp.route("/group/<int:group_id>/set_etudiant/<int:etudid>", methods=["POST"])
|
||||||
@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups)
|
@api_web_bp.route("/group/<int:group_id>/set_etudiant/<int:etudid>", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeGroups)
|
||||||
def set_etud_group(etudid: int, group_id: int):
|
def set_etud_group(etudid: int, group_id: int):
|
||||||
"""Affecte l'étudiant au groupe indiqué"""
|
"""Affecte l'étudiant au groupe indiqué"""
|
||||||
etud = Identite.query.get_or_404(etudid)
|
etud = Identite.query.get_or_404(etudid)
|
||||||
group = GroupDescr.query.get_or_404(group_id)
|
query = GroupDescr.query.filter_by(id=group_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = (
|
||||||
|
query.join(Partition)
|
||||||
|
.join(FormSemestre)
|
||||||
|
.filter_by(dept_id=group.scodoc_dept_id)
|
||||||
|
)
|
||||||
|
group = query.first_or_404()
|
||||||
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 error_response(404, "etud non inscrit au formsemestre du groupe")
|
return error_response(404, "etud non inscrit au formsemestre du groupe")
|
||||||
groups = (
|
groups = (
|
||||||
@ -139,11 +178,11 @@ def set_etud_group(etudid: int, group_id: int):
|
|||||||
.filter_by(etudid=etudid)
|
.filter_by(etudid=etudid)
|
||||||
)
|
)
|
||||||
ok = False
|
ok = False
|
||||||
for g in groups:
|
for group in groups:
|
||||||
if g.id == group_id:
|
if group.id == group_id:
|
||||||
ok = True
|
ok = True
|
||||||
else:
|
else:
|
||||||
g.etuds.remove(etud)
|
group.etuds.remove(etud)
|
||||||
if not ok:
|
if not ok:
|
||||||
group.etuds.append(etud)
|
group.etuds.append(etud)
|
||||||
log(f"set_etud_group({etud}, {group})")
|
log(f"set_etud_group({etud}, {group})")
|
||||||
@ -153,11 +192,21 @@ def set_etud_group(etudid: int, group_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/group/<int:group_id>/remove_etudiant/<int:etudid>", methods=["POST"])
|
@bp.route("/group/<int:group_id>/remove_etudiant/<int:etudid>", methods=["POST"])
|
||||||
@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups)
|
@api_web_bp.route(
|
||||||
|
"/group/<int:group_id>/remove_etudiant/<int:etudid>", methods=["POST"]
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeGroups)
|
||||||
def group_remove_etud(group_id: int, etudid: int):
|
def group_remove_etud(group_id: int, etudid: int):
|
||||||
"""Retire l'étudiant de ce groupe. S'il n'y est pas, ne fait rien."""
|
"""Retire l'étudiant de ce groupe. S'il n'y est pas, ne fait rien."""
|
||||||
etud = Identite.query.get_or_404(etudid)
|
etud = Identite.query.get_or_404(etudid)
|
||||||
group = GroupDescr.query.get_or_404(group_id)
|
query = GroupDescr.query.filter_by(id=group_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = (
|
||||||
|
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
)
|
||||||
|
group = query.first_or_404()
|
||||||
group.etuds.remove(etud)
|
group.etuds.remove(etud)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
sco_cache.invalidate_formsemestre(group.partition.formsemestre_id)
|
sco_cache.invalidate_formsemestre(group.partition.formsemestre_id)
|
||||||
@ -167,20 +216,28 @@ def group_remove_etud(group_id: int, etudid: int):
|
|||||||
@bp.route(
|
@bp.route(
|
||||||
"/partition/<int:partition_id>/remove_etudiant/<int:etudid>", methods=["POST"]
|
"/partition/<int:partition_id>/remove_etudiant/<int:etudid>", methods=["POST"]
|
||||||
)
|
)
|
||||||
@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups)
|
@api_web_bp.route(
|
||||||
|
"/partition/<int:partition_id>/remove_etudiant/<int:etudid>", methods=["POST"]
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeGroups)
|
||||||
def partition_remove_etud(partition_id: int, etudid: int):
|
def partition_remove_etud(partition_id: int, etudid: int):
|
||||||
"""Enlève l'étudiant de tous les groupes de cette partition
|
"""Enlève l'étudiant de tous les groupes de cette partition
|
||||||
(NB: en principe, un étudiant ne doit être que dans 0 ou 1 groupe d'une partition)
|
(NB: en principe, un étudiant ne doit être que dans 0 ou 1 groupe d'une partition)
|
||||||
"""
|
"""
|
||||||
etud = Identite.query.get_or_404(etudid)
|
etud = Identite.query.get_or_404(etudid)
|
||||||
partition = Partition.query.get_or_404(partition_id)
|
query = Partition.query.filter_by(id=partition_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
partition = query.first_or_404()
|
||||||
groups = (
|
groups = (
|
||||||
GroupDescr.query.filter_by(partition_id=partition_id)
|
GroupDescr.query.filter_by(partition_id=partition_id)
|
||||||
.join(group_membership)
|
.join(group_membership)
|
||||||
.filter_by(etudid=etudid)
|
.filter_by(etudid=etudid)
|
||||||
)
|
)
|
||||||
for g in groups:
|
for group in groups:
|
||||||
g.etuds.remove(etud)
|
group.etuds.remove(etud)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
app.set_sco_dept(partition.formsemestre.departement.acronym)
|
app.set_sco_dept(partition.formsemestre.departement.acronym)
|
||||||
sco_cache.invalidate_formsemestre(partition.formsemestre_id)
|
sco_cache.invalidate_formsemestre(partition.formsemestre_id)
|
||||||
@ -188,7 +245,10 @@ def partition_remove_etud(partition_id: int, etudid: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/partition/<int:partition_id>/group/create", methods=["POST"])
|
@bp.route("/partition/<int:partition_id>/group/create", methods=["POST"])
|
||||||
@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups)
|
@api_web_bp.route("/partition/<int:partition_id>/group/create", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeGroups)
|
||||||
def group_create(partition_id: int):
|
def group_create(partition_id: int):
|
||||||
"""Création d'un groupe dans une partition
|
"""Création d'un groupe dans une partition
|
||||||
|
|
||||||
@ -197,7 +257,10 @@ def group_create(partition_id: int):
|
|||||||
"group_name" : nom_du_groupe,
|
"group_name" : nom_du_groupe,
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
partition: Partition = Partition.query.get_or_404(partition_id)
|
query = Partition.query.filter_by(id=partition_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
partition: Partition = query.first_or_404()
|
||||||
if not partition.groups_editable:
|
if not partition.groups_editable:
|
||||||
return error_response(404, "partition non editable")
|
return error_response(404, "partition non editable")
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
@ -218,10 +281,18 @@ def group_create(partition_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/group/<int:group_id>/delete", methods=["POST"])
|
@bp.route("/group/<int:group_id>/delete", methods=["POST"])
|
||||||
@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups)
|
@api_web_bp.route("/group/<int:group_id>/delete", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeGroups)
|
||||||
def group_delete(group_id: int):
|
def group_delete(group_id: int):
|
||||||
"""Suppression d'un groupe"""
|
"""Suppression d'un groupe"""
|
||||||
group = GroupDescr.query.get_or_404(group_id)
|
query = GroupDescr.query.filter_by(id=group_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = (
|
||||||
|
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
)
|
||||||
|
group: GroupDescr = query.first_or_404()
|
||||||
if not group.partition.groups_editable:
|
if not group.partition.groups_editable:
|
||||||
return error_response(404, "partition non editable")
|
return error_response(404, "partition non editable")
|
||||||
formsemestre_id = group.partition.formsemestre_id
|
formsemestre_id = group.partition.formsemestre_id
|
||||||
@ -234,10 +305,18 @@ def group_delete(group_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/group/<int:group_id>/edit", methods=["POST"])
|
@bp.route("/group/<int:group_id>/edit", methods=["POST"])
|
||||||
@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups)
|
@api_web_bp.route("/group/<int:group_id>/edit", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeGroups)
|
||||||
def group_edit(group_id: int):
|
def group_edit(group_id: int):
|
||||||
"""Edit a group"""
|
"""Edit a group"""
|
||||||
group: GroupDescr = GroupDescr.query.get_or_404(group_id)
|
query = GroupDescr.query.filter_by(id=group_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = (
|
||||||
|
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
)
|
||||||
|
group: GroupDescr = query.first_or_404()
|
||||||
if not group.partition.groups_editable:
|
if not group.partition.groups_editable:
|
||||||
return error_response(404, "partition non editable")
|
return error_response(404, "partition non editable")
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
@ -255,7 +334,12 @@ def group_edit(group_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>/partition/create", methods=["POST"])
|
@bp.route("/formsemestre/<int:formsemestre_id>/partition/create", methods=["POST"])
|
||||||
@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups)
|
@api_web_bp.route(
|
||||||
|
"/formsemestre/<int:formsemestre_id>/partition/create", methods=["POST"]
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeGroups)
|
||||||
def partition_create(formsemestre_id: int):
|
def partition_create(formsemestre_id: int):
|
||||||
"""Création d'une partition dans un semestre
|
"""Création d'une partition dans un semestre
|
||||||
|
|
||||||
@ -268,7 +352,10 @@ def partition_create(formsemestre_id: int):
|
|||||||
"groups_editable":bool
|
"groups_editable":bool
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
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:
|
||||||
@ -301,12 +388,20 @@ def partition_create(formsemestre_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre/<int:formsemestre_id>/partitions/order", methods=["POST"])
|
@bp.route("/formsemestre/<int:formsemestre_id>/partitions/order", methods=["POST"])
|
||||||
@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups)
|
@api_web_bp.route(
|
||||||
|
"/formsemestre/<int:formsemestre_id>/partitions/order", methods=["POST"]
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeGroups)
|
||||||
def formsemestre_order_partitions(formsemestre_id: int):
|
def formsemestre_order_partitions(formsemestre_id: int):
|
||||||
"""Modifie l'ordre des partitions du formsemestre
|
"""Modifie l'ordre des partitions du formsemestre
|
||||||
JSON args: [partition_id1, partition_id2, ...]
|
JSON args: [partition_id1, partition_id2, ...]
|
||||||
"""
|
"""
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
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, int) and not all(
|
if not isinstance(partition_ids, int) and not all(
|
||||||
isinstance(x, int) for x in partition_ids
|
isinstance(x, int) for x in partition_ids
|
||||||
@ -326,12 +421,18 @@ def formsemestre_order_partitions(formsemestre_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/partition/<int:partition_id>/groups/order", methods=["POST"])
|
@bp.route("/partition/<int:partition_id>/groups/order", methods=["POST"])
|
||||||
@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups)
|
@api_web_bp.route("/partition/<int:partition_id>/groups/order", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeGroups)
|
||||||
def partition_order_groups(partition_id: int):
|
def partition_order_groups(partition_id: int):
|
||||||
"""Modifie l'ordre des groupes de la partition
|
"""Modifie l'ordre des groupes de la partition
|
||||||
JSON args: [group_id1, group_id2, ...]
|
JSON args: [group_id1, group_id2, ...]
|
||||||
"""
|
"""
|
||||||
partition = Partition.query.get_or_404(partition_id)
|
query = Partition.query.filter_by(id=partition_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
partition: Partition = query.first_or_404()
|
||||||
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, int) and not all(
|
if not isinstance(group_ids, int) and not all(
|
||||||
isinstance(x, int) for x in group_ids
|
isinstance(x, int) for x in group_ids
|
||||||
@ -351,7 +452,10 @@ def partition_order_groups(partition_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/partition/<int:partition_id>/edit", methods=["POST"])
|
@bp.route("/partition/<int:partition_id>/edit", methods=["POST"])
|
||||||
@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups)
|
@api_web_bp.route("/partition/<int:partition_id>/edit", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeGroups)
|
||||||
def partition_edit(partition_id: int):
|
def partition_edit(partition_id: int):
|
||||||
"""Modification d'une partition dans un semestre
|
"""Modification d'une partition dans un semestre
|
||||||
|
|
||||||
@ -365,7 +469,10 @@ def partition_edit(partition_id: int):
|
|||||||
"groups_editable":bool
|
"groups_editable":bool
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
partition = Partition.query.get_or_404(partition_id)
|
query = Partition.query.filter_by(id=partition_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
partition: Partition = query.first_or_404()
|
||||||
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")
|
||||||
@ -403,7 +510,10 @@ def partition_edit(partition_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/partition/<int:partition_id>/delete", methods=["POST"])
|
@bp.route("/partition/<int:partition_id>/delete", methods=["POST"])
|
||||||
@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups)
|
@api_web_bp.route("/partition/<int:partition_id>/delete", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEtudChangeGroups)
|
||||||
def partition_delete(partition_id: int):
|
def partition_delete(partition_id: int):
|
||||||
"""Suppression d'une partition (et de tous ses groupes).
|
"""Suppression d'une partition (et de tous ses groupes).
|
||||||
|
|
||||||
@ -412,7 +522,10 @@ def partition_delete(partition_id: int):
|
|||||||
Note 2: Si la partition de parcours est supprimée, les étudiants
|
Note 2: Si la partition de parcours est supprimée, les étudiants
|
||||||
sont désinscrits des parcours.
|
sont désinscrits des parcours.
|
||||||
"""
|
"""
|
||||||
partition = Partition.query.get_or_404(partition_id)
|
query = Partition.query.filter_by(id=partition_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
partition: Partition = query.first_or_404()
|
||||||
if not partition.partition_name:
|
if not partition.partition_name:
|
||||||
return error_response(404, "ne peut pas supprimer la partition par défaut")
|
return error_response(404, "ne peut pas supprimer la partition par défaut")
|
||||||
is_parcours = partition.is_parcours()
|
is_parcours = partition.is_parcours()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from flask import jsonify
|
from flask import jsonify
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.api import bp
|
from app.api import api_bp as bp
|
||||||
from app.api.auth import basic_auth, token_auth
|
from app.auth.logic import basic_auth, token_auth
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/tokens", methods=["POST"])
|
@bp.route("/tokens", methods=["POST"])
|
||||||
|
@ -26,9 +26,7 @@ def get_etud(etudid=None, nip=None, ine=None) -> models.Identite:
|
|||||||
|
|
||||||
Return None si étudiant inexistant.
|
Return None si étudiant inexistant.
|
||||||
"""
|
"""
|
||||||
allowed_depts = current_user.get_depts_with_permission(
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
||||||
Permission.APIView | Permission.ScoView
|
|
||||||
)
|
|
||||||
|
|
||||||
if etudid is not None:
|
if etudid is not None:
|
||||||
etud: Identite = Identite.query.get(etudid)
|
etud: Identite = Identite.query.get(etudid)
|
||||||
|
87
app/auth/logic.py
Normal file
87
app/auth/logic.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# -*- coding: UTF-8 -*
|
||||||
|
|
||||||
|
"""app.auth.logic.py
|
||||||
|
"""
|
||||||
|
import http
|
||||||
|
|
||||||
|
import flask
|
||||||
|
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.auth.models import User
|
||||||
|
|
||||||
|
basic_auth = HTTPBasicAuth()
|
||||||
|
token_auth = HTTPTokenAuth()
|
||||||
|
|
||||||
|
|
||||||
|
@basic_auth.verify_password
|
||||||
|
def verify_password(username, password):
|
||||||
|
"""Verify password for this user
|
||||||
|
Appelé lors d'une demande de jeton (normalement via la route /tokens)
|
||||||
|
"""
|
||||||
|
user: User = User.query.filter_by(user_name=username).first()
|
||||||
|
if user and user.check_password(password):
|
||||||
|
g.current_user = user
|
||||||
|
# note: est aussi basic_auth.current_user()
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@basic_auth.error_handler
|
||||||
|
def basic_auth_error(status):
|
||||||
|
"error response (401 for invalid auth.)"
|
||||||
|
return error_response(status)
|
||||||
|
|
||||||
|
|
||||||
|
@login.user_loader
|
||||||
|
def load_user(uid: str) -> User:
|
||||||
|
"flask-login: accès à un utilisateur"
|
||||||
|
return User.query.get(int(uid))
|
||||||
|
|
||||||
|
|
||||||
|
@token_auth.verify_token
|
||||||
|
def verify_token(token) -> User:
|
||||||
|
"""Retrouve l'utilisateur à partir du jeton.
|
||||||
|
Si la requête n'a pas de jeton, token == "".
|
||||||
|
"""
|
||||||
|
user = User.check_token(token) if token else None
|
||||||
|
if user is not None:
|
||||||
|
flask_login.login_user(user)
|
||||||
|
g.current_user = user
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@token_auth.error_handler
|
||||||
|
def token_auth_error(status):
|
||||||
|
"Réponse en cas d'erreur d'auth."
|
||||||
|
return error_response(status)
|
||||||
|
|
||||||
|
|
||||||
|
@token_auth.get_user_roles
|
||||||
|
def get_user_roles(user):
|
||||||
|
return user.roles
|
||||||
|
|
||||||
|
|
||||||
|
@login.request_loader
|
||||||
|
def load_user_from_request(req: flask.Request) -> User:
|
||||||
|
"""Custom Login using Request Loader"""
|
||||||
|
# Try token
|
||||||
|
try:
|
||||||
|
auth_type, token = req.headers["Authorization"].split(None, 1)
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
# The Authorization header is either empty or has no token
|
||||||
|
return None
|
||||||
|
if auth_type == "Bearer" and token:
|
||||||
|
return verify_token(token)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@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 redirect(url_for("auth.login"))
|
@ -11,6 +11,7 @@ from time import time
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import cracklib # pylint: disable=import-error
|
import cracklib # pylint: disable=import-error
|
||||||
|
|
||||||
from flask import current_app, g
|
from flask import current_app, g
|
||||||
from flask_login import UserMixin, AnonymousUserMixin
|
from flask_login import UserMixin, AnonymousUserMixin
|
||||||
|
|
||||||
@ -523,8 +524,3 @@ def get_super_admin():
|
|||||||
)
|
)
|
||||||
assert admin_user
|
assert admin_user
|
||||||
return admin_user
|
return admin_user
|
||||||
|
|
||||||
|
|
||||||
@login.user_loader
|
|
||||||
def load_user(uid):
|
|
||||||
return User.query.get(int(uid))
|
|
||||||
|
@ -3,11 +3,8 @@
|
|||||||
auth.routes.py
|
auth.routes.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from flask import current_app, flash, render_template
|
||||||
from flask import current_app, g, flash, render_template
|
|
||||||
from flask import redirect, url_for, request
|
from flask import redirect, url_for, request
|
||||||
from flask_login.utils import login_required
|
|
||||||
from werkzeug.urls import url_parse
|
|
||||||
from flask_login import login_user, logout_user, current_user
|
from flask_login import login_user, logout_user, current_user
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
@ -17,13 +14,11 @@ from app.auth.forms import (
|
|||||||
UserCreationForm,
|
UserCreationForm,
|
||||||
ResetPasswordRequestForm,
|
ResetPasswordRequestForm,
|
||||||
ResetPasswordForm,
|
ResetPasswordForm,
|
||||||
DeactivateUserForm,
|
|
||||||
)
|
)
|
||||||
from app.auth.models import Role
|
from app.auth.models import Role
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.auth.email import send_password_reset_email
|
from app.auth.email import send_password_reset_email
|
||||||
from app.decorators import admin_required
|
from app.decorators import admin_required
|
||||||
from app.decorators import permission_required
|
|
||||||
|
|
||||||
_ = lambda x: x # sans babel
|
_ = lambda x: x # sans babel
|
||||||
_l = _
|
_l = _
|
||||||
@ -31,6 +26,7 @@ _l = _
|
|||||||
|
|
||||||
@bp.route("/login", methods=["GET", "POST"])
|
@bp.route("/login", methods=["GET", "POST"])
|
||||||
def login():
|
def login():
|
||||||
|
"ScoDoc Login form"
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for("scodoc.index"))
|
return redirect(url_for("scodoc.index"))
|
||||||
form = LoginForm()
|
form = LoginForm()
|
||||||
@ -42,9 +38,6 @@ def login():
|
|||||||
return redirect(url_for("auth.login"))
|
return redirect(url_for("auth.login"))
|
||||||
login_user(user, remember=form.remember_me.data)
|
login_user(user, remember=form.remember_me.data)
|
||||||
current_app.logger.info("login: success (%s)", form.user_name.data)
|
current_app.logger.info("login: success (%s)", form.user_name.data)
|
||||||
# next_page = request.args.get("next")
|
|
||||||
# if not next_page or url_parse(next_page).netloc != "":
|
|
||||||
# next_page = url_for("scodoc.index")
|
|
||||||
return form.redirect("scodoc.index")
|
return form.redirect("scodoc.index")
|
||||||
message = request.args.get("message", "")
|
message = request.args.get("message", "")
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -54,6 +47,7 @@ def login():
|
|||||||
|
|
||||||
@bp.route("/logout")
|
@bp.route("/logout")
|
||||||
def logout():
|
def logout():
|
||||||
|
"Logout current user and redirect to home page"
|
||||||
logout_user()
|
logout_user()
|
||||||
return redirect(url_for("scodoc.index"))
|
return redirect(url_for("scodoc.index"))
|
||||||
|
|
||||||
@ -109,9 +103,10 @@ def reset_password_request():
|
|||||||
|
|
||||||
@bp.route("/reset_password/<token>", methods=["GET", "POST"])
|
@bp.route("/reset_password/<token>", methods=["GET", "POST"])
|
||||||
def reset_password(token):
|
def reset_password(token):
|
||||||
|
"Reset passord après demande par mail"
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for("scodoc.index"))
|
return redirect(url_for("scodoc.index"))
|
||||||
user = User.verify_reset_password_token(token)
|
user: User = User.verify_reset_password_token(token)
|
||||||
if user is None:
|
if user is None:
|
||||||
return redirect(url_for("scodoc.index"))
|
return redirect(url_for("scodoc.index"))
|
||||||
form = ResetPasswordForm()
|
form = ResetPasswordForm()
|
||||||
@ -126,6 +121,7 @@ def reset_password(token):
|
|||||||
@bp.route("/reset_standard_roles_permissions", methods=["GET", "POST"])
|
@bp.route("/reset_standard_roles_permissions", methods=["GET", "POST"])
|
||||||
@admin_required
|
@admin_required
|
||||||
def reset_standard_roles_permissions():
|
def reset_standard_roles_permissions():
|
||||||
|
"Réinitialise (recrée au besoin) les rôles standards de ScoDoc et leurs permissions"
|
||||||
Role.reset_standard_roles_permissions()
|
Role.reset_standard_roles_permissions()
|
||||||
flash("rôles standard réinitialisés !")
|
flash("rôles standards réinitialisés !")
|
||||||
return redirect(url_for("scodoc.configuration"))
|
return redirect(url_for("scodoc.configuration"))
|
||||||
|
@ -64,6 +64,7 @@ class Formation(db.Model):
|
|||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
e = dict(self.__dict__)
|
e = dict(self.__dict__)
|
||||||
e.pop("_sa_instance_state", None)
|
e.pop("_sa_instance_state", None)
|
||||||
|
e["departement"] = self.departement.to_dict()
|
||||||
# ScoDoc7 output_formators: (backward compat)
|
# ScoDoc7 output_formators: (backward compat)
|
||||||
e["formation_id"] = self.id
|
e["formation_id"] = self.id
|
||||||
return e
|
return e
|
||||||
|
@ -12,6 +12,7 @@ _SCO_PERMISSIONS = (
|
|||||||
# - ZScoDoc: add/delete departments
|
# - ZScoDoc: add/delete departments
|
||||||
# - tous rôles lors creation utilisateurs
|
# - tous rôles lors creation utilisateurs
|
||||||
(1 << 1, "ScoSuperAdmin", "Super Administrateur"),
|
(1 << 1, "ScoSuperAdmin", "Super Administrateur"),
|
||||||
|
(1 << 2, "APIView", "Voir"), # deprecated
|
||||||
(1 << 2, "ScoView", "Voir"),
|
(1 << 2, "ScoView", "Voir"),
|
||||||
(1 << 3, "ScoEnsView", "Voir les parties pour les enseignants"),
|
(1 << 3, "ScoEnsView", "Voir les parties pour les enseignants"),
|
||||||
(1 << 4, "ScoObservateur", "Observer (accès lecture restreint aux bulletins)"),
|
(1 << 4, "ScoObservateur", "Observer (accès lecture restreint aux bulletins)"),
|
||||||
@ -50,7 +51,7 @@ _SCO_PERMISSIONS = (
|
|||||||
(1 << 27, "RelationsEntreprisesCorrespondants", "Voir les correspondants"),
|
(1 << 27, "RelationsEntreprisesCorrespondants", "Voir les correspondants"),
|
||||||
# 27 à 39 ... réservé pour "entreprises"
|
# 27 à 39 ... réservé pour "entreprises"
|
||||||
# Api scodoc9
|
# Api scodoc9
|
||||||
(1 << 40, "APIView", "API: Lecture"),
|
# XXX à revoir
|
||||||
(1 << 41, "APIEditGroups", "API: Modifier les groupes"),
|
(1 << 41, "APIEditGroups", "API: Modifier les groupes"),
|
||||||
(1 << 42, "APIEditAllNotes", "API: Modifier toutes les notes"),
|
(1 << 42, "APIEditAllNotes", "API: Modifier toutes les notes"),
|
||||||
(1 << 43, "APIAbsChange", "API: Saisir des absences"),
|
(1 << 43, "APIAbsChange", "API: Saisir des absences"),
|
||||||
|
@ -53,7 +53,6 @@ SCO_ROLES_DEFAULTS = {
|
|||||||
p.ScoUsersAdmin,
|
p.ScoUsersAdmin,
|
||||||
p.ScoUsersView,
|
p.ScoUsersView,
|
||||||
p.ScoView,
|
p.ScoView,
|
||||||
p.APIView,
|
|
||||||
),
|
),
|
||||||
# RespPE est le responsable poursuites d'études
|
# RespPE est le responsable poursuites d'études
|
||||||
# il peut ajouter des tags sur les formations:
|
# il peut ajouter des tags sur les formations:
|
||||||
@ -78,7 +77,7 @@ SCO_ROLES_DEFAULTS = {
|
|||||||
p.RelationsEntreprisesCorrespondants,
|
p.RelationsEntreprisesCorrespondants,
|
||||||
),
|
),
|
||||||
# LecteurAPI peut utiliser l'API en lecture
|
# LecteurAPI peut utiliser l'API en lecture
|
||||||
"LecteurAPI": (p.APIView,),
|
"LecteurAPI": (p.ScoView,),
|
||||||
# Super Admin est un root: création/suppression de départements
|
# Super Admin est un root: création/suppression de départements
|
||||||
# _tous_ les droits
|
# _tous_ les droits
|
||||||
# Afin d'avoir tous les droits, il ne doit pas être asscoié à un département
|
# Afin d'avoir tous les droits, il ne doit pas être asscoié à un département
|
||||||
|
@ -55,9 +55,13 @@ class ScoError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def GET(path: str, headers={}, errmsg=None):
|
def GET(path: str, headers={}, errmsg=None, dept=None):
|
||||||
"""Get and returns as JSON"""
|
"""Get and returns as JSON"""
|
||||||
r = requests.get(API_URL + path, headers=headers or HEADERS, verify=CHK_CERT)
|
if dept:
|
||||||
|
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
|
||||||
|
else:
|
||||||
|
url = API_URL + path
|
||||||
|
r = requests.get(url, headers=headers or HEADERS, verify=CHK_CERT)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise ScoError(errmsg or f"""erreur status={r.status_code} !\n{r.text}""")
|
raise ScoError(errmsg or f"""erreur status={r.status_code} !\n{r.text}""")
|
||||||
return r.json() # decode la reponse JSON
|
return r.json() # decode la reponse JSON
|
||||||
@ -170,6 +174,11 @@ POST_JSON(f"/group/5559/delete")
|
|||||||
POST_JSON(f"/group/5327/edit", data={"group_name": "TDXXX"})
|
POST_JSON(f"/group/5327/edit", data={"group_name": "TDXXX"})
|
||||||
|
|
||||||
# --------- XXX à passer en dans les tests unitaires
|
# --------- XXX à passer en dans les tests unitaires
|
||||||
|
|
||||||
|
# 0- Prend un étudiant au hasard dans le semestre
|
||||||
|
etud = GET(f"/formsemestre/{formsemestre_id}/etudiants")[10]
|
||||||
|
etudid = etud["id"]
|
||||||
|
|
||||||
# 1- Crée une partition, puis la change de nom
|
# 1- Crée une partition, puis la change de nom
|
||||||
js = POST_JSON(
|
js = POST_JSON(
|
||||||
f"/formsemestre/{formsemestre_id}/partition/create",
|
f"/formsemestre/{formsemestre_id}/partition/create",
|
||||||
@ -182,21 +191,58 @@ POST_JSON(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 2- Crée un groupe
|
# 2- Crée un groupe
|
||||||
js = POST_JSON(f"/partition/{partition_id}/group/create", data={"group_name": "GG"})
|
js = POST_JSON(f"/partition/{partition_id}/group/create", data={"group_name": "G1"})
|
||||||
group_id = js["id"]
|
group_1 = js["id"]
|
||||||
# Prend un étudiant au hasard dans le semestre
|
|
||||||
etud = GET(f"/formsemestre/{formsemestre_id}/etudiants")[10]
|
|
||||||
etudid = etud["id"]
|
|
||||||
# 3- Affecte étudiant au groupe
|
|
||||||
POST_JSON(f"/group/{group_id}/set_etudiant/{etudid}")
|
|
||||||
|
|
||||||
# 4- retire du groupe
|
# 3- Crée deux autres groupes
|
||||||
POST_JSON(f"/group/{group_id}/remove_etudiant/{etudid}")
|
js = POST_JSON(f"/partition/{partition_id}/group/create", data={"group_name": "G2"})
|
||||||
|
js = POST_JSON(f"/partition/{partition_id}/group/create", data={"group_name": "G3"})
|
||||||
|
|
||||||
# 5- Suppression
|
# 4- Affecte étudiant au groupe G1
|
||||||
|
POST_JSON(f"/group/{group_1}/set_etudiant/{etudid}")
|
||||||
|
|
||||||
|
# 5- retire du groupe
|
||||||
|
POST_JSON(f"/group/{group_1}/remove_etudiant/{etudid}")
|
||||||
|
|
||||||
|
# 6- affecte au groupe G2
|
||||||
|
partition = GET(f"/partition/{partition_id}")
|
||||||
|
assert len(partition["groups"]) == 3
|
||||||
|
group_2 = [g for g in partition["groups"].values() if g["name"] == "G2"][0]["id"]
|
||||||
|
POST_JSON(f"/group/{group_2}/set_etudiant/{etudid}")
|
||||||
|
|
||||||
|
# 7- Membres du groupe
|
||||||
|
etuds_g2 = GET(f"/group/{group_2}/etudiants")
|
||||||
|
assert len(etuds_g2) == 1
|
||||||
|
assert etuds_g2[0]["id"] == etudid
|
||||||
|
|
||||||
|
# 8- Ordres des groupes
|
||||||
|
group_3 = [g for g in partition["groups"].values() if g["name"] == "G3"][0]["id"]
|
||||||
|
|
||||||
|
POST_JSON(
|
||||||
|
f"/partition/{partition_id}/groups/order",
|
||||||
|
data=[group_2, group_1, group_3],
|
||||||
|
)
|
||||||
|
|
||||||
|
new_groups = [g["id"] for g in GET(f"/partition/{partition_id}")["groups"].values()]
|
||||||
|
assert new_groups == [group_2, group_1, group_3]
|
||||||
|
|
||||||
|
# 9- Suppression
|
||||||
POST_JSON(f"/partition/{partition_id}/delete")
|
POST_JSON(f"/partition/{partition_id}/delete")
|
||||||
# ------
|
# ------
|
||||||
|
|
||||||
|
# Tests accès API:
|
||||||
|
"""
|
||||||
|
* En mode API:
|
||||||
|
Avec admin:
|
||||||
|
- GET, POST ci-dessus : OK
|
||||||
|
Avec user ayant ScoView (rôle LecteurAPI)
|
||||||
|
- idem
|
||||||
|
Avec user sans ScoView:
|
||||||
|
- GET et POST: erreur 403
|
||||||
|
* En mode Web:
|
||||||
|
admin: GET
|
||||||
|
user : GET = 403
|
||||||
|
"""
|
||||||
|
|
||||||
#
|
#
|
||||||
POST_JSON(
|
POST_JSON(
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"""Test permissions
|
"""Test permissions
|
||||||
|
|
||||||
On a deux utilisateurs dans la base test API:
|
On a deux utilisateurs dans la base test API:
|
||||||
- "test", avec le rôle LecteurAPI qui a APIView,
|
- "test", avec le rôle LecteurAPI qui a la permission ScoView,
|
||||||
- et "other", qui n'a aucune permission.
|
- et "other", qui n'a aucune permission.
|
||||||
|
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ from config import RunningConfig
|
|||||||
|
|
||||||
def test_permissions(api_headers):
|
def test_permissions(api_headers):
|
||||||
"""
|
"""
|
||||||
vérification de la permissions APIView et du non accès sans role
|
vérification de la permissions ScoView et du non accès sans role
|
||||||
de toutes les routes de l'API
|
de toutes les routes de l'API
|
||||||
"""
|
"""
|
||||||
# Ce test va récupérer toutes les routes de l'API
|
# Ce test va récupérer toutes les routes de l'API
|
||||||
|
@ -101,8 +101,8 @@ def create_users(dept: Departement) -> tuple:
|
|||||||
if role is None:
|
if role is None:
|
||||||
print("Erreur: rôle LecteurAPI non existant")
|
print("Erreur: rôle LecteurAPI non existant")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
perm_api_view = Permission.get_by_name("APIView")
|
perm_sco_view = Permission.get_by_name("ScoView")
|
||||||
role.add_permission(perm_api_view)
|
role.add_permission(perm_sco_view)
|
||||||
db.session.add(role)
|
db.session.add(role)
|
||||||
|
|
||||||
user.add_role(role, None)
|
user.add_role(role, None)
|
||||||
|
Loading…
Reference in New Issue
Block a user