diff --git a/app/api/departements.py b/app/api/departements.py index a8d76d8c59..49812916d7 100644 --- a/app/api/departements.py +++ b/app/api/departements.py @@ -1,12 +1,15 @@ ############################################### Departements ########################################################## -from flask import jsonify +from flask import jsonify, request import app -from app import models +from app import db, log from app.api import api_bp as bp +from app.api.errors import error_response from app.decorators import scodoc, permission_required from app.models import Departement, FormSemestre +from app.models import departements +from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_permissions import Permission @@ -24,7 +27,7 @@ def get_departement(dept_ident: str) -> Departement: @bp.route("/departements", methods=["GET"]) @scodoc @permission_required(Permission.ScoView) -def departements(): +def departements_list(): """Liste les départements""" return jsonify([dept.to_dict() for dept in Departement.query]) @@ -68,6 +71,66 @@ def departement_by_id(dept_id: int): return jsonify(dept.to_dict()) +@bp.route("/departement/create", methods=["POST"]) +@scodoc +@permission_required(Permission.ScoSuperAdmin) +def departement_create(): + """ + Création d'un département. + The request content type should be "application/json": + { + "acronym": str, + "visible":bool, + } + """ + data = request.get_json(force=True) # may raise 400 Bad Request + acronym = str(data.get("acronym", "")) + if not acronym: + return error_response(404, "missing acronym") + visible = bool(data.get("visible", True)) + try: + dept = departements.create_dept(acronym, visible=visible) + except ScoValueError as exc: + return error_response(404, exc.args[0] if exc.args else "") + return jsonify(dept.to_dict()) + + +@bp.route("/departement//edit", methods=["POST"]) +@scodoc +@permission_required(Permission.ScoSuperAdmin) +def departement_edit(acronym): + """ + Edition d'un département: seul visible peut être modifié + The request content type should be "application/json": + { + "visible":bool, + } + """ + dept = Departement.query.filter_by(acronym=acronym).first_or_404() + data = request.get_json(force=True) # may raise 400 Bad Request + visible = bool(data.get("visible", None)) + if visible is None: + return error_response(404, "missing argument: visible") + visible = bool(visible) + dept.visible = visible + db.session.add(dept) + db.session.commit() + return jsonify(dept.to_dict()) + + +@bp.route("/departement//delete", methods=["POST"]) +@scodoc +@permission_required(Permission.ScoSuperAdmin) +def departement_delete(acronym): + """ + Suppression d'un département. + """ + dept = Departement.query.filter_by(acronym=acronym).first_or_404() + db.session.delete(dept) + db.session.commit() + return jsonify({"OK": True}) + + @bp.route("/departement//etudiants", methods=["GET"]) @scodoc @permission_required(Permission.ScoView) @@ -168,11 +231,10 @@ def dept_formsemestres_courants(acronym: str): ... ] """ - # Le département, spécifié par un id ou un acronyme dept = Departement.query.filter_by(acronym=acronym).first_or_404() # Les semestres en cours de ce département - formsemestres = models.FormSemestre.query.filter( + formsemestres = FormSemestre.query.filter( FormSemestre.dept_id == dept.id, FormSemestre.date_debut <= app.db.func.now(), FormSemestre.date_fin >= app.db.func.now(), @@ -192,7 +254,7 @@ def dept_formsemestres_courants_by_id(dept_id: int): dept = Departement.query.get_or_404(dept_id) # Les semestres en cours de ce département - formsemestres = models.FormSemestre.query.filter( + formsemestres = FormSemestre.query.filter( FormSemestre.dept_id == dept.id, FormSemestre.date_debut <= app.db.func.now(), FormSemestre.date_fin >= app.db.func.now(), diff --git a/tests/api/setup_test_api.py b/tests/api/setup_test_api.py index 0f28617172..a24be6b5d0 100644 --- a/tests/api/setup_test_api.py +++ b/tests/api/setup_test_api.py @@ -25,21 +25,32 @@ SCODOC_URL = os.environ["SCODOC_URL"] API_URL = SCODOC_URL + "/ScoDoc/api" API_USER = os.environ.get("API_USER", "test") API_PASSWORD = os.environ.get("API_PASSWD", "test") +API_USER_ADMIN = os.environ.get("API_USER_ADMIN", "admin_api") +API_PASSWORD_ADMIN = os.environ.get("API_PASSWD_ADMIN", "admin_api") DEPT_ACRONYM = "TAPI" print(f"SCODOC_URL={SCODOC_URL}") print(f"API URL={API_URL}") -@pytest.fixture -def api_headers() -> dict: - """ - Demande un jeton et renvoie un dict à utiliser dans les en-têtes de requêtes http - """ - r0 = requests.post(API_URL + "/tokens", auth=(API_USER, API_PASSWORD)) +def get_auth_headers(user, password) -> dict: + "Demande de jeton, dict à utiliser dans les en-têtes de requêtes http" + r0 = requests.post(API_URL + "/tokens", auth=(user, password)) token = r0.json()["token"] return {"Authorization": f"Bearer {token}"} +@pytest.fixture +def api_headers() -> dict: + """Jeton, utilisateur API ordinaire""" + return get_auth_headers(API_USER, API_PASSWORD) + + +@pytest.fixture +def api_admin_headers() -> dict: + """Jeton, utilisateur API SuperAdmin""" + return get_auth_headers(API_USER_ADMIN, API_PASSWORD_ADMIN) + + class APIError(Exception): pass diff --git a/tests/api/test_api_departements.py b/tests/api/test_api_departements.py index de442c9c6b..da6b00d3bb 100644 --- a/tests/api/test_api_departements.py +++ b/tests/api/test_api_departements.py @@ -19,7 +19,14 @@ Utilisation : import requests -from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers +from tests.api.setup_test_api import ( + API_URL, + CHECK_CERTIFICATE, + GET, + POST_JSON, + api_headers, + api_admin_headers, +) from tests.api.tools_test_api import ( verify_fields, DEPARTEMENT_FIELDS, @@ -28,6 +35,34 @@ from tests.api.tools_test_api import ( ) +def test_create_dept(api_admin_headers): + """ + Routes: /departement/create, + /departement//edit + /departement//delete + """ + dept = POST_JSON( + "/departement/create", + {"acronym": "XTEST", "visible": True}, + headers=api_admin_headers, + ) + dept_r = GET(f"/departement/{dept['acronym']}", headers=api_admin_headers) + assert dept["acronym"] == dept_r["acronym"] + assert dept_r["visible"] is True + dept_e = POST_JSON( + f"/departement/{dept['acronym']}/edit", + {"visible": False}, + headers=api_admin_headers, + ) + dept_r = GET(f"/departement/{dept['acronym']}", headers=api_admin_headers) + assert dept_r["visible"] is False + r = POST_JSON( + f"/departement/{dept['acronym']}/delete", + headers=api_admin_headers, + ) + assert r["OK"] is True + + def test_departements(api_headers): """ Routes: /departements_ids, /departement, /departement//formsemestres_ids diff --git a/tools/fakedatabase/create_test_api_database.py b/tools/fakedatabase/create_test_api_database.py index fd836a9270..bb97ec1ca5 100644 --- a/tools/fakedatabase/create_test_api_database.py +++ b/tools/fakedatabase/create_test_api_database.py @@ -106,6 +106,16 @@ def create_users(dept: Departement) -> tuple: other.set_password("other") db.session.add(other) + # Un utilisateur "admin_api" + admin_api = User(user_name="admin_api", nom="Admin", prenom="API") + admin_api.set_password("admin_api") + db.session.add(admin_api) + role = Role.query.filter_by(name="SuperAdmin").first() + if role is None: + print("Erreur: rôle SuperAdmin non existant") + sys.exit(1) + admin_api.add_role(role, None) + db.session.commit() return user, other