Merge branch 'master' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises
This commit is contained in:
commit
8e730c2164
@ -46,23 +46,16 @@ from app.scodoc.sco_permissions import Permission
|
|||||||
|
|
||||||
@bp.route("/logos")
|
@bp.route("/logos")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
def api_get_glob_logos():
|
def api_get_glob_logos():
|
||||||
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
|
|
||||||
return json_error(403, message="accès interdit")
|
|
||||||
required_format = requested_format() # json only
|
|
||||||
if required_format is None:
|
|
||||||
return json_error(400, "Illegal format")
|
|
||||||
logos = list_logos()[None]
|
logos = list_logos()[None]
|
||||||
return jsonify(list(logos.keys()))
|
return jsonify(list(logos.keys()))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/logos/<string:logoname>")
|
@bp.route("/logo/<string:logoname>")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
def api_get_glob_logo(logoname):
|
def api_get_glob_logo(logoname):
|
||||||
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
|
|
||||||
return json_error(403, message="accès interdit")
|
|
||||||
logo = find_logo(logoname=logoname)
|
logo = find_logo(logoname=logoname)
|
||||||
if logo is None:
|
if logo is None:
|
||||||
return json_error(404, message="logo not found")
|
return json_error(404, message="logo not found")
|
||||||
@ -74,25 +67,27 @@ def api_get_glob_logo(logoname):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/departements/<string:departement>/logos")
|
def core_get_logos(dept_id):
|
||||||
@scodoc
|
|
||||||
@permission_required(Permission.ScoView)
|
|
||||||
def api_get_local_logos(departement):
|
|
||||||
dept_id = Departement.from_acronym(departement).id
|
|
||||||
if not g.current_user.has_permission(Permission.ScoChangePreferences, departement):
|
|
||||||
return json_error(403, message="accès interdit")
|
|
||||||
logos = list_logos().get(dept_id, dict())
|
logos = list_logos().get(dept_id, dict())
|
||||||
return jsonify(list(logos.keys()))
|
return jsonify(list(logos.keys()))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/departements/<string:departement>/logos/<string:logoname>")
|
@bp.route("/departement/<string:departement>/logos")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
def api_get_local_logo(departement, logoname):
|
def api_get_local_logos_by_acronym(departement):
|
||||||
# format = requested_format("jpg", ['png', 'jpg']) XXX ?
|
|
||||||
dept_id = Departement.from_acronym(departement).id
|
dept_id = Departement.from_acronym(departement).id
|
||||||
if not g.current_user.has_permission(Permission.ScoChangePreferences, departement):
|
return core_get_logos(dept_id)
|
||||||
return json_error(403, message="accès interdit")
|
|
||||||
|
|
||||||
|
@bp.route("/departement/id/<int:dept_id>/logos")
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
|
def api_get_local_logos_by_id(dept_id):
|
||||||
|
return core_get_logos(dept_id)
|
||||||
|
|
||||||
|
|
||||||
|
def core_get_logo(dept_id, logoname):
|
||||||
logo = find_logo(logoname=logoname, dept_id=dept_id)
|
logo = find_logo(logoname=logoname, dept_id=dept_id)
|
||||||
if logo is None:
|
if logo is None:
|
||||||
return json_error(404, message="logo not found")
|
return json_error(404, message="logo not found")
|
||||||
@ -102,3 +97,18 @@ def api_get_local_logo(departement, logoname):
|
|||||||
mimetype=f"image/{logo.suffix}",
|
mimetype=f"image/{logo.suffix}",
|
||||||
last_modified=datetime.now(),
|
last_modified=datetime.now(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/departement/<string:departement>/logo/<string:logoname>")
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
|
def api_get_local_logo_dept_by_acronym(departement, logoname):
|
||||||
|
dept_id = Departement.from_acronym(departement).id
|
||||||
|
return core_get_logo(dept_id, logoname)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/departement/id/<int:dept_id>/logo/<string:logoname>")
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
|
def api_get_local_logo_dept_by_id(dept_id, logoname):
|
||||||
|
return core_get_logo(dept_id, logoname)
|
||||||
|
@ -14,9 +14,9 @@ from flask_login import current_user, login_required
|
|||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.api import api_bp as bp, api_web_bp
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.models.etudiants import Identite
|
|
||||||
from app.scodoc.sco_utils import json_error
|
from app.scodoc.sco_utils import json_error
|
||||||
from app.auth.models import User, Role, UserRole
|
from app.auth.models import User, Role, UserRole
|
||||||
|
from app.auth.models import is_valid_password
|
||||||
from app.decorators import scodoc, permission_required
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import Departement
|
from app.models import Departement
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
@ -38,7 +38,7 @@ def user_info(uid: int):
|
|||||||
return json_error(404, "user not found")
|
return json_error(404, "user not found")
|
||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersView)
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersView)
|
||||||
if user.dept not in allowed_depts:
|
if (None not in allowed_depts) and (user.dept not in allowed_depts):
|
||||||
return json_error(404, "user not found")
|
return json_error(404, "user not found")
|
||||||
|
|
||||||
return jsonify(user.to_dict())
|
return jsonify(user.to_dict())
|
||||||
@ -109,7 +109,7 @@ def user_create():
|
|||||||
if dept == "@all":
|
if dept == "@all":
|
||||||
dept = None
|
dept = None
|
||||||
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersAdmin)
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersAdmin)
|
||||||
if dept not in allowed_depts:
|
if (None not in allowed_depts) and (dept not in allowed_depts):
|
||||||
return json_error(403, "user_create: departement non autorise")
|
return json_error(403, "user_create: departement non autorise")
|
||||||
if (dept is not None) and (
|
if (dept is not None) and (
|
||||||
Departement.query.filter_by(acronym=dept).first() is None
|
Departement.query.filter_by(acronym=dept).first() is None
|
||||||
@ -168,6 +168,35 @@ def user_edit(uid: int):
|
|||||||
return jsonify(user.to_dict())
|
return jsonify(user.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/user/<int:uid>/password", methods=["POST"])
|
||||||
|
@api_web_bp.route("/user/<int:uid>/password", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoUsersAdmin)
|
||||||
|
def user_password(uid: int):
|
||||||
|
"""Modification du mot de passe d'un utilisateur
|
||||||
|
Champs modifiables:
|
||||||
|
{
|
||||||
|
"password": str
|
||||||
|
}
|
||||||
|
Si le mot de passe ne convient pas, erreur 400.
|
||||||
|
"""
|
||||||
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
|
user: User = User.query.get_or_404(uid)
|
||||||
|
password = data.get("password")
|
||||||
|
if not password:
|
||||||
|
return json_error(404, "user_password: missing password")
|
||||||
|
if not is_valid_password(password):
|
||||||
|
return json_error(400, "user_password: invalid password")
|
||||||
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoUsersAdmin)
|
||||||
|
if (None not in allowed_depts) and ((user.dept not in allowed_depts)):
|
||||||
|
return json_error(403, "user_password: departement non autorise")
|
||||||
|
user.set_password(password)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify(user.to_dict())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/user/<int:uid>/role/<string:role_name>/add", methods=["POST"])
|
@bp.route("/user/<int:uid>/role/<string:role_name>/add", methods=["POST"])
|
||||||
@api_web_bp.route("/user/<int:uid>/role/<string:role_name>/add", methods=["POST"])
|
@api_web_bp.route("/user/<int:uid>/role/<string:role_name>/add", methods=["POST"])
|
||||||
@bp.route(
|
@bp.route(
|
||||||
|
@ -51,6 +51,7 @@ print(f"API URL={API_URL}")
|
|||||||
|
|
||||||
|
|
||||||
HEADERS = get_auth_headers(API_USER, API_PASSWORD)
|
HEADERS = get_auth_headers(API_USER, API_PASSWORD)
|
||||||
|
admin_h = get_auth_headers("admin_api", "admin_api")
|
||||||
|
|
||||||
departements = GET("/departements", headers=HEADERS)
|
departements = GET("/departements", headers=HEADERS)
|
||||||
pp(departements)
|
pp(departements)
|
||||||
|
@ -6,8 +6,9 @@
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
cd /opt/scodoc/tests/api
|
cd /opt/scodoc/tests/api
|
||||||
python make_samples.py
|
python make_samples.py [entry_names]
|
||||||
|
|
||||||
|
si entry_names est spécifié, la génération est restreints aux exemples cités. expl: `python make_samples departements departement-formsemestres`
|
||||||
doit être exécutée immédiatement apres une initialisation de la base pour test API! (car dépendant des identifiants générés lors de la création des objets)
|
doit être exécutée immédiatement apres une initialisation de la base pour test API! (car dépendant des identifiants générés lors de la création des objets)
|
||||||
cd /opt/scodoc/tests/api
|
cd /opt/scodoc/tests/api
|
||||||
tools/create_database.sh --drop SCODOC_TEST_API && flask db upgrade &&flask sco-db-init --erase && flask init-test-database
|
tools/create_database.sh --drop SCODOC_TEST_API && flask db upgrade &&flask sco-db-init --erase && flask init-test-database
|
||||||
@ -41,6 +42,8 @@ TODO: ajouter un argument au script permettant de ne générer qu'un seul fichie
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pprint import pprint as pp
|
from pprint import pprint as pp
|
||||||
from pprint import pformat as pf
|
from pprint import pformat as pf
|
||||||
@ -69,6 +72,7 @@ class Sample:
|
|||||||
self.url = url
|
self.url = url
|
||||||
self.method = method
|
self.method = method
|
||||||
self.result = None
|
self.result = None
|
||||||
|
self.output = "json"
|
||||||
if permission == "ScoView":
|
if permission == "ScoView":
|
||||||
HEADERS = get_auth_headers("test", "test")
|
HEADERS = get_auth_headers("test", "test")
|
||||||
elif permission == "ScoSuperAdmin":
|
elif permission == "ScoSuperAdmin":
|
||||||
@ -87,16 +91,16 @@ class Sample:
|
|||||||
self.result = POST_JSON(self.url, json.loads(self.content), HEADERS)
|
self.result = POST_JSON(self.url, json.loads(self.content), HEADERS)
|
||||||
elif self.method[0] != "#":
|
elif self.method[0] != "#":
|
||||||
raise Exception(f"Bad method : {self.method}")
|
raise Exception(f"Bad method : {self.method}")
|
||||||
else: # method begin with # => comment
|
|
||||||
print(" pass")
|
|
||||||
self.shorten()
|
self.shorten()
|
||||||
file = open(f"sample_TEST.json.md", "tw")
|
file = open(f"sample_TEST.json.md", "tw")
|
||||||
self.dump(file)
|
self.dump(file)
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
def _shorten(self, item):
|
def _shorten(
|
||||||
|
self, item
|
||||||
|
): # abrege les longues listes (limite à 2 éléments et affiche "... etc. à la place"
|
||||||
if isinstance(item, list):
|
if isinstance(item, list):
|
||||||
return [self._shorten(child) for child in item[:2]]
|
return [self._shorten(child) for child in item[:2]] + ["... etc."]
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def shorten(self):
|
def shorten(self):
|
||||||
@ -117,19 +121,36 @@ class Sample:
|
|||||||
file.write(f"> `{self.content}`\n\n")
|
file.write(f"> `{self.content}`\n\n")
|
||||||
|
|
||||||
file.write("```json\n")
|
file.write("```json\n")
|
||||||
file.write(json.dumps(self.result, indent=4))
|
content = json.dumps(self.result, indent=4, sort_keys=True)
|
||||||
|
content = content.replace("... etc.", "...")
|
||||||
|
# regexp for date like: "2022-08-14T10:01:44.043869+02:00"
|
||||||
|
regexp = re.compile(
|
||||||
|
r'"(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?"'
|
||||||
|
)
|
||||||
|
content = regexp.sub('"2022-08-20T12:00:00.000000+02:00"', content)
|
||||||
|
file.write(content)
|
||||||
file.write("\n```\n\n")
|
file.write("\n```\n\n")
|
||||||
|
|
||||||
|
|
||||||
class Samples:
|
class Samples:
|
||||||
def __init__(self):
|
def __init__(self, entry_names):
|
||||||
|
"""Entry_names: la liste des entrées à reconstruire.
|
||||||
|
si None, la totalité des lignes de samples.csv est prise en compte
|
||||||
|
"""
|
||||||
self.entries = defaultdict(lambda: set())
|
self.entries = defaultdict(lambda: set())
|
||||||
|
self.entry_names = entry_names
|
||||||
|
|
||||||
def add_sample(self, entry, url, method="GET", permission="ScoView", content=None):
|
def add_sample(self, entry, url, method="GET", permission="ScoView", content=None):
|
||||||
show_content = "" if content == "" else f": '{content}'"
|
if self.entry_names is None or entry in self.entry_names:
|
||||||
print(f"{entry:50} {method:5} {url:50} {show_content}")
|
if method[0] == "#":
|
||||||
sample = Sample(url, method, permission, content)
|
detail = "**ignored**"
|
||||||
self.entries[entry].add(sample)
|
elif content == "":
|
||||||
|
detail = ""
|
||||||
|
else:
|
||||||
|
detail = f": {content}"
|
||||||
|
print(f"{entry:50} {method:5} {url:50} {detail}")
|
||||||
|
sample = Sample(url, method, permission, content)
|
||||||
|
self.entries[entry].add(sample)
|
||||||
|
|
||||||
def pp(self):
|
def pp(self):
|
||||||
for entry, samples in self.entries.items():
|
for entry, samples in self.entries.items():
|
||||||
@ -141,12 +162,18 @@ class Samples:
|
|||||||
for entry, samples in self.entries.items():
|
for entry, samples in self.entries.items():
|
||||||
file = open(f"{DATA_DIR}sample_{entry}.json.md", "tw")
|
file = open(f"{DATA_DIR}sample_{entry}.json.md", "tw")
|
||||||
file.write(f"### {entry}\n\n")
|
file.write(f"### {entry}\n\n")
|
||||||
for sample in samples:
|
for sample in sorted(
|
||||||
|
samples, key=lambda s: s.url
|
||||||
|
): # sorted de façon à rendre le fichier résultat déterministe (i.e. indépendant de l ordre d arrivée des résultats)
|
||||||
sample.dump(file)
|
sample.dump(file)
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
|
|
||||||
def make_samples():
|
def make_samples():
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
entry_names = None
|
||||||
|
else:
|
||||||
|
entry_names = sys.argv[1:]
|
||||||
if os.path.exists(DATA_DIR):
|
if os.path.exists(DATA_DIR):
|
||||||
if not os.path.isdir(DATA_DIR):
|
if not os.path.isdir(DATA_DIR):
|
||||||
raise f"{DATA_DIR} existe déjà et n'est pas un répertoire"
|
raise f"{DATA_DIR} existe déjà et n'est pas un répertoire"
|
||||||
@ -157,9 +184,9 @@ def make_samples():
|
|||||||
else:
|
else:
|
||||||
os.mkdir("/tmp/samples")
|
os.mkdir("/tmp/samples")
|
||||||
|
|
||||||
samples = Samples()
|
samples = Samples(entry_names)
|
||||||
# samples.pp()
|
samples_file = os.path.dirname(__file__) + "/samples.csv"
|
||||||
with open("samples.csv") as f:
|
with open(samples_file) as f:
|
||||||
L = [x[:-1].split("\t") for x in f]
|
L = [x[:-1].split("\t") for x in f]
|
||||||
for line in L[1:]:
|
for line in L[1:]:
|
||||||
entry_name = line[0]
|
entry_name = line[0]
|
||||||
|
@ -26,6 +26,7 @@ etudiant_formsemestres /etudiant/nip/11/formsemestres GET
|
|||||||
etudiant-formsemestre-bulletin /etudiant/etudid/11/formsemestre/1/bulletin GET
|
etudiant-formsemestre-bulletin /etudiant/etudid/11/formsemestre/1/bulletin GET
|
||||||
etudiant-formsemestre-bulletin /etudiant/ine/INE11/formsemestre/1/bulletin GET
|
etudiant-formsemestre-bulletin /etudiant/ine/INE11/formsemestre/1/bulletin GET
|
||||||
etudiant-formsemestre-bulletin /etudiant/nip/11/formsemestre/1/bulletin GET
|
etudiant-formsemestre-bulletin /etudiant/nip/11/formsemestre/1/bulletin GET
|
||||||
|
etudiant-formsemestre-bulletin /etudiant/nip/11/formsemestre/1/bulletin/short/pdf GET
|
||||||
etudiant-formsemestre-groups /etudiant/etudid/11/formsemestre/1/groups GET
|
etudiant-formsemestre-groups /etudiant/etudid/11/formsemestre/1/groups GET
|
||||||
formations /formations GET
|
formations /formations GET
|
||||||
formations_ids /formations_ids GET
|
formations_ids /formations_ids GET
|
||||||
@ -67,11 +68,30 @@ partition-groups-order /partition/1/groups/order ScoSuperAdmin POST [ 1 ]
|
|||||||
partition-delete /partition/2/delete ScoSuperAdmin POST
|
partition-delete /partition/2/delete ScoSuperAdmin POST
|
||||||
user-create /user/create ScoSuperAdmin POST {"user_name": "alain", "dept": null, "nom": "alain", "prenom": "bruno", "active": true }
|
user-create /user/create ScoSuperAdmin POST {"user_name": "alain", "dept": null, "nom": "alain", "prenom": "bruno", "active": true }
|
||||||
user-edit /user/10/edit ScoSuperAdmin POST { "dept": "TAPI", "nom": "alain2", "prenom": "bruno2", "active": false }
|
user-edit /user/10/edit ScoSuperAdmin POST { "dept": "TAPI", "nom": "alain2", "prenom": "bruno2", "active": false }
|
||||||
|
user-password /user/3/password ScoSuperAdmin POST { "password": "rePlaCemeNT456averylongandcomplicated" }
|
||||||
|
user-password /user/3/password ScoSuperAdmin POST { "password": "too_simple" }
|
||||||
user-role-add /user/10/role/Observateur/add ScoSuperAdmin POST
|
user-role-add /user/10/role/Observateur/add ScoSuperAdmin POST
|
||||||
user-role-remove /user/10/role/Observateur/remove ScoSuperAdmin POST
|
user-role-remove /user/10/role/Observateur/remove ScoSuperAdmin POST
|
||||||
role-create /role/create/customRole ScoSuperAdmin POST {"permissions": ["ScoView", "ScoUsersView"]}
|
role-create /role/create/customRole ScoSuperAdmin POST {"permissions": ["ScoView", "ScoUsersView"]}
|
||||||
role-remove_permission /role/customRole/remove_permission/ScoUsersView ScoSuperAdmin POST
|
role-remove_permission /role/customRole/remove_permission/ScoUsersView ScoSuperAdmin POST
|
||||||
role-add_permission /role/customRole/add_permission/ScoUsersView ScoSuperAdmin POST
|
role-add_permission /role/customRole/add_permission/ScoUsersView ScoSuperAdmin POST
|
||||||
role-edit /role/customRole/edit ScoSuperAdmin POST { "name" : "LaveurDeVitres", "permissions" : [ "ScoView", "APIView" ] }
|
role-edit /role/customRole/edit ScoSuperAdmin POST { "name" : "LaveurDeVitres", "permissions" : [ "ScoView", "APIView" ] }
|
||||||
role-edit /role/customRole/edit ScoSuperAdmin POST { "name" : "LaveurDeVitres", "permissions" : [ "ScoView", "APIView" ] }
|
|
||||||
role-delete /role/customRole/delete ScoSuperAdmin POST
|
role-delete /role/customRole/delete ScoSuperAdmin POST
|
||||||
|
logos /logos ScoSuperAdmin GET
|
||||||
|
logo /logo/demo ScoSuperAdmin GET
|
||||||
|
departement-logos /departement/TAPI/logos ScoSuperAdmin GET
|
||||||
|
departement-logos /departement/id/1/logos ScoSuperAdmin GET
|
||||||
|
departement-logo /departement/TAPI/logo/demo ScoSuperAdmin GET
|
||||||
|
departement-logo /departement/id/1/logo/demo ScoSuperAdmin GET
|
||||||
|
test-pdf /etudiant/nip/11/formsemestre/1/bulletin/pdf GET
|
||||||
|
test-pdf /etudiant/nip/11/formsemestre/1/bulletin/pdf GET
|
||||||
|
test-pdf /etudiant/etudid/11/formsemestre/1/bulletin/short/pdf GET
|
||||||
|
test-pdf /etudiant/ine/INE11/formsemestre/1/bulletin/short/pdf GET
|
||||||
|
test-pdf /etudiant/nip/11/formsemestre/1/bulletin/short/pdf GET
|
||||||
|
test-pdf /etudiant/etudid/11/formsemestre/1/bulletin/pdf GET
|
||||||
|
test-pdf /etudiant/etudid/11/formsemestre/1/bulletin/short GET
|
||||||
|
test-pdf /etudiant/ine/INE11/formsemestre/1/bulletin/short GET
|
||||||
|
test-pdf /etudiant/nip/11/formsemestre/1/bulletin/short GET
|
||||||
|
test-pdf /etudiant/etudid/11/formsemestre/1/bulletin GET
|
||||||
|
test-pdf /etudiant/ine/INE11/formsemestre/1/bulletin GET
|
||||||
|
test-pdf /etudiant/nip/11/formsemestre/1/bulletin GET
|
||||||
|
Can't render this file because it contains an unexpected character in line 12 and column 60.
|
@ -65,7 +65,9 @@ def api_admin_headers() -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def GET(path: str, headers: dict = None, errmsg=None, dept=None):
|
def GET(path: str, headers: dict = None, errmsg=None, dept=None):
|
||||||
"""Get and returns as JSON"""
|
"""Get and returns as JSON
|
||||||
|
Special case for non json result (image or pdf): return Content-Disposition string (inline or attachment)
|
||||||
|
"""
|
||||||
if dept:
|
if dept:
|
||||||
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
|
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
|
||||||
else:
|
else:
|
||||||
@ -73,13 +75,34 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None):
|
|||||||
r = requests.get(url, headers=headers or {}, verify=CHECK_CERTIFICATE)
|
r = requests.get(url, headers=headers or {}, verify=CHECK_CERTIFICATE)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise APIError(errmsg or f"""erreur status={r.status_code} !""", r.json())
|
raise APIError(errmsg or f"""erreur status={r.status_code} !""", r.json())
|
||||||
|
|
||||||
|
if r.headers.get("Content-Type", None) == "application/json":
|
||||||
|
return r.json() # decode la reponse JSON
|
||||||
|
elif r.headers.get("Content-Type", None) in [
|
||||||
|
"image/jpg",
|
||||||
|
"image/png",
|
||||||
|
"application/pdf",
|
||||||
|
]:
|
||||||
|
retval = {
|
||||||
|
"Content-Type": r.headers.get("Content-Type", None),
|
||||||
|
"Content-Disposition": r.headers.get("Content-Disposition", None),
|
||||||
|
}
|
||||||
|
return retval
|
||||||
|
else:
|
||||||
|
raise APIError(
|
||||||
|
"Unknown returned content {r.headers.get('Content-Type', None} !\n"
|
||||||
|
)
|
||||||
return r.json() # decode la reponse JSON
|
return r.json() # decode la reponse JSON
|
||||||
|
|
||||||
|
|
||||||
def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None):
|
def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None, dept=None):
|
||||||
"""Post"""
|
"""Post"""
|
||||||
|
if dept:
|
||||||
|
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
|
||||||
|
else:
|
||||||
|
url = API_URL + path
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
API_URL + path,
|
url,
|
||||||
json=data,
|
json=data,
|
||||||
headers=headers or {},
|
headers=headers or {},
|
||||||
verify=CHECK_CERTIFICATE,
|
verify=CHECK_CERTIFICATE,
|
||||||
|
@ -14,7 +14,7 @@ utilisation:
|
|||||||
# Ce test a une logique très différente des autres : A UNIFIER
|
# Ce test a une logique très différente des autres : A UNIFIER
|
||||||
|
|
||||||
|
|
||||||
from tests.api.setup_test_api import API_URL
|
from tests.api.setup_test_api import API_URL, api_admin_headers, api_headers
|
||||||
|
|
||||||
from scodoc import app
|
from scodoc import app
|
||||||
from tests.unit.config_test_logos import (
|
from tests.unit.config_test_logos import (
|
||||||
@ -26,48 +26,152 @@ from tests.unit.config_test_logos import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_super_access(create_super_token):
|
def test_super_access(api_admin_headers):
|
||||||
"""
|
"""
|
||||||
Route: /logos
|
Route: /logos
|
||||||
"""
|
"""
|
||||||
dept1, dept2, dept3, token = create_super_token
|
headers = api_admin_headers
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
with app.test_client(api_admin_headers) as client:
|
||||||
with app.test_client() as client:
|
|
||||||
response = client.get(API_URL + "/logos", headers=headers)
|
response = client.get(API_URL + "/logos", headers=headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json is not None
|
assert response.json is not None
|
||||||
|
|
||||||
|
|
||||||
def test_admin_access(create_admin_token):
|
def test_admin_access(api_headers):
|
||||||
"""
|
"""
|
||||||
Route:
|
Route: /logos
|
||||||
"""
|
"""
|
||||||
dept1, dept2, dept3, token = create_admin_token
|
headers = api_headers
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
|
||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
response = client.get(API_URL + "/logos", headers=headers)
|
response = client.get(API_URL + "/logos", headers=headers)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
def test_lambda_access(create_lambda_token):
|
def test_lambda_access(api_headers):
|
||||||
"""
|
"""
|
||||||
Route:
|
Route: /logos
|
||||||
"""
|
"""
|
||||||
dept1, dept2, dept3, token = create_lambda_token
|
headers = api_headers
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
|
||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
response = client.get(API_URL + "/logos", headers=headers)
|
response = client.get(API_URL + "/logos", headers=headers)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
def test_initial_with_header_and_footer(create_super_token):
|
def test_global_logos(api_admin_headers):
|
||||||
"""
|
"""
|
||||||
Route:
|
Route:
|
||||||
"""
|
"""
|
||||||
dept1, dept2, dept3, token = create_super_token
|
headers = api_admin_headers
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
|
||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
response = client.get(API_URL + "/logos", headers=headers)
|
response = client.get(API_URL + "/logos", headers=headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json is not None
|
assert response.json is not None
|
||||||
assert len(response.json) == 7
|
assert (
|
||||||
|
len(response.json) == 4
|
||||||
|
) # 4 items in fakelogo context: ['header', 'footer', 'logo_B', 'logo_C']
|
||||||
|
|
||||||
|
|
||||||
|
def test_local_by_id_logos(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Route: /departement/id/1/logos
|
||||||
|
"""
|
||||||
|
headers = api_admin_headers
|
||||||
|
with app.test_client() as client:
|
||||||
|
response = client.get(API_URL + "/departement/id/1/logos", headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json is not None
|
||||||
|
assert (
|
||||||
|
len(response.json) == 2
|
||||||
|
) # 2 items in dept(1, TAPI) fakelogo context: ['logo_A', 'logo_D']
|
||||||
|
|
||||||
|
|
||||||
|
def test_local_by_name_logos(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Route: /departement/TAPI/logos
|
||||||
|
"""
|
||||||
|
headers = api_admin_headers
|
||||||
|
with app.test_client() as client:
|
||||||
|
response = client.get(API_URL + "/departement/TAPI/logos", headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json is not None
|
||||||
|
assert (
|
||||||
|
len(response.json) == 2
|
||||||
|
) # 2 items in dept(1, TAPI) fakelogo context: ['logo_A', 'logo_D']
|
||||||
|
|
||||||
|
|
||||||
|
def test_local_png_by_id_logo(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Route: /departement/id/1/logo/D
|
||||||
|
"""
|
||||||
|
headers = api_admin_headers
|
||||||
|
with app.test_client() as client:
|
||||||
|
response = client.get(API_URL + "/departement/id/1/logo/D", headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers["Content-Type"] == "image/png"
|
||||||
|
assert response.headers["Content-Disposition"].startswith("inline")
|
||||||
|
assert "logo_D.png" in response.headers["Content-Disposition"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_global_png_logo(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Route: /logo/C
|
||||||
|
"""
|
||||||
|
headers = api_admin_headers
|
||||||
|
with app.test_client() as client:
|
||||||
|
response = client.get(API_URL + "/logo/C", headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers["Content-Type"] == "image/png"
|
||||||
|
assert response.headers["Content-Disposition"].startswith("inline")
|
||||||
|
assert "logo_C.png" in response.headers["Content-Disposition"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_global_jpg_logo(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Route: /logo/B
|
||||||
|
"""
|
||||||
|
headers = api_admin_headers
|
||||||
|
with app.test_client() as client:
|
||||||
|
response = client.get(API_URL + "/logo/B", headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers["Content-Type"] == "image/jpg"
|
||||||
|
assert response.headers["Content-Disposition"].startswith("inline")
|
||||||
|
assert "logo_B.jpg" in response.headers["Content-Disposition"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_local_png_by_name_logo(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Route: /departement/TAPI/logo/A
|
||||||
|
"""
|
||||||
|
headers = api_admin_headers
|
||||||
|
with app.test_client() as client:
|
||||||
|
response = client.get(API_URL + "/departement/TAPI/logo/D", headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers["Content-Type"] == "image/png"
|
||||||
|
assert response.headers["Content-Disposition"].startswith("inline")
|
||||||
|
assert "logo_D.png" in response.headers["Content-Disposition"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_local_jpg_by_id_logo(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Route: /departement/id/1/logo/D
|
||||||
|
"""
|
||||||
|
headers = api_admin_headers
|
||||||
|
with app.test_client() as client:
|
||||||
|
response = client.get(API_URL + "/departement/id/1/logo/A", headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers["Content-Type"] == "image/jpg"
|
||||||
|
assert response.headers["Content-Disposition"].startswith("inline")
|
||||||
|
assert "logo_A.jpg" in response.headers["Content-Disposition"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_local_jpg_by_name_logo(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Route: /departement/TAPI/logo/A
|
||||||
|
"""
|
||||||
|
headers = api_admin_headers
|
||||||
|
with app.test_client() as client:
|
||||||
|
response = client.get(API_URL + "/departement/TAPI/logo/A", headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.headers["Content-Type"] == "image/jpg"
|
||||||
|
assert response.headers["Content-Disposition"].startswith("inline")
|
||||||
|
assert "logo_A.jpg" in response.headers["Content-Disposition"]
|
||||||
|
@ -8,6 +8,7 @@ Utilisation :
|
|||||||
|
|
||||||
from tests.api.setup_test_api import (
|
from tests.api.setup_test_api import (
|
||||||
API_URL,
|
API_URL,
|
||||||
|
APIError,
|
||||||
CHECK_CERTIFICATE,
|
CHECK_CERTIFICATE,
|
||||||
GET,
|
GET,
|
||||||
POST_JSON,
|
POST_JSON,
|
||||||
@ -83,7 +84,7 @@ def test_edit_users(api_admin_headers):
|
|||||||
assert (nb_users + 1) == len(GET("/users/query", headers=admin_h))
|
assert (nb_users + 1) == len(GET("/users/query", headers=admin_h))
|
||||||
# Change le dept et rend inactif
|
# Change le dept et rend inactif
|
||||||
user = POST_JSON(
|
user = POST_JSON(
|
||||||
f"/user/edit/{user['id']}",
|
f"/user/{user['id']}/edit",
|
||||||
{"active": False, "dept": "TAPI"},
|
{"active": False, "dept": "TAPI"},
|
||||||
headers=admin_h,
|
headers=admin_h,
|
||||||
)
|
)
|
||||||
@ -129,3 +130,102 @@ def test_roles(api_admin_headers):
|
|||||||
assert set(role["permissions"]) == {"ScoView", "ScoAbsAddBillet"}
|
assert set(role["permissions"]) == {"ScoView", "ScoAbsAddBillet"}
|
||||||
ans = POST_JSON("/role/Test_Y/delete", headers=admin_h)
|
ans = POST_JSON("/role/Test_Y/delete", headers=admin_h)
|
||||||
assert ans["OK"] is True
|
assert ans["OK"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_modif_users_depts(api_admin_headers):
|
||||||
|
"""
|
||||||
|
Ce test vise à vérifier qu'un admin déclaré sur deux départements peut
|
||||||
|
bien modifier les utilisateurs de ses départements mais pas ceux des autres.
|
||||||
|
"""
|
||||||
|
admin_h = api_admin_headers
|
||||||
|
depts = GET("/departements", headers=admin_h)
|
||||||
|
assert len(depts) > 2
|
||||||
|
dept1, dept2, dept3 = depts[:3]
|
||||||
|
# On va utiliser les 3 1er dept (TAPI, AA, BB)
|
||||||
|
# On crée un nouvel utilisateur "chef2", admin dans les 2 premiers dept
|
||||||
|
# puis un utilisateur lambda, dans le dept 2 (AA)
|
||||||
|
chef2 = POST_JSON(
|
||||||
|
"/user/create",
|
||||||
|
{
|
||||||
|
"user_name": "chef2",
|
||||||
|
"nom": "Chef",
|
||||||
|
"prenom": "Test",
|
||||||
|
"dept": dept1["acronym"], # rattaché à dept1
|
||||||
|
},
|
||||||
|
headers=admin_h,
|
||||||
|
)
|
||||||
|
role_chef = POST_JSON(
|
||||||
|
"/role/create/chef",
|
||||||
|
{"permissions": ["ScoView", "ScoUsersAdmin", "ScoUsersView"]},
|
||||||
|
headers=admin_h,
|
||||||
|
)
|
||||||
|
_ = POST_JSON(
|
||||||
|
f"/user/{chef2['id']}/role/chef/add/departement/{dept1['acronym']}",
|
||||||
|
headers=admin_h,
|
||||||
|
)
|
||||||
|
_ = POST_JSON(
|
||||||
|
f"/user/{chef2['id']}/role/chef/add/departement/{dept2['acronym']}",
|
||||||
|
headers=admin_h,
|
||||||
|
)
|
||||||
|
# Un mot de passe trop simple:
|
||||||
|
ok = False
|
||||||
|
try:
|
||||||
|
_ = POST_JSON(
|
||||||
|
f"/user/{chef2['id']}/password",
|
||||||
|
{"password": "123456"},
|
||||||
|
headers=admin_h,
|
||||||
|
)
|
||||||
|
except APIError as exc:
|
||||||
|
if exc.args[1]["status"] == 400:
|
||||||
|
ok = True
|
||||||
|
assert ok
|
||||||
|
# Un "vrai" mot de passe:
|
||||||
|
chef2_password = "17HIOPpYhabb8qw'E:/jd7FFddjd"
|
||||||
|
_ = POST_JSON(
|
||||||
|
f"/user/{chef2['id']}/password",
|
||||||
|
{"password": chef2_password},
|
||||||
|
headers=admin_h,
|
||||||
|
)
|
||||||
|
# Création user lambda:
|
||||||
|
u_lambda = POST_JSON(
|
||||||
|
"/user/create",
|
||||||
|
{
|
||||||
|
"user_name": "lambda",
|
||||||
|
"nom": "Lambda",
|
||||||
|
"prenom": "Test",
|
||||||
|
"dept": dept2["acronym"],
|
||||||
|
},
|
||||||
|
headers=admin_h,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Le chef va modifier u_lambda:
|
||||||
|
chef_h = get_auth_headers(chef2["user_name"], chef2_password)
|
||||||
|
# on utilise une URL avec département car on n'a pas le droit sur tous:
|
||||||
|
u = POST_JSON(
|
||||||
|
f"/user/{u_lambda['id']}/edit",
|
||||||
|
{"nom": "toto"},
|
||||||
|
headers=chef_h,
|
||||||
|
dept=dept1["acronym"],
|
||||||
|
)
|
||||||
|
assert u["nom"] == "toto"
|
||||||
|
# Dans l'autre ?
|
||||||
|
u = POST_JSON(
|
||||||
|
f"/user/{u_lambda['id']}/edit",
|
||||||
|
{"nom": "toto"},
|
||||||
|
headers=chef_h,
|
||||||
|
dept=dept2["acronym"],
|
||||||
|
)
|
||||||
|
# mais pas dans le troisième:
|
||||||
|
ok = False
|
||||||
|
try:
|
||||||
|
u = POST_JSON(
|
||||||
|
f"/user/{u_lambda['id']}/edit",
|
||||||
|
{"nom": "toto"},
|
||||||
|
headers=chef_h,
|
||||||
|
dept=dept3["acronym"],
|
||||||
|
)
|
||||||
|
except APIError as exc:
|
||||||
|
if exc.args[1]["status"] == 401:
|
||||||
|
ok = True
|
||||||
|
assert ok
|
||||||
|
# Nettoyage:
|
||||||
|
@ -8,7 +8,9 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
|
import os
|
||||||
import random
|
import random
|
||||||
|
import shutil
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@ -45,6 +47,10 @@ REFCOMP_FILENAME = (
|
|||||||
"ressources/referentiels/but2022/competences/but-RT-05012022-081735.xml"
|
"ressources/referentiels/but2022/competences/but-RT-05012022-081735.xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# la réserve de logos
|
||||||
|
LOGOS_STOCK = "/opt/scodoc/tests/ressources/test_logos/"
|
||||||
|
LOGOS_DIR = "/opt/scodoc-data/config/logos/"
|
||||||
|
|
||||||
|
|
||||||
def create_departements(acronyms: list[str]) -> list[Departement]:
|
def create_departements(acronyms: list[str]) -> list[Departement]:
|
||||||
"Create depts"
|
"Create depts"
|
||||||
@ -353,6 +359,27 @@ def create_etape_apo(formsemestre: FormSemestre):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def create_logos():
|
||||||
|
if not os.path.exists(LOGOS_DIR + "logos_1"):
|
||||||
|
os.mkdir(LOGOS_DIR + "logos_1")
|
||||||
|
shutil.copy(
|
||||||
|
LOGOS_STOCK + "logo_A.jpg",
|
||||||
|
LOGOS_DIR + "logos_1/logo_A.jpg",
|
||||||
|
)
|
||||||
|
shutil.copy(
|
||||||
|
LOGOS_STOCK + "logo_D.png",
|
||||||
|
LOGOS_DIR + "logos_1/logo_D.png",
|
||||||
|
)
|
||||||
|
shutil.copy(
|
||||||
|
LOGOS_STOCK + "logo_A.jpg",
|
||||||
|
LOGOS_DIR + "logo_B.jpg",
|
||||||
|
)
|
||||||
|
shutil.copy(
|
||||||
|
LOGOS_STOCK + "logo_D.png",
|
||||||
|
LOGOS_DIR + "logo_C.png",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def init_test_database():
|
def init_test_database():
|
||||||
"""Appelé par la commande `flask init-test-database`
|
"""Appelé par la commande `flask init-test-database`
|
||||||
|
|
||||||
@ -373,6 +400,7 @@ def init_test_database():
|
|||||||
saisie_notes_evaluations(formsemestre, user_lecteur)
|
saisie_notes_evaluations(formsemestre, user_lecteur)
|
||||||
add_absences(formsemestre)
|
add_absences(formsemestre)
|
||||||
create_etape_apo(formsemestre)
|
create_etape_apo(formsemestre)
|
||||||
|
create_logos()
|
||||||
# à compléter
|
# à compléter
|
||||||
# - groupes
|
# - groupes
|
||||||
# - absences
|
# - absences
|
||||||
|
Loading…
Reference in New Issue
Block a user