diff --git a/app/api/partitions.py b/app/api/partitions.py index 0b297871c..42307656b 100644 --- a/app/api/partitions.py +++ b/app/api/partitions.py @@ -171,6 +171,8 @@ def set_etud_group(etudid: int, group_id: int): query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id) ) group = query.first_or_404() + if not group.partition.formsemestre.etat: + return json_error(403, "formsemestre verrouillé") if etud.id not in {e.id for e in group.partition.formsemestre.etuds}: return json_error(404, "etud non inscrit au formsemestre du groupe") @@ -197,6 +199,8 @@ def group_remove_etud(group_id: int, etudid: int): query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id) ) group = query.first_or_404() + if not group.partition.formsemestre.etat: + return json_error(403, "formsemestre verrouillé") if etud in group.etuds: group.etuds.remove(etud) db.session.commit() @@ -222,6 +226,8 @@ def partition_remove_etud(partition_id: int, etudid: int): if g.scodoc_dept: query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id) partition = query.first_or_404() + if not partition.formsemestre.etat: + return json_error(403, "formsemestre verrouillé") groups = ( GroupDescr.query.filter_by(partition_id=partition_id) .join(group_membership) @@ -252,8 +258,10 @@ def group_create(partition_id: int): 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.formsemestre.etat: + return json_error(403, "formsemestre verrouillé") if not partition.groups_editable: - return json_error(404, "partition non editable") + return json_error(403, "partition non editable") data = request.get_json(force=True) # may raise 400 Bad Request group_name = data.get("group_name") if group_name is None: @@ -284,8 +292,10 @@ def group_delete(group_id: int): query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id) ) group: GroupDescr = query.first_or_404() + if not group.partition.formsemestre.etat: + return json_error(403, "formsemestre verrouillé") if not group.partition.groups_editable: - return json_error(404, "partition non editable") + return json_error(403, "partition non editable") formsemestre_id = group.partition.formsemestre_id log(f"deleting {group}") db.session.delete(group) @@ -308,8 +318,10 @@ def group_edit(group_id: int): query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id) ) group: GroupDescr = query.first_or_404() + if not group.partition.formsemestre.etat: + return json_error(403, "formsemestre verrouillé") if not group.partition.groups_editable: - return json_error(404, "partition non editable") + return json_error(403, "partition non editable") data = request.get_json(force=True) # may raise 400 Bad Request group_name = data.get("group_name") if group_name is not None: @@ -348,6 +360,8 @@ def partition_create(formsemestre_id: int): if g.scodoc_dept: query = query.filter_by(dept_id=g.scodoc_dept_id) formsemestre: FormSemestre = query.first_or_404(formsemestre_id) + if not formsemestre.etat: + return json_error(403, "formsemestre verrouillé") data = request.get_json(force=True) # may raise 400 Bad Request partition_name = data.get("partition_name") if partition_name is None: @@ -396,6 +410,8 @@ def formsemestre_order_partitions(formsemestre_id: int): if g.scodoc_dept: query = query.filter_by(dept_id=g.scodoc_dept_id) formsemestre: FormSemestre = query.first_or_404(formsemestre_id) + if not formsemestre.etat: + return json_error(403, "formsemestre verrouillé") partition_ids = request.get_json(force=True) # may raise 400 Bad Request if not isinstance(partition_ids, int) and not all( isinstance(x, int) for x in partition_ids @@ -433,6 +449,8 @@ def partition_order_groups(partition_id: int): 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.formsemestre.etat: + return json_error(403, "formsemestre verrouillé") group_ids = request.get_json(force=True) # may raise 400 Bad Request if not isinstance(group_ids, int) and not all( isinstance(x, int) for x in group_ids @@ -474,6 +492,8 @@ def partition_edit(partition_id: int): 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.formsemestre.etat: + return json_error(403, "formsemestre verrouillé") data = request.get_json(force=True) # may raise 400 Bad Request modified = False partition_name = data.get("partition_name") @@ -532,6 +552,8 @@ def partition_delete(partition_id: int): 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.formsemestre.etat: + return json_error(403, "formsemestre verrouillé") if not partition.partition_name: return json_error(404, "ne peut pas supprimer la partition par défaut") is_parcours = partition.is_parcours() diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 3c457d980..bafad116e 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -63,51 +63,51 @@ class FormSemestre(db.Model): "False si verrouillé" modalite = db.Column( db.String(SHORT_STR_LEN), db.ForeignKey("notes_form_modalites.modalite") - ) # "FI", "FAP", "FC", ... - # gestion compensation sem DUT: + ) + "Modalité de formation: 'FI', 'FAP', 'FC', ..." gestion_compensation = db.Column( db.Boolean(), nullable=False, default=False, server_default="false" ) - # ne publie pas le bulletin XML ou JSON: + "gestion compensation sem DUT (inutilisé en APC)" bul_hide_xml = db.Column( db.Boolean(), nullable=False, default=False, server_default="false" ) - # Bloque le calcul des moyennes (générale et d'UE) + "ne publie pas le bulletin XML ou JSON" block_moyennes = db.Column( db.Boolean(), nullable=False, default=False, server_default="false" ) - # Bloque le calcul de la moyenne générale (utile pour BUT) + "Bloque le calcul des moyennes (générale et d'UE)" block_moyenne_generale = db.Column( db.Boolean(), nullable=False, default=False, server_default="false" ) "Si vrai, la moyenne générale indicative BUT n'est pas calculée" - # semestres decales (pour gestion jurys): gestion_semestrielle = db.Column( db.Boolean(), nullable=False, default=False, server_default="false" ) - # couleur fond bulletins HTML: + "Semestres décalés (pour gestion jurys DUT, pas implémenté ou utile en BUT)" bul_bgcolor = db.Column( db.String(SHORT_STR_LEN), default="white", server_default="white", nullable=False, ) - # autorise resp. a modifier semestre: + "couleur fond bulletins HTML" resp_can_edit = db.Column( db.Boolean(), nullable=False, default=False, server_default="false" ) - # autorise resp. a modifier slt les enseignants: + "autorise resp. à modifier le formsemestre" resp_can_change_ens = db.Column( db.Boolean(), nullable=False, default=True, server_default="true" ) - # autorise les ens a creer des evals: + "autorise resp. a modifier slt les enseignants" ens_can_edit_eval = db.Column( db.Boolean(), nullable=False, default=False, server_default="False" ) - # code element semestre Apogee, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...' + "autorise les enseignants à créer des évals dans leurs modimpls" elt_sem_apo = db.Column(db.Text()) # peut être fort long ! - # code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...' + "code element semestre Apogee, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'" elt_annee_apo = db.Column(db.Text()) + "code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'" # Relations: etapes = db.relationship( diff --git a/pytest.ini b/pytest.ini index 67d286601..d0da2ce1e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,4 +4,5 @@ markers = but_gb lemans lyon + test_test diff --git a/sco_version.py b/sco_version.py index 2a68efadd..d3e3cd25e 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.4.30" +SCOVERSION = "9.4.31" SCONAME = "ScoDoc" diff --git a/scodoc.py b/scodoc.py index 7f46030e7..25ca43156 100755 --- a/scodoc.py +++ b/scodoc.py @@ -25,7 +25,7 @@ from app import models from app.auth.models import User, Role, UserRole from app.entreprises.models import entreprises_reset_database -from app.models import departements +from app.models import Departement, departements from app.models import Formation, UniteEns, Matiere, Module from app.models import FormSemestre, FormSemestreInscription from app.models import GroupDescr @@ -73,6 +73,7 @@ def make_shell_context(): "ctx": app.test_request_context(), "current_app": flask.current_app, "current_user": current_user, + "Departement": Departement, "db": db, "Evaluation": Evaluation, "flask": flask, diff --git a/tests/api/test_api_users.py b/tests/api/test_api_users.py index e9fe5aa4b..8a347700b 100644 --- a/tests/api/test_api_users.py +++ b/tests/api/test_api_users.py @@ -34,7 +34,11 @@ def test_list_users(api_admin_headers): # Tous les utilisateurs, vus par SuperAdmin: users = GET("/users/query", headers=admin_h) - + assert len(users) > 2 + # Les utilisateurs du dept. TAPI + users_TAPI = GET("/users/query?departement=TAPI", headers=admin_h) + nb_TAPI = len(users_TAPI) + assert nb_TAPI > 1 # Les utilisateurs de chaque département (+ ceux sans département) all_users = [] for acronym in [dept["acronym"] for dept in depts] + [""]: @@ -59,9 +63,8 @@ def test_list_users(api_admin_headers): for i, u in enumerate(u for u in u_users if u["dept"] != "TAPI"): headers = get_auth_headers(u["user_name"], "test") users_by_u = GET("/users/query", headers=headers) - assert len(users_by_u) == 4 + i - # explication: tous ont le droit de voir les 3 users de TAPI - # (test, other et u_TAPI) + assert len(users_by_u) == nb_TAPI + 1 + i + # explication: tous ont le droit de voir les users de TAPI # plus l'utilisateur de chaque département jusqu'au leur # (u_AA voit AA, u_BB voit AA et BB, etc) @@ -90,6 +93,10 @@ def test_edit_users(api_admin_headers): ) assert user["dept"] == "TAPI" assert user["active"] is False + user = GET(f"/user/{user['id']}", headers=admin_h) + assert user["nom"] == "Toto" + assert user["dept"] == "TAPI" + assert user["active"] is False def test_roles(api_admin_headers): @@ -229,3 +236,10 @@ def test_modif_users_depts(api_admin_headers): ok = True assert ok # Nettoyage: + # on ne peut pas supprimer l'utilisateur lambda, mais on + # le rend inactif et on le retire de son département + u = POST_JSON( + f"/user/{u_lambda['id']}/edit", + {"active": False, "dept": None}, + headers=admin_h, + ) diff --git a/tests/api/test_test.py b/tests/api/test_test.py new file mode 100644 index 000000000..28c62b277 --- /dev/null +++ b/tests/api/test_test.py @@ -0,0 +1,47 @@ +# -*- coding: UTF-8 -* + +"""Unit tests for... tests + +Ensure test DB is in the expected initial state. + +Usage: pytest tests/unit/test_test.py +""" + +import pytest + +from tests.api.setup_test_api import ( + api_headers, + GET, +) + + +@pytest.mark.test_test +def test_test_db(api_headers): + """Check that we indeed have: 2 users, 1 dept, 3 formsemestres. + Juste après init, les ensembles seront ceux donnés ci-dessous. + Les autres tests peuvent ajouter des éléments, c'edt pourquoi on utilise issubset(). + """ + headers = api_headers + assert { + "admin_api", + "admin", + "lecteur_api", + "other", + "test", + "u_AA", + "u_BB", + "u_CC", + "u_DD", + "u_TAPI", + }.issubset({u["user_name"] for u in GET("/users/query", headers=headers)}) + assert { + "AA", + "BB", + "CC", + "DD", + "TAPI", + }.issubset({d["acronym"] for d in GET("/departements", headers=headers)}) + assert 1 in ( + formsemestre["semestre_id"] + for formsemestre in GET("/formsemestres/query", headers=headers) + )