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 request
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoException
|
||||
|
||||
api_bp = Blueprint("api", __name__)
|
||||
api_web_bp = Blueprint("apiweb", __name__)
|
||||
|
||||
|
||||
@api_bp.errorhandler(ScoException)
|
||||
@api_bp.errorhandler(404)
|
||||
def api_error_handler(e):
|
||||
"erreurs API => json"
|
||||
|
@ -218,7 +218,6 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
||||
methods=["GET"],
|
||||
defaults={"version": "long", "pdf": False},
|
||||
)
|
||||
# Version PDF non testée
|
||||
@bp.route(
|
||||
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
||||
methods=["GET"],
|
||||
@ -254,6 +253,16 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
||||
methods=["GET"],
|
||||
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(
|
||||
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin",
|
||||
methods=["GET"],
|
||||
@ -269,7 +278,6 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
||||
methods=["GET"],
|
||||
defaults={"version": "long", "pdf": False},
|
||||
)
|
||||
# Version PDF non testée
|
||||
@api_web_bp.route(
|
||||
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin/pdf",
|
||||
methods=["GET"],
|
||||
|
@ -251,55 +251,52 @@ def formsemestre_programme(formsemestre_id: int):
|
||||
|
||||
@bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/etudiants",
|
||||
defaults={"etat": None},
|
||||
defaults={"with_query": False},
|
||||
)
|
||||
@bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/etudiants/actifs",
|
||||
defaults={"etat": scu.INSCRIT},
|
||||
)
|
||||
@bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/etudiants/demissionnaires",
|
||||
defaults={"etat": scu.DEMISSION},
|
||||
)
|
||||
@bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/etudiants/defaillants",
|
||||
defaults={"etat": scu.DEF},
|
||||
"/formsemestre/<int:formsemestre_id>/etudiants/query",
|
||||
defaults={"with_query": True},
|
||||
)
|
||||
@api_web_bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/etudiants",
|
||||
defaults={"etat": None},
|
||||
defaults={"with_query": False},
|
||||
)
|
||||
@api_web_bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/etudiants/actifs",
|
||||
defaults={"etat": scu.INSCRIT},
|
||||
)
|
||||
@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},
|
||||
"/formsemestre/<int:formsemestre_id>/etudiants/query",
|
||||
defaults={"with_query": True},
|
||||
)
|
||||
@login_required
|
||||
@scodoc
|
||||
@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."""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
if etat is None:
|
||||
inscriptions = formsemestre.inscriptions
|
||||
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
|
||||
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]
|
||||
# Ajout des groupes de chaque étudiants
|
||||
# XXX A REVOIR: trop inefficace !
|
||||
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)
|
||||
|
||||
|
@ -15,6 +15,7 @@ import app
|
||||
from app import db, log
|
||||
from app.api import api_bp as bp, api_web_bp
|
||||
from app.decorators import scodoc, permission_required
|
||||
from app.scodoc.sco_exceptions import ScoException
|
||||
from app.scodoc.sco_utils import json_error
|
||||
from app.but import jury_but_recap
|
||||
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."""
|
||||
# APC, pair:
|
||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||
app.set_sco_dept(formsemestre.departement.acronym)
|
||||
rows = jury_but_recap.get_jury_but_results(formsemestre)
|
||||
return jsonify(rows)
|
||||
if formsemestre.formation.is_apc():
|
||||
app.set_sco_dept(formsemestre.departement.acronym)
|
||||
rows = jury_but_recap.get_jury_but_results(formsemestre)
|
||||
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):
|
||||
"""Etudiants du groupe, filtrés par état"""
|
||||
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")
|
||||
query = GroupDescr.query.filter_by(id=group_id)
|
||||
if g.scodoc_dept:
|
||||
query = (
|
||||
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
|
||||
query = (
|
||||
Identite.query.join(FormSemestreInscription)
|
||||
.filter_by(formsemestre_id=group.partition.formsemestre_id, etat=etat)
|
||||
.join(group_membership)
|
||||
.filter_by(group_id=group_id)
|
||||
group = query.first_or_404() # just to ckeck that group exists in accessible dept
|
||||
|
||||
query = Identite.query.join(FormSemestreInscription).filter_by(
|
||||
formsemestre_id=group.partition.formsemestre_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])
|
||||
|
||||
|
@ -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)
|
||||
|
||||
if etudid is not None:
|
||||
etud: Identite = Identite.query.get(etudid)
|
||||
if (None in allowed_depts) or etud.departement.acronym in allowed_depts:
|
||||
return etud
|
||||
return None # accès interdit => pas d'étudiant
|
||||
|
||||
if nip is not None:
|
||||
query: Identite = 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)
|
||||
@ -45,7 +41,7 @@ def get_etud(etudid=None, nip=None, ine=None) -> models.Identite:
|
||||
)
|
||||
if None not in allowed_depts:
|
||||
# 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)
|
||||
)
|
||||
return query.join(Admission).order_by(desc(Admission.annee)).first()
|
||||
|
@ -124,8 +124,8 @@ def user_create():
|
||||
return jsonify(user.to_dict())
|
||||
|
||||
|
||||
@bp.route("/user/edit/<int:uid>", methods=["POST"])
|
||||
@api_web_bp.route("/user/edit/<int:uid>", methods=["POST"])
|
||||
@bp.route("/user/<int:uid>/edit", methods=["POST"])
|
||||
@api_web_bp.route("/user/<int:uid>/edit", methods=["POST"])
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoUsersAdmin)
|
||||
@ -306,8 +306,8 @@ def role_permission_remove(role_name: str, perm_name: str):
|
||||
return jsonify(role.to_dict())
|
||||
|
||||
|
||||
@bp.route("/role/<string:role_name>/create", methods=["POST"])
|
||||
@api_web_bp.route("/role/<string:role_name>/create", methods=["POST"])
|
||||
@bp.route("/role/create/<string:role_name>", methods=["POST"])
|
||||
@api_web_bp.route("/role/create/<string:role_name>", methods=["POST"])
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
|
@ -63,13 +63,13 @@ from operator import attrgetter
|
||||
import re
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from flask import g, url_for
|
||||
|
||||
from app import db
|
||||
from app import log
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.comp import inscr_mod, res_sem
|
||||
from app.models import formsemestre
|
||||
from app.comp import res_sem
|
||||
|
||||
from app.models.but_refcomp import (
|
||||
ApcAnneeParcours,
|
||||
@ -917,7 +917,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
||||
self.codes = [
|
||||
sco_codes.DEM if inscription_etat == scu.DEMISSION else sco_codes.DEF
|
||||
]
|
||||
self.moy_ue = "-"
|
||||
self.moy_ue = np.NaN
|
||||
return
|
||||
|
||||
# Moyenne de l'UE ?
|
||||
|
@ -448,6 +448,9 @@ def get_jury_but_table(
|
||||
|
||||
def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]:
|
||||
"""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)
|
||||
rows = []
|
||||
for etudid in formsemestre.etuds_inscriptions:
|
||||
@ -484,12 +487,16 @@ def get_jury_but_etud_result(
|
||||
rcue_dict = {
|
||||
"ue_1": {
|
||||
"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,
|
||||
},
|
||||
"ue_2": {
|
||||
"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,
|
||||
},
|
||||
"moy": rcue.moy_rcue,
|
||||
|
@ -221,10 +221,7 @@ def compute_ue_moys_apc(
|
||||
modimpl_mask: np.array,
|
||||
) -> pd.DataFrame:
|
||||
"""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
|
||||
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).
|
||||
La moyenne d'UE est un nombre (note/20), ou NaN si pas de notes disponibles
|
||||
|
||||
sem_cube: notes moyennes aux modules
|
||||
ndarray (etuds x modimpls x UEs)
|
||||
|
@ -50,6 +50,8 @@ main {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 32px;
|
||||
row-gap: 4px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
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 {
|
||||
background: #ddd;
|
||||
padding: 8px;
|
||||
@ -226,6 +236,9 @@ body:not(.editionActivated) .filtres>div>div>div>div:active {
|
||||
background: rgba(0, 153, 204, 0.5);
|
||||
}
|
||||
|
||||
/*****************************/
|
||||
/* Zone Etudiants */
|
||||
/*****************************/
|
||||
#zoneChoix .etudiants>div {
|
||||
background: #FFF;
|
||||
border: 1px solid #aaa;
|
||||
|
@ -1,33 +1,37 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
<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>
|
||||
<div class="wait"></div>
|
||||
|
||||
<section id="zoneChoix">
|
||||
<h2>Choix</h2>
|
||||
<div class="filtres">
|
||||
<div class="partitions">
|
||||
<h3>Afficher les partitions</h3>
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="masques">
|
||||
<h3>
|
||||
Afficher les étudiants affectés aux groupes<br>
|
||||
<small>Ne s'actualise pas automatiquement lors d'une modification</small>
|
||||
</h3>
|
||||
<div></div>
|
||||
<section id="zonePartitions">
|
||||
<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="partitions">
|
||||
<h3>Afficher les partitions</h3>
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="masques">
|
||||
<h3>
|
||||
Afficher les étudiants affectés aux groupes<br>
|
||||
<small>Ne s'actualise pas automatiquement lors d'une modification</small>
|
||||
</h3>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="zoneChoix">
|
||||
<h2>Etudiants</h2>
|
||||
<div class="etudiants"></div>
|
||||
</section>
|
||||
|
||||
<section id="zoneGroupes">
|
||||
<h2>Groupes</h2>
|
||||
<div class="groupes"></div>
|
||||
@ -78,7 +82,12 @@
|
||||
|
||||
arrayPartitions.forEach((partition) => {
|
||||
// Filtres
|
||||
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>`;
|
||||
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>`;
|
||||
} 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>`;
|
||||
|
||||
// Groupes
|
||||
@ -96,9 +105,13 @@
|
||||
let output = "";
|
||||
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;
|
||||
})()}
|
||||
@ -144,7 +157,7 @@
|
||||
if (!affected) {
|
||||
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>`;
|
||||
})
|
||||
@ -171,9 +184,6 @@
|
||||
/******************************/
|
||||
function input() {
|
||||
document.querySelector("body").classList.toggle("editionActivated");
|
||||
/*if (event.currentTarget.checked == false) {
|
||||
go();
|
||||
}*/
|
||||
}
|
||||
function processEvents() {
|
||||
/*--------------------*/
|
||||
@ -193,7 +203,7 @@
|
||||
/*--------------------*/
|
||||
/* 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) })
|
||||
}
|
||||
|
||||
@ -223,6 +233,7 @@
|
||||
}
|
||||
|
||||
if (!this.dataset.idgroupe) {
|
||||
// Partitions
|
||||
let groupesSelected = [];
|
||||
this.parentElement.querySelectorAll(":not(.unselect)").forEach(e => {
|
||||
groupesSelected.push(e.dataset.idpartition);
|
||||
@ -238,6 +249,7 @@
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Groupes
|
||||
let groupesSelected = {};
|
||||
|
||||
this.parentElement.parentElement.querySelectorAll("[data-idgroupe]:not(.unselect)").forEach(e => {
|
||||
@ -350,6 +362,8 @@
|
||||
div.querySelector(".move").addEventListener("mousedown", moveStart);
|
||||
this.parentElement.insertBefore(div, this);
|
||||
|
||||
div.querySelector(".modif").click();
|
||||
|
||||
// Save
|
||||
fetch(url,
|
||||
{
|
||||
@ -380,12 +394,12 @@
|
||||
div.querySelector("div").addEventListener("click", filtre);
|
||||
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
|
||||
let outputGroupes = "";
|
||||
|
||||
document.querySelectorAll(`#zoneChoix .grpPartitions`).forEach(e => {
|
||||
document.querySelectorAll(`#zonePartitions .grpPartitions`).forEach(e => {
|
||||
let etudid = e.previousElementSibling.dataset.etudid;
|
||||
|
||||
// Préparation pour la section suivante
|
||||
@ -447,10 +461,17 @@
|
||||
/* Edition du texte */
|
||||
/********************/
|
||||
function editText() {
|
||||
this.previousElementSibling.classList.add("editingText");
|
||||
this.previousElementSibling.setAttribute("contenteditable", "true");
|
||||
this.previousElementSibling.focus();
|
||||
this.previousElementSibling.addEventListener("keydown", writing);
|
||||
let e = this.previousElementSibling;
|
||||
e.classList.add("editingText");
|
||||
e.setAttribute("contenteditable", "true");
|
||||
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) {
|
||||
@ -613,12 +634,12 @@
|
||||
let formsemestre_id = params.get('formsemestre_id');
|
||||
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 => {
|
||||
parent.append(parent.querySelector(`[data-idpartition="${position}"]`))
|
||||
})
|
||||
})
|
||||
document.querySelectorAll(`#zoneChoix .grpPartitions`).forEach(parent => {
|
||||
document.querySelectorAll(`#zonePartitions .grpPartitions`).forEach(parent => {
|
||||
positions.forEach(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.absences import BilletAbsence
|
||||
from app.models.etudiants import Identite
|
||||
from app.views import absences_bp as bp
|
||||
|
||||
# ---------------
|
||||
@ -1099,7 +1100,7 @@ def AddBilletAbsence(
|
||||
begin,
|
||||
end,
|
||||
description,
|
||||
etudid=False,
|
||||
etudid=None,
|
||||
code_nip=None,
|
||||
code_ine=None,
|
||||
justified=True,
|
||||
@ -1114,7 +1115,7 @@ def AddBilletAbsence(
|
||||
end = str(end)
|
||||
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
|
||||
begin_date = dateutil.parser.isoparse(begin) # may raises ValueError
|
||||
end_date = dateutil.parser.isoparse(end)
|
||||
@ -1212,9 +1213,12 @@ def billets_etud(etudid=False):
|
||||
@scodoc
|
||||
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def XMLgetBilletsEtud(etudid=False):
|
||||
def XMLgetBilletsEtud(etudid=False, code_nip=False):
|
||||
"""Liste billets pour un etudiant"""
|
||||
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)
|
||||
if table:
|
||||
return table.make_page(format="xml")
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.3.25"
|
||||
SCOVERSION = "9.3.26"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
@ -10,6 +10,8 @@ SCONEWS = """
|
||||
<ul>
|
||||
<li>ScoDoc 9.3</li>
|
||||
<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>Association des UEs aux compétences du référentiel</li>
|
||||
<li>Jury BUT1</li>
|
||||
@ -21,7 +23,6 @@ SCONEWS = """
|
||||
<ul>
|
||||
<li>Tableau récap. complet pour BUT et autres formations.</li>
|
||||
<li>Tableau état évaluations</li>
|
||||
<li>Version alpha du module "relations entreprises"</li>
|
||||
<li>Export des trombinoscope en document docx</li>
|
||||
<li>Très nombreux correctifs</li>
|
||||
</ul>
|
||||
|
@ -4,143 +4,97 @@
|
||||
|
||||
"""Exemple utilisation API ScoDoc 9 avec jeton obtenu par basic authentication
|
||||
|
||||
|
||||
Utilisation: créer les variables d'environnement: (indiquer les valeurs
|
||||
pour le serveur ScoDoc que vous voulez interroger)
|
||||
|
||||
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).
|
||||
Usage:
|
||||
cd /opt/scodoc/tests/api
|
||||
python -i exemple-api-basic.py
|
||||
|
||||
|
||||
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
|
||||
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"))
|
||||
CHK_CERT = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
|
||||
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"]
|
||||
if not CHECK_CERTIFICATE:
|
||||
urllib3.disable_warnings()
|
||||
|
||||
print(f"SCODOC_URL={SCODOC_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):
|
||||
pass
|
||||
|
||||
|
||||
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())
|
||||
departements = GET("/departements", headers=HEADERS)
|
||||
pp(departements)
|
||||
|
||||
# Liste de tous les étudiants en cours (de tous les depts)
|
||||
r = requests.get(API_URL + "/etudiants/courant", headers=HEADERS, verify=CHK_CERT)
|
||||
if r.status_code != 200:
|
||||
raise ScoError("erreur de connexion: vérifier adresse et identifiants")
|
||||
etuds = GET("/etudiants/courants", headers=HEADERS)
|
||||
|
||||
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
|
||||
formsemestre_id = 1063 # A adapter
|
||||
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
|
||||
formsemestre_id = 1062 # A adapter
|
||||
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
|
||||
etudid = 3561
|
||||
code_nip = "11303314"
|
||||
etud = GET(f"/etudiant/etudid/{etudid}")
|
||||
etud = GET(f"/etudiant/etudid/{etudid}", headers=HEADERS)
|
||||
print(etud)
|
||||
|
||||
etud = GET(f"/etudiant/nip/{code_nip}")
|
||||
etud = GET(f"/etudiant/nip/{code_nip}", headers=HEADERS)
|
||||
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]))
|
||||
|
||||
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]))
|
||||
|
||||
# Evaluation
|
||||
@ -148,35 +102,72 @@ evals = GET("/evaluations/1")
|
||||
|
||||
# Partitions d'un BUT
|
||||
formsemestre_id = 1063 # A adapter
|
||||
partitions = GET(f"/formsemestre/{formsemestre_id}/partitions")
|
||||
partitions = GET(f"/formsemestre/{formsemestre_id}/partitions", headers=HEADERS)
|
||||
print(partitions)
|
||||
pid = partitions[1]["id"]
|
||||
partition = GET(f"/partition/{pid}")
|
||||
partition = GET(f"/partition/{pid}", headers=HEADERS)
|
||||
print(partition)
|
||||
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")
|
||||
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")
|
||||
|
||||
etudid = 16650
|
||||
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"})
|
||||
partitions = GET(f"/formsemestre/{formsemestre_id}/partitions")
|
||||
POST_JSON(
|
||||
f"/partition/{pid}/group/create", data={"group_name": "Omega10"}, headers=HEADERS
|
||||
)
|
||||
partitions = GET(f"/formsemestre/{formsemestre_id}/partitions", headers=HEADERS)
|
||||
pp(partitions)
|
||||
|
||||
POST_JSON(f"/group/5559/delete")
|
||||
POST_JSON(f"/group/5327/edit", data={"group_name": "TDXXX"})
|
||||
POST_JSON(f"/group/5559/delete", headers=HEADERS)
|
||||
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
|
||||
etud = GET(f"/formsemestre/{formsemestre_id}/etudiants")[10]
|
||||
etud = GET(f"/formsemestre/{formsemestre_id}/etudiants", headers=HEADERS)[10]
|
||||
etudid = etud["id"]
|
||||
|
||||
# 1- Crée une partition, puis la change de nom
|
||||
@ -188,30 +179,43 @@ partition_id = js["id"]
|
||||
POST_JSON(
|
||||
f"/partition/{partition_id}/edit",
|
||||
data={"partition_name": "PART1", "show_in_lists": True},
|
||||
headers=HEADERS,
|
||||
)
|
||||
|
||||
# 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"]
|
||||
|
||||
# 3- Crée deux autres groupes
|
||||
js = POST_JSON(f"/partition/{partition_id}/group/create", data={"group_name": "G2"})
|
||||
js = POST_JSON(f"/partition/{partition_id}/group/create", data={"group_name": "G3"})
|
||||
js = POST_JSON(
|
||||
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
|
||||
POST_JSON(f"/group/{group_1}/set_etudiant/{etudid}")
|
||||
POST_JSON(f"/group/{group_1}/set_etudiant/{etudid}", headers=HEADERS)
|
||||
|
||||
# 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
|
||||
partition = GET(f"/partition/{partition_id}")
|
||||
assert len(partition["groups"]) == 3
|
||||
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
|
||||
etuds_g2 = GET(f"/group/{group_2}/etudiants")
|
||||
etuds_g2 = GET(f"/group/{group_2}/etudiants", headers=HEADERS)
|
||||
assert len(etuds_g2) == 1
|
||||
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(
|
||||
f"/partition/{partition_id}/groups/order",
|
||||
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]
|
||||
|
||||
# 9- Suppression
|
||||
@ -248,23 +256,25 @@ POST_JSON(f"/partition/{partition_id}/delete")
|
||||
POST_JSON(
|
||||
"/partition/2264/groups/order",
|
||||
data=[5563, 5562, 5561, 5560, 5558, 5557, 5316, 5315],
|
||||
headers=HEADERS,
|
||||
)
|
||||
|
||||
POST_JSON(
|
||||
"/formsemestre/1063/partitions/order",
|
||||
data=[2264, 2263, 2265, 2266, 2267, 2372, 2378],
|
||||
headers=HEADERS,
|
||||
)
|
||||
|
||||
|
||||
GET(f"/partition/2264")
|
||||
GET(f"/partition/2264", headers=HEADERS)
|
||||
|
||||
# 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:
|
||||
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)
|
||||
# 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
|
||||
import pytest
|
||||
|
||||
BASEDIR = "/opt/scodoc/tests/api"
|
||||
# --- Lecture configuration (variables d'env ou .env)
|
||||
try:
|
||||
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
||||
except NameError:
|
||||
BASEDIR = "/opt/scodoc/tests/api"
|
||||
|
||||
load_dotenv(os.path.join(BASEDIR, ".env"))
|
||||
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_USER = os.environ.get("API_USER", "test")
|
||||
API_PASSWORD = os.environ.get("API_PASSWD", "test")
|
||||
@ -33,7 +38,9 @@ print(f"API URL={API_URL}")
|
||||
|
||||
|
||||
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:
|
||||
@ -65,7 +72,7 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None):
|
||||
url = API_URL + path
|
||||
r = requests.get(url, headers=headers or {}, verify=CHECK_CERTIFICATE)
|
||||
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
|
||||
|
||||
|
||||
@ -78,5 +85,5 @@ def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None):
|
||||
verify=CHECK_CERTIFICATE,
|
||||
)
|
||||
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
|
||||
|
@ -515,22 +515,28 @@ def test_formsemestre_etudiants(api_headers):
|
||||
assert isinstance(group["group_id"], 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
|
||||
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)
|
||||
|
||||
### démissionnaires
|
||||
etuds_dem = GET(
|
||||
f"/formsemestre/{formsemestre_id}/etudiants/demissionnaires",
|
||||
f"/formsemestre/{formsemestre_id}/etudiants/query?etat=D",
|
||||
headers=api_headers,
|
||||
)
|
||||
assert isinstance(etuds_dem, list)
|
||||
|
||||
### défaillants
|
||||
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)
|
||||
|
||||
|
@ -134,10 +134,10 @@ def test_etud_in_group(api_headers):
|
||||
- /group/<int:group_id>/etudiants/query?etat=<string:etat>
|
||||
"""
|
||||
group_id = 1
|
||||
etudiants = GET(f"/group/{group_id}/etudiants", headers=api_headers)
|
||||
assert isinstance(etudiants, list)
|
||||
etuds = GET(f"/group/{group_id}/etudiants", headers=api_headers)
|
||||
assert isinstance(etuds, list)
|
||||
|
||||
for etud in etudiants:
|
||||
for etud in etuds:
|
||||
assert verify_fields(etud, PARTITION_GROUPS_ETUD_FIELDS)
|
||||
assert isinstance(etud["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_ine"], str)
|
||||
|
||||
# query sans filtre:
|
||||
etuds_query = GET(f"/group/{group_id}/etudiants/query", headers=api_headers)
|
||||
assert etuds_query == etuds
|
||||
|
||||
etat = "I"
|
||||
etudiants = GET(
|
||||
f"/group/{group_id}/etudiants/query?etat={etat}", headers=api_headers
|
||||
)
|
||||
assert isinstance(etudiants, list)
|
||||
for etud in etudiants:
|
||||
etuds = GET(f"/group/{group_id}/etudiants/query?etat={etat}", headers=api_headers)
|
||||
assert isinstance(etuds, list)
|
||||
for etud in etuds:
|
||||
assert verify_fields(etud, PARTITION_GROUPS_ETUD_FIELDS)
|
||||
assert isinstance(etud["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):
|
||||
"""
|
||||
Routes: /user/create
|
||||
/user/edit/<int:uid>
|
||||
/user/<int:uid>/edit
|
||||
"""
|
||||
admin_h = api_admin_headers
|
||||
user = POST_JSON(
|
||||
@ -105,7 +105,7 @@ def test_roles(api_admin_headers):
|
||||
uid = user["id"]
|
||||
ans = POST_JSON(f"/user/{uid}/role/Secr/add", headers=admin_h)
|
||||
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["permissions"] == []
|
||||
role = GET("/role/Test_X", headers=admin_h)
|
||||
|
Loading…
Reference in New Issue
Block a user