forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises
This commit is contained in:
commit
809b5a992d
@ -4,11 +4,13 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import request
|
from flask import request
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.scodoc.sco_exceptions import ScoException
|
||||||
|
|
||||||
api_bp = Blueprint("api", __name__)
|
api_bp = Blueprint("api", __name__)
|
||||||
api_web_bp = Blueprint("apiweb", __name__)
|
api_web_bp = Blueprint("apiweb", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@api_bp.errorhandler(ScoException)
|
||||||
@api_bp.errorhandler(404)
|
@api_bp.errorhandler(404)
|
||||||
def api_error_handler(e):
|
def api_error_handler(e):
|
||||||
"erreurs API => json"
|
"erreurs API => json"
|
||||||
|
@ -218,7 +218,6 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
|||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
defaults={"version": "long", "pdf": False},
|
defaults={"version": "long", "pdf": False},
|
||||||
)
|
)
|
||||||
# Version PDF non testée
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
@ -254,6 +253,16 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
|||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
defaults={"version": "short", "pdf": True},
|
defaults={"version": "short", "pdf": True},
|
||||||
)
|
)
|
||||||
|
@bp.route(
|
||||||
|
"/etudiant/nip/<string:nip>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "long", "pdf": True},
|
||||||
|
)
|
||||||
|
@bp.route(
|
||||||
|
"/etudiant/ine/<string:ine>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
||||||
|
methods=["GET"],
|
||||||
|
defaults={"version": "long", "pdf": True},
|
||||||
|
)
|
||||||
@api_web_bp.route(
|
@api_web_bp.route(
|
||||||
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin",
|
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin",
|
||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
@ -269,7 +278,6 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
|||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
defaults={"version": "long", "pdf": False},
|
defaults={"version": "long", "pdf": False},
|
||||||
)
|
)
|
||||||
# Version PDF non testée
|
|
||||||
@api_web_bp.route(
|
@api_web_bp.route(
|
||||||
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
|
@ -251,55 +251,52 @@ def formsemestre_programme(formsemestre_id: int):
|
|||||||
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/formsemestre/<int:formsemestre_id>/etudiants",
|
"/formsemestre/<int:formsemestre_id>/etudiants",
|
||||||
defaults={"etat": None},
|
defaults={"with_query": False},
|
||||||
)
|
)
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/formsemestre/<int:formsemestre_id>/etudiants/actifs",
|
"/formsemestre/<int:formsemestre_id>/etudiants/query",
|
||||||
defaults={"etat": scu.INSCRIT},
|
defaults={"with_query": True},
|
||||||
)
|
|
||||||
@bp.route(
|
|
||||||
"/formsemestre/<int:formsemestre_id>/etudiants/demissionnaires",
|
|
||||||
defaults={"etat": scu.DEMISSION},
|
|
||||||
)
|
|
||||||
@bp.route(
|
|
||||||
"/formsemestre/<int:formsemestre_id>/etudiants/defaillants",
|
|
||||||
defaults={"etat": scu.DEF},
|
|
||||||
)
|
)
|
||||||
@api_web_bp.route(
|
@api_web_bp.route(
|
||||||
"/formsemestre/<int:formsemestre_id>/etudiants",
|
"/formsemestre/<int:formsemestre_id>/etudiants",
|
||||||
defaults={"etat": None},
|
defaults={"with_query": False},
|
||||||
)
|
)
|
||||||
@api_web_bp.route(
|
@api_web_bp.route(
|
||||||
"/formsemestre/<int:formsemestre_id>/etudiants/actifs",
|
"/formsemestre/<int:formsemestre_id>/etudiants/query",
|
||||||
defaults={"etat": scu.INSCRIT},
|
defaults={"with_query": True},
|
||||||
)
|
|
||||||
@api_web_bp.route(
|
|
||||||
"/formsemestre/<int:formsemestre_id>/etudiants/demissionnaires",
|
|
||||||
defaults={"etat": scu.DEMISSION},
|
|
||||||
)
|
|
||||||
@api_web_bp.route(
|
|
||||||
"/formsemestre/<int:formsemestre_id>/etudiants/defaillants",
|
|
||||||
defaults={"etat": scu.DEF},
|
|
||||||
)
|
)
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def formsemestre_etudiants(formsemestre_id: int, etat: str = None):
|
def formsemestre_etudiants(formsemestre_id: int, with_query: bool = False):
|
||||||
"""Etudiants d'un formsemestre."""
|
"""Etudiants d'un formsemestre."""
|
||||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
if etat is None:
|
if with_query:
|
||||||
|
etat = request.args.get("etat")
|
||||||
|
if etat is not None:
|
||||||
|
etat = {
|
||||||
|
"actifs": scu.INSCRIT,
|
||||||
|
"demissionnaires": scu.DEMISSION,
|
||||||
|
"defaillants": scu.DEF,
|
||||||
|
}.get(etat, etat)
|
||||||
|
inscriptions = [
|
||||||
|
ins for ins in formsemestre.inscriptions if ins.etat == etat
|
||||||
|
]
|
||||||
|
else:
|
||||||
inscriptions = formsemestre.inscriptions
|
inscriptions = formsemestre.inscriptions
|
||||||
else:
|
else:
|
||||||
inscriptions = [ins for ins in formsemestre.inscriptions if ins.etat == etat]
|
inscriptions = formsemestre.inscriptions
|
||||||
|
|
||||||
etuds = [ins.etud.to_dict_short() for ins in inscriptions]
|
etuds = [ins.etud.to_dict_short() for ins in inscriptions]
|
||||||
# Ajout des groupes de chaque étudiants
|
# Ajout des groupes de chaque étudiants
|
||||||
# XXX A REVOIR: trop inefficace !
|
# XXX A REVOIR: trop inefficace !
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
etud["groups"] = sco_groups.get_etud_groups(etud["id"], formsemestre_id)
|
etud["groups"] = sco_groups.get_etud_groups(
|
||||||
|
etud["id"], formsemestre_id, exclude_default=True
|
||||||
|
)
|
||||||
|
|
||||||
return jsonify(etuds)
|
return jsonify(etuds)
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import app
|
|||||||
from app import db, log
|
from app import db, log
|
||||||
from app.api import api_bp as bp, api_web_bp
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.decorators import scodoc, permission_required
|
from app.decorators import scodoc, permission_required
|
||||||
|
from app.scodoc.sco_exceptions import ScoException
|
||||||
from app.scodoc.sco_utils import json_error
|
from app.scodoc.sco_utils import json_error
|
||||||
from app.but import jury_but_recap
|
from app.but import jury_but_recap
|
||||||
from app.models import FormSemestre, FormSemestreInscription, Identite
|
from app.models import FormSemestre, FormSemestreInscription, Identite
|
||||||
@ -30,6 +31,9 @@ def decisions_jury(formsemestre_id: int):
|
|||||||
"""Décisions du jury des étudiants du formsemestre."""
|
"""Décisions du jury des étudiants du formsemestre."""
|
||||||
# APC, pair:
|
# APC, pair:
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||||
|
if formsemestre.formation.is_apc():
|
||||||
app.set_sco_dept(formsemestre.departement.acronym)
|
app.set_sco_dept(formsemestre.departement.acronym)
|
||||||
rows = jury_but_recap.get_jury_but_results(formsemestre)
|
rows = jury_but_recap.get_jury_but_results(formsemestre)
|
||||||
return jsonify(rows)
|
return jsonify(rows)
|
||||||
|
else:
|
||||||
|
raise ScoException("non implemente")
|
||||||
|
@ -136,20 +136,22 @@ def etud_in_group(group_id: int):
|
|||||||
def etud_in_group_query(group_id: int):
|
def etud_in_group_query(group_id: int):
|
||||||
"""Etudiants du groupe, filtrés par état"""
|
"""Etudiants du groupe, filtrés par état"""
|
||||||
etat = request.args.get("etat")
|
etat = request.args.get("etat")
|
||||||
if etat not in {scu.INSCRIT, scu.DEMISSION, scu.DEF}:
|
if etat not in {None, scu.INSCRIT, scu.DEMISSION, scu.DEF}:
|
||||||
return json_error(404, "etat: valeur invalide")
|
return json_error(404, "etat: valeur invalide")
|
||||||
query = GroupDescr.query.filter_by(id=group_id)
|
query = GroupDescr.query.filter_by(id=group_id)
|
||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = (
|
query = (
|
||||||
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
)
|
)
|
||||||
group = query.first_or_404() # just tro ckeck that group exists in accessible dept
|
group = query.first_or_404() # just to ckeck that group exists in accessible dept
|
||||||
query = (
|
|
||||||
Identite.query.join(FormSemestreInscription)
|
query = Identite.query.join(FormSemestreInscription).filter_by(
|
||||||
.filter_by(formsemestre_id=group.partition.formsemestre_id, etat=etat)
|
formsemestre_id=group.partition.formsemestre_id
|
||||||
.join(group_membership)
|
|
||||||
.filter_by(group_id=group_id)
|
|
||||||
)
|
)
|
||||||
|
if etat is not None:
|
||||||
|
query = query.filter_by(etat=etat)
|
||||||
|
|
||||||
|
query = query.join(group_membership).filter_by(group_id=group_id)
|
||||||
|
|
||||||
return jsonify([etud.to_dict_short() for etud in query])
|
return jsonify([etud.to_dict_short() for etud in query])
|
||||||
|
|
||||||
|
@ -29,12 +29,8 @@ def get_etud(etudid=None, nip=None, ine=None) -> models.Identite:
|
|||||||
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
||||||
|
|
||||||
if etudid is not None:
|
if etudid is not None:
|
||||||
etud: Identite = Identite.query.get(etudid)
|
query: Identite = Identite.query.filter_by(id=etudid)
|
||||||
if (None in allowed_depts) or etud.departement.acronym in allowed_depts:
|
elif nip is not None:
|
||||||
return etud
|
|
||||||
return None # accès interdit => pas d'étudiant
|
|
||||||
|
|
||||||
if nip is not None:
|
|
||||||
query = Identite.query.filter_by(code_nip=nip)
|
query = Identite.query.filter_by(code_nip=nip)
|
||||||
elif ine is not None:
|
elif ine is not None:
|
||||||
query = Identite.query.filter_by(code_ine=ine)
|
query = Identite.query.filter_by(code_ine=ine)
|
||||||
@ -45,7 +41,7 @@ def get_etud(etudid=None, nip=None, ine=None) -> models.Identite:
|
|||||||
)
|
)
|
||||||
if None not in allowed_depts:
|
if None not in allowed_depts:
|
||||||
# restreint aux départements autorisés:
|
# restreint aux départements autorisés:
|
||||||
etuds = etuds.join(Departement).filter(
|
query = query.join(Departement).filter(
|
||||||
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
||||||
)
|
)
|
||||||
return query.join(Admission).order_by(desc(Admission.annee)).first()
|
return query.join(Admission).order_by(desc(Admission.annee)).first()
|
||||||
|
@ -124,8 +124,8 @@ def user_create():
|
|||||||
return jsonify(user.to_dict())
|
return jsonify(user.to_dict())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/user/edit/<int:uid>", methods=["POST"])
|
@bp.route("/user/<int:uid>/edit", methods=["POST"])
|
||||||
@api_web_bp.route("/user/edit/<int:uid>", methods=["POST"])
|
@api_web_bp.route("/user/<int:uid>/edit", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoUsersAdmin)
|
@permission_required(Permission.ScoUsersAdmin)
|
||||||
@ -306,8 +306,8 @@ def role_permission_remove(role_name: str, perm_name: str):
|
|||||||
return jsonify(role.to_dict())
|
return jsonify(role.to_dict())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/role/<string:role_name>/create", methods=["POST"])
|
@bp.route("/role/create/<string:role_name>", methods=["POST"])
|
||||||
@api_web_bp.route("/role/<string:role_name>/create", methods=["POST"])
|
@api_web_bp.route("/role/create/<string:role_name>", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoSuperAdmin)
|
@permission_required(Permission.ScoSuperAdmin)
|
||||||
|
@ -63,13 +63,13 @@ from operator import attrgetter
|
|||||||
import re
|
import re
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app import log
|
from app import log
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.comp import inscr_mod, res_sem
|
from app.comp import res_sem
|
||||||
from app.models import formsemestre
|
|
||||||
|
|
||||||
from app.models.but_refcomp import (
|
from app.models.but_refcomp import (
|
||||||
ApcAnneeParcours,
|
ApcAnneeParcours,
|
||||||
@ -917,7 +917,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
self.codes = [
|
self.codes = [
|
||||||
sco_codes.DEM if inscription_etat == scu.DEMISSION else sco_codes.DEF
|
sco_codes.DEM if inscription_etat == scu.DEMISSION else sco_codes.DEF
|
||||||
]
|
]
|
||||||
self.moy_ue = "-"
|
self.moy_ue = np.NaN
|
||||||
return
|
return
|
||||||
|
|
||||||
# Moyenne de l'UE ?
|
# Moyenne de l'UE ?
|
||||||
|
@ -448,6 +448,9 @@ def get_jury_but_table(
|
|||||||
|
|
||||||
def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]:
|
def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]:
|
||||||
"""Liste des résultats jury BUT sous forme de dict, pour API"""
|
"""Liste des résultats jury BUT sous forme de dict, pour API"""
|
||||||
|
if formsemestre.formation.referentiel_competence is None:
|
||||||
|
# pas de ref. comp., donc pas de decisions de jury (ne lance pas d'exception)
|
||||||
|
return []
|
||||||
dpv = sco_pvjury.dict_pvjury(formsemestre.id)
|
dpv = sco_pvjury.dict_pvjury(formsemestre.id)
|
||||||
rows = []
|
rows = []
|
||||||
for etudid in formsemestre.etuds_inscriptions:
|
for etudid in formsemestre.etuds_inscriptions:
|
||||||
@ -484,12 +487,16 @@ def get_jury_but_etud_result(
|
|||||||
rcue_dict = {
|
rcue_dict = {
|
||||||
"ue_1": {
|
"ue_1": {
|
||||||
"ue_id": rcue.ue_1.id,
|
"ue_id": rcue.ue_1.id,
|
||||||
"moy": None if np.isnan(dec_ue1.moy_ue) else dec_ue1.moy_ue,
|
"moy": None
|
||||||
|
if (dec_ue1.moy_ue is None or np.isnan(dec_ue1.moy_ue))
|
||||||
|
else dec_ue1.moy_ue,
|
||||||
"code": dec_ue1.code_valide,
|
"code": dec_ue1.code_valide,
|
||||||
},
|
},
|
||||||
"ue_2": {
|
"ue_2": {
|
||||||
"ue_id": rcue.ue_2.id,
|
"ue_id": rcue.ue_2.id,
|
||||||
"moy": None if np.isnan(dec_ue2.moy_ue) else dec_ue2.moy_ue,
|
"moy": None
|
||||||
|
if (dec_ue2.moy_ue is None or np.isnan(dec_ue2.moy_ue))
|
||||||
|
else dec_ue2.moy_ue,
|
||||||
"code": dec_ue2.code_valide,
|
"code": dec_ue2.code_valide,
|
||||||
},
|
},
|
||||||
"moy": rcue.moy_rcue,
|
"moy": rcue.moy_rcue,
|
||||||
|
@ -221,10 +221,7 @@ def compute_ue_moys_apc(
|
|||||||
modimpl_mask: np.array,
|
modimpl_mask: np.array,
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
"""Calcul de la moyenne d'UE en mode APC (BUT).
|
"""Calcul de la moyenne d'UE en mode APC (BUT).
|
||||||
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
La moyenne d'UE est un nombre (note/20), ou NaN si pas de notes disponibles
|
||||||
NI non inscrit à (au moins un) module de cette UE
|
|
||||||
NA pas de notes disponibles
|
|
||||||
ERR erreur dans une formule utilisateurs (pas gérées ici).
|
|
||||||
|
|
||||||
sem_cube: notes moyennes aux modules
|
sem_cube: notes moyennes aux modules
|
||||||
ndarray (etuds x modimpls x UEs)
|
ndarray (etuds x modimpls x UEs)
|
||||||
|
@ -50,6 +50,8 @@ main {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 32px;
|
gap: 32px;
|
||||||
|
row-gap: 4px;
|
||||||
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
main h2 {
|
main h2 {
|
||||||
@ -184,8 +186,16 @@ body.editionActivated .filtres>div>div>div>div {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*****************************/
|
/*****************************/
|
||||||
/* Zone Choix */
|
/* Zone Partitions */
|
||||||
/*****************************/
|
/*****************************/
|
||||||
|
#zonePartitions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filtres {
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
.filtres>div {
|
.filtres>div {
|
||||||
background: #ddd;
|
background: #ddd;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
@ -226,6 +236,9 @@ body:not(.editionActivated) .filtres>div>div>div>div:active {
|
|||||||
background: rgba(0, 153, 204, 0.5);
|
background: rgba(0, 153, 204, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*****************************/
|
||||||
|
/* Zone Etudiants */
|
||||||
|
/*****************************/
|
||||||
#zoneChoix .etudiants>div {
|
#zoneChoix .etudiants>div {
|
||||||
background: #FFF;
|
background: #FFF;
|
||||||
border: 1px solid #aaa;
|
border: 1px solid #aaa;
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
{# -*- mode: jinja-html -*- #}
|
{# -*- mode: jinja-html -*- #}
|
||||||
<h1>{% if not read_only %}Édition des p{% else %}P{%endif%}artitions</h1>
|
<h1>{% if not read_only %}Édition des p{% else %}P{%endif%}artitions</h1>
|
||||||
|
|
||||||
<div>
|
|
||||||
<label class="edition">
|
|
||||||
<input type="checkbox" autocomplete="off">
|
|
||||||
Edition des partitions - tout s'enregistre automatiquement dès qu'il y a modification
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div class="wait"></div>
|
<div class="wait"></div>
|
||||||
|
|
||||||
<section id="zoneChoix">
|
<section id="zonePartitions">
|
||||||
<h2>Choix</h2>
|
<h2>Partitions et groupes</h2>
|
||||||
|
<div>
|
||||||
|
<label class="edition">
|
||||||
|
<input type="checkbox" autocomplete="off">
|
||||||
|
Modifier les partitions et groupes - tout s'enregistre automatiquement dès qu'il y a modification
|
||||||
|
</label>
|
||||||
<div class="filtres">
|
<div class="filtres">
|
||||||
<div class="partitions">
|
<div class="partitions">
|
||||||
<h3>Afficher les partitions</h3>
|
<h3>Afficher les partitions</h3>
|
||||||
@ -26,8 +24,14 @@
|
|||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="zoneChoix">
|
||||||
|
<h2>Etudiants</h2>
|
||||||
<div class="etudiants"></div>
|
<div class="etudiants"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="zoneGroupes">
|
<section id="zoneGroupes">
|
||||||
<h2>Groupes</h2>
|
<h2>Groupes</h2>
|
||||||
<div class="groupes"></div>
|
<div class="groupes"></div>
|
||||||
@ -78,7 +82,12 @@
|
|||||||
|
|
||||||
arrayPartitions.forEach((partition) => {
|
arrayPartitions.forEach((partition) => {
|
||||||
// Filtres
|
// Filtres
|
||||||
|
if (partition.groups_editable) {
|
||||||
outputPartitions += `<div data-idpartition="${partition.id}"><span class="editing move">||</span><span>${partition.partition_name}</span><span class="editing modif">✏️</span><span class="editing suppr">❌</span></div>`;
|
outputPartitions += `<div data-idpartition="${partition.id}"><span class="editing move">||</span><span>${partition.partition_name}</span><span class="editing modif">✏️</span><span class="editing suppr">❌</span></div>`;
|
||||||
|
} else {
|
||||||
|
outputPartitions += `<div data-idpartition="${partition.id}"><span>${partition.partition_name}</span></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
outputMasques += `<div data-idpartition="${partition.id}"><div data-idpartition="${partition.id}" data-idgroupe=aucun>Non affectés - ${partition.partition_name}</div>`;
|
outputMasques += `<div data-idpartition="${partition.id}"><div data-idpartition="${partition.id}" data-idgroupe=aucun>Non affectés - ${partition.partition_name}</div>`;
|
||||||
|
|
||||||
// Groupes
|
// Groupes
|
||||||
@ -96,9 +105,13 @@
|
|||||||
let output = "";
|
let output = "";
|
||||||
arrayGroups.forEach((groupe) => {
|
arrayGroups.forEach((groupe) => {
|
||||||
/***************/
|
/***************/
|
||||||
outputMasques += `<div data-idgroupe="${groupe.id}"><span class="editing move">||</span><span>${groupe.group_name}</span><span class="editing modif">✏️</span><span class="editing suppr">❌</span></div>`; // patch JMP (renommage du champ name dans l API)
|
if (partition.groups_editable) {
|
||||||
|
outputMasques += `<div data-idgroupe="${groupe.id}"><span class="editing move">||</span><span>${groupe.group_name}</span><span class="editing modif">✏️</span><span class="editing suppr">❌</span></div>`;
|
||||||
|
} else {
|
||||||
|
outputMasques += `<div data-idgroupe="${groupe.id}"><span>${groupe.group_name}</span></div>`;
|
||||||
|
}
|
||||||
/***************/
|
/***************/
|
||||||
output += templateGroupe_zoneGroupes(groupe.id, groupe.group_name); // patch JMP (renommage du champ name dans l API)
|
output += templateGroupe_zoneGroupes(groupe.id, groupe.group_name);
|
||||||
})
|
})
|
||||||
return output;
|
return output;
|
||||||
})()}
|
})()}
|
||||||
@ -144,7 +157,7 @@
|
|||||||
if (!affected) {
|
if (!affected) {
|
||||||
document.querySelector(`#zoneGroupes [data-idpartition="${partition.id}"]>[data-idgroupe="aucun"]>.etudiants`).innerHTML += templateEtudiant_zoneGroupes(etudiant);
|
document.querySelector(`#zoneGroupes [data-idpartition="${partition.id}"]>[data-idgroupe="aucun"]>.etudiants`).innerHTML += templateEtudiant_zoneGroupes(etudiant);
|
||||||
}
|
}
|
||||||
return `<label title="Aucun groupe"><input type=radio name="${partition.id}-${etudiant.etudid}" value="aucun" ${(!affected) ? "checked" : ""}><span class=aucun>❌</span></label>` + output;
|
return `<label title="Aucun groupe"><input type=radio name="${partition.id}-${etudiant.etudid}" value="aucun" ${(!affected) ? "checked" : ""}><span class=aucun> - </span></label>` + output;
|
||||||
})()}
|
})()}
|
||||||
</div>`;
|
</div>`;
|
||||||
})
|
})
|
||||||
@ -171,9 +184,6 @@
|
|||||||
/******************************/
|
/******************************/
|
||||||
function input() {
|
function input() {
|
||||||
document.querySelector("body").classList.toggle("editionActivated");
|
document.querySelector("body").classList.toggle("editionActivated");
|
||||||
/*if (event.currentTarget.checked == false) {
|
|
||||||
go();
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
function processEvents() {
|
function processEvents() {
|
||||||
/*--------------------*/
|
/*--------------------*/
|
||||||
@ -193,7 +203,7 @@
|
|||||||
/*--------------------*/
|
/*--------------------*/
|
||||||
/* Changement groupe */
|
/* Changement groupe */
|
||||||
/*--------------------*/
|
/*--------------------*/
|
||||||
document.querySelectorAll("#zoneChoix label").forEach(btn => { btn.addEventListener("mousedown", (event) => { event.preventDefault() }) });
|
document.querySelectorAll("label").forEach(btn => { btn.addEventListener("mousedown", (event) => { event.preventDefault() }) });
|
||||||
document.querySelectorAll(".etudiants input").forEach(input => { input.addEventListener("input", assignment) })
|
document.querySelectorAll(".etudiants input").forEach(input => { input.addEventListener("input", assignment) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,6 +233,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.dataset.idgroupe) {
|
if (!this.dataset.idgroupe) {
|
||||||
|
// Partitions
|
||||||
let groupesSelected = [];
|
let groupesSelected = [];
|
||||||
this.parentElement.querySelectorAll(":not(.unselect)").forEach(e => {
|
this.parentElement.querySelectorAll(":not(.unselect)").forEach(e => {
|
||||||
groupesSelected.push(e.dataset.idpartition);
|
groupesSelected.push(e.dataset.idpartition);
|
||||||
@ -238,6 +249,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
// Groupes
|
||||||
let groupesSelected = {};
|
let groupesSelected = {};
|
||||||
|
|
||||||
this.parentElement.parentElement.querySelectorAll("[data-idgroupe]:not(.unselect)").forEach(e => {
|
this.parentElement.parentElement.querySelectorAll("[data-idgroupe]:not(.unselect)").forEach(e => {
|
||||||
@ -350,6 +362,8 @@
|
|||||||
div.querySelector(".move").addEventListener("mousedown", moveStart);
|
div.querySelector(".move").addEventListener("mousedown", moveStart);
|
||||||
this.parentElement.insertBefore(div, this);
|
this.parentElement.insertBefore(div, this);
|
||||||
|
|
||||||
|
div.querySelector(".modif").click();
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
fetch(url,
|
fetch(url,
|
||||||
{
|
{
|
||||||
@ -380,12 +394,12 @@
|
|||||||
div.querySelector("div").addEventListener("click", filtre);
|
div.querySelector("div").addEventListener("click", filtre);
|
||||||
div.querySelector(".ajoutGroupe").addEventListener("click", addPartition);
|
div.querySelector(".ajoutGroupe").addEventListener("click", addPartition);
|
||||||
|
|
||||||
document.querySelector("#zoneChoix .masques>div").appendChild(div);
|
document.querySelector("#zonePartitions .masques>div").appendChild(div);
|
||||||
|
|
||||||
// Ajout de la zone pour chaque étudiant
|
// Ajout de la zone pour chaque étudiant
|
||||||
let outputGroupes = "";
|
let outputGroupes = "";
|
||||||
|
|
||||||
document.querySelectorAll(`#zoneChoix .grpPartitions`).forEach(e => {
|
document.querySelectorAll(`#zonePartitions .grpPartitions`).forEach(e => {
|
||||||
let etudid = e.previousElementSibling.dataset.etudid;
|
let etudid = e.previousElementSibling.dataset.etudid;
|
||||||
|
|
||||||
// Préparation pour la section suivante
|
// Préparation pour la section suivante
|
||||||
@ -447,10 +461,17 @@
|
|||||||
/* Edition du texte */
|
/* Edition du texte */
|
||||||
/********************/
|
/********************/
|
||||||
function editText() {
|
function editText() {
|
||||||
this.previousElementSibling.classList.add("editingText");
|
let e = this.previousElementSibling;
|
||||||
this.previousElementSibling.setAttribute("contenteditable", "true");
|
e.classList.add("editingText");
|
||||||
this.previousElementSibling.focus();
|
e.setAttribute("contenteditable", "true");
|
||||||
this.previousElementSibling.addEventListener("keydown", writing);
|
e.addEventListener("keydown", writing);
|
||||||
|
|
||||||
|
// On sélectionne la zone
|
||||||
|
const range = document.createRange();
|
||||||
|
const selection = window.getSelection();
|
||||||
|
selection.removeAllRanges();
|
||||||
|
range.selectNodeContents(e);
|
||||||
|
selection.addRange(range);
|
||||||
}
|
}
|
||||||
|
|
||||||
function writing(event) {
|
function writing(event) {
|
||||||
@ -613,12 +634,12 @@
|
|||||||
let formsemestre_id = params.get('formsemestre_id');
|
let formsemestre_id = params.get('formsemestre_id');
|
||||||
var url = `/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/${formsemestre_id}/partitions/order`;
|
var url = `/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/${formsemestre_id}/partitions/order`;
|
||||||
|
|
||||||
document.querySelectorAll(`#zoneChoix .masques>div`).forEach(parent => {
|
document.querySelectorAll(`#zonePartitions .masques>div`).forEach(parent => {
|
||||||
positions.forEach(position => {
|
positions.forEach(position => {
|
||||||
parent.append(parent.querySelector(`[data-idpartition="${position}"]`))
|
parent.append(parent.querySelector(`[data-idpartition="${position}"]`))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
document.querySelectorAll(`#zoneChoix .grpPartitions`).forEach(parent => {
|
document.querySelectorAll(`#zonePartitions .grpPartitions`).forEach(parent => {
|
||||||
positions.forEach(position => {
|
positions.forEach(position => {
|
||||||
parent.append(parent.querySelector(`[data-idpartition="${position}"]`))
|
parent.append(parent.querySelector(`[data-idpartition="${position}"]`))
|
||||||
})
|
})
|
||||||
|
@ -72,6 +72,7 @@ from app.decorators import (
|
|||||||
)
|
)
|
||||||
from app.models import FormSemestre, GroupDescr
|
from app.models import FormSemestre, GroupDescr
|
||||||
from app.models.absences import BilletAbsence
|
from app.models.absences import BilletAbsence
|
||||||
|
from app.models.etudiants import Identite
|
||||||
from app.views import absences_bp as bp
|
from app.views import absences_bp as bp
|
||||||
|
|
||||||
# ---------------
|
# ---------------
|
||||||
@ -1099,7 +1100,7 @@ def AddBilletAbsence(
|
|||||||
begin,
|
begin,
|
||||||
end,
|
end,
|
||||||
description,
|
description,
|
||||||
etudid=False,
|
etudid=None,
|
||||||
code_nip=None,
|
code_nip=None,
|
||||||
code_ine=None,
|
code_ine=None,
|
||||||
justified=True,
|
justified=True,
|
||||||
@ -1114,7 +1115,7 @@ def AddBilletAbsence(
|
|||||||
end = str(end)
|
end = str(end)
|
||||||
code_nip = str(code_nip) if code_nip else None
|
code_nip = str(code_nip) if code_nip else None
|
||||||
|
|
||||||
etud = api.tools.get_etud(etudid=None, nip=None, ine=None)
|
etud = api.tools.get_etud(etudid=etudid, nip=code_nip, ine=code_ine)
|
||||||
# check dates
|
# check dates
|
||||||
begin_date = dateutil.parser.isoparse(begin) # may raises ValueError
|
begin_date = dateutil.parser.isoparse(begin) # may raises ValueError
|
||||||
end_date = dateutil.parser.isoparse(end)
|
end_date = dateutil.parser.isoparse(end)
|
||||||
@ -1212,9 +1213,12 @@ def billets_etud(etudid=False):
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required_compat_scodoc7(Permission.ScoView)
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def XMLgetBilletsEtud(etudid=False):
|
def XMLgetBilletsEtud(etudid=False, code_nip=False):
|
||||||
"""Liste billets pour un etudiant"""
|
"""Liste billets pour un etudiant"""
|
||||||
log("Warning: called deprecated XMLgetBilletsEtud")
|
log("Warning: called deprecated XMLgetBilletsEtud")
|
||||||
|
if etudid is False:
|
||||||
|
etud = Identite.query.filter_by(code_nip=str(code_nip)).first_or_404()
|
||||||
|
etudid = etud.id
|
||||||
table = sco_abs_billets.table_billets_etud(etudid)
|
table = sco_abs_billets.table_billets_etud(etudid)
|
||||||
if table:
|
if table:
|
||||||
return table.make_page(format="xml")
|
return table.make_page(format="xml")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.3.25"
|
SCOVERSION = "9.3.26"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
@ -10,6 +10,8 @@ SCONEWS = """
|
|||||||
<ul>
|
<ul>
|
||||||
<li>ScoDoc 9.3</li>
|
<li>ScoDoc 9.3</li>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>Nouvelle API REST pour connecter ScoDoc à d'autres applications<li>
|
||||||
|
<li>Module de gestion des relations avec les entreprises</li>
|
||||||
<li>Prise en charge des parcours BUT</li>
|
<li>Prise en charge des parcours BUT</li>
|
||||||
<li>Association des UEs aux compétences du référentiel</li>
|
<li>Association des UEs aux compétences du référentiel</li>
|
||||||
<li>Jury BUT1</li>
|
<li>Jury BUT1</li>
|
||||||
@ -21,7 +23,6 @@ SCONEWS = """
|
|||||||
<ul>
|
<ul>
|
||||||
<li>Tableau récap. complet pour BUT et autres formations.</li>
|
<li>Tableau récap. complet pour BUT et autres formations.</li>
|
||||||
<li>Tableau état évaluations</li>
|
<li>Tableau état évaluations</li>
|
||||||
<li>Version alpha du module "relations entreprises"</li>
|
|
||||||
<li>Export des trombinoscope en document docx</li>
|
<li>Export des trombinoscope en document docx</li>
|
||||||
<li>Très nombreux correctifs</li>
|
<li>Très nombreux correctifs</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -4,143 +4,97 @@
|
|||||||
|
|
||||||
"""Exemple utilisation API ScoDoc 9 avec jeton obtenu par basic authentication
|
"""Exemple utilisation API ScoDoc 9 avec jeton obtenu par basic authentication
|
||||||
|
|
||||||
|
Usage:
|
||||||
Utilisation: créer les variables d'environnement: (indiquer les valeurs
|
cd /opt/scodoc/tests/api
|
||||||
pour le serveur ScoDoc que vous voulez interroger)
|
python -i exemple-api-basic.py
|
||||||
|
|
||||||
export SCODOC_URL="https://scodoc.xxx.net/"
|
|
||||||
export SCODOC_USER="xxx"
|
|
||||||
export SCODOC_PASSWD="xxx"
|
|
||||||
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
|
|
||||||
|
|
||||||
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
|
|
||||||
|
|
||||||
|
|
||||||
Travail en cours.
|
Pour utiliser l'API, (sur une base quelconque):
|
||||||
|
```
|
||||||
|
cd /opt/scodoc/tests/api
|
||||||
|
|
||||||
|
python -i exemple-api-basic.p
|
||||||
|
>>> admin_h = get_auth_headers("admin", "xxx")
|
||||||
|
>>> GET("/etudiant/etudid/14806", headers=admin_h)
|
||||||
|
```
|
||||||
|
|
||||||
|
Créer éventuellement un fichier `.env` dans /opt/scodoc/tests/api
|
||||||
|
avec la config du client API:
|
||||||
|
```
|
||||||
|
SCODOC_URL = "http://localhost:5000/"
|
||||||
|
API_USER = "admin"
|
||||||
|
API_PASSWORD = "test"
|
||||||
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import requests
|
|
||||||
import urllib3
|
|
||||||
from pprint import pprint as pp
|
from pprint import pprint as pp
|
||||||
|
import sys
|
||||||
|
import urllib3
|
||||||
|
from setup_test_api import (
|
||||||
|
API_PASSWORD,
|
||||||
|
API_URL,
|
||||||
|
API_USER,
|
||||||
|
APIError,
|
||||||
|
CHECK_CERTIFICATE,
|
||||||
|
get_auth_headers,
|
||||||
|
GET,
|
||||||
|
POST_JSON,
|
||||||
|
SCODOC_URL,
|
||||||
|
)
|
||||||
|
|
||||||
# --- Lecture configuration (variables d'env ou .env)
|
|
||||||
try:
|
|
||||||
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
except NameError:
|
|
||||||
BASEDIR = "."
|
|
||||||
|
|
||||||
load_dotenv(os.path.join(BASEDIR, ".env"))
|
if not CHECK_CERTIFICATE:
|
||||||
CHK_CERT = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
|
urllib3.disable_warnings()
|
||||||
SCODOC_URL = os.environ.get("SCODOC_URL") or "http://localhost:5000"
|
|
||||||
API_URL = SCODOC_URL + "/ScoDoc/api"
|
|
||||||
# Admin:
|
|
||||||
SCODOC_USER = os.environ["SCODOC_USER"]
|
|
||||||
SCODOC_PASSWORD = os.environ["SCODOC_PASSWORD"]
|
|
||||||
# Lecteur
|
|
||||||
SCODOC_USER_API_LECTEUR = os.environ["SCODOC_USER_API_LECTEUR"]
|
|
||||||
SCODOC_PASSWORD_API_LECTEUR = os.environ["SCODOC_PASSWORD_API_LECTEUR"]
|
|
||||||
|
|
||||||
print(f"SCODOC_URL={SCODOC_URL}")
|
print(f"SCODOC_URL={SCODOC_URL}")
|
||||||
print(f"API URL={API_URL}")
|
print(f"API URL={API_URL}")
|
||||||
|
|
||||||
# ---
|
|
||||||
if not CHK_CERT:
|
|
||||||
urllib3.disable_warnings()
|
|
||||||
|
|
||||||
|
HEADERS = get_auth_headers(API_USER, API_PASSWORD)
|
||||||
|
|
||||||
class ScoError(Exception):
|
departements = GET("/departements", headers=HEADERS)
|
||||||
pass
|
pp(departements)
|
||||||
|
|
||||||
|
|
||||||
def GET(path: str, headers={}, errmsg=None, dept=None):
|
|
||||||
"""Get and returns as JSON"""
|
|
||||||
if dept:
|
|
||||||
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
|
|
||||||
else:
|
|
||||||
url = API_URL + path
|
|
||||||
r = requests.get(url, headers=headers or HEADERS, verify=CHK_CERT)
|
|
||||||
if r.status_code != 200:
|
|
||||||
raise ScoError(errmsg or f"""erreur status={r.status_code} !\n{r.text}""")
|
|
||||||
return r.json() # decode la reponse JSON
|
|
||||||
|
|
||||||
|
|
||||||
def POST(path: str, data: dict = {}, headers={}, errmsg=None):
|
|
||||||
"""Post"""
|
|
||||||
r = requests.post(
|
|
||||||
API_URL + path,
|
|
||||||
data=data,
|
|
||||||
headers=headers or HEADERS,
|
|
||||||
verify=CHK_CERT,
|
|
||||||
)
|
|
||||||
if r.status_code != 200:
|
|
||||||
raise ScoError(errmsg or f"erreur status={r.status_code} !\n{r.text}")
|
|
||||||
return r.json() # decode la reponse JSON
|
|
||||||
|
|
||||||
|
|
||||||
def POST_JSON(path: str, data: dict = {}, headers={}, errmsg=None):
|
|
||||||
"""Post"""
|
|
||||||
r = requests.post(
|
|
||||||
API_URL + path,
|
|
||||||
json=data,
|
|
||||||
headers=headers or HEADERS,
|
|
||||||
verify=CHK_CERT,
|
|
||||||
)
|
|
||||||
if r.status_code != 200:
|
|
||||||
raise ScoError(errmsg or f"erreur status={r.status_code} !\n{r.text}")
|
|
||||||
return r.json() # decode la reponse JSON
|
|
||||||
|
|
||||||
|
|
||||||
def GET_TOKEN(user, password):
|
|
||||||
"Obtention du jeton (token)"
|
|
||||||
r = requests.post(API_URL + "/tokens", auth=(user, password))
|
|
||||||
assert r.status_code == 200
|
|
||||||
token = r.json()["token"]
|
|
||||||
return {"Authorization": f"Bearer {token}"}
|
|
||||||
|
|
||||||
|
|
||||||
HEADERS = GET_TOKEN(SCODOC_USER, SCODOC_PASSWORD)
|
|
||||||
HEADERS_USER = GET_TOKEN(SCODOC_USER_API_LECTEUR, SCODOC_PASSWORD_API_LECTEUR)
|
|
||||||
|
|
||||||
r = requests.get(API_URL + "/departements", headers=HEADERS, verify=CHK_CERT)
|
|
||||||
if r.status_code != 200:
|
|
||||||
raise ScoError("erreur de connexion: vérifier adresse et identifiants")
|
|
||||||
|
|
||||||
pp(r.json())
|
|
||||||
|
|
||||||
# Liste de tous les étudiants en cours (de tous les depts)
|
# Liste de tous les étudiants en cours (de tous les depts)
|
||||||
r = requests.get(API_URL + "/etudiants/courant", headers=HEADERS, verify=CHK_CERT)
|
etuds = GET("/etudiants/courants", headers=HEADERS)
|
||||||
if r.status_code != 200:
|
|
||||||
raise ScoError("erreur de connexion: vérifier adresse et identifiants")
|
|
||||||
|
|
||||||
print(f"{len(r.json())} étudiants courants")
|
print(f"{len(etuds)} étudiants courants")
|
||||||
|
|
||||||
|
raise Exception("arret en mode interactif")
|
||||||
|
|
||||||
|
# ---------------- DIVERS ESSAIS EN MODE INTERACTIF
|
||||||
|
# ---------------- A ADAPTER A VOS BESOINS
|
||||||
|
|
||||||
# Bulletin d'un BUT
|
# Bulletin d'un BUT
|
||||||
formsemestre_id = 1063 # A adapter
|
formsemestre_id = 1063 # A adapter
|
||||||
etudid = 16450
|
etudid = 16450
|
||||||
bul = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
|
bul = GET(
|
||||||
|
f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin",
|
||||||
|
headers=HEADERS,
|
||||||
|
)
|
||||||
|
|
||||||
# d'un DUT
|
# d'un DUT
|
||||||
formsemestre_id = 1062 # A adapter
|
formsemestre_id = 1062 # A adapter
|
||||||
etudid = 16309
|
etudid = 16309
|
||||||
bul_dut = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
|
bul_dut = GET(
|
||||||
|
f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin",
|
||||||
|
headers=HEADERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Infos sur un étudiant
|
# Infos sur un étudiant
|
||||||
etudid = 3561
|
etudid = 3561
|
||||||
code_nip = "11303314"
|
code_nip = "11303314"
|
||||||
etud = GET(f"/etudiant/etudid/{etudid}")
|
etud = GET(f"/etudiant/etudid/{etudid}", headers=HEADERS)
|
||||||
print(etud)
|
print(etud)
|
||||||
|
|
||||||
etud = GET(f"/etudiant/nip/{code_nip}")
|
etud = GET(f"/etudiant/nip/{code_nip}", headers=HEADERS)
|
||||||
print(etud)
|
print(etud)
|
||||||
|
|
||||||
sems = GET(f"/etudiant/etudid/{etudid}/formsemestres")
|
sems = GET(f"/etudiant/etudid/{etudid}/formsemestres", headers=HEADERS)
|
||||||
print("\n".join([s["titre_num"] for s in sems]))
|
print("\n".join([s["titre_num"] for s in sems]))
|
||||||
|
|
||||||
sems = GET(f"/etudiant/nip/{code_nip}/formsemestres")
|
sems = GET(f"/etudiant/nip/{code_nip}/formsemestres", headers=HEADERS)
|
||||||
print("\n".join([s["titre_num"] for s in sems]))
|
print("\n".join([s["titre_num"] for s in sems]))
|
||||||
|
|
||||||
# Evaluation
|
# Evaluation
|
||||||
@ -148,35 +102,72 @@ evals = GET("/evaluations/1")
|
|||||||
|
|
||||||
# Partitions d'un BUT
|
# Partitions d'un BUT
|
||||||
formsemestre_id = 1063 # A adapter
|
formsemestre_id = 1063 # A adapter
|
||||||
partitions = GET(f"/formsemestre/{formsemestre_id}/partitions")
|
partitions = GET(f"/formsemestre/{formsemestre_id}/partitions", headers=HEADERS)
|
||||||
print(partitions)
|
print(partitions)
|
||||||
pid = partitions[1]["id"]
|
pid = partitions[1]["id"]
|
||||||
partition = GET(f"/partition/{pid}")
|
partition = GET(f"/partition/{pid}", headers=HEADERS)
|
||||||
print(partition)
|
print(partition)
|
||||||
group_id = partition["groups"][0]["id"]
|
group_id = partition["groups"][0]["id"]
|
||||||
etuds = GET(f"/group/{group_id}/etudiants")
|
etuds = GET(f"/group/{group_id}/etudiants", headers=HEADERS)
|
||||||
print(f"{len(etuds)} étudiants")
|
print(f"{len(etuds)} étudiants")
|
||||||
pp(etuds[1])
|
pp(etuds[1])
|
||||||
|
|
||||||
etuds_dem = GET(f"/group/{group_id}/etudiants/query?etat=D")
|
etuds_dem = GET(f"/group/{group_id}/etudiants/query?etat=D", headers=HEADERS)
|
||||||
print(f"{len(etuds_dem)} étudiants")
|
print(f"{len(etuds_dem)} étudiants")
|
||||||
|
|
||||||
etudid = 16650
|
etudid = 16650
|
||||||
group_id = 5315
|
group_id = 5315
|
||||||
POST(f"/group/{group_id}/set_etudiant/{etudid}")
|
POST(f"/group/{group_id}/set_etudiant/{etudid}", headers=HEADERS)
|
||||||
|
|
||||||
|
|
||||||
POST_JSON(f"/partition/{pid}/group/create", data={"group_name": "Omega10"})
|
POST_JSON(
|
||||||
partitions = GET(f"/formsemestre/{formsemestre_id}/partitions")
|
f"/partition/{pid}/group/create", data={"group_name": "Omega10"}, headers=HEADERS
|
||||||
|
)
|
||||||
|
partitions = GET(f"/formsemestre/{formsemestre_id}/partitions", headers=HEADERS)
|
||||||
pp(partitions)
|
pp(partitions)
|
||||||
|
|
||||||
POST_JSON(f"/group/5559/delete")
|
POST_JSON(f"/group/5559/delete", headers=HEADERS)
|
||||||
POST_JSON(f"/group/5327/edit", data={"group_name": "TDXXX"})
|
POST_JSON(f"/group/5327/edit", data={"group_name": "TDXXX"}, headers=HEADERS)
|
||||||
|
|
||||||
# --------- XXX à passer en dans les tests unitaires
|
# --------- Toutes les bulletins, un à un, et les décisions de jury d'un semestre
|
||||||
|
formsemestre_id = 911
|
||||||
|
etuds = GET(f"/formsemestre/{formsemestre_id}/etudiants", headers=admin_h)
|
||||||
|
etudid = 16450
|
||||||
|
bul = GET(
|
||||||
|
f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin",
|
||||||
|
headers=HEADERS,
|
||||||
|
)
|
||||||
|
for etud in etuds:
|
||||||
|
bul = GET(
|
||||||
|
f"/etudiant/etudid/{etud['id']}/formsemestre/{formsemestre_id}/bulletin",
|
||||||
|
headers=HEADERS,
|
||||||
|
)
|
||||||
|
sys.stdout.write(".")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
print("")
|
||||||
|
decisions = GET(f"/formsemestre/{formsemestre_id}/decisions_jury", headers=HEADERS)
|
||||||
|
|
||||||
|
# Decisions de jury des _tous_ les formsemestre, un à un, en partant de l'id le plus élevé
|
||||||
|
formsemestres = GET("/formsemestres/query", headers=HEADERS)
|
||||||
|
formsemestres.sort(key=lambda s: s["id"], reverse=1)
|
||||||
|
print(f"###### Testing {len(formsemestres)} formsemestres...")
|
||||||
|
for formsemestre in formsemestres:
|
||||||
|
print(formsemestre["session_id"])
|
||||||
|
try:
|
||||||
|
decisions = GET(
|
||||||
|
f"/formsemestre/{formsemestre['id']}/decisions_jury", headers=HEADERS
|
||||||
|
)
|
||||||
|
except APIError as exc:
|
||||||
|
if exc.payload.get("message") != "non implemente":
|
||||||
|
raise
|
||||||
|
decisions = []
|
||||||
|
print(f"{len(decisions)} decisions")
|
||||||
|
|
||||||
|
# --------- A été passé dans les tests unitaires:
|
||||||
|
|
||||||
# 0- Prend un étudiant au hasard dans le semestre
|
# 0- Prend un étudiant au hasard dans le semestre
|
||||||
etud = GET(f"/formsemestre/{formsemestre_id}/etudiants")[10]
|
etud = GET(f"/formsemestre/{formsemestre_id}/etudiants", headers=HEADERS)[10]
|
||||||
etudid = etud["id"]
|
etudid = etud["id"]
|
||||||
|
|
||||||
# 1- Crée une partition, puis la change de nom
|
# 1- Crée une partition, puis la change de nom
|
||||||
@ -188,30 +179,43 @@ partition_id = js["id"]
|
|||||||
POST_JSON(
|
POST_JSON(
|
||||||
f"/partition/{partition_id}/edit",
|
f"/partition/{partition_id}/edit",
|
||||||
data={"partition_name": "PART1", "show_in_lists": True},
|
data={"partition_name": "PART1", "show_in_lists": True},
|
||||||
|
headers=HEADERS,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2- Crée un groupe
|
# 2- Crée un groupe
|
||||||
js = POST_JSON(f"/partition/{partition_id}/group/create", data={"group_name": "G1"})
|
js = POST_JSON(
|
||||||
|
f"/partition/{partition_id}/group/create",
|
||||||
|
data={"group_name": "G1"},
|
||||||
|
headers=HEADERS,
|
||||||
|
)
|
||||||
group_1 = js["id"]
|
group_1 = js["id"]
|
||||||
|
|
||||||
# 3- Crée deux autres groupes
|
# 3- Crée deux autres groupes
|
||||||
js = POST_JSON(f"/partition/{partition_id}/group/create", data={"group_name": "G2"})
|
js = POST_JSON(
|
||||||
js = POST_JSON(f"/partition/{partition_id}/group/create", data={"group_name": "G3"})
|
f"/partition/{partition_id}/group/create",
|
||||||
|
data={"group_name": "G2"},
|
||||||
|
headers=HEADERS,
|
||||||
|
)
|
||||||
|
js = POST_JSON(
|
||||||
|
f"/partition/{partition_id}/group/create",
|
||||||
|
data={"group_name": "G3"},
|
||||||
|
headers=HEADERS,
|
||||||
|
)
|
||||||
|
|
||||||
# 4- Affecte étudiant au groupe G1
|
# 4- Affecte étudiant au groupe G1
|
||||||
POST_JSON(f"/group/{group_1}/set_etudiant/{etudid}")
|
POST_JSON(f"/group/{group_1}/set_etudiant/{etudid}", headers=HEADERS)
|
||||||
|
|
||||||
# 5- retire du groupe
|
# 5- retire du groupe
|
||||||
POST_JSON(f"/group/{group_1}/remove_etudiant/{etudid}")
|
POST_JSON(f"/group/{group_1}/remove_etudiant/{etudid}", headers=HEADERS)
|
||||||
|
|
||||||
# 6- affecte au groupe G2
|
# 6- affecte au groupe G2
|
||||||
partition = GET(f"/partition/{partition_id}")
|
partition = GET(f"/partition/{partition_id}")
|
||||||
assert len(partition["groups"]) == 3
|
assert len(partition["groups"]) == 3
|
||||||
group_2 = [g for g in partition["groups"].values() if g["group_name"] == "G2"][0]["id"]
|
group_2 = [g for g in partition["groups"].values() if g["group_name"] == "G2"][0]["id"]
|
||||||
POST_JSON(f"/group/{group_2}/set_etudiant/{etudid}")
|
POST_JSON(f"/group/{group_2}/set_etudiant/{etudid}", headers=HEADERS)
|
||||||
|
|
||||||
# 7- Membres du groupe
|
# 7- Membres du groupe
|
||||||
etuds_g2 = GET(f"/group/{group_2}/etudiants")
|
etuds_g2 = GET(f"/group/{group_2}/etudiants", headers=HEADERS)
|
||||||
assert len(etuds_g2) == 1
|
assert len(etuds_g2) == 1
|
||||||
assert etuds_g2[0]["id"] == etudid
|
assert etuds_g2[0]["id"] == etudid
|
||||||
|
|
||||||
@ -221,9 +225,13 @@ group_3 = [g for g in partition["groups"].values() if g["group_name"] == "G3"][0
|
|||||||
POST_JSON(
|
POST_JSON(
|
||||||
f"/partition/{partition_id}/groups/order",
|
f"/partition/{partition_id}/groups/order",
|
||||||
data=[group_2, group_1, group_3],
|
data=[group_2, group_1, group_3],
|
||||||
|
headers=HEADERS,
|
||||||
)
|
)
|
||||||
|
|
||||||
new_groups = [g["id"] for g in GET(f"/partition/{partition_id}")["groups"].values()]
|
new_groups = [
|
||||||
|
g["id"]
|
||||||
|
for g in GET(f"/partition/{partition_id}", headers=HEADERS)["groups"].values()
|
||||||
|
]
|
||||||
assert new_groups == [group_2, group_1, group_3]
|
assert new_groups == [group_2, group_1, group_3]
|
||||||
|
|
||||||
# 9- Suppression
|
# 9- Suppression
|
||||||
@ -248,23 +256,25 @@ POST_JSON(f"/partition/{partition_id}/delete")
|
|||||||
POST_JSON(
|
POST_JSON(
|
||||||
"/partition/2264/groups/order",
|
"/partition/2264/groups/order",
|
||||||
data=[5563, 5562, 5561, 5560, 5558, 5557, 5316, 5315],
|
data=[5563, 5562, 5561, 5560, 5558, 5557, 5316, 5315],
|
||||||
|
headers=HEADERS,
|
||||||
)
|
)
|
||||||
|
|
||||||
POST_JSON(
|
POST_JSON(
|
||||||
"/formsemestre/1063/partitions/order",
|
"/formsemestre/1063/partitions/order",
|
||||||
data=[2264, 2263, 2265, 2266, 2267, 2372, 2378],
|
data=[2264, 2263, 2265, 2266, 2267, 2372, 2378],
|
||||||
|
headers=HEADERS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
GET(f"/partition/2264")
|
GET(f"/partition/2264", headers=HEADERS)
|
||||||
|
|
||||||
# Recherche de formsemestres
|
# Recherche de formsemestres
|
||||||
sems = GET(f"/formsemestres/query?etape_apo=V1RT&annee_scolaire=2021")
|
sems = GET(f"/formsemestres/query?etape_apo=V1RT&annee_scolaire=2021", headers=HEADERS)
|
||||||
|
|
||||||
# Table récap:
|
# Table récap:
|
||||||
pp(GET(f"/formsemestre/1063/resultats")[0])
|
pp(GET(f"/formsemestre/1063/resultats", headers=HEADERS)[0])
|
||||||
|
|
||||||
pp(GET(f"/formsemestre/880/resultats")[0])
|
pp(GET(f"/formsemestre/880/resultats", headers=HEADERS)[0])
|
||||||
|
|
||||||
# # sems est une liste de semestres (dictionnaires)
|
# # sems est une liste de semestres (dictionnaires)
|
||||||
# for sem in sems:
|
# for sem in sems:
|
||||||
|
177
tests/api/make_samples.py
Normal file
177
tests/api/make_samples.py
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Construction des fichiers exemples pour la documentation.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
cd /opt/scodoc/tests/api
|
||||||
|
python make_samples.py
|
||||||
|
|
||||||
|
doit être exécutée immédiatement apres une initialisation de la base pour test API! (car dépendant des identifiants générés lors de la création des objets)
|
||||||
|
cd /opt/scodoc/tests/api
|
||||||
|
tools/create_database.sh --drop SCODOC_TEST_API && flask db upgrade &&flask sco-db-init --erase && flask init-test-database
|
||||||
|
|
||||||
|
Créer éventuellement un fichier `.env` dans /opt/scodoc/tests/api
|
||||||
|
avec la config du client API:
|
||||||
|
```
|
||||||
|
SCODOC_URL = "http://localhost:5000/"
|
||||||
|
```
|
||||||
|
|
||||||
|
Cet utilitaire prend en donnée le fichier de nom `samples.csv` contenant la description des exemples (séparés par une tabulation (\t), une ligne par exemple)
|
||||||
|
* Le nom de l'exemple donne le nom du fichier généré (nom_exemple => nom_exemple.json.md). plusieurs lignes peuvent partager le même nom. dans ce cas le fichier contiendra chacun des exemples
|
||||||
|
* l'url utilisée
|
||||||
|
* la permission nécessaire (par défaut ScoView)
|
||||||
|
* la méthode GET,POST à utiliser (si commence par #, la ligne est ignorée)
|
||||||
|
* les arguments éventuel (en cas de POST): une chaîne de caractère selon json
|
||||||
|
|
||||||
|
Implémentation:
|
||||||
|
Le code complète une structure de données (Samples) qui est un dictionnaire de set (indicé par le nom des exemple.
|
||||||
|
Chacun des éléments du set est un exemple (Sample)
|
||||||
|
Quand la structure est complète, on génére tous les fichiers textes
|
||||||
|
- nom de l exemple
|
||||||
|
- un ou plusieurs exemples avec pour chaucn
|
||||||
|
- l url utilisée
|
||||||
|
- les arguments éventuels
|
||||||
|
- le résultat
|
||||||
|
Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples) qui est créé ou écrasé si déjà existant
|
||||||
|
|
||||||
|
TODO: ajouter un argument au script permettant de ne générer qu'un seul fichier (exemple: `python make_samples.py nom_exemple`)
|
||||||
|
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from collections import defaultdict
|
||||||
|
from pprint import pprint as pp
|
||||||
|
from pprint import pformat as pf
|
||||||
|
|
||||||
|
import urllib3
|
||||||
|
import json
|
||||||
|
from setup_test_api import (
|
||||||
|
API_PASSWORD,
|
||||||
|
API_URL,
|
||||||
|
API_USER,
|
||||||
|
APIError,
|
||||||
|
CHECK_CERTIFICATE,
|
||||||
|
get_auth_headers,
|
||||||
|
GET,
|
||||||
|
POST_JSON,
|
||||||
|
SCODOC_URL,
|
||||||
|
)
|
||||||
|
|
||||||
|
DATA_DIR = "/tmp/samples/"
|
||||||
|
|
||||||
|
|
||||||
|
class Sample:
|
||||||
|
def __init__(self, url, method="GET", permission="ScoView", content=None):
|
||||||
|
self.content = content
|
||||||
|
self.permission = permission
|
||||||
|
self.url = url
|
||||||
|
self.method = method
|
||||||
|
self.result = None
|
||||||
|
if permission == "ScoView":
|
||||||
|
HEADERS = get_auth_headers("test", "test")
|
||||||
|
elif permission == "ScoSuperAdmin":
|
||||||
|
HEADERS = get_auth_headers("admin_api", "admin_api")
|
||||||
|
elif permission == "ScoUsersAdmin":
|
||||||
|
HEADERS = get_auth_headers("admin_api", "admin_api")
|
||||||
|
else:
|
||||||
|
raise Exception(f"Bad permission : {permission}")
|
||||||
|
if self.method == "GET":
|
||||||
|
self.result = GET(self.url, HEADERS)
|
||||||
|
elif self.method == "POST":
|
||||||
|
if self.content == "":
|
||||||
|
self.result = POST_JSON(self.url, headers=HEADERS)
|
||||||
|
else:
|
||||||
|
HEADERS["Content-Type"] = "application/json ; charset=utf-8"
|
||||||
|
self.result = POST_JSON(self.url, json.loads(self.content), HEADERS)
|
||||||
|
elif self.method[0] != "#":
|
||||||
|
raise Exception(f"Bad method : {self.method}")
|
||||||
|
else: # method begin with # => comment
|
||||||
|
print(" pass")
|
||||||
|
self.shorten()
|
||||||
|
file = open(f"sample_TEST.json.md", "tw")
|
||||||
|
self.dump(file)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
def _shorten(self, item):
|
||||||
|
if isinstance(item, list):
|
||||||
|
return [self._shorten(child) for child in item[:2]]
|
||||||
|
return item
|
||||||
|
|
||||||
|
def shorten(self):
|
||||||
|
self.result = self._shorten(self.result)
|
||||||
|
|
||||||
|
def pp(self):
|
||||||
|
print(f"------ url: {self.url}")
|
||||||
|
print(f"method: {self.method}")
|
||||||
|
print(f"content: {self.content}")
|
||||||
|
print(f"permission: {self.permission}")
|
||||||
|
pp(self.result, indent=4)
|
||||||
|
|
||||||
|
def dump(self, file):
|
||||||
|
file.write(f"#### {self.method} {self.url}\n")
|
||||||
|
if len(self.content) > 0:
|
||||||
|
file.write(f"> `Content-Type: application/json`\n")
|
||||||
|
file.write(f"> \n")
|
||||||
|
file.write(f"> `{self.content}`\n\n")
|
||||||
|
|
||||||
|
file.write("```json\n")
|
||||||
|
file.write(json.dumps(self.result, indent=4))
|
||||||
|
file.write("\n```\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
class Samples:
|
||||||
|
def __init__(self):
|
||||||
|
self.entries = defaultdict(lambda: set())
|
||||||
|
|
||||||
|
def add_sample(self, entry, url, method="GET", permission="ScoView", content=None):
|
||||||
|
show_content = "" if content == "" else f": '{content}'"
|
||||||
|
print(f"{entry:50} {method:5} {url:50} {show_content}")
|
||||||
|
sample = Sample(url, method, permission, content)
|
||||||
|
self.entries[entry].add(sample)
|
||||||
|
|
||||||
|
def pp(self):
|
||||||
|
for entry, samples in self.entries.items():
|
||||||
|
print(f"=== {entry}")
|
||||||
|
for sample in samples:
|
||||||
|
sample.pp()
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
for entry, samples in self.entries.items():
|
||||||
|
file = open(f"{DATA_DIR}sample_{entry}.json.md", "tw")
|
||||||
|
file.write(f"### {entry}\n\n")
|
||||||
|
for sample in samples:
|
||||||
|
sample.dump(file)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
|
||||||
|
def make_samples():
|
||||||
|
if os.path.exists(DATA_DIR):
|
||||||
|
if not os.path.isdir(DATA_DIR):
|
||||||
|
raise f"{DATA_DIR} existe déjà et n'est pas un répertoire"
|
||||||
|
else:
|
||||||
|
# DATA_DIR existe déjà - effacer et recréer
|
||||||
|
shutil.rmtree(DATA_DIR)
|
||||||
|
os.mkdir(DATA_DIR)
|
||||||
|
else:
|
||||||
|
os.mkdir("/tmp/samples")
|
||||||
|
|
||||||
|
samples = Samples()
|
||||||
|
# samples.pp()
|
||||||
|
with open("samples.csv") as f:
|
||||||
|
L = [x[:-1].split("\t") for x in f]
|
||||||
|
for line in L[1:]:
|
||||||
|
entry_name = line[0]
|
||||||
|
url = line[1]
|
||||||
|
permission = line[2] if line[2] != "" else "ScoView"
|
||||||
|
method = line[3] if line[3] != "" else "GET"
|
||||||
|
content = line[4]
|
||||||
|
samples.add_sample(entry_name, url, method, permission, content)
|
||||||
|
samples.dump()
|
||||||
|
return samples
|
||||||
|
|
||||||
|
|
||||||
|
if not CHECK_CERTIFICATE:
|
||||||
|
urllib3.disable_warnings()
|
||||||
|
make_samples()
|
77
tests/api/samples.csv
Normal file
77
tests/api/samples.csv
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
reference url permission method content
|
||||||
|
departements /departements GET
|
||||||
|
departements-ids /departements_ids GET
|
||||||
|
departement /departement/TAPI GET
|
||||||
|
departement /departement/id/1 GET
|
||||||
|
departement-etudiants /departement/TAPI/etudiants GET
|
||||||
|
departement-etudiants /departement/id/1/etudiants GET
|
||||||
|
departement-formsemestres_ids /departement/TAPI/formsemestres_ids GET
|
||||||
|
departement-formsemestres_ids /departement/id/1/formsemestres_ids GET
|
||||||
|
departement-formsemestres-courants /departement/TAPI/formsemestres_courants GET
|
||||||
|
departement-formsemestres-courants /departement/id/1/formsemestres_courants GET
|
||||||
|
departement-create /departement/create ScoSuperAdmin POST {"acronym": "NEWONE" , "visible": true}
|
||||||
|
departement-edit /departement/NEWONE/edit ScoSuperAdmin POST {"visible": false}
|
||||||
|
departement-delete /departement/NEWONE/delete ScoSuperAdmin POST
|
||||||
|
etudiants-courants /etudiants/courants GET
|
||||||
|
etudiants-courants /etudiants/courants/long GET
|
||||||
|
etudiant /etudiant/etudid/11 GET
|
||||||
|
etudiant /etudiant/nip/11 GET
|
||||||
|
etudiant /etudiant/ine/INE11 GET
|
||||||
|
etudiants-clef /etudiants/etudid/11 GET
|
||||||
|
etudiants-clef /etudiants/ine/INE11 GET
|
||||||
|
etudiants-clef /etudiants/nip/11 GET
|
||||||
|
etudiant-formsemestres /etudiant/etudid/11/formsemestres GET
|
||||||
|
etudiant-formsemestres /etudiant/ine/INE11/formsemestres GET
|
||||||
|
etudiant_formsemestres /etudiant/nip/11/formsemestres GET
|
||||||
|
etudiant-formsemestre-bulletin /etudiant/etudid/11/formsemestre/1/bulletin GET
|
||||||
|
etudiant-formsemestre-bulletin /etudiant/ine/INE11/formsemestre/1/bulletin GET
|
||||||
|
etudiant-formsemestre-bulletin /etudiant/nip/11/formsemestre/1/bulletin GET
|
||||||
|
etudiant-formsemestre-groups /etudiant/etudid/11/formsemestre/1/groups GET
|
||||||
|
formations /formations GET
|
||||||
|
formations_ids /formations_ids GET
|
||||||
|
formation /formation/1 GET
|
||||||
|
formation-export /formation/1/export GET
|
||||||
|
formation-export /formation/1/export_with_ids GET
|
||||||
|
formation-referentiel_competences /formation/1/referentiel_competences GET
|
||||||
|
moduleimpl /moduleimpl/1 GET
|
||||||
|
formsemestre /formsemestre/1 GET
|
||||||
|
formsemestres-query /formsemestres/query?annee_scolaire=2022&etape_apo=A2 GET
|
||||||
|
formsemestre-bulletins /formsemestre/1/bulletins GET
|
||||||
|
formsemestre-programme /formsemestre/1/programme GET
|
||||||
|
formsemestre-etudiants /formsemestre/1/etudiants GET
|
||||||
|
formsemestre-etudiants-query /formsemestre/1/etudiants/query?etat=D GET
|
||||||
|
formsemestre-etat_evals /formsemestre/1/etat_evals GET
|
||||||
|
formsemestre-resultats /formsemestre/1/resultats GET
|
||||||
|
formsemestre-decisions_jury /formsemestre/1/decisions_jury GET
|
||||||
|
formsemestre-partitions /formsemestre/1/partitions GET
|
||||||
|
partition /partition/1 GET
|
||||||
|
group-etudiants /group/1/etudiants GET
|
||||||
|
group-etudiants-query /group/1/etudiants/query?etat=D GET
|
||||||
|
moduleimpl-evaluations /moduleimpl/1/evaluations GET
|
||||||
|
evaluation-notes /evaluation/1/notes GET
|
||||||
|
user /user/1 GET
|
||||||
|
users-query /users/query?starts_with=u_ GET
|
||||||
|
permissions /permissions GET
|
||||||
|
roles /roles GET
|
||||||
|
role /role/Observateur GET
|
||||||
|
group-set_etudiant /group/1/set_etudiant/10 ScoSuperAdmin POST
|
||||||
|
group-remove_etudiant /group/1/remove_etudiant/10 ScoSuperAdmin POST
|
||||||
|
partition-group-create /partition/1/group/create ScoSuperAdmin POST {"group_name": "NEW_GROUP"}
|
||||||
|
group-edit /group/2/edit ScoSuperAdmin POST {"group_name": "NEW_GROUP2"}
|
||||||
|
group-delete /group/2/delete ScoSuperAdmin POST
|
||||||
|
formsemestre-partition-create /formsemestre/1/partition/create ScoSuperAdmin POST {"partition_name": "PART"}
|
||||||
|
formsemestre-partitions-order /formsemestre/1/partitions/order ScoSuperAdmin POST [ 1 ]
|
||||||
|
partition-edit /partition/1/edit ScoSuperAdmin POST {"partition_name":"P2BIS", "numero":3,"bul_show_rank":true,"show_in_lists":false, "groups_editable":true}
|
||||||
|
partition-remove_etudiant /partition/2/remove_etudiant/10 ScoSuperAdmin POST
|
||||||
|
partition-groups-order /partition/1/groups/order ScoSuperAdmin POST [ 1 ]
|
||||||
|
partition-delete /partition/2/delete ScoSuperAdmin POST
|
||||||
|
user-create /user/create ScoSuperAdmin POST {"user_name": "alain", "dept": null, "nom": "alain", "prenom": "bruno", "active": true }
|
||||||
|
user-edit /user/10/edit ScoSuperAdmin POST { "dept": "TAPI", "nom": "alain2", "prenom": "bruno2", "active": false }
|
||||||
|
user-role-add /user/10/role/Observateur/add ScoSuperAdmin POST
|
||||||
|
user-role-remove /user/10/role/Observateur/remove ScoSuperAdmin POST
|
||||||
|
role-create /role/create/customRole ScoSuperAdmin POST {"permissions": ["ScoView", "ScoUsersView"]}
|
||||||
|
role-remove_permission /role/customRole/remove_permission/ScoUsersView ScoSuperAdmin POST
|
||||||
|
role-add_permission /role/customRole/add_permission/ScoUsersView ScoSuperAdmin POST
|
||||||
|
role-edit /role/customRole/edit ScoSuperAdmin POST { "name" : "LaveurDeVitres", "permissions" : [ "ScoView", "APIView" ] }
|
||||||
|
role-edit /role/customRole/edit ScoSuperAdmin POST { "name" : "LaveurDeVitres", "permissions" : [ "ScoView", "APIView" ] }
|
||||||
|
role-delete /role/customRole/delete ScoSuperAdmin POST
|
Can't render this file because it contains an unexpected character in line 12 and column 60.
|
@ -18,10 +18,15 @@ import requests
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
# --- Lecture configuration (variables d'env ou .env)
|
||||||
|
try:
|
||||||
|
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
except NameError:
|
||||||
BASEDIR = "/opt/scodoc/tests/api"
|
BASEDIR = "/opt/scodoc/tests/api"
|
||||||
|
|
||||||
load_dotenv(os.path.join(BASEDIR, ".env"))
|
load_dotenv(os.path.join(BASEDIR, ".env"))
|
||||||
CHECK_CERTIFICATE = bool(os.environ.get("CHECK_CERTIFICATE", False))
|
CHECK_CERTIFICATE = bool(os.environ.get("CHECK_CERTIFICATE", False))
|
||||||
SCODOC_URL = os.environ["SCODOC_URL"]
|
SCODOC_URL = os.environ["SCODOC_URL"] or "http://localhost:5000"
|
||||||
API_URL = SCODOC_URL + "/ScoDoc/api"
|
API_URL = SCODOC_URL + "/ScoDoc/api"
|
||||||
API_USER = os.environ.get("API_USER", "test")
|
API_USER = os.environ.get("API_USER", "test")
|
||||||
API_PASSWORD = os.environ.get("API_PASSWD", "test")
|
API_PASSWORD = os.environ.get("API_PASSWD", "test")
|
||||||
@ -33,7 +38,9 @@ print(f"API URL={API_URL}")
|
|||||||
|
|
||||||
|
|
||||||
class APIError(Exception):
|
class APIError(Exception):
|
||||||
pass
|
def __init__(self, message: str = "", payload=None):
|
||||||
|
self.message = message
|
||||||
|
self.payload = payload or {}
|
||||||
|
|
||||||
|
|
||||||
def get_auth_headers(user, password) -> dict:
|
def get_auth_headers(user, password) -> dict:
|
||||||
@ -65,7 +72,7 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None):
|
|||||||
url = API_URL + path
|
url = API_URL + path
|
||||||
r = requests.get(url, headers=headers or {}, verify=CHECK_CERTIFICATE)
|
r = requests.get(url, headers=headers or {}, verify=CHECK_CERTIFICATE)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise APIError(errmsg or f"""erreur status={r.status_code} !\n{r.text}""")
|
raise APIError(errmsg or f"""erreur status={r.status_code} !""", r.json())
|
||||||
return r.json() # decode la reponse JSON
|
return r.json() # decode la reponse JSON
|
||||||
|
|
||||||
|
|
||||||
@ -78,5 +85,5 @@ def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None):
|
|||||||
verify=CHECK_CERTIFICATE,
|
verify=CHECK_CERTIFICATE,
|
||||||
)
|
)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise APIError(errmsg or f"erreur status={r.status_code} !\n{r.text}")
|
raise APIError(errmsg or f"erreur status={r.status_code} !", r.json())
|
||||||
return r.json() # decode la reponse JSON
|
return r.json() # decode la reponse JSON
|
||||||
|
@ -515,22 +515,28 @@ def test_formsemestre_etudiants(api_headers):
|
|||||||
assert isinstance(group["group_id"], int)
|
assert isinstance(group["group_id"], int)
|
||||||
assert group["group_name"] is None or isinstance(group["group_name"], int)
|
assert group["group_name"] is None or isinstance(group["group_name"], int)
|
||||||
|
|
||||||
|
## Avec query:
|
||||||
|
etuds_query = GET(
|
||||||
|
f"/formsemestre/{formsemestre_id}/etudiants/query", headers=api_headers
|
||||||
|
)
|
||||||
|
assert etuds_query == etuds
|
||||||
|
|
||||||
### actifs
|
### actifs
|
||||||
etuds_actifs = GET(
|
etuds_actifs = GET(
|
||||||
f"/formsemestre/{formsemestre_id}/etudiants/actifs", headers=api_headers
|
f"/formsemestre/{formsemestre_id}/etudiants/query?etat=I", headers=api_headers
|
||||||
)
|
)
|
||||||
assert isinstance(etuds_actifs, list)
|
assert isinstance(etuds_actifs, list)
|
||||||
|
|
||||||
### démissionnaires
|
### démissionnaires
|
||||||
etuds_dem = GET(
|
etuds_dem = GET(
|
||||||
f"/formsemestre/{formsemestre_id}/etudiants/demissionnaires",
|
f"/formsemestre/{formsemestre_id}/etudiants/query?etat=D",
|
||||||
headers=api_headers,
|
headers=api_headers,
|
||||||
)
|
)
|
||||||
assert isinstance(etuds_dem, list)
|
assert isinstance(etuds_dem, list)
|
||||||
|
|
||||||
### défaillants
|
### défaillants
|
||||||
etuds_def = GET(
|
etuds_def = GET(
|
||||||
f"/formsemestre/{formsemestre_id}/etudiants/defaillants", headers=api_headers
|
f"/formsemestre/{formsemestre_id}/etudiants/query?etat=DEF", headers=api_headers
|
||||||
)
|
)
|
||||||
assert isinstance(etuds_def, list)
|
assert isinstance(etuds_def, list)
|
||||||
|
|
||||||
|
@ -134,10 +134,10 @@ def test_etud_in_group(api_headers):
|
|||||||
- /group/<int:group_id>/etudiants/query?etat=<string:etat>
|
- /group/<int:group_id>/etudiants/query?etat=<string:etat>
|
||||||
"""
|
"""
|
||||||
group_id = 1
|
group_id = 1
|
||||||
etudiants = GET(f"/group/{group_id}/etudiants", headers=api_headers)
|
etuds = GET(f"/group/{group_id}/etudiants", headers=api_headers)
|
||||||
assert isinstance(etudiants, list)
|
assert isinstance(etuds, list)
|
||||||
|
|
||||||
for etud in etudiants:
|
for etud in etuds:
|
||||||
assert verify_fields(etud, PARTITION_GROUPS_ETUD_FIELDS)
|
assert verify_fields(etud, PARTITION_GROUPS_ETUD_FIELDS)
|
||||||
assert isinstance(etud["id"], int)
|
assert isinstance(etud["id"], int)
|
||||||
assert isinstance(etud["dept_id"], int)
|
assert isinstance(etud["dept_id"], int)
|
||||||
@ -148,12 +148,14 @@ def test_etud_in_group(api_headers):
|
|||||||
assert isinstance(etud["code_nip"], str)
|
assert isinstance(etud["code_nip"], str)
|
||||||
assert isinstance(etud["code_ine"], str)
|
assert isinstance(etud["code_ine"], str)
|
||||||
|
|
||||||
|
# query sans filtre:
|
||||||
|
etuds_query = GET(f"/group/{group_id}/etudiants/query", headers=api_headers)
|
||||||
|
assert etuds_query == etuds
|
||||||
|
|
||||||
etat = "I"
|
etat = "I"
|
||||||
etudiants = GET(
|
etuds = GET(f"/group/{group_id}/etudiants/query?etat={etat}", headers=api_headers)
|
||||||
f"/group/{group_id}/etudiants/query?etat={etat}", headers=api_headers
|
assert isinstance(etuds, list)
|
||||||
)
|
for etud in etuds:
|
||||||
assert isinstance(etudiants, list)
|
|
||||||
for etud in etudiants:
|
|
||||||
assert verify_fields(etud, PARTITION_GROUPS_ETUD_FIELDS)
|
assert verify_fields(etud, PARTITION_GROUPS_ETUD_FIELDS)
|
||||||
assert isinstance(etud["id"], int)
|
assert isinstance(etud["id"], int)
|
||||||
assert isinstance(etud["dept_id"], int)
|
assert isinstance(etud["dept_id"], int)
|
||||||
|
@ -94,7 +94,7 @@ def test_edit_users(api_admin_headers):
|
|||||||
def test_roles(api_admin_headers):
|
def test_roles(api_admin_headers):
|
||||||
"""
|
"""
|
||||||
Routes: /user/create
|
Routes: /user/create
|
||||||
/user/edit/<int:uid>
|
/user/<int:uid>/edit
|
||||||
"""
|
"""
|
||||||
admin_h = api_admin_headers
|
admin_h = api_admin_headers
|
||||||
user = POST_JSON(
|
user = POST_JSON(
|
||||||
@ -105,7 +105,7 @@ def test_roles(api_admin_headers):
|
|||||||
uid = user["id"]
|
uid = user["id"]
|
||||||
ans = POST_JSON(f"/user/{uid}/role/Secr/add", headers=admin_h)
|
ans = POST_JSON(f"/user/{uid}/role/Secr/add", headers=admin_h)
|
||||||
assert ans["user_name"] == "test_roles"
|
assert ans["user_name"] == "test_roles"
|
||||||
role = POST_JSON("/role/Test_X/create", headers=admin_h)
|
role = POST_JSON("/role/create/Test_X", headers=admin_h)
|
||||||
assert role["role_name"] == "Test_X"
|
assert role["role_name"] == "Test_X"
|
||||||
assert role["permissions"] == []
|
assert role["permissions"] == []
|
||||||
role = GET("/role/Test_X", headers=admin_h)
|
role = GET("/role/Test_X", headers=admin_h)
|
||||||
|
Loading…
Reference in New Issue
Block a user