Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into dev93

This commit is contained in:
Emmanuel Viennet 2022-05-06 07:33:37 +02:00
commit fc0413303f
53 changed files with 1627 additions and 2364 deletions

View File

@ -22,8 +22,6 @@ def requested_format(default_format="json", allowed_formats=None):
from app.api import tokens
from app.api import sco_api
from app.api import test_api
from app.api import departements
from app.api import etudiants
from app.api import formations

View File

@ -4,7 +4,7 @@ from flask import jsonify
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.api.auth import token_auth, token_permission_required
from app.api.tools import get_etu_from_etudid_or_nip_or_ine
from app.scodoc import notesdb as ndb
@ -16,6 +16,7 @@ from app.scodoc.sco_permissions import Permission
@bp.route("/absences/etudid/<int:etudid>", methods=["GET"])
@bp.route("/absences/nip/<int:nip>", methods=["GET"])
@bp.route("/absences/ine/<int:ine>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def absences(etudid: int = None, nip: int = None, ine: int = None):
"""
@ -69,6 +70,7 @@ def absences(etudid: int = None, nip: int = None, ine: int = None):
@bp.route("/absences/etudid/<int:etudid>/just", methods=["GET"])
@bp.route("/absences/nip/<int:nip>/just", methods=["GET"])
@bp.route("/absences/ine/<int:ine>/just", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def absences_just(etudid: int = None, nip: int = None, ine: int = None):
"""
@ -93,7 +95,7 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None):
"jour": "Fri, 15 Apr 2022 00:00:00 GMT",
"matin": false,
"estabs": true,
"estjust": false,
"estjust": true,
"description": "",
"begin": "2022-04-15 12:00:00",
"end": "2022-04-15 17:59:59"
@ -113,38 +115,48 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None):
)
# Récupération des absences justifiées de l'étudiant
absences = sco_abs.list_abs_date(etudid)
for absence in [absence for absence in absences if absence["estjust"]]:
abs_just = [
absence for absence in sco_abs.list_abs_date(etudid) if absence["estjust"]
]
for absence in abs_just:
absence["jour"] = absence["jour"].isoformat()
return jsonify(absences)
return jsonify(abs_just)
@bp.route(
"/absences/abs_group_etat/?group_id=<int:group_id>&date_debut=date_debut&date_fin=date_fin",
methods=["GET"],
)
@token_permission_required(Permission.APIView)
def abs_groupe_etat( # XXX A REVOIR XXX
group_id: int, date_debut, date_fin, with_boursier=True, format="html"
):
"""
Retoune la liste des absences d'un ou plusieurs groupes entre deux dates
"""
# Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date()
# XXX TODO INACHEVEE
# @bp.route(
# "/absences/abs_group_etat/<int:group_id>",
# methods=["GET"],
# )
# @bp.route(
# "/absences/abs_group_etat/group_id/<int:group_id>/date_debut/<string:date_debut>/date_fin/<string:date_fin>",
# methods=["GET"],
# )
# @token_auth.login_required
# @token_permission_required(Permission.APIView)
# def abs_groupe_etat( # XXX A REVOIR XXX
# group_id: int, date_debut, date_fin, with_boursier=True, format="html"
# ):
# """
# Liste des absences d'un ou plusieurs groupes entre deux dates
# """
# return error_response(501, message="Not implemented")
try:
# Utilisation de la fonction get_group_members
members = get_group_members(group_id)
except ValueError:
return error_response(
409, message="La requête ne peut être traitée en létat actuel"
)
# # Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date()
data = []
# Filtre entre les deux dates renseignées
for member in members:
abs = sco_abs.list_abs_date(member.id, date_debut, date_fin)
data.append(abs)
# try:
# # Utilisation de la fonction get_group_members
# members = get_group_members(group_id)
# except ValueError:
# return error_response(
# 409, message="La requête ne peut être traitée en létat actuel"
# )
# return jsonify(data) # XXX TODO faire en sorte de pouvoir renvoyer sa (ex to_dict() dans absences)
return error_response(501, message="Not implemented")
# data = []
# # Filtre entre les deux dates renseignées
# for member in members:
# abs = sco_abs.list_abs_date(member.id, date_debut, date_fin)
# data.append(abs)
# # return jsonify(data) # XXX TODO faire en sorte de pouvoir renvoyer sa (ex to_dict() dans absences)
# return error_response(501, message="Not implemented")

View File

@ -30,6 +30,8 @@ from functools import wraps
from flask import abort
from flask import g
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth
from app import log
from app.auth.models import User
from app.api.errors import error_response
@ -39,19 +41,23 @@ token_auth = HTTPTokenAuth()
@basic_auth.verify_password
def verify_password(username, password):
"Verify password for this user"
user = User.query.filter_by(user_name=username).first()
if user and user.check_password(password):
g.current_user = user
# note: est aussi basic_auth.current_user()
return user
@basic_auth.error_handler
def basic_auth_error(status):
"error response (401 for invalid auth.)"
return error_response(status)
@token_auth.verify_token
def verify_token(token):
def verify_token(token) -> User:
"Retrouve l'utilisateur à partir du jeton"
user = User.check_token(token) if token else None
g.current_user = user
return user
@ -59,6 +65,7 @@ def verify_token(token):
@token_auth.error_handler
def token_auth_error(status):
"rréponse en cas d'erreur d'auth."
return error_response(status)
@ -68,16 +75,22 @@ def get_user_roles(user):
def token_permission_required(permission):
"Décorateur pour les fontions de l'API ScoDoc"
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
scodoc_dept = getattr(g, "scodoc_dept", None)
if hasattr(g, "current_user") and not g.current_user.has_permission(
permission, scodoc_dept
):
# token_auth.login_required()
current_user = basic_auth.current_user()
if not current_user or not current_user.has_permission(permission, None):
if current_user:
log(f"API permission denied (user {current_user})")
else:
log("API permission denied (no user supplied)")
abort(403)
return f(*args, **kwargs)
return decorated_function # login_required(decorated_function)
# return decorated_function(token_auth.login_required())
return decorated_function
return decorator

View File

