From ca1fe679db6388e115369efecbccd1539b482d17 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 19 Jul 2022 22:17:10 +0200 Subject: [PATCH] =?UTF-8?q?WIP:=20new=20API=20revue=20et=20corrig=C3=A9e?= =?UTF-8?q?=20(en=20cours)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/__init__.py | 1 - app/api/absences.py | 181 +++++++------- app/api/debug.py | 31 --- app/api/etudiants.py | 230 ++++------------- app/api/evaluations.py | 23 +- app/api/formations.py | 55 ++-- app/api/formsemestres.py | 501 ++++++++----------------------------- app/api/partitions.py | 94 +++---- app/api/tools.py | 53 ++-- app/comp/moy_mod.py | 15 ++ app/models/absences.py | 9 - app/models/but_refcomp.py | 99 -------- app/models/formsemestre.py | 5 - app/models/groups.py | 9 +- app/models/notes.py | 23 +- app/scodoc/sco_groups.py | 6 +- 16 files changed, 370 insertions(+), 965 deletions(-) delete mode 100644 app/api/debug.py diff --git a/app/api/__init__.py b/app/api/__init__.py index c51421f0..9be4d3b6 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -31,4 +31,3 @@ from app.api import evaluations from app.api import jury from app.api import absences from app.api import logos -from app.api import debug diff --git a/app/api/absences.py b/app/api/absences.py index 2d3e3022..a352bc0c 100644 --- a/app/api/absences.py +++ b/app/api/absences.py @@ -1,15 +1,22 @@ -#################################################### Absences ######################################################### +############################################################################## +# ScoDoc +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# See LICENSE +############################################################################## +"""ScoDoc 9 API : Absences +""" from flask import jsonify from app.api import bp from app.api.errors import error_response from app.api.auth import token_auth, token_permission_required -from app.models import Identite, FormSemestre +from app.models import Identite from app.scodoc import notesdb as ndb from app.scodoc import sco_abs -from app.scodoc.sco_abs import list_abs_date, annule_absence, annule_justif, add_abslist + +# from app.scodoc.sco_abs import annule_absence, annule_justif, add_abslist from app.scodoc.sco_groups import get_group_members from app.scodoc.sco_permissions import Permission @@ -53,10 +60,10 @@ def absences(etudid: int = None): ) # Absences de l'étudiant ndb.open_db_connection() - absences = sco_abs.list_abs_date(etud.id) - for absence in absences: + abs_list = sco_abs.list_abs_date(etud.id) + for absence in abs_list: absence["jour"] = absence["jour"].isoformat() - return jsonify(absences) + return jsonify(abs_list) @bp.route("/absences/etudid//just", methods=["GET"]) @@ -158,9 +165,6 @@ def abs_groupe_etat(group_id: int, date_debut=None, date_fin=None): ... ] """ - # Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date() - - # Utilisation de la fonction get_group_members members = get_group_members(group_id) data = [] @@ -175,88 +179,89 @@ def abs_groupe_etat(group_id: int, date_debut=None, date_fin=None): return jsonify(data) -@bp.route( - "/absences/etudid//list_abs//reset_etud_abs", - methods=["POST"], - defaults={"just_or_not": 0}, -) -@bp.route( - "/absences/etudid//list_abs//reset_etud_abs/only_not_just", - methods=["POST"], - defaults={"just_or_not": 1}, -) -@bp.route( - "/absences/etudid//list_abs//reset_etud_abs/only_just", - methods=["POST"], - defaults={"just_or_not": 2}, -) -@token_auth.login_required -@token_permission_required(Permission.APIAbsChange) -def reset_etud_abs(etudid: int, list_abs, just_or_not: int = 0): - """ - Set la liste des absences d'un étudiant sur tout un semestre. - (les absences existant pour cet étudiant sur cette période sont effacées) +# XXX TODO EV: A REVOIR (data json dans le POST + modifier les routes) +# @bp.route( +# "/absences/etudid//list_abs//reset_etud_abs", +# methods=["POST"], +# defaults={"just_or_not": 0}, +# ) +# @bp.route( +# "/absences/etudid//list_abs//reset_etud_abs/only_not_just", +# methods=["POST"], +# defaults={"just_or_not": 1}, +# ) +# @bp.route( +# "/absences/etudid//list_abs//reset_etud_abs/only_just", +# methods=["POST"], +# defaults={"just_or_not": 2}, +# ) +# @token_auth.login_required +# @token_permission_required(Permission.APIAbsChange) +# def reset_etud_abs(etudid: int, list_abs: str, just_or_not: int = 0): +# """ +# Set la liste des absences d'un étudiant sur tout un semestre. +# (les absences existant pour cet étudiant sur cette période sont effacées) - etudid : l'id d'un étudiant - list_abs : json d'absences - just_or_not : 0 (pour les absences justifiées et non justifiées), - 1 (pour les absences justifiées), - 2 (pour les absences non justifiées) - """ - # Toutes les absences - if just_or_not == 0: - # suppression des absences et justificatif déjà existant pour éviter les doublons - for abs in list_abs: - # Récupération de la date au format iso - jour = abs["jour"].isoformat() - if abs["matin"] is True: - annule_absence(etudid, jour, True) - annule_justif(etudid, jour, True) - else: - annule_absence(etudid, jour, False) - annule_justif(etudid, jour, False) +# etudid : l'id d'un étudiant +# list_abs : json d'absences +# just_or_not : 0 (pour les absences justifiées et non justifiées), +# 1 (pour les absences justifiées), +# 2 (pour les absences non justifiées) +# """ +# # Toutes les absences +# if just_or_not == 0: +# # suppression des absences et justificatif déjà existant pour éviter les doublons +# for abs in list_abs: +# # Récupération de la date au format iso +# jour = abs["jour"].isoformat() +# if abs["matin"] is True: +# annule_absence(etudid, jour, True) +# annule_justif(etudid, jour, True) +# else: +# annule_absence(etudid, jour, False) +# annule_justif(etudid, jour, False) - # Ajout de la liste d'absences en base - add_abslist(list_abs) +# # Ajout de la liste d'absences en base +# add_abslist(list_abs) - # Uniquement les absences justifiées - elif just_or_not == 1: - list_abs_not_just = [] - # Trie des absences justifiées - for abs in list_abs: - if abs["estjust"] is False: - list_abs_not_just.append(abs) - # suppression des absences et justificatif déjà existant pour éviter les doublons - for abs in list_abs: - # Récupération de la date au format iso - jour = abs["jour"].isoformat() - if abs["matin"] is True: - annule_absence(etudid, jour, True) - annule_justif(etudid, jour, True) - else: - annule_absence(etudid, jour, False) - annule_justif(etudid, jour, False) +# # Uniquement les absences justifiées +# elif just_or_not == 1: +# list_abs_not_just = [] +# # Trie des absences justifiées +# for abs in list_abs: +# if abs["estjust"] is False: +# list_abs_not_just.append(abs) +# # suppression des absences et justificatif déjà existant pour éviter les doublons +# for abs in list_abs: +# # Récupération de la date au format iso +# jour = abs["jour"].isoformat() +# if abs["matin"] is True: +# annule_absence(etudid, jour, True) +# annule_justif(etudid, jour, True) +# else: +# annule_absence(etudid, jour, False) +# annule_justif(etudid, jour, False) - # Ajout de la liste d'absences en base - add_abslist(list_abs_not_just) +# # Ajout de la liste d'absences en base +# add_abslist(list_abs_not_just) - # Uniquement les absences non justifiées - elif just_or_not == 2: - list_abs_just = [] - # Trie des absences non justifiées - for abs in list_abs: - if abs["estjust"] is True: - list_abs_just.append(abs) - # suppression des absences et justificatif déjà existant pour éviter les doublons - for abs in list_abs: - # Récupération de la date au format iso - jour = abs["jour"].isoformat() - if abs["matin"] is True: - annule_absence(etudid, jour, True) - annule_justif(etudid, jour, True) - else: - annule_absence(etudid, jour, False) - annule_justif(etudid, jour, False) +# # Uniquement les absences non justifiées +# elif just_or_not == 2: +# list_abs_just = [] +# # Trie des absences non justifiées +# for abs in list_abs: +# if abs["estjust"] is True: +# list_abs_just.append(abs) +# # suppression des absences et justificatif déjà existant pour éviter les doublons +# for abs in list_abs: +# # Récupération de la date au format iso +# jour = abs["jour"].isoformat() +# if abs["matin"] is True: +# annule_absence(etudid, jour, True) +# annule_justif(etudid, jour, True) +# else: +# annule_absence(etudid, jour, False) +# annule_justif(etudid, jour, False) - # Ajout de la liste d'absences en base - add_abslist(list_abs_just) +# # Ajout de la liste d'absences en base +# add_abslist(list_abs_just) diff --git a/app/api/debug.py b/app/api/debug.py deleted file mode 100644 index c2ed2eb5..00000000 --- a/app/api/debug.py +++ /dev/null @@ -1,31 +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 -# -############################################################################## - -""" -!!! ATTENTION !!! -Fichier a utilisé uniquement à des fins de debug -""" diff --git a/app/api/etudiants.py b/app/api/etudiants.py index 46478231..bb4805de 100644 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -14,12 +14,13 @@ import app from app.api import bp from app.api.errors import error_response from app.api.auth import token_auth, token_permission_required -from app.api.tools import get_last_instance_etud_from_etudid_or_nip_or_ine +from app.api import tools from app.models import Departement, FormSemestreInscription, FormSemestre, Identite from app.scodoc import sco_bulletins from app.scodoc import sco_groups from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud from app.scodoc.sco_permissions import Permission +from app.scodoc import sco_utils as scu @bp.route("/etudiants/courants", defaults={"long": False}) @@ -106,7 +107,7 @@ def etudiant(etudid: int = None, nip: str = None, ine: str = None): "description": "" } """ - etud = get_last_instance_etud_from_etudid_or_nip_or_ine(etudid, nip, ine) + etud = tools.get_etud(etudid, nip, ine) if etud is None: return error_response( @@ -153,36 +154,39 @@ def etudiants(etudid: int = None, nip: str = None, ine: str = None): def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None): """ Liste des semestres qu'un étudiant a suivi, triés par ordre chronologique. + Attention, si accès via NIP ou INE, les semestres peuvent être de départements différents + (si l'étudiant a changé de département). L'id du département est `dept_id`. + Accès par etudid, nip ou ine Exemple de résultat : [ { + "block_moyennes": false, + "bul_bgcolor": "white", + "bul_hide_xml": false, + "date_debut_iso": "2021-09-01", + "date_debut": "01/09/2021", + "date_fin_iso": "2022-08-31", "date_fin": "31/08/2022", - "resp_can_edit": false, "dept_id": 1, + "elt_annee_apo": null, + "elt_sem_apo": null, + "ens_can_edit_eval": false, "etat": true, - "resp_can_change_ens": true, + "formation_id": 1, + "formsemestre_id": 1, + "gestion_compensation": false, + "gestion_semestrielle": false, "id": 1, "modalite": "FI", - "ens_can_edit_eval": false, - "formation_id": 1, - "gestion_compensation": false, - "elt_sem_apo": null, - "semestre_id": 1, - "bul_hide_xml": false, - "elt_annee_apo": null, - "titre": "Semestre test", - "block_moyennes": false, - "scodoc7_id": null, - "date_debut": "01/09/2021", - "gestion_semestrielle": false, - "bul_bgcolor": "white", - "formsemestre_id": 1, - "titre_num": "Semestre test semestre 1", - "date_debut_iso": "2021-09-01", - "date_fin_iso": "2022-08-31", + "resp_can_change_ens": true, + "resp_can_edit": false, "responsables": [] + "scodoc7_id": null, + "semestre_id": 1, + "titre_num": "Semestre test semestre 1", + "titre": "Semestre test", }, ... ] @@ -263,22 +267,21 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None) methods=["GET"], defaults={"version": "short", "pdf": False}, ) -# Version PDF non fonctionnelle -# @bp.route( -# "/etudiant/etudid//formsemestre//bulletin/short/pdf", -# methods=["GET"], -# defaults={"version": "short", "pdf": True}, -# ) -# @bp.route( -# "/etudiant/nip//formsemestre//bulletin/short/pdf", -# methods=["GET"], -# defaults={"version": "short", "pdf": True}, -# ) -# @bp.route( -# "/etudiant/ine//formsemestre//bulletin/short/pdf", -# methods=["GET"], -# defaults={"version": "short", "pdf": True}, -# ) +@bp.route( + "/etudiant/etudid//formsemestre//bulletin/short/pdf", + methods=["GET"], + defaults={"version": "short", "pdf": True}, +) +@bp.route( + "/etudiant/nip//formsemestre//bulletin/short/pdf", + methods=["GET"], + defaults={"version": "short", "pdf": True}, +) +@bp.route( + "/etudiant/ine//formsemestre//bulletin/short/pdf", + methods=["GET"], + defaults={"version": "short", "pdf": True}, +) @token_auth.login_required @token_permission_required(Permission.APIView) def etudiant_bulletin_semestre( # XXX TODO Ajouter la possibilité de retourner en version pdf @@ -296,150 +299,8 @@ def etudiant_bulletin_semestre( # XXX TODO Ajouter la possibilité de retourner etudid : l'etudid d'un étudiant nip : le code nip d'un étudiant ine : le code ine d'un étudiant - Exemple de résultat : - { - "version": "0", - "type": "BUT", - "date": "2022-04-27T07:18:16.450634Z", - "publie": true, - "etudiant": { - "civilite": "X", - "code_ine": "1", - "code_nip": "1", - "date_naissance": "", - "email": "SACHA.COSTA@example.com", - "emailperso": "", - "etudid": 1, - "nom": "COSTA", - "prenom": "SACHA", - "nomprenom": "Sacha COSTA", - "lieu_naissance": "", - "dept_naissance": "", - "nationalite": "", - "boursier": "", - "fiche_url": "/ScoDoc/TAPI/Scolarite/ficheEtud?etudid=1", - "photo_url": "/ScoDoc/TAPI/Scolarite/get_photo_image?etudid=1&size=small", - "id": 1, - "codepostaldomicile": "", - "paysdomicile": "", - "telephonemobile": "", - "typeadresse": "domicile", - "domicile": "", - "villedomicile": "", - "telephone": "", - "fax": "", - "description": "", - }, - "formation": { - "id": 1, - "acronyme": "BUT R&T", - "titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications", - "titre": "BUT R&T", - }, - "formsemestre_id": 1, - "etat_inscription": "I", - "options": { - "show_abs": true, - "show_abs_modules": false, - "show_ects": true, - "show_codemodules": false, - "show_matieres": false, - "show_rangs": true, - "show_ue_rangs": true, - "show_mod_rangs": true, - "show_moypromo": false, - "show_minmax": false, - "show_minmax_mod": false, - "show_minmax_eval": false, - "show_coef": true, - "show_ue_cap_details": false, - "show_ue_cap_current": true, - "show_temporary": true, - "temporary_txt": "Provisoire", - "show_uevalid": true, - "show_date_inscr": true, - }, - "ressources": { - "R101": { - "id": 1, - "titre": "Initiation aux r\u00e9seaux informatiques", - "code_apogee": null, - "url": "/ScoDoc/TAPI/Scolarite/Notes/moduleimpl_status?moduleimpl_id=1", - "moyenne": {}, - "evaluations": [ - { - "id": 1, - "description": "eval1", - "date": "2022-04-20", - "heure_debut": "08:00", - "heure_fin": "09:00", - "coef": "01.00", - "poids": { - "RT1.1": 1.0, - }, - "note": { - "value": "12.00", - "min": "00.00", - "max": "18.00", - "moy": "10.88", - }, - "url": "/ScoDoc/TAPI/Scolarite/Notes/evaluation_listenotes?evaluation_id=1", - } - ], - }, - }, - "saes": { - "SAE11": { - "id": 2, - "titre": "Se sensibiliser \u00e0 l'hygi\u00e8ne informatique et \u00e0 la cybers\u00e9curit\u00e9", - "code_apogee": null, - "url": "/ScoDoc/TAPI/Scolarite/Notes/moduleimpl_status?moduleimpl_id=2", - "moyenne": {}, - "evaluations": [], - }, - }, - "ues": { - "RT1.1": { - "id": 1, - "titre": "Administrer les r\u00e9seaux et l\u2019Internet", - "numero": 1, - "type": 0, - "color": "#B80004", - "competence": null, - "moyenne": { - "value": "08.50", - "min": "06.00", - "max": "16.50", - "moy": "11.31", - "rang": "12", - "total": 16, - }, - "bonus": "00.00", - "malus": "00.00", - "capitalise": null, - "ressources": { - "R101": {"id": 1, "coef": 12.0, "moyenne": "12.00"}, - }, - "saes": { - "SAE11": {"id": 2, "coef": 16.0, "moyenne": "~"}, - }, - "ECTS": {"acquis": 0.0, "total": 12.0}, - }, - "semestre": { - "etapes": [], - "date_debut": "2021-09-01", - "date_fin": "2022-08-31", - "annee_universitaire": "2021 - 2022", - "numero": 1, - "inscription": "", - "groupes": [], - "absences": {"injustifie": 1, "total": 2}, - "ECTS": {"acquis": 0, "total": 30.0}, - "notes": {"value": "10.60", "min": "02.40", "moy": "11.05", "max": "17.40"}, - "rang": {"value": "10", "total": 16}, - }, - }, - } + Exemple de résultat : voir https://scodoc.org/ScoDoc9API/#bulletin + """ formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404() dept = Departement.query.filter_by(id=formsemestre.dept_id).first_or_404() @@ -466,11 +327,10 @@ def etudiant_bulletin_semestre( # XXX TODO Ajouter la possibilité de retourner app.set_sco_dept(dept.acronym) if pdf: - response = make_response( - do_formsemestre_bulletinetud(formsemestre, etudid, version, "pdf") + pdf_response, _ = do_formsemestre_bulletinetud( + formsemestre, etudid, version=version, format="pdf" ) - response.headers["Content-Type"] = "application/json" - return response + return pdf_response return sco_bulletins.get_formsemestre_bulletin_etud_json( formsemestre, etud, version=version diff --git a/app/api/evaluations.py b/app/api/evaluations.py index f72b85bd..b831d088 100644 --- a/app/api/evaluations.py +++ b/app/api/evaluations.py @@ -1,10 +1,18 @@ -############################################### Evaluations ########################################################### +############################################################################## +# ScoDoc +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# See LICENSE +############################################################################## + +""" + ScoDoc 9 API : accès aux évaluations +""" + from flask import jsonify import app from app import models -from app.models import Evaluation from app.api import bp from app.api.auth import token_auth, token_permission_required from app.api.errors import error_response @@ -18,7 +26,7 @@ from app.scodoc.sco_permissions import Permission @token_permission_required(Permission.APIView) def evaluations(moduleimpl_id: int): """ - Retourne la liste des évaluations à partir de l'id d'un moduleimpl + Retourne la liste des évaluations d'un moduleimpl moduleimpl_id : l'id d'un moduleimpl @@ -40,7 +48,7 @@ def evaluations(moduleimpl_id: int): "evaluation_id": 1, "jouriso": "2022-04-20", "duree": "1h", - "descrheure": " de 08h00 \u00e0 09h00", + "descrheure": " de 08h00 à 09h00", "matin": 1, "apresmidi": 0 }, @@ -88,16 +96,11 @@ def evaluation_notes(evaluation_id: int): ... } """ - # Fonction utilisée : app.scodoc.sco_evaluation_db.do_evaluation_get_all_notes() - evaluation = models.Evaluation.query.filter_by(id=evaluation_id).first_or_404() - dept = models.Departement.query.filter_by( - id=evaluation.moduleimpl.formsemestre.dept_id - ).first() + dept = evaluation.moduleimpl.formsemestre.departement app.set_sco_dept(dept.acronym) try: - # Utilisation de la fonction do_evaluation_get_all_notes data = do_evaluation_get_all_notes(evaluation_id) except AttributeError: # ??? return error_response( diff --git a/app/api/formations.py b/app/api/formations.py index 30e77395..d0a69ab5 100644 --- a/app/api/formations.py +++ b/app/api/formations.py @@ -1,4 +1,13 @@ -##############################################" Formations ############################################################ +############################################################################## +# ScoDoc +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# See LICENSE +############################################################################## + +""" + ScoDoc 9 API : accès aux formations +""" + from flask import jsonify import app @@ -10,6 +19,7 @@ from app.models.formations import Formation from app.scodoc import sco_formations from app.scodoc.sco_permissions import Permission + @bp.route("/formations", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) @@ -17,16 +27,11 @@ def formations(): """ Retourne la liste de toutes les formations (tous départements) - Exemple de résultat : """ - # Récupération de toutes les formations - list_formations = models.Formation.query.all() - - # Mise en forme des données - data = [d.to_dict() for d in list_formations] - + data = [d.to_dict() for d in models.Formation.query] return jsonify(data) + @bp.route("/formations_ids", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) @@ -36,12 +41,7 @@ def formations_ids(): Exemple de résultat : [ 17, 99, 32 ] """ - # Récupération de toutes les formations - list_formations = models.Formation.query.all() - - # Mise en forme des données - data = [d.id for d in list_formations] - + data = [d.id for d in models.Formation.query] return jsonify(data) @@ -50,7 +50,7 @@ def formations_ids(): @token_permission_required(Permission.APIView) def formation_by_id(formation_id: int): """ - Retourne une formation en fonction d'un id donné + La formation d'id donné formation_id : l'id d'une formation @@ -58,7 +58,7 @@ def formation_by_id(formation_id: int): { "id": 1, "acronyme": "BUT R&T", - "titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications", + "titre_officiel": "Bachelor technologique réseaux et télécommunications", "formation_code": "V1RET", "code_specialite": null, "dept_id": 1, @@ -69,13 +69,8 @@ def formation_by_id(formation_id: int): "formation_id": 1 } """ - # Récupération de la formation - formation = models.Formation.query.filter_by(id=formation_id).first_or_404() - - # Mise en forme des données - data = formation.to_dict() - - return jsonify(data) + formation = models.Formation.query.get_or_404(formation_id) + return jsonify(formation.to_dict()) @bp.route( @@ -187,10 +182,8 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False): } """ formation = Formation.query.get_or_404(formation_id) - dept = models.Departement.query.filter_by(id=formation.dept_id).first() - app.set_sco_dept(dept.acronym) + app.set_sco_dept(formation.departement.acronym) try: - # Utilisation de la fonction formation_export data = sco_formations.formation_export(formation_id, export_ids) except ValueError: return error_response(500, message="Erreur inconnue") @@ -236,9 +229,8 @@ def moduleimpl(moduleimpl_id: int): } } """ - modimpl = models.ModuleImpl.query.filter_by(id=moduleimpl_id).first_or_404() - data = modimpl.to_dict() - return jsonify(data) + modimpl = models.ModuleImpl.query.get_or_404(moduleimpl_id) + return jsonify(modimpl.to_dict()) @bp.route( @@ -253,10 +245,9 @@ def referentiel_competences(formation_id: int): formation_id : l'id d'une formation - return json, ou null si pas de référentiel associé. + return null si pas de référentiel associé. """ - formation = models.Formation.query.filter_by(id=formation_id).first_or_404() - + formation = models.Formation.query.get_or_404(formation_id) if formation.referentiel_competence is None: return jsonify(None) return jsonify(formation.referentiel_competence.to_dict()) diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index e3d9a761..2924949d 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -1,5 +1,12 @@ -########################################## Formsemestres ############################################################## -import statistics +############################################################################## +# ScoDoc +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# See LICENSE +############################################################################## + +""" + ScoDoc 9 API : accès aux formsemestres +""" from flask import jsonify @@ -7,11 +14,10 @@ import app from app import models from app.api import bp from app.api.auth import token_auth, token_permission_required -from app.api.errors import error_response from app.comp import res_sem from app.comp.moy_mod import ModuleImplResults from app.comp.res_compat import NotesTableCompat -from app.models import Departement, FormSemestre, FormSemestreEtape, ModuleImpl +from app.models import Evaluation, FormSemestre, FormSemestreEtape, ModuleImpl from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json from app.scodoc.sco_groups import get_etud_groups from app.scodoc.sco_permissions import Permission @@ -22,7 +28,7 @@ import app.scodoc.sco_utils as scu @bp.route("/formsemestre/", methods=["GET"]) @token_auth.login_required @token_permission_required(Permission.APIView) -def formsemestre(formsemestre_id: int): +def formsemestre_infos(formsemestre_id: int): """ Information sur le formsemestre indiqué. @@ -59,17 +65,8 @@ def formsemestre(formsemestre_id: int): } """ - formsemestre: FormSemestre = models.FormSemestre.query.filter_by( - id=formsemestre_id - ).first_or_404() - data = formsemestre.to_dict(convert_parcours=True) - # Pour le moment on a besoin de fixer le departement - # pour accéder aux préferences - dept = Departement.query.get(formsemestre.dept_id) - app.set_sco_dept(dept.acronym) - data["annee_scolaire"] = formsemestre.annee_scolaire_str() - data["session_id"] = formsemestre.session_id() - return jsonify(data) + formsemestre: FormSemestre = models.FormSemestre.query.get_or_404(formsemestre_id) + return jsonify(formsemestre.to_dict_api()) @bp.route("/formsemestre/apo/", methods=["GET"]) @@ -92,9 +89,7 @@ def formsemestre_apo(etape_apo: str): FormSemestreEtape.formsemestre_id == FormSemestre.id, ) - return jsonify( - [formsemestre.to_dict(convert_parcours=True) for formsemestre in formsemestres] - ) + return jsonify([formsemestre.to_dict_api() for formsemestre in formsemestres]) @bp.route("/formsemestre//bulletins", methods=["GET"]) @@ -106,183 +101,10 @@ def bulletins(formsemestre_id: int): formsemestre_id : l'id d'un formesemestre - Exemple de résultat : - [ - { - "version": "0", - "type": "BUT", - "date": "2022-04-27T07:18:16.450634Z", - "publie": true, - "etudiant": { - "civilite": "X", - "code_ine": "1", - "code_nip": "1", - "date_naissance": "", - "email": "SACHA.COSTA@example.com", - "emailperso": "", - "etudid": 1, - "nom": "COSTA", - "prenom": "SACHA", - "nomprenom": "Sacha COSTA", - "lieu_naissance": "", - "dept_naissance": "", - "nationalite": "", - "boursier": "", - "fiche_url": "/ScoDoc/TAPI/Scolarite/ficheEtud?etudid=1", - "photo_url": "/ScoDoc/TAPI/Scolarite/get_photo_image?etudid=1&size=small", - "id": 1, - "codepostaldomicile": "", - "paysdomicile": "", - "telephonemobile": "", - "typeadresse": "domicile", - "domicile": "", - "villedomicile": "", - "telephone": "", - "fax": "", - "description": "" - }, - "formation": { - "id": 1, - "acronyme": "BUT R&T", - "titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications", - "titre": "BUT R&T" - }, - "formsemestre_id": 1, - "etat_inscription": "I", - "options": { - "show_abs": true, - "show_abs_modules": false, - "show_ects": true, - "show_codemodules": false, - "show_matieres": false, - "show_rangs": true, - "show_ue_rangs": true, - "show_mod_rangs": true, - "show_moypromo": false, - "show_minmax": false, - "show_minmax_mod": false, - "show_minmax_eval": false, - "show_coef": true, - "show_ue_cap_details": false, - "show_ue_cap_current": true, - "show_temporary": true, - "temporary_txt": "Provisoire", - "show_uevalid": true, - "show_date_inscr": true - }, - "ressources": { - "R101": { - "id": 1, - "titre": "Initiation aux r\u00e9seaux informatiques", - "code_apogee": null, - "url": "/ScoDoc/TAPI/Scolarite/Notes/moduleimpl_status?moduleimpl_id=1", - "moyenne": {}, - "evaluations": [ - { - "id": 1, - "description": "eval1", - "date": "2022-04-20", - "heure_debut": "08:00", - "heure_fin": "09:00", - "coef": "01.00", - "poids": { - "RT1.1": 1.0, - }, - "note": { - "value": "12.00", - "min": "00.00", - "max": "18.00", - "moy": "10.88" - }, - "url": "/ScoDoc/TAPI/Scolarite/Notes/evaluation_listenotes?evaluation_id=1" - } - ] - }, - }, - "saes": { - "SAE11": { - "id": 2, - "titre": "Se sensibiliser \u00e0 l'hygi\u00e8ne informatique et \u00e0 la cybers\u00e9curit\u00e9", - "code_apogee": null, - "url": "/ScoDoc/TAPI/Scolarite/Notes/moduleimpl_status?moduleimpl_id=2", - "moyenne": {}, - "evaluations": [] - }, - }, - "ues": { - "RT1.1": { - "id": 1, - "titre": "Administrer les r\u00e9seaux et l\u2019Internet", - "numero": 1, - "type": 0, - "color": "#B80004", - "competence": null, - "moyenne": { - "value": "08.50", - "min": "06.00", - "max": "16.50", - "moy": "11.31", - "rang": "12", - "total": 16 - }, - "bonus": "00.00", - "malus": "00.00", - "capitalise": null, - "ressources": { - "R101": { - "id": 1, - "coef": 12.0, - "moyenne": "12.00" - }, - }, - "saes": { - "SAE11": { - "id": 2, - "coef": 16.0, - "moyenne": "~" - }, - }, - "ECTS": { - "acquis": 0.0, - "total": 12.0 - } - }, - "semestre": { - "etapes": [], - "date_debut": "2021-09-01", - "date_fin": "2022-08-31", - "annee_universitaire": "2021 - 2022", - "numero": 1, - "inscription": "", - "groupes": [], - "absences": { - "injustifie": 1, - "total": 2 - }, - "ECTS": { - "acquis": 0, - "total": 30.0 - }, - "notes": { - "value": "10.60", - "min": "02.40", - "moy": "11.05", - "max": "17.40" - }, - "rang": { - "value": "10", - "total": 16 - } - } - }, - ... - ] + Exemple de résultat : liste, voir https://scodoc.org/ScoDoc9API/#bulletin """ - formsemestre = models.FormSemestre.query.filter_by( - id=formsemestre_id - ).first_or_404() - dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404() - app.set_sco_dept(dept.acronym) + formsemestre = models.FormSemestre.query.get_or_404(formsemestre_id) + app.set_sco_dept(formsemestre.departement.acronym) data = [] for etu in formsemestre.etuds: @@ -292,44 +114,6 @@ def bulletins(formsemestre_id: int): return jsonify(data) -# XXX Attendre ScoDoc 9.3 -# @bp.route("/formsemestre//jury", methods=["GET"]) -# @token_auth.login_required -# @token_permission_required(Permission.APIView) -# def jury(formsemestre_id: int): -# """ -# Retourne le récapitulatif des décisions jury - -# formsemestre_id : l'id d'un formsemestre - -# Exemple de résultat : - -# """ -# # Fonction utilisée : app.scodoc.sco_pvjury.formsemestre_pvjury() - -# formsemestre = models.FormSemestre.query.filter_by( -# id=formsemestre_id -# ).first_or_404() - -# dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404() - -# app.set_sco_dept(dept.acronym) - -# data = formsemestre_pvjury(formsemestre_id) - -# # try: -# # # Utilisation de la fonction formsemestre_pvjury -# # data = formsemestre_pvjury(formsemestre_id) -# # except AttributeError: -# # return error_response( -# # 409, -# # message="La requête ne peut être traitée en l’état actuel. \n" -# # "Veillez vérifier la conformité du 'formation_id'", -# # ) - -# return jsonify(data) - - @bp.route( "/formsemestre//programme", methods=["GET"], @@ -401,10 +185,7 @@ def formsemestre_programme(formsemestre_id: int): "modules" : [ ... les modules qui ne sont ni des SAEs ni des ressources ... ] } """ - formsemestre: FormSemestre = models.FormSemestre.query.filter_by( - id=formsemestre_id - ).first_or_404() - + formsemestre: FormSemestre = models.FormSemestre.query.get_or_404(formsemestre_id) ues = formsemestre.query_ues() m_list = { ModuleType.RESSOURCE: [], @@ -428,48 +209,37 @@ def formsemestre_programme(formsemestre_id: int): @bp.route( "/formsemestre//etudiants", methods=["GET"], - defaults={"etat": "I"}, + defaults={"etat": scu.INSCRIT}, ) @bp.route( "/formsemestre//etudiants/demissionnaires", methods=["GET"], - defaults={"etat": "D"}, + defaults={"etat": scu.DEMISSION}, ) @bp.route( "/formsemestre//etudiants/defaillants", methods=["GET"], - defaults={"etat": "DEF"}, + defaults={"etat": scu.DEF}, ) @token_auth.login_required @token_permission_required(Permission.APIView) def formsemestre_etudiants(formsemestre_id: int, etat: str): """ - Retourne la liste des étudiants d'un semestre + Retourne la liste des étudiants d'un formsemestre - formsemestre_id : l'id d'un semestre + formsemestre_id : l'id d'un formsemestre """ - # fonction to use : sco_groups.get_etud_groups - - formsemestre = models.FormSemestre.query.filter_by( + formsemestre: FormSemestre = models.FormSemestre.query.filter_by( id=formsemestre_id ).first_or_404() - # Récupération des étudiants du formsemestre - etuds = [etu.to_dict_short() for etu in formsemestre.etuds] + inscriptions = [ins for ins in formsemestre.inscriptions if ins.etat == etat] + etuds = [ins.etud.to_dict_short() for ins in inscriptions] + # Ajout des groupes de chaque étudiants + for etud in etuds: + etud["groups"] = get_etud_groups(etud["id"], formsemestre_id) - res = [] - # Trie des étudiants suivant leur état d'inscription voulu - for etu in etuds: - formsemestre_inscription = models.FormSemestreInscription.query.filter_by( - formsemestre_id=formsemestre_id, etudid=etu["id"] - ).first_or_404() - if formsemestre_inscription.etat == etat: - res.append(etu) - # Ajout des groups de chaques étudiants - for etu in res: - etu["groups"] = get_etud_groups(etu["id"], formsemestre_id) - - return jsonify(res) + return jsonify(etuds) @bp.route("/formsemestre//etat_evals", methods=["GET"]) @@ -477,154 +247,103 @@ def formsemestre_etudiants(formsemestre_id: int, etat: str): @token_permission_required(Permission.APIView) def etat_evals(formsemestre_id: int): """ - Retourne les informations sur l'état des évaluations d'un semestre donnée + Informations sur l'état des évaluations d'un formsemestre. formsemestre_id : l'id d'un semestre Exemple de résultat : - { - "RT1.1": [ - { - "id": 1, - "titre": "Initiation aux réseaux informatiques", - "evaluations": [ - { - "id": 1, - "description": null, - "datetime_epreuve": null, - "heure_fin": "09:00:00", - "coefficient": "02.00" - "comptee": "oui", - "inscrits": 16, - "manquantes": 0, - "ABS": 0, - "ATT": 0, - "EXC": 0, - "saisie_notes": { - "datetime_debut": "2021-09-11T00:00:00+02:00", - "datetime_fin": "2022-08-25T00:00:00+02:00", - "datetime_mediane": "2022-03-19T00:00:00+01:00" - } - }, - { - "id": 22, - "description": null, - "datetime_epreuve": "Tue, 31 May 2022 00:00:00 GMT", - "heure_fin": "08:00:00", - "comptee": "oui", - "inscrits": 16, - "manquantes": 0, - "ABS": 0, - "ATT": 0, - "EXC": 0, - "saisie_notes": { - "datetime_debut": "2021-09-11T00:00:00+02:00", - "datetime_fin": "2022-08-25T00:00:00+02:00", - "datetime_mediane": "2022-03-19T00:00:00+01:00" - } - }, - ] - }, - ] - } + [ + { + "id": 1, // moduleimpl_id + "titre": "Initiation aux réseaux informatiques", + "evaluations": [ + { + "id": 1, + "description": null, + "datetime_epreuve": null, + "heure_fin": "09:00:00", + "coefficient": "02.00" + "is_complete": true, + "nb_inscrits": 16, + "nb_manquantes": 0, + "ABS": 0, + "ATT": 0, + "EXC": 0, + "saisie_notes": { + "datetime_debut": "2021-09-11T00:00:00+02:00", + "datetime_fin": "2022-08-25T00:00:00+02:00", + "datetime_mediane": "2022-03-19T00:00:00+01:00" + } + }, + ... + ] + }, + ] """ - # Récupération du semestre formsemestre = FormSemestre.query.get_or_404(formsemestre_id) - - # Set du dept - dept = Departement.query.get(formsemestre.dept_id) - app.set_sco_dept(dept.acronym) - - # Récupération des Ues - list_ues = formsemestre.query_ues() - + app.set_sco_dept(formsemestre.departement.acronym) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - ues = {} + result = [] + for modimpl_id in nt.modimpls_results: + modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl_id] + modimpl = ModuleImpl.query.get_or_404(modimpl_id) + modimpl_dict = modimpl.to_dict() - for ue in list_ues: - modules = [] - mods = ue.modules + list_eval = [] + for evaluation_id in modimpl_results.evaluations_etat: + eval_etat = modimpl_results.evaluations_etat[evaluation_id] + evaluation = Evaluation.query.get_or_404(evaluation_id) + eval_dict = evaluation.to_dict() + eval_dict["etat"] = eval_etat.to_dict() - for mod in mods: - dict_module = {} - moduleimpl = ModuleImpl.query.get_or_404(mod.id) + eval_dict["nb_inscrits"] = modimpl_results.nb_inscrits_module + eval_dict["nb_notes_manquantes"] = len( + modimpl_results.evals_etudids_sans_note[evaluation.id] + ) + eval_dict["nb_notes_abs"] = sum( + modimpl_results.evals_notes[evaluation.id] == scu.NOTES_ABSENCE + ) + eval_dict["nb_notes_att"] = eval_etat.nb_attente + eval_dict["nb_notes_exc"] = sum( + modimpl_results.evals_notes[evaluation.id] == scu.NOTES_NEUTRALISE + ) - modimpl_results: ModuleImplResults = nt.modimpls_results[moduleimpl.id] + # Récupération de toutes les notes de l'évaluation + # eval["notes"] = modimpl_results.get_eval_notes_dict(evaluation_id) - dict_module["id"] = mod.id - dict_module["titre"] = mod.titre + notes = models.NotesNotes.query.filter_by(evaluation_id=evaluation.id).all() - list_eval = [] - for evaluation in moduleimpl.evaluations: - eval_etat = modimpl_results.evaluations_etat[evaluation.id] - eval = {} + date_debut = None + date_fin = None + date_mediane = None - eval["id"] = evaluation.id - eval["description"] = evaluation.description - eval["datetime_epreuve"] = ( - evaluation.jour.isoformat() if evaluation.jour is not None else None - ) - eval["heure_fin"] = evaluation.heure_fin.isoformat() - eval["coefficient"] = evaluation.coefficient - eval["comptee"] = "oui" if eval_etat.is_complete else "non" - eval["inscrits"] = modimpl_results.nb_inscrits_module - eval["manquantes"] = len( - modimpl_results.evals_etudids_sans_note[evaluation.id] - ) - eval["ABS"] = sum( - modimpl_results.evals_notes[evaluation.id] == scu.NOTES_ABSENCE - ) - eval["ATT"] = eval_etat.nb_attente - eval["EXC"] = sum( - modimpl_results.evals_notes[evaluation.id] == scu.NOTES_NEUTRALISE - ) + # Si il y a plus d'une note saisie pour l'évaluation + if len(notes) >= 1: + # Trie des notes en fonction de leurs dates + notes_sorted = sorted(notes, key=lambda note: note.date) - # Récupération de toutes les notes de l'évaluation - notes = models.NotesNotes.query.filter_by( - evaluation_id=evaluation.id - ).all() + date_debut = notes_sorted[0].date + date_fin = notes_sorted[-1].date - date_debut = None - date_fin = None - date_mediane = None + # Récupération de l'id de la note médiane + list_id_notes_sorted = [note.id for note in notes_sorted] + id_mediane = list_id_notes_sorted[len(list_id_notes_sorted) // 2] + date_mediane = notes_sorted[id_mediane].date - # Si il y a plus d'une note saisie pour l'évaluation - if len(notes) >= 1: - # Trie des notes en fonction de leurs dates - notes_sorted = sorted(notes, key=lambda note: note.date) + eval_dict["saisie_notes"] = { + "datetime_debut": date_debut.isoformat() + if date_debut is not None + else None, + "datetime_fin": date_fin.isoformat() if date_fin is not None else None, + "datetime_mediane": date_mediane.isoformat() + if date_mediane is not None + else None, + } - date_debut = notes_sorted[0].date - date_fin = notes_sorted[-1].date + list_eval.append(eval) - # Récupération de l'id de la note médiane - list_id_notes_sorted = [note.id for note in notes_sorted] + modimpl_dict["evaluations"] = list_eval + result.append(modimpl_dict) - # Ici si la longueur est paire on prend, on prend le +1 car un indice ne peux pas avoir de nombre floatant - id_mediane = list_id_notes_sorted[ - int((len(list_id_notes_sorted)) / 2) - ] - - for n in notes_sorted: - if n.id == id_mediane: - date_mediane = n.date - - eval["saisie_notes"] = { - "datetime_debut": date_debut.isoformat() - if date_debut is not None - else None, - "datetime_fin": date_fin.isoformat() - if date_fin is not None - else None, - "datetime_mediane": date_mediane.isoformat() - if date_mediane is not None - else None, - } - - list_eval.append(eval) - - dict_module["evaluations"] = list_eval - modules.append(dict_module) - ues[ue.acronyme] = modules - - return jsonify(ues) + return jsonify(result) diff --git a/app/api/partitions.py b/app/api/partitions.py index 8ab9a4da..2bb31355 100644 --- a/app/api/partitions.py +++ b/app/api/partitions.py @@ -1,12 +1,21 @@ -############################################### Partitions ############################################################ +############################################################################## +# ScoDoc +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# See LICENSE +############################################################################## + +""" + ScoDoc 9 API : partitions +""" from flask import jsonify -from app import models from app.api import bp from app.api.errors import error_response from app.api.auth import token_auth, token_permission_required -from app.scodoc.sco_groups import get_group_members, setGroups, get_partitions_list +from app.models import FormSemestre, FormSemestreInscription, Identite +from app.models import GroupDescr, Partition +from app.models.groups import group_membership from app.scodoc.sco_permissions import Permission @@ -41,15 +50,8 @@ def partition(formsemestre_id: int): } ] """ - # # Récupération de toutes les partitions - # partitions = models.Partition.query.filter_by(id=formsemestre_id) - # - # # Mise en forme des données - # data = [partition.to_dict() for partition in partitions] - - data = get_partitions_list(formsemestre_id) - - return jsonify(data) + formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) + return jsonify([partition.to_dict() for partition in formsemestre.partitions]) @bp.route("/partition/group/", methods=["GET"]) @@ -64,58 +66,32 @@ def etud_in_group(group_id: int, etat=None): etat : état de l'inscription Exemple de résultat : - [ - { - "etudid": 10, - "id": 10, - "dept_id": 1, - "nom": "BOUTET", - "prenom": "Marguerite", - "nom_usuel": "", - "civilite": "F", - "date_naissance": null, - "lieu_naissance": null, - "dept_naissance": null, - "nationalite": null, - "statut": null, - "boursier": null, - "photo_filename": null, - "code_nip": "10", - "code_ine": "10", - "scodoc7_id": null, - "email": "MARGUERITE.BOUTET@example.com", - "emailperso": null, - "domicile": null, - "codepostaldomicile": null, - "villedomicile": null, - "paysdomicile": null, - "telephone": null, - "telephonemobile": null, - "fax": null, - "typeadresse": "domicile", - "description": null, - "group_id": 1, - "etat": "I", - "civilite_str": "Mme", - "nom_disp": "BOUTET", - "nomprenom": "Mme Marguerite BOUTET", - "ne": "e", - "email_default": "MARGUERITE.BOUTET@example.com" - }, - ... - ] + [ + { + 'civilite': 'M', + 'id': 123456, + 'ine': None, + 'nip': '987654321', + 'nom': 'MARTIN', + 'nom_usuel': null, + 'prenom': 'JEAN'} + }, + ... + ] """ # Fonction utilisée : app.scodoc.sco_groups.get_group_members() - + group = GroupDescr.query.get_or_404(group_id) if etat is None: - data = get_group_members(group_id) + query = group.etuds else: - data = get_group_members(group_id, etat) + query = ( + Identite.query.join(FormSemestreInscription) + .filter_by(formsemestre_id=group.partition.formsemestre_id, etat=etat) + .join(group_membership) + .filter_by(group_id=group_id) + ) - if len(data) == 0: - return error_response(404, message="group_id inconnu") - - return jsonify(data) + return jsonify([etud.to_dict_short() for etud in query]) @bp.route( diff --git a/app/api/tools.py b/app/api/tools.py index 462a5cbf..d2a4d1d6 100644 --- a/app/api/tools.py +++ b/app/api/tools.py @@ -1,14 +1,22 @@ +############################################################################## +# ScoDoc +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# See LICENSE +############################################################################## +"""ScoDoc 9 API : outils +""" + +from sqlalchemy import desc + from app import models - from app.api.errors import error_response -from app.models import Identite +from app.models import Identite, Admission -def get_last_instance_etud_from_etudid_or_nip_or_ine( - etudid=None, nip=None, ine=None -) -> models.Identite: +def get_etud(etudid=None, nip=None, ine=None) -> models.Identite: """ - Retourne l'instance de l'etudiant la plus récente en fonction de l'etudid, code nip et code ine rentré en paramètres + L'instance d'étudiant la plus récente en fonction de l'etudid, + ou du code nip ou code ine. etudid : None ou un int etudid nip : None ou un int code_nip @@ -17,26 +25,15 @@ def get_last_instance_etud_from_etudid_or_nip_or_ine( Return None si étudiant inexistant. """ if etudid is not None: - etud = Identite.query.get(etudid) + return Identite.query.get(etudid) + + if nip is not None: + query = Identite.query.filter_by(code_nip=nip) + elif ine is not None: + query = Identite.query.filter_by(code_ine=ine) else: - if nip is not None: - query = Identite.query.filter_by(code_nip=nip) - elif ine is not None: - query = Identite.query.filter_by(code_ine=ine) - else: - return error_response( - 404, - message="parametre manquant", - ) - if query.count() > 1: # cas rare d'un étudiant présent dans plusieurs depts - etuds = [] - for e in query: - admission = e.admission.first() - etuds.append((((admission.annee or 0) if admission else 0), e)) - etuds.sort() - etud = etuds[-1][1] - else: - etud = query.first() - - return etud - + return error_response( + 404, + message="parametre manquant", + ) + return query.join(Admission).order_by(desc(Admission.annee)).first() diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py index aba032fd..7ea795f1 100644 --- a/app/comp/moy_mod.py +++ b/app/comp/moy_mod.py @@ -33,6 +33,7 @@ Rappel: pour éviter les confusions, on appelera *poids* les coefficients d'une évaluation dans un module, et *coefficients* ceux utilisés pour le calcul de la moyenne générale d'une UE. """ +import dataclasses from dataclasses import dataclass import numpy as np import pandas as pd @@ -54,6 +55,10 @@ class EvaluationEtat: nb_attente: int is_complete: bool + def to_dict(self): + "convert to dict" + return dataclasses.asdict(self) + class ModuleImplResults: """Classe commune à toutes les formations (standard et APC). @@ -236,6 +241,16 @@ class ModuleImplResults: self.evals_notes.values > scu.NOTES_ABSENCE, self.evals_notes.values, 0.0 ) / [e.note_max / 20.0 for e in moduleimpl.evaluations] + def get_eval_notes_dict(self, evaluation_id: int) -> dict: + """Notes d'une évaulation, brutes, sous forme d'un dict + { etudid : valeur } + avec les valeurs float, ou "ABS" ou EXC + """ + return { + etudid: scu.fmt_note(x, keep_numeric=True) + for (etudid, x) in self.evals_notes[evaluation_id].items() + } + def get_evaluation_rattrapage(self, moduleimpl: ModuleImpl): """L'évaluation de rattrapage de ce module, ou None s'il n'en a pas. Rattrapage: la moyenne du module est la meilleure note entre moyenne diff --git a/app/models/absences.py b/app/models/absences.py index 881a81e3..405ea6bf 100644 --- a/app/models/absences.py +++ b/app/models/absences.py @@ -29,15 +29,6 @@ class Absence(db.Model): # XXX TODO: contrainte ajoutée: vérifier suppression du module # (mettre à NULL sans supprimer) - def __init__(self, id, etudid, jour, estabs, estjust, matin, description): - self.id = id - self.etudid = etudid - self.jour = jour - self.estabs = estabs - self.estjust = estjust - self.matin = matin - self.description = description - def to_dict(self): data = { "id": self.id, diff --git a/app/models/but_refcomp.py b/app/models/but_refcomp.py index 62710eee..0a4afd07 100644 --- a/app/models/but_refcomp.py +++ b/app/models/but_refcomp.py @@ -83,40 +83,6 @@ class ApcReferentielCompetences(db.Model, XMLModel): ) formations = db.relationship("Formation", backref="referentiel_competence") - def __init__( - self, - id, - dept_id, - annexe, - specialite, - specialite_long, - type_titre, - type_structure, - type_departement, - version_orebut, - _xml_attribs, - #scodoc_date_loaded, - scodoc_orig_filename, - # competences, - # parcours, - # formations, - ): - self.id = id - self.dept_id = dept_id - self.annexe = annexe - self.specialite = specialite - self.specialite_long = specialite_long - self.type_titre = type_titre - self.type_structure = type_structure - self.type_departement = type_departement - self.version_orebut = version_orebut - self._xml_attribs = _xml_attribs - #self.scodoc_date_loaded = scodoc_date_loaded - self.scodoc_orig_filename = scodoc_orig_filename - # self.competences = competences - # self.parcours = parcours - # self.formations = formations - def __repr__(self): return f"" @@ -225,32 +191,6 @@ class ApcCompetence(db.Model, XMLModel): cascade="all, delete-orphan", ) - def __init__( - self, - id, - referentiel_id, - id_orebut, - titre, - titre_long, - couleur, - numero, - _xml_attribs, - situations, - composantes_essentielles, - niveaux, - ): - self.id = id - self.referentiel_id = referentiel_id - self.id_orebut = id_orebut - self.titre = titre - self.titre_long = titre_long - self.couleur = couleur - self.numero = numero - self._xml_attribs = _xml_attribs - self.situations = situations - self.composantes_essentielles = composantes_essentielles - self.niveaux = niveaux - def __repr__(self): return f"" @@ -288,12 +228,6 @@ class ApcSituationPro(db.Model, XMLModel): ) libelle = db.Column(db.Text(), nullable=False) # aucun attribut (le text devient le libellé) - - def __init__(self, id, competence_id, libelle): - self.id = id - self.competence_id = competence_id - self.libelle = libelle - def to_dict(self): return {"libelle": self.libelle} @@ -306,11 +240,6 @@ class ApcComposanteEssentielle(db.Model, XMLModel): ) libelle = db.Column(db.Text(), nullable=False) - def __init__(self, id, competence_id, libelle): - self.id = id - self.competence_id = competence_id - self.libelle = libelle - def to_dict(self): return {"libelle": self.libelle} @@ -339,14 +268,6 @@ class ApcNiveau(db.Model, XMLModel): ) ues = db.relationship("UniteEns", back_populates="niveau_competence") - def __init__(self, id, competence_id, libelle, annee, ordre, app_critiques): - self.id = id - self.competence_id = competence_id - self.libelle = libelle - self.annee = annee - self.ordre = ordre - self.app_critiques = app_critiques - def __repr__(self): return f"""<{self.__class__.__name__} {self.id} ordre={self.ordre!r} annee={ self.annee!r} {self.competence!r}>""" @@ -452,13 +373,6 @@ class ApcAppCritique(db.Model, XMLModel): query = query.filter(ApcNiveau.competence == competence) return query - def __init__(self, id, niveau_id, code, libelle, modules): - self.id = id - self.niveau_id = niveau_id - self.code = code - self.libelle = libelle - self.modules = modules - def to_dict(self) -> dict: return {"libelle": self.libelle} @@ -517,14 +431,6 @@ class ApcParcours(db.Model, XMLModel): cascade="all, delete-orphan", ) - def __init__(self, id, referentiel_id, numero, code, libelle, annes): - self.id = id - self.referentiel_id = referentiel_id - self.numero = numero - self.code = code - self.libelle = libelle - self.annes = annes - def __repr__(self): return f"<{self.__class__.__name__} {self.id} {self.code!r} ref={self.referentiel}>" @@ -545,11 +451,6 @@ class ApcAnneeParcours(db.Model, XMLModel): ordre = db.Column(db.Integer) "numéro de l'année: 1, 2, 3" - def __init__(self, id, parcours_id, ordre): - self.id = id - self.parcours_id = parcours_id - self.ordre = ordre - def __repr__(self): return f"<{self.__class__.__name__} {self.id} ordre={self.ordre!r} parcours={self.parcours.code!r}>" diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 68379e8b..4887c7b1 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -654,11 +654,6 @@ class FormSemestreEtape(db.Model): # etape_apo aurait du etre not null, mais oublié etape_apo = db.Column(db.String(APO_CODE_STR_LEN), index=True) - def __init__(self, id, formsemestre_id, etape_apo): - self.id = id - self.formsemestre_id = formsemestre_id - self.etape_apo = etape_apo - def __bool__(self): "Etape False if code empty" return self.etape_apo is not None and (len(self.etape_apo) > 0) diff --git a/app/models/groups.py b/app/models/groups.py index 27b763d1..4eca1ca7 100644 --- a/app/models/groups.py +++ b/app/models/groups.py @@ -58,12 +58,9 @@ class Partition(db.Model): def to_dict(self, with_groups=False) -> dict: """as a dict, with or without groups""" - d = { - "id": self.id, - "formsemestre_id": self.partition_id, - "name": self.partition_name, - "numero": self.numero, - } + d = dict(self.__dict__) + d.pop("_sa_instance_state", None) + if with_groups: d["groups"] = [group.to_dict(with_partition=False) for group in self.groups] return d diff --git a/app/models/notes.py b/app/models/notes.py index b3595126..8d152a58 100644 --- a/app/models/notes.py +++ b/app/models/notes.py @@ -47,24 +47,11 @@ class NotesNotes(db.Model): date = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) uid = db.Column(db.Integer, db.ForeignKey("user.id")) - def __init__(self, etudid, evaluation_id, value, comment, date, uid): - self.etudid = etudid - self.evaluation_id = evaluation_id - self.value = value - self.comment = comment - self.date = date - self.uid = uid - - def to_dict(self): - return { - "id": self.id, - "etudid": self.etudid, - "evaluation_id": self.evaluation_id, - "value": self.value, - "comment": self.comment, - "date": self.date, - "uid": self.uid, - } + def to_dict(self) -> dict: + "dict" + d = dict(self.__dict__) + d.pop("_sa_instance_state", None) + return d class NotesNotesLog(db.Model): diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index d2cd636c..e0561334 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -136,7 +136,7 @@ def get_partition(partition_id): return r[0] -def get_partitions_list(formsemestre_id, with_default=True): +def get_partitions_list(formsemestre_id, with_default=True) -> list[dict]: """Liste des partitions pour ce semestre (list of dicts)""" partitions = ndb.SimpleDictFetch( """SELECT p.id AS partition_id, p.* @@ -146,9 +146,9 @@ def get_partitions_list(formsemestre_id, with_default=True): {"formsemestre_id": formsemestre_id}, ) # Move 'all' at end of list (for menus) - R = [p for p in partitions if p["partition_name"] != None] + R = [p for p in partitions if p["partition_name"] is not None] if with_default: - R += [p for p in partitions if p["partition_name"] == None] + R += [p for p in partitions if p["partition_name"] is None] return R