ajout du dept_acronym dans le résultat de la requête /etudiant/nip/<int:nip>

This commit is contained in:
leonard_montalbano 2022-05-13 09:50:03 +02:00
commit 4250d33cf5
21 changed files with 496 additions and 507 deletions

View File

@ -5,73 +5,62 @@ 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.api.tools import get_etud_from_etudid_or_nip_or_ine
from app.scodoc import notesdb as ndb
from app.models import Identite
from app.scodoc import notesdb as ndb
from app.scodoc import sco_abs
from app.scodoc.sco_groups import get_group_members
from app.scodoc.sco_permissions import Permission
@bp.route("/absences/etudid/<int:etudid>", methods=["GET"])
@bp.route("/absences/nip/<int:nip>", methods=["GET"])
@bp.route("/absences/ine/<int:ine>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def absences(etudid: int = None, nip: int = None, ine: int = None):
def absences(etudid: int = None):
"""
Retourne la liste des absences d'un étudiant donné
etudid : l'etudid d'un étudiant
nip: le code nip d'un étudiant
ine : le code ine d'un étudiant
Exemple de résultat:
[
{
"jour": "2022-04-15",
"matin": true,
"estabs": true,
"estjust": true,
"description": "",
"begin": "2022-04-15 08:00:00",
"end": "2022-04-15 11:59:59"
},
{
"jour": "2022-04-15",
"matin": false,
"estabs": true,
"estjust": false,
"description": "",
"begin": "2022-04-15 12:00:00",
"end": "2022-04-15 17:59:59"
}
]
[
{
"jour": "2022-04-15",
"matin": true,
"estabs": true,
"estjust": true,
"description": "",
"begin": "2022-04-15 08:00:00",
"end": "2022-04-15 11:59:59"
},
{
"jour": "2022-04-15",
"matin": false,
"estabs": true,
"estjust": false,
"description": "",
"begin": "2022-04-15 12:00:00",
"end": "2022-04-15 17:59:59"
}
]
"""
if etudid is None:
# Récupération de l'étudiant
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
)
etudid = etud.etudid
# Récupération des absences de l'étudiant
etud = Identite.query.get(etudid)
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
)
# Absences de l'étudiant
ndb.open_db_connection()
absences = sco_abs.list_abs_date(etudid)
absences = sco_abs.list_abs_date(etud.id)
for absence in absences:
absence["jour"] = absence["jour"].isoformat()
return jsonify(absences)
@bp.route("/absences/etudid/<int:etudid>/just", methods=["GET"])
@bp.route("/absences/nip/<int:nip>/just", methods=["GET"])
@bp.route("/absences/ine/<int:ine>/just", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def absences_just(etudid: int = None, nip: int = None, ine: int = None):
def absences_just(etudid: int = None):
"""
Retourne la liste des absences justifiées d'un étudiant donné
@ -80,108 +69,77 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None):
ine : le code ine d'un étudiant
Exemple de résultat :
[
{
"jour": "2022-04-15",
"matin": true,
"estabs": true,
"estjust": true,
"description": "",
"begin": "2022-04-15 08:00:00",
"end": "2022-04-15 11:59:59"
},
{
"jour": "Fri, 15 Apr 2022 00:00:00 GMT",
"matin": false,
"estabs": true,
"estjust": true,
"description": "",
"begin": "2022-04-15 12:00:00",
"end": "2022-04-15 17:59:59"
}
]
[
{
"jour": "2022-04-15",
"matin": true,
"estabs": true,
"estjust": true,
"description": "",
"begin": "2022-04-15 08:00:00",
"end": "2022-04-15 11:59:59"
},
{
"jour": "Fri, 15 Apr 2022 00:00:00 GMT",
"matin": false,
"estabs": true,
"estjust": true,
"description": "",
"begin": "2022-04-15 12:00:00",
"end": "2022-04-15 17:59:59"
}
]
"""
if etudid is None:
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
)
etudid = etud.etudid
etud = Identite.query.get(etudid)
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
)
# Récupération des absences justifiées de l'étudiant
# Absences justifiées de l'étudiant
abs_just = [
absence for absence in sco_abs.list_abs_date(etudid) if absence["estjust"]
absence for absence in sco_abs.list_abs_date(etud.id) if absence["estjust"]
]
for absence in abs_just:
absence["jour"] = absence["jour"].isoformat()
return jsonify(abs_just)
@bp.route(
"/absences/abs_group_etat/<int:group_id>",
methods=["GET"],
)
@bp.route(
"/absences/abs_group_etat/group_id/<int:group_id>/date_debut/<string:date_debut>/date_fin/<string:date_fin>",
methods=["GET"],
)
@token_auth.login_required
@token_permission_required(Permission.APIView)
def abs_groupe_etat(group_id: int, date_debut=None, date_fin=None):
"""
Liste des absences d'un groupe (possibilité de choisir entre deux dates)
# XXX TODO INACHEVEE
# @bp.route(
# "/absences/abs_group_etat/<int:group_id>",
# methods=["GET"],
# )
# @bp.route(
# "/absences/abs_group_etat/group_id/<int:group_id>/date_debut/<string:date_debut>/date_fin/<string:date_fin>",
# methods=["GET"],
# )
# @token_auth.login_required
# @token_permission_required(Permission.APIView)
# def abs_groupe_etat( # XXX A REVOIR XXX
# group_id: int, date_debut, date_fin, with_boursier=True, format="html"
# ):
# """
# Liste des absences d'un ou plusieurs groupes entre deux dates
# """
# return error_response(501, message="Not implemented")
group_id = l'id du groupe
date_debut = None par défaut, sinon la date ISO du début de notre filtre
date_fin = None par défaut, sinon la date ISO de la fin de notre filtre
# # Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date()
Exemple de résultat :
[
{
"etudid": 1,
"list_abs": []
},
{
"etudid": 2,
"list_abs": [
{
"jour": "Fri, 15 Apr 2022 00:00:00 GMT",
"matin": true,
"estabs": true,
"estjust": true,
"description": "",
"begin": "2022-04-15 08:00:00",
"end": "2022-04-15 11:59:59"
},
{
"jour": "Fri, 15 Apr 2022 00:00:00 GMT",
"matin": false,
"estabs": true,
"estjust": false,
"description": "",
"begin": "2022-04-15 12:00:00",
"end": "2022-04-15 17:59:59"
},
]
},
...
]
"""
# Fonction utilisée : app.scodoc.sco_groups.get_group_members() et app.scodoc.sco_abs.list_abs_date()
# try:
# # Utilisation de la fonction get_group_members
# members = get_group_members(group_id)
# except ValueError:
# return error_response(
# 404, message="La requête ne peut être traitée en létat actuel"
# )
# Utilisation de la fonction get_group_members
members = get_group_members(group_id)
# data = []
# # Filtre entre les deux dates renseignées
# for member in members:
# abs = sco_abs.list_abs_date(member.id, date_debut, date_fin)
# data.append(abs)
data = []
# Filtre entre les deux dates renseignées
for member in members:
print(member)
abs = {
"etudid": member["etudid"],
"list_abs": sco_abs.list_abs_date(member["etudid"], date_debut, date_fin),
}
data.append(abs)
return jsonify(data)
# # return jsonify(data) # XXX TODO faire en sorte de pouvoir renvoyer sa (ex to_dict() dans absences)
# return error_response(501, message="Not implemented")

View File

@ -1,64 +1,25 @@
#################################################### Etudiants ########################################################
import json
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""
API : accès aux étudiants
"""
from flask import jsonify
import app
from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_auth, token_permission_required
from app.api.tools import get_etud_from_etudid_or_nip_or_ine
from app.models import FormSemestreInscription, FormSemestre, Identite
from app.models import Departement, FormSemestreInscription, FormSemestre, Identite
from app.scodoc import sco_bulletins
from app.scodoc import sco_groups
from app.scodoc.sco_permissions import Permission
@bp.route("/etudiant/test/etudid/<int:etudid>", methods=["GET"])
@bp.route("/etudiant/test/nip/<string:nip>", methods=["GET"])
@bp.route("/etudiant/test/ine/<string:ine>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def etudiant_test(etudid: int = None, nip: int = None, ine: int = None):
# Récupération de tous les étudiants qui portent le même code_nip
etus = models.Identite.query.filter_by(code_nip=nip).all()
# Mise en place d'une liste des différents etudid de l'étudiant
list_id_etu = []
for etu in etus:
list_id_etu.append(etu.id)
list_res = []
# Pour chaque etudid de l'étudiant
for i in range(len(list_id_etu)):
etudid = list_id_etu[i]
# on va chercher les formsemestres auquel il est inscrit
formsemestres = models.FormSemestre.query.filter(
models.FormSemestreInscription.etudid == etudid,
models.FormSemestreInscription.formsemestre_id == models.FormSemestre.id,
).order_by(models.FormSemestre.date_debut)
# récupération du dernier en date
res = formsemestres[-1]
list_res.append(res)
# trie les formsemestres trouvé de l'étudiants par date
def sort_by_key(list):
return list.date_debut
list_res_sorted = sorted(list_res, key=sort_by_key)
# récupération du dernier formsemestre en date tout départements confondu
res = list_res_sorted[-1].to_dict()
return jsonify(res)
# return error_response(504, message="not implemented")
@bp.route("/etudiants/courant", defaults={"long": False})
@bp.route("/etudiants/courant/long", defaults={"long": True})
@token_auth.login_required
@ -104,53 +65,102 @@ def etudiants_courant(long=False):
@bp.route("/etudiant/ine/<string:ine>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def etudiant(etudid: int = None, nip: int = None, ine: int = None):
def etudiant(etudid: int = None, nip: str = None, ine: str = None):
"""
Retourne les informations de l'étudiant correspondant à l'id passé en paramètres.
Retourne les informations de l'étudiant correspondant, ou 404 si non trouvé.
etudid : l'etudid d'un étudiant
nip : le code nip d'un étudiant
ine : le code ine d'un étudiant
etudid : l'etudid de l'étudiant
nip : le code nip de l'étudiant
ine : le code ine de l'étudiant
Les codes INE et NIP sont uniques au sein d'un département.
Si plusieurs objets ont le même code, on ramène le plus récemment inscrit.
Exemple de résultat :
{
"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": "",
"id": 1,
"codepostaldomicile": "",
"paysdomicile": "",
"telephonemobile": "",
"typeadresse": "domicile",
"domicile": "",
"villedomicile": "",
"telephone": "",
"fax": "",
"description": ""
}
{
"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": "",
"id": 1,
"codepostaldomicile": "",
"paysdomicile": "",
"telephonemobile": "",
"typeadresse": "domicile",
"domicile": "",
"villedomicile": "",
"telephone": "",
"fax": "",
"description": ""
}
"""
# Récupération de l'étudiant
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
if etudid is not None:
etud = Identite.query.get(etudid)
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()
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
message="étudiant inconnu",
)
# Mise en forme des données
data = etud.to_dict_bul(include_urls=False)
return jsonify(data)
return jsonify(etud.to_dict_bul(include_urls=False))
@bp.route("/etudiants/etudid/<int:etudid>", methods=["GET"])
@bp.route("/etudiants/nip/<string:nip>", methods=["GET"])
@bp.route("/etudiants/ine/<string:ine>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def etudiants(etudid: int = None, nip: str = None, ine: str = None):
"""
Info sur le ou les étudiants correspondant. Comme /etudiant mais renvoie
toujours une liste.
Si non trouvé, liste vide, pas d'erreur.
Dans 99% des cas, la liste contient un seul étudiant, mais si l'étudiant a
été inscrit dans plusieurs départements, on a plusieurs objets (1 par dept.).
"""
if etudid is not None:
query = Identite.query.filter_by(id=etudid)
elif 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",
)
return jsonify([etud.to_dict_bul(include_urls=False) for etud in query])
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
@ -160,56 +170,65 @@ def etudiant(etudid: int = None, nip: int = None, ine: int = None):
@token_permission_required(Permission.APIView)
def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None):
"""
Retourne la liste des semestres qu'un étudiant a suivis, triés par ordre chronologique.
etudid : l'etudid d'un étudiant
nip : le code nip d'un étudiant
ine : le code ine d'un étudiant
Liste des semestres qu'un étudiant a suivi, triés par ordre chronologique.
Accès par etudid, nip ou ine
Exemple de résultat :
[
{
"date_fin": "31/08/2022",
"resp_can_edit": false,
"dept_id": 1,
"etat": true,
"resp_can_change_ens": true,
"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",
"responsables": []
},
...
]
[
{
"date_fin": "31/08/2022",
"resp_can_edit": false,
"dept_id": 1,
"etat": true,
"resp_can_change_ens": true,
"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",
"responsables": []
},
...
]
"""
# Récupération de l'étudiant
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
if etud is None:
if etudid is not None:
query = FormSemestre.query.filter(
FormSemestreInscription.etudid == etudid,
FormSemestreInscription.formsemestre_id == FormSemestre.id,
)
elif nip is not None:
query = FormSemestre.query.filter(
Identite.code_nip == nip,
FormSemestreInscription.etudid == Identite.id,
FormSemestreInscription.formsemestre_id == FormSemestre.id,
)
elif ine is not None:
query = FormSemestre.query.filter(
Identite.code_ine == ine,
FormSemestreInscription.etudid == Identite.id,
FormSemestreInscription.formsemestre_id == FormSemestre.id,
)
else:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
message="parametre manquant",
)
formsemestres = models.FormSemestre.query.filter(
models.FormSemestreInscription.etudid == etud.id,
models.FormSemestreInscription.formsemestre_id == models.FormSemestre.id,
).order_by(models.FormSemestre.date_debut)
formsemestres = query.order_by(FormSemestre.date_debut)
return jsonify([formsemestre.to_dict() for formsemestre in formsemestres])
@ -249,8 +268,8 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
def etudiant_bulletin_semestre(
formsemestre_id,
etudid: int = None,
nip: int = None,
ine: int = None,
nip: str = None,
ine: str = None,
version="long",
):
"""
@ -261,12 +280,12 @@ def etudiant_bulletin_semestre(
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": {
{
"version": "0",
"type": "BUT",
"date": "2022-04-27T07:18:16.450634Z",
"publie": true,
"etudiant": {
"civilite": "X",
"code_ine": "1",
"code_nip": "1",
@ -292,17 +311,17 @@ def etudiant_bulletin_semestre(
"villedomicile": "",
"telephone": "",
"fax": "",
"description": ""
},
"formation": {
"description": "",
},
"formation": {
"id": 1,
"acronyme": "BUT R&amp;T",
"titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications",
"titre": "BUT R&amp;T"
},
"formsemestre_id": 1,
"etat_inscription": "I",
"options": {
"titre": "BUT R&amp;T",
},
"formsemestre_id": 1,
"etat_inscription": "I",
"options": {
"show_abs": true,
"show_abs_modules": false,
"show_ects": true,
@ -321,128 +340,113 @@ def etudiant_bulletin_semestre(
"show_temporary": true,
"temporary_txt": "Provisoire",
"show_uevalid": true,
"show_date_inscr": true
},
"ressources": {
"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"
}
]
"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": {
},
"saes": {
"SAE11": {
"id": 2,
"titre": "Se sensibiliser \u00e0 l&apos;hygi\u00e8ne informatique et \u00e0 la cybers\u00e9curit\u00e9",
"code_apogee": null,
"url": "/ScoDoc/TAPI/Scolarite/Notes/moduleimpl_status?moduleimpl_id=2",
"moyenne": {},
"evaluations": []
"id": 2,
"titre": "Se sensibiliser \u00e0 l&apos;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": {
},
"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"
"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,
},
},
"saes": {
"SAE11": {
"id": 2,
"coef": 16.0,
"moyenne": "~"
"bonus": "00.00",
"malus": "00.00",
"capitalise": null,
"ressources": {
"R101": {"id": 1, "coef": 12.0, "moyenne": "12.00"},
},
},
"ECTS": {
"acquis": 0.0,
"total": 12.0
}
"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
"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},
},
"ECTS": {
"acquis": 0,
"total": 30.0
},
"notes": {
"value": "10.60",
"min": "02.40",
"moy": "11.05",
"max": "17.40"
},
"rang": {
"value": "10",
"total": 16
}
}
}
},
}
"""
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
dept = Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
if etudid is not None:
query = Identite.query.filter_by(id=etudid)
elif nip is not None:
query = Identite.query.filter_by(code_nip=nip, dept_id=dept.id)
elif ine is not None:
query = Identite.query.filter_by(code_ine=ine, dept_id=dept.id)
else:
return error_response(
404,
message="parametre manquant",
)
app.set_sco_dept(dept.acronym)
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
etud = query.first()
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
)
app.set_sco_dept(dept.acronym)
return sco_bulletins.get_formsemestre_bulletin_etud_json(
formsemestre, etud, version
)
@ -474,44 +478,57 @@ def etudiant_groups(
ine : le code ine d'un étudiant
Exemple de résultat :
[
{
"partition_id": 1,
"id": 1,
"formsemestre_id": 1,
"partition_name": null,
"numero": 0,
"bul_show_rank": false,
"show_in_lists": true,
"group_id": 1,
"group_name": null
},
{
"partition_id": 2,
"id": 2,
"formsemestre_id": 1,
"partition_name": "TD",
"numero": 1,
"bul_show_rank": false,
"show_in_lists": true,
"group_id": 2,
"group_name": "A"
}
]
[
{
"partition_id": 1,
"id": 1,
"formsemestre_id": 1,
"partition_name": null,
"numero": 0,
"bul_show_rank": false,
"show_in_lists": true,
"group_id": 1,
"group_name": null
},
{
"partition_id": 2,
"id": 2,
"formsemestre_id": 1,
"partition_name": "TD",
"numero": 1,
"bul_show_rank": false,
"show_in_lists": true,
"group_id": 2,
"group_name": "A"
}
]
"""
if etudid is None:
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
)
etudid = etud.etudid
# Récupération du formsemestre
sem = models.FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
dept = models.Departement.query.get(sem.dept_id)
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
if formsemestre is None:
return error_response(
404,
message="formsemestre inconnu",
)
dept = Departement.query.get(formsemestre.dept_id)
if etudid is not None:
query = Identite.query.filter_by(id=etudid)
elif nip is not None:
query = Identite.query.filter_by(code_nip=nip, dept_id=dept.id)
elif ine is not None:
query = Identite.query.filter_by(code_ine=ine, dept_id=dept.id)
else:
return error_response(
404,
message="parametre manquant",
)
etud = query.first()
if etud is None:
return error_response(
404,
message="etudiant inconnu",
)
app.set_sco_dept(dept.acronym)
data = sco_groups.get_etud_groups(etudid, sem.id)
data = sco_groups.get_etud_groups(etud.id, formsemestre.id)
return jsonify(data)

View File

@ -348,7 +348,7 @@ class User(UserMixin, db.Model):
return None
def get_nom_fmt(self):
"""Nom formatté: "Martin" """
"""Nom formaté: "Martin" """
if self.nom:
return sco_etud.format_nom(self.nom, uppercase=False)
else:

View File

@ -481,6 +481,19 @@ class BonusBezier(BonusSportAdditif):
proportion_point = 0.03
class BonusBlagnac(BonusSportAdditif):
"""Calcul bonus modules optionnels (sport, culture), règle IUT de Blagnac.
Le bonus est égal à 5% des points au dessus de 10 à appliquer sur toutes
les UE du semestre, applicable dans toutes les formations (DUT, BUT, ...).
"""
name = "bonus_iutblagnac"
displayed_name = "IUT de Blagnac"
proportion_point = 0.05
classic_use_bonus_ues = True # toujours sur les UE
class BonusBordeaux1(BonusSportMultiplicatif):
"""Calcul bonus modules optionnels (sport, culture), règle IUT Bordeaux 1,
sur moyenne générale et UEs.

View File

@ -171,6 +171,7 @@ class Identite(db.Model):
"""
from app.scodoc import sco_photos
dept = models.Departement.query.filter_by(id=self.dept_id).first_or_404()
d = {
"civilite": self.civilite,
"code_ine": self.code_ine or "",
@ -178,6 +179,8 @@ class Identite(db.Model):
"date_naissance": self.date_naissance.strftime("%d/%m/%Y")
if self.date_naissance
else "",
"dept_id": self.dept_id,
"dept_acronym": dept.acronym,
"email": self.get_first_email() or "",
"emailperso": self.get_first_email("emailperso"),
"etudid": self.id,

View File

@ -309,7 +309,7 @@ class FormSemestre(db.Model):
"""
if not self.etapes:
return ""
return ", ".join(sorted([str(x.etape_apo) for x in self.etapes]))
return ", ".join(sorted([etape.etape_apo for etape in self.etapes if etape]))
def responsables_str(self, abbrev_prenom=True) -> str:
"""chaîne "J. Dupond, X. Martin"
@ -471,10 +471,15 @@ class FormSemestreEtape(db.Model):
db.Integer,
db.ForeignKey("notes_formsemestre.id"),
)
# etape_apo aurait du etre not null, mais oublié
etape_apo = db.Column(db.String(APO_CODE_STR_LEN), index=True)
def __bool__(self):
"Etape False if code empty"
return self.etape_apo is not None and (len(self.etape_apo) > 0)
def __repr__(self):
return f"<Etape {self.id} apo={self.etape_apo}>"
return f"<Etape {self.id} apo={self.etape_apo!r}>"
def as_apovdi(self):
return ApoEtapeVDI(self.etape_apo)

View File

@ -210,7 +210,7 @@ class SetTagInterClasse(pe_tagtable.TableTag):
# -------------------------------------------------------------------------------------------------------------------
def __init__(self, nom_combinaison, diplome):
pe_tagtable.TableTag.__init__(self, nom=nom_combinaison + "_%d" % diplome)
pe_tagtable.TableTag.__init__(self, nom=f"{nom_combinaison}_{diplome or ''}")
self.combinaison = nom_combinaison
self.parcoursDict = {}

View File

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

View File

@ -359,7 +359,6 @@ def can_delete_module(module):
def do_module_delete(oid):
"delete module"
from app.scodoc import sco_formations
module = Module.query.get_or_404(oid)
mod = module_list({"module_id": oid})[0] # sco7
@ -422,13 +421,14 @@ def module_delete(module_id=None):
H = [
html_sco_header.sco_header(page_title="Suppression d'un module"),
"""<h2>Suppression du module %(titre)s (%(code)s)</h2>""" % mod,
f"""<h2>Suppression du module {module.titre} ({module.code})</h2>""",
]
dest_url = url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=str(mod["formation_id"]),
formation_id=module.formation_id,
semestre_idx=module.ue.semestre_idx,
)
tf = TrivialFormulator(
request.base_url,

View File

@ -156,6 +156,8 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
from app.scodoc import sco_parcours_dut
ue = UniteEns.query.get_or_404(ue_id)
formation_id = ue.formation_id
semestre_idx = ue.semestre_idx
if not can_delete_ue(ue):
raise ScoNonEmptyFormationObject(
"UE",
@ -163,8 +165,8 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
dest_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=ue.formation_id,
semestre_idx=ue.semestre_idx,
formation_id=formation_id,
semestre_idx=semestre_idx,
),
)
@ -187,13 +189,13 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
cancel_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=ue.formation_id,
semestre_idx=ue.semestre_idx,
formation_id=formation_id,
semestre_idx=semestre_idx,
),
parameters={"ue_id": ue.id, "dialog_confirmed": 1},
)
if delete_validations:
log("deleting all validations of UE %s" % ue.id)
log(f"deleting all validations of UE {ue.id}")
ndb.SimpleQuery(
"DELETE FROM scolar_formsemestre_validation WHERE ue_id=%(ue_id)s",
{"ue_id": ue.id},
@ -215,10 +217,10 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
# utilisé: acceptable de tout invalider):
sco_cache.invalidate_formsemestre()
# news
F = sco_formations.formation_list(args={"formation_id": ue.formation_id})[0]
F = sco_formations.formation_list(args={"formation_id": formation_id})[0]
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=ue.formation_id,
obj=formation_id,
text=f"Modification de la formation {F['acronyme']}",
max_frequency=10 * 60,
)
@ -228,8 +230,8 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=ue.formation_id,
semestre_idx=ue.semestre_idx,
formation_id=formation_id,
semestre_idx=semestre_idx,
)
)
return None

View File

@ -208,25 +208,29 @@ def _build_results_list(dpv_by_sem, etuds_infos):
return rows, titles, columns_ids
def get_set_formsemestre_id_dates(start_date, end_date):
def get_set_formsemestre_id_dates(start_date, end_date) -> set:
"""Ensemble des formsemestre_id entre ces dates"""
s = ndb.SimpleDictFetch(
"""SELECT id
FROM notes_formsemestre
WHERE date_debut >= %(start_date)s AND date_fin <= %(end_date)s
WHERE date_debut >= %(start_date)s
AND date_fin <= %(end_date)s
AND dept_id = %(dept_id)s
""",
{"start_date": start_date, "end_date": end_date},
{"start_date": start_date, "end_date": end_date, "dept_id": g.scodoc_dept_id},
)
return {x["id"] for x in s}
def scodoc_table_results(start_date="", end_date="", types_parcours=[], format="html"):
def scodoc_table_results(
start_date="", end_date="", types_parcours: list = None, format="html"
):
"""Page affichant la table des résultats
Les dates sont en dd/mm/yyyy (datepicker javascript)
types_parcours est la liste des types de parcours à afficher
(liste de chaines, eg ['100', '210'] )
"""
log("scodoc_table_results: start_date=%s" % (start_date,)) # XXX
log(f"scodoc_table_results: start_date={start_date!r}")
if not types_parcours:
types_parcours = []
if not isinstance(types_parcours, list):

View File

@ -256,6 +256,8 @@ def formation_import_xml(doc: str, import_tags=True):
mod_info[1]["formation_id"] = formation_id
mod_info[1]["matiere_id"] = mat_id
mod_info[1]["ue_id"] = ue_id
if not "module_type" in mod_info[1]:
mod_info[1]["module_type"] = scu.ModuleType.STANDARD
mod_id = sco_edit_module.do_module_create(mod_info[1])
if xml_module_id:
modules_old2new[int(xml_module_id)] = mod_id

View File

@ -262,7 +262,7 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid):
)
def _make_page(etud, sem, tf, message=""):
def _make_page(etud: dict, sem, tf, message="") -> list:
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
moy_gen = nt.get_etud_moy_gen(etud["etudid"])
@ -277,21 +277,20 @@ def _make_page(etud, sem, tf, message=""):
</p>
"""
% etud,
"""<p>La moyenne de ce semestre serait:
<span class="ext_sem_moy"><span class="ext_sem_moy_val">%s</span> / 20</span>
f"""<p>La moyenne de ce semestre serait:
<span class="ext_sem_moy"><span class="ext_sem_moy_val">{moy_gen}</span> / 20</span>
</p>
"""
% moy_gen,
""",
'<div id="formsemestre_ext_edit_ue_validations">',
tf[1],
"</div>",
"""<div>
<a class="stdlink"
href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s">
retour au bulletin de notes
</a></div>
"""
% (sem["formsemestre_id"], etud["etudid"]),
f"""<div>
<a class="stdlink"
href="{url_for("notes.formsemestre_bulletinetud", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id, etudid=etud['etudid']
)}">retour au bulletin de notes</a>
</div>
""",
html_sco_header.sco_footer(),
]
return H

