diff --git a/README.md b/README.md
index 603ac93dfc573fa974102faa0b599f345acff428..115ef229d80deeda244f79c51bd31fed0e9725fb 100644
--- a/README.md
+++ b/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`.
diff --git a/app/api/departements.py b/app/api/departements.py
index b498cd66772baf40a3a207a5cf489e02389f35f2..066197d3f9d01b715a1d1779dea7c2e1069bfdd3 100644
--- a/app/api/departements.py
+++ b/app/api/departements.py
@@ -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
diff --git a/app/api/etudiants.py b/app/api/etudiants.py
index f8728f444e56882bfe096a9a3790c84a2f5461f9..fcafae68ce5e4ab231982084c3ed984077eac773 100644
--- a/app/api/etudiants.py
+++ b/app/api/etudiants.py
@@ -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(
diff --git a/app/api/evaluations.py b/app/api/evaluations.py
index 5fe479f2b00155cb5cb3206690946fde124db7e6..a8fb2c26ccb88c493e8cba9c32df96a925805002 100644
--- a/app/api/evaluations.py
+++ b/app/api/evaluations.py
@@ -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")
diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py
index e80da766a0333be4e63d679d8dd1cd91188277d5..38794d625e6d704ededf2146e8d307474465823e 100644
--- a/app/api/formsemestres.py
+++ b/app/api/formsemestres.py
@@ -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
diff --git a/app/models/evaluations.py b/app/models/evaluations.py
index 04ed7dce5994a91bffc54c8cafbcd6c1bba045c0..71d54d82ed9b09493282e4db2e3c4f8315911663 100644
--- a/app/models/evaluations.py
+++ b/app/models/evaluations.py
@@ -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:
diff --git a/app/scodoc/sco_evaluation_db.py b/app/scodoc/sco_evaluation_db.py
index 7aec908896c75847dbd4a682c286557cbbc0334d..09873bbe17ee1be226c8d92c31cd459055c71123 100644
--- a/app/scodoc/sco_evaluation_db.py
+++ b/app/scodoc/sco_evaluation_db.py
@@ -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)
diff --git a/tests/api/README.md b/tests/api/README.md
index c8ae788391c55554354a9d93e05454c2b31e7b1d..483da78af947fe9589cc27426e52ebf63155f0d8 100644
--- a/tests/api/README.md
+++ b/tests/api/README.md
@@ -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
diff --git a/tests/api/make_samples.py b/tests/api/make_samples.py
index 57a7b477bcf382a40763056cdd158424abbf2f80..576f12633c5f40cea52d14b06d725fcd5f1b9e8f 100644
--- a/tests/api/make_samples.py
+++ b/tests/api/make_samples.py
@@ -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:
diff --git a/tests/api/test_api_evaluations.py b/tests/api/test_api_evaluations.py
index 59ff3c050b80223bec6a933a4ee0f6c5862c32fc..383f5fe5d972847902c6ca311510b9ae865cfac2 100644
--- a/tests/api/test_api_evaluations.py
+++ b/tests/api/test_api_evaluations.py
@@ -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
diff --git a/tests/api/tools_test_api.py b/tests/api/tools_test_api.py
index 794095fe3abd172483128eccef7ec7c9ea95f15d..e3b1158a8ba938023d37d1820049cd23a30f0ee8 100644
--- a/tests/api/tools_test_api.py
+++ b/tests/api/tools_test_api.py
@@ -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 = {