From a62d8a6f224ce4cb84877c91203a72261360c783 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 01:35:17 +0200 Subject: [PATCH 1/6] Modification bonus St Brieuc --- app/comp/bonus_spo.py | 2 +- sco_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index da8cce5d7..86d444c31 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -946,7 +946,7 @@ class BonusStBrieuc(BonusSportAdditif): name = "bonus_iut_stbrieuc" displayed_name = "IUT de Saint-Brieuc" proportion_point = 1 / 20.0 - classic_use_bonus_ues = True + classic_use_bonus_ues = False def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): """calcul du bonus""" diff --git a/sco_version.py b/sco_version.py index ad7f762ec..f4ef3e98c 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.14" +SCOVERSION = "9.2.15" SCONAME = "ScoDoc" From ae3a59172ed2bc74c9bc9038c873b627d1fcf33a Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 02:24:09 +0200 Subject: [PATCH 2/6] =?UTF-8?q?Choix=20dates=20d=C3=A9but/fin=20sur=20Etat?= =?UTF-8?q?AbsencesGr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/absences.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/app/views/absences.py b/app/views/absences.py index f664333af..721c07369 100644 --- a/app/views/absences.py +++ b/app/views/absences.py @@ -922,9 +922,36 @@ def EtatAbsencesGr( h = groups_infos.groups_titles gr_tit = p + h - title = "État des absences %s" % gr_tit + title = f"État des absences {gr_tit}" if format == "xls" or format == "xml" or format == "json": columns_ids = ["etudid"] + columns_ids + # --- Formulaire choix dates début / fin + form_date = ( + f""" +
+ + Période du + +  au  + + +   + (nombre de demi-journées) +
""" + + """ + + """ + ) tab = GenTable( columns_ids=columns_ids, rows=T, @@ -945,8 +972,9 @@ def EtatAbsencesGr( init_qtip=True, javascripts=["js/etud_info.js"], ), - html_title=html_sco_header.html_sem_header("%s" % title, with_page_header=False) - + "

Période du %s au %s (nombre de demi-journées)
" % (debut, fin), + html_title=html_sco_header.html_sem_header(title, with_page_header=False) + + form_date, + # "

Période du %s au %s (nombre de demi-journées)
" % (debut, fin), base_url="%s&formsemestre_id=%s&debut=%s&fin=%s" % (groups_infos.base_url, formsemestre_id, debut, fin), filename="etat_abs_" From 78b081335b8d243283caf1fafa8599c0a93fd550 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 02:37:14 +0200 Subject: [PATCH 3/6] =?UTF-8?q?EtatAbsencesGr:=20fix=20affichage=20boursie?= =?UTF-8?q?r=20et=20justif.=20non=20utilis=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/absences.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/views/absences.py b/app/views/absences.py index 721c07369..ea9e2793f 100644 --- a/app/views/absences.py +++ b/app/views/absences.py @@ -871,7 +871,10 @@ def EtatAbsencesGr( ) nbjustifs_noabs = len( sco_abs.list_abs_justifs( - etudid=etud["etudid"], datedebut=datedebut, only_no_abs=True + etudid=etud["etudid"], + datedebut=datedebut, + datefin=datefin, + only_no_abs=True, ) ) # retrouve sem dans etud['sems'] @@ -894,7 +897,7 @@ def EtatAbsencesGr( "nbjustifs_noabs": nbjustifs_noabs, "_nomprenom_target": "CalAbs?etudid=%s" % etud["etudid"], "_nomprenom_td_attrs": 'id="%s" class="etudinfo"' % etud["etudid"], - "boursier": etud["boursier"], + "boursier": "oui" if etud["boursier"] else "non", } ) if s["ins"]["etat"] == "D": From 259fe0f66b9c4c92eec8d350a33493b5a2d73a19 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 08:55:56 +0200 Subject: [PATCH 4/6] FIX: SECURITY - disable broken API --- app/api/auth.py | 9 ++++++++- app/api/tokens.py | 3 ++- sco_version.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/api/auth.py b/app/api/auth.py index 20dd7ded8..0832e1354 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -30,6 +30,8 @@ from functools import wraps from flask import abort from flask import g from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth + +from app import log from app.auth.models import User from app.api.errors import error_response @@ -71,10 +73,15 @@ def token_permission_required(permission): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): + abort(501) scodoc_dept = getattr(g, "scodoc_dept", None) - if hasattr(g, "current_user") and not g.current_user.has_permission( + if not hasattr(g, "current_user") or not g.current_user.has_permission( permission, scodoc_dept ): + if hasattr(g, "current_user"): + log(f"API permission denied (user {g.current_user})") + else: + log(f"API permission denied (no user supplied)") abort(403) return f(*args, **kwargs) diff --git a/app/api/tokens.py b/app/api/tokens.py index f36ec7b0e..32f5a8f48 100644 --- a/app/api/tokens.py +++ b/app/api/tokens.py @@ -1,5 +1,5 @@ from flask import jsonify -from app import db +from app import db, log from app.api import bp from app.api.auth import basic_auth, token_auth @@ -8,6 +8,7 @@ from app.api.auth import basic_auth, token_auth @basic_auth.login_required def get_token(): token = basic_auth.current_user().get_token() + log(f"API: giving token to {basic_auth.current_user()}") db.session.commit() return jsonify({"token": token}) diff --git a/sco_version.py b/sco_version.py index f4ef3e98c..f6933b48a 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.15" +SCOVERSION = "9.2.16" SCONAME = "ScoDoc" From 840a221a4c34e7a0cece17aac7bcb686459aa9d1 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 12:50:43 +0200 Subject: [PATCH 5/6] Bulletins JSON: indique type "classic" et version "0" --- app/scodoc/sco_bulletins_json.py | 3 +-- sco_version.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index f34923a5f..7a6bbd49e 100644 --- a/app/scodoc/sco_bulletins_json.py +++ b/app/scodoc/sco_bulletins_json.py @@ -31,7 +31,6 @@ import datetime import json -from app.but import bulletin_but from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models.formsemestre import FormSemestre @@ -92,7 +91,7 @@ def formsemestre_bulletinetud_published_dict( sem = sco_formsemestre.get_formsemestre(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - d = {} + d = {"type": "classic", "version": "0"} if (not sem["bul_hide_xml"]) or force_publishing: published = True diff --git a/sco_version.py b/sco_version.py index f6933b48a..fe5771ed7 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.16" +SCOVERSION = "9.2.17" SCONAME = "ScoDoc" From 76e9a924f10c5af49cfab4ac6d5bc5239b45a26e Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 3 May 2022 13:35:17 +0200 Subject: [PATCH 6/6] =?UTF-8?q?Premi=C3=A8re=20relecture=20de=20la=20nouve?= =?UTF-8?q?lle=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/absences.py | 17 +- app/api/auth.py | 26 +- app/api/departements.py | 50 +-- app/api/etudiants.py | 34 +- app/api/evaluations.py | 23 +- app/api/formations.py | 89 +++-- app/api/formsemestres.py | 452 ++++++++----------------- app/api/jury.py | 58 ++-- app/api/logos.py | 5 +- app/api/partitions.py | 5 +- app/api/remiser.py | 253 -------------- app/api/sco_api.py | 152 --------- app/api/test_api.py | 444 ------------------------ app/api/tokens.py | 2 + app/auth/models.py | 21 +- app/auth/routes.py | 4 +- app/templates/auth/user_info_page.html | 2 +- tests/api/exemple-api-basic.py | 58 ++-- tests/api/test_api_departements.py | 4 +- tests/api/test_api_formations.py | 2 +- 20 files changed, 358 insertions(+), 1343 deletions(-) delete mode 100644 app/api/remiser.py delete mode 100644 app/api/sco_api.py delete mode 100644 app/api/test_api.py diff --git a/app/api/absences.py b/app/api/absences.py index 35056a05b..42838562a 100644 --- a/app/api/absences.py +++ b/app/api/absences.py @@ -4,7 +4,7 @@ from flask import jsonify from app.api import bp from app.api.errors import error_response -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.api.tools import get_etu_from_etudid_or_nip_or_ine from app.scodoc import notesdb as ndb @@ -16,6 +16,7 @@ from app.scodoc.sco_permissions import Permission @bp.route("/absences/etudid/", methods=["GET"]) @bp.route("/absences/nip/", methods=["GET"]) @bp.route("/absences/ine/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def absences(etudid: int = None, nip: int = None, ine: int = None): """ @@ -69,6 +70,7 @@ def absences(etudid: int = None, nip: int = None, ine: int = None): @bp.route("/absences/etudid//just", methods=["GET"]) @bp.route("/absences/nip//just", methods=["GET"]) @bp.route("/absences/ine//just", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def absences_just(etudid: int = None, nip: int = None, ine: int = None): """ @@ -113,10 +115,12 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None): ) # Récupération des absences justifiées de l'étudiant - absences = sco_abs.list_abs_date(etudid) - for absence in [absence for absence in absences if absence["estjust"]]: + abs_just = [ + absence for absence in sco_abs.list_abs_date(etudid) if absence["estjust"] + ] + for absence in abs_just: absence["jour"] = absence["jour"].isoformat() - return jsonify(absences) + return jsonify(abs_just) @bp.route( @@ -127,13 +131,16 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None): "/absences/abs_group_etat/group_id//date_debut//date_fin/", methods=["GET"], ) +@token_auth.login_required @token_permission_required(Permission.APIView) def abs_groupe_etat( # XXX A REVOIR XXX group_id: int, date_debut, date_fin, with_boursier=True, format="html" ): """ - Retoune la liste des absences d'un ou plusieurs groupes entre deux dates + Liste des absences d'un ou plusieurs groupes entre deux dates """ + return error_response(501, message="Not implemented") + # Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date() try: diff --git a/app/api/auth.py b/app/api/auth.py index 0832e1354..67d6fba14 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -41,19 +41,23 @@ token_auth = HTTPTokenAuth() @basic_auth.verify_password def verify_password(username, password): + "Verify password for this user" user = User.query.filter_by(user_name=username).first() if user and user.check_password(password): g.current_user = user + # note: est aussi basic_auth.current_user() return user @basic_auth.error_handler def basic_auth_error(status): + "error response (401 for invalid auth.)" return error_response(status) @token_auth.verify_token -def verify_token(token): +def verify_token(token) -> User: + "Retrouve l'utilisateur à partir du jeton" user = User.check_token(token) if token else None g.current_user = user return user @@ -61,6 +65,7 @@ def verify_token(token): @token_auth.error_handler def token_auth_error(status): + "rréponse en cas d'erreur d'auth." return error_response(status) @@ -70,21 +75,22 @@ def get_user_roles(user): def token_permission_required(permission): + "Décorateur pour les fontions de l'API ScoDoc" + def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): - abort(501) - scodoc_dept = getattr(g, "scodoc_dept", None) - if not hasattr(g, "current_user") or not g.current_user.has_permission( - permission, scodoc_dept - ): - if hasattr(g, "current_user"): - log(f"API permission denied (user {g.current_user})") + # token_auth.login_required() + current_user = basic_auth.current_user() + if not current_user or not current_user.has_permission(permission, None): + if current_user: + log(f"API permission denied (user {current_user})") else: - log(f"API permission denied (no user supplied)") + log("API permission denied (no user supplied)") abort(403) return f(*args, **kwargs) - return decorated_function # login_required(decorated_function) + # return decorated_function(token_auth.login_required()) + return decorated_function return decorator diff --git a/app/api/departements.py b/app/api/departements.py index 482bceca6..aaf194267 100644 --- a/app/api/departements.py +++ b/app/api/departements.py @@ -1,16 +1,17 @@ ############################################### Departements ########################################################## -import app + +import json +from flask import jsonify from app import models from app.api import bp -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.api.errors import error_response from app.scodoc.sco_permissions import Permission -from flask import jsonify - @bp.route("/departements", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def departements(): """ @@ -44,12 +45,13 @@ def departements(): return jsonify(data) -@bp.route("/departements//etudiants/liste", methods=["GET"]) +@bp.route("/departements//etudiants/list", methods=["GET"]) @bp.route( - "/departements//etudiants/liste/", methods=["GET"] + "/departements//etudiants/list/", methods=["GET"] ) +@token_auth.login_required @token_permission_required(Permission.APIView) -def liste_etudiants(dept: str, formsemestre_id=None): +def list_etudiants(dept: str, formsemestre_id=None): """ Retourne la liste des étudiants d'un département @@ -97,16 +99,14 @@ def liste_etudiants(dept: str, formsemestre_id=None): # 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] + list_etu = [etu.to_dict_bul(include_urls=False) for etu in departement.etudiants] return jsonify(list_etu) @bp.route("/departements//semestres_courants", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def liste_semestres_courant(dept: str): """ @@ -159,31 +159,3 @@ def liste_semestres_courant(dept: str): data = [d.to_dict() for d in semestres] return jsonify(data) - - -@bp.route( - "/departements//formations//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) diff --git a/app/api/etudiants.py b/app/api/etudiants.py index c04495abe..72f33bf8d 100644 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -6,7 +6,7 @@ import app from app import models from app.api import bp from app.api.errors import error_response -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.api.tools import get_etu_from_etudid_or_nip_or_ine from app.models import FormSemestreInscription, FormSemestre, Identite from app.scodoc import sco_bulletins @@ -16,6 +16,7 @@ from app.scodoc.sco_permissions import Permission @bp.route("/etudiants/courant", defaults={"long": False}) @bp.route("/etudiants/courant/long", defaults={"long": True}) +@token_auth.login_required @token_permission_required(Permission.APIView) def etudiants_courant(long=False): """ @@ -51,13 +52,13 @@ def etudiants_courant(long=False): data = [etud.to_dict_bul(include_urls=False) for etud in etuds] else: data = [etud.to_dict_short() for etud in etuds] - print(jsonify(data)) return jsonify(data) @bp.route("/etudiant/etudid/", methods=["GET"]) @bp.route("/etudiant/nip/", methods=["GET"]) @bp.route("/etudiant/ine/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def etudiant(etudid: int = None, nip: int = None, ine: int = None): """ @@ -107,6 +108,7 @@ def etudiant(etudid: int = None, nip: int = None, ine: int = None): @bp.route("/etudiant/etudid//formsemestres") @bp.route("/etudiant/nip//formsemestres") @bp.route("/etudiant/ine//formsemestres") +@token_auth.login_required @token_permission_required(Permission.APIView) def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None): """ @@ -162,18 +164,41 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None) @bp.route( "/etudiant/etudid//formsemestre//bulletin", methods=["GET"], + defaults={"version": "long"}, ) @bp.route( "/etudiant/nip//formsemestre//bulletin", methods=["GET"], + defaults={"version": "long"}, ) @bp.route( "/etudiant/ine//formsemestre//bulletin", methods=["GET"], + defaults={"version": "long"}, ) +@bp.route( + "/etudiant/etudid//formsemestre//bulletin/short", + methods=["GET"], + defaults={"version": "short"}, +) +@bp.route( + "/etudiant/nip//formsemestre//bulletin/short", + methods=["GET"], + defaults={"version": "short"}, +) +@bp.route( + "/etudiant/ine//formsemestre//bulletin/short", + methods=["GET"], + defaults={"version": "short"}, +) +@token_auth.login_required @token_permission_required(Permission.APIView) def etudiant_bulletin_semestre( - formsemestre_id, etudid: int = None, nip: int = None, ine: int = None + formsemestre_id, + etudid: int = None, + nip: int = None, + ine: int = None, + version="long", ): """ Retourne le bulletin d'un étudiant en fonction de son id et d'un semestre donné @@ -369,7 +394,7 @@ def etudiant_bulletin_semestre( "Veuillez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide", ) - return sco_bulletins.get_formsemestre_bulletin_etud_json(formsemestre, etu) + return sco_bulletins.get_formsemestre_bulletin_etud_json(formsemestre, etu, version) @bp.route( @@ -382,6 +407,7 @@ def etudiant_bulletin_semestre( @bp.route( "/etudiant/ine//semestre//groups", methods=["GET"] ) +@token_auth.login_required @token_permission_required(Permission.APIView) def etudiant_groups( formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None diff --git a/app/api/evaluations.py b/app/api/evaluations.py index 4cbfa9f33..9e0bb43e4 100644 --- a/app/api/evaluations.py +++ b/app/api/evaluations.py @@ -5,13 +5,14 @@ import app from app import models from app.api import bp -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.api.errors import error_response from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes from app.scodoc.sco_permissions import Permission @bp.route("/evaluations/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def evaluations(moduleimpl_id: int): """ @@ -54,6 +55,7 @@ def evaluations(moduleimpl_id: int): @bp.route("/evaluations/eval_notes/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def evaluation_notes(evaluation_id: int): """ @@ -86,26 +88,19 @@ def evaluation_notes(evaluation_id: int): """ # Fonction utilisée : app.scodoc.sco_evaluation_db.do_evaluation_get_all_notes() - eval = models.Evaluation.query.filter_by(id=evaluation_id).first_or_404() - - moduleimpl = models.ModuleImpl.query.filter_by(id=eval.moduleimpl_id).first_or_404() - - formsemestre = models.FormSemestre.query.filter_by( - id=moduleimpl.formsemestre_id - ).first_or_404() - - dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404() - + evaluation = models.Evaluation.query.filter_by(id=evaluation_id).first_or_404() + dept = models.Departement.query.filter_by( + id=evaluation.moduleimpl.formsemestre.dept_id + ).first() app.set_sco_dept(dept.acronym) try: # Utilisation de la fonction do_evaluation_get_all_notes data = do_evaluation_get_all_notes(evaluation_id) - except AttributeError: + except AttributeError: # ??? return error_response( 409, - message="La requête ne peut être traitée en l’état actuel. \n" - "Veillez vérifier la conformité du 'evaluation_id'", + message="La requête ne peut être traitée en l’état actuel.", ) return jsonify(data) diff --git a/app/api/formations.py b/app/api/formations.py index 920d9af4c..3724c96a3 100644 --- a/app/api/formations.py +++ b/app/api/formations.py @@ -1,48 +1,36 @@ ##############################################" Formations ############################################################ from flask import jsonify +import app from app import models from app.api import bp from app.api.errors import error_response -from app.api.auth import token_permission_required -from app.scodoc.sco_formations import formation_export +from app.api.auth import token_auth, token_permission_required +from app.models.formations import Formation +from app.scodoc import sco_formations from app.scodoc.sco_permissions import Permission -@bp.route("/formations", methods=["GET"]) +@bp.route("/formations_ids", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) -def formations(): +def formations_ids(): """ - Retourne la liste des formations + Retourne la liste de toutes les formations (tous départements) - Exemple de résultat : - [ - { - "id": 1, - "acronyme": "BUT R&T", - "titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications", - "formation_code": "V1RET", - "code_specialite": null, - "dept_id": 1, - "titre": "BUT R&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 list_formations = models.Formation.query.all() # Mise en forme des données - data = [d.to_dict() for d in list_formations] + data = [d.id for d in list_formations] return jsonify(data) @bp.route("/formations/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def formations_by_id(formation_id: int): """ @@ -66,15 +54,25 @@ def formations_by_id(formation_id: int): } """ # Récupération de la formation - forma = models.Formation.query.filter_by(id=formation_id).first_or_404() + formation = models.Formation.query.filter_by(id=formation_id).first_or_404() # Mise en forme des données - data = forma.to_dict() + data = formation.to_dict() return jsonify(data) -@bp.route("/formations/formation_export/", methods=["GET"]) +@bp.route( + "/formations/formation_export/", + methods=["GET"], + defaults={"export_ids": False}, +) +@bp.route( + "/formations/formation_export//with_ids", + methods=["GET"], + defaults={"export_ids": True}, +) +@token_auth.login_required @token_permission_required(Permission.APIView) def formation_export_by_formation_id(formation_id: int, export_ids=False): """ @@ -171,11 +169,12 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False): ] } """ - # Fonction utilité : app.scodoc.sco_formations.formation_export() - + formation = Formation.query.get_or_404(formation_id) + dept = models.Departement.query.filter_by(id=formation.dept_id).first() + app.set_sco_dept(dept.acronym) try: # Utilisation de la fonction formation_export - data = formation_export(formation_id, export_ids) + data = sco_formations.formation_export(formation_id, export_ids) except ValueError: return error_response( 409, @@ -187,6 +186,7 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False): @bp.route("/formations/moduleimpl/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def moduleimpl(moduleimpl_id: int): """ @@ -224,19 +224,16 @@ def moduleimpl(moduleimpl_id: int): } } """ - # Récupération des tous les moduleimpl - moduleimpl = models.ModuleImpl.query.filter_by(id=moduleimpl_id).first_or_404() - - # Mise en forme des données - data = moduleimpl.to_dict() - + modimpl = models.ModuleImpl.query.filter_by(id=moduleimpl_id).first_or_404() + data = modimpl.to_dict() return jsonify(data) @bp.route( - "/formations/moduleimpl/formsemestre//liste", + "/formations/moduleimpl/formsemestre//list", methods=["GET"], ) +@token_auth.login_required @token_permission_required(Permission.APIView) def moduleimpls_sem(formsemestre_id: int): """ @@ -286,3 +283,23 @@ def moduleimpls_sem(formsemestre_id: int): data = [moduleimpl.to_dict() for moduleimpl in moduleimpls] return jsonify(data) + + +@bp.route( + "/formations//referentiel_competences", + methods=["GET"], +) +@token_auth.login_required +@token_permission_required(Permission.APIView) +def referentiel_competences(formation_id: int): + """ + Retourne le référentiel de compétences + formation_id : l'id d'une formation + + return json, ou null si pas de référentiel associé. + """ + formation = models.Formation.query.filter_by(id=formation_id).first_or_404() + + if formation.referentiel_competence is None: + return jsonify(None) + return jsonify(formation.referentiel_competence.to_dict()) diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index 122abf148..24ec5eeb2 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -5,7 +5,7 @@ import app from app import models from app.api import bp from app.api.errors import error_response -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.api.tools import get_etu_from_etudid_or_nip_or_ine from app.models import FormSemestre, FormSemestreEtape from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json @@ -15,6 +15,7 @@ from app.scodoc.sco_pvjury import formsemestre_pvjury @bp.route("/formsemestre/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def formsemestre(formsemestre_id: int): """ @@ -48,28 +49,28 @@ def formsemestre(formsemestre_id: int): "titre_num": "Semestre test semestre 1", "date_debut_iso": "2021-09-01", "date_fin_iso": "2022-08-31", - "responsables": [] + "responsables": [] <<< A DOCUMENTER XXX } """ - # Récupération de tous les formsemestres - formsemetre = models.FormSemestre.query.filter_by(id=formsemestre_id).first_or_404() - - # Mise en forme des données - data = formsemetre.to_dict() - + formsemestre = models.FormSemestre.query.filter_by( + id=formsemestre_id + ).first_or_404() + data = formsemestre.to_dict() return jsonify(data) @bp.route("/formsemestre/apo/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def formsemestre_apo(etape_apo: str): """ - Retourne les informations sur les formsemestres + Retourne les informations sur les formsemestres ayant cette étape Apogée - etape_apo : l'id d'une étape apogée + etape_apo : un code étape apogée Exemple de résultat : + [ { "date_fin": "31/08/2022", "resp_can_edit": false, @@ -96,7 +97,8 @@ def formsemestre_apo(etape_apo: str): "date_debut_iso": "2021-09-01", "date_fin_iso": "2022-08-31", "responsables": [] - } + }, ... + ] """ formsemestres = FormSemestre.query.filter( FormSemestreEtape.etape_apo == etape_apo, @@ -106,173 +108,8 @@ def formsemestre_apo(etape_apo: str): return jsonify([formsemestre.to_dict() for formsemestre in formsemestres]) -@bp.route( - "/formsemestre//etudiant/etudid//bulletin", - methods=["GET"], -) -@bp.route( - "/formsemestre//etudiant/nip//bulletin", - methods=["GET"], -) -@bp.route( - "/formsemestre//etudiant/ine//bulletin", - methods=["GET"], -) -@token_permission_required(Permission.APIView) -def etudiant_bulletin( - formsemestre_id, - 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&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: - 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) - 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//bulletins", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def bulletins(formsemestre_id: int): """ @@ -452,169 +289,166 @@ def bulletins(formsemestre_id: int): ... ] """ - # Fonction utilisée : app.scodoc.sco_bulletins.get_formsemestre_bulletin_etud_json() - formsemestre = models.FormSemestre.query.filter_by( id=formsemestre_id ).first_or_404() - dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404() - app.set_sco_dept(dept.acronym) - etuds = formsemestre.etuds - data = [] - for etu in etuds: + for etu in formsemestre.etuds: bul_etu = get_formsemestre_bulletin_etud_json(formsemestre, etu) data.append(bul_etu.json) return jsonify(data) -@bp.route("/formsemestre//jury", methods=["GET"]) -@token_permission_required(Permission.APIView) -def jury(formsemestre_id: int): - """ - Retourne le récapitulatif des décisions jury +# XXX Attendre ScoDoc 9.3 +# @bp.route("/formsemestre//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 +# formsemestre_id : l'id d'un formsemestre - Exemple de résultat : +# Exemple de résultat : - """ - # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury() +# """ +# # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury() - formsemestre = models.FormSemestre.query.filter_by( - id=formsemestre_id - ).first_or_404() +# 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() +# 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) - data = formsemestre_pvjury(formsemestre_id) +# 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'", - # ) +# # try: +# # # Utilisation de la fonction formsemestre_pvjury +# # data = formsemestre_pvjury(formsemestre_id) +# # except AttributeError: +# # return error_response( +# # 409, +# # message="La requête ne peut être traitée en l’état actuel. \n" +# # "Veillez vérifier la conformité du 'formation_id'", +# # ) - return jsonify(data) +# return jsonify(data) -@bp.route( - "/formsemestre//programme", - methods=["GET"], -) -@token_permission_required(Permission.APIView) -def semestre_index(formsemestre_id: int): - """ - Retourne la liste des Ues, ressources et SAE d'un semestre +# XXX A spécifier et compléter TODO +# @bp.route( +# "/formsemestre//programme", +# methods=["GET"], +# ) +# @token_auth.login_required +# @token_permission_required(Permission.APIView) +# def semestre_index(formsemestre_id: int): # XXX nom bizarre ?? +# """ +# 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 +# formsemestre_id : l'id d'un formsemestre - Exemple de résultat : - { - "ues": [ - { - "type": 0, - "formation_id": 1, - "ue_code": "UCOD11", - "id": 1, - "ects": 12.0, - "acronyme": "RT1.1", - "is_external": false, - "numero": 1, - "code_apogee": "", - "titre": "Administrer les r\u00e9seaux et l\u2019Internet", - "coefficient": 0.0, - "semestre_idx": 1, - "color": "#B80004", - "ue_id": 1 - }, - ... - ], - "ressources": [ - { - "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 - }, - ... - ] - } - """ +# 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 +# }, +# ... +# ] +# } +# """ - formsemestre = models.FormSemestre.query.filter_by( - id=formsemestre_id - ).first_or_404() +# formsemestre: FormSemestre = models.FormSemestre.query.filter_by( +# id=formsemestre_id +# ).first_or_404() - ues = formsemestre.query_ues() +# ues = formsemestre.query_ues() - ues_dict = [] - ressources = [] - saes = [] +# ues_dict = [] +# ressources = [] +# saes = [] - for ue in ues: - ues_dict.append(ue.to_dict()) - ressources = ue.get_ressources() - saes = ue.get_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_ressources = [] +# for ressource in ressources: +# data_ressources.append(ressource.to_dict()) - data_saes = [] - for sae in saes: - data_saes.append(sae.to_dict()) +# data_saes = [] +# for sae in saes: +# data_saes.append(sae.to_dict()) - data = { - "ues": ues_dict, - "ressources": data_ressources, - "saes": data_saes, - } +# data = { +# "ues": ues_dict, +# "ressources": data_ressources, +# "saes": data_saes, +# } - return data +# return data diff --git a/app/api/jury.py b/app/api/jury.py index 982280776..1131a2847 100644 --- a/app/api/jury.py +++ b/app/api/jury.py @@ -1,41 +1,41 @@ #################################################### Jury ############################################################# -from flask import jsonify +# from flask import jsonify -from app import models -from app.api import bp -from app.api.errors import error_response -from app.api.auth import token_permission_required -from app.scodoc.sco_prepajury import feuille_preparation_jury -from app.scodoc.sco_pvjury import formsemestre_pvjury +# from app import models +# from app.api import bp +# from app.api.errors import error_response +# from app.api.auth import token_auth, token_permission_required +# from app.scodoc.sco_prepajury import feuille_preparation_jury +# from app.scodoc.sco_pvjury import formsemestre_pvjury -@bp.route("/jury/formsemestre//preparation_jury", methods=["GET"]) -# @token_permission_required(Permission.?) -def jury_preparation(formsemestre_id: int): - """ - Retourne la feuille de préparation du jury +# # @bp.route("/jury/formsemestre//preparation_jury", methods=["GET"]) +# # @token_permission_required(Permission.?) +# def jury_preparation(formsemestre_id: int): +# """ +# Retourne la feuille de préparation du jury - formsemestre_id : l'id d'un formsemestre - """ - # Fonction utilisée : app.scodoc.sco_prepajury.feuille_preparation_jury() +# formsemestre_id : l'id d'un formsemestre +# """ +# # Fonction utilisée : app.scodoc.sco_prepajury.feuille_preparation_jury() - # Utilisation de la fonction feuille_preparation_jury - prepa_jury = feuille_preparation_jury(formsemestre_id) +# # Utilisation de la fonction feuille_preparation_jury +# prepa_jury = feuille_preparation_jury(formsemestre_id) - return error_response(501, message="Not implemented") +# return error_response(501, message="Not implemented") -@bp.route("/jury/formsemestre//decisions_jury", methods=["GET"]) -# @token_permission_required(Permission.?) -def jury_decisions(formsemestre_id: int): - """ - Retourne les décisions du jury suivant un formsemestre donné +# # @bp.route("/jury/formsemestre//decisions_jury", methods=["GET"]) +# # @token_permission_required(Permission.?) +# def jury_decisions(formsemestre_id: int): +# """ +# Retourne les décisions du jury suivant un formsemestre donné - formsemestre_id : l'id d'un formsemestre - """ - # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury() +# formsemestre_id : l'id d'un formsemestre +# """ +# # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury() - # Utilisation de la fonction formsemestre_pvjury - decision_jury = formsemestre_pvjury(formsemestre_id) +# # Utilisation de la fonction formsemestre_pvjury +# decision_jury = formsemestre_pvjury(formsemestre_id) - return error_response(501, message="Not implemented") +# return error_response(501, message="Not implemented") diff --git a/app/api/logos.py b/app/api/logos.py index 663ef602a..a2c1fbde7 100644 --- a/app/api/logos.py +++ b/app/api/logos.py @@ -36,7 +36,6 @@ from app.api import bp from app.api import requested_format from app.api.auth import token_auth from app.api.errors import error_response -from app.decorators import permission_required from app.models import Departement from app.scodoc.sco_logos import list_logos, find_logo from app.api.auth import token_auth, token_permission_required @@ -44,6 +43,7 @@ from app.scodoc.sco_permissions import Permission @bp.route("/logos", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def api_get_glob_logos(): if not g.current_user.has_permission(Permission.ScoSuperAdmin, None): @@ -56,6 +56,7 @@ def api_get_glob_logos(): @bp.route("/logos/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def api_get_glob_logo(logoname): if not g.current_user.has_permission(Permission.ScoSuperAdmin, None): @@ -72,6 +73,7 @@ def api_get_glob_logo(logoname): @bp.route("/departements//logos", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def api_get_local_logos(departement): dept_id = Departement.from_acronym(departement).id @@ -82,6 +84,7 @@ def api_get_local_logos(departement): @bp.route("/departements//logos/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def api_get_local_logo(departement, logoname): # format = requested_format("jpg", ['png', 'jpg']) XXX ? diff --git a/app/api/partitions.py b/app/api/partitions.py index d925d1749..ed75fd113 100644 --- a/app/api/partitions.py +++ b/app/api/partitions.py @@ -5,12 +5,13 @@ from app import models from app.api import bp from app.api.errors import error_response -from app.api.auth import token_permission_required +from app.api.auth import token_auth, token_permission_required from app.scodoc.sco_groups import get_group_members, setGroups, get_partitions_list from app.scodoc.sco_permissions import Permission @bp.route("/partitions/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def partition(formsemestre_id: int): """ @@ -53,6 +54,7 @@ def partition(formsemestre_id: int): @bp.route("/partitions/groups/", methods=["GET"]) @bp.route("/partitions/groups//etat/", methods=["GET"]) +@token_auth.login_required @token_permission_required(Permission.APIView) def etud_in_group(group_id: int, etat=None): """ @@ -125,6 +127,7 @@ def etud_in_group(group_id: int, etat=None): "/create/", methods=["POST"], ) +@token_auth.login_required @token_permission_required(Permission.APIEtudChangeGroups) def set_groups( partition_id: int, groups_lists: str, groups_to_delete: str, groups_to_create: str diff --git a/app/api/remiser.py b/app/api/remiser.py deleted file mode 100644 index 156a4c5fd..000000000 --- a/app/api/remiser.py +++ /dev/null @@ -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=&etudid=¬e=", -# methods=["POST"], -# ) -# @bp.route( -# "/evaluations/eval_set_notes?eval_id=&nip=¬e=", -# methods=["POST"], -# ) -# @bp.route( -# "/evaluations/eval_set_notes?eval_id=&ine=¬e=", -# 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=&date=&matin=&justif=" -# "&description=", -# methods=["POST"], -# ) -# @bp.route( -# "/absences/abs_signale?nip=&date=&matin=&justif=" -# "&description=", -# methods=["POST"], -# ) -# @bp.route( -# "/absences/abs_signale?ine=&date=&matin=&justif=" -# "&description=", -# methods=["POST"], -# ) -# @bp.route( -# "/absences/abs_signale?ine=&date=&matin=&justif=" -# "&description=&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=&jour=&matin=", -# methods=["POST"], -# ) -# @bp.route( -# "/absences/abs_annule_justif?nip=&jour=&matin=", -# methods=["POST"], -# ) -# @bp.route( -# "/absences/abs_annule_justif?ine=&jour=&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=&formsemestre_id=" -# "&jury=&devenir=&assiduite=", -# methods=["POST"], -# ) -# @bp.route( -# "/jury/set_decision/nip?etudid=&formsemestre_id=" -# "&jury=&devenir=&assiduite=", -# methods=["POST"], -# ) -# @bp.route( -# "/jury/set_decision/ine?etudid=&formsemestre_id=" -# "&jury=&devenir=&assiduite=", -# 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//formsemestre//annule_decision", -# methods=["DELETE"], -# ) -# @bp.route( -# "/jury/nip//formsemestre//annule_decision", -# methods=["DELETE"], -# ) -# @bp.route( -# "/jury/ine//formsemestre//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") diff --git a/app/api/sco_api.py b/app/api/sco_api.py deleted file mode 100644 index 8440aa556..000000000 --- a/app/api/sco_api.py +++ /dev/null @@ -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/", 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//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//logos/", 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 diff --git a/app/api/test_api.py b/app/api/test_api.py deleted file mode 100644 index 821e3a01d..000000000 --- a/app/api/test_api.py +++ /dev/null @@ -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)) diff --git a/app/api/tokens.py b/app/api/tokens.py index 32f5a8f48..f5c11b096 100644 --- a/app/api/tokens.py +++ b/app/api/tokens.py @@ -7,6 +7,7 @@ from app.api.auth import basic_auth, token_auth @bp.route("/tokens", methods=["POST"]) @basic_auth.login_required def get_token(): + "renvoie un jeton jwt pour l'utilisateur courant" token = basic_auth.current_user().get_token() log(f"API: giving token to {basic_auth.current_user()}") db.session.commit() @@ -16,6 +17,7 @@ def get_token(): @bp.route("/tokens", methods=["DELETE"]) @token_auth.login_required def revoke_token(): + "révoque le jeton de l'utilisateur courant" token_auth.current_user().revoke_token() db.session.commit() return "", 204 diff --git a/app/auth/models.py b/app/auth/models.py index cfab21a9c..4ed1e41d5 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -18,7 +18,7 @@ from werkzeug.security import generate_password_hash, check_password_hash import jwt -from app import db, login +from app import db, log, login from app.models import Departement from app.models import SHORT_STR_LEN from app.scodoc.sco_exceptions import ScoValueError @@ -150,11 +150,22 @@ class User(UserMixin, db.Model): def verify_reset_password_token(token): "Vérification du token de reéinitialisation du mot de passe" try: - user_id = jwt.decode( + token = jwt.decode( token, current_app.config["SECRET_KEY"], algorithms=["HS256"] - )["reset_password"] + ) + except jwt.exceptions.ExpiredSignatureError: + log(f"verify_reset_password_token: token expired") except: - return + return None + try: + user_id = token["reset_password"] + # double check en principe inutile car déjà fait dans decode() + expire = float(token["exp"]) + if time() > expire: + log(f"verify_reset_password_token: token expired for uid={user_id}") + return None + except (TypeError, KeyError): + return None return User.query.get(user_id) def to_dict(self, include_email=True): @@ -214,6 +225,7 @@ class User(UserMixin, db.Model): self.add_role(role, dept) def get_token(self, expires_in=3600): + "Un jeton pour cet user. Stocké en base, non commité." now = datetime.utcnow() if self.token and self.token_expiration > now + timedelta(seconds=60): return self.token @@ -223,6 +235,7 @@ class User(UserMixin, db.Model): return self.token def revoke_token(self): + "Révoque le jeton de cet utilisateur" self.token_expiration = datetime.utcnow() - timedelta(seconds=1) @staticmethod diff --git a/app/auth/routes.py b/app/auth/routes.py index 24daa8ca0..1f0259ab5 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -71,7 +71,7 @@ def create_user(): flash("User {} created".format(user.user_name)) return redirect(url_for("scodoc.index")) return render_template( - "auth/register.html", title=u"Création utilisateur", form=form + "auth/register.html", title="Création utilisateur", form=form ) @@ -112,7 +112,7 @@ def reset_password(token): if current_user.is_authenticated: return redirect(url_for("scodoc.index")) user = User.verify_reset_password_token(token) - if not user: + if user is None: return redirect(url_for("scodoc.index")) form = ResetPasswordForm() if form.validate_on_submit(): diff --git a/app/templates/auth/user_info_page.html b/app/templates/auth/user_info_page.html index 8a287c01d..f344fb115 100644 --- a/app/templates/auth/user_info_page.html +++ b/app/templates/auth/user_info_page.html @@ -45,7 +45,7 @@ {# Liste des permissions #}

-

Permissions de cet utilisateur dans le département {dept}:

+

Permissions de cet utilisateur dans le département {{dept}}:

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