View File

@ -85,7 +85,7 @@ def formsemestre_recapcomplet(
"""
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
file_formats = {"csv", "json", "xls", "xlsx", "xlsall", "xml"}
supported_formats = file_formats | {"html"}
supported_formats = file_formats | {"html", "evals"}
if tabformat not in supported_formats:
raise ScoValueError(f"Format non supporté: {tabformat}")
is_file = tabformat in file_formats
@ -131,7 +131,8 @@ def formsemestre_recapcomplet(
for (format, label) in (
("html", "Tableau"),
("evals", "Avec toutes les évaluations"),
("xlsx", "Excel non formatté"),
("xlsx", "Excel (non formaté)"),
("xlsall", "Excel avec évaluations"),
("xml", "Bulletins XML (obsolète)"),
("json", "Bulletins JSON"),
):

View File

@ -100,7 +100,7 @@ $(function () {
},
{
// Elimine les 0 à gauche pour les exports excel et les "copy"
targets: ["col_mod", "col_moy_gen", "col_ue", "col_res", "col_sae"],
targets: ["col_mod", "col_moy_gen", "col_ue", "col_res", "col_sae", "evaluation"],
render: function (data, type, row) {
return type === 'export' ? data.replace(/0(\d\..*)/, '$1') : data;
}

View File

@ -648,17 +648,6 @@ def formation_export(formation_id, export_ids=False, format=None):
)
@bp.route("/formation_import_xml")
@scodoc
@permission_required(Permission.ScoChangeFormation)
@scodoc7func
def formation_import_xml(file):
"import d'une formation en XML"
log("formation_import_xml")
doc = file.read()
return sco_formations.formation_import_xml(doc)
@bp.route("/formation_import_xml_form", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoChangeFormation)

View File

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

View File

@ -26,13 +26,17 @@ import urllib3
from pprint import pprint as pp
# --- Lecture configuration (variables d'env ou .env)
BASEDIR = os.path.abspath(os.path.dirname(__file__))
try:
BASEDIR = os.path.abspath(os.path.dirname(__file__))
except NameError:
BASEDIR = "."
load_dotenv(os.path.join(BASEDIR, ".env"))
CHK_CERT = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
SCODOC_URL = os.environ["SCODOC_URL"]
API_URL = SCODOC_URL + "/ScoDoc/api"
SCODOC_USER = os.environ["SCODOC_USER"]
SCODOC_PASSWORD = os.environ["SCODOC_PASSWD"]
SCODOC_PASSWORD = os.environ["SCODOC_PASSWORD"]
print(f"SCODOC_URL={SCODOC_URL}")
print(f"API URL={API_URL}")
@ -90,6 +94,23 @@ formsemestre_id = 1028 # A adapter
etudid = 14721
bul_dut = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
# Infos sur un étudiant
etudid = 3561
code_nip = "11303314"
etud = GET(f"/etudiant/etudid/{etudid}")
print(etud)
etud = GET(f"/etudiant/nip/{code_nip}")
print(etud)
sems = GET(f"/etudiant/etudid/{etudid}/formsemestres")
print("\n".join([s["titre_num"] for s in sems]))
sems = GET(f"/etudiant/nip/{code_nip}/formsemestres")
print("\n".join([s["titre_num"] for s in sems]))
# # --- Recupere la liste de tous les semestres:
# sems = GET(s, "Notes/formsemestre_list?format=json", "Aucun semestre !")

View File

@ -22,8 +22,7 @@ from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
# Etudiant pour les tests
ETUDID = 1
INE = "1"
NIP = "1"
# absences
def test_absences(api_headers):
@ -42,20 +41,6 @@ def test_absences(api_headers):
)
assert r.status_code == 200
r = requests.get(
f"{API_URL}/absences/nip/{NIP}",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
f"{API_URL}/absences/ine/{INE}",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# absences_justify
def test_absences_justify(api_headers):
@ -75,22 +60,6 @@ def test_absences_justify(api_headers):
assert r.status_code == 200
# TODO vérifier résultat
r = requests.get(
API_URL + f"/absences/nip/{NIP}/just",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# TODO vérifier résultat
r = requests.get(
API_URL + f"/absences/ine/{INE}/just",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# TODO vérifier résultat
# XXX TODO
# def test_abs_groupe_etat(api_headers):

View File

@ -101,7 +101,7 @@ def test_etudiant(api_headers):
)
assert r.status_code == 200
etud = r.json()
assert len(etud) == 24
assert len(etud) == 25
fields_ok = verify_fields(etud, ETUD_FIELDS)
assert fields_ok is True

View File

@ -24,6 +24,11 @@ from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
from tests.api.tools_test_api import MODIMPL_FIELDS, verify_fields
from tests.api.tools_test_api import FSEM_FIELDS, UE_FIELDS, MODULE_FIELDS
# Etudiant pour les tests
ETUDID = 1
NIP = "1"
INE = "INE1"
def test_formsemestre(api_headers):
"""
@ -53,7 +58,7 @@ def test_etudiant_bulletin(api_headers):
bull_a = r.json()
r = requests.get(
f"{API_URL}/etudiant/nip/1/formsemestre/{formsemestre_id}/bulletin",
f"{API_URL}/etudiant/nip/{NIP}/formsemestre/{formsemestre_id}/bulletin",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
@ -61,7 +66,7 @@ def test_etudiant_bulletin(api_headers):
bull_b = r.json()
r = requests.get(
f"{API_URL}/etudiant/ine/1/formsemestre/{formsemestre_id}/bulletin",
f"{API_URL}/etudiant/ine/{INE}/formsemestre/{formsemestre_id}/bulletin",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)