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 tokens
from app.api import sco_api
from app.api import test_api
from app.api import departements from app.api import departements
from app.api import etudiants from app.api import etudiants
from app.api import formations from app.api import formations

View File

@ -4,7 +4,7 @@ from flask import jsonify
from app.api import bp from app.api import bp
from app.api.errors import error_response 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.api.tools import get_etu_from_etudid_or_nip_or_ine
from app.scodoc import notesdb as ndb 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/etudid/<int:etudid>", methods=["GET"])
@bp.route("/absences/nip/<int:nip>", methods=["GET"]) @bp.route("/absences/nip/<int:nip>", methods=["GET"])
@bp.route("/absences/ine/<int:ine>", methods=["GET"]) @bp.route("/absences/ine/<int:ine>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def absences(etudid: int = None, nip: int = None, ine: int = None): 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/etudid/<int:etudid>/just", methods=["GET"])
@bp.route("/absences/nip/<int:nip>/just", methods=["GET"]) @bp.route("/absences/nip/<int:nip>/just", methods=["GET"])
@bp.route("/absences/ine/<int:ine>/just", methods=["GET"]) @bp.route("/absences/ine/<int:ine>/just", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def absences_just(etudid: int = None, nip: int = None, ine: int = None): 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", "jour": "Fri, 15 Apr 2022 00:00:00 GMT",
"matin": false, "matin": false,
"estabs": true, "estabs": true,
"estjust": false, "estjust": true,
"description": "", "description": "",
"begin": "2022-04-15 12:00:00", "begin": "2022-04-15 12:00:00",
"end": "2022-04-15 17:59:59" "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 # Récupération des absences justifiées de l'étudiant
absences = sco_abs.list_abs_date(etudid) abs_just = [
for absence in [absence for absence in absences if absence["estjust"]]: absence for absence in sco_abs.list_abs_date(etudid) if absence["estjust"]
]
for absence in abs_just:
absence["jour"] = absence["jour"].isoformat() absence["jour"] = absence["jour"].isoformat()
return jsonify(absences) return jsonify(abs_just)
@bp.route( # XXX TODO INACHEVEE
"/absences/abs_group_etat/?group_id=<int:group_id>&date_debut=date_debut&date_fin=date_fin", # @bp.route(
methods=["GET"], # "/absences/abs_group_etat/<int:group_id>",
) # methods=["GET"],
@token_permission_required(Permission.APIView) # )
def abs_groupe_etat( # XXX A REVOIR XXX # @bp.route(
group_id: int, date_debut, date_fin, with_boursier=True, format="html" # "/absences/abs_group_etat/group_id/<int:group_id>/date_debut/<string:date_debut>/date_fin/<string:date_fin>",
): # methods=["GET"],
""" # )
Retoune la liste des absences d'un ou plusieurs groupes entre deux dates # @token_auth.login_required
""" # @token_permission_required(Permission.APIView)
# Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date() # 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: # # Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date()
# 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"
)
data = [] # try:
# Filtre entre les deux dates renseignées # # Utilisation de la fonction get_group_members
for member in members: # members = get_group_members(group_id)
abs = sco_abs.list_abs_date(member.id, date_debut, date_fin) # except ValueError:
data.append(abs) # 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) # data = []
return error_response(501, message="Not implemented") # # 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 abort
from flask import g from flask import g
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth
from app import log
from app.auth.models import User from app.auth.models import User
from app.api.errors import error_response from app.api.errors import error_response
@ -39,19 +41,23 @@ token_auth = HTTPTokenAuth()
@basic_auth.verify_password @basic_auth.verify_password
def verify_password(username, password): def verify_password(username, password):
"Verify password for this user"
user = User.query.filter_by(user_name=username).first() user = User.query.filter_by(user_name=username).first()
if user and user.check_password(password): if user and user.check_password(password):
g.current_user = user g.current_user = user
# note: est aussi basic_auth.current_user()
return user return user
@basic_auth.error_handler @basic_auth.error_handler
def basic_auth_error(status): def basic_auth_error(status):
"error response (401 for invalid auth.)"
return error_response(status) return error_response(status)
@token_auth.verify_token @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 user = User.check_token(token) if token else None
g.current_user = user g.current_user = user
return user return user
@ -59,6 +65,7 @@ def verify_token(token):
@token_auth.error_handler @token_auth.error_handler
def token_auth_error(status): def token_auth_error(status):
"rréponse en cas d'erreur d'auth."
return error_response(status) return error_response(status)
@ -68,16 +75,22 @@ def get_user_roles(user):
def token_permission_required(permission): def token_permission_required(permission):
"Décorateur pour les fontions de l'API ScoDoc"
def decorator(f): def decorator(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
scodoc_dept = getattr(g, "scodoc_dept", None) # token_auth.login_required()
if hasattr(g, "current_user") and not g.current_user.has_permission( current_user = basic_auth.current_user()
permission, scodoc_dept 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) abort(403)
return f(*args, **kwargs) return f(*args, **kwargs)
return decorated_function # login_required(decorated_function) # return decorated_function(token_auth.login_required())
return decorated_function
return decorator return decorator

View File

@ -1,55 +1,66 @@
############################################### Departements ########################################################## ############################################### 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 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) @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 : Exemple de résultat :
[
{ {
"id": 1, "id": 1,
"acronym": "TAPI", "acronym": "TAPI",
"description": null, "description": null,
"visible": true, "visible": true,
"date_creation": "Fri, 15 Apr 2022 12:19:28 GMT" "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 dept = get_departement(dept_ident)
depts = models.Departement.query.all() return jsonify(dept.to_dict())
# Mise en place de la liste avec tous les départements
data = [d.to_dict() for d in depts]
return jsonify(data)
@bp.route("/departements/<string:dept>/etudiants/liste", methods=["GET"]) @bp.route("/departements", methods=["GET"])
@bp.route( @token_auth.login_required
"/departements/<string:dept>/etudiants/liste/<int:formsemestre_id>", methods=["GET"]
)
@token_permission_required(Permission.APIView) @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 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 : Exemple de résultat :
[ [
{ {
"civilite": "X", "civilite": "M",
"code_ine": null, "ine": "7899X61616",
"code_nip": null, "nip": "F6777H88",
"date_naissance": null, "date_naissance": null,
"email": null, "email": "toto@toto.fr",
"emailperso": null, "emailperso": null,
"etudid": 18, "etudid": 18,
"nom": "MOREL", "nom": "MOREL",
"prenom": "JACQUES" "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é # Le département, spécifié par un id ou un acronyme
if formsemestre_id is not None: dept = get_departement(dept_ident)
# 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
# Si le formsemestre_id n'a pas été renseigné return jsonify([etud.to_dict_short() for etud in dept.etudiants])
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)
@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) @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é 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 # Le département, spécifié par un id ou un acronyme
dept = models.Departement.query.filter_by(acronym=dept).first_or_404() dept = get_departement(dept_ident)
# Récupération des semestres suivant id_dept # Les semestres en cours de ce département
semestres = models.FormSemestre.query.filter_by(dept_id=dept.id, etat=True) formsemestres = models.FormSemestre.query.filter(
FormSemestre.dept_id == dept.id,
FormSemestre.date_debut <= app.db.func.now(),
FormSemestre.date_fin >= app.db.func.now(),
)
# Mise en forme des données return jsonify([d.to_dict() for d in formsemestres])
data = [d.to_dict() for d in semestres]
return jsonify(data)
@bp.route(
"/departements/<string:dept>/formations/<int:formation_id>/referentiel_competences",
methods=["GET"],
)
@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

View File

@ -6,7 +6,7 @@ import app
from app import models from app import models
from app.api import bp from app.api import bp
from app.api.errors import error_response 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.api.tools import get_etu_from_etudid_or_nip_or_ine
from app.models import FormSemestreInscription, FormSemestre, Identite from app.models import FormSemestreInscription, FormSemestre, Identite
from app.scodoc import sco_bulletins 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", defaults={"long": False})
@bp.route("/etudiants/courant/long", defaults={"long": True}) @bp.route("/etudiants/courant/long", defaults={"long": True})
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def etudiants_courant(long=False): 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 : Exemple de résultat :
{ [
{ {
"civilite": "X", "id": 1,
"code_ine": null, "nip": 1,
"code_nip": null,
"date_naissance": null,
"email": null,
"emailperso": null,
"etudid": 18,
"nom": "MOREL", "nom": "MOREL",
"prenom": "JACQUES" "prenom": "JACQUES",
},
{
"civilite": "X", "civilite": "X",
"code_ine": null, },
"code_nip": null, {
"date_naissance": null, "id": 2,
"email": null, "nip": 2,
"emailperso": null, "nom": "GILLES",
"etudid": 19, "prenom": "MAXIME",
"nom": "FOURNIER", "civilite": "X",
"prenom": "ANNE" },
}, ...
... ]
}
""" """
# Récupération de tous les étudiants
etuds = Identite.query.filter( etuds = Identite.query.filter(
Identite.id == FormSemestreInscription.etudid, Identite.id == FormSemestreInscription.etudid,
FormSemestreInscription.formsemestre_id == FormSemestre.id, 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] data = [etud.to_dict_bul(include_urls=False) for etud in etuds]
else: else:
data = [etud.to_dict_short() for etud in etuds] data = [etud.to_dict_short() for etud in etuds]
return jsonify(data) return jsonify(data)
@bp.route("/etudiant/etudid/<int:etudid>", methods=["GET"]) @bp.route("/etudiant/etudid/<int:etudid>", methods=["GET"])
@bp.route("/etudiant/nip/<int:nip>", methods=["GET"]) @bp.route("/etudiant/nip/<int:nip>", methods=["GET"])
@bp.route("/etudiant/ine/<int:ine>", methods=["GET"]) @bp.route("/etudiant/ine/<int:ine>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def etudiant(etudid: int = None, nip: int = None, ine: int = None): def etudiant(etudid: int = None, nip: int = None, ine: int = None):
""" """
@ -76,17 +68,32 @@ def etudiant(etudid: int = None, nip: int = None, ine: int = None):
ine : le code ine d'un étudiant ine : le code ine d'un étudiant
Exemple de résultat : Exemple de résultat :
{ {
"civilite": "X", "civilite": "X",
"code_ine": null, "code_ine": "1",
"code_nip": null, "code_nip": "1",
"date_naissance": null, "date_naissance": "",
"email": null, "email": "SACHA.COSTA@example.com",
"emailperso": null, "emailperso": "",
"etudid": 18, "etudid": 1,
"nom": "MOREL", "nom": "COSTA",
"prenom": "JACQUES" "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 # Récupération de l'étudiant
etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine) etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
@ -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/etudid/<int:etudid>/formsemestres")
@bp.route("/etudiant/nip/<int:nip>/formsemestres") @bp.route("/etudiant/nip/<int:nip>/formsemestres")
@bp.route("/etudiant/ine/<int:ine>/formsemestres") @bp.route("/etudiant/ine/<int:ine>/formsemestres")
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None): def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None):
""" """
@ -110,38 +118,36 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
ine : le code ine d'un étudiant ine : le code ine d'un étudiant
Exemple de résultat : Exemple de résultat :
[ [
{ {
"titre": "master machine info", "date_fin": "31/08/2022",
"gestion_semestrielle": false, "resp_can_edit": false,
"date_debut": "01/09/2021", "dept_id": 1,
"bul_bgcolor": null, "etat": true,
"date_fin": "15/12/2022", "resp_can_change_ens": true,
"resp_can_edit": false, "id": 1,
"dept_id": 1, "modalite": "FI",
"etat": true, "ens_can_edit_eval": false,
"resp_can_change_ens": false, "formation_id": 1,
"id": 1, "gestion_compensation": false,
"modalite": "FI", "elt_sem_apo": null,
"ens_can_edit_eval": false, "semestre_id": 1,
"formation_id": 1, "bul_hide_xml": false,
"gestion_compensation": false, "elt_annee_apo": null,
"elt_sem_apo": null, "titre": "Semestre test",
"semestre_id": 1, "block_moyennes": false,
"bul_hide_xml": false, "scodoc7_id": null,
"elt_annee_apo": null, "date_debut": "01/09/2021",
"block_moyennes": false, "gestion_semestrielle": false,
"formsemestre_id": 1, "bul_bgcolor": "white",
"titre_num": "master machine info semestre 1", "formsemestre_id": 1,
"date_debut_iso": "2021-09-01", "titre_num": "Semestre test semestre 1",
"date_fin_iso": "2022-12-15", "date_debut_iso": "2021-09-01",
"responsables": [ "date_fin_iso": "2022-08-31",
3, "responsables": []
2 },
] ...
}, ]
...
]
""" """
# Récupération de l'étudiant # Récupération de l'étudiant
etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine) etud = get_etu_from_etudid_or_nip_or_ine(etudid, nip, ine)
@ -157,18 +163,41 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
@bp.route( @bp.route(
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin", "/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin",
methods=["GET"], methods=["GET"],
defaults={"version": "long"},
) )
@bp.route( @bp.route(
"/etudiant/nip/<int:nip>/formsemestre/<int:formsemestre_id>/bulletin", "/etudiant/nip/<int:nip>/formsemestre/<int:formsemestre_id>/bulletin",
methods=["GET"], methods=["GET"],
defaults={"version": "long"},
) )
@bp.route( @bp.route(
"/etudiant/ine/<int:ine>/formsemestre/<int:formsemestre_id>/bulletin", "/etudiant/ine/<int:ine>/formsemestre/<int:formsemestre_id>/bulletin",
methods=["GET"], 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) @token_permission_required(Permission.APIView)
def etudiant_bulletin_semestre( 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é 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", "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( @bp.route(
"/etudiant/etudid/<int:etudid>/semestre/<int:formsemestre_id>/groups", "/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/groups",
methods=["GET"], methods=["GET"],
) )
@bp.route( @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( @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) @token_permission_required(Permission.APIView)
def etudiant_groups( def etudiant_groups(
formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None 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 formsemestre_id : l'id d'un formsemestre
etudid : l'etudid d'un étudiant etudid : l'etudid d'un étudiant

View File

@ -5,13 +5,14 @@ import app
from app import models from app import models
from app.api import bp 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.api.errors import error_response
from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
@bp.route("/evaluations/<int:moduleimpl_id>", methods=["GET"]) @bp.route("/evaluations/<int:moduleimpl_id>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def evaluations(moduleimpl_id: int): def evaluations(moduleimpl_id: int):
""" """
@ -53,7 +54,8 @@ def evaluations(moduleimpl_id: int):
return jsonify(data) 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) @token_permission_required(Permission.APIView)
def evaluation_notes(evaluation_id: int): def evaluation_notes(evaluation_id: int):
""" """
@ -62,50 +64,43 @@ def evaluation_notes(evaluation_id: int):
evaluation_id : l'id d'une évaluation evaluation_id : l'id d'une évaluation
Exemple de résultat : Exemple de résultat :
{ {
"1": { "1": {
"id": 1, "id": 1,
"etudid": 10, "etudid": 10,
"evaluation_id": 1, "evaluation_id": 1,
"value": 15.0, "value": 15.0,
"comment": "", "comment": "",
"date": "Wed, 20 Apr 2022 06:49:05 GMT", "date": "Wed, 20 Apr 2022 06:49:05 GMT",
"uid": 2 "uid": 2
}, },
"2": { "2": {
"id": 2, "id": 2,
"etudid": 1, "etudid": 1,
"evaluation_id": 1, "evaluation_id": 1,
"value": 12.0, "value": 12.0,
"comment": "", "comment": "",
"date": "Wed, 20 Apr 2022 06:49:06 GMT", "date": "Wed, 20 Apr 2022 06:49:06 GMT",
"uid": 2 "uid": 2
}, },
... ...
} }
""" """
# Fonction utilisée : app.scodoc.sco_evaluation_db.do_evaluation_get_all_notes() # 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() evaluation = models.Evaluation.query.filter_by(id=evaluation_id).first_or_404()
dept = models.Departement.query.filter_by(
moduleimpl = models.ModuleImpl.query.filter_by(id=eval.moduleimpl_id).first_or_404() id=evaluation.moduleimpl.formsemestre.dept_id
).first()
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()
app.set_sco_dept(dept.acronym) app.set_sco_dept(dept.acronym)
try: try:
# Utilisation de la fonction do_evaluation_get_all_notes # Utilisation de la fonction do_evaluation_get_all_notes
data = do_evaluation_get_all_notes(evaluation_id) data = do_evaluation_get_all_notes(evaluation_id)
except AttributeError: except AttributeError: # ???
return error_response( return error_response(
409, 409,
message="La requête ne peut être traitée en létat actuel. \n" message="La requête ne peut être traitée en létat actuel.",
"Veillez vérifier la conformité du 'evaluation_id'",
) )
return jsonify(data) return jsonify(data)

View File

@ -1,51 +1,38 @@
##############################################" Formations ############################################################ ##############################################" Formations ############################################################
from flask import jsonify from flask import jsonify
import app
from app import models from app import models
from app.api import bp from app.api import bp
from app.api.errors import error_response 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_formations import formation_export from app.models.formations import Formation
from app.scodoc import sco_formations
from app.scodoc.sco_permissions import Permission 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) @token_permission_required(Permission.APIView)
def formations(): def formations_ids():
""" """
Retourne la liste des formations Retourne la liste de toutes les formations (tous départements)
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
},
...
]
Exemple de résultat : [ 17, 99, 32 ]
""" """
# Récupération de toutes les formations # Récupération de toutes les formations
list_formations = models.Formation.query.all() list_formations = models.Formation.query.all()
# Mise en forme des données # 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) 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) @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é 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 # 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 # Mise en forme des données
data = forma.to_dict() data = formation.to_dict()
return jsonify(data) 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) @token_permission_required(Permission.APIView)
def formation_export_by_formation_id(formation_id: int, export_ids=False): 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: try:
# Utilisation de la fonction formation_export # Utilisation de la fonction formation_export
data = formation_export(formation_id) data = sco_formations.formation_export(formation_id, export_ids)
except ValueError: except ValueError:
return error_response( return error_response(
409, 409,
@ -188,73 +185,64 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False):
return jsonify(data) 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) @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 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 modimpl = models.ModuleImpl.query.filter_by(id=moduleimpl_id).first_or_404()
list_moduleimpls = models.ModuleImpl.query.filter_by(id=moduleimpl_id) data = modimpl.to_dict()
# Mise en forme des données
data = [moduleimpl.to_dict() for moduleimpl in list_moduleimpls]
return jsonify(data) return jsonify(data)
@bp.route( @bp.route(
"/formations/moduleimpl/formsemestre/<int:formsemestre_id>/liste", "/formation/<int:formation_id>/referentiel_competences",
methods=["GET"], methods=["GET"],
) # XXX TODO penser à changer la route sur la doc )
@token_auth.login_required
@token_permission_required(Permission.APIView) @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 return json, ou null si pas de référentiel associé.
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": []
},
...
]
""" """
formsemestre = models.FormSemestre.query.filter_by( formation = models.Formation.query.filter_by(id=formation_id).first_or_404()
id=formsemestre_id
).first_or_404()
moduleimpls = formsemestre.modimpls_sorted if formation.referentiel_competence is None:
return jsonify(None)
data = [moduleimpl.to_dict() for moduleimpl in moduleimpls] return jsonify(formation.referentiel_competence.to_dict())
return jsonify(data)

View File

@ -4,70 +4,79 @@ from flask import jsonify
import app import app
from app import models from app import models
from app.api import bp from app.api import bp
from app.api.errors import error_response from app.api.auth import token_auth, token_permission_required
from app.api.auth import token_permission_required from app.models import Departement, FormSemestre, FormSemestreEtape
from app.api.tools import get_etu_from_etudid_or_nip_or_ine
from app.models import FormSemestre, FormSemestreEtape
from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json 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_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"]) @bp.route("/formsemestre/<int:formsemestre_id>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def formsemestre(formsemestre_id: int): 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 : 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", "date_fin": "31/08/2022",
"resp_can_edit": false,
"dept_id": 1, "dept_id": 1,
"elt_annee_apo": null,
"elt_sem_apo": null,
"ens_can_edit_eval": false,
"etat": true, "etat": true,
"resp_can_change_ens": true, "formation_id": 1,
"formsemestre_id": 1,
"gestion_compensation": false,
"gestion_semestrielle": false,
"id": 1, "id": 1,
"modalite": "FI", "modalite": "FI",
"ens_can_edit_eval": false, "resp_can_change_ens": true,
"formation_id": 1, "resp_can_edit": false,
"gestion_compensation": false, "responsables": [1, 99], // uids
"elt_sem_apo": null,
"semestre_id": 1,
"bul_hide_xml": false,
"elt_annee_apo": null,
"titre": "Semestre test",
"block_moyennes": false,
"scodoc7_id": null, "scodoc7_id": null,
"date_debut": "01/09/2021", "semestre_id": 1,
"gestion_semestrielle": false, "titre_formation" : "BUT GEA",
"bul_bgcolor": "white", "titre_num": "BUT GEA semestre 1",
"formsemestre_id": 1, "titre": "BUT GEA",
"titre_num": "Semestre test semestre 1",
"date_debut_iso": "2021-09-01",
"date_fin_iso": "2022-08-31",
"responsables": []
} }
""" """
# Récupération de tous les formsemestres formsemestre: FormSemestre = models.FormSemestre.query.filter_by(
formsemetre = models.FormSemestre.query.filter_by(id=formsemestre_id).first_or_404() id=formsemestre_id
).first_or_404()
# Mise en forme des données data = formsemestre.to_dict()
data = formsemetre.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) return jsonify(data)
@bp.route("/formsemestre/apo/<string:etape_apo>", methods=["GET"]) @bp.route("/formsemestre/apo/<string:etape_apo>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def formsemestre_apo(etape_apo: str): 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( formsemestres = FormSemestre.query.filter(
FormSemestreEtape.etape_apo == etape_apo, FormSemestreEtape.etape_apo == etape_apo,
@ -77,166 +86,8 @@ def formsemestre_apo(etape_apo: str):
return jsonify([formsemestre.to_dict() for formsemestre in formsemestres]) 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"]) @bp.route("/formsemestre/<int:formsemestre_id>/bulletins", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def bulletins(formsemestre_id: int): 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( formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id id=formsemestre_id
).first_or_404() ).first_or_404()
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404() dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
app.set_sco_dept(dept.acronym) app.set_sco_dept(dept.acronym)
etuds = formsemestre.etuds
data = [] data = []
for etu in etuds: for etu in formsemestre.etuds:
bul_etu = get_formsemestre_bulletin_etud_json(formsemestre, etu) bul_etu = get_formsemestre_bulletin_etud_json(formsemestre, etu)
data.append(bul_etu.json) data.append(bul_etu.json)
return jsonify(data) 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) @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 formsemestre_id : l'id d'un formsemestre
Exemple de résultat : 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: FormSemestre = models.FormSemestre.query.filter_by(
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id id=formsemestre_id
).first_or_404() ).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) return jsonify(
{
data = formsemestre_pvjury(formsemestre_id) "ues": [ue.to_dict() for ue in ues],
"ressources": m_list[ModuleType.RESSOURCE],
# try: "saes": m_list[ModuleType.SAE],
# # Utilisation de la fonction formsemestre_pvjury "modules": m_list[ModuleType.STANDARD],
# 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)

View File

@ -1,41 +1,41 @@
#################################################### Jury ############################################################# #################################################### Jury #############################################################
from flask import jsonify # from flask import jsonify
from app import models # from app import models
from app.api import bp # from app.api import bp
from app.api.errors import error_response # 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_prepajury import feuille_preparation_jury # from app.scodoc.sco_prepajury import feuille_preparation_jury
from app.scodoc.sco_pvjury import formsemestre_pvjury # from app.scodoc.sco_pvjury import formsemestre_pvjury
@bp.route("/jury/formsemestre/<int:formsemestre_id>/preparation_jury", methods=["GET"]) # # @bp.route("/jury/formsemestre/<int:formsemestre_id>/preparation_jury", methods=["GET"])
# @token_permission_required(Permission.?) # # @token_permission_required(Permission.?)
def jury_preparation(formsemestre_id: int): # def jury_preparation(formsemestre_id: int):
""" # """
Retourne la feuille de préparation du jury # Retourne la feuille de préparation du jury
formsemestre_id : l'id d'un formsemestre # formsemestre_id : l'id d'un formsemestre
""" # """
# Fonction utilisée : app.scodoc.sco_prepajury.feuille_preparation_jury() # # Fonction utilisée : app.scodoc.sco_prepajury.feuille_preparation_jury()
# Utilisation de la fonction feuille_preparation_jury # # Utilisation de la fonction feuille_preparation_jury
prepa_jury = feuille_preparation_jury(formsemestre_id) # 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"]) # # @bp.route("/jury/formsemestre/<int:formsemestre_id>/decisions_jury", methods=["GET"])
# @token_permission_required(Permission.?) # # @token_permission_required(Permission.?)
def jury_decisions(formsemestre_id: int): # def jury_decisions(formsemestre_id: int):
""" # """
Retourne les décisions du jury suivant un formsemestre donné # Retourne les décisions du jury suivant un formsemestre donné
formsemestre_id : l'id d'un formsemestre # formsemestre_id : l'id d'un formsemestre
""" # """
# Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury() # # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury()
# Utilisation de la fonction formsemestre_pvjury # # Utilisation de la fonction formsemestre_pvjury
decision_jury = formsemestre_pvjury(formsemestre_id) # 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 import requested_format
from app.api.auth import token_auth from app.api.auth import token_auth
from app.api.errors import error_response from app.api.errors import error_response
from app.decorators import permission_required
from app.models import Departement from app.models import Departement
from app.scodoc.sco_logos import list_logos, find_logo from app.scodoc.sco_logos import list_logos, find_logo
from app.api.auth import token_auth, token_permission_required 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"]) @bp.route("/logos", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def api_get_glob_logos(): def api_get_glob_logos():
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None): 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"]) @bp.route("/logos/<string:logoname>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def api_get_glob_logo(logoname): def api_get_glob_logo(logoname):
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None): 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"]) @bp.route("/departements/<string:departement>/logos", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def api_get_local_logos(departement): def api_get_local_logos(departement):
dept_id = Departement.from_acronym(departement).id 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"]) @bp.route("/departements/<string:departement>/logos/<string:logoname>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def api_get_local_logo(departement, logoname): def api_get_local_logo(departement, logoname):
# format = requested_format("jpg", ['png', 'jpg']) XXX ? # 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 import bp
from app.api.errors import error_response 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_groups import get_group_members, setGroups, get_partitions_list
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
@bp.route("/partitions/<int:formsemestre_id>", methods=["GET"]) @bp.route("/partitions/<int:formsemestre_id>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def partition(formsemestre_id: int): 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>", methods=["GET"])
@bp.route("/partitions/groups/<int:group_id>/etat/<string:etat>", methods=["GET"]) @bp.route("/partitions/groups/<int:group_id>/etat/<string:etat>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView) @token_permission_required(Permission.APIView)
def etud_in_group(group_id: int, etat=None): def etud_in_group(group_id: int, etat=None):
""" """
@ -121,13 +123,14 @@ def etud_in_group(group_id: int, etat=None):
@bp.route( @bp.route(
"/partitions/set_groups?partition_id=<int:partition_id>&groups_lists=<int:groups_lists>&" "/partitions/set_groups/partition/<int:partition_id>/groups/<string:groups_id>/delete/<string:groups_to_delete>"
"groups_to_create=<int:groups_to_create>&groups_to_delete=<int:groups_to_delete>", "/create/<string:groups_to_create>",
methods=["POST"], methods=["POST"],
) )
@token_auth.login_required
@token_permission_required(Permission.APIEtudChangeGroups) @token_permission_required(Permission.APIEtudChangeGroups)
def set_groups( 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 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 flask import jsonify
from app import db from app import db, log
from app.api import bp from app.api import bp
from app.api.auth import basic_auth, token_auth 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"]) @bp.route("/tokens", methods=["POST"])
@basic_auth.login_required @basic_auth.login_required
def get_token(): def get_token():
"renvoie un jeton jwt pour l'utilisateur courant"
token = basic_auth.current_user().get_token() token = basic_auth.current_user().get_token()
log(f"API: giving token to {basic_auth.current_user()}")
db.session.commit() db.session.commit()
return jsonify({"token": token}) return jsonify({"token": token})
@ -15,6 +17,7 @@ def get_token():
@bp.route("/tokens", methods=["DELETE"]) @bp.route("/tokens", methods=["DELETE"])
@token_auth.login_required @token_auth.login_required
def revoke_token(): def revoke_token():
"révoque le jeton de l'utilisateur courant"
token_auth.current_user().revoke_token() token_auth.current_user().revoke_token()
db.session.commit() db.session.commit()
return "", 204 return "", 204

View File

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

View File

@ -71,7 +71,7 @@ def create_user():
flash("User {} created".format(user.user_name)) flash("User {} created".format(user.user_name))
return redirect(url_for("scodoc.index")) return redirect(url_for("scodoc.index"))
return render_template( 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: if current_user.is_authenticated:
return redirect(url_for("scodoc.index")) return redirect(url_for("scodoc.index"))
user = User.verify_reset_password_token(token) user = User.verify_reset_password_token(token)
if not user: if user is None:
return redirect(url_for("scodoc.index")) return redirect(url_for("scodoc.index"))
form = ResetPasswordForm() form = ResetPasswordForm()
if form.validate_on_submit(): 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): 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" "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 # 1er ligne titre UE
moy_ue = ue.get("moyenne") moy_ue = ue.get("moyenne")
t = { t = {
@ -206,7 +209,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
for mod_code, mod in ue["modules"].items(): for mod_code, mod in ue["modules"].items():
rows.append( rows.append(
{ {
"titre": f"{mod_code} {mod['titre']}", "titre": f"{mod_code or ''} {mod['titre'] or ''}",
} }
) )
self.evaluations_rows(rows, mod["evaluations"]) self.evaluations_rows(rows, mod["evaluations"])
@ -313,7 +316,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
"lignes des évaluations" "lignes des évaluations"
for e in evaluations: for e in evaluations:
t = { t = {
"titre": f"{e['description']}", "titre": f"{e['description'] or ''}",
"moyenne": e["note"]["value"], "moyenne": e["note"]["value"],
"_moyenne_pdf": Paragraph( "_moyenne_pdf": Paragraph(
f"""<para align=right>{e["note"]["value"]}</para>""" f"""<para align=right>{e["note"]["value"]}</para>"""

View File

@ -946,7 +946,7 @@ class BonusStBrieuc(BonusSportAdditif):
name = "bonus_iut_stbrieuc" name = "bonus_iut_stbrieuc"
displayed_name = "IUT de Saint-Brieuc" displayed_name = "IUT de Saint-Brieuc"
proportion_point = 1 / 20.0 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): def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus""" """calcul du bonus"""

View File

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

View File

@ -178,11 +178,12 @@ def evaluation_enrich_dict(e):
else: else:
e["descrheure"] = "" e["descrheure"] = ""
# matin, apresmidi: utile pour se referer aux absences: # 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 e["matin"] = 1
else: else:
e["matin"] = 0 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 e["apresmidi"] = 1
else: else:
e["apresmidi"] = 0 e["apresmidi"] = 0

View File

@ -139,6 +139,7 @@ class FormSemestre(db.Model):
else: else:
d["date_fin"] = d["date_fin_iso"] = "" d["date_fin"] = d["date_fin_iso"] = ""
d["responsables"] = [u.id for u in self.responsables] d["responsables"] = [u.id for u in self.responsables]
d["titre_formation"] = self.titre_formation()
return d return d
def get_infos_dict(self) -> dict: 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) 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: if not imputation_dept:
imputation_dept = sco_preferences.get_preference("DeptName") imputation_dept = prefs["DeptName"]
imputation_dept = imputation_dept.upper() imputation_dept = imputation_dept.upper()
parcours_name = self.formation.get_parcours().NAME parcours_name = self.formation.get_parcours().NAME
modalite = self.modalite modalite = self.modalite
@ -346,7 +348,7 @@ class FormSemestre(db.Model):
scu.annee_scolaire_debut(self.date_debut.year, self.date_debut.month) scu.annee_scolaire_debut(self.date_debut.year, self.date_debut.month)
) )
return scu.sanitize_string( 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: def titre_annee(self) -> str:
@ -358,6 +360,12 @@ class FormSemestre(db.Model):
titre_annee += "-" + str(self.date_fin.year) titre_annee += "-" + str(self.date_fin.year)
return titre_annee 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: def titre_mois(self) -> str:
"""Le titre et les dates du semestre, pour affichage dans des listes """Le titre et les dates du semestre, pour affichage dans des listes
Ex: "BUT QLIO (PN 2022) semestre 1 FI (Sept 2022 - Jan 2023)" Ex: "BUT QLIO (PN 2022) semestre 1 FI (Sept 2022 - Jan 2023)"

View File

@ -965,7 +965,7 @@ def _tables_abs_etud(
)[0] )[0]
if format == "html": if format == "html":
ex.append( 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"])} scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
">{mod["module"]["code"] or "(module sans code)"}</a>""" ">{mod["module"]["code"] or "(module sans code)"}</a>"""
) )

View File

@ -31,7 +31,6 @@
import datetime import datetime
import json import json
from app.but import bulletin_but
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
@ -92,7 +91,7 @@ def formsemestre_bulletinetud_published_dict(
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
d = {} d = {"type": "classic", "version": "0"}
if (not sem["bul_hide_xml"]) or force_publishing: if (not sem["bul_hide_xml"]) or force_publishing:
published = True 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): if getattr(g, "defer_cache_invalidation", False):
g.sem_to_invalidate.add(formsemestre_id) g.sem_to_invalidate.add(formsemestre_id)
return 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: if formsemestre_id is None:
# clear all caches # clear all caches
log("----- invalidate_formsemestre: clearing 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) SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
class DefferedSemCacheManager: class DeferredSemCacheManager:
"""Contexte pour effectuer des opérations indépendantes dans la """Contexte pour effectuer des opérations indépendantes dans la
même requete qui invalident le cache. Par exemple, quand on inscrit même requete qui invalident le cache. Par exemple, quand on inscrit
des étudiants un par un à un semestre, chaque inscription va invalider 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: if not showsemtable:
H.append( H.append(
f"""<hr> f"""<hr>
<p><a class="stdlink" href="{url_for('scolar.index_html', scodoc_dept=g.scodoc_dept, showsemtable=1) <p><a class="stdlink" href="{url_for('scolar.index_html',
}">Voir tous les semestres ({len(othersems)} verrouillés)</a> scodoc_dept=g.scodoc_dept, showsemtable=1)
}">Voir table des semestres (dont {len(othersems)}
verrouillé{'s' if len(othersems) else ''})</a>
</p>""" </p>"""
) )

View File

@ -70,7 +70,8 @@ class Permission(object):
setattr(Permission, symbol, perm) setattr(Permission, symbol, perm)
Permission.description[symbol] = description Permission.description[symbol] = description
Permission.permission_by_name[symbol] = perm 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 @staticmethod
def get_by_name(permission_name: str) -> int: def get_by_name(permission_name: str) -> int:

View File

@ -132,9 +132,12 @@ def clear_base_preferences():
g._SCO_BASE_PREFERENCES = {} # { dept_id: BasePreferences instance } g._SCO_BASE_PREFERENCES = {} # { dept_id: BasePreferences instance }
def get_base_preferences(): def get_base_preferences(dept_id: int = None):
"""Return global preferences for the current department""" """Return global preferences for the specified department
dept_id = g.scodoc_dept_id or the current departement
"""
if dept_id is None:
dept_id = g.scodoc_dept_id
if not hasattr(g, "_SCO_BASE_PREFERENCES"): if not hasattr(g, "_SCO_BASE_PREFERENCES"):
g._SCO_BASE_PREFERENCES = {} g._SCO_BASE_PREFERENCES = {}
if not dept_id in g._SCO_BASE_PREFERENCES: if not dept_id in g._SCO_BASE_PREFERENCES:
@ -142,12 +145,12 @@ def get_base_preferences():
return g._SCO_BASE_PREFERENCES[dept_id] 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. """Returns value of named preference.
All preferences have a sensible default value, so this All preferences have a sensible default value, so this
function always returns a usable value for all defined preferences names. 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): def _convert_pref_type(p, pref_spec):
@ -2145,9 +2148,9 @@ class BasePreferences(object):
class SemPreferences: class SemPreferences:
"""Preferences for a formsemestre""" """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.formsemestre_id = formsemestre_id
self.base_prefs = get_base_preferences() self.base_prefs = get_base_preferences(dept_id=dept_id)
def __getitem__(self, name): def __getitem__(self, name):
return self.base_prefs.get(self.formsemestre_id, 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}) evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals: if not evals:
raise ScoValueError("invalid evaluation_id") raise ScoValueError("invalid evaluation_id")
E = evals[0] eval_dict = evals[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=eval_dict["moduleimpl_id"])[0]
formsemestre_id = M["formsemestre_id"] formsemestre_id = M["formsemestre_id"]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0] Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"]) sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
mod_responsable = sco_users.user_info(M["responsable_id"]) mod_responsable = sco_users.user_info(M["responsable_id"])
if E["jour"]: if eval_dict["jour"]:
indication_date = ndb.DateDMYtoISO(E["jour"]) indication_date = ndb.DateDMYtoISO(eval_dict["jour"])
else: else:
indication_date = scu.sanitize_filename(E["description"])[:12] indication_date = scu.sanitize_filename(eval_dict["description"])[:12]
evalname = "%s-%s" % (Mod["code"], indication_date) eval_name = "%s-%s" % (Mod["code"], indication_date)
if E["description"]: if eval_dict["description"]:
evaltitre = "%s du %s" % (E["description"], E["jour"]) evaltitre = "%s du %s" % (eval_dict["description"], eval_dict["jour"])
else: else:
evaltitre = "évaluation du %s" % E["jour"] evaltitre = "évaluation du %s" % eval_dict["jour"]
description = "%s en %s (%s) resp. %s" % ( description = "%s en %s (%s) resp. %s" % (
evaltitre, evaltitre,
Mod["abbrev"] or "", 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 # une liste de liste de chaines: lignes de la feuille de calcul
L = [] L = []
etuds = _get_sorted_etuds(E, etudids, formsemestre_id) etuds = _get_sorted_etuds(eval_dict, etudids, formsemestre_id)
for e in etuds: for e in etuds:
etudid = e["etudid"] etudid = e["etudid"]
groups = sco_groups.get_etud_groups(etudid, formsemestre_id) 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) filename = "notes_%s_%s" % (eval_name, gr_title_filename)
xls = sco_excel.excel_feuille_saisie(E, sem["titreannee"], description, lines=L) 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 scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
# return sco_excel.send_excel_file(xls, filename) # return sco_excel.send_excel_file(xls, filename)
@ -1008,10 +1010,9 @@ def saisie_notes(evaluation_id, group_ids=[]):
return "\n".join(H) return "\n".join(H)
def _get_sorted_etuds(E, etudids, formsemestre_id): def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
notes_db = sco_evaluation_db.do_evaluation_get_all_notes( notes_db = sco_evaluation_db.do_evaluation_get_all_notes(
E["evaluation_id"] eval_dict["evaluation_id"]
) # Notes existantes ) # Notes existantes
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
etuds = [] etuds = []
@ -1028,9 +1029,9 @@ def _get_sorted_etuds(E, etudids, formsemestre_id):
e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id) e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id)
# Information sur absence (tenant compte de la demi-journée) # 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 = [] warn_abs_lst = []
if E["matin"]: if eval_dict["matin"]:
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=1) 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) nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=1)
if nbabs: if nbabs:
@ -1038,7 +1039,7 @@ def _get_sorted_etuds(E, etudids, formsemestre_id):
warn_abs_lst.append("absent justifié le matin !") warn_abs_lst.append("absent justifié le matin !")
else: else:
warn_abs_lst.append("absent le matin !") 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) 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) nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=0)
if nbabs: 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 = [nip2etudid(x) for x in a_desinscrire]
etudids_a_desinscrire += a_desinscrire_without_key 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) do_import_etuds_from_portal(sem, a_importer, etudsapo_ident)
sco_inscr_passage.do_inscrit(sem, etudids_a_inscrire) sco_inscr_passage.do_inscrit(sem, etudids_a_inscrire)
sco_inscr_passage.do_desinscrit(sem, etudids_a_desinscrire) sco_inscr_passage.do_desinscrit(sem, etudids_a_desinscrire)