@ -1,55 +1,66 @@
############################################### Departements ##########################################################
import app
from app import models
from app.api import bp
from app.api.auth import token_permission_required
from app.api.errors import error_response
from app.scodoc.sco_permissions import Permission
from flask import jsonify
import app
from app import models
from app.api import bp
from app.api.auth import token_auth, token_permission_required
from app.models import Departement, FormSemestre
from app.scodoc.sco_permissions import Permission
@bp.route("/departements", methods=["GET"])
def get_departement(dept_ident: str) -> Departement:
"Le departement, par id ou acronyme. Erreur 404 si pas trouvé."
try:
dept_id = int(dept_ident)
except ValueError:
dept_id = None
if dept_id is None:
return Departement.query.filter_by(acronym=dept_ident).first_or_404()
return Departement.query.get_or_404(dept_id)
@bp.route("/departements_ids", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def departements():
def departements_ids():
"""Liste des ids de départements"""
return jsonify([dept.id for dept in Departement.query])
@bp.route("/departement/<string:dept_ident>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def departement(dept_ident: str):
"""
Retourne la liste des ids de départements visibles
Info sur un département. Accès par id ou acronyme.
Exemple de résultat :
[
{
"id": 1,
"acronym": "TAPI",
"description": null,
"visible": true,
"date_creation": "Fri, 15 Apr 2022 12:19:28 GMT"
},
{
"id": 2,
"acronym": "MMI",
"description": null,
"visible": false,
"date_creation": "Fri, 18 Apr 2022 11:20:8 GMT"
},
...
]
}
"""
# Récupération de tous les départements
depts = models.Departement.query.all()
# Mise en place de la liste avec tous les départements
data = [d.to_dict() for d in depts]
return jsonify(data)
dept = get_departement(dept_ident)
return jsonify(dept.to_dict())
@bp.route("/departements/<string:dept>/etudiants/liste", methods=["GET"])
@bp.route(
"/departements/<string:dept>/etudiants/liste/<int:formsemestre_id>", methods=["GET"]
)
@bp.route("/departements", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def liste_etudiants(dept: str, formsemestre_id=None):
def departements():
"""Liste les départements"""
return jsonify([dept.to_dict() for dept in Departement.query])
@bp.route("/departement/<string:dept_ident>/etudiants", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def list_etudiants(dept_ident: str):
"""
Retourne la liste des étudiants d'un département
@ -59,56 +70,39 @@ def liste_etudiants(dept: str, formsemestre_id=None):
Exemple de résultat :
[
{
"civilite": "X",
"code_ine": null,
"code_nip": null,
"civilite": "M",
"ine": "7899X61616",
"nip": "F6777H88",
"date_naissance": null,
"email": null,
"email": "toto@toto.fr",
"emailperso": null,
"etudid": 18,
"nom": "MOREL",
"prenom": "JACQUES"
},
{
"civilite": "X",
"code_ine": null,
"code_nip": null,
"date_naissance": null,
"email": null,
"emailperso": null,
"etudid": 19,
"nom": "FOURNIER",
"prenom": "ANNE"
},
...
]
"""
# Si le formsemestre_id a été renseigné
if formsemestre_id is not None:
# Récupération du formsemestre
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
# Récupération du département
departement = formsemestre.departement
# Le département, spécifié par un id ou un acronyme
dept = get_departement(dept_ident)
# Si le formsemestre_id n'a pas été renseigné
else:
# Récupération du formsemestre
departement = models.Departement.query.filter_by(acronym=dept).first_or_404()
# Récupération des étudiants
etudiants = departement.etudiants.all()
# Mise en forme des données
list_etu = [etu.to_dict_bul(include_urls=False) for etu in etudiants]
return jsonify(list_etu)
return jsonify([etud.to_dict_short() for etud in dept.etudiants])
@bp.route("/departements/<string:dept>/semestres_courants", methods=["GET"])
@bp.route("/departement/<string:dept_ident>/formsemestres_ids", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def liste_semestres_courant(dept: str):
def formsemestres_ids(dept_ident: str):
"""liste des ids formsemestre du département"""
# Le département, spécifié par un id ou un acronyme
dept = get_departement(dept_ident)
return jsonify([formsemestre.id for formsemestre in dept.formsemestres])
@bp.route("/departement/<string:dept_ident>/formsemestres_courants", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def liste_semestres_courant(dept_ident: str):
"""
Liste des semestres actifs d'un départements donné
@ -149,155 +143,14 @@ def liste_semestres_courant(dept: str):
...
]
"""
# Récupération des départements comportant l'acronym mit en paramètre
dept = models.Departement.query.filter_by(acronym=dept).first_or_404()
# Le département, spécifié par un id ou un acronyme
dept = get_departement(dept_ident)
# Récupération des semestres suivant id_dept
semestres = models.FormSemestre.query.filter_by(dept_id=dept.id, etat=True)
# Mise en forme des données
data = [d.to_dict() for d in semestres]
return jsonify(data)
@bp.route(
"/departements/<string:dept>/formations/<int:formation_id>/referentiel_competences",
methods=["GET"],
# Les semestres en cours de ce département
formsemestres = models.FormSemestre.query.filter(
FormSemestre.dept_id == dept.id,
FormSemestre.date_debut <= app.db.func.now(),
FormSemestre.date_fin >= app.db.func.now(),
)
@token_permission_required(Permission.APIView)
def referenciel_competences(dept: str, formation_id: int):
"""
Retourne le référentiel de compétences
dept : l'acronym d'un département
formation_id : l'id d'une formation
"""
dept = models.Departement.query.filter_by(acronym=dept).first_or_404()
formation = models.Formation.query.filter_by(
id=formation_id, dept_id=dept.id
).first_or_404()
ref_comp = formation.referentiel_competence_id
if ref_comp is None:
return error_response(
204, message="Pas de référenciel de compétences pour cette formation"
)
else:
return jsonify(ref_comp)
@bp.route(
"/departements/<string:dept>/formsemestre/<string:formsemestre_id>/programme",
methods=["GET"],
)
@token_permission_required(Permission.APIView)
def semestre_index(dept: str, formsemestre_id: int):
"""
Retourne la liste des Ues, ressources et SAE d'un semestre
dept : l'acronym d'un département
formsemestre_id : l'id d'un formesemestre
Exemple de résultat :
{
"ues": [
{
"type": 0,
"formation_id": 1,
"ue_code": "UCOD11",
"id": 1,
"ects": 12.0,
"acronyme": "RT1.1",
"is_external": false,
"numero": 1,
"code_apogee": "",
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
"coefficient": 0.0,
"semestre_idx": 1,
"color": "#B80004",
"ue_id": 1
},
...
],
"ressources": [
{
"titre": "Fondamentaux de la programmation",
"coefficient": 1.0,
"module_type": 2,
"id": 17,
"ects": null,
"abbrev": null,
"ue_id": 3,
"code": "R107",
"formation_id": 1,
"heures_cours": 0.0,
"matiere_id": 3,
"heures_td": 0.0,
"semestre_id": 1,
"heures_tp": 0.0,
"numero": 70,
"code_apogee": "",
"module_id": 17
},
...
],
"saes": [
{
"titre": "Se pr\u00e9senter sur Internet",
"coefficient": 1.0,
"module_type": 3,
"id": 14,
"ects": null,
"abbrev": null,
"ue_id": 3,
"code": "SAE14",
"formation_id": 1,
"heures_cours": 0.0,
"matiere_id": 3,
"heures_td": 0.0,
"semestre_id": 1,
"heures_tp": 0.0,
"numero": 40,
"code_apogee": "",
"module_id": 14
},
...
]
}
"""
app.set_sco_dept(dept)
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
ues = formsemestre.query_ues()
ues_dict = []
ressources = []
saes = []
for ue in ues:
ues_dict.append(ue.to_dict())
ressources = ue.get_ressources()
saes = ue.get_saes()
data_ressources = []
for ressource in ressources:
data_ressources.append(ressource.to_dict())
data_saes = []
for sae in saes:
data_saes.append(sae.to_dict())
data = {
"ues": ues_dict,
"ressources": data_ressources,
"saes": data_saes,
}
return data
return jsonify([d.to_dict() for d in formsemestres])

View File

@ -6,7 +6,7 @@ import app
from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.api.auth import token_auth, token_permission_required
from app.api.tools import get_etu_from_etudid_or_nip_or_ine
from app.models import FormSemestreInscription, FormSemestre, Identite
from app.scodoc import sco_bulletins
@ -16,39 +16,31 @@ from app.scodoc.sco_permissions import Permission
@bp.route("/etudiants/courant", defaults={"long": False})
@bp.route("/etudiants/courant/long", defaults={"long": True})
@token_auth.login_required
@token_permission_required(Permission.APIView)
def etudiants_courant(long=False):
"""
Retourne la liste des étudiants courant
Liste des étudiants inscrits dans un formsemestre actuellement en cours.
Exemple de résultat :
[
{
{
"civilite": "X",
"code_ine": null,
"code_nip": null,
"date_naissance": null,
"email": null,
"emailperso": null,
"etudid": 18,
"id": 1,
"nip": 1,
"nom": "MOREL",
"prenom": "JACQUES"
"prenom": "JACQUES",
"civilite": "X",
},
{
"id": 2,
"nip": 2,
"nom": "GILLES",
"prenom": "MAXIME",
"civilite": "X",
"code_ine": null,
"code_nip": null,
"date_naissance": null,
"email": null,
"emailperso": null,
"etudid": 19,
"nom": "FOURNIER",
"prenom": "ANNE"
},
...
}
]
"""
# Récupération de tous les étudiants
etuds = Identite.query.filter(
Identite.id == FormSemestreInscription.etudid,
FormSemestreInscription.formsemestre_id == FormSemestre.id,
@ -59,13 +51,13 @@ def etudiants_courant(long=False):
data = [etud.to_dict_bul(include_urls=False) for etud in etuds]
else:
data = [etud.to_dict_short() for etud in etuds]
return jsonify(data)
@bp.route("/etudiant/etudid/<int:etudid>", methods=["GET"])
@bp.route("/etudiant/nip/<int:nip>", methods=["GET"])
@bp.route("/etudiant/ine/<int:ine>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def etudiant(etudid: int = None, nip: int = None, ine: int = None):
"""
@ -78,14 +70,29 @@ def etudiant(etudid: int = None, nip: int = None, ine: int = None):
Exemple de résultat :
{
"civilite": "X",
"code_ine": null,
"code_nip": null,
"date_naissance": null,
"email": null,
"emailperso": null,
"etudid": 18,
"nom": "MOREL",
"prenom": "JACQUES"
"code_ine": "1",
"code_nip": "1",
"date_naissance": "",
"email": "SACHA.COSTA@example.com",
"emailperso": "",
"etudid": 1,
"nom": "COSTA",
"prenom": "SACHA",
"nomprenom": "Sacha COSTA",
"lieu_naissance": "",
"dept_naissance": "",
"nationalite": "",
"boursier": "",
"id": 1,
"codepostaldomicile": "",
"paysdomicile": "",
"telephonemobile": "",
"typeadresse": "domicile",
"domicile": "",
"villedomicile": "",
"telephone": "",
"fax": "",
"description": ""
}
"""
# Récupération de l'étudiant
@ -100,6 +107,7 @@ def etudiant(etudid: int = None, nip: int = None, ine: int = None):
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
@bp.route("/etudiant/nip/<int:nip>/formsemestres")
@bp.route("/etudiant/ine/<int:ine>/formsemestres")
@token_auth.login_required
@token_permission_required(Permission.APIView)
def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None):
"""
@ -112,15 +120,11 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
Exemple de résultat :
[
{
"titre": "master machine info",
"gestion_semestrielle": false,
"date_debut": "01/09/2021",
"bul_bgcolor": null,
"date_fin": "15/12/2022",
"date_fin": "31/08/2022",
"resp_can_edit": false,
"dept_id": 1,
"etat": true,
"resp_can_change_ens": false,
"resp_can_change_ens": true,
"id": 1,
"modalite": "FI",
"ens_can_edit_eval": false,
@ -130,15 +134,17 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
"semestre_id": 1,
"bul_hide_xml": false,
"elt_annee_apo": null,
"titre": "Semestre test",
"block_moyennes": false,
"scodoc7_id": null,
"date_debut": "01/09/2021",
"gestion_semestrielle": false,
"bul_bgcolor": "white",
"formsemestre_id": 1,
"titre_num": "master machine info semestre 1",
"titre_num": "Semestre test semestre 1",
"date_debut_iso": "2021-09-01",
"date_fin_iso": "2022-12-15",
"responsables": [
3,
2
]
"date_fin_iso": "2022-08-31",
"responsables": []
},
...
]
@ -157,18 +163,41 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
@bp.route(
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin",
methods=["GET"],
defaults={"version": "long"},
)
@bp.route(
"/etudiant/nip/<int:nip>/formsemestre/<int:formsemestre_id>/bulletin",
methods=["GET"],
defaults={"version": "long"},
)
@bp.route(
"/etudiant/ine/<int:ine>/formsemestre/<int:formsemestre_id>/bulletin",
methods=["GET"],
defaults={"version": "long"},
)
@bp.route(
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/short",
methods=["GET"],
defaults={"version": "short"},
)
@bp.route(
"/etudiant/nip/<int:nip>/formsemestre/<int:formsemestre_id>/bulletin/short",
methods=["GET"],
defaults={"version": "short"},
)
@bp.route(
"/etudiant/ine/<int:ine>/formsemestre/<int:formsemestre_id>/bulletin/short",
methods=["GET"],
defaults={"version": "short"},
)
@token_auth.login_required
@token_permission_required(Permission.APIView)
def etudiant_bulletin_semestre(
formsemestre_id, etudid: int = None, nip: int = None, ine: int = None
formsemestre_id,
etudid: int = None,
nip: int = None,
ine: int = None,
version="long",
):
"""
Retourne le bulletin d'un étudiant en fonction de son id et d'un semestre donné
@ -364,25 +393,26 @@ def etudiant_bulletin_semestre(
"Veuillez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
)
return sco_bulletins.get_formsemestre_bulletin_etud_json(formsemestre, etu)
return sco_bulletins.get_formsemestre_bulletin_etud_json(formsemestre, etu, version)
@bp.route(
"/etudiant/etudid/<int:etudid>/semestre/<int:formsemestre_id>/groups",
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/groups",
methods=["GET"],
)
@bp.route(
"/etudiant/nip/<int:nip>/semestre/<int:formsemestre_id>/groups", methods=["GET"]
"/etudiant/nip/<int:nip>/formsemestre/<int:formsemestre_id>/groups", methods=["GET"]
)
@bp.route(
"/etudiant/ine/<int:ine>/semestre/<int:formsemestre_id>/groups", methods=["GET"]
"/etudiant/ine/<int:ine>/formsemestre/<int:formsemestre_id>/groups", methods=["GET"]
)
@token_auth.login_required
@token_permission_required(Permission.APIView)
def etudiant_groups(
formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None
):
"""
Retourne la liste des groupes auxquels appartient l'étudiant dans le semestre indiqué
Retourne la liste des groupes auxquels appartient l'étudiant dans le formsemestre indiqué
formsemestre_id : l'id d'un formsemestre
etudid : l'etudid d'un étudiant

View File

@ -5,13 +5,14 @@ import app
from app import models
from app.api import bp
from app.api.auth import token_permission_required
from app.api.auth import token_auth, token_permission_required
from app.api.errors import error_response
from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes
from app.scodoc.sco_permissions import Permission
@bp.route("/evaluations/<int:moduleimpl_id>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def evaluations(moduleimpl_id: int):
"""
@ -53,7 +54,8 @@ def evaluations(moduleimpl_id: int):
return jsonify(data)
@bp.route("/evaluations/eval_notes/<int:evaluation_id>", methods=["GET"])
@bp.route("/evaluation/eval_notes/<int:evaluation_id>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def evaluation_notes(evaluation_id: int):
"""
@ -86,26 +88,19 @@ def evaluation_notes(evaluation_id: int):
"""
# Fonction utilisée : app.scodoc.sco_evaluation_db.do_evaluation_get_all_notes()
eval = models.Evaluation.query.filter_by(id=evaluation_id).first_or_404()
moduleimpl = models.ModuleImpl.query.filter_by(id=eval.moduleimpl_id).first_or_404()
formsemestre = models.FormSemestre.query.filter_by(
id=moduleimpl.formsemestre_id
).first_or_404()
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
evaluation = models.Evaluation.query.filter_by(id=evaluation_id).first_or_404()
dept = models.Departement.query.filter_by(
id=evaluation.moduleimpl.formsemestre.dept_id
).first()
app.set_sco_dept(dept.acronym)
try:
# Utilisation de la fonction do_evaluation_get_all_notes
data = do_evaluation_get_all_notes(evaluation_id)
except AttributeError:
except AttributeError: # ???
return error_response(
409,
message="La requête ne peut être traitée en létat actuel. \n"
"Veillez vérifier la conformité du 'evaluation_id'",
message="La requête ne peut être traitée en létat actuel.",
)
return jsonify(data)

View File

@ -1,51 +1,38 @@
##############################################" Formations ############################################################
from flask import jsonify
import app
from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.scodoc.sco_formations import formation_export
from app.api.auth import token_auth, token_permission_required
from app.models.formations import Formation
from app.scodoc import sco_formations
from app.scodoc.sco_permissions import Permission
@bp.route("/formations", methods=["GET"])
@bp.route("/formations_ids", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def formations():
def formations_ids():
"""
Retourne la liste des formations
Exemple de résultat :
[
{
"id": 1,
"acronyme": "BUT R&amp;T",
"titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications",
"formation_code": "V1RET",
"code_specialite": null,
"dept_id": 1,
"titre": "BUT R&amp;T",
"version": 1,
"type_parcours": 700,
"referentiel_competence_id": null,
"formation_id": 1
},
...
]
Retourne la liste de toutes les formations (tous départements)
Exemple de résultat : [ 17, 99, 32 ]
"""
# Récupération de toutes les formations
list_formations = models.Formation.query.all()
# Mise en forme des données
data = [d.to_dict() for d in list_formations]
data = [d.id for d in list_formations]
return jsonify(data)
@bp.route("/formations/<int:formation_id>", methods=["GET"])
@bp.route("/formation/<int:formation_id>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def formations_by_id(formation_id: int):
def formation_by_id(formation_id: int):
"""
Retourne une formation en fonction d'un id donné
@ -67,15 +54,25 @@ def formations_by_id(formation_id: int):
}
"""
# Récupération de la formation
forma = models.Formation.query.filter_by(id=formation_id).first_or_404()
formation = models.Formation.query.filter_by(id=formation_id).first_or_404()
# Mise en forme des données
data = forma.to_dict()
data = formation.to_dict()
return jsonify(data)
@bp.route("/formations/formation_export/<int:formation_id>", methods=["GET"])
@bp.route(
"/formation/formation_export/<int:formation_id>",
methods=["GET"],
defaults={"export_ids": False},
)
@bp.route(
"/formation/formation_export/<int:formation_id>/with_ids",
methods=["GET"],
defaults={"export_ids": True},
)
@token_auth.login_required
@token_permission_required(Permission.APIView)
def formation_export_by_formation_id(formation_id: int, export_ids=False):
"""
@ -171,13 +168,13 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False):
},
]
}
"""
# Fonction utilité : app.scodoc.sco_formations.formation_export()
formation = Formation.query.get_or_404(formation_id)
dept = models.Departement.query.filter_by(id=formation.dept_id).first()
app.set_sco_dept(dept.acronym)
try:
# Utilisation de la fonction formation_export
data = formation_export(formation_id)
data = sco_formations.formation_export(formation_id, export_ids)
except ValueError:
return error_response(
409,
@ -188,73 +185,64 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False):
return jsonify(data)
@bp.route("/formations/moduleimpl/<int:moduleimpl_id>", methods=["GET"])
@bp.route("/formation/moduleimpl/<int:moduleimpl_id>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def moduleimpls(moduleimpl_id: int):
def moduleimpl(moduleimpl_id: int):
"""
Retourne la liste des moduleimpl
Retourne un module moduleimpl en fonction de son id
moduleimpl_id : l'id d'un moduleimpl
Exemple de résultat :
{
"id": 1,
"formsemestre_id": 1,
"module_id": 1,
"responsable_id": 2,
"moduleimpl_id": 1,
"ens": [],
"module": {
"heures_tp": 0,
"code_apogee": "",
"titre": "Initiation aux réseaux informatiques",
"coefficient": 1,
"module_type": 2,
"id": 1,
"ects": null,
"abbrev": "Init aux réseaux informatiques",
"ue_id": 1,
"code": "R101",
"formation_id": 1,
"heures_cours": 0,
"matiere_id": 1,
"heures_td": 0,
"semestre_id": 1,
"numero": 10,
"module_id": 1
}
}
"""
# Récupération des tous les moduleimpl
list_moduleimpls = models.ModuleImpl.query.filter_by(id=moduleimpl_id)
# Mise en forme des données
data = [moduleimpl.to_dict() for moduleimpl in list_moduleimpls]
modimpl = models.ModuleImpl.query.filter_by(id=moduleimpl_id).first_or_404()
data = modimpl.to_dict()
return jsonify(data)
@bp.route(
"/formations/moduleimpl/formsemestre/<int:formsemestre_id>/liste",
"/formation/<int:formation_id>/referentiel_competences",
methods=["GET"],
) # XXX TODO penser à changer la route sur la doc
)
@token_auth.login_required
@token_permission_required(Permission.APIView)
def moduleimpls_sem(formsemestre_id: int):
def referentiel_competences(formation_id: int):
"""
Retourne la liste des moduleimpl d'un semestre
Retourne le référentiel de compétences
formation_id : l'id d'une formation
formsemestre_id : l'id d'un formsemestre
Exemple d'utilisation :
[
{
"id": 1,
"formsemestre_id": 1,
"computation_expr": null,
"module_id": 1,
"responsable_id": 2,
"module": {
"heures_tp": 0.0,
"code_apogee": "",
"titre": "Initiation aux r\u00e9seaux informatiques",
"coefficient": 1.0,
"module_type": 2,
"id": 1,
"ects": null,
"abbrev": "Init aux r\u00e9seaux informatiques",
"ue_id": 1,
"code": "R101",
"formation_id": 1,
"heures_cours": 0.0,
"matiere_id": 1,
"heures_td": 0.0,
"semestre_id": 1,
"numero": 10,
"module_id": 1
},
"moduleimpl_id": 1,
"ens": []
},
...
]
return json, ou null si pas de référentiel associé.
"""
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
formation = models.Formation.query.filter_by(id=formation_id).first_or_404()
moduleimpls = formsemestre.modimpls_sorted
data = [moduleimpl.to_dict() for moduleimpl in moduleimpls]
return jsonify(data)
if formation.referentiel_competence is None:
return jsonify(None)
return jsonify(formation.referentiel_competence.to_dict())

View File

@ -4,70 +4,79 @@ from flask import jsonify
import app
from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.api.tools import get_etu_from_etudid_or_nip_or_ine
from app.models import FormSemestre, FormSemestreEtape
from app.api.auth import token_auth, token_permission_required
from app.models import Departement, FormSemestre, FormSemestreEtape
from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json
from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_pvjury import formsemestre_pvjury
from app.scodoc.sco_utils import ModuleType
@bp.route("/formsemestre/<int:formsemestre_id>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def formsemestre(formsemestre_id: int):
"""
Retourne l'information sur le formsemestre correspondant au formsemestre_id
Information sur le formsemestre indiqué.
formsemestre_id : l'id d'un formsemestre
formsemestre_id : l'id du formsemestre
Exemple de résultat :
{
"block_moyennes": false,
"bul_bgcolor": "white",
"bul_hide_xml": false,
"date_debut_iso": "2021-09-01",
"date_debut": "01/09/2021",
"date_fin_iso": "2022-08-31",
"date_fin": "31/08/2022",
"resp_can_edit": false,
"dept_id": 1,
"elt_annee_apo": null,
"elt_sem_apo": null,
"ens_can_edit_eval": false,
"etat": true,
"resp_can_change_ens": true,
"formation_id": 1,
"formsemestre_id": 1,
"gestion_compensation": false,
"gestion_semestrielle": false,
"id": 1,
"modalite": "FI",
"ens_can_edit_eval": false,
"formation_id": 1,
"gestion_compensation": false,
"elt_sem_apo": null,
"semestre_id": 1,
"bul_hide_xml": false,
"elt_annee_apo": null,
"titre": "Semestre test",
"block_moyennes": false,
"resp_can_change_ens": true,
"resp_can_edit": false,
"responsables": [1, 99], // uids
"scodoc7_id": null,
"date_debut": "01/09/2021",
"gestion_semestrielle": false,
"bul_bgcolor": "white",
"formsemestre_id": 1,
"titre_num": "Semestre test semestre 1",
"date_debut_iso": "2021-09-01",
"date_fin_iso": "2022-08-31",
"responsables": []
"semestre_id": 1,
"titre_formation" : "BUT GEA",
"titre_num": "BUT GEA semestre 1",
"titre": "BUT GEA",
}
"""
# Récupération de tous les formsemestres
formsemetre = models.FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
# Mise en forme des données
data = formsemetre.to_dict()
formsemestre: FormSemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
data = formsemestre.to_dict()
# Pour le moment on a besoin de fixer le departement
# pour accéder aux préferences
dept = Departement.query.get(formsemestre.dept_id)
app.set_sco_dept(dept.acronym)
data["session_id"] = formsemestre.session_id()
return jsonify(data)
@bp.route("/formsemestre/apo/<string:etape_apo>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def formsemestre_apo(etape_apo: str):
"""
Retourne les informations sur les formsemestres
Retourne les informations sur les formsemestres ayant cette étape Apogée
etape_apo : l'id d'une étape apogée
etape_apo : un code étape apogée
Exemple de résultat :
[
{ ...formsemestre...
}, ...
]
"""
formsemestres = FormSemestre.query.filter(
FormSemestreEtape.etape_apo == etape_apo,
@ -77,166 +86,8 @@ def formsemestre_apo(etape_apo: str):
return jsonify([formsemestre.to_dict() for formsemestre in formsemestres])
@bp.route(
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/etudid/<int:etudid>/bulletin",
methods=["GET"],
)
@bp.route(
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/nip/<int:nip>/bulletin",
methods=["GET"],
)
@bp.route(
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/ine/<int:ine>/bulletin",
methods=["GET"],
)
@token_permission_required(Permission.APIView)
def etudiant_bulletin(
formsemestre_id,
dept,
etudid: int = None,
nip: int = None,
ine: int = None,
):
"""
Retourne le bulletin de note d'un étudiant
formsemestre_id : l'id d'un formsemestre
etudid : l'etudid d'un étudiant
nip : le code nip d'un étudiant
ine : le code ine d'un étudiant
Exemple de résultat :
{
"etudid":1,
"formsemestre_id":1,
"date":"2022-04-27T10:44:47.448094",
"publie":true,
"etapes":[
],
"etudiant":{
"etudid":1,
"code_nip":"1",
"code_ine":"1",
"nom":"COSTA",
"prenom":"Sacha",
"civilite":"",
"photo_url":"/ScoDoc/TAPI/Scolarite/get_photo_image?etudid=1&amp;size=small",
"email":"SACHA.COSTA@example.com",
"emailperso":"",
"sexe":""
},
"note":{
"value":"10.60",
"min":"-",
"max":"-",
"moy":"-"
},
"rang":{
"value":"10",
"ninscrits":16
},
"rang_group":[
{
"group_type":"TD",
"group_name":"",
"value":"",
"ninscrits":""
}
],
"note_max":{
"value":20
},
"bonus_sport_culture":{
"value":0.0
},
"ue":[
{
"id":1,
"numero":"1",
"acronyme":"RT1.1",
"titre":"Administrer les r\u00e9seaux et l\u2019Internet",
"note":{
"value":"08.50",
"min":"06.00",
"max":"16.50",
"moy":"11.31"
},
"rang":"12",
"effectif":16,
"ects":"12",
"code_apogee":"",
"module":[
{
"id":1,
"code":"R101",
"coefficient":1.0,
"numero":10,
"titre":"Initiation aux r\u00e9seaux informatiques",
"abbrev":"Init aux r\u00e9seaux informatiques",
"note":{
"value":"12.00",
"moy":"-",
"max":"-",
"min":"-",
"nb_notes":"-",
"nb_missing":"-",
"nb_valid_evals":"-"
},
"code_apogee":"",
"evaluation":[
{
"jour":"2022-04-20",
"heure_debut":"08:00:00",
"heure_fin":"09:00:00",
"coefficient":1.0,
"evaluation_type":0,
"evaluation_id":1,
"description":"eval1",
"note":"12.00"
}
]
},
...
]
}
],
"ue_capitalisee":[],
"absences":{
"nbabs":2,
"nbabsjust":1
},
"appreciation":[]
}
"""
# Fonction utilisée : app.scodoc.sco_bulletins_json.make_json_formsemestre_bulletinetud()
try:
app.set_sco_dept(dept)
except:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel.\n "
"Veilliez vérifier que le nom de département est valide",
)
if etudid is None:
# Récupération de l'étudiant
try:
etu = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
etudid = etu.etudid
except AttributeError:
return error_response(
409,
message="La requête ne peut être traitée en létat actuel.\n "
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
)
data = make_json_formsemestre_bulletinetud(formsemestre_id, etudid)
return data
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def bulletins(formsemestre_id: int):
"""
@ -416,57 +267,148 @@ def bulletins(formsemestre_id: int):
...
]
"""
# Fonction utilisée : app.scodoc.sco_bulletins.get_formsemestre_bulletin_etud_json()
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
app.set_sco_dept(dept.acronym)
etuds = formsemestre.etuds
data = []
for etu in etuds:
for etu in formsemestre.etuds:
bul_etu = get_formsemestre_bulletin_etud_json(formsemestre, etu)
data.append(bul_etu.json)
return jsonify(data)
@bp.route("/formsemestre/<int:formsemestre_id>/jury", methods=["GET"])
# XXX Attendre ScoDoc 9.3
# @bp.route("/formsemestre/<int:formsemestre_id>/jury", methods=["GET"])
# @token_auth.login_required
# @token_permission_required(Permission.APIView)
# def jury(formsemestre_id: int):
# """
# Retourne le récapitulatif des décisions jury
# formsemestre_id : l'id d'un formsemestre
# Exemple de résultat :
# """
# # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury()
# formsemestre = models.FormSemestre.query.filter_by(
# id=formsemestre_id
# ).first_or_404()
# dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
# app.set_sco_dept(dept.acronym)
# data = formsemestre_pvjury(formsemestre_id)
# # try:
# # # Utilisation de la fonction formsemestre_pvjury
# # data = formsemestre_pvjury(formsemestre_id)
# # except AttributeError:
# # return error_response(
# # 409,
# # message="La requête ne peut être traitée en létat actuel. \n"
# # "Veillez vérifier la conformité du 'formation_id'",
# # )
# return jsonify(data)
@bp.route(
"/formsemestre/<int:formsemestre_id>/programme",
methods=["GET"],
)
@token_auth.login_required
@token_permission_required(Permission.APIView)
def jury(formsemestre_id: int):
def formsemestre_programme(formsemestre_id: int):
"""
Retourne le récapitulatif des décisions jury
Retourne la liste des Ues, ressources et SAE d'un semestre
formsemestre_id : l'id d'un formsemestre
Exemple de résultat :
{
"ues": [
{
"type": 0,
"formation_id": 1,
"ue_code": "UCOD11",
"id": 1,
"ects": 12.0,
"acronyme": "RT1.1",
"is_external": false,
"numero": 1,
"code_apogee": "",
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
"coefficient": 0.0,
"semestre_idx": 1,
"color": "#B80004",
"ue_id": 1
},
...
],
"ressources": [
{
"ens": [ 10, 18 ],
"formsemestre_id": 1,
"id": 15,
"module": {
"abbrev": "Programmer",
"code": "SAE15",
"code_apogee": "V7GOP",
"coefficient": 1.0,
"formation_id": 1,
"heures_cours": 0.0,
"heures_td": 0.0,
"heures_tp": 0.0,
"id": 15,
"matiere_id": 3,
"module_id": 15,
"module_type": 3,
"numero": 50,
"semestre_id": 1,
"titre": "Programmer en Python",
"ue_id": 3
},
"module_id": 15,
"moduleimpl_id": 15,
"responsable_id": 2
},
...
],
"saes": [
{
...
},
...
],
"modules" : [ ... les modules qui ne sont ni des SAEs ni des ressources ... ]
}
"""
# Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury()
formsemestre = models.FormSemestre.query.filter_by(
formsemestre: FormSemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
ues = formsemestre.query_ues()
m_list = {
ModuleType.RESSOURCE: [],
ModuleType.SAE: [],
ModuleType.STANDARD: [],
}
for modimpl in formsemestre.modimpls_sorted:
d = modimpl.to_dict()
m_list[modimpl.module.module_type].append(d)
app.set_sco_dept(dept.acronym)
data = formsemestre_pvjury(formsemestre_id)
# try:
# # Utilisation de la fonction formsemestre_pvjury
# data = formsemestre_pvjury(formsemestre_id)
# except AttributeError:
# return error_response(
# 409,
# message="La requête ne peut être traitée en létat actuel. \n"
# "Veillez vérifier la conformité du 'formation_id'",
# )
return jsonify(data)
return jsonify(
{
"ues": [ue.to_dict() for ue in ues],
"ressources": m_list[ModuleType.RESSOURCE],
"saes": m_list[ModuleType.SAE],
"modules": m_list[ModuleType.STANDARD],
}
)

View File

@ -1,41 +1,41 @@
#################################################### Jury #############################################################
from flask import jsonify
# from flask import jsonify
from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.scodoc.sco_prepajury import feuille_preparation_jury
from app.scodoc.sco_pvjury import formsemestre_pvjury
# from app import models
# from app.api import bp
# from app.api.errors import error_response
# from app.api.auth import token_auth, token_permission_required
# from app.scodoc.sco_prepajury import feuille_preparation_jury
# from app.scodoc.sco_pvjury import formsemestre_pvjury
@bp.route("/jury/formsemestre/<int:formsemestre_id>/preparation_jury", methods=["GET"])
# @token_permission_required(Permission.?)
def jury_preparation(formsemestre_id: int):
"""
Retourne la feuille de préparation du jury
# # @bp.route("/jury/formsemestre/<int:formsemestre_id>/preparation_jury", methods=["GET"])
# # @token_permission_required(Permission.?)
# def jury_preparation(formsemestre_id: int):
# """
# Retourne la feuille de préparation du jury
formsemestre_id : l'id d'un formsemestre
"""
# Fonction utilisée : app.scodoc.sco_prepajury.feuille_preparation_jury()
# formsemestre_id : l'id d'un formsemestre
# """
# # Fonction utilisée : app.scodoc.sco_prepajury.feuille_preparation_jury()
# Utilisation de la fonction feuille_preparation_jury
prepa_jury = feuille_preparation_jury(formsemestre_id)
# # Utilisation de la fonction feuille_preparation_jury
# prepa_jury = feuille_preparation_jury(formsemestre_id)
return error_response(501, message="Not implemented")
# return error_response(501, message="Not implemented")
@bp.route("/jury/formsemestre/<int:formsemestre_id>/decisions_jury", methods=["GET"])
# @token_permission_required(Permission.?)
def jury_decisions(formsemestre_id: int):
"""
Retourne les décisions du jury suivant un formsemestre donné
# # @bp.route("/jury/formsemestre/<int:formsemestre_id>/decisions_jury", methods=["GET"])
# # @token_permission_required(Permission.?)
# def jury_decisions(formsemestre_id: int):
# """
# Retourne les décisions du jury suivant un formsemestre donné
formsemestre_id : l'id d'un formsemestre
"""
# Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury()
# formsemestre_id : l'id d'un formsemestre
# """
# # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury()
# Utilisation de la fonction formsemestre_pvjury
decision_jury = formsemestre_pvjury(formsemestre_id)
# # Utilisation de la fonction formsemestre_pvjury
# decision_jury = formsemestre_pvjury(formsemestre_id)
return error_response(501, message="Not implemented")
# return error_response(501, message="Not implemented")

View File

@ -36,7 +36,6 @@ from app.api import bp
from app.api import requested_format
from app.api.auth import token_auth
from app.api.errors import error_response
from app.decorators import permission_required
from app.models import Departement
from app.scodoc.sco_logos import list_logos, find_logo
from app.api.auth import token_auth, token_permission_required
@ -44,6 +43,7 @@ from app.scodoc.sco_permissions import Permission
@bp.route("/logos", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def api_get_glob_logos():
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
@ -56,6 +56,7 @@ def api_get_glob_logos():
@bp.route("/logos/<string:logoname>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def api_get_glob_logo(logoname):
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
@ -72,6 +73,7 @@ def api_get_glob_logo(logoname):
@bp.route("/departements/<string:departement>/logos", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def api_get_local_logos(departement):
dept_id = Departement.from_acronym(departement).id
@ -82,6 +84,7 @@ def api_get_local_logos(departement):
@bp.route("/departements/<string:departement>/logos/<string:logoname>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def api_get_local_logo(departement, logoname):
# format = requested_format("jpg", ['png', 'jpg']) XXX ?

View File

@ -5,12 +5,13 @@ from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_permission_required
from app.api.auth import token_auth, token_permission_required
from app.scodoc.sco_groups import get_group_members, setGroups, get_partitions_list
from app.scodoc.sco_permissions import Permission
@bp.route("/partitions/<int:formsemestre_id>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def partition(formsemestre_id: int):
"""
@ -53,6 +54,7 @@ def partition(formsemestre_id: int):
@bp.route("/partitions/groups/<int:group_id>", methods=["GET"])
@bp.route("/partitions/groups/<int:group_id>/etat/<string:etat>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def etud_in_group(group_id: int, etat=None):
"""
@ -121,13 +123,14 @@ def etud_in_group(group_id: int, etat=None):
@bp.route(
"/partitions/set_groups?partition_id=<int:partition_id>&groups_lists=<int:groups_lists>&"
"groups_to_create=<int:groups_to_create>&groups_to_delete=<int:groups_to_delete>",
"/partitions/set_groups/partition/<int:partition_id>/groups/<string:groups_id>/delete/<string:groups_to_delete>"
"/create/<string:groups_to_create>",
methods=["POST"],
)
@token_auth.login_required
@token_permission_required(Permission.APIEtudChangeGroups)
def set_groups(
partition_id: int, groups_lists: int, groups_to_delete: int, groups_to_create: int
partition_id: int, groups_lists: str, groups_to_delete: str, groups_to_create: str
):
"""
Set les groups

View File

@ -1,253 +0,0 @@
# @bp.route("/etudiants", methods=["GET"])
# @token_permission_required(Permission.APIView)
# def etudiants():
# """
# Retourne la liste de tous les étudiants
#
# Exemple de résultat :
# {
# "civilite": "X",
# "code_ine": null,
# "code_nip": null,
# "date_naissance": null,
# "email": null,
# "emailperso": null,
# "etudid": 18,
# "nom": "MOREL",
# "prenom": "JACQUES"
# },
# {
# "civilite": "X",
# "code_ine": null,
# "code_nip": null,
# "date_naissance": null,
# "email": null,
# "emailperso": null,
# "etudid": 19,
# "nom": "FOURNIER",
# "prenom": "ANNE"
# },
# ...
# """
# # Récupération de tous les étudiants
# etu = models.Identite.query.all()
#
# # Mise en forme des données
# data = [d.to_dict_bul(include_urls=False) for d in etu]
#
# return jsonify(data)
# @bp.route(
# "/evaluations/eval_set_notes?eval_id=<int:eval_id>&etudid=<int:etudid>&note=<float:note>",
# methods=["POST"],
# )
# @bp.route(
# "/evaluations/eval_set_notes?eval_id=<int:eval_id>&nip=<int:nip>&note=<float:note>",
# methods=["POST"],
# )
# @bp.route(
# "/evaluations/eval_set_notes?eval_id=<int:eval_id>&ine=<int:ine>&note=<float:note>",
# methods=["POST"],
# )
# @token_permission_required(Permission.APIEditAllNotes)
# def evaluation_set_notes(
# eval_id: int, note: float, etudid: int = None, nip: int = None, ine: int = None
# ):
# """
# Set les notes d'une évaluation pour un étudiant donnée
#
# eval_id : l'id d'une évaluation
# note : la note à attribuer
# etudid : l'etudid d'un étudiant
# nip : le code nip d'un étudiant
# ine : le code ine d'un étudiant
# """
# # Fonction utilisée : app.scodoc.sco_saisie_notes.notes_add()
#
# # Qu'est ce qu'un user ???
# # notes_add()
# return error_response(501, message="Not implemented")
# ### Inutil en définitif ###
# @bp.route(
# "/absences/abs_signale?etudid=<int:etudid>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
# "&description=<string:description>",
# methods=["POST"],
# )
# @bp.route(
# "/absences/abs_signale?nip=<int:nip>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
# "&description=<string:description>",
# methods=["POST"],
# )
# @bp.route(
# "/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
# "&description=<string:description>",
# methods=["POST"],
# )
# @bp.route(
# "/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
# "&description=<string:description>&moduleimpl_id=<int:moduleimpl_id>",
# methods=["POST"],
# )
# @token_permission_required(Permission.APIAbsChange)
# def abs_signale(
# date: datetime,
# matin: bool,
# justif: bool,
# etudid: int = None,
# nip: int = None,
# ine: int = None, ### Inutil en définitif
# description: str = None,
# moduleimpl_id: int = None,
# ):
# """
# Permet d'ajouter une absence en base
#
# date : la date de l'absence
# matin : True ou False
# justif : True ou False
# etudid : l'etudid d'un étudiant
# nip: le code nip d'un étudiant
# ine : le code ine d'un étudiant
# description : description possible à ajouter sur l'absence
# moduleimpl_id : l'id d'un moduleimpl
# """
# # Fonctions utilisées : app.scodoc.sco_abs.add_absence() et app.scodoc.sco_abs.add_justif()
#
# if etudid is None:
# # Récupération de l'étudiant
# try:
# etu = get_etu_from_request(etudid, nip, ine)
# etudid = etu.etudid
# except AttributeError:
# return error_response(
# 409,
# message="La requête ne peut être traitée en létat actuel.\n "
# "Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
# )
# try:
# # Utilisation de la fonction add_absence
# add_absence(etudid, date, matin, justif, description, moduleimpl_id)
# if justif == True:
# # Utilisation de la fonction add_justif
# add_justif(etudid, date, matin, description)
# except ValueError:
# return error_response(
# 409, message="La requête ne peut être traitée en létat actuel"
# )
# @bp.route(
# "/absences/abs_annule_justif?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>",
# methods=["POST"],
# )
# @bp.route(
# "/absences/abs_annule_justif?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>",
# methods=["POST"],
# )
# @bp.route(
# "/absences/abs_annule_justif?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>",
# methods=["POST"],
# )
# @token_permission_required(Permission.APIAbsChange)
# def abs_annule_justif(
# jour: datetime, matin: str, etudid: int = None, nip: int = None, ine: int = None
# ):
# """
# Retourne un html
# jour : la date de l'absence a annulé
# matin : True ou False
# etudid : l'etudid d'un étudiant
# nip: le code nip d'un étudiant
# ine : le code ine d'un étudiant
# """
# # Fonction utilisée : app.scodoc.sco_abs.annule_justif()
# if etudid is None:
# # Récupération de l'étudiant
# try:
# etu = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
# etudid = etu.etudid
# except AttributeError:
# return error_response(
# 409,
# message="La requête ne peut être traitée en létat actuel.\n "
# "Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
# )
# try:
# # Utilisation de la fonction annule_justif
# annule_justif(etudid, jour, matin)
# except ValueError:
# return error_response(
# 409,
# message="La requête ne peut être traitée en létat actuel.\n "
# "Veilliez vérifier que le 'jour' et le 'matin' sont valides",
# )
# return error_response(200, message="OK")
# @bp.route(
# "/jury/set_decision/etudid?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
# "&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
# methods=["POST"],
# )
# @bp.route(
# "/jury/set_decision/nip?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
# "&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
# methods=["POST"],
# )
# @bp.route(
# "/jury/set_decision/ine?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
# "&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
# methods=["POST"],
# )
# # @token_permission_required(Permission.)
# def set_decision_jury(
# formsemestre_id: int,
# decision_jury: str,
# devenir_jury: str,
# assiduite: bool,
# etudid: int = None,
# nip: int = None,
# ine: int = None,
# ):
# """
# Attribuer la décision du jury et le devenir à un etudiant
#
# formsemestre_id : l'id d'un formsemestre
# decision_jury : la décision du jury
# devenir_jury : le devenir du jury
# assiduite : True ou False
# etudid : l'etudid d'un étudiant
# nip: le code nip d'un étudiant
# ine : le code ine d'un étudiant
# """
# return error_response(501, message="Not implemented")
#
#
# @bp.route(
# "/jury/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/annule_decision",
# methods=["DELETE"],
# )
# @bp.route(
# "/jury/nip/<int:nip>/formsemestre/<int:formsemestre_id>/annule_decision",
# methods=["DELETE"],
# )
# @bp.route(
# "/jury/ine/<int:ine>/formsemestre/<int:formsemestre_id>/annule_decision",
# methods=["DELETE"],
# )
# # @token_permission_required(Permission.)
# def annule_decision_jury(
# formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None
# ):
# """
# Supprime la déciosion du jury pour un étudiant donné
#
# formsemestre_id : l'id d'un formsemestre
# etudid : l'etudid d'un étudiant
# nip: le code nip d'un étudiant
# ine : le code ine d'un étudiant
# """
# return error_response(501, message="Not implemented")

View File

@ -1,152 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""API ScoDoc 9
"""
# PAS ENCORE IMPLEMENTEE, juste un essai
# Pour P. Bouron, il faudrait en priorité l'équivalent de
# Scolarite/Notes/moduleimpl_withmodule_list (alias scodoc7 do_moduleimpl_withmodule_list)
# Scolarite/Notes/evaluation_create
# Scolarite/Notes/evaluation_delete
# Scolarite/Notes/formation_list
# Scolarite/Notes/formsemestre_list
# Scolarite/Notes/formsemestre_partition_list
# Scolarite/Notes/groups_view
# Scolarite/Notes/moduleimpl_status
# Scolarite/setGroups
from datetime import datetime
from flask import jsonify, request, g, send_file
from sqlalchemy.sql import func
from app import db, log
from app.api import bp, requested_format
from app.api.auth import token_auth
from app.api.errors import error_response
from app import models
from app.models import FormSemestre, FormSemestreInscription, Identite
from app.models import ApcReferentielCompetences
from app.scodoc.sco_abs import (
annule_absence,
annule_justif,
add_absence,
add_justif,
list_abs_date,
)
from app.scodoc.sco_bulletins import formsemestre_bulletinetud_dict
from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud
from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes
from app.scodoc.sco_formations import formation_export
from app.scodoc.sco_formsemestre_inscriptions import (
do_formsemestre_inscription_listinscrits,
)
from app.scodoc.sco_groups import setGroups, get_etud_groups, get_group_members
from app.scodoc.sco_logos import list_logos, find_logo, _list_dept_logos
from app.scodoc.sco_moduleimpl import moduleimpl_list
from app.scodoc.sco_permissions import Permission
# ###################################################### Logos ##########################################################
#
# # XXX TODO voir get_logo déjà existant dans app/views/scodoc.py
#
# @bp.route("/logos", methods=["GET"])
# def liste_logos(format="json"):
# """
# Liste des logos définis pour le site scodoc.
# """
# # fonction to use : list_logos()
# # try:
# # res = list_logos()
# # except ValueError:
# # return error_response(409, message="La requête ne peut être traitée en létat actuel")
# #
# # if res is None:
# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
# #
# # return res
#
#
#
# @bp.route("/logos/<string:logo_name>", methods=["GET"])
# def recup_logo_global(logo_name: str):
# """
# Retourne l'image au format png ou jpg
#
# logo_name : le nom du logo rechercher
# """
# # fonction to use find_logo
# # try:
# # res = find_logo(logo_name)
# # except ValueError:
# # return error_response(409, message="La requête ne peut être traitée en létat actuel")
# #
# # if res is None:
# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
# #
# # return res
#
#
# @bp.route("/departements/<string:dept>/logos", methods=["GET"])
# def logo_dept(dept: str):
# """
# Liste des logos définis pour le département visé.
#
# dept : l'id d'un département
# """
# # fonction to use: _list_dept_logos
# # dept_id = models.Departement.query.filter_by(acronym=dept).first()
# # try:
# # res = _list_dept_logos(dept_id.id)
# # except ValueError:
# # return error_response(409, message="La requête ne peut être traitée en létat actuel")
# #
# # if res is None:
# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
# #
# # return res
#
#
# @bp.route("/departement/<string:dept>/logos/<string:logo_name>", methods=["GET"])
# def recup_logo_dept_global(dept: str, logo_name: str):
# """
# L'image format png ou jpg
#
# dept : l'id d'un département
# logo_name : le nom du logo rechercher
# """
# # fonction to use find_logo
# # dept_id = models.Departement.query.filter_by(acronym=dept).first()
# # try:
# # res = find_logo(logo_name, dept_id.id)
# # except ValueError:
# # return error_response(409, message="La requête ne peut être traitée en létat actuel")
# #
# # if res is None:
# # return error_response(200, message="Aucun logo trouvé correspondant aux informations renseignés")
# #
# # return res

View File

@ -1,444 +0,0 @@
################################################## Tests ##############################################################
# XXX OBSOLETE ??? XXX
import requests
import os
from app import models
from app.api import bp, requested_format
from app.api.auth import token_auth
from app.api.errors import error_response
SCODOC_USER = "test"
SCODOC_PASSWORD = "test"
SCODOC_URL = "http://192.168.1.12:5000"
CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
HEADERS = None
def get_token():
"""
Permet de set le token dans le header
"""
global HEADERS
global SCODOC_USER
global SCODOC_PASSWORD
r0 = requests.post(
SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD)
)
token = r0.json()["token"]
HEADERS = {"Authorization": f"Bearer {token}"}
DEPT = None
FORMSEMESTRE = None
ETU = None
@bp.route("/test_dept", methods=["GET"])
def get_departement():
"""
Permet de tester departements() mais également de set un département dans DEPT pour la suite des tests
"""
get_token()
global HEADERS
global CHECK_CERTIFICATE
global SCODOC_USER
global SCODOC_PASSWORD
# print(HEADERS)
# departements
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
if r.status_code == 200:
dept_id = r.json()[0]
# print(dept_id)
dept = models.Departement.query.filter_by(id=dept_id).first()
dept = dept.to_dict()
fields = ["id", "acronym", "description", "visible", "date_creation"]
for field in dept:
if field not in fields:
return error_response(501, field + " field missing")
global DEPT
DEPT = dept
return error_response(200, "OK")
return error_response(409, "La requête ne peut être traitée en létat actuel")
@bp.route("/test_formsemestre", methods=["GET"])
def get_formsemestre():
"""
Permet de tester liste_semestres_courant() mais également de set un formsemestre dans FORMSEMESTRE
pour la suite des tests
"""
get_departement()
global DEPT
dept_acronym = DEPT["acronym"]
# liste_semestres_courant
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/" + dept_acronym + "/semestres_courants",
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
if r.status_code == 200:
formsemestre = r.json()[0]
print(r.json()[0])
fields = [
"gestion_semestrielle",
"titre",
"scodoc7_id",
"date_debut",
"bul_bgcolor",
"date_fin",
"resp_can_edit",
"dept_id",
"etat",
"resp_can_change_ens",
"id",
"modalite",
"ens_can_edit_eval",
"formation_id",
"gestion_compensation",
"elt_sem_apo",
"semestre_id",
"bul_hide_xml",
"elt_annee_apo",
"block_moyennes",
"formsemestre_id",
"titre_num",
"date_debut_iso",
"date_fin_iso",
"responsables",
]
for field in formsemestre:
if field not in fields:
return error_response(501, field + " field missing")
global FORMSEMESTRE
FORMSEMESTRE = formsemestre
return error_response(200, "OK")
return error_response(409, "La requête ne peut être traitée en létat actuel")
@bp.route("/test_etu", methods=["GET"])
def get_etudiant():
"""
Permet de tester etudiants() mais également de set un etudiant dans ETU pour la suite des tests
"""
# etudiants
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiants/courant",
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
if r.status_code == 200:
etu = r.json()[0]
fields = [
"civilite",
"code_ine",
"code_nip",
"date_naissance",
"email",
"emailperso",
"etudid",
"nom",
"prenom",
]
for field in etu:
if field not in fields:
return error_response(501, field + " field missing")
global ETU
ETU = etu
print(etu)
return error_response(200, "OK")
return error_response(409, "La requête ne peut être traitée en létat actuel")
############################################### Departements ##########################################################
@bp.route("/test_liste_etudiants")
def test_departements_liste_etudiants():
"""
Test la route liste_etudiants
"""
# Set un département et un formsemestre pour les tests
get_departement()
get_formsemestre()
global DEPT
global FORMSEMESTRE
# Set les fields à vérifier
fields = [
"civilite",
"code_ine",
"code_nip",
"date_naissance",
"email",
"emailperso",
"etudid",
"nom",
"prenom",
]
# liste_etudiants (sans formsemestre)
r1 = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/" + DEPT["acronym"] + "/etudiants/liste",
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
if r1.status_code == 200: # Si la requête est "OK"
# On récupère la liste des étudiants
etudiants = r1.json()
# Vérification que tous les étudiants ont bien tous les bons champs
for etu in etudiants:
for field in etu:
if field not in fields:
return error_response(501, field + " field missing")
# liste_etudiants (avec formsemestre)
r2 = requests.get(
SCODOC_URL
+ "/ScoDoc/api/departements/"
+ DEPT["acronym"]
+ "/etudiants/liste/"
+ str(FORMSEMESTRE["formsemestre_id"]),
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
if r2.status_code == 200: # Si la requête est "OK"
# On récupère la liste des étudiants
etudiants = r2.json()
# Vérification que tous les étudiants ont bien tous les bons champs
for etu in etudiants:
for field in etu:
if field not in fields:
return error_response(501, field + " field missing")
return error_response(200, "OK")
return error_response(409, "La requête ne peut être traitée en létat actuel")
@bp.route("/test_referenciel_competences")
def test_departements_referenciel_competences():
"""
Test la route referenciel_competences
"""
get_departement()
get_formsemestre()
global DEPT
global FORMSEMESTRE
# referenciel_competences
r = requests.post(
SCODOC_URL
+ "/ScoDoc/api/departements/"
+ DEPT["acronym"]
+ "/formations/"
+ FORMSEMESTRE["formation_id"]
+ "/referentiel_competences",
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
@bp.route("/test_liste_semestre_index")
def test_departements_semestre_index():
"""
Test la route semestre_index
"""
# semestre_index
r5 = requests.post(
SCODOC_URL
+ "/ScoDoc/api/departements/"
+ DEPT["acronym"]
+ "/formsemestre/"
+ FORMSEMESTRE["formation_id"]
+ "/programme",
auth=(SCODOC_USER, SCODOC_PASSWORD),
)
#################################################### Etudiants ########################################################
def test_routes_etudiants():
"""
Test les routes de la partie Etudiants
"""
# etudiants
r1 = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiants", auth=(SCODOC_USER, SCODOC_PASSWORD)
)
# etudiants_courant
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etudiant
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etudiant_formsemestres
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etudiant_bulletin_semestre
r5 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etudiant_groups
r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_formation():
"""
Test les routes de la partie Formation
"""
# formations
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# formations_by_id
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# formation_export_by_formation_id
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# formsemestre_apo
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# moduleimpls
r5 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# moduleimpls_sem
r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_formsemestres():
"""
Test les routes de la partie Formsemestres
"""
# formsemestre
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etudiant_bulletin
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# bulletins
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# jury
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_partitions():
"""
Test les routes de la partie Partitions
"""
# partition
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# etud_in_group
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# set_groups
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_evaluations():
"""
Test les routes de la partie Evaluations
"""
# evaluations
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# evaluation_notes
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# evaluation_set_notes
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_jury():
"""
Test les routes de la partie Jury
"""
# jury_preparation
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# jury_decisions
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# set_decision_jury
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# annule_decision_jury
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_absences():
"""
Test les routes de la partie Absences
"""
# absences
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# absences_justify
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# abs_signale
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# abs_annule
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# abs_annule_justif
r5 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# abs_groupe_etat
r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
def test_routes_logos():
"""
Test les routes de la partie Logos
"""
# liste_logos
r1 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# recup_logo_global
r2 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# logo_dept
r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))
# recup_logo_dept_global
r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD))

View File

@ -1,5 +1,5 @@
from flask import jsonify
from app import db
from app import db, log
from app.api import bp
from app.api.auth import basic_auth, token_auth
@ -7,7 +7,9 @@ from app.api.auth import basic_auth, token_auth
@bp.route("/tokens", methods=["POST"])
@basic_auth.login_required
def get_token():
"renvoie un jeton jwt pour l'utilisateur courant"
token = basic_auth.current_user().get_token()
log(f"API: giving token to {basic_auth.current_user()}")
db.session.commit()
return jsonify({"token": token})
@ -15,6 +17,7 @@ def get_token():
@bp.route("/tokens", methods=["DELETE"])
@token_auth.login_required
def revoke_token():
"révoque le jeton de l'utilisateur courant"
token_auth.current_user().revoke_token()
db.session.commit()
return "", 204

View File

@ -18,7 +18,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
import jwt
from app import db, login
from app import db, log, login
from app.models import Departement
from app.models import SHORT_STR_LEN
from app.scodoc.sco_exceptions import ScoValueError
@ -150,11 +150,22 @@ class User(UserMixin, db.Model):
def verify_reset_password_token(token):
"Vérification du token de reéinitialisation du mot de passe"
try:
user_id = jwt.decode(
token = jwt.decode(
token, current_app.config["SECRET_KEY"], algorithms=["HS256"]
)["reset_password"]
)
except jwt.exceptions.ExpiredSignatureError:
log(f"verify_reset_password_token: token expired")
except:
return
return None
try:
user_id = token["reset_password"]
# double check en principe inutile car déjà fait dans decode()
expire = float(token["exp"])
if time() > expire:
log(f"verify_reset_password_token: token expired for uid={user_id}")
return None
except (TypeError, KeyError):
return None
return User.query.get(user_id)
def to_dict(self, include_email=True):
@ -214,6 +225,7 @@ class User(UserMixin, db.Model):
self.add_role(role, dept)
def get_token(self, expires_in=3600):
"Un jeton pour cet user. Stocké en base, non commité."
now = datetime.utcnow()
if self.token and self.token_expiration > now + timedelta(seconds=60):
return self.token
@ -223,6 +235,7 @@ class User(UserMixin, db.Model):
return self.token
def revoke_token(self):
"Révoque le jeton de cet utilisateur"
self.token_expiration = datetime.utcnow() - timedelta(seconds=1)
@staticmethod

View File

@ -71,7 +71,7 @@ def create_user():
flash("User {} created".format(user.user_name))
return redirect(url_for("scodoc.index"))
return render_template(
"auth/register.html", title=u"Création utilisateur", form=form
"auth/register.html", title="Création utilisateur", form=form
)
@ -112,7 +112,7 @@ def reset_password(token):
if current_user.is_authenticated:
return redirect(url_for("scodoc.index"))
user = User.verify_reset_password_token(token)
if not user:
if user is None:
return redirect(url_for("scodoc.index"))
form = ResetPasswordForm()
if form.validate_on_submit():

View File

@ -127,6 +127,9 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
def ue_rows(self, rows: list, ue_acronym: str, ue: dict, title_bg: tuple):
"Décrit une UE dans la table synthèse: titre, sous-titre et liste modules"
if (ue["type"] == UE_SPORT) and len(ue.get("modules", [])) == 0:
# ne mentionne l'UE que s'il y a des modules
return
# 1er ligne titre UE
moy_ue = ue.get("moyenne")
t = {
@ -206,7 +209,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
for mod_code, mod in ue["modules"].items():
rows.append(
{
"titre": f"{mod_code} {mod['titre']}",
"titre": f"{mod_code or ''} {mod['titre'] or ''}",
}
)
self.evaluations_rows(rows, mod["evaluations"])
@ -313,7 +316,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
"lignes des évaluations"
for e in evaluations:
t = {
"titre": f"{e['description']}",
"titre": f"{e['description'] or ''}",
"moyenne": e["note"]["value"],
"_moyenne_pdf": Paragraph(
f"""<para align=right>{e["note"]["value"]}</para>"""

View File

@ -946,7 +946,7 @@ class BonusStBrieuc(BonusSportAdditif):
name = "bonus_iut_stbrieuc"
displayed_name = "IUT de Saint-Brieuc"
proportion_point = 1 / 20.0
classic_use_bonus_ues = True
classic_use_bonus_ues = False
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus"""

View File

@ -146,6 +146,7 @@ class Identite(db.Model):
return {
"id": self.id,
"nip": self.code_nip,
"ine": self.code_ine,
"nom": self.nom,
"nom_usuel": self.nom_usuel,
"prenom": self.prenom,

View File

@ -178,11 +178,12 @@ def evaluation_enrich_dict(e):
else:
e["descrheure"] = ""
# matin, apresmidi: utile pour se referer aux absences:
if heure_debut_dt < datetime.time(12, 00):
if e["jour"] and heure_debut_dt < datetime.time(12, 00):
e["matin"] = 1
else:
e["matin"] = 0
if heure_fin_dt > datetime.time(12, 00):
if e["jour"] and heure_fin_dt > datetime.time(12, 00):
e["apresmidi"] = 1
else:
e["apresmidi"] = 0

View File

@ -139,6 +139,7 @@ class FormSemestre(db.Model):
else:
d["date_fin"] = d["date_fin_iso"] = ""
d["responsables"] = [u.id for u in self.responsables]
d["titre_formation"] = self.titre_formation()
return d
def get_infos_dict(self) -> dict:
@ -329,9 +330,10 @@ class FormSemestre(db.Model):
ANNEE=annee universitaire de debut (exemple: un S2 de 2013-2014 sera S2-2013)
"""
imputation_dept = sco_preferences.get_preference("ImputationDept", self.id)
prefs = sco_preferences.SemPreferences(dept_id=self.dept_id)
imputation_dept = prefs["ImputationDept"]
if not imputation_dept:
imputation_dept = sco_preferences.get_preference("DeptName")
imputation_dept = prefs["DeptName"]
imputation_dept = imputation_dept.upper()
parcours_name = self.formation.get_parcours().NAME
modalite = self.modalite
@ -346,7 +348,7 @@ class FormSemestre(db.Model):
scu.annee_scolaire_debut(self.date_debut.year, self.date_debut.month)
)
return scu.sanitize_string(
"-".join((imputation_dept, parcours_name, modalite, semestre_id, annee_sco))
f"{imputation_dept}-{parcours_name}-{modalite}-{semestre_id}-{annee_sco}"
)
def titre_annee(self) -> str:
@ -358,6 +360,12 @@ class FormSemestre(db.Model):
titre_annee += "-" + str(self.date_fin.year)
return titre_annee
def titre_formation(self):
"""Titre avec formation, court, pour passerelle: "BUT R&T"
(méthode de formsemestre car on pourrait ajouter le semestre, ou d'autres infos, à voir)
"""
return self.formation.acronyme
def titre_mois(self) -> str:
"""Le titre et les dates du semestre, pour affichage dans des listes
Ex: "BUT QLIO (PN 2022) semestre 1 FI (Sept 2022 - Jan 2023)"

View File

@ -965,7 +965,7 @@ def _tables_abs_etud(
)[0]
if format == "html":
ex.append(
f"""<a href="{url_for('notes.moduleimpl_status',
f"""<a title="{mod['module']['titre']}" href="{url_for('notes.moduleimpl_status',
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
">{mod["module"]["code"] or "(module sans code)"}</a>"""
)

View File

@ -31,7 +31,6 @@
import datetime
import json
from app.but import bulletin_but
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models.formsemestre import FormSemestre
@ -92,7 +91,7 @@ def formsemestre_bulletinetud_published_dict(
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
d = {}
d = {"type": "classic", "version": "0"}
if (not sem["bul_hide_xml"]) or force_publishing:
published = True

View File

@ -228,7 +228,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
if getattr(g, "defer_cache_invalidation", False):
g.sem_to_invalidate.add(formsemestre_id)
return
log("inval_cache, formsemestre_id={formsemestre_id} pdfonly={pdfonly}")
log(f"inval_cache, formsemestre_id={formsemestre_id} pdfonly={pdfonly}")
if formsemestre_id is None:
# clear all caches
log("----- invalidate_formsemestre: clearing all caches -----")
@ -272,7 +272,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
class DefferedSemCacheManager:
class DeferredSemCacheManager:
"""Contexte pour effectuer des opérations indépendantes dans la
même requete qui invalident le cache. Par exemple, quand on inscrit
des étudiants un par un à un semestre, chaque inscription va invalider

View File

@ -131,8 +131,10 @@ def index_html(showcodes=0, showsemtable=0):
if not showsemtable:
H.append(
f"""<hr>
<p><a class="stdlink" href="{url_for('scolar.index_html', scodoc_dept=g.scodoc_dept, showsemtable=1)
}">Voir tous les semestres ({len(othersems)} verrouillés)</a>
<p><a class="stdlink" href="{url_for('scolar.index_html',
scodoc_dept=g.scodoc_dept, showsemtable=1)
}">Voir table des semestres (dont {len(othersems)}
verrouillé{'s' if len(othersems) else ''})</a>
</p>"""
)

View File

@ -70,7 +70,8 @@ class Permission(object):
setattr(Permission, symbol, perm)
Permission.description[symbol] = description
Permission.permission_by_name[symbol] = perm
Permission.NBITS = len(_SCO_PERMISSIONS)
max_perm = max(p[0] for p in _SCO_PERMISSIONS)
Permission.NBITS = max_perm.bit_length()
@staticmethod
def get_by_name(permission_name: str) -> int:

View File

@ -132,8 +132,11 @@ def clear_base_preferences():
g._SCO_BASE_PREFERENCES = {} # { dept_id: BasePreferences instance }
def get_base_preferences():
"""Return global preferences for the current department"""
def get_base_preferences(dept_id: int = None):
"""Return global preferences for the specified department
or the current departement
"""
if dept_id is None:
dept_id = g.scodoc_dept_id
if not hasattr(g, "_SCO_BASE_PREFERENCES"):
g._SCO_BASE_PREFERENCES = {}
@ -142,12 +145,12 @@ def get_base_preferences():
return g._SCO_BASE_PREFERENCES[dept_id]
def get_preference(name, formsemestre_id=None):
def get_preference(name, formsemestre_id=None, dept_id=None):
"""Returns value of named preference.
All preferences have a sensible default value, so this
function always returns a usable value for all defined preferences names.
"""
return get_base_preferences().get(formsemestre_id, name)
return get_base_preferences(dept_id=dept_id).get(formsemestre_id, name)
def _convert_pref_type(p, pref_spec):
@ -2145,9 +2148,9 @@ class BasePreferences(object):
class SemPreferences:
"""Preferences for a formsemestre"""
def __init__(self, formsemestre_id=None):
def __init__(self, formsemestre_id=None, dept_id=None):
self.formsemestre_id = formsemestre_id
self.base_prefs = get_base_preferences()
self.base_prefs = get_base_preferences(dept_id=dept_id)
def __getitem__(self, name):
return self.base_prefs.get(self.formsemestre_id, name)

View File

@ -799,22 +799,22 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
raise ScoValueError("invalid evaluation_id")
E = evals[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
eval_dict = evals[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=eval_dict["moduleimpl_id"])[0]
formsemestre_id = M["formsemestre_id"]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
mod_responsable = sco_users.user_info(M["responsable_id"])
if E["jour"]:
indication_date = ndb.DateDMYtoISO(E["jour"])
if eval_dict["jour"]:
indication_date = ndb.DateDMYtoISO(eval_dict["jour"])
else:
indication_date = scu.sanitize_filename(E["description"])[:12]
evalname = "%s-%s" % (Mod["code"], indication_date)
indication_date = scu.sanitize_filename(eval_dict["description"])[:12]
eval_name = "%s-%s" % (Mod["code"], indication_date)
if E["description"]:
evaltitre = "%s du %s" % (E["description"], E["jour"])
if eval_dict["description"]:
evaltitre = "%s du %s" % (eval_dict["description"], eval_dict["jour"])
else:
evaltitre = "évaluation du %s" % E["jour"]
evaltitre = "évaluation du %s" % eval_dict["jour"]
description = "%s en %s (%s) resp. %s" % (
evaltitre,
Mod["abbrev"] or "",
@ -847,7 +847,7 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
# une liste de liste de chaines: lignes de la feuille de calcul
L = []
etuds = _get_sorted_etuds(E, etudids, formsemestre_id)
etuds = _get_sorted_etuds(eval_dict, etudids, formsemestre_id)
for e in etuds:
etudid = e["etudid"]
groups = sco_groups.get_etud_groups(etudid, formsemestre_id)
@ -865,8 +865,10 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
]
)
filename = "notes_%s_%s" % (evalname, gr_title_filename)
xls = sco_excel.excel_feuille_saisie(E, sem["titreannee"], description, lines=L)
filename = "notes_%s_%s" % (eval_name, gr_title_filename)
xls = sco_excel.excel_feuille_saisie(
eval_dict, sem["titreannee"], description, lines=L
)
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
# return sco_excel.send_excel_file(xls, filename)
@ -1008,10 +1010,9 @@ def saisie_notes(evaluation_id, group_ids=[]):
return "\n".join(H)
def _get_sorted_etuds(E, etudids, formsemestre_id):
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(
E["evaluation_id"]
eval_dict["evaluation_id"]
) # Notes existantes
cnx = ndb.GetDBConnexion()
etuds = []
@ -1028,9 +1029,9 @@ def _get_sorted_etuds(E, etudids, formsemestre_id):
e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id)
# Information sur absence (tenant compte de la demi-journée)
jour_iso = ndb.DateDMYtoISO(E["jour"])
jour_iso = ndb.DateDMYtoISO(eval_dict["jour"])
warn_abs_lst = []
if E["matin"]:
if eval_dict["matin"]:
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=1)
nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=1)
if nbabs:
@ -1038,7 +1039,7 @@ def _get_sorted_etuds(E, etudids, formsemestre_id):
warn_abs_lst.append("absent justifié le matin !")
else:
warn_abs_lst.append("absent le matin !")
if E["apresmidi"]:
if eval_dict["apresmidi"]:
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=0)
nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=0)
if nbabs:

View File

@ -252,7 +252,7 @@ def formsemestre_synchro_etuds(
etudids_a_desinscrire = [nip2etudid(x) for x in a_desinscrire]
etudids_a_desinscrire += a_desinscrire_without_key
#
with sco_cache.DefferedSemCacheManager():
with sco_cache.DeferredSemCacheManager():
do_import_etuds_from_portal(sem, a_importer, etudsapo_ident)
sco_inscr_passage.do_inscrit(sem, etudids_a_inscrire)
sco_inscr_passage.do_desinscrit(sem, etudids_a_desinscrire)

View File

@ -45,7 +45,7 @@
{# Liste des permissions #}
<div class="permissions">
<p>Permissions de cet utilisateur dans le département {dept}:</p>
<p>Permissions de cet utilisateur dans le département {{dept}}:</p>
<ul>
{% for p in Permission.description %}
<li>{{Permission.description[p]}} :

View File

@ -871,7 +871,10 @@ def EtatAbsencesGr(
)
nbjustifs_noabs = len(
sco_abs.list_abs_justifs(
etudid=etud["etudid"], datedebut=datedebut, only_no_abs=True
etudid=etud["etudid"],
datedebut=datedebut,
datefin=datefin,
only_no_abs=True,
)
)
# retrouve sem dans etud['sems']
@ -894,7 +897,7 @@ def EtatAbsencesGr(
"nbjustifs_noabs": nbjustifs_noabs,
"_nomprenom_target": "CalAbs?etudid=%s" % etud["etudid"],
"_nomprenom_td_attrs": 'id="%s" class="etudinfo"' % etud["etudid"],
"boursier": etud["boursier"],
"boursier": "oui" if etud["boursier"] else "non",
}
)
if s["ins"]["etat"] == "D":
@ -922,9 +925,36 @@ def EtatAbsencesGr(
h = groups_infos.groups_titles
gr_tit = p + h
title = "État des absences %s" % gr_tit
title = f"État des absences {gr_tit}"
if format == "xls" or format == "xml" or format == "json":
columns_ids = ["etudid"] + columns_ids
# --- Formulaire choix dates début / fin
form_date = (
f"""
<form action="{groups_infos.base_url}" method="get">
<input type="hidden" name="group_ids" value="{group_ids}">
<span style="font-size: 120%"><b>Période du
<input type="text" name="debut" size="10" value="{debut}" class="datepicker"
onchange="validate_date(this);">
&nbsp;au&nbsp;
<input type="text" name="fin" size="10" value="{fin}" class="datepicker"
onchange="validate_date(this);">
</b></span>
&nbsp;
(nombre de <em>demi-journées</em>)
</form>"""
+ """
<script>
function validate_date(el) {
const regex = /^[0-3]?[0-9]\/[0-9]{1,2}\/[0-9]{1,4}$/;
if (regex.test(el.value)) {
return el.form.submit();
}
return false;
}
</script>
"""
)
tab = GenTable(
columns_ids=columns_ids,
rows=T,
@ -945,8 +975,9 @@ def EtatAbsencesGr(
init_qtip=True,
javascripts=["js/etud_info.js"],
),
html_title=html_sco_header.html_sem_header("%s" % title, with_page_header=False)
+ "<p>Période du %s au %s (nombre de <b>demi-journées</b>)<br/>" % (debut, fin),
html_title=html_sco_header.html_sem_header(title, with_page_header=False)
+ form_date,
# "<p>Période du %s au %s (nombre de <b>demi-journées</b>)<br/>" % (debut, fin),
base_url="%s&formsemestre_id=%s&debut=%s&fin=%s"
% (groups_infos.base_url, formsemestre_id, debut, fin),
filename="etat_abs_"

View File

@ -314,7 +314,7 @@ def formsemestre_bulletinetud(
)
if format == "json":
return sco_bulletins.get_formsemestre_bulletin_etud_json(
formsemestre, etud, version=version
formsemestre, etud, version=version, force_publishing=force_publishing
)
if formsemestre.formation.is_apc() and format == "html":
return render_template(

View File

@ -13,7 +13,7 @@ class Config:
SQLALCHEMY_DATABASE_URI = None # set in subclass
FLASK_ENV = None # # set in subclass
SECRET_KEY = os.environ.get("SECRET_KEY") or "90e01e75831e4176a3c70d29564b425f"
SECRET_KEY = os.environ.get("SECRET_KEY") or "90e01e75831e4276a4c70d29564b425f"
SQLALCHEMY_TRACK_MODIFICATIONS = False
LOG_TO_STDOUT = os.environ.get("LOG_TO_STDOUT")
MAIL_SERVER = os.environ.get("MAIL_SERVER", "localhost")
@ -46,6 +46,7 @@ class Config:
class ProdConfig(Config):
"mode production, normalement derrière nginx/gunicorn"
FLASK_ENV = "production"
DEBUG = False
TESTING = False
@ -56,6 +57,7 @@ class ProdConfig(Config):
class DevConfig(Config):
"mode développement"
FLASK_ENV = "development"
DEBUG = True
TESTING = False
@ -66,6 +68,7 @@ class DevConfig(Config):
class TestConfig(DevConfig):
"Pour les tests unitaires"
TESTING = True
DEBUG = True
SQLALCHEMY_DATABASE_URI = (
@ -76,6 +79,19 @@ class TestConfig(DevConfig):
SECRET_KEY = os.environ.get("TEST_SECRET_KEY") or "c7ecff5db1594c208f573ff30e0f6bca"
class TestAPIConfig(Config):
"Pour les tests de l'API"
FLASK_ENV = "test_api"
TESTING = False
DEBUG = True
SQLALCHEMY_DATABASE_URI = (
os.environ.get("SCODOC_TEST_API_DATABASE_URI")
or "postgresql:///SCODOC_TEST_API"
)
DEPT_TEST = "TAPI_" # nom du département, ne pas l'utiliser pour un "vrai"
SECRET_KEY = os.environ.get("TEST_SECRET_KEY") or "c7ecff5db15946789Hhahbh88aja175"
mode = os.environ.get("FLASK_ENV", "production")
if mode == "production":
RunningConfig = ProdConfig
@ -83,3 +99,5 @@ elif mode == "development":
RunningConfig = DevConfig
elif mode == "test":
RunningConfig = TestConfig
elif mode == "test_api":
RunningConfig = TestAPIConfig

View File

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

View File

@ -496,12 +496,11 @@ def clear_cache(sanitize): # clear-cache
@app.cli.command()
def init_test_database():
def init_test_database(): # init-test-database
"""Initialise les objets en base pour les tests API
(à appliquer sur SCODOC_TEST ou SCODOC_DEV)
"""
click.echo("Initialisation base de test API...")
# import app as mapp # le package app
ctx = app.test_request_context()
ctx.push()

1
tests/api/__init__.py Normal file
View File

@ -0,0 +1 @@
# API tests

11
tests/api/dotenv_exemple Normal file
View File

@ -0,0 +1,11 @@
# Configuration du _client_ test API
# A renommer .env
# and /opt/scodoc/tests/api/
# et à remplir.
# URL du serveur ScoDoc à interroger
SCODOC_URL = "http://localhost:5000/"
# Le client (python) doit-il vérifier le certificat SSL du serveur ?
# ou True si serveur de production avec certif SSL valide
CHECK_CERTIFICATE = False

View File

@ -16,12 +16,11 @@ export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valid
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
Travail en cours, un seul point d'API (list_depts).
Travail en cours.
"""
from dotenv import load_dotenv
import os
import pdb
import requests
import urllib3
from pprint import pprint as pp
@ -29,16 +28,16 @@ from pprint import pprint as pp
# --- Lecture configuration (variables d'env ou .env)
BASEDIR = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(BASEDIR, ".env"))
CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
CHK_CERT = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
SCODOC_URL = os.environ["SCODOC_URL"]
SCODOC_DEPT = os.environ["SCODOC_DEPT"]
DEPT_URL = SCODOC_URL + "/ScoDoc/" + SCODOC_DEPT + "/Scolarite/"
API_URL = SCODOC_URL + "/ScoDoc/api"
SCODOC_USER = os.environ["SCODOC_USER"]
SCODOC_PASSWORD = os.environ["SCODOC_PASSWD"]
print(f"SCODOC_URL={SCODOC_URL}")
print(f"API URL={API_URL}")
# ---
if not CHECK_CERTIFICATE:
if not CHK_CERT:
urllib3.disable_warnings()
@ -48,9 +47,7 @@ class ScoError(Exception):
def GET(path: str, headers={}, errmsg=None):
"""Get and returns as JSON"""
r = requests.get(
DEPT_URL + "/" + path, headers=headers or HEADERS, verify=CHECK_CERTIFICATE
)
r = requests.get(API_URL + "/" + path, headers=headers or HEADERS, verify=CHK_CERT)
if r.status_code != 200:
raise ScoError(errmsg or "erreur !")
return r.json() # decode la reponse JSON
@ -58,39 +55,40 @@ def GET(path: str, headers={}, errmsg=None):
def POST(s, path: str, data: dict, errmsg=None):
"""Post"""
r = s.post(DEPT_URL + "/" + path, data=data, verify=CHECK_CERTIFICATE)
r = s.post(API_URL + "/" + path, data=data, verify=CHK_CERT)
if r.status_code != 200:
raise ScoError(errmsg or "erreur !")
return r.text
# --- Obtention du jeton (token)
r = requests.post(
SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD)
)
r = requests.post(API_URL + "/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD))
assert r.status_code == 200
token = r.json()["token"]
HEADERS = {"Authorization": f"Bearer {token}"}
r = requests.get(
SCODOC_URL + "/ScoDoc/api/list_depts",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
r = requests.get(API_URL + "/departements", headers=HEADERS, verify=CHK_CERT)
if r.status_code != 200:
raise ScoError("erreur de connexion: vérifier adresse et identifiants")
pp(r.json())
# Liste des tous les étudiants en cours (de tous les depts)
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiants/courant",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
# Liste de tous les étudiants en cours (de tous les depts)
r = requests.get(API_URL + "/etudiants/courant", headers=HEADERS, verify=CHK_CERT)
if r.status_code != 200:
raise ScoError("erreur de connexion: vérifier adresse et identifiants")
print(f"{len(r.json())} étudiants courants")
# Bulletin d'un BUT
formsemestre_id = 1052 # A adapter
etudid = 16400
bul = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
# d'un DUT
formsemestre_id = 1028 # A adapter
etudid = 14721
bul_dut = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
# # --- Recupere la liste de tous les semestres:
# sems = GET(s, "Notes/formsemestre_list?format=json", "Aucun semestre !")
@ -146,15 +144,3 @@ if r.status_code != 200:
# print(
# f"Pour vérifier, aller sur: {DEPT_URL}/Notes/moduleimpl_status?moduleimpl_id={mod['moduleimpl_id']}",
# )
# # ---- Saisie d'une note
# junk = POST(
# s,
# "/Notes/save_note",
# data={
# "etudid": etudid,
# "evaluation_id": evaluation_id,
# "value": 16.66, # la note !
# "comment": "test API",
# },
# )

View File

@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
"""Test Logos
"""Test API
Utilisation :
créer les variables d'environnement: (indiquer les valeurs
pour le serveur ScoDoc que vous voulez interroger)
export SCODOC_URL="https://scodoc.xxx.net/"
export SCODOC_USER="xxx"
export API_USER="xxx"
export SCODOC_PASSWD="xxx"
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
@ -15,23 +15,25 @@ Utilisation :
"""
import os
import requests
from dotenv import load_dotenv
import pytest
BASEDIR = "/opt/scodoc/tests/api"
load_dotenv(os.path.join(BASEDIR, ".env"))
CHECK_CERTIFICATE = bool(os.environ.get("CHECK_CERTIFICATE", False))
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")
print(f"SCODOC_URL={SCODOC_URL}")
print(f"API URL={API_URL}")
SCODOC_USER = "test"
SCODOC_PASSWORD = "test"
SCODOC_URL = "http://192.168.1.12:5000"
CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
def get_token():
@pytest.fixture
def api_headers() -> dict:
"""
Permet de set le token dans le header
Demande un jeton et renvoie un dict à utiliser dans les en-têtes de requêtes http
"""
r0 = requests.post(
SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD)
)
r0 = requests.post(API_URL + "/tokens", auth=(API_USER, API_PASSWORD))
token = r0.json()["token"]
return {"Authorization": f"Bearer {token}"}
HEADERS = get_token()

View File

@ -18,63 +18,78 @@ Utilisation :
"""
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
# Etudiant pour les tests
ETUDID = 1
INE = "1"
NIP = "1"
# absences
def test_absences():
def test_absences(api_headers):
"""
Route: /absences/etudid/<int:etudid>
"""
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/etudid/<int:etudid>",
headers=HEADERS,
f"{API_URL}/absences/etudid/{ETUDID}",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/nip/<int:nip>",
headers=HEADERS,
f"{API_URL}/absences/nip/{NIP}",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/ine/<int:ine>",
headers=HEADERS,
f"{API_URL}/absences/ine/{INE}",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# absences_justify
def test_absences_justify():
def test_absences_justify(api_headers):
"""
Route: /absences/etudid/<etudid:int>/just
"""
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/etudid/1/just",
headers=HEADERS,
API_URL + f"/absences/etudid/{ETUDID}/just",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# TODO vérifier résultat
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/nip/1/just",
headers=HEADERS,
API_URL + f"/absences/nip/{NIP}/just",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# TODO vérifier résultat
r = requests.get(
SCODOC_URL + "/ScoDoc/api/absences/ine/1/just",
headers=HEADERS,
API_URL + f"/absences/ine/{INE}/just",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# TODO vérifier résultat
# abs_groupe_etat
def test_abs_groupe_etat():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/absences/abs_group_etat/?group_id=<int:group_id>&date_debut=date_debut&date_fin=date_fin",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# XXX TODO
# def test_abs_groupe_etat(api_headers):
# """
# Route:
# """
# r = requests.get(
# API_URL + "/absences/abs_group_etat/?group_id=<int:group_id>&date_debut=date_debut&date_fin=date_fin",
# headers=api_headers,
# verify=CHECK_CERTIFICATE,
# )
# assert r.status_code == 200

View File

@ -19,147 +19,140 @@ Utilisation :
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
from tests.api.tools_test_api import verify_fields
DEPARTEMENT_FIELDS = [
"id",
"acronym",
"description",
"visible",
"date_creation",
]
# departements
def test_departements(): #XXX TODO pour Seb
def test_departements(api_headers):
""" "
Routes: /departements_ids, /departement, /departement/<string:dept>/formsemestres_ids
"""
# --- Liste des ids
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements",
headers=HEADERS,
API_URL + "/departements_ids",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
departements_ids = r.json()
assert isinstance(departements_ids, list)
assert len(departements_ids) > 0
assert all(isinstance(x, int) for x in departements_ids)
# liste_etudiants
def test_liste_etudiants(): #XXX TODO pour Seb
dept_id = departements_ids[0]
# --- Infos sur un département, accès par id
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste",
headers=HEADERS,
f"{API_URL}/departement/{dept_id}",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 16
dept_a = r.json()
assert verify_fields(dept_a, DEPARTEMENT_FIELDS) is True
# --- Infos sur un département, accès par acronyme4
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste/1",
headers=HEADERS,
f"{API_URL}/departement/{dept_a['acronym']}",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 16
dept_b = r.json()
assert dept_a == dept_b
# Liste des formsemestres
r = requests.get(
f"{API_URL}/departement/{dept_a['acronym']}/formsemestres_ids",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
dept_ids = r.json()
assert isinstance(dept_ids, list)
assert all(isinstance(x, int) for x in dept_ids)
assert len(dept_ids) > 0
assert dept_id in dept_ids
def test_list_etudiants(api_headers):
fields = {"id", "nip", "ine", "nom", "nom_usuel", "prenom", "civilite"}
r = requests.get(
API_URL + "/departement/TAPI/etudiants",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
etud = r.json()[0]
assert verify_fields(etud, fields) is True
# liste_semestres_courant
def test_semestres_courant(): #XXX TODO pour Seb
def test_semestres_courant(api_headers):
fields = [
"titre",
"gestion_semestrielle",
"scodoc7_id",
"date_debut",
"bul_bgcolor",
"date_fin",
"resp_can_edit",
"dept_id",
"etat",
"resp_can_change_ens",
"id",
"modalite",
"ens_can_edit_eval",
"formation_id",
"gestion_compensation",
"elt_sem_apo",
"semestre_id",
"bul_hide_xml",
"elt_annee_apo",
"block_moyennes",
"formsemestre_id",
"titre_num",
"titre_formation",
"date_debut_iso",
"date_fin_iso",
"responsables",
]
dept_id = 1
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/semestres_courants",
headers=HEADERS,
f"{API_URL}/departement/{dept_id}",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
# referenciel_competences
def test_referenciel_competences():
dept = r.json()
assert dept["id"] == dept_id
# Accès via acronyme
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/departements/TAPI/formations/1/referentiel_competences",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200 or 204
# semestre_index
def test_semestre_index(): #XXX TODO pour Seb
ue_fields = [
"semestre_idx",
"type",
"formation_id",
"ue_code",
"id",
"ects",
"acronyme",
"is_external",
"numero",
"code_apogee",
"titre",
"coefficient",
"color",
"ue_id",
]
ressource_fields = [
"heures_tp",
"code_apogee",
"titre",
"coefficient",
"module_type",
"id",
"ects",
"abbrev",
"ue_id",
"code",
"formation_id",
"heures_cours",
"matiere_id",
"heures_td",
"semestre_id",
"numero",
"module_id",
]
sae_fields = [
"heures_tp",
"code_apogee",
"titre",
"coefficient",
"module_type",
"id",
"ects",
"abbrev",
"ue_id",
"code",
"formation_id",
"heures_cours",
"matiere_id",
"heures_td",
"semestre_id",
"numero",
"module_id",
]
r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/formsemestre/1/programme",
headers=HEADERS,
f"{API_URL}/departement/{dept['acronym']}/formsemestres_courants",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 3
result_a = r.json()
assert isinstance(result_a, list) # liste de formsemestres
assert len(result_a) > 0
sem = result_a[0]
assert verify_fields(sem, fields) is True
ue = r.json()["ues"][0]
ressource = r.json()["ressources"][0]
sae = r.json()["saes"][0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in ue:
if field not in ue_fields:
fields_OK = False
for field in ressource:
if field not in ressource_fields:
fields_OK = False
for field in sae:
if field not in sae_fields:
fields_OK = False
assert fields_OK is True
# accès via dept_id
r = requests.get(
f"{API_URL}/departement/{dept['id']}/formsemestres_courants",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
result_b = r.json()
assert result_a == result_b

View File

@ -16,392 +16,176 @@ Utilisation :
Lancer :
pytest tests/api/test_api_etudiants.py
"""
from random import randint
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
from tests.api.tools_test_api import verify_fields
from tests.api.tools_test_api import ETUD_FIELDS, FSEM_FIELDS
# # etudiants
# def test_etudiants():
#
# fields = [
# "civilite",
# "code_ine",
# "code_nip",
# "date_naissance",
# "email",
# "emailperso",
# "etudid",
# "nom",
# "prenom",
# "nomprenom",
# "lieu_naissance",
# "dept_naissance",
# "nationalite",
# "boursier",
# "id",
# "domicile",
# "villedomicile",
# "telephone",
# "fax",
# "description",
# "codepostaldomicile",
# "paysdomicile",
# "telephonemobile",
# "typeadresse",
# ]
#
# r = requests.get(
# SCODOC_URL + "/ScoDoc/api/etudiants",
# headers=HEADERS,
# verify=CHECK_CERTIFICATE,
# )
# assert r.status_code == 200
# assert len(r.json()) == 16
#
# # Choisis aléatoirement un étudiant dans la liste des étudiants
# etu = r.json()[randint(0, len(r.json())) - 1]
#
# fields_OK = True
#
# # Vérifie si tous les champs sont bien présents
# for field in etu:
# if field not in fields:
# fields_OK = False
#
# assert fields_OK is True
# etudiants_courant
def test_etudiants_courant(): # XXX TODO pour Seb
fields = [
"civilite",
"code_ine",
"code_nip",
"date_naissance",
"email",
"emailperso",
"etudid",
"nom",
"prenom",
"nomprenom",
"lieu_naissance",
"dept_naissance",
"nationalite",
"boursier",
"id",
"domicile",
"villedomicile",
"telephone",
"fax",
"description",
"codepostaldomicile",
"paysdomicile",
"telephonemobile",
"typeadresse",
]
def test_etudiants_courant(api_headers):
"""
Route: /etudiants/courant
"""
fields = {"id", "nip", "ine", "nom", "nom_usuel", "prenom", "civilite"}
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiants/courant",
headers=HEADERS,
API_URL + "/etudiants/courant",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 16
etudiants = r.json()
assert len(etudiants) > 0
# Choisis aléatoirement un étudiant dans la liste des étudiants
etu = r.json()[randint(0, len(r.json())) - 1]
etud = etudiants[-1]
assert verify_fields(etud, fields) is True
fields_OK = True
########## Version long ################
r = requests.get(
API_URL + "/etudiants/courant/long",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
etudiants = r.json()
assert len(etudiants) == 16 # HARDCODED
# Vérifie si tous les champs sont bien présents
for field in etu:
if field not in fields:
fields_OK = False
assert fields_OK is True
etud = etudiants[-1]
assert verify_fields(etud, ETUD_FIELDS) is True
# etudiant
def test_etudiant(): # XXX TODO pour Seb
fields = [
"civilite",
"code_ine",
"code_nip",
"date_naissance",
"email",
"emailperso",
"etudid",
"nom",
"prenom",
"nomprenom",
"lieu_naissance",
"dept_naissance",
"nationalite",
"boursier",
"id",
"domicile",
"villedomicile",
"telephone",
"fax",
"description",
"codepostaldomicile",
"paysdomicile",
"telephonemobile",
"typeadresse",
]
def test_etudiant(api_headers):
"""
Route:
"""
######### Test etudid #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1",
headers=HEADERS,
API_URL + "/etudiant/etudid/1",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 24
etud = r.json()
etu = r.json()
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in etu:
if field not in fields:
fields_OK = False
assert fields_OK is True
assert verify_fields(etud, ETUD_FIELDS) is True
######### Test code nip #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1",
headers=HEADERS,
API_URL + "/etudiant/nip/1",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 24
etu = r.json()
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in etu:
if field not in fields:
fields_OK = False
assert fields_OK is True
etud = r.json()
fields_ok = verify_fields(etud, ETUD_FIELDS)
assert fields_ok is True
######### Test code ine #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1",
headers=HEADERS,
API_URL + "/etudiant/ine/1",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 24
etu = r.json()
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in etu:
if field not in fields:
fields_OK = False
assert fields_OK is True
etud = r.json()
assert len(etud) == 24
fields_ok = verify_fields(etud, ETUD_FIELDS)
assert fields_ok is True
# etudiant_formsemestres
def test_etudiant_formsemestres(): # XXX TODO pour Seb
fields = [
"date_fin",
"resp_can_edit",
"dept_id",
"etat",
"resp_can_change_ens",
"id",
"modalite",
"ens_can_edit_eval",
"formation_id",
"gestion_compensation",
"elt_sem_apo",
"semestre_id",
"bul_hide_xml",
"elt_annee_apo",
"titre",
"block_moyennes",
"scodoc7_id",
"date_debut",
"gestion_semestrielle",
"bul_bgcolor",
"formsemestre_id",
"titre_num",
"date_debut_iso",
"date_fin_iso",
"responsables",
]
def test_etudiant_formsemestres(api_headers):
"""
Route: /etudiant/etudid/<etudid:int>/formsemestres
"""
######### Test etudid #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/formsemestres",
headers=HEADERS,
API_URL + "/etudiant/etudid/1/formsemestres",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
formsemestres = r.json()
assert len(formsemestres) == 1
formsemestre = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in formsemestre:
if field not in fields:
fields_OK = False
assert fields_OK is True
formsemestre = formsemestres[0]
assert verify_fields(formsemestre, FSEM_FIELDS) is True
######### Test code nip #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/formsemestres",
headers=HEADERS,
API_URL + "/etudiant/nip/1/formsemestres",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
formsemestres = r.json()
assert len(formsemestres) == 1
formsemestre = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in formsemestre:
if field not in fields:
fields_OK = False
assert fields_OK is True
formsemestre = formsemestres[0]
assert verify_fields(formsemestre, FSEM_FIELDS) is True
######### Test code ine #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/formsemestres",
headers=HEADERS,
API_URL + "/etudiant/ine/1/formsemestres",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
formsemestres = r.json()
assert len(formsemestres) == 1
formsemestre = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in formsemestre:
if field not in fields:
fields_OK = False
assert fields_OK is True
formsemestre = formsemestres[0]
assert verify_fields(formsemestre, FSEM_FIELDS) is True
# etudiant_bulletin_semestre
def test_etudiant_bulletin_semestre():
# fields = [
# "etudid",
# "formsemestre_id",
# "date",
# "publie",
# "etapes",
# "etudiant",
# "note",
# "rang",
# "rang_group",
# "note_max",
# "bonus_sport_culture",
# "ue",
# "ue_capitalisee",
# "absences",
# "appreciation",
# ]
def test_etudiant_bulletin_semestre(api_headers):
"""
Route: /etudiant/etudid/<etudid>/formsemestre/<formsemestre_id>/bulletin
"""
######### Test etudid #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/formsemestre/1/bulletin",
headers=HEADERS,
API_URL + "/etudiant/etudid/1/formsemestre/1/bulletin",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 13
# bulletin = r.json()
#
# fields_OK = True
#
# # Vérifie si tous les champs sont bien présents
# for field in bulletin:
# if field not in fields:
# fields_OK = False
#
# assert fields_OK is True
bul = r.json()
assert len(bul) == 13 # HARDCODED
######### Test code nip #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/formsemestre/1/bulletin",
headers=HEADERS,
API_URL + "/etudiant/nip/1/formsemestre/1/bulletin",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 13
# bulletin = r.json()
#
# fields_OK = True
#
# # Vérifie si tous les champs sont bien présents
# for field in bulletin:
# if field not in fields:
# fields_OK = False
#
# assert fields_OK is True
bul = r.json()
assert len(bul) == 13 # HARDCODED
######### Test code ine #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/formsemestre/1/bulletin",
headers=HEADERS,
API_URL + "/etudiant/ine/1/formsemestre/1/bulletin",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 13
# bulletin = r.json()
#
# fields_OK = True
#
# # Vérifie si tous les champs sont bien présents
# for field in bulletin:
# if field not in fields:
# fields_OK = False
#
# assert fields_OK is True
bul = r.json()
assert len(bul) == 13 # HARDCODED
# etudiant_groups
def test_etudiant_groups():
def test_etudiant_groups(api_headers):
"""
Route:
/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/groups
"""
fields = [
"partition_id",
"id",
@ -417,62 +201,39 @@ def test_etudiant_groups():
######### Test etudid #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/semestre/1/groups",
headers=HEADERS,
API_URL + "/etudiant/etudid/1/formsemestre/1/groups",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
groups = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in groups:
if field not in fields:
fields_OK = False
assert fields_OK is True
groups = r.json()
assert len(groups) == 1 # dans un seul groupe
group = groups[0]
fields_ok = verify_fields(group, fields)
assert fields_ok is True
######### Test code nip #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/semestre/1/groups",
headers=HEADERS,
API_URL + "/etudiant/nip/1/formsemestre/1/groups",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
groups = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in groups:
if field not in fields:
fields_OK = False
assert fields_OK is True
groups = r.json()
assert len(groups) == 1 # dans un seul groupe
group = groups[0]
fields_ok = verify_fields(group, fields)
assert fields_ok is True
######### Test code ine #########
r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/semestre/1/groups",
headers=HEADERS,
API_URL + "/etudiant/ine/1/formsemestre/1/groups",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
assert len(r.json()) == 1
groups = r.json()[0]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in groups:
if field not in fields:
fields_OK = False
assert fields_OK is True
groups = r.json()
assert len(groups) == 1 # dans un seul groupe
group = groups[0]
fields_ok = verify_fields(group, fields)
assert fields_ok is True

View File

@ -19,23 +19,31 @@ Utilisation :
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
# evaluations
def test_evaluations():
def test_evaluations(api_headers):
"""
Route: /evaluation/<int:moduleimpl_id>
"""
r = requests.get(
SCODOC_URL + "/ScoDoc/api/evaluations/1",
headers=HEADERS,
API_URL + "/evaluations/1",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# TODO
# evaluation_notes
def test_evaluation_notes():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/evaluations/eval_notes/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# TODO car pas d'évaluations créées à ce stade
# def test_evaluation_notes(api_headers):
# """
# Route: /evaluation/eval_notes/<int:evaluation_id>
# """
# r = requests.get(
# API_URL + "/evaluation/eval_notes/1",
# headers=api_headers,
# verify=CHECK_CERTIFICATE,
# )
# assert r.status_code == 200
# # TODO

View File

@ -19,64 +19,91 @@ Utilisation :
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
from tests.api.tools_test_api import verify_fields
from tests.api.tools_test_api import FORMATION_FIELDS, MODIMPL_FIELDS
# formations
def test_formations():
def test_formations_ids(api_headers):
"""
Route: /formations_ids
"""
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations",
headers=HEADERS,
API_URL + "/formations_ids",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
formations_ids = r.json()
# Une liste non vide d'entiers
assert isinstance(formations_ids, list)
assert len(formations_ids) > 0
assert all(isinstance(x, int) for x in formations_ids)
# formations_by_id
def test_formations_by_id():
def test_formations_by_id(api_headers):
"""
Route: /formation/<int:formation_id>
"""
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/1",
headers=HEADERS,
API_URL + "/formation/1",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
formation = r.json()
assert verify_fields(formation, FORMATION_FIELDS) is True
# TODO tester le contenu de certains champs
# formation_export_by_formation_id
def test_formation_export_by_formation_id():
def test_formation_export(api_headers):
"""
Route: /formation/formation_export/<int:formation_id>
"""
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/formation_export/1",
headers=HEADERS,
API_URL + "/formation/formation_export/1",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
export_formation = r.json()
assert verify_fields(export_formation, FORMATION_FIELDS) is True
# TODO tester le contenu de certains champs
# formsemestre_apo
def test_formsemestre_apo():
# TODO
# def test_formsemestre_apo(api_headers):
# r = requests.get(
# API_URL + "/formation/apo/<string:etape_apo>",
# headers=api_headers,
# verify=CHECK_CERTIFICATE,
# )
# assert r.status_code == 200
def test_moduleimpl(api_headers):
"""
Route: /formation/moduleimpl/<int:moduleimpl_id>
"""
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/apo/<string:etape_apo>",
headers=HEADERS,
API_URL + "/formation/moduleimpl/1",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
moduleimpl = r.json()
assert verify_fields(moduleimpl, MODIMPL_FIELDS) is True
# TODO tester le contenu de certains champs
# moduleimpls
def test_moduleimpls():
def test_referentiel_competences(api_headers):
"""
Route: "/formation/<int:formation_id>/referentiel_competences",
"""
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/<int:moduleimpl_id>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# moduleimpls_sem
def test_moduleimpls_sem():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/formations/moduleimpl/formsemestre/1/liste",
headers=HEADERS,
API_URL + "/formation/1/referentiel_competences",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# XXX A compléter

View File

@ -18,90 +18,69 @@ Utilisation :
"""
import requests
from app.api.formsemestres import formsemestre
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
from tests.api.tools_test_api import MODIMPL_FIELDS, verify_fields
from tests.api.tools_test_api import FSEM_FIELDS, UE_FIELDS, MODULE_FIELDS
# formsemestre
def test_formsemestre():
def test_formsemestre(api_headers):
"""
Route: /formsemestre/<id>
"""
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formsemestre/1",
headers=HEADERS,
API_URL + "/formsemestre/1",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
formsemestre = r.json()
fields = [
"date_fin",
"resp_can_edit",
"dept_id",
"etat",
"resp_can_change_ens",
"id",
"modalite",
"ens_can_edit_eval",
"formation_id",
"gestion_compensation",
"elt_sem_apo",
"semestre_id",
"bul_hide_xml",
"elt_annee_apo",
"titre",
"block_moyennes",
"scodoc7_id",
"date_debut",
"gestion_semestrielle",
"bul_bgcolor",
"formsemestre_id",
"titre_num",
"date_debut_iso",
"date_fin_iso",
"responsables",
]
fields_OK = True
# Vérifie si tous les champs sont bien présents
for field in formsemestre:
if field not in fields:
fields_OK = False
assert fields_OK is True
assert verify_fields(formsemestre, FSEM_FIELDS)
# etudiant_bulletin
def test_etudiant_bulletin(): #XXX TODO pour Seb
def test_etudiant_bulletin(api_headers):
"""
Route:
"""
formsemestre_id = 1
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/etudid/1/bulletin",
headers=HEADERS,
f"{API_URL}/etudiant/etudid/1/formsemestre/{formsemestre_id}/bulletin",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
bull_a = r.json()
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/nip/1/bulletin",
headers=HEADERS,
f"{API_URL}/etudiant/nip/1/formsemestre/{formsemestre_id}/bulletin",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
bull_b = r.json()
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/ine/1/bulletin",
headers=HEADERS,
f"{API_URL}/etudiant/ine/1/formsemestre/{formsemestre_id}/bulletin",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
bull_c = r.json()
# elimine les dates de publication pour comparer les autres champs
del bull_a["date"]
del bull_b["date"]
del bull_c["date"]
assert bull_a == bull_b == bull_c
# bulletins
def test_bulletins():
def test_bulletins(api_headers):
"""
Route:
"""
r = requests.get(
SCODOC_URL + "/ScoDoc/api/formsemestre/1/bulletins",
headers=HEADERS,
API_URL + "/formsemestre/1/bulletins",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
@ -110,8 +89,40 @@ def test_bulletins():
# # jury
# def test_jury():
# r = requests.get(
# SCODOC_URL + "/ScoDoc/api/formsemestre/1/jury",
# headers=HEADERS,
# API_URL + "/formsemestre/1/jury",
# headers=api_headers,
# verify=CHECK_CERTIFICATE,
# )
# assert r.status_code == 200
def test_formsemestre_programme(api_headers):
"""
Route: /formsemestre/1/programme
"""
r = requests.get(
API_URL + "/formsemestre/1/programme",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
prog = r.json()
assert isinstance(prog, dict)
assert "ues" in prog
assert "modules" in prog
assert "ressources" in prog
assert "saes" in prog
assert isinstance(prog["ues"], list)
assert isinstance(prog["modules"], list)
ue = prog["ues"][0]
modules = prog["modules"]
# Il y a toujours au moins une SAE et une ressources dans notre base de test
ressource = prog["ressources"][0]
sae = prog["saes"][0]
assert verify_fields(ue, UE_FIELDS)
if len(modules) > 1:
assert verify_fields(modules[0], MODIMPL_FIELDS)
assert verify_fields(ressource, MODIMPL_FIELDS)
assert verify_fields(sae, MODIMPL_FIELDS)

View File

@ -19,37 +19,41 @@ Utilisation :
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
# jury_preparation
def test_jury_preparation():
def test_jury_preparation(api_headers):
"""
Route:
"""
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/formsemestre/<int:formsemestre_id>/preparation_jury",
headers=HEADERS,
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# jury_decisions
def test_jury_decisions():
def test_jury_decisions(api_headers):
"""
Route:
"""
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/formsemestre/<int:formsemestre_id>/decisions_jury",
headers=HEADERS,
API_URL + "/jury/formsemestre/<int:formsemestre_id>/decisions_jury",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# set_decision_jury
def test_set_decision_jury():
def test_set_decision_jury(api_headers):
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/set_decision/etudid?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
headers=HEADERS,
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
@ -58,7 +62,7 @@ def test_set_decision_jury():
SCODOC_URL
+ "/ScoDoc/api/jury/set_decision/nip?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
headers=HEADERS,
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
@ -67,18 +71,20 @@ def test_set_decision_jury():
SCODOC_URL
+ "/ScoDoc/api/jury/set_decision/ine?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
headers=HEADERS,
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# annule_decision_jury
def test_annule_decision_jury():
def test_annule_decision_jury(api_headers):
"""
Route:
"""
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/annule_decision",
headers=HEADERS,
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
@ -86,7 +92,7 @@ def test_annule_decision_jury():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/nip/<int:nip>/formsemestre/<int:formsemestre_id>/annule_decision",
headers=HEADERS,
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
@ -94,7 +100,7 @@ def test_annule_decision_jury():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/jury/ine/<int:ine>/formsemestre/<int:formsemestre_id>/annule_decision",
headers=HEADERS,
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200

View File

@ -5,11 +5,16 @@
"""Exemple utilisation API ScoDoc 9 avec jeton obtenu par basic authentication
utilisation:
à faire fonctionner en environnment de test (FLASK_ENV=test dans le fichier .env)
à faire fonctionner en environnment de test (FLASK_ENV=test_api dans le fichier .env)
pytest tests/api/test_api_logos.py
"""
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
# XXX TODO
# Ce test a une logique très différente des autres : A UNIFIER
from tests.api.setup_test_api import API_URL
from scodoc import app
from tests.unit.config_test_logos import (
@ -22,35 +27,47 @@ from tests.unit.config_test_logos import (
def test_super_access(create_super_token):
"""
Route:
"""
dept1, dept2, dept3, token = create_super_token
HEADERS = {"Authorization": f"Bearer {token}"}
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client:
response = client.get("/ScoDoc/api/logos", headers=HEADERS)
response = client.get(API_URL + "/logos", headers=headers)
assert response.status_code == 200
assert response.json is not None
def test_admin_access(create_admin_token):
"""
Route:
"""
dept1, dept2, dept3, token = create_admin_token
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client:
response = client.get("/ScoDoc/api/logos", headers=headers)
response = client.get(API_URL + "/logos", headers=headers)
assert response.status_code == 401
def test_lambda_access(create_lambda_token):
"""
Route:
"""
dept1, dept2, dept3, token = create_lambda_token
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client:
response = client.get("/ScoDoc/api/logos", headers=headers)
response = client.get(API_URL + "/logos", headers=headers)
assert response.status_code == 401
def test_initial_with_header_and_footer(create_super_token):
"""
Route:
"""
dept1, dept2, dept3, token = create_super_token
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client:
response = client.get("/ScoDoc/api/logos", headers=headers)
response = client.get(API_URL + "/logos", headers=headers)
assert response.status_code == 200
assert response.json is not None
assert len(response.json) == 7

View File

@ -19,41 +19,110 @@ Utilisation :
import requests
from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS
from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
from tests.api.tools_test_api import verify_fields
def test_partition(api_headers):
"""
Route:
"""
fields = [
"partition_id",
"id",
"formsemestre_id",
"partition_name",
"numero",
"bul_show_rank",
"show_in_lists",
]
# partition
def test_partition():
r = requests.get(
SCODOC_URL + "/ScoDoc/api/partitions/1",
headers=HEADERS,
API_URL + "/partitions/1",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
partitions = r.json()
assert len(partitions) == 1
partition = partitions[0]
fields_ok = verify_fields(partition, fields)
assert fields_ok is True
# etud_in_group
def test_etud_in_group():
def test_etud_in_group(api_headers):
"""
Route:
"""
fields = [
"etudid",
"id",
"dept_id",
"nom",
"prenom",
"nom_usuel",
"civilite",
"date_naissance",
"lieu_naissance",
"dept_naissance",
"nationalite",
"statut",
"boursier",
"photo_filename",
"code_nip",
"code_ine",
"scodoc7_id",
"email",
"emailperso",
"domicile",
"codepostaldomicile",
"villedomicile",
"paysdomicile",
"telephone",
"telephonemobile",
"fax",
"typeadresse",
"description",
"group_id",
"etat",
"civilite_str",
"nom_disp",
"nomprenom",
"ne",
"email_default",
]
r = requests.get(
SCODOC_URL + "/ScoDoc/api/partitions/groups/1",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
SCODOC_URL + "/ScoDoc/api/partitions/groups/<int:group_id>/etat/<string:etat>",
headers=HEADERS,
API_URL + "/partitions/groups/1",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
etu = r.json()[0]
fields_ok = verify_fields(etu, fields)
assert r.status_code == 200
assert len(r.json()) == 16
assert fields_ok is True
# r = requests.get(
# API_URL + "/partitions/groups/1/etat/<string:etat>",
# headers=api_headers,
# verify=CHECK_CERTIFICATE,
# )
# assert r.status_code == 200
# set_groups
def test_set_groups():
r = requests.get(
SCODOC_URL
+ "/ScoDoc/api/partitions/set_groups?partition_id=<int:partition_id>&groups_lists=<int:groups_lists>&"
"groups_to_create=<int:groups_to_create>&groups_to_delete=<int:groups_to_delete>",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# # set_groups
# def test_set_groups(api_headers):
# """
# Route:
# """
# r = requests.get(
# SCODOC_URL
# + "/partitions/set_groups/partition/<int:partition_id>/groups/<string:groups_id>"
# "/delete/<string:groups_to_delete>/create/<string:groups_to_create>",
# headers=api_headers,
# verify=CHECK_CERTIFICATE,
# )
# assert r.status_code == 200

View File

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
"""Test permissions
On a deux utilisateurs dans la base test API:
- "test", avec le rôle LecteurAPI qui a APIView,
- et "other", qui n'a aucune permission.
Lancer :
pytest tests/api/test_api_permissions.py
"""
import requests
import flask
from tests.api.setup_test_api import API_URL, SCODOC_URL, CHECK_CERTIFICATE, api_headers
from tests.api.tools_test_api import verify_fields
from app import create_app
from config import RunningConfig
def test_permissions(api_headers):
"""
vérification de la permissions APIView et du non accès sans role
de toutes les routes de l'API
"""
# Ce test va récupérer toutes les routes de l'API
app = create_app(RunningConfig)
assert app
# Les routes de l'API avec GET, excluant les logos pour le momeent XXX
api_rules = [
r
for r in app.url_map.iter_rules()
if str(r).startswith("/ScoDoc/api")
and not "logo" in str(r) # ignore logos
and "GET" in r.methods
]
assert len(api_rules) > 0
args = {
"etudid": 1,
# "date_debut":
# "date_fin":
"dept": "TAPI",
"dept_ident": "TAPI",
"dept_id": 1,
"etape_apo": "???",
"etat": "I",
"evaluation_id": 1,
"formation_id": 1,
"formsemestre_id": 1,
"group_id": 1,
"ine": "1",
"module_id": 1,
"moduleimpl_id": 1,
"nip": 1,
"partition_id": 1,
}
for rule in api_rules:
path = rule.build(args)[1]
if not "GET" in rule.methods:
# skip all POST routes
continue
r = requests.get(
SCODOC_URL + path,
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# Même chose sans le jeton:
for rule in api_rules:
path = rule.build(args)[1]
if not "GET" in rule.methods:
# skip all POST routes
continue
r = requests.get(
SCODOC_URL + path,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 401
# Demande un jeton pour "other"
r = requests.post(API_URL + "/tokens", auth=("other", "other"))
assert r.status_code == 200
token = r.json()["token"]
headers = {"Authorization": f"Bearer {token}"}
# Vérifie que tout est interdit
for rule in api_rules:
path = rule.build(args)[1]
if not "GET" in rule.methods:
# skip all POST routes
continue
r = requests.get(
SCODOC_URL + path,
headers=headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 403

131
tests/api/tools_test_api.py Normal file
View File

@ -0,0 +1,131 @@
"""Utilitaires pour les tests de l'API
"""
def verify_fields(json_response: dict, expected_fields: set) -> bool:
"""
Vérifie si les champs attendu de la réponse json sont présents
json_response : la réponse de la requête
expected_fields : ensemble des champs à vérifier
Retourne True ou False
"""
return all(field in json_response for field in expected_fields)
ETUD_FIELDS = {
"boursier",
"civilite",
"code_ine",
"code_nip",
"codepostaldomicile",
"date_naissance",
"dept_naissance",
"description",
"domicile",
"email",
"emailperso",
"etudid",
"id",
"lieu_naissance",
"nationalite",
"nom",
"nomprenom",
"paysdomicile",
"prenom",
"telephone",
"telephonemobile",
"typeadresse",
"villedomicile",
}
FORMATION_FIELDS = {
"id",
"acronyme",
"titre_officiel",
"formation_code",
"code_specialite",
"dept_id",
"titre",
"version",
"type_parcours",
"referentiel_competence_id",
"formation_id",
}
FSEM_FIELDS = {
"block_moyennes",
"bul_bgcolor",
"bul_hide_xml",
"date_debut_iso",
"date_debut",
"date_fin_iso",
"date_fin",
"dept_id",
"elt_annee_apo",
"elt_sem_apo",
"ens_can_edit_eval",
"etat",
"formation_id",
"formsemestre_id",
"gestion_compensation",
"gestion_semestrielle",
"id",
"modalite",
"resp_can_change_ens",
"resp_can_edit",
"responsables",
"semestre_id",
"titre_formation",
"titre_num",
"titre",
}
MODIMPL_FIELDS = {
"id",
"formsemestre_id",
"computation_expr",
"module_id",
"responsable_id",
"moduleimpl_id",
"ens",
"module",
}
MODULE_FIELDS = {
"heures_tp",
"code_apogee",
"titre",
"coefficient",
"module_type",
"id",
"ects",
"abbrev",
"ue_id",
"code",
"formation_id",
"heures_cours",
"matiere_id",
"heures_td",
"semestre_id",
"numero",
"module_id",
}
UE_FIELDS = {
"semestre_idx",
"type",
"formation_id",
"ue_code",
"id",
"ects",
"acronyme",
"is_external",
"numero",
"code_apogee",
"titre",
"coefficient",
"color",
"ue_id",
}

View File

@ -9,8 +9,16 @@ die() {
echo
exit 1
}
[ $# = 1 ] || die "Usage $0 db_name"
[ $# = 1 ] || [ $# = 2 ] || die "Usage $0 [--drop] db_name"
if [ "$1" = "--drop" ]
then
db_name="$2"
echo "Dropping database $db_name..."
dropdb "$db_name"
else
db_name="$1"
fi
# Le répertoire de ce script:
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"

View File

@ -5,60 +5,59 @@
Création des départements, formations, semestres, étudiants, groupes...
utilisation:
1) modifier le .env pour indiquer
SCODOC_DATABASE_URI="postgresql:///SCO_TEST_API"
1) modifier /opt/scodoc/.env pour indiquer
FLASK_ENV=test_api
FLASK_DEBUG=1
2) En tant qu'utilisateur scodoc, lancer:
tools/create_database.sh SCO_TEST_API
tools/create_database.sh SCODOC_TEST_API
flask db upgrade
flask sco-db-init --erase
flask init-test-database
flask user-role -a Admin -d TAPI test
flask user-password test
flask create-role APIUserViewer
flask edit-role APIUserViewer -a APIView
flask user-role test -a APIUserViewer
3) relancer ScoDoc:
flask run --host 0.0.0.0
4) lancer client de test (ou vérifier dans le navigateur)
4) lancer client de test
"""
import datetime
import random
import sys
random.seed(12345678) # tests reproductibles
from flask_login import login_user
from app import auth
from app.auth.models import Role, User
from app import models
from app.models import Departement, Formation, FormSemestre
from app import db
from app.scodoc import (
sco_cache,
sco_evaluation_db,
sco_formations,
sco_formsemestre,
sco_formsemestre_inscriptions,
sco_groups,
)
from app.scodoc.sco_permissions import Permission
from tools.fakeportal.gen_nomprenoms import nomprenom
random.seed(12345678) # tests reproductibles
# La formation à utiliser:
FORMATION_XML_FILENAME = "tests/ressources/formations/scodoc_formation_RT_BUT_RT_v1.xml"
def init_departement(acronym):
def init_departement(acronym: str) -> Departement:
"Create dept, and switch context into it."
import app as mapp
dept = models.Departement(acronym=acronym)
dept = Departement(acronym=acronym)
db.session.add(dept)
mapp.set_sco_dept(acronym)
db.session.commit()
return dept
def import_formation() -> models.Formation:
def import_formation() -> Formation:
"""Import formation from XML.
Returns formation_id
"""
@ -66,20 +65,37 @@ def import_formation() -> models.Formation:
doc = f.read()
# --- Création de la formation
f = sco_formations.formation_import_xml(doc)
return models.Formation.query.get(f[0])
return Formation.query.get(f[0])
def create_user(dept):
"""créé les utilisaterurs nécessaires aux tests"""
user = auth.models.User(
user_name="test", nom="Doe", prenom="John", dept=dept.acronym
)
def create_users(dept: Departement) -> tuple:
"""créé les utilisateurs nécessaires aux tests"""
# Un utilisateur "test" (passwd test) pouvant lire l'API
user = User(user_name="test", nom="Doe", prenom="John", dept=dept.acronym)
user.set_password("test")
db.session.add(user)
# Le rôle standard LecteurAPI existe déjà
role = Role.query.filter_by(name="LecteurAPI").first()
if role is None:
print("Erreur: rôle LecteurAPI non existant")
sys.exit(1)
perm_api_view = Permission.get_by_name("APIView")
role.add_permission(perm_api_view)
db.session.add(role)
user.add_role(role, None)
# Un utilisateur "other" n'ayant aucune permission sur l'API
other = User(user_name="other", nom="Sans", prenom="Permission", dept=dept.acronym)
other.set_password("other")
db.session.add(other)
db.session.commit()
return user
return user, other
def create_fake_etud(dept):
def create_fake_etud(dept: Departement) -> models.Identite:
"""Créé un faux étudiant et l'insère dans la base"""
civilite = random.choice(("M", "F", "X"))
nom, prenom = nomprenom(civilite)
@ -100,14 +116,18 @@ def create_fake_etud(dept):
return etud
def create_etuds(dept, nb=16):
def create_etuds(dept: Departement, nb=16) -> list:
"create nb etuds"
return [create_fake_etud(dept) for _ in range(nb)]
def create_formsemestre(formation, user, semestre_idx=1):
"""Create formsemestre and moduleimpls"""
formsemestre = models.FormSemestre(
def create_formsemestre(
formation: Formation, responsable: User, semestre_idx=1
) -> FormSemestre:
"""Create formsemestre and moduleimpls
responsable: resp. du formsemestre
"""
formsemestre = FormSemestre(
dept_id=formation.dept_id,
semestre_id=semestre_idx,
titre="Semestre test",
@ -121,7 +141,9 @@ def create_formsemestre(formation, user, semestre_idx=1):
# Crée un modulimpl par module de ce semestre:
for module in formation.modules.filter_by(semestre_id=semestre_idx):
modimpl = models.ModuleImpl(
module_id=module.id, formsemestre_id=formsemestre.id, responsable_id=user.id
module_id=module.id,
formsemestre_id=formsemestre.id,
responsable_id=responsable.id,
)
db.session.add(modimpl)
db.session.commit()
@ -132,7 +154,7 @@ def create_formsemestre(formation, user, semestre_idx=1):
return formsemestre
def inscrit_etudiants(etuds, formsemestre):
def inscrit_etudiants(etuds: list, formsemestre: FormSemestre):
"""Inscrit les etudiants aux semestres et à tous ses modules"""
for etud in etuds:
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
@ -144,13 +166,37 @@ def inscrit_etudiants(etuds, formsemestre):
)
def init_test_database():
def create_evaluations(formsemestre: FormSemestre):
"creation d'une evaluation dans cahque modimpl du semestre"
for modimpl in formsemestre.modimpls:
args = {
"moduleimpl_id": modimpl.id,
"jour": None,
"heure_debut": "8h00",
"heure_fin": "9h00",
"description": None,
"note_max": 20,
"coefficient": 1.0,
"visibulletin": True,
"publish_incomplete": True,
"evaluation_type": None,
"numero": None,
}
evaluation_id = sco_evaluation_db.do_evaluation_create(**args)
def init_test_database():
"""Appelé par la commande `flask init-test-database`
Création d'un département et de son contenu pour les tests
"""
dept = init_departement("TAPI")
user = create_user(dept)
user_lecteur, user_autre = create_users(dept)
with sco_cache.DeferredSemCacheManager():
etuds = create_etuds(dept)
formation = import_formation()
formsemestre = create_formsemestre(formation, user)
formsemestre = create_formsemestre(formation, user_lecteur)
create_evaluations(formsemestre)
inscrit_etudiants(etuds, formsemestre)
# à compléter
# - groupes