forked from ScoDoc/ScoDoc
Fix: scodoc_dept : API
This commit is contained in:
parent
10e0aa28b2
commit
6632fce008
@ -250,6 +250,7 @@ def create_app(config_class=DevConfig):
|
|||||||
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 bp as api_bp
|
||||||
|
from app.api import api_web_bp as api_web_bp
|
||||||
|
|
||||||
# https://scodoc.fr/ScoDoc
|
# https://scodoc.fr/ScoDoc
|
||||||
app.register_blueprint(scodoc_bp)
|
app.register_blueprint(scodoc_bp)
|
||||||
@ -264,6 +265,8 @@ 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")
|
||||||
|
|
||||||
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"
|
||||||
"%(levelname)s: %(message)s"
|
"%(levelname)s: %(message)s"
|
||||||
@ -351,7 +354,7 @@ def create_app(config_class=DevConfig):
|
|||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
def set_sco_dept(scodoc_dept: str):
|
def set_sco_dept(scodoc_dept: str, open_cnx=True):
|
||||||
"""Set global g object to given dept and open db connection if needed"""
|
"""Set global g object to given dept and open db connection if needed"""
|
||||||
# Check that dept exists
|
# Check that dept exists
|
||||||
try:
|
try:
|
||||||
@ -362,7 +365,7 @@ def set_sco_dept(scodoc_dept: str):
|
|||||||
raise ScoValueError(f"Invalid dept: {scodoc_dept}")
|
raise ScoValueError(f"Invalid dept: {scodoc_dept}")
|
||||||
g.scodoc_dept = scodoc_dept # l'acronyme
|
g.scodoc_dept = scodoc_dept # l'acronyme
|
||||||
g.scodoc_dept_id = dept.id # l'id
|
g.scodoc_dept_id = dept.id # l'id
|
||||||
if not hasattr(g, "db_conn"):
|
if open_cnx and not hasattr(g, "db_conn"):
|
||||||
ndb.open_db_connection()
|
ndb.open_db_connection()
|
||||||
if not hasattr(g, "stored_get_formsemestre"):
|
if not hasattr(g, "stored_get_formsemestre"):
|
||||||
g.stored_get_formsemestre = {}
|
g.stored_get_formsemestre = {}
|
||||||
|
@ -5,6 +5,7 @@ from flask import Blueprint
|
|||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
bp = Blueprint("api", __name__)
|
bp = Blueprint("api", __name__)
|
||||||
|
api_web_bp = Blueprint("apiweb", __name__)
|
||||||
|
|
||||||
|
|
||||||
def requested_format(default_format="json", allowed_formats=None):
|
def requested_format(default_format="json", allowed_formats=None):
|
||||||
|
@ -33,7 +33,9 @@ from flask_login import current_user
|
|||||||
|
|
||||||
from app import log
|
from app import log
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
|
from app.api import bp, api_web_bp
|
||||||
from app.api.errors import error_response
|
from app.api.errors import error_response
|
||||||
|
from app.decorators import scodoc, permission_required
|
||||||
|
|
||||||
basic_auth = HTTPBasicAuth()
|
basic_auth = HTTPBasicAuth()
|
||||||
token_auth = HTTPTokenAuth()
|
token_auth = HTTPTokenAuth()
|
||||||
@ -42,7 +44,7 @@ token_auth = HTTPTokenAuth()
|
|||||||
@basic_auth.verify_password
|
@basic_auth.verify_password
|
||||||
def verify_password(username, password):
|
def verify_password(username, password):
|
||||||
"Verify password for this user"
|
"Verify password for this user"
|
||||||
user = User.query.filter_by(user_name=username).first()
|
user: User = User.query.filter_by(user_name=username).first()
|
||||||
if user and user.check_password(password):
|
if user and user.check_password(password):
|
||||||
g.current_user = user
|
g.current_user = user
|
||||||
# note: est aussi basic_auth.current_user()
|
# note: est aussi basic_auth.current_user()
|
||||||
@ -85,7 +87,6 @@ def token_permission_required(permission):
|
|||||||
def decorator(f):
|
def decorator(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
# token_auth.login_required()
|
|
||||||
current_user = basic_auth.current_user()
|
current_user = basic_auth.current_user()
|
||||||
if not current_user or not current_user.has_permission(permission, None):
|
if not current_user or not current_user.has_permission(permission, None):
|
||||||
if current_user:
|
if current_user:
|
||||||
@ -95,6 +96,8 @@ def token_permission_required(permission):
|
|||||||
log(message)
|
log(message)
|
||||||
# raise werkzeug.exceptions.Forbidden(description=message)
|
# raise werkzeug.exceptions.Forbidden(description=message)
|
||||||
return error_response(403, message=None)
|
return error_response(403, message=None)
|
||||||
|
if not hasattr(g, "scodoc_dept"):
|
||||||
|
g.scodoc_dept = None
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
# return decorated_function(token_auth.login_required())
|
# return decorated_function(token_auth.login_required())
|
||||||
@ -125,3 +128,21 @@ def permission_required_api(permission_web, permission_api):
|
|||||||
return decorated_function
|
return decorated_function
|
||||||
|
|
||||||
return decorator
|
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))
|
||||||
|
)
|
||||||
|
@ -15,8 +15,9 @@ from sqlalchemy import or_
|
|||||||
import app
|
import app
|
||||||
from app.api import 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.api.auth import permission_required_api, api_publish, web_publish
|
||||||
from app.api import tools
|
from app.api import tools
|
||||||
|
|
||||||
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
|
||||||
@ -24,6 +25,17 @@ from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud
|
|||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
|
||||||
|
def api_function(arg: int):
|
||||||
|
"""Une fonction quelconque de l'API"""
|
||||||
|
# u = current_user
|
||||||
|
# dept = g.scodoc_dept # peut être None si accès API
|
||||||
|
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)
|
@permission_required_api(Permission.ScoView, Permission.APIView)
|
||||||
@ -164,6 +176,9 @@ 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(
|
||||||
|
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:
|
||||||
@ -175,7 +190,11 @@ def etudiants(etudid: int = None, nip: str = None, ine: str = None):
|
|||||||
404,
|
404,
|
||||||
message="parametre manquant",
|
message="parametre manquant",
|
||||||
)
|
)
|
||||||
|
if not None in allowed_depts:
|
||||||
|
# restreint aux départements autorisés:
|
||||||
|
etuds = etuds.join(Departement).filter(
|
||||||
|
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
||||||
|
)
|
||||||
return jsonify([etud.to_dict_bul(include_urls=False) for etud in query])
|
return jsonify([etud.to_dict_bul(include_urls=False) for etud in query])
|
||||||
|
|
||||||
|
|
||||||
|
@ -182,6 +182,7 @@ def partition_remove_etud(partition_id: int, etudid: int):
|
|||||||
for g in groups:
|
for g in groups:
|
||||||
g.etuds.remove(etud)
|
g.etuds.remove(etud)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
app.set_sco_dept(partition.formsemestre.departement.acronym)
|
||||||
sco_cache.invalidate_formsemestre(partition.formsemestre_id)
|
sco_cache.invalidate_formsemestre(partition.formsemestre_id)
|
||||||
return jsonify({"partition_id": partition_id, "etudid": etudid})
|
return jsonify({"partition_id": partition_id, "etudid": etudid})
|
||||||
|
|
||||||
@ -319,6 +320,7 @@ def formsemestre_order_partitions(formsemestre_id: int):
|
|||||||
p.numero = numero
|
p.numero = numero
|
||||||
db.session.add(p)
|
db.session.add(p)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
app.set_sco_dept(formsemestre.departement.acronym)
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id)
|
sco_cache.invalidate_formsemestre(formsemestre_id)
|
||||||
return jsonify(formsemestre.to_dict())
|
return jsonify(formsemestre.to_dict())
|
||||||
|
|
||||||
@ -343,6 +345,7 @@ def partition_order_groups(partition_id: int):
|
|||||||
group.numero = numero
|
group.numero = numero
|
||||||
db.session.add(group)
|
db.session.add(group)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
app.set_sco_dept(partition.formsemestre.departement.acronym)
|
||||||
sco_cache.invalidate_formsemestre(partition.formsemestre_id)
|
sco_cache.invalidate_formsemestre(partition.formsemestre_id)
|
||||||
return jsonify(partition.to_dict(with_groups=True))
|
return jsonify(partition.to_dict(with_groups=True))
|
||||||
|
|
||||||
|
@ -6,11 +6,13 @@
|
|||||||
"""ScoDoc 9 API : outils
|
"""ScoDoc 9 API : outils
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sqlalchemy import desc
|
from flask_login import current_user
|
||||||
|
from sqlalchemy import desc, or_
|
||||||
|
|
||||||
from app import models
|
from app import models
|
||||||
from app.api.errors import error_response
|
from app.api.errors import error_response
|
||||||
from app.models import Identite, Admission
|
from app.models import Departement, Identite, Admission
|
||||||
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
|
||||||
def get_etud(etudid=None, nip=None, ine=None) -> models.Identite:
|
def get_etud(etudid=None, nip=None, ine=None) -> models.Identite:
|
||||||
@ -24,8 +26,15 @@ 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(
|
||||||
|
Permission.APIView | Permission.ScoView
|
||||||
|
)
|
||||||
|
|
||||||
if etudid is not None:
|
if etudid is not None:
|
||||||
return Identite.query.get(etudid)
|
etud: Identite = Identite.query.get(etudid)
|
||||||
|
if (None in allowed_depts) or etud.departement.acronym in allowed_depts:
|
||||||
|
return etud
|
||||||
|
return None # accès interdit => pas d'étudiant
|
||||||
|
|
||||||
if nip is not None:
|
if nip is not None:
|
||||||
query = Identite.query.filter_by(code_nip=nip)
|
query = Identite.query.filter_by(code_nip=nip)
|
||||||
@ -36,4 +45,9 @@ def get_etud(etudid=None, nip=None, ine=None) -> models.Identite:
|
|||||||
404,
|
404,
|
||||||
message="parametre manquant",
|
message="parametre manquant",
|
||||||
)
|
)
|
||||||
|
if None not in allowed_depts:
|
||||||
|
# restreint aux départements autorisés:
|
||||||
|
etuds = etuds.join(Departement).filter(
|
||||||
|
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
||||||
|
)
|
||||||
return query.join(Admission).order_by(desc(Admission.annee)).first()
|
return query.join(Admission).order_by(desc(Admission.annee)).first()
|
||||||
|
@ -53,16 +53,18 @@ import traceback
|
|||||||
|
|
||||||
from flask import g
|
from flask import g
|
||||||
|
|
||||||
|
import app
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.scodoc.sco_exceptions import ScoException
|
||||||
|
|
||||||
CACHE = None # set in app.__init__.py
|
CACHE = None # set in app.__init__.py
|
||||||
|
|
||||||
|
|
||||||
class ScoDocCache:
|
class ScoDocCache:
|
||||||
"""Cache for ScoDoc objects.
|
"""Cache for ScoDoc objects.
|
||||||
keys are prefixed by the current departement.
|
keys are prefixed by the current departement: g.scodoc_dept MUST be set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
timeout = None # ttl, infinite by default
|
timeout = None # ttl, infinite by default
|
||||||
@ -240,6 +242,15 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
|||||||
if getattr(g, "defer_cache_invalidation", 0) > 0:
|
if getattr(g, "defer_cache_invalidation", 0) > 0:
|
||||||
g.sem_to_invalidate.add(formsemestre_id)
|
g.sem_to_invalidate.add(formsemestre_id)
|
||||||
return
|
return
|
||||||
|
if getattr(g, "scodoc_dept") is None:
|
||||||
|
# appel via API ou tests sans dept:
|
||||||
|
formsemestre = None
|
||||||
|
if formsemestre_id:
|
||||||
|
formsemestre = FormSemestre.query.get(formsemestre_id)
|
||||||
|
if formsemestre is None:
|
||||||
|
raise ScoException("invalidate_formsemestre: departement must be set")
|
||||||
|
app.set_sco_dept(formsemestre.departement.acronym, open_cnx=False)
|
||||||
|
|
||||||
if formsemestre_id is None:
|
if formsemestre_id is None:
|
||||||
# clear all caches
|
# clear all caches
|
||||||
log(
|
log(
|
||||||
|
@ -153,34 +153,6 @@ def sco_publish(route, function, permission, methods=("GET",)):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# --------------------- Quelques essais élémentaires:
|
|
||||||
# @bp.route("/essai")
|
|
||||||
# @scodoc
|
|
||||||
# @permission_required(Permission.ScoView)
|
|
||||||
# @scodoc7func
|
|
||||||
# def essai():
|
|
||||||
# return essai_()
|
|
||||||
|
|
||||||
|
|
||||||
# def essai_():
|
|
||||||
# return "<html><body><h2>essai !</h2><p>%s</p></body></html>" % ()
|
|
||||||
|
|
||||||
|
|
||||||
# def essai2():
|
|
||||||
# err_page = f"""<h3>Destruction du module impossible car il est utilisé dans des semestres existants !</h3>
|
|
||||||
# <p class="help">Il faut d'abord supprimer le semestre. Mais il est peut être préférable de
|
|
||||||
# laisser ce programme intact et d'en créer une nouvelle version pour la modifier.
|
|
||||||
# </p>
|
|
||||||
# <a href="url_for('notes.ue_table', scodoc-dept=g.scodoc_dept, formation_id='XXX')">reprendre</a>
|
|
||||||
# """
|
|
||||||
# raise ScoGenError(err_page)
|
|
||||||
# # raise ScoGenError("une erreur banale")
|
|
||||||
# return essai_("sans request")
|
|
||||||
|
|
||||||
|
|
||||||
# sco_publish("/essai2", essai2, Permission.ScoImplement)
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Notes/ methods
|
# Notes/ methods
|
||||||
|
@ -169,6 +169,7 @@ pp(partitions)
|
|||||||
POST_JSON(f"/group/5559/delete")
|
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
|
||||||
# 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",
|
||||||
@ -192,8 +193,10 @@ POST_JSON(f"/group/{group_id}/set_etudiant/{etudid}")
|
|||||||
# 4- retire du groupe
|
# 4- retire du groupe
|
||||||
POST_JSON(f"/group/{group_id}/remove_etudiant/{etudid}")
|
POST_JSON(f"/group/{group_id}/remove_etudiant/{etudid}")
|
||||||
|
|
||||||
# Suppression
|
# 5- Suppression
|
||||||
POST_JSON(f"/partition/{partition_id}/delete")
|
POST_JSON(f"/partition/{partition_id}/delete")
|
||||||
|
# ------
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
POST_JSON(
|
POST_JSON(
|
||||||
|
Loading…
Reference in New Issue
Block a user