API: modification format evaluations, et ajout route /evaluation.
This commit is contained in:
parent
c85a51a8c5
commit
155a093635
63
README.md
63
README.md
@ -1,5 +1,4 @@
|
||||
i
|
||||
# ScoDoc - Gestion de la scolarité - Version ScoDoc 9
|
||||
# ScoDoc - Gestion de la scolarité - Version ScoDoc 9
|
||||
|
||||
(c) Emmanuel Viennet 1999 - 2022 (voir LICENCE.txt).
|
||||
|
||||
@ -9,39 +8,34 @@ Documentation utilisateur: <https://scodoc.org>
|
||||
|
||||
## Version ScoDoc 9
|
||||
|
||||
La version ScoDoc 9 est parue en septembre 2021.
|
||||
Elle représente une évolution majeure du projet, maintenant basé sur
|
||||
Flask (au lieu de Zope) et sur **python 3.9+**.
|
||||
La version ScoDoc 9 est parue en septembre 2021. Elle représente une évolution
|
||||
majeure du projet, maintenant basé sur Flask (au lieu de Zope) et sur **python
|
||||
3.9+**.
|
||||
|
||||
La version 9.0 s'efforce de reproduire presque à l'identique le fonctionnement
|
||||
La version 9.0 s'efforce de reproduire presque à l'identique le fonctionnement
|
||||
de ScoDoc7, avec des composants logiciels différents (Debian 11, Python 3,
|
||||
Flask, SQLAlchemy, au lien de Python2/Zope dans les versions précédentes).
|
||||
|
||||
### État actuel (nov 22)
|
||||
|
||||
- 9.3.x est en production
|
||||
- le prochain jalon est 9.4. Voir branches sur gitea.
|
||||
|
||||
### État actuel (26 jan 22)
|
||||
|
||||
- 9.1.5x (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
|
||||
- ancien module "Entreprises" (obsolète) et ajoute la gestion du BUT.
|
||||
|
||||
- 9.2 (branche dev92) est la version de développement.
|
||||
|
||||
|
||||
### Lignes de commandes
|
||||
|
||||
Voir [https://scodoc.org/GuideConfig](le guide de configuration).
|
||||
|
||||
|
||||
## Organisation des fichiers
|
||||
|
||||
L'installation comporte les fichiers de l'application, sous `/opt/scodoc/`, et
|
||||
les fichiers locaux (archives, photos, configurations, logs) sous
|
||||
`/opt/scodoc-data`. Par ailleurs, il y a évidemment les bases de données
|
||||
postgresql et la configuration du système Linux.
|
||||
postgresql et la configuration du système Linux.
|
||||
|
||||
### Fichiers locaux
|
||||
Sous `/opt/scodoc-data`, fichiers et répertoires appartenant à l'utilisateur `scodoc`.
|
||||
Ils ne doivent pas être modifiés à la main, sauf certains fichiers de configuration sous
|
||||
|
||||
Sous `/opt/scodoc-data`, fichiers et répertoires appartenant à l'utilisateur `scodoc`.
|
||||
Ils ne doivent pas être modifiés à la main, sauf certains fichiers de configuration sous
|
||||
`/opt/scodoc-data/config`.
|
||||
|
||||
Le répertoire `/opt/scodoc-data` doit être régulièrement sauvegardé.
|
||||
@ -62,7 +56,7 @@ Principaux contenus:
|
||||
|
||||
Installer ScoDoc 9 normalement ([voir la doc](https://scodoc.org/GuideInstallDebian11)).
|
||||
|
||||
Puis remplacer `/opt/scodoc` par un clone du git.
|
||||
Puis remplacer `/opt/scodoc` par un clone du git.
|
||||
|
||||
sudo su
|
||||
mv /opt/scodoc /opt/off-scodoc # ou ce que vous voulez
|
||||
@ -76,7 +70,7 @@ Puis remplacer `/opt/scodoc` par un clone du git.
|
||||
|
||||
# Et donner ce répertoire à l'utilisateur scodoc:
|
||||
chown -R scodoc.scodoc /opt/scodoc
|
||||
|
||||
|
||||
Il faut ensuite installer l'environnement et le fichier de configuration:
|
||||
|
||||
# Le plus simple est de piquer le virtualenv configuré par l'installeur:
|
||||
@ -100,10 +94,10 @@ Avant le premier lancement, créer cette base ainsi:
|
||||
flask db upgrade
|
||||
|
||||
Cette commande n'est nécessaire que la première fois (le contenu de la base
|
||||
est effacé au début de chaque test, mais son schéma reste) et aussi si des
|
||||
est effacé au début de chaque test, mais son schéma reste) et aussi si des
|
||||
migrations (changements de schéma) ont eu lieu dans le code.
|
||||
|
||||
Certains tests ont besoin d'un département déjà créé, qui n'est pas créé par les
|
||||
Certains tests ont besoin d'un département déjà créé, qui n'est pas créé par les
|
||||
scripts de tests:
|
||||
Lancer au préalable:
|
||||
|
||||
@ -117,24 +111,24 @@ Ou avec couverture (`pip install pytest-cov`)
|
||||
|
||||
pytest --cov=app --cov-report=term-missing --cov-branch tests/unit/*
|
||||
|
||||
|
||||
#### Utilisation des tests unitaires pour initialiser la base de dev
|
||||
On peut aussi utiliser les tests unitaires pour mettre la base
|
||||
de données de développement dans un état connu, par exemple pour éviter de
|
||||
recréer à la main étudiants et semestres quand on développe.
|
||||
|
||||
Il suffit de positionner une variable d'environnement indiquant la BD
|
||||
utilisée par les tests:
|
||||
On peut aussi utiliser les tests unitaires pour mettre la base de données de
|
||||
développement dans un état connu, par exemple pour éviter de recréer à la main
|
||||
étudiants et semestres quand on développe.
|
||||
|
||||
Il suffit de positionner une variable d'environnement indiquant la BD utilisée
|
||||
par les tests:
|
||||
|
||||
export SCODOC_TEST_DATABASE_URI=postgresql:///SCODOC_DEV
|
||||
|
||||
(si elle n'existe pas, voir plus loin pour la créer) puis de les lancer
|
||||
normalement, par exemple:
|
||||
normalement, par exemple:
|
||||
|
||||
pytest tests/unit/test_sco_basic.py
|
||||
|
||||
Il est en général nécessaire d'affecter ensuite un mot de passe à (au moins)
|
||||
un utilisateur:
|
||||
Il est en général nécessaire d'affecter ensuite un mot de passe à (au moins) un
|
||||
utilisateur:
|
||||
|
||||
flask user-password admin
|
||||
|
||||
@ -178,12 +172,10 @@ Pour la visualisation, [snakeviz](https://jiffyclub.github.io/snakeviz/) est bie
|
||||
|
||||
pip install snakeviz
|
||||
|
||||
puis
|
||||
puis
|
||||
|
||||
snakeviz -s --hostname 0.0.0.0 -p 5555 /opt/scodoc-data/GET.ScoDoc......prof
|
||||
|
||||
|
||||
|
||||
# Paquet Debian 11
|
||||
|
||||
Les scripts associés au paquet Debian (.deb) sont dans `tools/debian`. Le plus
|
||||
@ -191,5 +183,4 @@ important est `postinst`qui se charge de configurer le système (install ou
|
||||
upgrade de scodoc9).
|
||||
|
||||
La préparation d'une release se fait à l'aide du script
|
||||
`tools/build_release.sh`.
|
||||
|
||||
`tools/build_release.sh`.
|
||||
|
@ -257,9 +257,9 @@ def dept_formsemestres_courants(acronym: str):
|
||||
]
|
||||
"""
|
||||
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||
faked_date = request.args.get("faked_date")
|
||||
if faked_date:
|
||||
test_date = datetime.fromisoformat(faked_date)
|
||||
date_courante = request.args.get("date_courante")
|
||||
if date_courante:
|
||||
test_date = datetime.fromisoformat(date_courante)
|
||||
else:
|
||||
test_date = app.db.func.now()
|
||||
# Les semestres en cours de ce département
|
||||
@ -281,9 +281,9 @@ def dept_formsemestres_courants_by_id(dept_id: int):
|
||||
"""
|
||||
# Le département, spécifié par un id ou un acronyme
|
||||
dept = Departement.query.get_or_404(dept_id)
|
||||
faked_date = request.args.get("faked_date")
|
||||
if faked_date:
|
||||
test_date = datetime.fromisoformat(faked_date)
|
||||
date_courante = request.args.get("date_courante")
|
||||
if date_courante:
|
||||
test_date = datetime.fromisoformat(date_courante)
|
||||
else:
|
||||
test_date = app.db.func.now()
|
||||
# Les semestres en cours de ce département
|
||||
|
@ -76,9 +76,9 @@ def etudiants_courants(long=False):
|
||||
|
||||
"""
|
||||
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
||||
faked_date = request.args.get("faked_date")
|
||||
if faked_date:
|
||||
test_date = datetime.fromisoformat(faked_date)
|
||||
date_courante = request.args.get("date_courante")
|
||||
if date_courante:
|
||||
test_date = datetime.fromisoformat(date_courante)
|
||||
else:
|
||||
test_date = app.db.func.now()
|
||||
etuds = Identite.query.filter(
|
||||
|
@ -22,6 +22,44 @@ from app.scodoc.sco_permissions import Permission
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
|
||||
@bp.route("/evaluation/<int:evaluation_id>")
|
||||
@api_web_bp.route("/evaluation/<int:evaluation_id>")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def evaluation(evaluation_id: int):
|
||||
"""Description d'une évaluation.
|
||||
|
||||
{
|
||||
'coefficient': 1.0,
|
||||
'date_debut': '2016-01-04T08:30:00',
|
||||
'date_fin': '2016-01-04T12:30:00',
|
||||
'description': 'TP NI9219 Température',
|
||||
'evaluation_type': 0,
|
||||
'id': 15797,
|
||||
'moduleimpl_id': 1234,
|
||||
'note_max': 20.0,
|
||||
'numero': 3,
|
||||
'poids': {
|
||||
'UE1.1': 1.0,
|
||||
'UE1.2': 1.0,
|
||||
'UE1.3': 1.0
|
||||
},
|
||||
'publish_incomplete': False,
|
||||
'visi_bulletin': True
|
||||
}
|
||||
"""
|
||||
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||
if g.scodoc_dept:
|
||||
query = (
|
||||
query.join(ModuleImpl)
|
||||
.join(FormSemestre)
|
||||
.filter_by(dept_id=g.scodoc_dept_id)
|
||||
)
|
||||
e = query.first_or_404()
|
||||
return jsonify(e.to_dict_api())
|
||||
|
||||
|
||||
@bp.route("/moduleimpl/<int:moduleimpl_id>/evaluations")
|
||||
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>/evaluations")
|
||||
@login_required
|
||||
@ -33,39 +71,16 @@ def evaluations(moduleimpl_id: int):
|
||||
|
||||
moduleimpl_id : l'id d'un moduleimpl
|
||||
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
"moduleimpl_id": 1,
|
||||
"jour": "20/04/2022",
|
||||
"heure_debut": "08h00",
|
||||
"description": "eval1",
|
||||
"coefficient": 1.0,
|
||||
"publish_incomplete": false,
|
||||
"numero": 0,
|
||||
"id": 1,
|
||||
"heure_fin": "09h00",
|
||||
"note_max": 20.0,
|
||||
"visibulletin": true,
|
||||
"evaluation_type": 0,
|
||||
"evaluation_id": 1,
|
||||
"jouriso": "2022-04-20",
|
||||
"duree": "1h",
|
||||
"descrheure": " de 08h00 à 09h00",
|
||||
"matin": 1,
|
||||
"apresmidi": 0
|
||||
},
|
||||
...
|
||||
]
|
||||
Exemple de résultat : voir /evaluation
|
||||
"""
|
||||
query = Evaluation.query.filter_by(id=moduleimpl_id)
|
||||
query = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id)
|
||||
if g.scodoc_dept:
|
||||
query = (
|
||||
query.join(ModuleImpl)
|
||||
.join(FormSemestre)
|
||||
.filter_by(dept_id=g.scodoc_dept_id)
|
||||
)
|
||||
return jsonify([d.to_dict() for d in query])
|
||||
return jsonify([e.to_dict_api() for e in query])
|
||||
|
||||
|
||||
@bp.route("/evaluation/<int:evaluation_id>/notes")
|
||||
|
@ -398,7 +398,7 @@ def etat_evals(formsemestre_id: int):
|
||||
for evaluation_id in modimpl_results.evaluations_etat:
|
||||
eval_etat = modimpl_results.evaluations_etat[evaluation_id]
|
||||
evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
eval_dict = evaluation.to_dict()
|
||||
eval_dict = evaluation.to_dict_api()
|
||||
eval_dict["etat"] = eval_etat.to_dict()
|
||||
|
||||
eval_dict["nb_inscrits"] = modimpl_results.nb_inscrits_module
|
||||
|
@ -51,7 +51,7 @@ class Evaluation(db.Model):
|
||||
self.description[:16] if self.description else ''}">"""
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"Représentation dict, pour json"
|
||||
"Représentation dict (riche, compat ScoDoc 7)"
|
||||
e = dict(self.__dict__)
|
||||
e.pop("_sa_instance_state", None)
|
||||
# ScoDoc7 output_formators
|
||||
@ -71,6 +71,34 @@ class Evaluation(db.Model):
|
||||
e["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
|
||||
return evaluation_enrich_dict(e)
|
||||
|
||||
def to_dict_api(self) -> dict:
|
||||
"Représentation dict pour API JSON"
|
||||
if self.jour is None:
|
||||
date_debut = None
|
||||
date_fin = None
|
||||
else:
|
||||
date_debut = datetime.datetime.combine(
|
||||
self.jour, self.heure_debut or datetime.time(0, 0)
|
||||
).isoformat()
|
||||
date_fin = datetime.datetime.combine(
|
||||
self.jour, self.heure_fin or datetime.time(0, 0)
|
||||
).isoformat()
|
||||
|
||||
return {
|
||||
"coefficient": self.coefficient,
|
||||
"date_debut": date_debut,
|
||||
"date_fin": date_fin,
|
||||
"description": self.description,
|
||||
"evaluation_type": self.evaluation_type,
|
||||
"id": self.id,
|
||||
"moduleimpl_id": self.moduleimpl_id,
|
||||
"note_max": self.note_max,
|
||||
"numero": self.numero,
|
||||
"poids": self.get_ue_poids_dict(),
|
||||
"publish_incomplete": self.publish_incomplete,
|
||||
"visi_bulletin": self.visibulletin,
|
||||
}
|
||||
|
||||
def from_dict(self, data):
|
||||
"""Set evaluation attributes from given dict values."""
|
||||
check_evaluation_args(data)
|
||||
@ -227,7 +255,7 @@ def evaluation_enrich_dict(e: dict):
|
||||
heure_fin_dt = e["heure_fin"] or datetime.time(8, 00)
|
||||
e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"])
|
||||
e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"])
|
||||
e["jouriso"] = ndb.DateDMYtoISO(e["jour"])
|
||||
e["jour_iso"] = ndb.DateDMYtoISO(e["jour"])
|
||||
heure_debut, heure_fin = e["heure_debut"], e["heure_fin"]
|
||||
d = ndb.TimeDuration(heure_debut, heure_fin)
|
||||
if d is not None:
|
||||
|
@ -93,7 +93,7 @@ def do_evaluation_list(args, sortkey=None):
|
||||
# Attention: transformation fonction ScoDoc7 en SQLAlchemy
|
||||
cnx = ndb.GetDBConnexion()
|
||||
evals = _evaluationEditor.list(cnx, args, sortkey=sortkey)
|
||||
# calcule duree (chaine de car.) de chaque evaluation et ajoute jouriso, matin, apresmidi
|
||||
# calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi
|
||||
for e in evals:
|
||||
evaluation_enrich_dict(e)
|
||||
|
||||
|
@ -1,18 +1,19 @@
|
||||
# Tests unitaires de l'API ScoDoc
|
||||
|
||||
Démarche générale:
|
||||
Démarche générale:
|
||||
|
||||
1. On génère une base SQL de test: voir
|
||||
`tools/fakedatabase/create_test_api_database.py`
|
||||
|
||||
|
||||
1. modifier /opt/scodoc/.env pour indiquer
|
||||
1. modifier /opt/scodoc/.env pour indiquer
|
||||
|
||||
```
|
||||
FLASK_ENV=test_api
|
||||
FLASK_DEBUG=1
|
||||
```
|
||||
|
||||
2. En tant qu'utilisateur scodoc, lancer:
|
||||
|
||||
```
|
||||
tools/create_database.sh --drop SCODOC_TEST_API
|
||||
flask db upgrade
|
||||
@ -25,17 +26,20 @@ Démarche générale:
|
||||
```
|
||||
|
||||
2. On lance le serveur ScoDoc sur cette base
|
||||
|
||||
```
|
||||
flask run --host 0.0.0.0
|
||||
```
|
||||
|
||||
3. On lance les tests unitaires API
|
||||
|
||||
```
|
||||
pytest tests/api/test_api_departements.py
|
||||
```
|
||||
|
||||
Rappel: pour interroger l'API, il fait avoir un utilisateur avec (au moins) la permission
|
||||
ScoView dans tous les départements. Pour en créer un:
|
||||
|
||||
```
|
||||
flask user-create lecteur_api LecteurAPI @all
|
||||
flask user-password lecteur_api
|
||||
|
@ -115,7 +115,7 @@ class Sample:
|
||||
pp(self.result, indent=4)
|
||||
|
||||
def dump(self, file):
|
||||
self.url = self.url.replace("?faked_date=2022-07-20", "")
|
||||
self.url = self.url.replace("?date_courante=2022-07-20", "")
|
||||
|
||||
file.write(f"#### {self.method} {self.url}\n")
|
||||
if len(self.content) > 0:
|
||||
|
@ -46,26 +46,17 @@ def test_evaluations(api_headers):
|
||||
for eval in list_eval:
|
||||
assert verify_fields(eval, EVALUATIONS_FIELDS) is True
|
||||
assert isinstance(eval["id"], int)
|
||||
assert isinstance(eval["jour"], str)
|
||||
assert isinstance(eval["heure_fin"], str)
|
||||
assert isinstance(eval["note_max"], float)
|
||||
assert isinstance(eval["visibulletin"], bool)
|
||||
assert isinstance(eval["visi_bulletin"], bool)
|
||||
assert isinstance(eval["evaluation_type"], int)
|
||||
assert isinstance(eval["moduleimpl_id"], int)
|
||||
assert isinstance(eval["heure_debut"], str)
|
||||
assert eval["description"] is None or isinstance(eval["description"], str)
|
||||
assert isinstance(eval["coefficient"], float)
|
||||
assert isinstance(eval["publish_incomplete"], bool)
|
||||
assert isinstance(eval["numero"], int)
|
||||
assert isinstance(eval["evaluation_id"], int)
|
||||
assert eval["date_debut"] is None or isinstance(eval["date_debut"], str)
|
||||
assert eval["date_fin"] is None or isinstance(eval["date_fin"], str)
|
||||
assert isinstance(eval["poids"], dict)
|
||||
assert eval["jouriso"] is None or isinstance(eval["jouriso"], str)
|
||||
assert isinstance(eval["duree"], str)
|
||||
assert isinstance(eval["descrheure"], str)
|
||||
assert isinstance(eval["matin"], int)
|
||||
assert isinstance(eval["apresmidi"], int)
|
||||
|
||||
assert eval["moduleimpl_id"] == moduleimpl_id
|
||||
|
||||
|
@ -545,27 +545,17 @@ FORMSEMESTRE_ETUS_GROUPS_FIELDS = {
|
||||
}
|
||||
|
||||
EVALUATIONS_FIELDS = {
|
||||
"id",
|
||||
"jour",
|
||||
"heure_fin",
|
||||
"note_max",
|
||||
"visibulletin",
|
||||
"evaluation_type",
|
||||
"moduleimpl_id",
|
||||
"heure_debut",
|
||||
"description",
|
||||
"coefficient",
|
||||
"publish_incomplete",
|
||||
"numero",
|
||||
"evaluation_id",
|
||||
"date_debut",
|
||||
"date_fin",
|
||||
"description",
|
||||
"evaluation_type",
|
||||
"id",
|
||||
"note_max",
|
||||
"numero",
|
||||
"poids",
|
||||
"jouriso",
|
||||
"duree",
|
||||
"descrheure",
|
||||
"matin",
|
||||
"apresmidi",
|
||||
"publish_incomplete",
|
||||
"visi_bulletin",
|
||||
}
|
||||
|
||||
EVALUATION_FIELDS = {
|
||||
|
Loading…
Reference in New Issue
Block a user