View File

@ -45,7 +45,7 @@
{# Liste des permissions #} {# Liste des permissions #}
<div class="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> <ul>
{% for p in Permission.description %} {% for p in Permission.description %}
<li>{{Permission.description[p]}} : <li>{{Permission.description[p]}} :

View File

@ -871,7 +871,10 @@ def EtatAbsencesGr(
) )
nbjustifs_noabs = len( nbjustifs_noabs = len(
sco_abs.list_abs_justifs( 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'] # retrouve sem dans etud['sems']
@ -894,7 +897,7 @@ def EtatAbsencesGr(
"nbjustifs_noabs": nbjustifs_noabs, "nbjustifs_noabs": nbjustifs_noabs,
"_nomprenom_target": "CalAbs?etudid=%s" % etud["etudid"], "_nomprenom_target": "CalAbs?etudid=%s" % etud["etudid"],
"_nomprenom_td_attrs": 'id="%s" class="etudinfo"' % 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": if s["ins"]["etat"] == "D":
@ -922,9 +925,36 @@ def EtatAbsencesGr(
h = groups_infos.groups_titles h = groups_infos.groups_titles
gr_tit = p + h 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": if format == "xls" or format == "xml" or format == "json":
columns_ids = ["etudid"] + columns_ids 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( tab = GenTable(
columns_ids=columns_ids, columns_ids=columns_ids,
rows=T, rows=T,
@ -945,8 +975,9 @@ def EtatAbsencesGr(
init_qtip=True, init_qtip=True,
javascripts=["js/etud_info.js"], javascripts=["js/etud_info.js"],
), ),
html_title=html_sco_header.html_sem_header("%s" % title, with_page_header=False) html_title=html_sco_header.html_sem_header(title, with_page_header=False)
+ "<p>Période du %s au %s (nombre de <b>demi-journées</b>)<br/>" % (debut, fin), + 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" base_url="%s&formsemestre_id=%s&debut=%s&fin=%s"
% (groups_infos.base_url, formsemestre_id, debut, fin), % (groups_infos.base_url, formsemestre_id, debut, fin),
filename="etat_abs_" filename="etat_abs_"

View File

@ -314,7 +314,7 @@ def formsemestre_bulletinetud(
) )
if format == "json": if format == "json":
return sco_bulletins.get_formsemestre_bulletin_etud_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": if formsemestre.formation.is_apc() and format == "html":
return render_template( return render_template(

View File

@ -13,7 +13,7 @@ class Config:
SQLALCHEMY_DATABASE_URI = None # set in subclass SQLALCHEMY_DATABASE_URI = None # set in subclass
FLASK_ENV = 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 SQLALCHEMY_TRACK_MODIFICATIONS = False
LOG_TO_STDOUT = os.environ.get("LOG_TO_STDOUT") LOG_TO_STDOUT = os.environ.get("LOG_TO_STDOUT")
MAIL_SERVER = os.environ.get("MAIL_SERVER", "localhost") MAIL_SERVER = os.environ.get("MAIL_SERVER", "localhost")
@ -46,6 +46,7 @@ class Config:
class ProdConfig(Config): class ProdConfig(Config):
"mode production, normalement derrière nginx/gunicorn"
FLASK_ENV = "production" FLASK_ENV = "production"
DEBUG = False DEBUG = False
TESTING = False TESTING = False
@ -56,6 +57,7 @@ class ProdConfig(Config):
class DevConfig(Config): class DevConfig(Config):
"mode développement"
FLASK_ENV = "development" FLASK_ENV = "development"
DEBUG = True DEBUG = True
TESTING = False TESTING = False
@ -66,6 +68,7 @@ class DevConfig(Config):
class TestConfig(DevConfig): class TestConfig(DevConfig):
"Pour les tests unitaires"
TESTING = True TESTING = True
DEBUG = True DEBUG = True
SQLALCHEMY_DATABASE_URI = ( SQLALCHEMY_DATABASE_URI = (
@ -76,6 +79,19 @@ class TestConfig(DevConfig):
SECRET_KEY = os.environ.get("TEST_SECRET_KEY") or "c7ecff5db1594c208f573ff30e0f6bca" 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") mode = os.environ.get("FLASK_ENV", "production")
if mode == "production": if mode == "production":
RunningConfig = ProdConfig RunningConfig = ProdConfig
@ -83,3 +99,5 @@ elif mode == "development":
RunningConfig = DevConfig RunningConfig = DevConfig
elif mode == "test": elif mode == "test":
RunningConfig = TestConfig RunningConfig = TestConfig
elif mode == "test_api":
RunningConfig = TestAPIConfig

View File

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

View File

@ -496,12 +496,11 @@ def clear_cache(sanitize): # clear-cache
@app.cli.command() @app.cli.command()
def init_test_database(): def init_test_database(): # init-test-database
"""Initialise les objets en base pour les tests API """Initialise les objets en base pour les tests API
(à appliquer sur SCODOC_TEST ou SCODOC_DEV) (à appliquer sur SCODOC_TEST ou SCODOC_DEV)
""" """
click.echo("Initialisation base de test API...") click.echo("Initialisation base de test API...")
# import app as mapp # le package app
ctx = app.test_request_context() ctx = app.test_request_context()
ctx.push() 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). (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 from dotenv import load_dotenv
import os import os
import pdb
import requests import requests
import urllib3 import urllib3
from pprint import pprint as pp from pprint import pprint as pp
@ -29,16 +28,16 @@ from pprint import pprint as pp
# --- Lecture configuration (variables d'env ou .env) # --- Lecture configuration (variables d'env ou .env)
BASEDIR = os.path.abspath(os.path.dirname(__file__)) BASEDIR = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(BASEDIR, ".env")) 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_URL = os.environ["SCODOC_URL"]
SCODOC_DEPT = os.environ["SCODOC_DEPT"] API_URL = SCODOC_URL + "/ScoDoc/api"
DEPT_URL = SCODOC_URL + "/ScoDoc/" + SCODOC_DEPT + "/Scolarite/"
SCODOC_USER = os.environ["SCODOC_USER"] SCODOC_USER = os.environ["SCODOC_USER"]
SCODOC_PASSWORD = os.environ["SCODOC_PASSWD"] SCODOC_PASSWORD = os.environ["SCODOC_PASSWD"]
print(f"SCODOC_URL={SCODOC_URL}") print(f"SCODOC_URL={SCODOC_URL}")
print(f"API URL={API_URL}")
# --- # ---
if not CHECK_CERTIFICATE: if not CHK_CERT:
urllib3.disable_warnings() urllib3.disable_warnings()
@ -48,9 +47,7 @@ class ScoError(Exception):
def GET(path: str, headers={}, errmsg=None): def GET(path: str, headers={}, errmsg=None):
"""Get and returns as JSON""" """Get and returns as JSON"""
r = requests.get( r = requests.get(API_URL + "/" + path, headers=headers or HEADERS, verify=CHK_CERT)
DEPT_URL + "/" + path, headers=headers or HEADERS, verify=CHECK_CERTIFICATE
)
if r.status_code != 200: if r.status_code != 200:
raise ScoError(errmsg or "erreur !") raise ScoError(errmsg or "erreur !")
return r.json() # decode la reponse JSON 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): def POST(s, path: str, data: dict, errmsg=None):
"""Post""" """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: if r.status_code != 200:
raise ScoError(errmsg or "erreur !") raise ScoError(errmsg or "erreur !")
return r.text return r.text
# --- Obtention du jeton (token) # --- Obtention du jeton (token)
r = requests.post( r = requests.post(API_URL + "/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD))
SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD)
)
assert r.status_code == 200 assert r.status_code == 200
token = r.json()["token"] token = r.json()["token"]
HEADERS = {"Authorization": f"Bearer {token}"} HEADERS = {"Authorization": f"Bearer {token}"}
r = requests.get( r = requests.get(API_URL + "/departements", headers=HEADERS, verify=CHK_CERT)
SCODOC_URL + "/ScoDoc/api/list_depts",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
if r.status_code != 200: if r.status_code != 200:
raise ScoError("erreur de connexion: vérifier adresse et identifiants") raise ScoError("erreur de connexion: vérifier adresse et identifiants")
pp(r.json()) pp(r.json())
# Liste des tous les étudiants en cours (de tous les depts) # Liste de tous les étudiants en cours (de tous les depts)
r = requests.get( r = requests.get(API_URL + "/etudiants/courant", headers=HEADERS, verify=CHK_CERT)
SCODOC_URL + "/ScoDoc/api/etudiants/courant",
headers=HEADERS,
verify=CHECK_CERTIFICATE,
)
if r.status_code != 200: if r.status_code != 200:
raise ScoError("erreur de connexion: vérifier adresse et identifiants") 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: # # --- Recupere la liste de tous les semestres:
# sems = GET(s, "Notes/formsemestre_list?format=json", "Aucun semestre !") # sems = GET(s, "Notes/formsemestre_list?format=json", "Aucun semestre !")
@ -146,15 +144,3 @@ if r.status_code != 200:
# print( # print(
# f"Pour vérifier, aller sur: {DEPT_URL}/Notes/moduleimpl_status?moduleimpl_id={mod['moduleimpl_id']}", # 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 -*- # -*- coding: utf-8 -*-
"""Test Logos """Test API
Utilisation : Utilisation :
créer les variables d'environnement: (indiquer les valeurs créer les variables d'environnement: (indiquer les valeurs
pour le serveur ScoDoc que vous voulez interroger) pour le serveur ScoDoc que vous voulez interroger)
export SCODOC_URL="https://scodoc.xxx.net/" export SCODOC_URL="https://scodoc.xxx.net/"
export SCODOC_USER="xxx" export API_USER="xxx"
export SCODOC_PASSWD="xxx" export SCODOC_PASSWD="xxx"
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
@ -15,23 +15,25 @@ Utilisation :
""" """
import os import os
import requests 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" @pytest.fixture
SCODOC_PASSWORD = "test" def api_headers() -> dict:
SCODOC_URL = "http://192.168.1.12:5000"
CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
def get_token():
""" """
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( r0 = requests.post(API_URL + "/tokens", auth=(API_USER, API_PASSWORD))
SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD)
)
token = r0.json()["token"] token = r0.json()["token"]
return {"Authorization": f"Bearer {token}"} return {"Authorization": f"Bearer {token}"}
HEADERS = get_token()

View File

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

View File

@ -19,147 +19,140 @@ Utilisation :
import requests 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(api_headers):
def test_departements(): #XXX TODO pour Seb """ "
Routes: /departements_ids, /departement, /departement/<string:dept>/formsemestres_ids
"""
# --- Liste des ids
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements", API_URL + "/departements_ids",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 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)
dept_id = departements_ids[0]
# liste_etudiants # --- Infos sur un département, accès par id
def test_liste_etudiants(): #XXX TODO pour Seb
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste", f"{API_URL}/departement/{dept_id}",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 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( r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste/1", f"{API_URL}/departement/{dept_a['acronym']}",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 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 # 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( r = requests.get(
SCODOC_URL + "/ScoDoc/api/departements/TAPI/semestres_courants", f"{API_URL}/departement/{dept_id}",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 1 dept = r.json()
assert dept["id"] == dept_id
# Accès via acronyme
# referenciel_competences
def test_referenciel_competences():
r = requests.get( r = requests.get(
SCODOC_URL f"{API_URL}/departement/{dept['acronym']}/formsemestres_courants",
+ "/ScoDoc/api/departements/TAPI/formations/1/referentiel_competences", headers=api_headers,
headers=HEADERS, verify=CHECK_CERTIFICATE,
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,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200 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] # accès via dept_id
ressource = r.json()["ressources"][0] r = requests.get(
sae = r.json()["saes"][0] f"{API_URL}/departement/{dept['id']}/formsemestres_courants",
headers=api_headers,
fields_OK = True verify=CHECK_CERTIFICATE,
)
# Vérifie si tous les champs sont bien présents assert r.status_code == 200
for field in ue: result_b = r.json()
if field not in ue_fields: assert result_a == result_b
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

View File

@ -16,392 +16,176 @@ Utilisation :
Lancer : Lancer :
pytest tests/api/test_api_etudiants.py pytest tests/api/test_api_etudiants.py
""" """
from random import randint
import requests 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_courant(api_headers):
# def test_etudiants(): """
# Route: /etudiants/courant
# fields = [ """
# "civilite", fields = {"id", "nip", "ine", "nom", "nom_usuel", "prenom", "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",
]
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiants/courant", API_URL + "/etudiants/courant",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 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 etud = etudiants[-1]
etu = r.json()[randint(0, len(r.json())) - 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 etud = etudiants[-1]
for field in etu: assert verify_fields(etud, ETUD_FIELDS) is True
if field not in fields:
fields_OK = False
assert fields_OK is True
# etudiant def test_etudiant(api_headers):
def test_etudiant(): # XXX TODO pour Seb """
Route:
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",
]
######### Test etudid ######### ######### Test etudid #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1", API_URL + "/etudiant/etudid/1",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 24 etud = r.json()
etu = r.json() assert verify_fields(etud, ETUD_FIELDS) is True
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
######### Test code nip ######### ######### Test code nip #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1", API_URL + "/etudiant/nip/1",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 24 etud = r.json()
fields_ok = verify_fields(etud, ETUD_FIELDS)
etu = r.json() assert fields_ok is True
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
######### Test code ine ######### ######### Test code ine #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1", API_URL + "/etudiant/ine/1",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 24 etud = r.json()
assert len(etud) == 24
etu = r.json() fields_ok = verify_fields(etud, ETUD_FIELDS)
assert fields_ok is True
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
# etudiant_formsemestres def test_etudiant_formsemestres(api_headers):
def test_etudiant_formsemestres(): # XXX TODO pour Seb """
Route: /etudiant/etudid/<etudid:int>/formsemestres
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",
]
######### Test etudid ######### ######### Test etudid #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/formsemestres", API_URL + "/etudiant/etudid/1/formsemestres",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 1 formsemestres = r.json()
assert len(formsemestres) == 1
formsemestre = r.json()[0] formsemestre = formsemestres[0]
assert verify_fields(formsemestre, FSEM_FIELDS) is True
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
######### Test code nip ######### ######### Test code nip #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/formsemestres", API_URL + "/etudiant/nip/1/formsemestres",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 1 formsemestres = r.json()
assert len(formsemestres) == 1
formsemestre = r.json()[0] formsemestre = formsemestres[0]
assert verify_fields(formsemestre, FSEM_FIELDS) is True
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
######### Test code ine ######### ######### Test code ine #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/formsemestres", API_URL + "/etudiant/ine/1/formsemestres",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 1 formsemestres = r.json()
assert len(formsemestres) == 1
formsemestre = r.json()[0] formsemestre = formsemestres[0]
assert verify_fields(formsemestre, FSEM_FIELDS) is True
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
# etudiant_bulletin_semestre def test_etudiant_bulletin_semestre(api_headers):
def test_etudiant_bulletin_semestre(): """
Route: /etudiant/etudid/<etudid>/formsemestre/<formsemestre_id>/bulletin
# fields = [ """
# "etudid",
# "formsemestre_id",
# "date",
# "publie",
# "etapes",
# "etudiant",
# "note",
# "rang",
# "rang_group",
# "note_max",
# "bonus_sport_culture",
# "ue",
# "ue_capitalisee",
# "absences",
# "appreciation",
# ]
######### Test etudid ######### ######### Test etudid #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/formsemestre/1/bulletin", API_URL + "/etudiant/etudid/1/formsemestre/1/bulletin",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 13 bul = r.json()
assert len(bul) == 13 # HARDCODED
# 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
######### Test code nip ######### ######### Test code nip #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/formsemestre/1/bulletin", API_URL + "/etudiant/nip/1/formsemestre/1/bulletin",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 13 bul = r.json()
assert len(bul) == 13 # HARDCODED
# 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
######### Test code ine ######### ######### Test code ine #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/formsemestre/1/bulletin", API_URL + "/etudiant/ine/1/formsemestre/1/bulletin",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 13 bul = r.json()
assert len(bul) == 13 # HARDCODED
# 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
# etudiant_groups def test_etudiant_groups(api_headers):
def test_etudiant_groups(): """
Route:
/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/groups
"""
fields = [ fields = [
"partition_id", "partition_id",
"id", "id",
@ -417,62 +201,39 @@ def test_etudiant_groups():
######### Test etudid ######### ######### Test etudid #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/semestre/1/groups", API_URL + "/etudiant/etudid/1/formsemestre/1/groups",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 1 groups = r.json()
assert len(groups) == 1 # dans un seul groupe
groups = r.json()[0] group = groups[0]
fields_ok = verify_fields(group, fields)
fields_OK = True assert fields_ok is 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
######### Test code nip ######### ######### Test code nip #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/nip/1/semestre/1/groups", API_URL + "/etudiant/nip/1/formsemestre/1/groups",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 1 groups = r.json()
assert len(groups) == 1 # dans un seul groupe
groups = r.json()[0] group = groups[0]
fields_ok = verify_fields(group, fields)
fields_OK = True assert fields_ok is 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
######### Test code ine ######### ######### Test code ine #########
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/etudiant/ine/1/semestre/1/groups", API_URL + "/etudiant/ine/1/formsemestre/1/groups",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json()) == 1 groups = r.json()
assert len(groups) == 1 # dans un seul groupe
groups = r.json()[0] group = groups[0]
fields_ok = verify_fields(group, fields)
fields_OK = True assert fields_ok is 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

View File

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

View File

@ -19,64 +19,91 @@ Utilisation :
import requests 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( r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations", API_URL + "/formations_ids",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 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(api_headers):
def test_formations_by_id(): """
Route: /formation/<int:formation_id>
"""
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/1", API_URL + "/formation/1",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 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(api_headers):
def test_formation_export_by_formation_id(): """
Route: /formation/formation_export/<int:formation_id>
"""
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/formation_export/1", API_URL + "/formation/formation_export/1",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 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 # TODO
def test_formsemestre_apo(): # 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( r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/apo/<string:etape_apo>", API_URL + "/formation/moduleimpl/1",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 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_referentiel_competences(api_headers):
def test_moduleimpls(): """
Route: "/formation/<int:formation_id>/referentiel_competences",
"""
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/<int:moduleimpl_id>", API_URL + "/formation/1/referentiel_competences",
headers=HEADERS, headers=api_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,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
# XXX A compléter

View File

@ -18,90 +18,69 @@ Utilisation :
""" """
import requests 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( r = requests.get(
SCODOC_URL + "/ScoDoc/api/formsemestre/1", API_URL + "/formsemestre/1",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
formsemestre = r.json() formsemestre = r.json()
assert verify_fields(formsemestre, FSEM_FIELDS)
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
# etudiant_bulletin def test_etudiant_bulletin(api_headers):
def test_etudiant_bulletin(): #XXX TODO pour Seb """
Route:
"""
formsemestre_id = 1
r = requests.get( r = requests.get(
SCODOC_URL f"{API_URL}/etudiant/etudid/1/formsemestre/{formsemestre_id}/bulletin",
+ "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/etudid/1/bulletin", headers=api_headers,
headers=HEADERS,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
bull_a = r.json()
r = requests.get( r = requests.get(
SCODOC_URL f"{API_URL}/etudiant/nip/1/formsemestre/{formsemestre_id}/bulletin",
+ "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/nip/1/bulletin", headers=api_headers,
headers=HEADERS,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
bull_b = r.json()
r = requests.get( r = requests.get(
SCODOC_URL f"{API_URL}/etudiant/ine/1/formsemestre/{formsemestre_id}/bulletin",
+ "/ScoDoc/api/formsemestre/1/departements/TAPI/etudiant/ine/1/bulletin", headers=api_headers,
headers=HEADERS,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 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(api_headers):
def test_bulletins(): """
Route:
"""
r = requests.get( r = requests.get(
SCODOC_URL + "/ScoDoc/api/formsemestre/1/bulletins", API_URL + "/formsemestre/1/bulletins",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
@ -110,8 +89,40 @@ def test_bulletins():
# # jury # # jury
# def test_jury(): # def test_jury():
# r = requests.get( # r = requests.get(
# SCODOC_URL + "/ScoDoc/api/formsemestre/1/jury", # API_URL + "/formsemestre/1/jury",
# headers=HEADERS, # headers=api_headers,
# verify=CHECK_CERTIFICATE, # verify=CHECK_CERTIFICATE,
# ) # )
# assert r.status_code == 200 # 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 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( r = requests.get(
SCODOC_URL SCODOC_URL
+ "/ScoDoc/api/jury/formsemestre/<int:formsemestre_id>/preparation_jury", + "/ScoDoc/api/jury/formsemestre/<int:formsemestre_id>/preparation_jury",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
# jury_decisions def test_jury_decisions(api_headers):
def test_jury_decisions(): """
Route:
"""
r = requests.get( r = requests.get(
SCODOC_URL API_URL + "/jury/formsemestre/<int:formsemestre_id>/decisions_jury",
+ "/ScoDoc/api/jury/formsemestre/<int:formsemestre_id>/decisions_jury", headers=api_headers,
headers=HEADERS,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
# set_decision_jury # set_decision_jury
def test_set_decision_jury(): def test_set_decision_jury(api_headers):
r = requests.get( r = requests.get(
SCODOC_URL SCODOC_URL
+ "/ScoDoc/api/jury/set_decision/etudid?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>" + "/ScoDoc/api/jury/set_decision/etudid?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>", "&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
@ -58,7 +62,7 @@ def test_set_decision_jury():
SCODOC_URL SCODOC_URL
+ "/ScoDoc/api/jury/set_decision/nip?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>" + "/ScoDoc/api/jury/set_decision/nip?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>", "&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
@ -67,18 +71,20 @@ def test_set_decision_jury():
SCODOC_URL SCODOC_URL
+ "/ScoDoc/api/jury/set_decision/ine?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>" + "/ScoDoc/api/jury/set_decision/ine?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>", "&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
# annule_decision_jury def test_annule_decision_jury(api_headers):
def test_annule_decision_jury(): """
Route:
"""
r = requests.get( r = requests.get(
SCODOC_URL SCODOC_URL
+ "/ScoDoc/api/jury/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/annule_decision", + "/ScoDoc/api/jury/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/annule_decision",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
@ -86,7 +92,7 @@ def test_annule_decision_jury():
r = requests.get( r = requests.get(
SCODOC_URL SCODOC_URL
+ "/ScoDoc/api/jury/nip/<int:nip>/formsemestre/<int:formsemestre_id>/annule_decision", + "/ScoDoc/api/jury/nip/<int:nip>/formsemestre/<int:formsemestre_id>/annule_decision",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200
@ -94,7 +100,7 @@ def test_annule_decision_jury():
r = requests.get( r = requests.get(
SCODOC_URL SCODOC_URL
+ "/ScoDoc/api/jury/ine/<int:ine>/formsemestre/<int:formsemestre_id>/annule_decision", + "/ScoDoc/api/jury/ine/<int:ine>/formsemestre/<int:formsemestre_id>/annule_decision",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 assert r.status_code == 200

View File

@ -5,11 +5,16 @@
"""Exemple utilisation API ScoDoc 9 avec jeton obtenu par basic authentication """Exemple utilisation API ScoDoc 9 avec jeton obtenu par basic authentication
utilisation: 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 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 scodoc import app
from tests.unit.config_test_logos import ( 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): def test_super_access(create_super_token):
"""
Route:
"""
dept1, dept2, dept3, token = create_super_token dept1, dept2, dept3, token = create_super_token
HEADERS = {"Authorization": f"Bearer {token}"} headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client: 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.status_code == 200
assert response.json is not None assert response.json is not None
def test_admin_access(create_admin_token): def test_admin_access(create_admin_token):
"""
Route:
"""
dept1, dept2, dept3, token = create_admin_token dept1, dept2, dept3, token = create_admin_token
headers = {"Authorization": f"Bearer {token}"} headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client: 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 assert response.status_code == 401
def test_lambda_access(create_lambda_token): def test_lambda_access(create_lambda_token):
"""
Route:
"""
dept1, dept2, dept3, token = create_lambda_token dept1, dept2, dept3, token = create_lambda_token
headers = {"Authorization": f"Bearer {token}"} headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client: 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 assert response.status_code == 401
def test_initial_with_header_and_footer(create_super_token): def test_initial_with_header_and_footer(create_super_token):
"""
Route:
"""
dept1, dept2, dept3, token = create_super_token dept1, dept2, dept3, token = create_super_token
headers = {"Authorization": f"Bearer {token}"} headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client: 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.status_code == 200
assert response.json is not None assert response.json is not None
assert len(response.json) == 7 assert len(response.json) == 7

View File

@ -19,41 +19,110 @@ Utilisation :
import requests 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( r = requests.get(
SCODOC_URL + "/ScoDoc/api/partitions/1", API_URL + "/partitions/1",
headers=HEADERS, headers=api_headers,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
assert r.status_code == 200 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(api_headers):
def test_etud_in_group(): """
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( r = requests.get(
SCODOC_URL + "/ScoDoc/api/partitions/groups/1", API_URL + "/partitions/groups/1",
headers=HEADERS, headers=api_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,
verify=CHECK_CERTIFICATE, verify=CHECK_CERTIFICATE,
) )
etu = r.json()[0]
fields_ok = verify_fields(etu, fields)
assert r.status_code == 200 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 # # set_groups
def test_set_groups(): # def test_set_groups(api_headers):
r = requests.get( # """
SCODOC_URL # Route:
+ "/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>", # r = requests.get(
headers=HEADERS, # SCODOC_URL
verify=CHECK_CERTIFICATE, # + "/partitions/set_groups/partition/<int:partition_id>/groups/<string:groups_id>"
) # "/delete/<string:groups_to_delete>/create/<string:groups_to_create>",
assert r.status_code == 200 # 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 echo
exit 1 exit 1
} }
[ $# = 1 ] || die "Usage $0 db_name" [ $# = 1 ] || [ $# = 2 ] || die "Usage $0 [--drop] db_name"
db_name="$1"
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: # Le répertoire de ce script:
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 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... Création des départements, formations, semestres, étudiants, groupes...
utilisation: utilisation:
1) modifier le .env pour indiquer 1) modifier /opt/scodoc/.env pour indiquer
SCODOC_DATABASE_URI="postgresql:///SCO_TEST_API" FLASK_ENV=test_api
FLASK_DEBUG=1
2) En tant qu'utilisateur scodoc, lancer: 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 db upgrade
flask sco-db-init --erase flask sco-db-init --erase
flask init-test-database 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: 3) relancer ScoDoc:
flask run --host 0.0.0.0 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 datetime
import random import random
import sys
random.seed(12345678) # tests reproductibles from app.auth.models import Role, User
from flask_login import login_user
from app import auth
from app import models from app import models
from app.models import Departement, Formation, FormSemestre
from app import db from app import db
from app.scodoc import ( from app.scodoc import (
sco_cache,
sco_evaluation_db,
sco_formations, sco_formations,
sco_formsemestre,
sco_formsemestre_inscriptions, sco_formsemestre_inscriptions,
sco_groups, sco_groups,
) )
from app.scodoc.sco_permissions import Permission
from tools.fakeportal.gen_nomprenoms import nomprenom from tools.fakeportal.gen_nomprenoms import nomprenom
random.seed(12345678) # tests reproductibles
# La formation à utiliser: # La formation à utiliser:
FORMATION_XML_FILENAME = "tests/ressources/formations/scodoc_formation_RT_BUT_RT_v1.xml" 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." "Create dept, and switch context into it."
import app as mapp import app as mapp
dept = models.Departement(acronym=acronym) dept = Departement(acronym=acronym)
db.session.add(dept) db.session.add(dept)
mapp.set_sco_dept(acronym) mapp.set_sco_dept(acronym)
db.session.commit() db.session.commit()
return dept return dept
def import_formation() -> models.Formation: def import_formation() -> Formation:
"""Import formation from XML. """Import formation from XML.
Returns formation_id Returns formation_id
""" """
@ -66,20 +65,37 @@ def import_formation() -> models.Formation:
doc = f.read() doc = f.read()
# --- Création de la formation # --- Création de la formation
f = sco_formations.formation_import_xml(doc) f = sco_formations.formation_import_xml(doc)
return models.Formation.query.get(f[0]) return Formation.query.get(f[0])
def create_user(dept): def create_users(dept: Departement) -> tuple:
"""créé les utilisaterurs nécessaires aux tests""" """créé les utilisateurs nécessaires aux tests"""
user = auth.models.User( # Un utilisateur "test" (passwd test) pouvant lire l'API
user_name="test", nom="Doe", prenom="John", dept=dept.acronym user = User(user_name="test", nom="Doe", prenom="John", dept=dept.acronym)
) user.set_password("test")
db.session.add(user) 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() 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""" """Créé un faux étudiant et l'insère dans la base"""
civilite = random.choice(("M", "F", "X")) civilite = random.choice(("M", "F", "X"))
nom, prenom = nomprenom(civilite) nom, prenom = nomprenom(civilite)
@ -100,14 +116,18 @@ def create_fake_etud(dept):
return etud return etud
def create_etuds(dept, nb=16): def create_etuds(dept: Departement, nb=16) -> list:
"create nb etuds" "create nb etuds"
return [create_fake_etud(dept) for _ in range(nb)] return [create_fake_etud(dept) for _ in range(nb)]
def create_formsemestre(formation, user, semestre_idx=1): def create_formsemestre(
"""Create formsemestre and moduleimpls""" formation: Formation, responsable: User, semestre_idx=1
formsemestre = models.FormSemestre( ) -> FormSemestre:
"""Create formsemestre and moduleimpls
responsable: resp. du formsemestre
"""
formsemestre = FormSemestre(
dept_id=formation.dept_id, dept_id=formation.dept_id,
semestre_id=semestre_idx, semestre_id=semestre_idx,
titre="Semestre test", titre="Semestre test",
@ -121,7 +141,9 @@ def create_formsemestre(formation, user, semestre_idx=1):
# Crée un modulimpl par module de ce semestre: # Crée un modulimpl par module de ce semestre:
for module in formation.modules.filter_by(semestre_id=semestre_idx): for module in formation.modules.filter_by(semestre_id=semestre_idx):
modimpl = models.ModuleImpl( 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.add(modimpl)
db.session.commit() db.session.commit()
@ -132,7 +154,7 @@ def create_formsemestre(formation, user, semestre_idx=1):
return formsemestre return formsemestre
def inscrit_etudiants(etuds, formsemestre): def inscrit_etudiants(etuds: list, formsemestre: FormSemestre):
"""Inscrit les etudiants aux semestres et à tous ses modules""" """Inscrit les etudiants aux semestres et à tous ses modules"""
for etud in etuds: for etud in etuds:
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules( sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
@ -144,14 +166,38 @@ 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") dept = init_departement("TAPI")
user = create_user(dept) user_lecteur, user_autre = create_users(dept)
etuds = create_etuds(dept) with sco_cache.DeferredSemCacheManager():
formation = import_formation() etuds = create_etuds(dept)
formsemestre = create_formsemestre(formation, user) formation = import_formation()
inscrit_etudiants(etuds, formsemestre) formsemestre = create_formsemestre(formation, user_lecteur)
create_evaluations(formsemestre)
inscrit_etudiants(etuds, formsemestre)
# à compléter # à compléter
# - groupes # - groupes
# - absences # - absences