Fix #578 API : Gestion semestre verrouillé. + tests unitaires API OK.

This commit is contained in:
Emmanuel Viennet 2023-01-24 08:12:24 -03:00 committed by iziram
parent f3b2c6d4fe
commit 91e8c9185b
7 changed files with 106 additions and 21 deletions

View File

@ -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) query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
) )
group = query.first_or_404() 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}: 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") 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) query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
) )
group = query.first_or_404() group = query.first_or_404()
if not group.partition.formsemestre.etat:
return json_error(403, "formsemestre verrouillé")
if etud in group.etuds: if etud in group.etuds:
group.etuds.remove(etud) group.etuds.remove(etud)
db.session.commit() db.session.commit()
@ -222,6 +226,8 @@ def partition_remove_etud(partition_id: int, etudid: int):
if g.scodoc_dept: if g.scodoc_dept:
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id) query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
partition = query.first_or_404() partition = query.first_or_404()
if not partition.formsemestre.etat:
return json_error(403, "formsemestre verrouillé")
groups = ( groups = (
GroupDescr.query.filter_by(partition_id=partition_id) GroupDescr.query.filter_by(partition_id=partition_id)
.join(group_membership) .join(group_membership)
@ -252,8 +258,10 @@ def group_create(partition_id: int):
if g.scodoc_dept: if g.scodoc_dept:
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id) query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
partition: Partition = query.first_or_404() partition: Partition = query.first_or_404()
if not partition.formsemestre.etat:
return json_error(403, "formsemestre verrouillé")
if not partition.groups_editable: 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 data = request.get_json(force=True) # may raise 400 Bad Request
group_name = data.get("group_name") group_name = data.get("group_name")
if group_name is None: 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) query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
) )
group: GroupDescr = query.first_or_404() group: GroupDescr = query.first_or_404()
if not group.partition.formsemestre.etat:
return json_error(403, "formsemestre verrouillé")
if not group.partition.groups_editable: 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 formsemestre_id = group.partition.formsemestre_id
log(f"deleting {group}") log(f"deleting {group}")
db.session.delete(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) query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
) )
group: GroupDescr = query.first_or_404() group: GroupDescr = query.first_or_404()
if not group.partition.formsemestre.etat:
return json_error(403, "formsemestre verrouillé")
if not group.partition.groups_editable: 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 data = request.get_json(force=True) # may raise 400 Bad Request
group_name = data.get("group_name") group_name = data.get("group_name")
if group_name is not None: if group_name is not None:
@ -348,6 +360,8 @@ def partition_create(formsemestre_id: int):
if g.scodoc_dept: if g.scodoc_dept:
query = query.filter_by(dept_id=g.scodoc_dept_id) query = query.filter_by(dept_id=g.scodoc_dept_id)
formsemestre: FormSemestre = query.first_or_404(formsemestre_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 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:
@ -396,6 +410,8 @@ def formsemestre_order_partitions(formsemestre_id: int):
if g.scodoc_dept: if g.scodoc_dept:
query = query.filter_by(dept_id=g.scodoc_dept_id) query = query.filter_by(dept_id=g.scodoc_dept_id)
formsemestre: FormSemestre = query.first_or_404(formsemestre_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 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
@ -433,6 +449,8 @@ def partition_order_groups(partition_id: int):
if g.scodoc_dept: if g.scodoc_dept:
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id) query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
partition: Partition = query.first_or_404() 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 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
@ -474,6 +492,8 @@ def partition_edit(partition_id: int):
if g.scodoc_dept: if g.scodoc_dept:
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id) query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
partition: Partition = query.first_or_404() 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 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")
@ -532,6 +552,8 @@ def partition_delete(partition_id: int):
if g.scodoc_dept: if g.scodoc_dept:
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id) query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
partition: Partition = query.first_or_404() partition: Partition = query.first_or_404()
if not partition.formsemestre.etat:
return json_error(403, "formsemestre verrouillé")
if not partition.partition_name: if not partition.partition_name:
return json_error(404, "ne peut pas supprimer la partition par défaut") return json_error(404, "ne peut pas supprimer la partition par défaut")
is_parcours = partition.is_parcours() is_parcours = partition.is_parcours()

