diff --git a/app/api/auth.py b/app/api/auth.py index 331cd388d..20dd7ded8 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -24,6 +24,10 @@ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +from functools import wraps + + +from flask import abort from flask import g from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth from app.auth.models import User @@ -63,15 +67,17 @@ def get_user_roles(user): return user.roles -# def token_permission_required(permission): -# def decorator(f): -# @wraps(f) -# def decorated_function(*args, **kwargs): -# scodoc_dept = getattr(g, "scodoc_dept", None) -# if not current_user.has_permission(permission, scodoc_dept): -# abort(403) -# return f(*args, **kwargs) +def token_permission_required(permission): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + scodoc_dept = getattr(g, "scodoc_dept", None) + if hasattr(g, "current_user") and not g.current_user.has_permission( + permission, scodoc_dept + ): + abort(403) + return f(*args, **kwargs) -# return login_required(decorated_function) + return decorated_function # login_required(decorated_function) -# return decorator + return decorator diff --git a/app/api/departements.py b/app/api/departements.py index 43137cd9f..ee7a8899b 100644 --- a/app/api/departements.py +++ b/app/api/departements.py @@ -1,23 +1,17 @@ ############################################### Departements ########################################################## +from flask import g from flask import jsonify from app import models from app.api import bp -from app.api.auth import token_auth +from app.api.auth import token_auth, token_permission_required from app.api.errors import error_response -from app.decorators import permission_required -from app.models import ApcReferentielCompetences from app.scodoc.sco_permissions import Permission -from app.scodoc.sco_prepajury import feuille_preparation_jury -from app.scodoc.sco_pvjury import formsemestre_pvjury -from app.scodoc.sco_recapcomplet import formsemestre_recapcomplet -from app.scodoc.sco_saisie_notes import notes_add - @bp.route("/departements", methods=["GET"]) -@token_auth.login_required # Commenté le temps des tests -# @permission_required(Permission.ScoView) +@token_auth.login_required +@token_permission_required(Permission.APIView) def departements(): """ Retourne la liste des ids de départements visibles @@ -34,7 +28,9 @@ def departements(): @bp.route("/departements//etudiants/liste", methods=["GET"]) -@bp.route("/departements//etudiants/liste/", methods=["GET"]) +@bp.route( + "/departements//etudiants/liste/", methods=["GET"] +) @token_auth.login_required # @permission_required(Permission.APIView) def liste_etudiants(dept: str, formsemestre_id=None): @@ -154,7 +150,10 @@ def liste_semestres_courant(dept: str): return jsonify(data) -@bp.route("/departements//formations//referentiel_competences", methods=["GET"]) +@bp.route( + "/departements//formations//referentiel_competences", + methods=["GET"], +) # @permission_required(Permission.APIView) def referenciel_competences(dept: str, formation_id: int): """ @@ -167,12 +166,16 @@ def referenciel_competences(dept: str, formation_id: int): id_dept = depts[0].id - formations = models.Formation.query.filter_by(id=formation_id, dept_id=id_dept).all() + formations = models.Formation.query.filter_by( + id=formation_id, dept_id=id_dept + ).all() ref_comp = formations[0].referentiel_competence_id if ref_comp is None: - return error_response(204, message="Pas de référenciel de compétences pour cette formation") + return error_response( + 204, message="Pas de référenciel de compétences pour cette formation" + ) else: return jsonify(ref_comp) @@ -181,11 +184,14 @@ def referenciel_competences(dept: str, formation_id: int): # return jsonify(ref.to_dict()) -@bp.route("/departements//formsemestre//programme", methods=["GET"]) +@bp.route( + "/departements//formsemestre//programme", + methods=["GET"], +) # @permission_required(Permission.APIView) def semestre_index(dept: str, formsemestre_id: int): """ Retourne la liste des Ues, ressources et SAE d'un semestre """ - return error_response(501, message="not implemented") \ No newline at end of file + return error_response(501, message="not implemented") diff --git a/app/api/test_api.py b/app/api/test_api.py index e269412fe..821e3a01d 100644 --- a/app/api/test_api.py +++ b/app/api/test_api.py @@ -1,5 +1,8 @@ ################################################## Tests ############################################################## + +# XXX OBSOLETE ??? XXX + import requests import os @@ -15,6 +18,7 @@ CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False))) HEADERS = None + def get_token(): """ Permet de set le token dans le header @@ -25,7 +29,7 @@ def get_token(): r0 = requests.post( SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD) - ) + ) token = r0.json()["token"] HEADERS = {"Authorization": f"Bearer {token}"} @@ -52,7 +56,8 @@ def get_departement(): # departements r = requests.get( SCODOC_URL + "/ScoDoc/api/departements", - headers=HEADERS, verify=CHECK_CERTIFICATE, + headers=HEADERS, + verify=CHECK_CERTIFICATE, ) if r.status_code == 200: @@ -90,17 +95,40 @@ def get_formsemestre(): # liste_semestres_courant r = requests.get( SCODOC_URL + "/ScoDoc/api/departements/" + dept_acronym + "/semestres_courants", - auth=(SCODOC_USER, SCODOC_PASSWORD) + 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"] + 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: @@ -114,7 +142,6 @@ def get_formsemestre(): 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(): """ @@ -124,14 +151,23 @@ def get_etudiant(): # etudiants r = requests.get( SCODOC_URL + "/ScoDoc/api/etudiants/courant", - auth=(SCODOC_USER, SCODOC_PASSWORD) + 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"] + fields = [ + "civilite", + "code_ine", + "code_nip", + "date_naissance", + "email", + "emailperso", + "etudid", + "nom", + "prenom", + ] for field in etu: if field not in fields: @@ -162,15 +198,25 @@ def test_departements_liste_etudiants(): global FORMSEMESTRE # Set les fields à vérifier - fields = ["civilite", "code_ine", "code_nip", "date_naissance", "email", "emailperso", "etudid", "nom", "prenom"] + 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) - ) + auth=(SCODOC_USER, SCODOC_PASSWORD), + ) - if r1.status_code == 200: # Si la requête est "OK" + if r1.status_code == 200: # Si la requête est "OK" # On récupère la liste des étudiants etudiants = r1.json() @@ -180,15 +226,17 @@ def test_departements_liste_etudiants(): 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) + 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" + if r2.status_code == 200: # Si la requête est "OK" # On récupère la liste des étudiants etudiants = r2.json() @@ -216,10 +264,14 @@ def test_departements_referenciel_competences(): # referenciel_competences r = requests.post( - SCODOC_URL + "/ScoDoc/api/departements/" + DEPT["acronym"] + "/formations/" + - FORMSEMESTRE["formation_id"] + "/referentiel_competences", - auth=(SCODOC_USER, SCODOC_PASSWORD) - ) + SCODOC_URL + + "/ScoDoc/api/departements/" + + DEPT["acronym"] + + "/formations/" + + FORMSEMESTRE["formation_id"] + + "/referentiel_competences", + auth=(SCODOC_USER, SCODOC_PASSWORD), + ) @bp.route("/test_liste_semestre_index") @@ -229,53 +281,42 @@ def test_departements_semestre_index(): """ # semestre_index r5 = requests.post( - SCODOC_URL + "/ScoDoc/api/departements/" + DEPT["acronym"] + "/formsemestre/" + - FORMSEMESTRE["formation_id"] + "/programme", - auth=(SCODOC_USER, SCODOC_PASSWORD) - ) + 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) - ) + SCODOC_URL + "/ScoDoc/api/etudiants", auth=(SCODOC_USER, SCODOC_PASSWORD) + ) # etudiants_courant - r2 = requests.post( - SCODOC_URL + "/ScoDoc/api", - auth=(SCODOC_USER, SCODOC_PASSWORD) - ) + 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) - ) + 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) - ) + 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) - ) + 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) - ) + r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) def test_routes_formation(): @@ -283,40 +324,22 @@ def test_routes_formation(): Test les routes de la partie Formation """ # formations - r1 = requests.post( - SCODOC_URL + "/ScoDoc/api", - auth=(SCODOC_USER, SCODOC_PASSWORD) - ) + 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) - ) + 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) - ) + 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) - ) + 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) - ) + 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) - ) + r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) def test_routes_formsemestres(): @@ -324,28 +347,16 @@ def test_routes_formsemestres(): Test les routes de la partie Formsemestres """ # formsemestre - r1 = requests.post( - SCODOC_URL + "/ScoDoc/api", - auth=(SCODOC_USER, SCODOC_PASSWORD) - ) + 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) - ) + 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) - ) + 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) - ) + r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) def test_routes_partitions(): @@ -353,22 +364,13 @@ def test_routes_partitions(): Test les routes de la partie Partitions """ # partition - r1 = requests.post( - SCODOC_URL + "/ScoDoc/api", - auth=(SCODOC_USER, SCODOC_PASSWORD) - ) + 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) - ) + 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) - ) + r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) def test_routes_evaluations(): @@ -376,22 +378,13 @@ def test_routes_evaluations(): Test les routes de la partie Evaluations """ # evaluations - r1 = requests.post( - SCODOC_URL + "/ScoDoc/api", - auth=(SCODOC_USER, SCODOC_PASSWORD) - ) + 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) - ) + 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) - ) + r3 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) def test_routes_jury(): @@ -399,28 +392,16 @@ 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) - ) + 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) - ) + 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) - ) + 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) - ) + r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) def test_routes_absences(): @@ -428,40 +409,22 @@ def test_routes_absences(): Test les routes de la partie Absences """ # absences - r1 = requests.post( - SCODOC_URL + "/ScoDoc/api", - auth=(SCODOC_USER, SCODOC_PASSWORD) - ) + 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) - ) + 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) - ) + 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) - ) + 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) - ) + 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) - ) + r6 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) def test_routes_logos(): @@ -469,25 +432,13 @@ 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) - ) + 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) - ) + 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) - ) + 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) - ) \ No newline at end of file + r4 = requests.post(SCODOC_URL + "/ScoDoc/api", auth=(SCODOC_USER, SCODOC_PASSWORD)) diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index 416a0d0ee..bc6dca945 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -531,10 +531,11 @@ class BonusCachan1(BonusSportAdditif):
  • DUT/LP : la meilleure note d'option, si elle est supérieure à 10, - bonifie les moyennes d'UE (sauf l'UE41 dont le code est UE41_E) à raison + bonifie les moyennes d'UE (uniquement UE13_E pour le semestre 1, UE23_E + pour le semestre 2, UE33_E pour le semestre 3 et UE43_E pour le semestre + 4) à raison de bonus = (option - 10)/10.
  • -
  • BUT : la meilleure note d'option, si elle est supérieure à 10, bonifie les moyennes d'UE à raison de bonus = (option - 10) * 3%.
@@ -545,6 +546,7 @@ class BonusCachan1(BonusSportAdditif): seuil_moy_gen = 10.0 # tous les points sont comptés proportion_point = 0.03 classic_use_bonus_ues = True + ues_bonifiables_cachan = {"UE13_E", "UE23_E", "UE33_E", "UE43_E"} def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): """calcul du bonus, avec réglage différent suivant le type de formation""" @@ -569,7 +571,7 @@ class BonusCachan1(BonusSportAdditif): dtype=float, ) else: # --- DUT - # pareil mais proportion différente et exclusion d'une UE + # pareil mais proportion différente et application à certaines UEs proportion_point = 0.1 bonus_moy_arr = np.where( note_bonus_max > self.seuil_moy_gen, @@ -582,10 +584,10 @@ class BonusCachan1(BonusSportAdditif): columns=ues_idx, dtype=float, ) - # Pas de bonus sur la ou les ue de code "UE41_E" - ue_exclues = [ue for ue in ues if ue.ue_code == "UE41_E"] - for ue in ue_exclues: - self.bonus_ues[ue.id] = 0.0 + # Applique bonus seulement sur certaines UE de code connu: + for ue in ues: + if ue.ue_code not in self.ues_bonifiables_cachan: + self.bonus_ues[ue.id] = 0.0 # annule class BonusCalais(BonusSportAdditif): @@ -1011,7 +1013,7 @@ class BonusTarbes(BonusSportAdditif): """ name = "bonus_tarbes" - displayed_name = "IUT de Tazrbes" + displayed_name = "IUT de Tarbes" seuil_moy_gen = 10.0 proportion_point = 1 / 30.0 classic_use_bonus_ues = True diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 962e7baaf..3aacb66a9 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -16,7 +16,7 @@ from app import models from app.scodoc import notesdb as ndb from app.scodoc.sco_bac import Baccalaureat -from app.scodoc.sco_exceptions import ScoValueError +from app.scodoc.sco_exceptions import ScoInvalidParamError import app.scodoc.sco_utils as scu @@ -358,7 +358,7 @@ def make_etud_args( try: args = {"etudid": int(etudid)} except ValueError as exc: - raise ScoValueError("Adresse invalide") from exc + raise ScoInvalidParamError() from exc elif code_nip: args = {"code_nip": code_nip} elif use_request: # use form from current request (Flask global) diff --git a/app/models/events.py b/app/models/events.py index 55b34d38d..ccb6396e5 100644 --- a/app/models/events.py +++ b/app/models/events.py @@ -2,9 +2,21 @@ """Evenements et logs divers """ +import datetime +import re + +from flask import g, url_for +from flask_login import current_user from app import db +from app import email +from app import log +from app.auth.models import User from app.models import SHORT_STR_LEN +from app.models.formsemestre import FormSemestre +from app.models.moduleimpls import ModuleImpl +import app.scodoc.sco_utils as scu +from app.scodoc import sco_preferences class Scolog(db.Model): @@ -24,13 +36,213 @@ class Scolog(db.Model): class ScolarNews(db.Model): """Nouvelles pour page d'accueil""" + NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id) + NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id) + NEWS_FORM = "FORM" # modification formation (object=formation_id) + NEWS_SEM = "SEM" # creation semestre (object=None) + NEWS_ABS = "ABS" # saisie absence + NEWS_MISC = "MISC" # unused + NEWS_MAP = { + NEWS_INSCR: "inscription d'étudiants", + NEWS_NOTE: "saisie note", + NEWS_FORM: "modification formation", + NEWS_SEM: "création semestre", + NEWS_MISC: "opération", # unused + } + NEWS_TYPES = list(NEWS_MAP.keys()) + __tablename__ = "scolar_news" id = db.Column(db.Integer, primary_key=True) dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True) - date = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) - authenticated_user = db.Column(db.Text) # login, sans contrainte + date = db.Column( + db.DateTime(timezone=True), server_default=db.func.now(), index=True + ) + authenticated_user = db.Column(db.Text, index=True) # login, sans contrainte # type in 'INSCR', 'NOTES', 'FORM', 'SEM', 'MISC' - type = db.Column(db.String(SHORT_STR_LEN)) - object = db.Column(db.Integer) # moduleimpl_id, formation_id, formsemestre_id + type = db.Column(db.String(SHORT_STR_LEN), index=True) + object = db.Column( + db.Integer, index=True + ) # moduleimpl_id, formation_id, formsemestre_id text = db.Column(db.Text) url = db.Column(db.Text) + + def __repr__(self): + return ( + f"<{self.__class__.__name__}(id={self.id}, date='{self.date.isoformat()}')>" + ) + + def __str__(self): + "'Chargement notes dans Stage (S3 FI) par Aurélie Dupont'" + formsemestre = self.get_news_formsemestre() + user = User.query.filter_by(user_name=self.authenticated_user).first() + + sem_text = ( + f"""({formsemestre.sem_modalite()})""" + if formsemestre + else "" + ) + author = f"par {user.get_nomcomplet()}" if user else "" + return f"{self.text} {sem_text} {author}" + + def formatted_date(self) -> str: + "06 Avr 14h23" + mois = scu.MONTH_NAMES_ABBREV[self.date.month - 1] + return f"{self.date.day} {mois} {self.date.hour:02d}h{self.date.minute:02d}" + + def to_dict(self): + return { + "date": { + "display": self.date.strftime("%d/%m/%Y %H:%M"), + "timestamp": self.date.timestamp(), + }, + "type": self.NEWS_MAP.get(self.type, "?"), + "authenticated_user": self.authenticated_user, + "text": self.text, + } + + @classmethod + def last_news(cls, n=1) -> list: + "The most recent n news. Returns list of ScolarNews instances." + return cls.query.order_by(cls.date.desc()).limit(n).all() + + @classmethod + def add(cls, typ, obj=None, text="", url=None, max_frequency=0): + """Enregistre une nouvelle + Si max_frequency, ne génère pas 2 nouvelles "identiques" + à moins de max_frequency secondes d'intervalle. + Deux nouvelles sont considérées comme "identiques" si elles ont + même (obj, typ, user). + La nouvelle enregistrée est aussi envoyée par mail. + """ + if max_frequency: + last_news = ( + cls.query.filter_by( + dept_id=g.scodoc_dept_id, + authenticated_user=current_user.user_name, + type=typ, + object=obj, + ) + .order_by(cls.date.desc()) + .limit(1) + .first() + ) + if last_news: + now = datetime.datetime.now(tz=last_news.date.tzinfo) + if (now - last_news.date) < datetime.timedelta(seconds=max_frequency): + # on n'enregistre pas + return + + news = ScolarNews( + dept_id=g.scodoc_dept_id, + authenticated_user=current_user.user_name, + type=typ, + object=obj, + text=text, + url=url, + ) + db.session.add(news) + db.session.commit() + log(f"news: {news}") + news.notify_by_mail() + + def get_news_formsemestre(self) -> FormSemestre: + """formsemestre concerné par la nouvelle + None si inexistant + """ + formsemestre_id = None + if self.type == self.NEWS_INSCR: + formsemestre_id = self.object + elif self.type == self.NEWS_NOTE: + moduleimpl_id = self.object + if moduleimpl_id: + modimpl = ModuleImpl.query.get(moduleimpl_id) + if modimpl is None: + return None # module does not exists anymore + formsemestre_id = modimpl.formsemestre_id + + if not formsemestre_id: + return None + formsemestre = FormSemestre.query.get(formsemestre_id) + return formsemestre + + def notify_by_mail(self): + """Notify by email""" + formsemestre = self.get_news_formsemestre() + + prefs = sco_preferences.SemPreferences( + formsemestre_id=formsemestre.id if formsemestre else None + ) + destinations = prefs["emails_notifications"] or "" + destinations = [x.strip() for x in destinations.split(",")] + destinations = [x for x in destinations if x] + if not destinations: + return + # + txt = self.text + if formsemestre: + txt += f"""\n\nSemestre {formsemestre.titre_mois()}\n\n""" + txt += f"""{formsemestre.sem_modalite()} + """ + user = User.query.filter_by(user_name=self.authenticated_user).first() + if user: + txt += f"\n\nEffectué par: {user.get_nomcomplet()}\n" + + txt = ( + "\n" + + txt + + """\n + --- Ceci est un message de notification automatique issu de ScoDoc + --- vous recevez ce message car votre adresse est indiquée dans les paramètres de ScoDoc. + """ + ) + + # Transforme les URL en URL absolues + base = scu.ScoURL() + txt = re.sub('href=/.*?"', 'href="' + base + "/", txt) + + # Transforme les liens HTML en texte brut: 'texte' devient 'texte: url' + # (si on veut des messages non html) + txt = re.sub(r'(.*?)', r"\2: \1", txt) + + subject = "[ScoDoc] " + self.NEWS_MAP.get(self.type, "?") + sender = prefs["email_from_addr"] + + email.send_email(subject, sender, destinations, txt) + + @classmethod + def scolar_news_summary_html(cls, n=5) -> str: + """News summary, formated in HTML""" + news_list = cls.last_news(n=n) + if not news_list: + return "" + H = [ + f"""
Dernières opérations +
    """ + ] + + for news in news_list: + H.append( + f"""
  • {news.formatted_date()}{news}
  • """ + ) + + H.append("
") + + # Informations générales + H.append( + f"""
+ Pour être informé des évolutions de ScoDoc, + vous pouvez vous + + abonner à la liste de diffusion. +
+ """ + ) + + H.append("
") + return "\n".join(H) diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 4ec90052b..edf5fa68d 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -374,6 +374,16 @@ class FormSemestre(db.Model): return self.titre return f"{self.titre} {self.formation.get_parcours().SESSION_NAME} {self.semestre_id}" + def sem_modalite(self) -> str: + """Le semestre et la modialité, ex "S2 FI" ou "S3 APP" """ + if self.semestre_id > 0: + descr_sem = f"S{self.semestre_id}" + else: + descr_sem = "" + if self.modalite: + descr_sem += " " + self.modalite + return descr_sem + def get_abs_count(self, etudid): """Les comptes d'absences de cet étudiant dans ce semestre: tuple (nb abs, nb abs justifiées) diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py index 453aa2f6c..c19f93608 100644 --- a/app/scodoc/sco_dept.py +++ b/app/scodoc/sco_dept.py @@ -32,6 +32,7 @@ from flask import g, request from flask_login import current_user import app +from app.models import ScolarNews import app.scodoc.sco_utils as scu from app.scodoc.gen_tables import GenTable from app.scodoc.sco_permissions import Permission @@ -40,9 +41,7 @@ import app.scodoc.notesdb as ndb from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_modalites -from app.scodoc import sco_news from app.scodoc import sco_preferences -from app.scodoc import sco_up_to_date from app.scodoc import sco_users @@ -53,7 +52,7 @@ def index_html(showcodes=0, showsemtable=0): H = [] # News: - H.append(sco_news.scolar_news_summary_html()) + H.append(ScolarNews.scolar_news_summary_html()) # Avertissement de mise à jour: H.append("""
""") diff --git a/app/scodoc/sco_edit_formation.py b/app/scodoc/sco_edit_formation.py index 48caf2d5a..aabfaddce 100644 --- a/app/scodoc/sco_edit_formation.py +++ b/app/scodoc/sco_edit_formation.py @@ -37,6 +37,7 @@ from app.models import SHORT_STR_LEN from app.models.formations import Formation from app.models.modules import Module from app.models.ues import UniteEns +from app.models import ScolarNews import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu @@ -44,13 +45,10 @@ from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError from app.scodoc import html_sco_header -from app.scodoc import sco_cache from app.scodoc import sco_codes_parcours -from app.scodoc import sco_edit_module from app.scodoc import sco_edit_ue from app.scodoc import sco_formations from app.scodoc import sco_formsemestre -from app.scodoc import sco_news def formation_delete(formation_id=None, dialog_confirmed=False): @@ -117,11 +115,10 @@ def do_formation_delete(oid): sco_formations._formationEditor.delete(cnx, oid) # news - sco_news.add( - typ=sco_news.NEWS_FORM, - object=oid, - text="Suppression de la formation %(acronyme)s" % F, - max_frequency=3, + ScolarNews.add( + typ=ScolarNews.NEWS_FORM, + obj=oid, + text=f"Suppression de la formation {F['acronyme']}", ) @@ -281,10 +278,9 @@ def do_formation_create(args): # r = sco_formations._formationEditor.create(cnx, args) - sco_news.add( - typ=sco_news.NEWS_FORM, + ScolarNews.add( + typ=ScolarNews.NEWS_FORM, text="Création de la formation %(titre)s (%(acronyme)s)" % args, - max_frequency=3, ) return r diff --git a/app/scodoc/sco_edit_matiere.py b/app/scodoc/sco_edit_matiere.py index f691e350a..dd9ffa9bd 100644 --- a/app/scodoc/sco_edit_matiere.py +++ b/app/scodoc/sco_edit_matiere.py @@ -30,6 +30,7 @@ """ import flask from flask import g, url_for, request +from app.models.events import ScolarNews from app.models.formations import Matiere import app.scodoc.notesdb as ndb @@ -78,8 +79,7 @@ def do_matiere_edit(*args, **kw): def do_matiere_create(args): "create a matiere" from app.scodoc import sco_edit_ue - from app.scodoc import sco_formations - from app.scodoc import sco_news + from app.models import ScolarNews cnx = ndb.GetDBConnexion() # check @@ -89,11 +89,11 @@ def do_matiere_create(args): # news formation = Formation.query.get(ue["formation_id"]) - sco_news.add( - typ=sco_news.NEWS_FORM, - object=ue["formation_id"], + ScolarNews.add( + typ=ScolarNews.NEWS_FORM, + obj=ue["formation_id"], text=f"Modification de la formation {formation.acronyme}", - max_frequency=3, + max_frequency=10 * 60, ) formation.invalidate_cached_sems() return r @@ -174,10 +174,8 @@ def can_delete_matiere(matiere: Matiere) -> tuple[bool, str]: def do_matiere_delete(oid): "delete matiere and attached modules" - from app.scodoc import sco_formations from app.scodoc import sco_edit_ue from app.scodoc import sco_edit_module - from app.scodoc import sco_news cnx = ndb.GetDBConnexion() # check @@ -197,11 +195,11 @@ def do_matiere_delete(oid): # news formation = Formation.query.get(ue["formation_id"]) - sco_news.add( - typ=sco_news.NEWS_FORM, - object=ue["formation_id"], + ScolarNews.add( + typ=ScolarNews.NEWS_FORM, + obj=ue["formation_id"], text=f"Modification de la formation {formation.acronyme}", - max_frequency=3, + max_frequency=10 * 60, ) formation.invalidate_cached_sems() diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index 2f6b3f430..ece30a345 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -38,6 +38,7 @@ from app import models from app.models import APO_CODE_STR_LEN from app.models import Formation, Matiere, Module, UniteEns from app.models import FormSemestre, ModuleImpl +from app.models import ScolarNews import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu @@ -53,7 +54,6 @@ from app.scodoc import html_sco_header from app.scodoc import sco_codes_parcours from app.scodoc import sco_edit_matiere from app.scodoc import sco_moduleimpl -from app.scodoc import sco_news _moduleEditor = ndb.EditableTable( "notes_modules", @@ -98,18 +98,16 @@ def module_list(*args, **kw): def do_module_create(args) -> int: "Create a module. Returns id of new object." # create - from app.scodoc import sco_formations - cnx = ndb.GetDBConnexion() r = _moduleEditor.create(cnx, args) # news formation = Formation.query.get(args["formation_id"]) - sco_news.add( - typ=sco_news.NEWS_FORM, - object=formation.id, + ScolarNews.add( + typ=ScolarNews.NEWS_FORM, + obj=formation.id, text=f"Modification de la formation {formation.acronyme}", - max_frequency=3, + max_frequency=10 * 60, ) formation.invalidate_cached_sems() return r @@ -396,11 +394,11 @@ def do_module_delete(oid): # news formation = module.formation - sco_news.add( - typ=sco_news.NEWS_FORM, - object=mod["formation_id"], + ScolarNews.add( + typ=ScolarNews.NEWS_FORM, + obj=mod["formation_id"], text=f"Modification de la formation {formation.acronyme}", - max_frequency=3, + max_frequency=10 * 60, ) formation.invalidate_cached_sems() diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index ffe4d64fc..be2523bbc 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -37,6 +37,7 @@ from app import db from app import log from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN from app.models import Formation, UniteEns, ModuleImpl, Module +from app.models import ScolarNews from app.models.formations import Matiere import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu @@ -55,15 +56,11 @@ from app.scodoc import html_sco_header from app.scodoc import sco_cache from app.scodoc import sco_codes_parcours from app.scodoc import sco_edit_apc -from app.scodoc import sco_edit_formation from app.scodoc import sco_edit_matiere from app.scodoc import sco_edit_module -from app.scodoc import sco_etud from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl -from app.scodoc import sco_news -from app.scodoc import sco_permissions from app.scodoc import sco_preferences from app.scodoc import sco_tag_module @@ -138,11 +135,11 @@ def do_ue_create(args): ue = UniteEns.query.get(ue_id) flash(f"UE créée (code {ue.ue_code})") formation = Formation.query.get(args["formation_id"]) - sco_news.add( - typ=sco_news.NEWS_FORM, - object=args["formation_id"], + ScolarNews.add( + typ=ScolarNews.NEWS_FORM, + obj=args["formation_id"], text=f"Modification de la formation {formation.acronyme}", - max_frequency=3, + max_frequency=10 * 60, ) formation.invalidate_cached_sems() return ue_id @@ -222,11 +219,11 @@ def do_ue_delete(ue_id, delete_validations=False, force=False): sco_cache.invalidate_formsemestre() # news F = sco_formations.formation_list(args={"formation_id": ue.formation_id})[0] - sco_news.add( - typ=sco_news.NEWS_FORM, - object=ue.formation_id, + ScolarNews.add( + typ=ScolarNews.NEWS_FORM, + obj=ue.formation_id, text="Modification de la formation %(acronyme)s" % F, - max_frequency=3, + max_frequency=10 * 60, ) # if not force: diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py index 710f85bc4..598ec96b0 100644 --- a/app/scodoc/sco_etud.py +++ b/app/scodoc/sco_etud.py @@ -637,7 +637,7 @@ def create_etud(cnx, args={}): Returns: etud, l'étudiant créé. """ - from app.scodoc import sco_news + from app.models import ScolarNews # creation d'un etudiant etudid = etudident_create(cnx, args) @@ -671,9 +671,8 @@ def create_etud(cnx, args={}): etud = etudident_list(cnx, {"etudid": etudid})[0] fill_etuds_info([etud]) etud["url"] = url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) - sco_news.add( - typ=sco_news.NEWS_INSCR, - object=None, # pas d'object pour ne montrer qu'un etudiant + ScolarNews.add( + typ=ScolarNews.NEWS_INSCR, text='Nouvel étudiant %(nomprenom)s' % etud, url=etud["url"], ) diff --git a/app/scodoc/sco_evaluation_db.py b/app/scodoc/sco_evaluation_db.py index 59dbc5fe8..7aec90889 100644 --- a/app/scodoc/sco_evaluation_db.py +++ b/app/scodoc/sco_evaluation_db.py @@ -28,7 +28,6 @@ """Gestion evaluations (ScoDoc7, sans SQlAlchemy) """ -import datetime import pprint import flask @@ -37,6 +36,7 @@ from flask_login import current_user from app import log +from app.models import ScolarNews from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb @@ -44,9 +44,7 @@ from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc import sco_cache from app.scodoc import sco_edit_module -from app.scodoc import sco_formsemestre from app.scodoc import sco_moduleimpl -from app.scodoc import sco_news from app.scodoc import sco_permissions_check @@ -179,9 +177,9 @@ def do_evaluation_create( mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0] mod["moduleimpl_id"] = M["moduleimpl_id"] mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod - sco_news.add( - typ=sco_news.NEWS_NOTE, - object=moduleimpl_id, + ScolarNews.add( + typ=ScolarNews.NEWS_NOTE, + obj=moduleimpl_id, text='Création d\'une évaluation dans %(titre)s' % mod, url=mod["url"], ) @@ -240,9 +238,9 @@ def do_evaluation_delete(evaluation_id): mod["url"] = ( scu.NotesURL() + "/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod ) - sco_news.add( - typ=sco_news.NEWS_NOTE, - object=moduleimpl_id, + ScolarNews.add( + typ=ScolarNews.NEWS_NOTE, + obj=moduleimpl_id, text='Suppression d\'une évaluation dans %(titre)s' % mod, url=mod["url"], ) diff --git a/app/scodoc/sco_evaluation_recap.py b/app/scodoc/sco_evaluation_recap.py index 168b463b8..7f1120b78 100644 --- a/app/scodoc/sco_evaluation_recap.py +++ b/app/scodoc/sco_evaluation_recap.py @@ -53,7 +53,11 @@ def evaluations_recap(formsemestre_id: int) -> str: for row in rows: H.append(f"{scu.gen_row(column_ids, row, with_col_classes=True)}\n") - H.append("""""") + H.append( + """ +
Les étudiants démissionnaires ou défaillants ne sont pas pris en compte dans cette table.
+ """ + ) H.append( html_sco_header.sco_footer(), ) diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index 43b585907..6152e8810 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -36,32 +36,27 @@ from flask import g from flask_login import current_user from flask import request -from app import log - from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import FormSemestre +from app.models import ScolarNews import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import ModuleType import app.scodoc.notesdb as ndb -from app.scodoc.sco_exceptions import AccessDenied, ScoValueError -import sco_version from app.scodoc.gen_tables import GenTable from app.scodoc import html_sco_header from app.scodoc import sco_evaluation_db from app.scodoc import sco_abs -from app.scodoc import sco_cache from app.scodoc import sco_edit_module from app.scodoc import sco_edit_ue -from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl -from app.scodoc import sco_news from app.scodoc import sco_permissions_check from app.scodoc import sco_preferences from app.scodoc import sco_users +import sco_version # -------------------------------------------------------------------- @@ -633,13 +628,16 @@ def evaluation_describe(evaluation_id="", edit_in_place=True): 'voir toutes les notes du module' % moduleimpl_id ) - mod_descr = '%s %s (resp. %s) %s' % ( - moduleimpl_id, - Mod["code"] or "", - Mod["titre"] or "?", - nomcomplet, - resp, - link, + mod_descr = ( + '%s %s (resp. %s) %s' + % ( + moduleimpl_id, + Mod["code"] or "", + Mod["titre"] or "?", + nomcomplet, + resp, + link, + ) ) etit = E["description"] or "" diff --git a/app/scodoc/sco_exceptions.py b/app/scodoc/sco_exceptions.py index 35d2d9d6e..17d39a4c2 100644 --- a/app/scodoc/sco_exceptions.py +++ b/app/scodoc/sco_exceptions.py @@ -63,6 +63,17 @@ class ScoFormatError(ScoValueError): pass +class ScoInvalidParamError(ScoValueError): + """Paramètres requete invalides. + A utilisée lorsqu'une route est appelée avec des paramètres invalides + (id strings, ...) + """ + + def __init__(self, msg=None, dest_url=None): + msg = msg or "Adresse invalide. Vérifiez vos signets." + super().__init__(msg, dest_url=dest_url) + + class ScoPDFFormatError(ScoValueError): "erreur génération PDF (templates platypus, ...)" diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py index 8b99a0678..178138b22 100644 --- a/app/scodoc/sco_formations.py +++ b/app/scodoc/sco_formations.py @@ -39,12 +39,12 @@ import app.scodoc.notesdb as ndb from app import db from app import log from app.models import Formation, Module +from app.models import ScolarNews from app.scodoc import sco_codes_parcours from app.scodoc import sco_edit_matiere from app.scodoc import sco_edit_module from app.scodoc import sco_edit_ue from app.scodoc import sco_formsemestre -from app.scodoc import sco_news from app.scodoc import sco_preferences from app.scodoc import sco_tag_module from app.scodoc import sco_xml @@ -351,10 +351,13 @@ def formation_list_table(formation_id=None, args={}): else: but_locked = '' if editable and not locked: - but_suppr = '%s' % ( - f["formation_id"], - f["acronyme"].lower().replace(" ", "-"), - suppricon, + but_suppr = ( + '%s' + % ( + f["formation_id"], + f["acronyme"].lower().replace(" ", "-"), + suppricon, + ) ) else: but_suppr = '' @@ -422,9 +425,9 @@ def formation_create_new_version(formation_id, redirect=True): new_id, modules_old2new, ues_old2new = formation_import_xml(xml_data) # news F = formation_list(args={"formation_id": new_id})[0] - sco_news.add( - typ=sco_news.NEWS_FORM, - object=new_id, + ScolarNews.add( + typ=ScolarNews.NEWS_FORM, + obj=new_id, text="Nouvelle version de la formation %(acronyme)s" % F, ) if redirect: diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index e5f8129e8..5d8510ac2 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -229,7 +229,7 @@ def etapes_apo_str(etapes): def do_formsemestre_create(args, silent=False): "create a formsemestre" from app.scodoc import sco_groups - from app.scodoc import sco_news + from app.models import ScolarNews cnx = ndb.GetDBConnexion() formsemestre_id = _formsemestreEditor.create(cnx, args) @@ -254,8 +254,8 @@ def do_formsemestre_create(args, silent=False): args["formsemestre_id"] = formsemestre_id args["url"] = "Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s" % args if not silent: - sco_news.add( - typ=sco_news.NEWS_SEM, + ScolarNews.add( + typ=ScolarNews.NEWS_SEM, text='Création du semestre %(titre)s' % args, url=args["url"], ) diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index 7d903f6cf..3ccba7728 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -36,6 +36,7 @@ from app import db from app.auth.models import User from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN from app.models import Module, ModuleImpl, Evaluation, EvaluationUEPoids, UniteEns +from app.models import ScolarNews from app.models.formations import Formation from app.models.formsemestre import FormSemestre import app.scodoc.notesdb as ndb @@ -191,10 +192,10 @@ def do_formsemestre_createwithmodules(edit=False): modimpl.responsable_id, f"inconnu numéro {modimpl.responsable_id} resp. de {modimpl.id} !", ) - - initvalues["responsable_id"] = uid2display.get( - sem["responsables"][0], sem["responsables"][0] - ) + if sem["responsables"]: + initvalues["responsable_id"] = uid2display.get( + sem["responsables"][0], sem["responsables"][0] + ) if len(sem["responsables"]) > 1: initvalues["responsable_id2"] = uid2display.get( sem["responsables"][1], sem["responsables"][1] @@ -1493,11 +1494,9 @@ def do_formsemestre_delete(formsemestre_id): sco_formsemestre._formsemestreEditor.delete(cnx, formsemestre_id) # news - from app.scodoc import sco_news - - sco_news.add( - typ=sco_news.NEWS_SEM, - object=formsemestre_id, + ScolarNews.add( + typ=ScolarNews.NEWS_SEM, + obj=formsemestre_id, text="Suppression du semestre %(titre)s" % sem, ) diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index 802399f2c..83314cf85 100644 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -1186,6 +1186,7 @@ def formsemestre_tableau_modules( H.append("") if mod.module_type in (ModuleType.RESSOURCE, ModuleType.SAE): coefs = mod.ue_coefs_list() + H.append(f'') for coef in coefs: if coef[1] > 0: H.append( @@ -1197,6 +1198,7 @@ def formsemestre_tableau_modules( ) else: H.append(f"""""") + H.append("") H.append("") if mod.module_type in ( None, # ne devrait pas être nécessaire car la migration a remplacé les NULLs diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py index da117b5d0..32b2530d4 100644 --- a/app/scodoc/sco_import_etuds.py +++ b/app/scodoc/sco_import_etuds.py @@ -40,6 +40,8 @@ from flask import g, url_for import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app import log +from app.models import ScolarNews + from app.scodoc.sco_excel import COLORS from app.scodoc.sco_formsemestre_inscriptions import ( do_formsemestre_inscription_with_modules, @@ -54,14 +56,13 @@ from app.scodoc.sco_exceptions import ( ScoLockedFormError, ScoGenError, ) + from app.scodoc import html_sco_header from app.scodoc import sco_cache from app.scodoc import sco_etud -from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_excel from app.scodoc import sco_groups_view -from app.scodoc import sco_news from app.scodoc import sco_preferences # format description (in tools/) @@ -472,11 +473,11 @@ def scolars_import_excel_file( diag.append("Import et inscription de %s étudiants" % len(created_etudids)) - sco_news.add( - typ=sco_news.NEWS_INSCR, + ScolarNews.add( + typ=ScolarNews.NEWS_INSCR, text="Inscription de %d étudiants" # peuvent avoir ete inscrits a des semestres differents % len(created_etudids), - object=formsemestre_id, + obj=formsemestre_id, ) log("scolars_import_excel_file: completing transaction") diff --git a/app/scodoc/sco_news.py b/app/scodoc/sco_news.py deleted file mode 100644 index e0977a951..000000000 --- a/app/scodoc/sco_news.py +++ /dev/null @@ -1,276 +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 -# -############################################################################## - -"""Gestion des "nouvelles" -""" -import re -import time - - -from operator import itemgetter - -from flask import g -from flask_login import current_user - -import app.scodoc.sco_utils as scu -import app.scodoc.notesdb as ndb -from app import log -from app.scodoc import sco_formsemestre -from app.scodoc import sco_moduleimpl -from app.scodoc import sco_preferences -from app import email - - -_scolar_news_editor = ndb.EditableTable( - "scolar_news", - "news_id", - ("date", "authenticated_user", "type", "object", "text", "url"), - filter_dept=True, - sortkey="date desc", - output_formators={"date": ndb.DateISOtoDMY}, - input_formators={"date": ndb.DateDMYtoISO}, - html_quote=False, # no user supplied data, needed to store html links -) - -NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id) -NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id) -NEWS_FORM = "FORM" # modification formation (object=formation_id) -NEWS_SEM = "SEM" # creation semestre (object=None) -NEWS_MISC = "MISC" # unused -NEWS_MAP = { - NEWS_INSCR: "inscription d'étudiants", - NEWS_NOTE: "saisie note", - NEWS_FORM: "modification formation", - NEWS_SEM: "création semestre", - NEWS_MISC: "opération", # unused -} -NEWS_TYPES = list(NEWS_MAP.keys()) - -scolar_news_create = _scolar_news_editor.create -scolar_news_list = _scolar_news_editor.list - -_LAST_NEWS = {} # { (authuser_name, type, object) : time } - - -def add(typ, object=None, text="", url=None, max_frequency=False): - """Ajoute une nouvelle. - Si max_frequency, ne genere pas 2 nouvelles identiques à moins de max_frequency - secondes d'intervalle. - """ - from app.scodoc import sco_users - - authuser_name = current_user.user_name - cnx = ndb.GetDBConnexion() - args = { - "authenticated_user": authuser_name, - "user_info": sco_users.user_info(authuser_name), - "type": typ, - "object": object, - "text": text, - "url": url, - } - t = time.time() - if max_frequency: - last_news_time = _LAST_NEWS.get((authuser_name, typ, object), False) - if last_news_time and (t - last_news_time < max_frequency): - # log("not recording") - return - - log("news: %s" % args) - - _LAST_NEWS[(authuser_name, typ, object)] = t - - _send_news_by_mail(args) - return scolar_news_create(cnx, args) - - -def scolar_news_summary(n=5): - """Return last n news. - News are "compressed", ie redondant events are joined. - """ - from app.scodoc import sco_etud - from app.scodoc import sco_users - - cnx = ndb.GetDBConnexion() - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - cursor.execute( - """SELECT id AS news_id, * - FROM scolar_news - WHERE dept_id=%(dept_id)s - ORDER BY date DESC LIMIT 100 - """, - {"dept_id": g.scodoc_dept_id}, - ) - selected_news = {} # (type,object) : news dict - news = cursor.dictfetchall() # la plus récente d'abord - - for r in reversed(news): # la plus ancienne d'abord - # si on a deja une news avec meme (type,object) - # et du meme jour, on la remplace - dmy = ndb.DateISOtoDMY(r["date"]) # round - key = (r["type"], r["object"], dmy) - selected_news[key] = r - - news = list(selected_news.values()) - # sort by date, descending - news.sort(key=itemgetter("date"), reverse=True) - news = news[:n] - # mimic EditableTable.list output formatting: - for n in news: - n["date822"] = n["date"].strftime("%a, %d %b %Y %H:%M:%S %z") - # heure - n["hm"] = n["date"].strftime("%Hh%M") - for k in n.keys(): - if n[k] is None: - n[k] = "" - if k in _scolar_news_editor.output_formators: - n[k] = _scolar_news_editor.output_formators[k](n[k]) - # date resumee - j, m = n["date"].split("/")[:2] - mois = scu.MONTH_NAMES_ABBREV[int(m) - 1] - n["formatted_date"] = "%s %s %s" % (j, mois, n["hm"]) - # indication semestre si ajout notes: - infos = _get_formsemestre_infos_from_news(n) - if infos: - n["text"] += ( - ' (%(descr_sem)s)' - % infos - ) - n["text"] += ( - " par " + sco_users.user_info(n["authenticated_user"])["nomcomplet"] - ) - return news - - -def _get_formsemestre_infos_from_news(n): - """Informations sur le semestre concerné par la nouvelle n - {} si inexistant - """ - formsemestre_id = None - if n["type"] == NEWS_INSCR: - formsemestre_id = n["object"] - elif n["type"] == NEWS_NOTE: - moduleimpl_id = n["object"] - if n["object"]: - mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id) - if not mods: - return {} # module does not exists anymore - return {} # pas d'indication du module - mod = mods[0] - formsemestre_id = mod["formsemestre_id"] - - if not formsemestre_id: - return {} - - try: - sem = sco_formsemestre.get_formsemestre(formsemestre_id) - except: - # semestre n'existe plus - return {} - - if sem["semestre_id"] > 0: - descr_sem = "S%d" % sem["semestre_id"] - else: - descr_sem = "" - if sem["modalite"]: - descr_sem += " " + sem["modalite"] - return {"formsemestre_id": formsemestre_id, "sem": sem, "descr_sem": descr_sem} - - -def scolar_news_summary_html(n=5): - """News summary, formated in HTML""" - news = scolar_news_summary(n=n) - if not news: - return "" - H = ['
Dernières opérations'] - H.append('
    ') - - for n in news: - H.append( - '
  • %(formatted_date)s%(text)s
  • ' - % n - ) - H.append("
") - - # Informations générales - H.append( - """
- Pour être informé des évolutions de ScoDoc, - vous pouvez vous - - abonner à la liste de diffusion. -
- """ - % scu.SCO_ANNONCES_WEBSITE - ) - - H.append("
") - return "\n".join(H) - - -def _send_news_by_mail(n): - """Notify by email""" - infos = _get_formsemestre_infos_from_news(n) - formsemestre_id = infos.get("formsemestre_id", None) - prefs = sco_preferences.SemPreferences(formsemestre_id=formsemestre_id) - destinations = prefs["emails_notifications"] or "" - destinations = [x.strip() for x in destinations.split(",")] - destinations = [x for x in destinations if x] - if not destinations: - return - # - txt = n["text"] - if infos: - txt += "\n\nSemestre %(titremois)s\n\n" % infos["sem"] - txt += ( - """%(descr_sem)s - """ - % infos - ) - txt += "\n\nEffectué par: %(nomcomplet)s\n" % n["user_info"] - - txt = ( - "\n" - + txt - + """\n ---- Ceci est un message de notification automatique issu de ScoDoc ---- vous recevez ce message car votre adresse est indiquée dans les paramètres de ScoDoc. -""" - ) - - # Transforme les URL en URL absolue - base = scu.ScoURL() - txt = re.sub('href=.*?"', 'href="' + base + "/", txt) - - # Transforme les liens HTML en texte brut: 'texte' devient 'texte: url' - # (si on veut des messages non html) - txt = re.sub(r'(.*?)', r"\2: \1", txt) - - subject = "[ScoDoc] " + NEWS_MAP.get(n["type"], "?") - sender = prefs["email_from_addr"] - - email.send_email(subject, sender, destinations, txt) diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index 112b0da50..9ccb63628 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -350,7 +350,7 @@ class BasePreferences(object): "initvalue": "", "title": "e-mails à qui notifier les opérations", "size": 70, - "explanation": "adresses séparées par des virgules; notifie les opérations (saisies de notes, etc). (vous pouvez préférer utiliser le flux rss)", + "explanation": "adresses séparées par des virgules; notifie les opérations (saisies de notes, etc).", "category": "general", "only_global": False, # peut être spécifique à un semestre }, diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index fbef92458..9cda23725 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -39,6 +39,7 @@ from flask_login import current_user from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import FormSemestre +from app.models import ScolarNews import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import ModuleType import app.scodoc.notesdb as ndb @@ -48,6 +49,7 @@ from app.scodoc.sco_exceptions import ( InvalidNoteValue, NoteProcessError, ScoGenError, + ScoInvalidParamError, ScoValueError, ) from app.scodoc.TrivialFormulator import TrivialFormulator, TF @@ -64,7 +66,6 @@ from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_groups from app.scodoc import sco_groups_view from app.scodoc import sco_moduleimpl -from app.scodoc import sco_news from app.scodoc import sco_permissions_check from app.scodoc import sco_undo_notes from app.scodoc import sco_etud @@ -274,11 +275,12 @@ def do_evaluation_upload_xls(): moduleimpl_id=mod["moduleimpl_id"], _external=True, ) - sco_news.add( - typ=sco_news.NEWS_NOTE, - object=M["moduleimpl_id"], + ScolarNews.add( + typ=ScolarNews.NEWS_NOTE, + obj=M["moduleimpl_id"], text='Chargement notes dans %(titre)s' % mod, url=mod["url"], + max_frequency=30 * 60, # 30 minutes ) msg = ( @@ -359,11 +361,12 @@ def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False): scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"], ) - sco_news.add( - typ=sco_news.NEWS_NOTE, - object=M["moduleimpl_id"], + ScolarNews.add( + typ=ScolarNews.NEWS_NOTE, + obj=M["moduleimpl_id"], text='Initialisation notes dans %(titre)s' % mod, url=mod["url"], + max_frequency=30 * 60, ) return ( html_sco_header.sco_header() @@ -451,9 +454,9 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False): mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0] mod["moduleimpl_id"] = M["moduleimpl_id"] mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod - sco_news.add( - typ=sco_news.NEWS_NOTE, - object=M["moduleimpl_id"], + ScolarNews.add( + typ=ScolarNews.NEWS_NOTE, + obj=M["moduleimpl_id"], text='Suppression des notes d\'une évaluation dans %(titre)s' % mod, url=mod["url"], @@ -893,10 +896,12 @@ def has_existing_decision(M, E, etudid): def saisie_notes(evaluation_id, group_ids=[]): """Formulaire saisie notes d'une évaluation pour un groupe""" + if not isinstance(evaluation_id, int): + raise ScoInvalidParamError() group_ids = [int(group_id) for group_id in group_ids] evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id}) if not evals: - raise ScoValueError("invalid evaluation_id") + raise ScoValueError("évaluation inexistante") E = evals[0] M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0] formsemestre_id = M["formsemestre_id"] @@ -1283,9 +1288,9 @@ def save_note(etudid=None, evaluation_id=None, value=None, comment=""): nbchanged, _, existing_decisions = notes_add( authuser, evaluation_id, L, comment=comment, do_it=True ) - sco_news.add( - typ=sco_news.NEWS_NOTE, - object=M["moduleimpl_id"], + ScolarNews.add( + typ=ScolarNews.NEWS_NOTE, + obj=M["moduleimpl_id"], text='Chargement notes dans %(titre)s' % Mod, url=Mod["url"], max_frequency=30 * 60, # 30 minutes diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py index db775438c..6483ffcff 100644 --- a/app/scodoc/sco_synchro_etuds.py +++ b/app/scodoc/sco_synchro_etuds.py @@ -29,12 +29,14 @@ """ import time -import pprint from operator import itemgetter from flask import g, url_for from flask_login import current_user +from app import log +from app.models import ScolarNews + import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc import html_sco_header @@ -43,11 +45,8 @@ from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_groups from app.scodoc import sco_inscr_passage -from app.scodoc import sco_news -from app.scodoc import sco_excel from app.scodoc import sco_portal_apogee from app.scodoc import sco_etud -from app import log from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_permissions import Permission @@ -701,10 +700,10 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident): sco_cache.invalidate_formsemestre() raise - sco_news.add( - typ=sco_news.NEWS_INSCR, + ScolarNews.add( + typ=ScolarNews.NEWS_INSCR, text="Import Apogée de %d étudiants en " % len(created_etudids), - object=sem["formsemestre_id"], + obj=sem["formsemestre_id"], ) diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index 57a951b3a..5a244863d 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -487,6 +487,16 @@ div.news { border-radius: 8px; } +div.news a { + color: black; + text-decoration: none; +} + +div.news a:hover { + color: rgb(153, 51, 51); + text-decoration: underline; +} + span.newstitle { font-weight: bold; } @@ -987,9 +997,34 @@ span.wtf-field ul.errors li { font-weight: bold; } -.configuration_logo div.img {} +.configuration_logo summary { + display: list-item !important; +} -.configuration_logo div.img-container { +.configuration_logo h1 { + display: inline-block; +} + +.configuration_logo h2 { + display: inline-block; +} + +.configuration_logo h3 { + display: inline-block; +} + +.configuration_logo details>*:not(summary) { + margin-left: 32px; +} + +.configuration_logo .content { + display: grid; + grid-template-columns: auto auto 1fr; +} + +.configuration_logo .image_logo { + vertical-align: top; + grid-column: 1/2; width: 256px; } @@ -997,8 +1032,27 @@ span.wtf-field ul.errors li { max-width: 100%; } -.configuration_logo div.img-data { - vertical-align: top; +.configuration_logo .infos_logo { + grid-column: 2/3; +} + +.configuration_logo .actions_logo { + grid-column: 3/5; + display: grid; + grid-template-columns: auto auto; + grid-column-gap: 10px; + align-self: start; + grid-row-gap: 10px; +} + +.configuration_logo .actions_logo .action_label { + grid-column: 1/2; + grid-template-columns: auto auto; +} + +.configuration_logo .actions_logo .action_button { + grid-column: 2/3; + align-self: start; } .configuration_logo logo-edit titre { @@ -2432,6 +2486,12 @@ span.bul_minmax:before { content: " "; } +a.invisible_link, +a.invisible_link:hover { + text-decoration: none; + color: rgb(20, 30, 30); +} + a.bull_link { text-decoration: none; color: rgb(20, 30, 30); @@ -3841,4 +3901,12 @@ table.evaluations_recap tr.evaluation.incomplete td a { table.evaluations_recap tr.evaluation.incomplete td a.incomplete { font-weight: bold; +} + +table.evaluations_recap td.inscrits, +table.evaluations_recap td.manquantes, +table.evaluations_recap td.nb_abs, +table.evaluations_recap td.nb_att, +table.evaluations_recap td.nb_exc { + text-align: center; } \ No newline at end of file diff --git a/app/templates/config_logos.html b/app/templates/config_logos.html index f4bd543cb..a4974ca78 100644 --- a/app/templates/config_logos.html +++ b/app/templates/config_logos.html @@ -20,73 +20,70 @@ {% endmacro %} {% macro render_add_logo(add_logo_form) %} -
-

Ajouter un logo

- {{ add_logo_form.hidden_tag() }} - {{ render_field(add_logo_form.name) }} - {{ render_field(add_logo_form.upload) }} - {{ render_field(add_logo_form.do_insert, False, onSubmit="submit_form") }} -
+
+ +

Ajouter un logo

+
+
+ {{ render_field(add_logo_form.name) }} + {{ render_field(add_logo_form.upload) }} + {{ render_field(add_logo_form.do_insert, False, onSubmit="submit_form") }} +
+
{% endmacro %} {% macro render_logo(dept_form, logo_form) %} -
- {{ logo_form.hidden_tag() }} - {% if logo_form.titre %} - - -
-

{{ logo_form.titre }}

-
-
{{ logo_form.description or "" }}
- - - {% else %} - - - -

Logo personalisé: {{ logo_form.logo_id.data }}

-
- {{ logo_form.description or "" }} - - - {% endif %} - - -
+
+ {{ logo_form.hidden_tag() }} + + {% if logo_form.titre %} + + {% if logo_form.description %} +
{{ logo_form.description }}
+ {% endif %} + {% else %} + + {% if logo_form.description %} +
{{ logo_form.description }}
+ {% endif %} + {% endif %} +
+
+ - - -

{{ logo_form.logo.logoname }} (Format: {{ logo_form.logo.suffix }})

- Taille: {{ logo_form.logo.size }} px - {% if logo_form.logo.mm %}   /   {{ logo_form.logo.mm }} mm {% endif %}
- Aspect ratio: {{ logo_form.logo.aspect_ratio }}
- Usage: {{ logo_form.logo.get_usage() }} - - -

Modifier l'image

- {{ render_field(logo_form.upload, False, onchange="submit_form()") }} - {% if logo_form.can_delete %} -

Supprimer l'image

- {{ render_field(logo_form.do_delete, False, onSubmit="submit_form()") }} - {% endif %} - - -
+ + +
+ {% endmacro %} {% macro render_logos(dept_form) %} - {% for logo_entry in dept_form.logos.entries %} {% set logo_form = logo_entry.form %} {{ render_logo(dept_form, logo_form) }} {% else %} -

-

Aucun logo défini en propre à ce département

+

{% endfor %} -
{% endmacro %} {% block app_content %} @@ -100,25 +97,26 @@ diff --git a/app/templates/dept_news.html b/app/templates/dept_news.html new file mode 100644 index 000000000..23e3f26c8 --- /dev/null +++ b/app/templates/dept_news.html @@ -0,0 +1,47 @@ +{# -*- mode: jinja-html -*- #} +{% extends "sco_page.html" %} +{% block styles %} +{{super()}} +{% endblock %} + +{% block app_content %} +

Opérations dans le département {{g.scodoc_dept}}

+ + + + + + + + + + + + +
DateTypeAuteurDétail
+{% endblock %} + + +{% block scripts %} +{{super()}} + +{% endblock %} diff --git a/app/views/notes.py b/app/views/notes.py index 82c455393..688811c09 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -1738,12 +1738,13 @@ def evaluation_listenotes(): evaluation_id = None moduleimpl_id = None vals = scu.get_request_args() - if "evaluation_id" in vals: - evaluation_id = int(vals["evaluation_id"]) - mode = "eval" - if "moduleimpl_id" in vals and vals["moduleimpl_id"]: - moduleimpl_id = int(vals["moduleimpl_id"]) - mode = "module" + try: + if "evaluation_id" in vals: + evaluation_id = int(vals["evaluation_id"]) + if "moduleimpl_id" in vals and vals["moduleimpl_id"]: + moduleimpl_id = int(vals["moduleimpl_id"]) + except ValueError as exc: + raise ScoValueError("adresse invalide !") from exc format = vals.get("format", "html") html_content, page_title = sco_liste_notes.do_evaluation_listenotes( diff --git a/app/views/scolar.py b/app/views/scolar.py index 1b88575e4..de987cb17 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -41,6 +41,7 @@ from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileAllowed from wtforms import SubmitField +from app import db from app import log from app.decorators import ( scodoc, @@ -52,8 +53,10 @@ from app.decorators import ( ) from app.models.etudiants import Identite from app.models.etudiants import make_etud_args +from app.models.events import ScolarNews from app.views import scolar_bp as bp +from app.views import ScoData import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb @@ -339,6 +342,67 @@ def install_info(): return sco_up_to_date.is_up_to_date() +@bp.route("/dept_news") +@scodoc +@permission_required(Permission.ScoView) +def dept_news(): + "Affiche table des dernières opérations" + return render_template( + "dept_news.html", title=f"Opérations {g.scodoc_dept}", sco=ScoData() + ) + + +@bp.route("/dept_news_json") +@scodoc +@permission_required(Permission.ScoView) +def dept_news_json(): + "Table des news du département" + start = request.args.get("start", type=int) + length = request.args.get("length", type=int) + + log(f"dept_news_json( start={start}, length={length})") + query = ScolarNews.query.filter_by(dept_id=g.scodoc_dept_id) + # search + search = request.args.get("search[value]") + if search: + query = query.filter( + db.or_( + ScolarNews.authenticated_user.like(f"%{search}%"), + ScolarNews.text.like(f"%{search}%"), + ) + ) + total_filtered = query.count() + # sorting + order = [] + i = 0 + while True: + col_index = request.args.get(f"order[{i}][column]") + if col_index is None: + break + col_name = request.args.get(f"columns[{col_index}][data]") + if col_name not in ["date", "type", "authenticated_user"]: + col_name = "date" + descending = request.args.get(f"order[{i}][dir]") == "desc" + col = getattr(ScolarNews, col_name) + if descending: + col = col.desc() + order.append(col) + i += 1 + if order: + query = query.order_by(*order) + + # pagination + query = query.offset(start).limit(length) + data = [news.to_dict() for news in query] + # response + return { + "data": data, + "recordsFiltered": total_filtered, + "recordsTotal": ScolarNews.query.count(), + "draw": request.args.get("draw", type=int), + } + + sco_publish( "/trombino", sco_trombino.trombino, Permission.ScoView, methods=["GET", "POST"] ) diff --git a/requirements-3.9.txt b/requirements-3.9.txt index b4a10ac16..2f91f07a7 100755 --- a/requirements-3.9.txt +++ b/requirements-3.9.txt @@ -1,5 +1,5 @@ alembic==1.7.5 -astroid==2.9.1 +astroid==2.11.2 attrs==21.4.0 Babel==2.9.1 blinker==1.4 @@ -35,6 +35,7 @@ isort==5.10.1 itsdangerous==2.0.1 Jinja2==3.0.3 lazy-object-proxy==1.7.1 +lxml==4.8.0 Mako==1.1.6 MarkupSafe==2.0.1 mccabe==0.6.1 @@ -53,6 +54,7 @@ pyOpenSSL==21.0.0 pyparsing==3.0.6 pytest==6.2.5 python-dateutil==2.8.2 +python-docx==0.8.11 python-dotenv==0.19.2 python-editor==1.0.4 pytz==2021.3 diff --git a/sco_version.py b/sco_version.py index 15814bf76..0f7ec6c57 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,13 +1,21 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.0a" +SCOVERSION = "9.2.1" SCONAME = "ScoDoc" SCONEWS = """

Année 2021

    +
  • ScoDoc 9.2: +
      +
    • Tableau récap. complet pour BUT et autres formations.
    • +
    • Tableau état évaluations
    • +
    • Version alpha du module "relations entreprises"
    • +
    • Export des trombinoscope en document docx
    • +
    • Très nombreux correctifs
    • +
  • ScoDoc 9.1.75: bulletins BUT pdf
  • ScoDoc 9.1.50: nombreuses amélioration gestion BUT
  • ScoDoc 9.1: gestion des formations par compétences, type BUT.
  • diff --git a/scodoc.py b/scodoc.py index c728bed98..1d3b17262 100755 --- a/scodoc.py +++ b/scodoc.py @@ -77,6 +77,7 @@ def make_shell_context(): "pp": pp, "Role": Role, "scolar": scolar, + "ScolarNews": models.ScolarNews, "scu": scu, "UniteEns": UniteEns, "User": User, diff --git a/tests/api/exemple-api-basic.py b/tests/api/exemple-api-basic.py index 0975e1003..88ba892f6 100644 --- a/tests/api/exemple-api-basic.py +++ b/tests/api/exemple-api-basic.py @@ -73,7 +73,9 @@ token = r.json()["token"] HEADERS = {"Authorization": f"Bearer {token}"} r = requests.get( - SCODOC_URL + "/ScoDoc/api/list_depts", headers=HEADERS, verify=CHECK_CERTIFICATE + SCODOC_URL + "/ScoDoc/api/list_depts", + headers=HEADERS, + verify=CHECK_CERTIFICATE, ) if r.status_code != 200: raise ScoError("erreur de connexion: vérifier adresse et identifiants") diff --git a/tests/api/setup_test_api.py b/tests/api/setup_test_api.py index 5338f6ff5..8ca163586 100644 --- a/tests/api/setup_test_api.py +++ b/tests/api/setup_test_api.py @@ -16,25 +16,22 @@ Utilisation : import os import requests -SCODOC_USER = "test" -SCODOC_PASSWORD = "test" -SCODOC_URL = "http://192.168.1.12:5000" + +SCODOC_USER = "api_tester" +SCODOC_PASSWORD = "api_tester" +SCODOC_URL = "http://deb11.viennet.net: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}"} + return {"Authorization": f"Bearer {token}"} -get_token() \ No newline at end of file + +HEADERS = get_token() diff --git a/tests/api/test_api_absences.py b/tests/api/test_api_absences.py index 3f8a8f0a4..bbe1588ef 100644 --- a/tests/api/test_api_absences.py +++ b/tests/api/test_api_absences.py @@ -18,126 +18,156 @@ Utilisation : """ import requests -from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE +from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS # absences def test_absences(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/etudid/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/absences/etudid/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/nip/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/absences/nip/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/ine/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/absences/ine/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 # absences_justify def test_absences_justify(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/etudid//abs_just_only", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/absences/etudid//abs_just_only", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/nip//abs_just_only", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/absences/nip//abs_just_only", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/ine//abs_just_only", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/absences/ine//abs_just_only", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # abs_signale def test_abs_signale(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/abs_signale?etudid=&date=&matin=&justif=" - "&description=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/absences/abs_signale?etudid=&date=&matin=&justif=" + "&description=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/abs_signale?nip=&date=&matin=&justif=" - "&description=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/absences/abs_signale?nip=&date=&matin=&justif=" + "&description=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/abs_signale?ine=&date=&matin=&justif=" - "&description=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/absences/abs_signale?ine=&date=&matin=&justif=" + "&description=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/abs_signale?ine=&date=&matin=&justif=" - "&description=&moduleimpl_id=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/absences/abs_signale?ine=&date=&matin=&justif=" + "&description=&moduleimpl_id=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 # abs_annule def test_abs_annule(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/abs_annule?etudid=&jour=&matin=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/absences/abs_annule?etudid=&jour=&matin=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/abs_annule?nip=&jour=&matin=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/absences/abs_annule?nip=&jour=&matin=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/abs_annule?ine=&jour=&matin=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/absences/abs_annule?ine=&jour=&matin=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 # abs_annule_justif def test_abs_annule_justif(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/abs_annule_justif?etudid=&jour=&matin=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/absences/abs_annule_justif?etudid=&jour=&matin=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/abs_annule_justif?nip=&jour=&matin=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/absences/abs_annule_justif?nip=&jour=&matin=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/abs_annule_justif?ine=&jour=&matin=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/absences/abs_annule_justif?ine=&jour=&matin=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # abs_groupe_etat def test_abs_groupe_etat(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/absences/abs_group_etat/?group_id=&date_debut=date_debut&date_fin=date_fin", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/absences/abs_group_etat/?group_id=&date_debut=date_debut&date_fin=date_fin", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 diff --git a/tests/api/test_api_departements.py b/tests/api/test_api_departements.py index b35e6959e..1e5e901ef 100644 --- a/tests/api/test_api_departements.py +++ b/tests/api/test_api_departements.py @@ -18,14 +18,16 @@ Utilisation : """ import requests -from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE + +from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS # departements def test_departements(): r = requests.get( SCODOC_URL + "/ScoDoc/api/departements", - headers=HEADERS, verify=CHECK_CERTIFICATE + headers=HEADERS, + verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 assert len(r.json()) == 1 @@ -34,16 +36,18 @@ def test_departements(): # liste_etudiants def test_liste_etudiants(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 assert len(r.json()) == 16 r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste/1", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/departements/TAPI/etudiants/liste/1", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 assert len(r.json()) == 16 @@ -51,9 +55,10 @@ def test_liste_etudiants(): # liste_semestres_courant def test_semestres_courant(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/TAPI/semestres_courants", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/departements/TAPI/semestres_courants", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 assert len(r.json()) == 1 @@ -61,9 +66,11 @@ def test_semestres_courant(): # referenciel_competences def test_referenciel_competences(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/departements/TAPI/formations/1/referentiel_competences", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/departements/TAPI/formations/1/referentiel_competences", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 or 204 @@ -74,21 +81,3 @@ def test_referenciel_competences(): # headers=HEADERS, verify=CHECK_CERTIFICATE # ) # assert r.status_code == 200 - - - - - - - - - - - - - - - - - - diff --git a/tests/api/test_api_etudiants.py b/tests/api/test_api_etudiants.py index f58c6e62e..9c93804ff 100644 --- a/tests/api/test_api_etudiants.py +++ b/tests/api/test_api_etudiants.py @@ -19,23 +19,34 @@ Utilisation : from random import randint import requests -from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE +from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS # etudiants def test_etudiants(): r = requests.get( SCODOC_URL + "/ScoDoc/api/etudiants", - headers=HEADERS, verify=CHECK_CERTIFICATE + 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] + etu = r.json()[randint(0, len(r.json())) - 1] - fields = ["civilite", "code_ine", "code_nip", "date_naissance", "email", "emailperso", "etudid", "nom", - "prenom", "nomprenom"] + fields = [ + "civilite", + "code_ine", + "code_nip", + "date_naissance", + "email", + "emailperso", + "etudid", + "nom", + "prenom", + "nomprenom", + ] fields_OK = True @@ -51,7 +62,8 @@ def test_etudiants(): def test_etudiants_courant(): r = requests.get( SCODOC_URL + "/ScoDoc/api/etudiants/courant", - headers=HEADERS, verify=CHECK_CERTIFICATE + headers=HEADERS, + verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 assert len(r.json()) == 0 @@ -61,7 +73,8 @@ def test_etudiants_courant(): def test_etudiant(): r = requests.get( SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1", - headers=HEADERS, verify=CHECK_CERTIFICATE + headers=HEADERS, + verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 assert len(r.json()) == 10 @@ -82,7 +95,8 @@ def test_etudiant(): def test_etudiant_formsemestres(): r = requests.get( SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/formsemestres", - headers=HEADERS, verify=CHECK_CERTIFICATE + headers=HEADERS, + verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 assert len(r.json()) == 1 @@ -110,29 +124,29 @@ def test_etudiant_formsemestres(): # assert len(r.json()) == 1 - # r = requests.get( - # SCODOC_URL + "/ScoDoc/api/etudiant/nip//formsemestre//bulletin", - # headers=HEADERS, verify=CHECK_CERTIFICATE - # ) - # assert r.status_code == 200 - # - # r = requests.get( - # SCODOC_URL + "/ScoDoc/api/etudiant/ine//formsemestre//bulletin", - # headers=HEADERS, verify=CHECK_CERTIFICATE - # ) - # assert r.status_code == 200 +# r = requests.get( +# SCODOC_URL + "/ScoDoc/api/etudiant/nip//formsemestre//bulletin", +# headers=HEADERS, verify=CHECK_CERTIFICATE +# ) +# assert r.status_code == 200 +# +# r = requests.get( +# SCODOC_URL + "/ScoDoc/api/etudiant/ine//formsemestre//bulletin", +# headers=HEADERS, verify=CHECK_CERTIFICATE +# ) +# assert r.status_code == 200 # etudiant_groups def test_etudiant_groups(): r = requests.get( SCODOC_URL + "/ScoDoc/api/etudiant/etudid/1/semestre/1/groups", - headers=HEADERS, verify=CHECK_CERTIFICATE + headers=HEADERS, + verify=CHECK_CERTIFICATE, ) assert r.status_code == 200 assert len(r.json()) == 1 - # r = requests.get( # SCODOC_URL + "/ScoDoc/api/etudiant/nip//semestre//groups", # headers=HEADERS, verify=CHECK_CERTIFICATE diff --git a/tests/api/test_api_evaluations.py b/tests/api/test_api_evaluations.py index 50b8ad52d..0d67bc1ca 100644 --- a/tests/api/test_api_evaluations.py +++ b/tests/api/test_api_evaluations.py @@ -18,41 +18,51 @@ Utilisation : """ import requests -from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE +from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS # evaluations def test_evaluations(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/evaluations/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/evaluations/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # evaluation_notes def test_evaluation_notes(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/evaluations/eval_notes/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/evaluations/eval_notes/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # evaluation_set_notes def test_evaluation_set_notes(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/evaluations/eval_set_notes?eval_id=&etudid=¬e=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/evaluations/eval_set_notes?eval_id=&etudid=¬e=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/evaluations/eval_set_notes?eval_id=&nip=¬e=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/evaluations/eval_set_notes?eval_id=&nip=¬e=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/evaluations/eval_set_notes?eval_id=&ine=¬e=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/evaluations/eval_set_notes?eval_id=&ine=¬e=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 diff --git a/tests/api/test_api_formations.py b/tests/api/test_api_formations.py index 2afe7602f..14aa22852 100644 --- a/tests/api/test_api_formations.py +++ b/tests/api/test_api_formations.py @@ -18,55 +18,65 @@ Utilisation : """ import requests -from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE +from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS # formations def test_formations(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/formations", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # formations_by_id def test_formations_by_id(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/formations/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # formation_export_by_formation_id def test_formation_export_by_formation_id(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/formation_export/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/formations/formation_export/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # formsemestre_apo def test_formsemestre_apo(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/apo/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/formations/apo/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 # moduleimpls def test_moduleimpls(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/formations/moduleimpl/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 # moduleimpls_sem def test_moduleimpls_sem(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/moduleimpl//formsemestre/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) - assert r.status_code == 200 \ No newline at end of file + SCODOC_URL + + "/ScoDoc/api/formations/moduleimpl//formsemestre/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 diff --git a/tests/api/test_api_formsemestre_.py b/tests/api/test_api_formsemestre_.py index 5cd40c2ae..d703ac7ed 100644 --- a/tests/api/test_api_formsemestre_.py +++ b/tests/api/test_api_formsemestre_.py @@ -18,50 +18,61 @@ Utilisation : """ import requests -from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE +from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS # formsemestre def test_formsemestre(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/formations/formsemestre/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/formations/formsemestre/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # etudiant_bulletin def test_etudiant_bulletin(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/formsemestre//departements//etudiant/etudid//bulletin", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/formsemestre//departements//etudiant/etudid//bulletin", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/formsemestre//departements//etudiant/nip//bulletin", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/formsemestre//departements//etudiant/nip//bulletin", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/formsemestre//departements//etudiant/ine//bulletin", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/formsemestre//departements//etudiant/ine//bulletin", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # bulletins def test_bulletins(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/formsemestre//bulletins", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/formsemestre//bulletins", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 # jury def test_jury(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/formsemestre//jury", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) - assert r.status_code == 200 \ No newline at end of file + SCODOC_URL + "/ScoDoc/api/formsemestre//jury", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 diff --git a/tests/api/test_api_jury.py b/tests/api/test_api_jury.py index f128d27d2..e9be6a017 100644 --- a/tests/api/test_api_jury.py +++ b/tests/api/test_api_jury.py @@ -18,66 +18,83 @@ Utilisation : """ import requests -from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE +from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS # jury_preparation def test_jury_preparation(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/jury/formsemestre//preparation_jury", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/jury/formsemestre//preparation_jury", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # jury_decisions def test_jury_decisions(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/jury/formsemestre//decisions_jury", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/jury/formsemestre//decisions_jury", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # set_decision_jury def test_set_decision_jury(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/jury/set_decision/etudid?etudid=&formsemestre_id=" - "&jury=&devenir=&assiduite=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/jury/set_decision/etudid?etudid=&formsemestre_id=" + "&jury=&devenir=&assiduite=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/jury/set_decision/nip?etudid=&formsemestre_id=" - "&jury=&devenir=&assiduite=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/jury/set_decision/nip?etudid=&formsemestre_id=" + "&jury=&devenir=&assiduite=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/jury/set_decision/ine?etudid=&formsemestre_id=" - "&jury=&devenir=&assiduite=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/jury/set_decision/ine?etudid=&formsemestre_id=" + "&jury=&devenir=&assiduite=", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 # annule_decision_jury def test_annule_decision_jury(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/jury/etudid//formsemestre//annule_decision", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) - assert r.status_code == 200 - - - r = requests.get( - SCODOC_URL + "/ScoDoc/api/jury/nip//formsemestre//annule_decision", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + + "/ScoDoc/api/jury/etudid//formsemestre//annule_decision", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/jury/ine//formsemestre//annule_decision", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) - assert r.status_code == 200 \ No newline at end of file + SCODOC_URL + + "/ScoDoc/api/jury/nip//formsemestre//annule_decision", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + + r = requests.get( + SCODOC_URL + + "/ScoDoc/api/jury/ine//formsemestre//annule_decision", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 diff --git a/tests/api/test_api_logos.py b/tests/api/test_api_logos.py index 22fcf709d..0e2250ffd 100644 --- a/tests/api/test_api_logos.py +++ b/tests/api/test_api_logos.py @@ -9,7 +9,7 @@ utilisation: pytest tests/api/test_api_logos.py """ -import pytest +from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS from scodoc import app from tests.unit.config_test_logos import ( diff --git a/tests/api/test_api_partitions.py b/tests/api/test_api_partitions.py index a3c33518b..626ab9e3f 100644 --- a/tests/api/test_api_partitions.py +++ b/tests/api/test_api_partitions.py @@ -18,36 +18,42 @@ Utilisation : """ import requests -from tests.api.setup_test_api import SCODOC_URL, HEADERS, CHECK_CERTIFICATE +from tests.api.setup_test_api import SCODOC_URL, CHECK_CERTIFICATE, HEADERS # partition def test_partition(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/partitions/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/partitions/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 + # etud_in_group def test_etud_in_group(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/partitions/groups/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/partitions/groups/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 r = requests.get( - SCODOC_URL + "/ScoDoc/api/partitions/groups//etat/", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) + SCODOC_URL + "/ScoDoc/api/partitions/groups//etat/", + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) assert r.status_code == 200 # set_groups def test_set_groups(): r = requests.get( - SCODOC_URL + "/ScoDoc/api/partitions/set_groups?partition_id=&groups_lists=&" + SCODOC_URL + + "/ScoDoc/api/partitions/set_groups?partition_id=&groups_lists=&" "groups_to_create=&groups_to_delete=", - headers=HEADERS, verify=CHECK_CERTIFICATE - ) - assert r.status_code == 200 \ No newline at end of file + headers=HEADERS, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200