185 lines
6.6 KiB
Markdown
185 lines
6.6 KiB
Markdown
# Développement ScoDoc: Introduction
|
|
|
|
## Composants logiciels
|
|
|
|
- le code est écrit en Python 3.11.
|
|
- le code doit être formatté par [black](https://pypi.org/project/black/) qui
|
|
est normalement intégré à votre éditeur (VSCode et PyCharm sont deux choix
|
|
judicieux).
|
|
- outre Python, les principaux composants logiciels sont:
|
|
- [Flask](https://flask-sqlalchemy.palletsprojects.com/en/2.x/): le
|
|
framework Web, dont on utilise notamment:
|
|
- l'ORM [SQLAlchemy](https://www.sqlalchemy.org/)
|
|
- les templates [Jinja2](https://jinja.palletsprojects.com/en/3.0.x/)
|
|
- [Postgresql](https://www.postgresql.org/)
|
|
- [Redis](https://redis.io/) cache persistant
|
|
- [NGINX](https://www.nginx.com/) serveur Web frontal
|
|
- [gunicorn](https://gunicorn.org/) WSGI HTTP server
|
|
- et bien sûr Linux (Debian 12 en 2023-2024) et systemd.
|
|
|
|
## Principaux objets
|
|
|
|
Les objets manipulés par ScoDoc sont pour la plupart stockés en base postgres et
|
|
accédé soit directement en SQL (anciennes parties de ScoDoc), soit à travers
|
|
l'ORM SQLAlchemy (recommandé pour tout nouveau code).
|
|
|
|
Les modèles correspondant sont déclarés dans `/opt/scodoc/app/models/`.
|
|
|
|
Principales classes (les noms des classes Python sont en `CamelCase`).
|
|
|
|
- Étudiants (classe `Identite`): nom, codes INE/NIP, etc
|
|
- Formations: programmes pédagogiques, contenant
|
|
- Unités d'Enseignement (`UniteEns`);
|
|
- Matières et Modules (`Module`, avec son type standard, bonus, ressources
|
|
ou SAÉ).
|
|
- FormSemestre: instanciation d'une session de formation, avec un programme
|
|
pédagogique donné (Formation), les dates de début et fin, des étudiants
|
|
inscrits, des responsables, divers codes, et les ModuleImpl mis en œuvre.
|
|
- ModuleImpl: la mise en place d'un module pédagogique (le ModuleImpl est au
|
|
Module ce que le FormSemestre est à la Formation): lié à un module, avec un
|
|
enseignant responsable et des étudiants inscrits.
|
|
- Inscriptions: tables d'association avec codes et/ou état (démission,
|
|
défaillant): FormsemestreInscription ModuleImplInscription.
|
|
|
|
## Vues et décorateurs
|
|
|
|
Une vue ordinaire (Web) pourrait ressembler à cela. Noter la présence de
|
|
décorateurs:
|
|
|
|
- `@scodoc` récupère le département (présent dans l'URL) et initialise quelques
|
|
trucs, notamment `g.scodoc_dept` (l'acronyme du département courant) et
|
|
`g.scodoc_dept_id` (l'id du dépt. courant).
|
|
- `@permission_required`: permet de contrôler l'accès, en se basant sur les
|
|
permissions définies dans la classe `Permission`.
|
|
|
|
```py
|
|
@bp.route("/un_exemple")
|
|
@scodoc
|
|
@permission_required(Permission.EditFormation)
|
|
def un_exemple():
|
|
# Récupérer le ou les arguments: exemple avec formation_id
|
|
formation_id = int(request.args["formation_id"])
|
|
# Charger le ou les objets utilies:
|
|
formation = models.Formation.query.get(
|
|
formation_id=formation_id
|
|
).first_or_404()
|
|
# Effectuer au besoin un traitement
|
|
resultat = ...
|
|
# Afficher le résultat
|
|
return render_template(
|
|
"exemple_template.html",
|
|
resultat=resultat, # par exemple
|
|
formation=formation,
|
|
... # etc
|
|
)
|
|
```
|
|
|
|
## Vues de l'API et permissions
|
|
|
|
L'API REST est documentée ici : [ScoDoc9API](ScoDoc9API.md).
|
|
|
|
Les fonctions de l'API sont donc accessibles via les routes de la forme
|
|
`https://scodoc.monsite.tld/ScoDoc/api/fonction`
|
|
et aussi `https://scodoc.monsite.tld/ScoDoc/api/<dept_acronyme>/fonction`.
|
|
La seconde forme précise un département.
|
|
|
|
La seconde forme est notamment utilisée par les pages web de ScoDoc. Elle permet
|
|
un calcul des permissions liées à un département: l'idée est de donner accès à
|
|
l'API à un utilisateur qui n'ait pas la permission (par ex. `ScoView`) dans tous les
|
|
départements, ce qui est en général le cas des utilisateurs Web, mais est aussi
|
|
utile pour sécuriser certains usages de l'API.
|
|
|
|
Une vue API (avec accès via token API et/ou cookie Web) se déclare donc ainsi:
|
|
|
|
```py
|
|
@bp.route("/formsemestres/query")
|
|
@api_web_bp.route("/formsemestres/query")
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def formsemestres_query():
|
|
...
|
|
```
|
|
|
|
Son usage par un utilisateur n'ayant accès qu'à un seul département passera par
|
|
la route départementale
|
|
`http://scodoc.monsite.tld:5000/ScoDoc/<dept_acronyme>/api/formsemestres/query`.
|
|
|
|
### Exemple complet d'usage
|
|
|
|
Création du rôle, de l'utilisateur, association à un département, requêtage.
|
|
|
|
```bash
|
|
flask create-role LecteurAPI2 # 2 car LecteurAPi était déjà pris sur mon serveur de test
|
|
flask edit-role -a ScoView LecteurAPI2
|
|
flask user-create lecteur_rt LecteurAPI2 RT # Seulement dans dept RT
|
|
flask user-password lecteur_rt
|
|
```
|
|
|
|
puis
|
|
|
|
```bash
|
|
http -a lecteur_rt:mot_de_passe POST 'http://localhost:5000/ScoDoc/api/tokens'
|
|
# récupérer le token...
|
|
|
|
http GET http://localhost:5000/ScoDoc/api/RT/formsemestres/query "Authorization:Bearer xxxxxxxxxxx"
|
|
# -> réponse ok
|
|
|
|
http GET http://localhost:5000/ScoDoc/api/formsemestres/query "Authorization:Bearer xxxxxxxxxxx"
|
|
# -> 401, "Non autorise (logic)"
|
|
```
|
|
|
|
### Côté programmation serveur
|
|
|
|
Reprenons le même exemple (voir `app/api/formsemestres.py`` ligne 91,
|
|
<https://scodoc.org/git/ScoDoc/ScoDoc/src/branch/master/app/api/formsemestres.py#L91>):
|
|
|
|
```py
|
|
@bp.route("/formsemestres/query")
|
|
@api_web_bp.route("/formsemestres/query")
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def formsemestres_query():
|
|
...
|
|
formsemestres = FormSemestre.query
|
|
if g.scodoc_dept:
|
|
formsemestres = formsemestres.filter_by(dept_id=g.scodoc_dept_id)
|
|
...
|
|
```
|
|
|
|
En effet, `g.scodoc_dept` et `g.scodoc_dept_id` sont positionnés par le
|
|
décorateur si on a un appel via la route départementale.
|
|
|
|
Il est donc important, pour toutes les vues API, de prendre soin de ne pas
|
|
divulguer d'informations hors du département spécifié, en filtrant la ou les
|
|
requêtes si `g.scodoc_dept` est non `None`.
|
|
|
|
## Caches
|
|
|
|
Il est bon de savoir que les requêtes SQL de SQLAlchemy ne sont pas cachées: ni
|
|
la requête elle même (construction du SQL à partir des appels à l'ORM), ni son
|
|
résultat.
|
|
|
|
Le module `sco_cache.py` offre la possibilité de cacher des objets python
|
|
identifiés par un id unique dans le cache Redis. Ce cache est persistant, il
|
|
faut donc invalider les objets quand on écrit des données susceptibles de les
|
|
modifier, et penser à le vider quand on modifie le code.
|
|
Par exemple:
|
|
|
|
```bash
|
|
git pull
|
|
flask clear-cache
|
|
```
|
|
|
|
La commande `redis-cli FLUSHALL` permet aussi de vider le cache sans avoir à
|
|
lancer flask (plus rapide).
|
|
|
|
!!! note "Voir aussi"
|
|
|
|
- [Conventions de codage](DevConventions.md)
|
|
- [Guide développeurs](GuideDeveloppeurs.md)
|
|
- [API ScoDoc 9](ScoDoc9API.md)
|
|
- [Modélisation du BUT](ModelisationParcoursBUT.md)
|
|
- [Contacts](Contact.md)
|