View File

@ -63,51 +63,51 @@ class FormSemestre(db.Model):
"False si verrouillé" "False si verrouillé"
modalite = db.Column( modalite = db.Column(
db.String(SHORT_STR_LEN), db.ForeignKey("notes_form_modalites.modalite") 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( gestion_compensation = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false" 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( bul_hide_xml = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false" 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( block_moyennes = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false" 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( block_moyenne_generale = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false" db.Boolean(), nullable=False, default=False, server_default="false"
) )
"Si vrai, la moyenne générale indicative BUT n'est pas calculée" "Si vrai, la moyenne générale indicative BUT n'est pas calculée"
# semestres decales (pour gestion jurys):
gestion_semestrielle = db.Column( gestion_semestrielle = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false" 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( bul_bgcolor = db.Column(
db.String(SHORT_STR_LEN), db.String(SHORT_STR_LEN),
default="white", default="white",
server_default="white", server_default="white",
nullable=False, nullable=False,
) )
# autorise resp. a modifier semestre: "couleur fond bulletins HTML"
resp_can_edit = db.Column( resp_can_edit = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false" 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( resp_can_change_ens = db.Column(
db.Boolean(), nullable=False, default=True, server_default="true" 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( ens_can_edit_eval = db.Column(
db.Boolean(), nullable=False, default=False, server_default="False" 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 ! 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()) elt_annee_apo = db.Column(db.Text())
"code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'"
# Relations: # Relations:
etapes = db.relationship( etapes = db.relationship(

View File

@ -4,4 +4,5 @@ markers =
but_gb but_gb
lemans lemans
lyon lyon
test_test

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.4.30" SCOVERSION = "9.4.31"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"

View File

@ -25,7 +25,7 @@ from app import models
from app.auth.models import User, Role, UserRole from app.auth.models import User, Role, UserRole
from app.entreprises.models import entreprises_reset_database 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 Formation, UniteEns, Matiere, Module
from app.models import FormSemestre, FormSemestreInscription from app.models import FormSemestre, FormSemestreInscription
from app.models import GroupDescr from app.models import GroupDescr
@ -73,6 +73,7 @@ def make_shell_context():
"ctx": app.test_request_context(), "ctx": app.test_request_context(),
"current_app": flask.current_app, "current_app": flask.current_app,
"current_user": current_user, "current_user": current_user,
"Departement": Departement,
"db": db, "db": db,
"Evaluation": Evaluation, "Evaluation": Evaluation,
"flask": flask, "flask": flask,

View File

@ -34,7 +34,11 @@ def test_list_users(api_admin_headers):
# Tous les utilisateurs, vus par SuperAdmin: # Tous les utilisateurs, vus par SuperAdmin:
users = GET("/users/query", headers=admin_h) 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) # Les utilisateurs de chaque département (+ ceux sans département)
all_users = [] all_users = []
for acronym in [dept["acronym"] for dept in depts] + [""]: 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"): for i, u in enumerate(u for u in u_users if u["dept"] != "TAPI"):
headers = get_auth_headers(u["user_name"], "test") headers = get_auth_headers(u["user_name"], "test")
users_by_u = GET("/users/query", headers=headers) users_by_u = GET("/users/query", headers=headers)
assert len(users_by_u) == 4 + i assert len(users_by_u) == nb_TAPI + 1 + i
# explication: tous ont le droit de voir les 3 users de TAPI # explication: tous ont le droit de voir les users de TAPI
# (test, other et u_TAPI)
# plus l'utilisateur de chaque département jusqu'au leur # plus l'utilisateur de chaque département jusqu'au leur
# (u_AA voit AA, u_BB voit AA et BB, etc) # (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["dept"] == "TAPI"
assert user["active"] is False 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): def test_roles(api_admin_headers):
@ -229,3 +236,10 @@ def test_modif_users_depts(api_admin_headers):
ok = True ok = True
assert ok assert ok
# Nettoyage: # 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,
)

47
tests/api/test_test.py Normal file
View File

@ -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)
)