Compare commits

...

125 Commits

Author SHA1 Message Date
Jean-Marie Place 53aea89560 Merge branch 'master' into dyn_fields 2021-09-25 14:01:01 +02:00
Emmanuel Viennet 671ef6a7fa Fix urgent: regression sur XMLgetAbsEtud 2021-09-25 12:35:26 +02:00
Emmanuel Viennet edc6da3005 applique upgrade alembic dans le script de restoration 2021-09-25 12:33:37 +02:00
Emmanuel Viennet b015cf3f88 Merge pull request 'enable xml/json result as file' (#136) from jmplace/ScoDoc-Lille:export_json_xml_as_files into master
Reviewed-on: viennet/ScoDoc#136
2021-09-25 10:46:43 +02:00
Emmanuel Viennet a2c16207cb Ordre boites export Apogee + fix export excel 2021-09-25 10:43:06 +02:00
Jean-Marie Place 1aa39c72fd Merge branch 'master' into dyn_fields 2021-09-25 10:01:09 +02:00
Jean-Marie Place 00dbd25b42 enable xml/json result as file 2021-09-25 09:53:31 +02:00
Emmanuel Viennet 4e59b9597b Merge pull request 'fix suffix processing' (#135) from jmplace/ScoDoc-Lille:fix_suffix into master
Reviewed-on: viennet/ScoDoc#135
2021-09-25 09:21:08 +02:00
Emmanuel Viennet f1660e12e1 version 2021-09-25 09:13:56 +02:00
Emmanuel Viennet cb03cc962c Scripts save/restore 2021-09-25 09:13:39 +02:00
Jean-Marie Place 81df68b491 fix suffix processing 2021-09-25 09:07:05 +02:00
Jean-Marie Place bd6e3e6648 temp 2021-09-24 23:15:14 +02:00
Jean-Marie Place cd06a780d5 Merge branch 'master' into dyn_fields 2021-09-24 21:51:25 +02:00
Emmanuel Viennet 1741e75f72 formation_delete sans REQUESt 2021-09-24 20:35:50 +02:00
Emmanuel Viennet c41726c4a8 Fix: association nouvelle version 2021-09-24 20:20:45 +02:00
Emmanuel Viennet 7879c176dd Bonus Béziers (à valider) 2021-09-24 20:19:20 +02:00
Emmanuel Viennet 75f43bbdde élimination des qq REQUESTS et correction publication XML/XLS/JSON/... 2021-09-24 16:32:49 +02:00
Jean-Marie Place 3f86d9b380 WIP 2021-09-24 16:10:01 +02:00
Jean-Marie Place 37303df74c add_fields ok 2021-09-24 16:00:55 +02:00
Emmanuel Viennet 0a50edc9f0 removed some useless REQUESTs 2021-09-24 12:10:53 +02:00
Emmanuel Viennet 373feece76 fix: POST suppr. photo 2021-09-24 11:12:49 +02:00
Jean-Marie Place e4c9e5f9e8 merged master 2021-09-24 10:44:13 +02:00
Jean-Marie Place 1af6f79da9 Merge branch 'master' into dyn_fields
# Conflicts:
#	app/scodoc/sco_placement.py
#	app/static/css/scodoc.css
#	app/templates/scodoc/forms/placement.html
#	tests/unit/test_abs_demijournee.py
#	tests/unit/test_formations.py
2021-09-24 10:29:24 +02:00
Emmanuel Viennet 6d1ffb122b Fix: billets d'absences (POST) 2021-09-24 01:07:00 +02:00
Emmanuel Viennet 92c401f17c Fix #133. Image photo: enleve canal alpha avant conversion en jpeg. 2021-09-24 00:54:59 +02:00
Emmanuel Viennet 36c7358eed Améliore message d'erreur si upload image invalide 2021-09-24 00:47:06 +02:00
Emmanuel Viennet 9c5408f503 Fix: export tables en XML si id de colonnes numeriques 2021-09-24 00:33:18 +02:00
Emmanuel Viennet 2add3e12cc Fix: lien validation jury 2021-09-24 00:28:09 +02:00
Emmanuel Viennet d0ab9dc66a Fix: remove obsolete field "debouche" from scodoc7 editor 2021-09-24 00:19:19 +02:00
Emmanuel Viennet beeca54a94 Fix: liens dans table liste absences 2021-09-24 00:11:56 +02:00
Emmanuel Viennet 73cf9a6f4d Fix: evaluations à dates vides (tri) 2021-09-24 00:04:28 +02:00
Emmanuel Viennet fede1ae7af Merge pull request 'fix_group_selector' (#134) from jmplace/ScoDoc-Lille:fix_group_selector into master
Reviewed-on: viennet/ScoDoc#134
2021-09-23 23:51:11 +02:00
Jean-Marie Place 845152afdd put back one empty line 2021-09-23 14:44:28 +02:00
Jean-Marie Place a4d091fa2d retrait du cast int(retreive_formsemestre_from_request(...)) réalisée par la fonction 2021-09-23 14:36:47 +02:00
Jean-Marie Place ffa7e07cd3 Merge branch 'master' into fix_group_selector 2021-09-23 13:44:30 +02:00
Emmanuel Viennet 865192bc0d type hint 2021-09-23 08:15:54 +02:00
Jean-Marie Place b56f205e89 fix group_selector / info_etu popup 2021-09-23 07:34:42 +02:00
Emmanuel Viennet eded2fffe9 enhance scu.send_file, fix some API bugs. 2021-09-21 22:19:08 +02:00
Emmanuel Viennet 13f1539282 Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc 2021-09-21 16:51:07 +02:00
Emmanuel Viennet ae51e4c17a Merge pull request 'gentil message sur erreur d import esxcel' (#131) from jmplace/ScoDoc-Lille:import_excel_errmsg into master
Reviewed-on: viennet/ScoDoc#131
2021-09-21 16:50:45 +02:00
Emmanuel Viennet 7214627994 Merge pull request 'transmit multiple occurence of an argument into a list' (#130) from jmplace/ScoDoc-Lille:fix_multivalued_args into master
Reviewed-on: viennet/ScoDoc#130
2021-09-21 16:49:41 +02:00
Emmanuel Viennet 6cc1b60da4 Adaptation des tests unitaires aux vues de l'API (qui renvoient des réponses Flask). 2021-09-21 16:34:58 +02:00
Jean-Marie Place 4297d36dad gentil message sur erreur d import esxcel 2021-09-21 16:28:26 +02:00
Emmanuel Viennet 2999199b19 La config test (tests unitaire) utilise SCODOC_TEST_DATABASE_URI et non plus SCODOC_DATABASE_URI 2021-09-21 15:55:09 +02:00
Emmanuel Viennet f516ccdfe7 retire arguments REQUEST de sendResult et sendPDFFile 2021-09-21 15:53:33 +02:00
Emmanuel Viennet 2c97349acf améliore affichage exceptions (ex: erreur inscription étudiants) 2021-09-21 15:02:22 +02:00
Emmanuel Viennet 5dfc64a62d sco_debouche APi views return empty OK responses / removed useless REQUEST 2021-09-21 13:36:56 +02:00
Jean-Marie Place 9dd8198c7b transmit multiple occurence of an argument into a list 2021-09-21 06:48:54 +02:00
Jean-Marie Place b05aea95b6 test ajout cb 2021-09-21 05:35:56 +02:00
Emmanuel Viennet f18a9c7559 fix: requete billets absences 2021-09-20 15:55:48 +02:00
Emmanuel Viennet 985c6df3b6 empeche noms de feuilles excel invalides 2021-09-20 15:54:38 +02:00
Emmanuel Viennet 286e9cdc2f version 9.0.32 2021-09-19 21:39:34 +02:00
Emmanuel Viennet 0381576750 modif contrainte sur formations 2021-09-19 21:31:35 +02:00
Emmanuel Viennet 7a0a04bdb3 fix install 2021-09-19 20:44:26 +02:00
Emmanuel Viennet 35f23995aa fix retrait semestre d'un export Apo 2021-09-19 16:19:02 +02:00
Emmanuel Viennet 29221666a4 Merge pull request 'patch_placement' (#127) from jmplace/ScoDoc-Lille:patch_placement into master
Reviewed-on: viennet/ScoDoc#127
2021-09-19 16:14:58 +02:00
Emmanuel Viennet d7e6a7d714 ameliore assistance 2021-09-19 16:13:57 +02:00
Jean-Marie Place 179be1baa0 fix rang 1 twice in excel map 2021-09-19 09:21:37 +02:00
Jean-Marie Place a5ed9b815f fix plantage si un seul groupe ; correction indentation for radio button in wtf_forms (css) 2021-09-19 09:11:11 +02:00
Jean-Marie Place b552588c1c remove cr 2021-09-19 06:19:11 +02:00
Emmanuel Viennet 13c027fc19 Adapte assistance à ScoDoc 9 2021-09-18 22:00:10 +02:00
Jean-Marie Place 2dbc1ca695 push avec crlf 2021-09-18 14:34:59 +02:00
Jean-Marie Place 9383a53569 restest 2021-09-18 14:31:45 +02:00
Emmanuel Viennet 31505e1330 détails feuille placement 2021-09-18 14:21:15 +02:00
Jean-Marie Place 0621cb1d0f test cr 2021-09-18 14:04:45 +02:00
Emmanuel Viennet 9a9dc4a483 Fix conflicts 2021-09-18 14:03:36 +02:00
Emmanuel Viennet 11ba73d264 Merge pull request 'scodoc9_placement_PR' (#126) from jmplace/ScoDoc-Lille:scodoc9_placement_PR into master
Reviewed-on: viennet/ScoDoc#126
2021-09-18 13:43:11 +02:00
Emmanuel Viennet 7daa49f2aa Elimine les attributs de ZREQUEST, sauf forms. 2021-09-18 13:42:19 +02:00
Jean-Marie Place f7961a135a finalisation 2021-09-18 11:08:04 +02:00
Jean-Marie Place c955870e1e recheck 2021-09-18 10:51:19 +02:00
Jean-Marie Place 80f5536de5 Merge remote-tracking branch 'origin/master' into scodoc9_placement_PR
# Conflicts:
#	app/scodoc/sco_groups_view.py
#	app/scodoc/sco_placement.py
2021-09-18 10:39:53 +02:00
Jean-Marie Place 2519d08e40 remove breakpoints() + blackify 2021-09-18 10:11:46 +02:00
Emmanuel Viennet 987800c30e Remplace REQUEST.URL par accès à Flask request global object 2021-09-18 10:10:02 +02:00
Jean-Marie Place ae1feba96c template + accessors 2021-09-18 10:05:11 +02:00
Jean-Marie Place 2a72fb881b replace send_excel_file by scu.send_file 2021-09-18 01:30:47 +02:00
Jean-Marie Place 87ecd09f0e fix a regression ; eliminate send_from_flask 2021-09-17 16:01:01 +02:00
Jean-Marie Place 6e7a104fb0 pylint corrections (suite) 2021-09-17 12:31:43 +02:00
Jean-Marie Place b03eee12a1 pylint corrections 2021-09-17 10:59:26 +02:00
Jean-Marie Place 44117fb0e2 blackify + suppress cr-at-eol 2021-09-17 10:26:20 +02:00
Jean-Marie Place 42ef9f795f Merge remote-tracking branch 'origin/master' into temp
# Conflicts:
#	app/scodoc/sco_placement.py
2021-09-17 10:12:16 +02:00
Emmanuel Viennet bd2e0ccde5 Archives: utilise dept_id et non acronyme 2021-09-17 10:02:27 +02:00
Jean-Marie Place 5f0f437f2e Merge remote-tracking branch 'jmplace/temp' into temp
# Conflicts:
#	app/scodoc/sco_placement.py
2021-09-17 09:49:00 +02:00
Jean-Marie Place b6cc251c94 minor changes 2021-09-17 09:44:47 +02:00
Jean-Marie Place 5f6c434497 minor refactor 2021-09-17 09:20:04 +02:00
Emmanuel Viennet 45352d9248 Script migration: vérification existence departements dans base cible 2021-09-17 09:15:12 +02:00
Jean-Marie Place b3225e07f7 avant tests 2021-09-17 08:22:54 +02:00
Jean-Marie Place 0ef822cfd8 déplacement du template placement 2021-09-17 05:54:11 +02:00
Jean-Marie Place a23ae38014 integration master 2021-09-17 05:03:34 +02:00
Emmanuel Viennet 7d59b52018 version bump 2021-09-16 22:33:15 +02:00
Emmanuel Viennet 80238545f3 oups: fichiers oubliés. 2021-09-16 22:31:47 +02:00
Emmanuel Viennet 72e075530c Pour la transition BUT: bloquage du calcul des moyennes 2021-09-16 22:24:08 +02:00
Emmanuel Viennet 91cc421ef8 Post-migration des archives 2021-09-16 21:42:45 +02:00
Emmanuel Viennet 8b6a569a31 Classe ReverseProxied WSGI pour ré-écriture des URL http/https 2021-09-16 16:05:37 +02:00
Emmanuel Viennet c8949e870f code cleaning 2021-09-16 14:58:56 +02:00
Emmanuel Viennet 30481e4729 ajout doc et modif config SCODOC_DATABASE_URI 2021-09-16 11:46:36 +02:00
Emmanuel Viennet 085aff657a fix: preference par departement 2021-09-16 11:45:39 +02:00
Emmanuel Viennet 3666f8b1ec Améliore sidebar avec template jinja 2021-09-16 10:09:17 +02:00
Emmanuel Viennet bec7deb581 Ajout perm. export Apo aux rôles Secr et Admin 2021-09-16 09:13:05 +02:00
Emmanuel Viennet 6dbbcde454 unifie fichiers moodlecsv 2021-09-16 08:17:26 +02:00
Emmanuel Viennet 9578c789dc Fix sort groupe (#py3) 2021-09-16 00:16:50 +02:00
Emmanuel Viennet 0fedb7771c Améliore téléchargement fichiers (REQUEST => send_file) 2021-09-16 00:15:29 +02:00
Emmanuel Viennet 6dba8933c4 fix selection groupe saisie note 2021-09-15 23:02:11 +02:00
Emmanuel Viennet 5efc493542 Escape html read-only values 2021-09-15 22:31:16 +02:00
Emmanuel Viennet a34dd656be Change html header: xhtml -> html5 2021-09-15 22:13:04 +02:00
Emmanuel Viennet 2ec2be4234 fix link 2021-09-15 20:24:44 +02:00
Emmanuel Viennet 49609fa657 harmless typo in migration script 2021-09-15 19:20:41 +02:00
Emmanuel Viennet 8a16216d4b fixes: lien params seulement pour admin, type passage étudiants, log sources ips 2021-09-15 15:19:08 +02:00
Emmanuel Viennet 96f457260f version 9.0.25 2021-09-15 00:55:26 +02:00
Emmanuel Viennet 0f9b52bc9b fix sco_inscr_passage 2021-09-15 00:54:18 +02:00
Emmanuel Viennet 83174f2f5e typo (synchro apo) 2021-09-15 00:40:19 +02:00
Emmanuel Viennet 3fbda90a2f Better logging. New log for exceptions: /opt/scodoc-data/log/scodoc_exc.log 2021-09-15 00:33:30 +02:00
Emmanuel Viennet de206674d9 Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc 2021-09-14 23:11:14 +02:00
Emmanuel Viennet b06f37b18e Merge pull request 'fix export excel en neutralisant les formules comme chaine' (#124) from jmplace/ScoDoc-Lille:fix_formula into master
Reviewed-on: viennet/ScoDoc#124
2021-09-14 23:10:47 +02:00
Emmanuel Viennet 3496cc7beb fix envoi mail etud change 2021-09-14 12:21:22 +02:00
Jean-Marie Place 01c264c3c7 add copy to .gitignore 2021-09-13 07:17:18 +02:00
Jean-Marie Place c44aa808df preparation envoi fichier 2021-09-13 07:16:37 +02:00
Jean-Marie Place c8872bd220 excel file returned 2021-09-12 09:31:07 +02:00
Jean-Marie Place 7f63ab222b before refactoring 2021-09-12 07:04:05 +02:00
Jean-Marie Place ed07e42222 placement fait 2021-09-11 19:37:32 +02:00
Jean-Marie Place 35768e9241 placement fait 2021-09-11 19:35:30 +02:00
Jean-Marie Place 050e54de3e placement fait 2021-09-11 18:33:55 +02:00
Jean-Marie Place 37484b7fc9 Merge branch 'master' into clean 2021-09-11 10:21:54 +02:00
Jean-Marie Place f828134ea2 complements scodoc.css pour formulaire 2021-09-11 10:04:52 +02:00
Jean-Marie Place a4d0205cc7 Merge remote-tracking branch 'origin/master' into clean 2021-09-05 14:52:58 +02:00
Jean-Marie Place 770ccb4d6e fin fusion 2021-09-05 14:50:35 +02:00
112 changed files with 4362 additions and 3958 deletions

1
.gitignore vendored
View File

@ -170,3 +170,4 @@ Thumbs.db
*.code-workspace
copy

327
README.md
View File

@ -1,163 +1,164 @@
# ScoDoc - Gestion de la scolarité - Version ScoDoc 9
(c) Emmanuel Viennet 1999 - 2021 (voir LICENCE.txt)
VERSION EXPERIMENTALE - NE PAS DEPLOYER - TESTS EN COURS
Installation: voir instructions à jour sur <https://scodoc.org/GuideInstallDebian11>
Documentation utilisateur: <https://scodoc.org>
## Version ScoDoc 9
N'utiliser que pour les développements et tests.
La version ScoDoc 9 est basée sur Flask (au lieu de Zope) et sur
**python 3.9+**.
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 (27 août 21)
- Tests en cours, notamment système d'installation et de migration.
**Fonctionnalités non intégrées:**
- feuille "placement" (en cours)
- ancien module "Entreprises" (obsolete)
### 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.
### Fichiers locaux
Sous `/opt/scodoc-data`, fichiers et répertoires appartienant à 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é.
Principaux contenus:
/opt/scodoc-data
/opt/scodoc-data/log # Fichiers de log ScoDoc
/opt/scodoc-data/config # Fichiers de configuration
.../config/logos # Logos de l'établissement
.../config/depts # un fichier par département
/opt/scodoc-data/photos # Photos des étudiants
/opt/scodoc-data/archives # Archives: PV de jury, maquettes Apogée, fichiers étudiants
## Pour les développeurs
### Installation du code
Installer ScoDoc 9 normalement ([voir la doc](https://scodoc.org/GuideInstallDebian11)).
Puis remplacer `/opt/scodoc` par un clone du git.
sudo su
mv /opt/scodoc /opt/off-scodoc # ou ce que vous voulez
apt-get install git # si besoin
cd /opt
git clone https://scodoc.org/git/viennet/ScoDoc.git
# (ou bien utiliser votre clone gitea si vous l'avez déjà créé !)
mv ScoDoc scodoc # important !
Il faut ensuite installer l'environnement et le fichier de configuration:
# Le plus simple est de piquer le virtualenv configuré par l'installeur:
mv /opt/off-scodoc/venv /opt/scodoc
Et la config:
ln -s /opt/scodoc-data/.env /opt/scodoc
Cette dernière commande utilise le `.env` crée lors de l'install, ce qui
n'est pas toujours le plus judicieux: vous pouvez modifier son contenu, par
exemple pour travailler en mode "développement" avec `FLASK_ENV=development`.
### Tests unitaires
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:
flask sco-delete-dept TEST00 && flask sco-create-dept TEST00
Puis dérouler les tests unitaires:
pytest tests/unit
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 étudianst 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
puis de les lancer 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:
flask user-password admin
**Attention:** les tests unitaires **effacent** complètement le contenu de la
base de données (tous les départements, et les utilisateurs) avant de commencer !
#### Modification du schéma de la base
On utilise SQLAlchemy avec Alembic et Flask-Migrate.
flask db migrate -m "ScoDoc 9.0.x: ..." # ajuster le message !
flask db upgrade
Ne pas oublier de commiter les migrations (`git add migrations` ...).
Mémo pour développeurs: séquence re-création d'une base:
dropdb SCODOC_DEV
tools/create_database.sh SCODOC_DEV # créé base SQL
flask db upgrade # créé les tables à partir des migrations
flask sco-db-init # ajoute au besoin les constantes (fait en migration 0)
# puis imports:
flask import-scodoc7-users
flask import-scodoc7-dept STID SCOSTID
Si la base utilisée pour les dev n'est plus en phase avec les scripts de
migration, utiliser les commandes `flask db history`et `flask db stamp`pour se
positionner à la bonne étape.
# Paquet debian 11
Les scripts associés au paquet Debian (.deb) sont dans `tools/debian`. Le plus
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`.
# ScoDoc - Gestion de la scolarité - Version ScoDoc 9
(c) Emmanuel Viennet 1999 - 2021 (voir LICENCE.txt)\r
VERSION EXPERIMENTALE - NE PAS DEPLOYER - TESTS EN COURS
Installation: voir instructions à jour sur <https://scodoc.org/GuideInstallDebian11>
Documentation utilisateur: <https://scodoc.org>
## Version ScoDoc 9
N'utiliser que pour les développements et tests.
La version ScoDoc 9 est basée sur Flask (au lieu de Zope) et sur
**python 3.9+**.
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 (27 août 21)
- Tests en cours, notamment système d'installation et de migration.
**Fonctionnalités non intégrées:**
- feuille "placement" (en cours)
- ancien module "Entreprises" (obsolete)
### 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.
### Fichiers locaux
Sous `/opt/scodoc-data`, fichiers et répertoires appartienant à 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é.
Principaux contenus:
/opt/scodoc-data
/opt/scodoc-data/log # Fichiers de log ScoDoc
/opt/scodoc-data/config # Fichiers de configuration
.../config/logos # Logos de l'établissement
.../config/depts # un fichier par département
/opt/scodoc-data/photos # Photos des étudiants
/opt/scodoc-data/archives # Archives: PV de jury, maquettes Apogée, fichiers étudiants
## Pour les développeurs
### Installation du code
Installer ScoDoc 9 normalement ([voir la doc](https://scodoc.org/GuideInstallDebian11)).
Puis remplacer `/opt/scodoc` par un clone du git.
sudo su
mv /opt/scodoc /opt/off-scodoc # ou ce que vous voulez
apt-get install git # si besoin
cd /opt
git clone https://scodoc.org/git/viennet/ScoDoc.git
# (ou bien utiliser votre clone gitea si vous l'avez déjà créé !)
mv ScoDoc scodoc # important !
Il faut ensuite installer l'environnement et le fichier de configuration:
# Le plus simple est de piquer le virtualenv configuré par l'installeur:
mv /opt/off-scodoc/venv /opt/scodoc
Et la config:
ln -s /opt/scodoc-data/.env /opt/scodoc
Cette dernière commande utilise le `.env` crée lors de l'install, ce qui
n'est pas toujours le plus judicieux: vous pouvez modifier son contenu, par
exemple pour travailler en mode "développement" avec `FLASK_ENV=development`.
### Tests unitaires
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:
flask sco-delete-dept TEST00 && flask sco-create-dept TEST00
Puis dérouler les tests unitaires:
pytest tests/unit
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 étudianst 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
puis de les lancer 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:
flask user-password admin
**Attention:** les tests unitaires **effacent** complètement le contenu de la
base de données (tous les départements, et les utilisateurs) avant de commencer !
#### Modification du schéma de la base
On utilise SQLAlchemy avec Alembic et Flask-Migrate.
flask db migrate -m "ScoDoc 9.0.x: ..." # ajuster le message !
flask db upgrade
Ne pas oublier de commiter les migrations (`git add migrations` ...).
Mémo pour développeurs: séquence re-création d'une base:
dropdb SCODOC_DEV
tools/create_database.sh SCODOC_DEV # créé base SQL
flask db upgrade # créé les tables à partir des migrations
flask sco-db-init # ajoute au besoin les constantes (fait en migration 0)
# puis imports:
flask import-scodoc7-users
flask import-scodoc7-dept STID SCOSTID
Si la base utilisée pour les dev n'est plus en phase avec les scripts de
migration, utiliser les commandes `flask db history`et `flask db stamp`pour se
positionner à la bonne étape.
# Paquet debian 11
Les scripts associés au paquet Debian (.deb) sont dans `tools/debian`. Le plus
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`.

View File

@ -2,6 +2,7 @@
# pylint: disable=invalid-name
import os
import re
import socket
import sys
import time
@ -17,14 +18,14 @@ from flask import render_template
from flask.logging import default_handler
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_login import LoginManager, current_user
from flask_mail import Mail
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from flask_caching import Cache
import sqlalchemy
from app.scodoc.sco_exceptions import ScoValueError, APIInvalidParams
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError, APIInvalidParams
from config import DevConfig
import sco_version
@ -82,7 +83,7 @@ def postgresql_server_error(e):
return render_raw_html("error_503.html", SCOVERSION=sco_version.SCOVERSION), 503
class RequestFormatter(logging.Formatter):
class LogRequestFormatter(logging.Formatter):
"""Ajoute URL et remote_addr for logging"""
def format(self, record):
@ -92,6 +93,33 @@ class RequestFormatter(logging.Formatter):
else:
record.url = None
record.remote_addr = None
record.sco_user = current_user
return super().format(record)
class LogExceptionFormatter(logging.Formatter):
"""Formatteur pour les exceptions: ajoute détails"""
def format(self, record):
if has_request_context():
record.url = request.url
record.remote_addr = request.environ.get(
"HTTP_X_FORWARDED_FOR", request.remote_addr
)
record.http_referrer = request.referrer
record.http_method = request.method
if request.method == "GET":
record.http_params = str(request.args)
else:
record.http_params = "(post data not loggued)"
else:
record.url = None
record.remote_addr = None
record.http_referrer = None
record.http_method = None
record.http_params = None
record.sco_user = current_user
return super().format(record)
@ -105,8 +133,24 @@ class ScoSMTPHandler(SMTPHandler):
return subject
class ReverseProxied(object):
"""Adaptateur wsgi qui nous permet d'avoir toutes les URL calculées en https
sauf quand on est en dev.
La variable HTTP_X_FORWARDED_PROTO est positionnée par notre config nginx"""
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
scheme = environ.get("HTTP_X_FORWARDED_PROTO")
if scheme:
environ["wsgi.url_scheme"] = scheme # ou forcer à https ici ?
return self.app(environ, start_response)
def create_app(config_class=DevConfig):
app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static")
app.wsgi_app = ReverseProxied(app.wsgi_app)
app.logger.setLevel(logging.DEBUG)
app.config.from_object(config_class)
@ -119,6 +163,7 @@ def create_app(config_class=DevConfig):
cache.init_app(app)
sco_cache.CACHE = cache
app.register_error_handler(ScoGenError, handle_sco_value_error)
app.register_error_handler(ScoValueError, handle_sco_value_error)
app.register_error_handler(500, internal_server_error)
app.register_error_handler(503, postgresql_server_error)
@ -148,9 +193,16 @@ def create_app(config_class=DevConfig):
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
)
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
scodoc_exc_formatter = RequestFormatter(
"[%(asctime)s] %(remote_addr)s requested %(url)s\n"
"%(levelname)s in %(module)s: %(message)s"
scodoc_log_formatter = LogRequestFormatter(
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
"%(levelname)s: %(message)s"
)
scodoc_exc_formatter = LogExceptionFormatter(
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
"%(levelname)s: %(message)s\n"
"Referrer: %(http_referrer)s\n"
"Method: %(http_method)s\n"
"Params: %(http_params)s\n"
)
if not app.testing:
if not app.debug:
@ -179,7 +231,7 @@ def create_app(config_class=DevConfig):
app.logger.addHandler(mail_handler)
else:
# Pour logs en DEV uniquement:
default_handler.setFormatter(scodoc_exc_formatter)
default_handler.setFormatter(scodoc_log_formatter)
# Config logs pour DEV et PRODUCTION
# Configuration des logs (actifs aussi en mode development)
@ -188,9 +240,17 @@ def create_app(config_class=DevConfig):
file_handler = WatchedFileHandler(
app.config["SCODOC_LOG_FILE"], encoding="utf-8"
)
file_handler.setFormatter(scodoc_exc_formatter)
file_handler.setFormatter(scodoc_log_formatter)
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
# Log pour les erreurs (exceptions) uniquement:
# usually /opt/scodoc-data/log/scodoc_exc.log
file_handler = WatchedFileHandler(
app.config["SCODOC_ERR_FILE"], encoding="utf-8"
)
file_handler.setFormatter(scodoc_exc_formatter)
file_handler.setLevel(logging.ERROR)
app.logger.addHandler(file_handler)
# app.logger.setLevel(logging.INFO)
app.logger.info(f"{sco_version.SCONAME} {sco_version.SCOVERSION} startup")

View File

@ -20,7 +20,9 @@
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.from flask import jsonify
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from flask import jsonify
from werkzeug.http import HTTP_STATUS_CODES

View File

@ -43,18 +43,20 @@ class ZRequest(object):
"Emulating Zope 2 REQUEST"
def __init__(self):
if current_app.config["DEBUG"]:
self.URL = request.base_url
self.BASE0 = request.url_root
else:
self.URL = request.base_url.replace("http://", "https://")
self.BASE0 = request.url_root.replace("http://", "https://")
self.URL0 = self.URL
# if current_app.config["DEBUG"]:
# le ReverseProxied se charge maintenant de mettre le bon protocole http ou https
# self.URL = request.base_url
# self.BASE0 = request.url_root
# else:
# self.URL = request.base_url.replace("http://", "https://")
# self.BASE0 = request.url_root.replace("http://", "https://")
# self.URL0 = self.URL
# query_string is bytes:
self.QUERY_STRING = request.query_string.decode("utf-8")
self.REQUEST_METHOD = request.method
self.AUTHENTICATED_USER = current_user
self.REMOTE_ADDR = request.remote_addr
# self.QUERY_STRING = request.query_string.decode("utf-8")
# self.REQUEST_METHOD = request.method
# self.AUTHENTICATED_USER = current_user
# self.REMOTE_ADDR = request.remote_addr
if request.method == "POST":
# request.form is a werkzeug.datastructures.ImmutableMultiDict
# must copy to get a mutable version (needed by TrivialFormulator)
@ -72,16 +74,13 @@ class ZRequest(object):
if k.endswith(":list"):
self.form[k[:-5]] = request.args.getlist(k)
else:
self.form[k] = request.args[k]
values = request.args.getlist(k)
self.form[k] = values[0] if len(values) == 1 else values
# current_app.logger.info("ZRequest.form=%s" % str(self.form))
self.RESPONSE = ZResponse()
def __str__(self):
return """REQUEST
URL={r.URL}
QUERY_STRING={r.QUERY_STRING}
REQUEST_METHOD={r.REQUEST_METHOD}
AUTHENTICATED_USER={r.AUTHENTICATED_USER}
return """ZREQUEST
form={r.form}
""".format(
r=self
@ -231,6 +230,7 @@ def scodoc7func(func):
if arg_name == "REQUEST": # special case
pos_arg_values.append(REQUEST)
else:
# peut produire une KeyError s'il manque un argument attendu:
v = req_args[arg_name]
# try to convert all arguments to INTEGERS
# necessary for db ids and boolean values

View File

@ -41,6 +41,7 @@ class Identite(db.Model):
code_nip = db.Column(db.Text())
code_ine = db.Column(db.Text())
# Ancien id ScoDoc7 pour les migrations de bases anciennes
# ne pas utiliser après migrate_scodoc7_dept_archive
scodoc7_id = db.Column(db.Text(), nullable=True)

View File

@ -11,7 +11,7 @@ class NotesFormation(db.Model):
"""Programme pédagogique d'une formation"""
__tablename__ = "notes_formations"
__table_args__ = (db.UniqueConstraint("acronyme", "titre", "version"),)
__table_args__ = (db.UniqueConstraint("dept_id", "acronyme", "titre", "version"),)
id = db.Column(db.Integer, primary_key=True)
formation_id = db.synonym("id")

View File

@ -41,6 +41,10 @@ class FormSemestre(db.Model):
bul_hide_xml = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false"
)
# Bloque le calcul des moyennes (générale et d'UE)
block_moyennes = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false"
)
# semestres decales (pour gestion jurys):
gestion_semestrielle = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false"
@ -70,6 +74,7 @@ class FormSemestre(db.Model):
"NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
)
# Ancien id ScoDoc7 pour les migrations de bases anciennes
# ne pas utiliser après migrate_scodoc7_dept_archive
scodoc7_id = db.Column(db.Text(), nullable=True)
def __init__(self, **kwargs):

View File

@ -8,6 +8,7 @@
v 1.3 (python3)
"""
import html
def TrivialFormulator(
@ -722,7 +723,9 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
if str(descr["allowed_values"][i]) == str(self.values[field]):
R.append('<span class="tf-ro-value">%s</span>' % labels[i])
elif input_type == "textarea":
R.append('<div class="tf-ro-textarea">%s</div>' % self.values[field])
R.append(
'<div class="tf-ro-textarea">%s</div>' % html.escape(self.values[field])
)
elif input_type == "separator" or input_type == "hidden":
pass
elif input_type == "file":

View File

@ -379,6 +379,25 @@ def bonus_iutbethune(notes_sport, coefs, infos=None):
return bonus
def bonus_iutbeziers(notes_sport, coefs, infos=None):
"""Calcul bonus modules optionels (sport, culture), regle IUT BEZIERS
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
sport , etc) non rattaches à une unité d'enseignement. Les points
au-dessus de 10 sur 20 obtenus dans chacune des matières
optionnelles sont cumulés et 3% de ces points cumulés s'ajoutent à
la moyenne générale du semestre déjà obtenue par l'étudiant.
"""
sumc = sum(coefs) # assumes sum. coefs > 0
# note_sport = sum(map(mul, notes_sport, coefs)) / sumc # moyenne pondérée
bonus = sum([(x - 10) * 0.03 for x in notes_sport if x > 10])
# le total du bonus ne doit pas dépasser 0.3 - Fred, 28/01/2020
if bonus > 0.3:
bonus = 0.3
return bonus
def bonus_demo(notes_sport, coefs, infos=None):
"""Fausse fonction "bonus" pour afficher les informations disponibles
et aider les développeurs.
@ -386,8 +405,8 @@ def bonus_demo(notes_sport, coefs, infos=None):
qui est ECRASE à chaque appel.
*** Ne pas utiliser en production !!! ***
"""
f = open("/tmp/scodoc_bonus.log", "w") # mettre 'a' pour ajouter en fin
f.write("\n---------------\n" + pprint.pformat(infos) + "\n")
with open("/tmp/scodoc_bonus.log", "w") as f: # mettre 'a' pour ajouter en fin
f.write("\n---------------\n" + pprint.pformat(infos) + "\n")
# Statut de chaque UE
# for ue_id in infos['moy_ues']:
# ue_status = infos['moy_ues'][ue_id]

View File

@ -185,6 +185,9 @@ class GenTable(object):
else:
self.preferences = DEFAULT_TABLE_PREFERENCES()
def __repr__(self):
return f"<gen_table( nrows={self.get_nb_rows()}, ncols={self.get_nb_cols()} )>"
def get_nb_cols(self):
return len(self.columns_ids)
@ -482,9 +485,9 @@ class GenTable(object):
ses.append_blank_row() # empty line
ses.append_single_cell_row(self.origin, style_base)
if wb is None:
return ses.generate_standalone()
return ses.generate()
else:
ses.generate_embeded()
ses.generate()
def text(self):
"raw text representation of the table"
@ -573,7 +576,7 @@ class GenTable(object):
"""
doc = ElementTree.Element(
self.xml_outer_tag,
id=self.table_id,
id=str(self.table_id),
origin=self.origin or "",
caption=self.caption or "",
)
@ -587,7 +590,7 @@ class GenTable(object):
v = row.get(cid, "")
if v is None:
v = ""
x_cell = ElementTree.Element(cid, value=str(v))
x_cell = ElementTree.Element(str(cid), value=str(v))
x_row.append(x_cell)
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
@ -610,7 +613,6 @@ class GenTable(object):
format="html",
page_title="",
filename=None,
REQUEST=None,
javascripts=[],
with_html_headers=True,
publish=True,
@ -643,35 +645,53 @@ class GenTable(object):
H.append(html_sco_header.sco_footer())
return "\n".join(H)
elif format == "pdf":
objects = self.pdf()
doc = sco_pdf.pdf_basic_page(
objects, title=title, preferences=self.preferences
pdf_objs = self.pdf()
pdf_doc = sco_pdf.pdf_basic_page(
pdf_objs, title=title, preferences=self.preferences
)
if publish:
return scu.sendPDFFile(REQUEST, doc, filename + ".pdf")
return scu.send_file(
pdf_doc,
filename,
suffix=".pdf",
mime=scu.PDF_MIMETYPE,
)
else:
return doc
elif format == "xls" or format == "xlsx":
return pdf_doc
elif format == "xls" or format == "xlsx": # dans les 2 cas retourne du xlsx
xls = self.excel()
if publish:
return sco_excel.send_excel_file(
REQUEST, xls, filename + scu.XLSX_SUFFIX
return scu.send_file(
xls,
filename,
suffix=scu.XLSX_SUFFIX,
mime=scu.XLSX_MIMETYPE,
)
else:
return xls
elif format == "text":
return self.text()
elif format == "csv":
return scu.sendCSVFile(REQUEST, self.text(), filename + ".csv")
return scu.send_file(
self.text(),
filename,
suffix=".csv",
mime=scu.CSV_MIMETYPE,
attached=True,
)
elif format == "xml":
xml = self.xml()
if REQUEST and publish:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
if publish:
return scu.send_file(
xml, filename, suffix=".xml", mime=scu.XML_MIMETYPE
)
return xml
elif format == "json":
js = self.json()
if REQUEST and publish:
REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE)
if publish:
return scu.send_file(
js, filename, suffix=".json", mime=scu.JSON_MIMETYPE
)
return js
else:
log("make_page: format=%s" % format)
@ -732,5 +752,5 @@ if __name__ == "__main__":
document.build(objects)
data = doc.getvalue()
open("/tmp/gen_table.pdf", "wb").write(data)
p = T.make_page(format="pdf", REQUEST=None)
p = T.make_page(format="pdf")
open("toto.pdf", "wb").write(p)

View File

@ -87,10 +87,6 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
)
_TOP_LEVEL_CSS = """
<style type="text/css">
</style>"""
_HTML_BEGIN = """<?xml version="1.0" encoding="%(encoding)s"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
@ -105,31 +101,30 @@ _HTML_BEGIN = """<?xml version="1.0" encoding="%(encoding)s"?>
<link href="/ScoDoc/static/css/scodoc.css" rel="stylesheet" type="text/css" />
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/menu.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/sorttable.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
<script type="text/javascript">
<script src="/ScoDoc/static/libjs/menu.js"></script>
<script src="/ScoDoc/static/libjs/sorttable.js"></script>
<script src="/ScoDoc/static/libjs/bubble.js"></script>
<script>
window.onload=function(){enableTooltips("gtrcontent")};
</script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery-migrate-1.2.0.min.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>
<script src="/ScoDoc/static/jQuery/jquery.js"></script>
<script src="/ScoDoc/static/jQuery/jquery-migrate-1.2.0.min.js"></script>
<script src="/ScoDoc/static/libjs/jquery.field.min.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
<script src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
<script src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/scodoc.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/etud_info.js"></script>
<script src="/ScoDoc/static/js/scodoc.js"></script>
<script src="/ScoDoc/static/js/etud_info.js"></script>
"""
def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
H = [
_HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING},
_TOP_LEVEL_CSS,
"""</head><body class="gtrcontent" id="gtrcontent">""",
scu.CUSTOM_HTML_HEADER_CNX,
]
@ -185,13 +180,10 @@ def sco_header(
init_jquery = True
H = [
"""<?xml version="1.0" encoding="%(encoding)s"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
"""<!DOCTYPE html><html lang="fr">
<head>
<meta charset="utf-8"/>
<title>%(page_title)s</title>
<meta http-equiv="Content-Type" content="text/html; charset=%(encoding)s" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="LANG" content="fr" />
<meta name="DESCRIPTION" content="ScoDoc" />
@ -206,9 +198,7 @@ def sco_header(
)
if init_google_maps:
# It may be necessary to add an API key:
H.append(
'<script type="text/javascript" src="https://maps.google.com/maps/api/js"></script>'
)
H.append('<script src="https://maps.google.com/maps/api/js"></script>')
# Feuilles de style additionnelles:
for cssstyle in cssstyles:
@ -223,9 +213,9 @@ def sco_header(
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
<link href="/ScoDoc/static/css/gt_table.css" rel="stylesheet" type="text/css" />
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/menu.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
<script type="text/javascript">
<script src="/ScoDoc/static/libjs/menu.js"></script>
<script src="/ScoDoc/static/libjs/bubble.js"></script>
<script>
window.onload=function(){enableTooltips("gtrcontent")};
var SCO_URL="%(ScoURL)s";
@ -236,16 +226,14 @@ def sco_header(
# jQuery
if init_jquery:
H.append(
"""<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script>
"""<script src="/ScoDoc/static/jQuery/jquery.js"></script>
"""
)
H.append(
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>'
)
H.append('<script src="/ScoDoc/static/libjs/jquery.field.min.js"></script>')
# qTip
if init_qtip:
H.append(
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>'
'<script src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>'
)
H.append(
'<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />'
@ -253,32 +241,25 @@ def sco_header(
if init_jquery_ui:
H.append(
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>'
)
# H.append('<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui/js/jquery-ui-i18n.js"></script>')
H.append(
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/scodoc.js"></script>'
'<script src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>'
)
# H.append('<script src="/ScoDoc/static/libjs/jquery-ui/js/jquery-ui-i18n.js"></script>')
H.append('<script src="/ScoDoc/static/js/scodoc.js"></script>')
if init_google_maps:
H.append(
'<script type="text/javascript" src="/ScoDoc/static/libjs/jquery.ui.map.full.min.js"></script>'
'<script src="/ScoDoc/static/libjs/jquery.ui.map.full.min.js"></script>'
)
if init_datatables:
H.append(
'<link rel="stylesheet" type="text/css" href="/ScoDoc/static/DataTables/datatables.min.css"/>'
)
H.append(
'<script type="text/javascript" src="/ScoDoc/static/DataTables/datatables.min.js"></script>'
)
H.append('<script src="/ScoDoc/static/DataTables/datatables.min.js"></script>')
# JS additionels
for js in javascripts:
H.append(
"""<script language="javascript" type="text/javascript" src="/ScoDoc/static/%s"></script>\n"""
% js
)
H.append("""<script src="/ScoDoc/static/%s"></script>\n""" % js)
H.append(
"""<style type="text/css">
"""<style>
.gtrcontent {
margin-left: %(margin_left)s;
height: 100%%;
@ -290,7 +271,7 @@ def sco_header(
)
# Scripts de la page:
if scripts:
H.append("""<script language="javascript" type="text/javascript">""")
H.append("""<script>""")
for script in scripts:
H.append(script)
H.append("""</script>""")
@ -337,13 +318,7 @@ def sco_footer():
def html_sem_header(
REQUEST,
title,
sem=None,
with_page_header=True,
with_h2=True,
page_title=None,
**args
title, sem=None, with_page_header=True, with_h2=True, page_title=None, **args
):
"Titre d'une page semestre avec lien vers tableau de bord"
# sem now unused and thus optional...

View File

@ -28,9 +28,8 @@
"""
Génération de la "sidebar" (marge gauche des pages HTML)
"""
from flask import url_for
from flask import g
from flask import request
from flask import render_template, url_for
from flask import g, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
@ -155,8 +154,9 @@ def sidebar():
<div class="sidebar-bottom"><a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }" class="sidebar">À propos</a><br/>
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
</div></div>
<div class="logo-logo"><a href= { url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }
">{ scu.icontag("scologo_img", no_size=True) }</a>
<div class="logo-logo">
<a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }">
{ scu.icontag("scologo_img", no_size=True) }</a>
</div>
</div>
<!-- end of sidebar -->
@ -167,19 +167,7 @@ def sidebar():
def sidebar_dept():
"""Partie supérieure de la marge de gauche"""
H = [
f"""<h2 class="insidebar">Dépt. {sco_preferences.get_preference("DeptName")}</h2>
<a href="{url_for("scodoc.index")}" class="sidebar">Accueil</a> <br/> """
]
dept_intranet_url = sco_preferences.get_preference("DeptIntranetURL")
if dept_intranet_url:
H.append(
f"""<a href="{dept_intranet_url}" class="sidebar">{
sco_preferences.get_preference("DeptIntranetTitle")}</a> <br/>"""
)
# Entreprises pas encore supporté en ScoDoc8
# H.append(
# """<br/><a href="%(ScoURL)s/Entreprises" class="sidebar">Entreprises</a> <br/>"""
# % infos
# )
return "\n".join(H)
return render_template(
"sidebar_dept.html",
prefs=sco_preferences.SemPreferences(),
)

View File

@ -186,6 +186,8 @@ class NotesTable(object):
self.use_ue_coefs = sco_preferences.get_preference(
"use_ue_coefs", formsemestre_id
)
# si vrai, bloque calcul des moy gen. et d'UE.:
self.block_moyennes = self.sem["block_moyennes"]
# Infos sur les etudiants
self.inscrlist = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id}
@ -738,6 +740,7 @@ class NotesTable(object):
block_computation = (
self.inscrdict[etudid]["etat"] == "D"
or self.inscrdict[etudid]["etat"] == DEF
or self.block_moyennes
)
moy_ues = {}

View File

@ -626,7 +626,6 @@ def add_absence(
jour,
matin,
estjust,
REQUEST,
description=None,
moduleimpl_id=None,
):
@ -656,7 +655,7 @@ def add_absence(
sco_abs_notification.abs_notify(etudid, jour)
def add_justif(etudid, jour, matin, REQUEST, description=None):
def add_justif(etudid, jour, matin, description=None):
"Ajoute un justificatif dans la base"
# unpublished
if _isFarFutur(jour):
@ -665,7 +664,9 @@ def add_justif(etudid, jour, matin, REQUEST, description=None):
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"insert into absences (etudid,jour,estabs,estjust,matin, description) values (%(etudid)s,%(jour)s, FALSE, TRUE, %(matin)s, %(description)s )",
"""INSERT INTO absences (etudid, jour, estabs, estjust, matin, description)
VALUES (%(etudid)s, %(jour)s, FALSE, TRUE, %(matin)s, %(description)s)
""",
vars(),
)
logdb(
@ -678,7 +679,7 @@ def add_justif(etudid, jour, matin, REQUEST, description=None):
invalidate_abs_etud_date(etudid, jour)
def _add_abslist(abslist, REQUEST, moduleimpl_id=None):
def add_abslist(abslist, moduleimpl_id=None):
for a in abslist:
etudid, jour, ampm = a.split(":")
if ampm == "am":
@ -689,7 +690,7 @@ def _add_abslist(abslist, REQUEST, moduleimpl_id=None):
raise ValueError("invalid ampm !")
# ajoute abs si pas deja absent
if count_abs(etudid, jour, jour, matin, moduleimpl_id) == 0:
add_absence(etudid, jour, matin, 0, REQUEST, "", moduleimpl_id)
add_absence(etudid, jour, matin, 0, "", moduleimpl_id)
def annule_absence(etudid, jour, matin, moduleimpl_id=None):
@ -721,7 +722,7 @@ def annule_absence(etudid, jour, matin, moduleimpl_id=None):
invalidate_abs_etud_date(etudid, jour)
def annule_justif(etudid, jour, matin, REQUEST=None):
def annule_justif(etudid, jour, matin):
"Annule un justificatif"
# unpublished
matin = _toboolean(matin)

View File

@ -30,7 +30,7 @@
"""
import datetime
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
from app.scodoc import notesdb as ndb
@ -58,7 +58,6 @@ def doSignaleAbsence(
estjust=False,
description=None,
etudid=False,
REQUEST=None,
): # etudid implied
"""Signalement d'une absence.
@ -69,7 +68,8 @@ def doSignaleAbsence(
demijournee: 2 si journée complète, 1 matin, 0 après-midi
estjust: absence justifiée
description: str
etudid: etudiant concerné. Si non spécifié, cherche dans REQUEST.form
etudid: etudiant concerné. Si non spécifié, cherche dans
les paramètres de la requête courante.
"""
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
etudid = etud["etudid"]
@ -86,7 +86,6 @@ def doSignaleAbsence(
jour,
False,
estjust,
REQUEST,
description_abs,
moduleimpl_id,
)
@ -95,7 +94,6 @@ def doSignaleAbsence(
jour,
True,
estjust,
REQUEST,
description_abs,
moduleimpl_id,
)
@ -106,7 +104,6 @@ def doSignaleAbsence(
jour,
demijournee,
estjust,
REQUEST,
description_abs,
moduleimpl_id,
)
@ -156,7 +153,7 @@ def doSignaleAbsence(
return "\n".join(H)
def SignaleAbsenceEtud(REQUEST=None): # etudid implied
def SignaleAbsenceEtud(): # etudid implied
"""Formulaire individuel simple de signalement d'une absence"""
# brute-force portage from very old dtml code ...
etud = sco_etud.get_etud_info(filled=True)[0]
@ -228,7 +225,6 @@ def SignaleAbsenceEtud(REQUEST=None): # etudid implied
sco_photos.etud_photo_html(
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
"""</a></td></tr></table>""",
"""
@ -281,7 +277,6 @@ def doJustifAbsence(
demijournee,
description=None,
etudid=False,
REQUEST=None,
): # etudid implied
"""Justification d'une absence
@ -291,7 +286,8 @@ def doJustifAbsence(
demijournee: 2 si journée complète, 1 matin, 0 après-midi
estjust: absence justifiée
description: str
etudid: etudiant concerné. Si non spécifié, cherche dans REQUEST.form
etudid: etudiant concerné. Si non spécifié, cherche dans les
paramètres de la requête.
"""
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
etudid = etud["etudid"]
@ -305,14 +301,12 @@ def doJustifAbsence(
etudid=etudid,
jour=jour,
matin=False,
REQUEST=REQUEST,
description=description_abs,
)
sco_abs.add_justif(
etudid=etudid,
jour=jour,
matin=True,
REQUEST=REQUEST,
description=description_abs,
)
nbadded += 2
@ -321,7 +315,6 @@ def doJustifAbsence(
etudid=etudid,
jour=jour,
matin=demijournee,
REQUEST=REQUEST,
description=description_abs,
)
nbadded += 1
@ -357,7 +350,7 @@ def doJustifAbsence(
return "\n".join(H)
def JustifAbsenceEtud(REQUEST=None): # etudid implied
def JustifAbsenceEtud(): # etudid implied
"""Formulaire individuel simple de justification d'une absence"""
# brute-force portage from very old dtml code ...
etud = sco_etud.get_etud_info(filled=True)[0]
@ -376,7 +369,6 @@ def JustifAbsenceEtud(REQUEST=None): # etudid implied
sco_photos.etud_photo_html(
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
"""</a></td></tr></table>""",
"""
@ -412,9 +404,7 @@ Raison: <input type="text" name="description" size="42"/> (optionnel)
return "\n".join(H)
def doAnnuleAbsence(
datedebut, datefin, demijournee, etudid=False, REQUEST=None
): # etudid implied
def doAnnuleAbsence(datedebut, datefin, demijournee, etudid=False): # etudid implied
"""Annulation des absences pour une demi journée"""
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
etudid = etud["etudid"]
@ -462,7 +452,7 @@ autre absence pour <b>%(nomprenom)s</b></a></li>
return "\n".join(H)
def AnnuleAbsenceEtud(REQUEST=None): # etudid implied
def AnnuleAbsenceEtud(): # etudid implied
"""Formulaire individuel simple d'annulation d'une absence"""
# brute-force portage from very old dtml code ...
etud = sco_etud.get_etud_info(filled=True)[0]
@ -482,7 +472,6 @@ def AnnuleAbsenceEtud(REQUEST=None): # etudid implied
sco_photos.etud_photo_html(
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
"""</a></td></tr></table>""",
"""<p>A n'utiliser que suite à une erreur de saisie ou lorsqu'il s'avère que l'étudiant était en fait présent. </p>
@ -548,7 +537,7 @@ def AnnuleAbsenceEtud(REQUEST=None): # etudid implied
return "\n".join(H)
def doAnnuleJustif(datedebut0, datefin0, demijournee, REQUEST=None): # etudid implied
def doAnnuleJustif(datedebut0, datefin0, demijournee): # etudid implied
"""Annulation d'une justification"""
etud = sco_etud.get_etud_info(filled=True)[0]
etudid = etud["etudid"]
@ -558,11 +547,11 @@ def doAnnuleJustif(datedebut0, datefin0, demijournee, REQUEST=None): # etudid i
for jour in dates:
# Attention: supprime matin et après-midi
if demijournee == 2:
sco_abs.annule_justif(etudid, jour, False, REQUEST=REQUEST)
sco_abs.annule_justif(etudid, jour, True, REQUEST=REQUEST)
sco_abs.annule_justif(etudid, jour, False)
sco_abs.annule_justif(etudid, jour, True)
nbadded += 2
else:
sco_abs.annule_justif(etudid, jour, demijournee, REQUEST=REQUEST)
sco_abs.annule_justif(etudid, jour, demijournee)
nbadded += 1
#
H = [
@ -716,7 +705,6 @@ def formChoixSemestreGroupe(all=False):
def CalAbs(etudid, sco_year=None):
"""Calendrier des absences d'un etudiant"""
# crude portage from 1999 DTML
REQUEST = None # XXX
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
etudid = etud["etudid"]
anneescolaire = int(scu.AnneeScolaire(sco_year))
@ -766,7 +754,6 @@ def CalAbs(etudid, sco_year=None):
sco_photos.etud_photo_html(
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
),
CalHTML,
@ -791,7 +778,6 @@ def ListeAbsEtud(
format="html",
absjust_only=0,
sco_year=None,
REQUEST=None,
):
"""Liste des absences d'un étudiant sur l'année en cours
En format 'html': page avec deux tableaux (non justifiées et justifiées).
@ -810,12 +796,12 @@ def ListeAbsEtud(
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
# Liste des absences et titres colonnes tables:
titles, columns_ids, absnonjust, absjust = _TablesAbsEtud(
titles, columns_ids, absnonjust, absjust = _tables_abs_etud(
etudid, datedebut, with_evals=with_evals, format=format
)
if REQUEST:
base_url_nj = "%s?etudid=%s&absjust_only=0" % (REQUEST.URL0, etudid)
base_url_j = "%s?etudid=%s&absjust_only=1" % (REQUEST.URL0, etudid)
if request.base_url:
base_url_nj = "%s?etudid=%s&absjust_only=0" % (request.base_url, etudid)
base_url_j = "%s?etudid=%s&absjust_only=1" % (request.base_url, etudid)
else:
base_url_nj = base_url_j = ""
tab_absnonjust = GenTable(
@ -844,9 +830,9 @@ def ListeAbsEtud(
# Formats non HTML et demande d'une seule table:
if format != "html" and format != "text":
if absjust_only == 1:
return tab_absjust.make_page(format=format, REQUEST=REQUEST)
return tab_absjust.make_page(format=format)
else:
return tab_absnonjust.make_page(format=format, REQUEST=REQUEST)
return tab_absnonjust.make_page(format=format)
if format == "html":
# Mise en forme HTML:
@ -896,13 +882,12 @@ def ListeAbsEtud(
raise ValueError("Invalid format !")
def _TablesAbsEtud(
def _tables_abs_etud(
etudid,
datedebut,
with_evals=True,
format="html",
absjust_only=0,
REQUEST=None,
):
"""Tables des absences justifiees et non justifiees d'un étudiant
sur l'année en cours
@ -928,11 +913,11 @@ def _TablesAbsEtud(
cursor.execute(
"""SELECT mi.moduleimpl_id
FROM absences abs, notes_moduleimpl_inscription mi, notes_moduleimpl m
WHERE abs.matin = %(matin)s
and abs.jour = %(jour)s
and abs.etudid = %(etudid)s
and abs.moduleimpl_id = mi.moduleimpl_id
and mi.moduleimpl_id = m.id
WHERE abs.matin = %(matin)s
and abs.jour = %(jour)s
and abs.etudid = %(etudid)s
and abs.moduleimpl_id = mi.moduleimpl_id
and mi.moduleimpl_id = m.id
and mi.etudid = %(etudid)s
""",
{
@ -959,8 +944,9 @@ def _TablesAbsEtud(
)[0]
if format == "html":
ex.append(
'<a href="Notes/moduleimpl_status?moduleimpl_id=%s">%s</a>'
% (mod["moduleimpl_id"], mod["module"]["code"])
f"""<a href="{url_for('notes.moduleimpl_status',
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
">{mod["module"]["code"]}</a>"""
)
else:
ex.append(mod["module"]["code"])
@ -976,8 +962,9 @@ def _TablesAbsEtud(
)[0]
if format == "html":
ex.append(
'<a href="Notes/moduleimpl_status?moduleimpl_id=%s">%s</a>'
% (mod["moduleimpl_id"], mod["module"]["code"])
f"""<a href="{url_for('notes.moduleimpl_status',
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
">{mod["module"]["code"]}</a>"""
)
else:
ex.append(mod["module"]["code"])

View File

@ -29,37 +29,40 @@
Archives are plain files, stored in
<SCODOC_VAR_DIR>/archives/<deptid>
(where <SCODOC_VAR_DIR> is usually /opt/scodoc-data, and <deptid> a departement id)
<SCODOC_VAR_DIR>/archives/<dept_id>
(where <SCODOC_VAR_DIR> is usually /opt/scodoc-data, and <dept_id> a departement id (int))
Les PV de jurys et documents associés sont stockées dans un sous-repertoire de la forme
<archivedir>/<dept>/<formsemestre_id>/<YYYY-MM-DD-HH-MM-SS>
(formsemestre_id est ici FormSemestre.scodoc7_id ou à défaut FormSemestre.id)
(formsemestre_id est ici FormSemestre.id)
Les documents liés à l'étudiant sont dans
<archivedir>/docetuds/<dept>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
(etudid est ici soit Identite.scodoc7id, soit à défaut Identite.id)
<archivedir>/docetuds/<dept_id>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
(etudid est ici Identite.id)
Les maquettes Apogée pour l'export des notes sont dans
<archivedir>/apo_csv/<dept>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv
<archivedir>/apo_csv/<dept_id>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv
Un répertoire d'archive contient des fichiers quelconques, et un fichier texte nommé _description.txt
qui est une description (humaine, format libre) de l'archive.
"""
import os
import time
import datetime
import glob
import mimetypes
import os
import re
import shutil
import glob
import time
import flask
from flask import g
from flask import g, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
from config import Config
from app import log
from app.models import Departement
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_exceptions import (
AccessDenied,
@ -108,7 +111,8 @@ class BaseArchiver(object):
If directory does not yet exist, create it.
"""
self.initialize()
dept_dir = os.path.join(self.root, g.scodoc_dept)
dept = Departement.query.filter_by(acronym=g.scodoc_dept).first()
dept_dir = os.path.join(self.root, str(dept.id))
try:
scu.GSL.acquire()
if not os.path.isdir(dept_dir):
@ -127,7 +131,8 @@ class BaseArchiver(object):
:return: list of archive oids
"""
self.initialize()
base = os.path.join(self.root, g.scodoc_dept) + os.path.sep
dept = Departement.query.filter_by(acronym=g.scodoc_dept).first()
base = os.path.join(self.root, str(dept.id)) + os.path.sep
dirs = glob.glob(base + "*")
return [os.path.split(x)[1] for x in dirs]
@ -244,31 +249,15 @@ class BaseArchiver(object):
log("reading archive file %s" % fname)
return open(fname, "rb").read()
def get_archived_file(self, REQUEST, oid, archive_name, filename):
def get_archived_file(self, oid, archive_name, filename):
"""Recupere donnees du fichier indiqué et envoie au client"""
# XXX très incomplet: devrait inférer et assigner un type MIME
archive_id = self.get_id_from_name(oid, archive_name)
data = self.get(archive_id, filename)
ext = os.path.splitext(filename.lower())[1]
if ext == ".html" or ext == ".htm":
return data
elif ext == ".xml":
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
return data
elif ext == ".xls":
return sco_excel.send_excel_file(
REQUEST, data, filename, mime=scu.XLS_MIMETYPE
)
elif ext == ".xlsx":
return sco_excel.send_excel_file(
REQUEST, data, filename, mime=scu.XLSX_MIMETYPE
)
elif ext == ".csv":
return scu.sendCSVFile(REQUEST, data, filename)
elif ext == ".pdf":
return scu.sendPDFFile(REQUEST, data, filename)
REQUEST.RESPONSE.setHeader("content-type", "application/octet-stream")
return data # should set mimetype for known files like images
mime = mimetypes.guess_type(filename)[0]
if mime is None:
mime = "application/octet-stream"
return scu.send_file(data, filename, mime=mime)
class SemsArchiver(BaseArchiver):
@ -305,7 +294,7 @@ def do_formsemestre_archive(
from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
sem_archive_id = formsemestre_id
archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
@ -394,9 +383,7 @@ def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]):
(all students or only selected groups)
"""
if not sco_permissions_check.can_edit_pv(formsemestre_id):
raise AccessDenied(
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
)
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if not group_ids:
@ -408,7 +395,6 @@ def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]):
H = [
html_sco_header.html_sem_header(
REQUEST,
"Archiver les PV et résultats du semestre",
sem=sem,
javascripts=sco_groups_view.JAVASCRIPTS,
@ -469,7 +455,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
)
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
cancelbutton="Annuler",
@ -519,7 +505,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
def formsemestre_list_archives(REQUEST, formsemestre_id):
"""Page listing archives"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
sem_archive_id = formsemestre_id
L = []
for archive_id in PVArchive.list_obj_archives(sem_archive_id):
a = {
@ -530,7 +516,7 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
}
L.append(a)
H = [html_sco_header.html_sem_header(REQUEST, "Archive des PV et résultats ", sem)]
H = [html_sco_header.html_sem_header("Archive des PV et résultats ", sem)]
if not L:
H.append("<p>aucune archive enregistrée</p>")
else:
@ -559,11 +545,11 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
return "\n".join(H) + html_sco_header.sco_footer()
def formsemestre_get_archived_file(REQUEST, formsemestre_id, archive_name, filename):
def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
"""Send file to client."""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
return PVArchive.get_archived_file(REQUEST, sem_archive_id, archive_name, filename)
sem_archive_id = formsemestre_id
return PVArchive.get_archived_file(sem_archive_id, archive_name, filename)
def formsemestre_delete_archive(
@ -571,11 +557,9 @@ def formsemestre_delete_archive(
):
"""Delete an archive"""
if not sco_permissions_check.can_edit_pv(formsemestre_id):
raise AccessDenied(
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
)
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
sem_archive_id = formsemestre_id
archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id)

View File

@ -30,7 +30,8 @@
les dossiers d'admission et autres pièces utiles.
"""
import flask
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
from app.scodoc import sco_import_etuds
@ -60,12 +61,12 @@ def can_edit_etud_archive(authuser):
def etud_list_archives_html(REQUEST, etudid):
"""HTML snippet listing archives"""
can_edit = can_edit_etud_archive(REQUEST.AUTHENTICATED_USER)
can_edit = can_edit_etud_archive(current_user)
etuds = sco_etud.get_etud_info(etudid=etudid)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
etud_archive_id = etud["scodoc7_id"] or etudid
etud_archive_id = etudid
L = []
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
a = {
@ -118,7 +119,7 @@ def add_archives_info_to_etud_list(etuds):
"""
for etud in etuds:
l = []
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
etud_archive_id = etud["etudid"]
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
l.append(
"%s (%s)"
@ -133,10 +134,8 @@ def add_archives_info_to_etud_list(etuds):
def etud_upload_file_form(REQUEST, etudid):
"""Page with a form to choose and upload a file, with a description."""
# check permission
if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER):
raise AccessDenied(
"opération non autorisée pour %s" % REQUEST.AUTHENTICATED_USER
)
if not can_edit_etud_archive(current_user):
raise AccessDenied("opération non autorisée pour %s" % current_user)
etuds = sco_etud.get_etud_info(filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
@ -153,7 +152,7 @@ def etud_upload_file_form(REQUEST, etudid):
% (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)),
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("etudid", {"default": etudid, "input_type": "hidden"}),
@ -181,7 +180,7 @@ def etud_upload_file_form(REQUEST, etudid):
data = tf[2]["datafile"].read()
descr = tf[2]["description"]
filename = tf[2]["datafile"].filename
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
etud_archive_id = etud["etudid"]
_store_etud_file_to_new_archive(
etud_archive_id, data, filename, description=descr
)
@ -202,15 +201,13 @@ def _store_etud_file_to_new_archive(etud_archive_id, data, filename, description
def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
"""Delete an archive"""
# check permission
if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER):
raise AccessDenied(
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
)
if not can_edit_etud_archive(current_user):
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
etuds = sco_etud.get_etud_info(filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
etud_archive_id = etud["etudid"]
archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
if not dialog_confirmed:
return scu.confirm_dialog(
@ -242,16 +239,14 @@ def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
)
def etud_get_archived_file(REQUEST, etudid, archive_name, filename):
def etud_get_archived_file(etudid, archive_name, filename):
"""Send file to client."""
etuds = sco_etud.get_etud_info(filled=True)
etuds = sco_etud.get_etud_info(etudid=etudid, filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
return EtudsArchive.get_archived_file(
REQUEST, etud_archive_id, archive_name, filename
)
etud_archive_id = etud["etudid"]
return EtudsArchive.get_archived_file(etud_archive_id, archive_name, filename)
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
@ -271,8 +266,11 @@ def etudarchive_generate_excel_sample(group_id=None, REQUEST=None):
],
extra_cols=["fichier_a_charger"],
)
return sco_excel.send_excel_file(
REQUEST, data, "ImportFichiersEtudiants" + scu.XLSX_SUFFIX
return scu.send_file(
data,
"ImportFichiersEtudiants",
suffix=scu.XLSX_SUFFIX,
mime=scu.XLSX_MIMETYPE,
)
@ -310,7 +308,7 @@ def etudarchive_import_files_form(group_id, REQUEST=None):
]
F = html_sco_header.sco_footer()
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}),

View File

@ -28,6 +28,7 @@
"""Génération des bulletins de notes
"""
from app.models import formsemestre
import time
import pprint
import email
@ -35,11 +36,10 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.header import Header
from reportlab.lib.colors import Color
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
import urllib
from flask import g
from flask import g, request
from flask import url_for
from flask_login import current_user
from flask_mail import Message
@ -48,7 +48,7 @@ import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import AccessDenied
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc import html_sco_header
from app.scodoc import htmlutils
from app.scodoc import sco_abs
@ -121,9 +121,7 @@ def make_context_dict(sem, etud):
return C
def formsemestre_bulletinetud_dict(
formsemestre_id, etudid, version="long", REQUEST=None
):
def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
"""Collecte informations pour bulletin de notes
Retourne un dictionnaire (avec valeur par défaut chaine vide).
Le contenu du dictionnaire dépend des options (rangs, ...)
@ -143,10 +141,7 @@ def formsemestre_bulletinetud_dict(
I["etudid"] = etudid
I["formsemestre_id"] = formsemestre_id
I["sem"] = nt.sem
if REQUEST:
I["server_name"] = REQUEST.BASE0
else:
I["server_name"] = ""
I["server_name"] = request.url_root
# Formation et parcours
I["formation"] = sco_formations.formation_list(
@ -778,7 +773,10 @@ def formsemestre_bulletinetud(
etud = sco_etud.get_etud_info(filled=True)[0]
etudid = etud["etudid"]
except:
return scu.log_unknown_etud(REQUEST, format=format)
sco_etud.log_unknown_etud()
raise ScoValueError("étudiant inconnu")
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
bulletin = do_formsemestre_bulletinetud(
formsemestre_id,
@ -791,7 +789,8 @@ def formsemestre_bulletinetud(
REQUEST=REQUEST,
)[0]
if format not in {"html", "pdfmail"}:
return bulletin
filename = scu.bul_filename(sem, etud, format)
return scu.send_file(bulletin, filename, mime=scu.get_mime_suffix(format)[0])
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
@ -862,14 +861,13 @@ def do_formsemestre_bulletinetud(
):
"""Génère le bulletin au format demandé.
Retourne: (bul, filigranne)
bul est au format demandé (html, pdf, pdfmail, pdfpart, xml)
bul est str ou bytes au format demandé (html, pdf, pdfmail, pdfpart, xml, json)
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
"""
if format == "xml":
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
formsemestre_id,
etudid,
REQUEST=REQUEST,
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
version=version,
@ -881,19 +879,18 @@ def do_formsemestre_bulletinetud(
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
formsemestre_id,
etudid,
REQUEST=REQUEST,
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
version=version,
)
return bul, ""
I = formsemestre_bulletinetud_dict(formsemestre_id, etudid, REQUEST=REQUEST)
I = formsemestre_bulletinetud_dict(formsemestre_id, etudid)
etud = I["etud"]
if format == "html":
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
I, version=version, format="html", REQUEST=REQUEST
I, version=version, format="html"
)
return htm, I["filigranne"]
@ -903,11 +900,10 @@ def do_formsemestre_bulletinetud(
version=version,
format="pdf",
stand_alone=(format != "pdfpart"),
REQUEST=REQUEST,
)
if format == "pdf":
return (
scu.sendPDFFile(REQUEST, bul, filename),
scu.sendPDFFile(bul, filename),
I["filigranne"],
) # unused ret. value
else:
@ -923,11 +919,11 @@ def do_formsemestre_bulletinetud(
htm = "" # speed up if html version not needed
else:
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
I, version=version, format="html", REQUEST=REQUEST
I, version=version, format="html"
)
pdfdata, filename = sco_bulletins_generator.make_formsemestre_bulletinetud(
I, version=version, format="pdf", REQUEST=REQUEST
I, version=version, format="pdf"
)
if prefer_mail_perso:
@ -998,7 +994,6 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
# Attach pdf
msg.attach(filename, scu.PDF_MIMETYPE, pdfdata)
log("mail bulletin a %s" % recipient_addr)
email.send_message(msg)
@ -1033,7 +1028,7 @@ def _formsemestre_bulletinetud_header_html(
),
"""
<form name="f" method="GET" action="%s">"""
% REQUEST.URL0,
% request.base_url,
f"""Bulletin <span class="bull_liensemestre"><a href="{
url_for("notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
@ -1063,14 +1058,20 @@ def _formsemestre_bulletinetud_header_html(
H.append("""</select></td>""")
# Menu
endpoint = "notes.formsemestre_bulletinetud"
url = REQUEST.URL0
qurl = six.moves.urllib.parse.quote_plus(url + "?" + REQUEST.QUERY_STRING)
menuBul = [
{
"title": "Réglages bulletins",
"endpoint": "notes.formsemestre_edit_options",
"args": {"formsemestre_id": formsemestre_id, "target_url": qurl},
"args": {
"formsemestre_id": formsemestre_id,
# "target_url": url_for(
# "notes.formsemestre_bulletinetud",
# scodoc_dept=g.scodoc_dept,
# formsemestre_id=formsemestre_id,
# etudid=etudid,
# ),
},
"enabled": (current_user.id in sem["responsables"])
or current_user.has_permission(Permission.ScoImplement),
},
@ -1113,6 +1114,16 @@ def _formsemestre_bulletinetud_header_html(
"enabled": etud["emailperso"]
and can_send_bulletin_by_mail(formsemestre_id),
},
{
"title": "Version json",
"endpoint": endpoint,
"args": {
"formsemestre_id": formsemestre_id,
"etudid": etudid,
"version": version,
"format": "json",
},
},
{
"title": "Version XML",
"endpoint": endpoint,
@ -1188,9 +1199,14 @@ def _formsemestre_bulletinetud_header_html(
H.append(
'<td> <a href="%s">%s</a></td>'
% (
url
+ "?formsemestre_id=%s&etudid=%s&format=pdf&version=%s"
% (formsemestre_id, etudid, version),
url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
etudid=etudid,
format="pdf",
version=version,
),
scu.ICON_PDF,
)
)
@ -1201,9 +1217,7 @@ def _formsemestre_bulletinetud_header_html(
"""
% (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_html(
etud, title="fiche de " + etud["nom"], REQUEST=REQUEST
),
sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"]),
)
)
H.append(

View File

@ -52,6 +52,9 @@ import reportlab
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
from flask import request
from flask_login import current_user
from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import NoteProcessError
from app import log
@ -148,14 +151,7 @@ class BulletinGenerator(object):
def get_filename(self):
"""Build a filename to be proposed to the web client"""
sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"])
dt = time.strftime("%Y-%m-%d")
filename = "bul-%s-%s-%s.pdf" % (
sem["titre_num"],
dt,
self.infos["etud"]["nom"],
)
filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "")
return filename
return scu.bul_filename(sem, self.infos["etud"], "pdf")
def generate(self, format="", stand_alone=True):
"""Return bulletin in specified format"""
@ -260,7 +256,6 @@ def make_formsemestre_bulletinetud(
version="long", # short, long, selectedevals
format="pdf", # html, pdf
stand_alone=True,
REQUEST=None,
):
"""Bulletin de notes
@ -286,10 +281,10 @@ def make_formsemestre_bulletinetud(
PDFLOCK.acquire()
bul_generator = gen_class(
infos,
authuser=REQUEST.AUTHENTICATED_USER,
authuser=current_user,
version=version,
filigranne=infos["filigranne"],
server_name=REQUEST.BASE0,
server_name=request.url_root,
)
if format not in bul_generator.supported_formats:
# use standard generator
@ -301,10 +296,10 @@ def make_formsemestre_bulletinetud(
gen_class = bulletin_get_class(bul_class_name)
bul_generator = gen_class(
infos,
authuser=REQUEST.AUTHENTICATED_USER,
authuser=current_user,
version=version,
filigranne=infos["filigranne"],
server_name=REQUEST.BASE0,
server_name=request.url_root,
)
data = bul_generator.generate(format=format, stand_alone=stand_alone)

View File

@ -47,27 +47,22 @@ from app.scodoc import sco_etud
def make_json_formsemestre_bulletinetud(
formsemestre_id,
etudid,
REQUEST=None,
formsemestre_id: int,
etudid: int,
xml_with_decisions=False,
version="long",
force_publishing=False, # force publication meme si semestre non publie sur "portail"
):
) -> str:
"""Renvoie bulletin en chaine JSON"""
d = formsemestre_bulletinetud_published_dict(
formsemestre_id,
etudid,
force_publishing=force_publishing,
REQUEST=REQUEST,
xml_with_decisions=xml_with_decisions,
version=version,
)
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE)
return json.dumps(d, cls=scu.ScoDocJSONEncoder)
@ -79,7 +74,6 @@ def formsemestre_bulletinetud_published_dict(
etudid,
force_publishing=False,
xml_nodate=False,
REQUEST=None,
xml_with_decisions=False, # inclue les decisions même si non publiées
version="long",
):

View File

@ -58,7 +58,7 @@ import traceback
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
from flask import g, url_for
from flask import g, url_for, request
import app.scodoc.sco_utils as scu
from app import log
@ -193,7 +193,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, REQUEST, version="selectedev
#
infos = {"DeptName": sco_preferences.get_preference("DeptName", formsemestre_id)}
if REQUEST:
server_name = REQUEST.BASE0
server_name = request.url_root
else:
server_name = ""
try:
@ -243,7 +243,7 @@ def get_etud_bulletins_pdf(etudid, REQUEST, version="selectedevals"):
i = i + 1
infos = {"DeptName": sco_preferences.get_preference("DeptName")}
if REQUEST:
server_name = REQUEST.BASE0
server_name = request.url_root
else:
server_name = ""
try:

View File

@ -69,16 +69,13 @@ def make_xml_formsemestre_bulletinetud(
doc=None, # XML document
force_publishing=False,
xml_nodate=False,
REQUEST=None,
xml_with_decisions=False, # inclue les decisions même si non publiées
version="long",
):
) -> str:
"bulletin au format XML"
from app.scodoc import sco_bulletins
log("xml_bulletin( formsemestre_id=%s, etudid=%s )" % (formsemestre_id, etudid))
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if (not sem["bul_hide_xml"]) or force_publishing:

View File

@ -30,6 +30,8 @@
(coût théorique en heures équivalent TD)
"""
from flask import request
import app.scodoc.sco_utils as scu
from app.scodoc.gen_tables import GenTable
from app.scodoc import sco_formsemestre
@ -182,7 +184,7 @@ def formsemestre_estim_cost(
<br/>
</form>
""" % (
REQUEST.URL0,
request.base_url,
formsemestre_id,
n_group_td,
n_group_tp,
@ -190,11 +192,11 @@ def formsemestre_estim_cost(
)
tab.html_before_table = h
tab.base_url = "%s?formsemestre_id=%s&n_group_td=%s&n_group_tp=%s&coef_tp=%s" % (
REQUEST.URL0,
request.base_url,
formsemestre_id,
n_group_td,
n_group_tp,
coef_tp,
)
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)

View File

@ -29,7 +29,7 @@
Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudiant
"""
import http
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -47,10 +47,10 @@ from app.scodoc import sco_etud
import sco_version
def report_debouche_date(start_year=None, format="html", REQUEST=None):
def report_debouche_date(start_year=None, format="html"):
"""Rapport (table) pour les débouchés des étudiants sortis à partir de l'année indiquée."""
if not start_year:
return report_debouche_ask_date(REQUEST=REQUEST)
return report_debouche_ask_date()
if format == "xls":
keep_numeric = True # pas de conversion des notes en strings
else:
@ -64,13 +64,12 @@ def report_debouche_date(start_year=None, format="html", REQUEST=None):
"Généré par %s le " % sco_version.SCONAME + scu.timedate_human_repr() + ""
)
tab.caption = "Récapitulatif débouchés à partir du 1/1/%s." % start_year
tab.base_url = "%s?start_year=%s" % (REQUEST.URL0, start_year)
tab.base_url = "%s?start_year=%s" % (request.base_url, start_year)
return tab.make_page(
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
init_qtip=True,
javascripts=["js/etud_info.js"],
format=format,
REQUEST=REQUEST,
with_html_headers=True,
)
@ -194,7 +193,7 @@ def table_debouche_etudids(etudids, keep_numeric=True):
return tab
def report_debouche_ask_date(REQUEST=None):
def report_debouche_ask_date():
"""Formulaire demande date départ"""
return (
html_sco_header.sco_header()
@ -249,7 +248,7 @@ def itemsuivi_get(cnx, itemsuivi_id, ignore_errors=False):
return None
def itemsuivi_suppress(itemsuivi_id, REQUEST=None):
def itemsuivi_suppress(itemsuivi_id):
"""Suppression d'un item"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
@ -259,9 +258,10 @@ def itemsuivi_suppress(itemsuivi_id, REQUEST=None):
_itemsuivi_delete(cnx, itemsuivi_id)
logdb(cnx, method="itemsuivi_suppress", etudid=item["etudid"])
log("suppressed itemsuivi %s" % (itemsuivi_id,))
return ("", 204)
def itemsuivi_create(etudid, item_date=None, situation="", REQUEST=None, format=None):
def itemsuivi_create(etudid, item_date=None, situation="", format=None):
"""Creation d'un item"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
@ -273,11 +273,11 @@ def itemsuivi_create(etudid, item_date=None, situation="", REQUEST=None, format=
log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
item = itemsuivi_get(cnx, itemsuivi_id)
if format == "json":
return scu.sendJSON(REQUEST, item)
return scu.sendJSON(item)
return item
def itemsuivi_set_date(itemsuivi_id, item_date, REQUEST=None):
def itemsuivi_set_date(itemsuivi_id, item_date):
"""set item date
item_date is a string dd/mm/yyyy
"""
@ -288,9 +288,10 @@ def itemsuivi_set_date(itemsuivi_id, item_date, REQUEST=None):
item = itemsuivi_get(cnx, itemsuivi_id)
item["item_date"] = item_date
_itemsuivi_edit(cnx, item)
return ("", 204)
def itemsuivi_set_situation(object, value, REQUEST=None):
def itemsuivi_set_situation(object, value):
"""set situation"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
@ -304,14 +305,14 @@ def itemsuivi_set_situation(object, value, REQUEST=None):
return situation or scu.IT_SITUATION_MISSING_STR
def itemsuivi_list_etud(etudid, format=None, REQUEST=None):
def itemsuivi_list_etud(etudid, format=None):
"""Liste des items pour cet étudiant, avec tags"""
cnx = ndb.GetDBConnexion()
items = _itemsuivi_list(cnx, {"etudid": etudid})
for it in items:
it["tags"] = ", ".join(itemsuivi_tag_list(it["itemsuivi_id"]))
if format == "json":
return scu.sendJSON(REQUEST, items)
return scu.sendJSON(items)
return items
@ -328,7 +329,7 @@ def itemsuivi_tag_list(itemsuivi_id):
return [x["title"] for x in r]
def itemsuivi_tag_search(term, REQUEST=None):
def itemsuivi_tag_search(term):
"""List all used tag names (for auto-completion)"""
# restrict charset to avoid injections
if not scu.ALPHANUM_EXP.match(term):
@ -343,10 +344,10 @@ def itemsuivi_tag_search(term, REQUEST=None):
)
data = [x["title"] for x in r]
return scu.sendJSON(REQUEST, data)
return scu.sendJSON(data)
def itemsuivi_tag_set(itemsuivi_id="", taglist=[], REQUEST=None):
def itemsuivi_tag_set(itemsuivi_id="", taglist=None):
"""taglist may either be:
a string with tag names separated by commas ("un;deux")
or a list of strings (["un", "deux"])

View File

@ -28,7 +28,7 @@
"""Page accueil département (liste des semestres, etc)
"""
from flask import g
from flask import g, request
from flask_login import current_user
import app
@ -131,7 +131,7 @@ def index_html(REQUEST=None, showcodes=0, showsemtable=0):
if not showsemtable:
H.append(
'<hr/><p><a href="%s?showsemtable=1">Voir tous les semestres</a></p>'
% REQUEST.URL0
% request.base_url
)
H.append(
@ -242,7 +242,7 @@ def _sem_table_gt(sems, showcodes=False):
rows=sems,
html_class="table_leftalign semlist",
html_sortable=True,
# base_url = '%s?formsemestre_id=%s' % (REQUEST.URL0, formsemestre_id),
# base_url = '%s?formsemestre_id=%s' % (request.base_url, formsemestre_id),
# caption='Maquettes enregistrées',
preferences=sco_preferences.SemPreferences(),
)

View File

@ -51,6 +51,7 @@ import fcntl
import subprocess
import requests
from flask_login import current_user
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -64,7 +65,7 @@ from app.scodoc.sco_exceptions import ScoValueError
SCO_DUMP_LOCK = "/tmp/scodump.lock"
def sco_dump_and_send_db(REQUEST=None):
def sco_dump_and_send_db():
"""Dump base de données et l'envoie anonymisée pour debug"""
H = [html_sco_header.sco_header(page_title="Assistance technique")]
# get currect (dept) DB name:
@ -93,7 +94,7 @@ def sco_dump_and_send_db(REQUEST=None):
_anonymize_db(ano_db_name)
# Send
r = _send_db(REQUEST, ano_db_name)
r = _send_db(ano_db_name)
if (
r.status_code
== requests.codes.INSUFFICIENT_STORAGE # pylint: disable=no-member
@ -171,29 +172,27 @@ def _get_scodoc_serial():
return 0
def _send_db(REQUEST, ano_db_name):
def _send_db(ano_db_name):
"""Dump this (anonymized) database and send it to tech support"""
log("dumping anonymized database {}".format(ano_db_name))
log(f"dumping anonymized database {ano_db_name}")
try:
data = subprocess.check_output("pg_dump {} | gzip".format(ano_db_name), shell=1)
except subprocess.CalledProcessError as e:
log("sco_dump_and_send_db: exception in anonymisation: {}".format(e))
raise ScoValueError(
"erreur lors de l'anonymisation de la base {}".format(ano_db_name)
dump = subprocess.check_output(
f"pg_dump --format=custom {ano_db_name}", shell=1
)
except subprocess.CalledProcessError as e:
log(f"sco_dump_and_send_db: exception in anonymisation: {e}")
raise ScoValueError(f"erreur lors de l'anonymisation de la base {ano_db_name}")
log("uploading anonymized dump...")
files = {"file": (ano_db_name + ".gz", data)}
files = {"file": (ano_db_name + ".dump", dump)}
r = requests.post(
scu.SCO_DUMP_UP_URL,
files=files,
data={
"dept_name": sco_preferences.get_preference("DeptName"),
"serial": _get_scodoc_serial(),
"sco_user": str(REQUEST.AUTHENTICATED_USER),
"sent_by": sco_users.user_info(str(REQUEST.AUTHENTICATED_USER))[
"nomcomplet"
],
"sco_user": str(current_user),
"sent_by": sco_users.user_info(str(current_user))["nomcomplet"],
"sco_version": sco_version.SCOVERSION,
"sco_fullversion": scu.get_scodoc_version(),
},

View File

@ -29,7 +29,7 @@
(portage from DTML)
"""
import flask
from flask import g, url_for
from flask import g, url_for, request
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -47,7 +47,7 @@ from app.scodoc import sco_formsemestre
from app.scodoc import sco_news
def formation_delete(formation_id=None, dialog_confirmed=False, REQUEST=None):
def formation_delete(formation_id=None, dialog_confirmed=False):
"""Delete a formation"""
F = sco_formations.formation_list(args={"formation_id": formation_id})
if not F:
@ -159,7 +159,7 @@ def formation_edit(formation_id=None, create=False, REQUEST=None):
)
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("formation_id", {"default": formation_id, "input_type": "hidden"}),
@ -311,7 +311,7 @@ def invalidate_sems_in_formation(formation_id):
) # > formation modif.
def module_move(module_id, after=0, REQUEST=None, redirect=1):
def module_move(module_id, after=0, redirect=1):
"""Move before/after previous one (decrement/increment numero)"""
module = sco_edit_module.do_module_list({"module_id": module_id})[0]
redirect = int(redirect)

View File

@ -29,7 +29,7 @@
(portage from DTML)
"""
import flask
from flask import g, url_for
from flask import g, url_for, request
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -116,7 +116,7 @@ associé.
</p>""",
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("ue_id", {"input_type": "hidden", "default": ue_id}),
@ -202,7 +202,7 @@ def matiere_delete(matiere_id=None, REQUEST=None):
]
dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(UE["formation_id"])
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(("matiere_id", {"input_type": "hidden"}),),
initvalues=M,
@ -256,7 +256,7 @@ des notes.</em>
associé.
</p>"""
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("matiere_id", {"input_type": "hidden"}),
@ -323,4 +323,4 @@ def matiere_is_locked(matiere_id):
""",
{"matiere_id": matiere_id},
)
return len(r) > 0
return len(r) > 0

View File

@ -29,7 +29,8 @@
(portage from DTML)
"""
import flask
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -143,7 +144,7 @@ def module_create(matiere_id=None, REQUEST=None):
else:
default_num = 10
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
(
@ -258,12 +259,13 @@ def do_module_delete(oid):
# S'il y a des moduleimpls, on ne peut pas detruire le module !
mods = sco_moduleimpl.do_moduleimpl_list(module_id=oid)
if mods:
err_page = scu.confirm_dialog(
message="""<h3>Destruction du module impossible car il est utilisé dans des semestres existants !</h3>""",
helpmsg="""Il faut d'abord supprimer le semestre. Mais il est peut être préférable de laisser ce programme intact et d'en créer une nouvelle version pour la modifier.""",
dest_url="ue_list",
parameters={"formation_id": mod["formation_id"]},
)
err_page = f"""<h3>Destruction du module impossible car il est utilisé dans des semestres existants !</h3>
<p class="help">Il faut d'abord supprimer le semestre. Mais il est peut être préférable de
laisser ce programme intact et d'en créer une nouvelle version pour la modifier.
</p>
<a href="{url_for('notes.ue_list', scodoc_dept=g.scodoc_dept,
formation_id=mod["formation_id"])}">reprendre</a>
"""
raise ScoGenError(err_page)
# delete
cnx = ndb.GetDBConnexion()
@ -294,7 +296,7 @@ def module_delete(module_id=None, REQUEST=None):
dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(Mod["formation_id"])
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(("module_id", {"input_type": "hidden"}),),
initvalues=Mod,
@ -388,7 +390,7 @@ def module_edit(module_id=None, REQUEST=None):
)
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
(
@ -544,7 +546,7 @@ def module_list(formation_id, REQUEST=None):
% F,
'<ul class="notes_module_list">',
]
editable = REQUEST.AUTHENTICATED_USER.has_permission(Permission.ScoChangeFormation)
editable = current_user.has_permission(Permission.ScoChangeFormation)
for Mod in do_module_list(args={"formation_id": formation_id}):
H.append('<li class="notes_module_list">%s' % Mod)

View File

@ -29,7 +29,7 @@
"""
import flask
from flask import g, url_for
from flask import g, url_for, request
from flask_login import current_user
import app.scodoc.notesdb as ndb
@ -326,7 +326,11 @@ def ue_edit(ue_id=None, create=False, formation_id=None, REQUEST=None):
)
)
tf = TrivialFormulator(
REQUEST.URL0, REQUEST.form, fw, initvalues=initvalues, submitlabel=submitlabel
request.base_url,
REQUEST.form,
fw,
initvalues=initvalues,
submitlabel=submitlabel,
)
if tf[0] == 0:
X = """<div id="ue_list_code"></div>
@ -1033,13 +1037,13 @@ def formation_table_recap(formation_id, format="html", REQUEST=None):
caption=title,
html_caption=title,
html_class="table_leftalign",
base_url="%s?formation_id=%s" % (REQUEST.URL0, formation_id),
base_url="%s?formation_id=%s" % (request.base_url, formation_id),
page_title=title,
html_title="<h2>" + title + "</h2>",
pdf_title=title,
preferences=sco_preferences.SemPreferences(),
)
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
def ue_list_semestre_ids(ue):

View File

@ -149,7 +149,7 @@ def group_edt_json(group_id, start="", end="", REQUEST=None): # actuellement in
}
J.append(d)
return scu.sendJSON(REQUEST, J)
return scu.sendJSON(J)
"""XXX

View File

@ -32,11 +32,11 @@
Voir sco_apogee_csv.py pour la structure du fichier Apogée.
Stockage: utilise sco_archive.py
=> /opt/scodoc/var/scodoc/archives/apo_csv/RT/2016-1/2016-07-03-16-12-19/V3ASR.csv
=> /opt/scodoc/var/scodoc/archives/apo_csv/<dept_id>/2016-1/2016-07-03-16-12-19/V3ASR.csv
pour une maquette de l'année scolaire 2016, semestre 1, etape V3ASR
ou bien (à partir de ScoDoc 1678) :
/opt/scodoc/var/scodoc/archives/apo_csv/RT/2016-1/2016-07-03-16-12-19/V3ASR!111.csv
/opt/scodoc/var/scodoc/archives/apo_csv/<dept_id>/2016-1/2016-07-03-16-12-19/V3ASR!111.csv
pour une maquette de l'étape V3ASR version VDI 111.
La version VDI sera ignorée sauf si elle est indiquée dans l'étape du semestre.

View File

@ -32,7 +32,7 @@ import io
from zipfile import ZipFile
import flask
from flask import url_for, g, send_file
from flask import url_for, g, send_file, request
# from werkzeug.utils import send_file
@ -250,7 +250,7 @@ def apo_semset_maq_status(
"""<form name="f" method="get" action="%s">
<input type="hidden" name="semset_id" value="%s"></input>
<div><input type="checkbox" name="allow_missing_apo" value="1" onchange="document.f.submit()" """
% (REQUEST.URL0, semset_id)
% (request.base_url, semset_id)
)
if allow_missing_apo:
H.append("checked")
@ -476,7 +476,7 @@ def table_apo_csv_list(semset, REQUEST=None):
rows=T,
html_class="table_leftalign apo_maq_list",
html_sortable=True,
# base_url = '%s?formsemestre_id=%s' % (REQUEST.URL0, formsemestre_id),
# base_url = '%s?formsemestre_id=%s' % (request.base_url, formsemestre_id),
# caption='Maquettes enregistrées',
preferences=sco_preferences.SemPreferences(),
)
@ -578,7 +578,7 @@ def _view_etuds_page(
preferences=sco_preferences.SemPreferences(),
)
if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
H.append(tab.html())
@ -678,7 +678,8 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
sem_id = semset["sem_id"]
csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
if format == "raw":
return scu.sendCSVFile(REQUEST, csv_data, etape_apo + ".txt")
scu.send_file(csv_data, etape_apo, suffix=".txt", mime=scu.CSV_MIMETYPE)
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
(
@ -746,14 +747,15 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
rows=etuds,
html_sortable=True,
html_class="table_leftalign apo_maq_table",
base_url="%s?etape_apo=%s&semset_id=%s" % (REQUEST.URL0, etape_apo, semset_id),
base_url="%s?etape_apo=%s&semset_id=%s"
% (request.base_url, etape_apo, semset_id),
filename="students_" + etape_apo,
caption="Etudiants Apogée en " + etape_apo,
preferences=sco_preferences.SemPreferences(),
)
if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
H += [
tab.html(),
@ -768,7 +770,7 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
return "\n".join(H)
# called from Web
# called from Web (GET)
def apo_csv_export_results(
semset_id,
block_export_res_etape=False,

View File

@ -31,27 +31,21 @@
# Ancien module "scolars"
import os
import time
from flask import url_for, g, request
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header
from email.mime.base import MIMEBase
from operator import itemgetter
from flask import url_for, g, request
from flask_mail import Message
from app import email
from app import log
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import SCO_ENCODING
import app.scodoc.notesdb as ndb
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
from app import log
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc import safehtml
from app.scodoc import sco_preferences
from app.scodoc.scolog import logdb
from flask_mail import Message
from app import mail
from app.scodoc.TrivialFormulator import TrivialFormulator
MONTH_NAMES_ABBREV = [
"Jan ",
@ -256,7 +250,6 @@ _identiteEditor = ndb.EditableTable(
"photo_filename",
"code_ine",
"code_nip",
"scodoc7_id",
),
filter_dept=True,
sortkey="nom",
@ -345,31 +338,33 @@ def _check_duplicate_code(
)
if etudid:
OK = "retour à la fiche étudiant"
dest_url = "ficheEtud"
dest_endpoint = "scolar.ficheEtud"
parameters = {"etudid": etudid}
else:
if "tf_submitted" in args:
del args["tf_submitted"]
OK = "Continuer"
dest_url = "etudident_create_form"
dest_endpoint = "scolar.etudident_create_form"
parameters = args
else:
OK = "Annuler"
dest_url = ""
dest_endpoint = "notes.index_html"
parameters = {}
if not disable_notify:
err_page = scu.confirm_dialog(
message="""<h3>Code étudiant (%s) dupliqué !</h3>""" % code_name,
helpmsg="""Le %s %s est déjà utilisé: un seul étudiant peut avoir ce code. Vérifier votre valeur ou supprimer l'autre étudiant avec cette valeur.<p><ul><li>"""
% (code_name, args[code_name])
+ "</li><li>".join(listh)
+ "</li></ul><p>",
OK=OK,
dest_url=dest_url,
parameters=parameters,
)
err_page = f"""<h3><h3>Code étudiant ({code_name}) dupliqué !</h3>
<p class="help">Le {code_name} {args[code_name]} est déjà utilisé: un seul étudiant peut avoir
ce code. Vérifier votre valeur ou supprimer l'autre étudiant avec cette valeur.
</p>
<ul><li>
{ '</li><li>'.join(listh) }
</li></ul>
<p>
<a href="{ url_for(dest_endpoint, scodoc_dept=g.scodoc_dept, **parameters) }
">{OK}</a>
</p>
"""
else:
err_page = """<h3>Code étudiant (%s) dupliqué !</h3>""" % code_name
err_page = f"""<h3>Code étudiant ({code_name}) dupliqué !</h3>"""
log("*** error: code %s duplique: %s" % (code_name, args[code_name]))
raise ScoGenError(err_page)
@ -456,7 +451,7 @@ def notify_etud_change(email_addr, etud, before, after, subject):
log("notify_etud_change: sending notification to %s" % email_addr)
log("notify_etud_change: subject: %s" % subject)
log(txt)
mail.send_email(
email.send_email(
subject, sco_preferences.get_preference("email_from_addr"), [email_addr], txt
)
return txt
@ -559,7 +554,6 @@ _admissionEditor = ndb.EditableTable(
"villelycee",
"codepostallycee",
"codelycee",
"debouche",
"type_admission",
"boursier_prec",
),
@ -656,6 +650,12 @@ def make_etud_args(etudid=None, code_nip=None, use_request=True, raise_exc=True)
return args
def log_unknown_etud():
"""Log request: cas ou getEtudInfo n'a pas ramene de resultat"""
etud_args = make_etud_args(raise_exc=False)
log(f"unknown student: args={etud_args}")
def get_etud_info(etudid=False, code_nip=False, filled=False) -> list:
"""infos sur un etudiant (API). If not foud, returns empty list.
On peut specifier etudid ou code_nip

View File

@ -31,7 +31,7 @@ import datetime
import operator
import pprint
import time
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
import urllib
import flask
from flask import url_for
@ -274,13 +274,13 @@ def do_evaluation_create(
if args["jour"]:
next_eval = None
t = (
ndb.DateDMYtoISO(args["jour"]),
ndb.TimetoISO8601(args["heure_debut"]),
ndb.DateDMYtoISO(args["jour"], null_is_empty=True),
ndb.TimetoISO8601(args["heure_debut"], null_is_empty=True),
)
for e in ModEvals:
if (
ndb.DateDMYtoISO(e["jour"]),
ndb.TimetoISO8601(e["heure_debut"]),
ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
ndb.TimetoISO8601(e["heure_debut"], null_is_empty=True),
) > t:
next_eval = e
break
@ -780,7 +780,6 @@ def formsemestre_evaluations_cal(formsemestre_id, REQUEST=None):
H = [
html_sco_header.html_sem_header(
REQUEST,
"Evaluations du semestre",
sem,
cssstyles=["css/calabs.css"],
@ -915,13 +914,13 @@ def formsemestre_evaluations_delai_correction(
html_title="<h2>Correction des évaluations du semestre</h2>",
caption="Correction des évaluations du semestre",
preferences=sco_preferences.SemPreferences(formsemestre_id),
base_url="%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id),
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
origin="Généré par %s le " % sco_version.SCONAME
+ scu.timedate_human_repr()
+ "",
filename=scu.make_filename("evaluations_delais_" + sem["titreannee"]),
)
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
def module_evaluation_insert_before(ModEvals, next_eval):
@ -1089,7 +1088,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
% (
scu.ScoURL(),
group_id,
six.moves.urllib.parse.quote(E["jour"], safe=""),
urllib.parse.quote(E["jour"], safe=""),
)
)
H.append(
@ -1346,7 +1345,7 @@ def evaluation_create_form(
),
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
form,
cancelbutton="Annuler",

View File

@ -35,11 +35,11 @@ from enum import Enum
from tempfile import NamedTemporaryFile
import openpyxl.utils.datetime
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL
from openpyxl.comments import Comment
from openpyxl import Workbook, load_workbook
from openpyxl.cell import WriteOnlyCell
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL
from openpyxl.comments import Comment
import app.scodoc.sco_utils as scu
from app.scodoc import notesdb
@ -59,24 +59,9 @@ class COLORS(Enum):
LIGHT_YELLOW = "FFFFFF99"
def send_excel_file(request, data, filename, mime=scu.XLSX_MIMETYPE):
"""publication fichier.
(on ne doit rien avoir émis avant, car ici sont générés les entetes)
"""
filename = (
scu.unescape_html(scu.suppress_accents(filename))
.replace("&", "")
.replace(" ", "_")
)
request.RESPONSE.setHeader("content-type", mime)
request.RESPONSE.setHeader(
"content-disposition", 'attachment; filename="%s"' % filename
)
return data
# Un style est enregistré comme un dictionnaire qui précise la valeur d'un attributdans la liste suivante:
# font, border, number_format, fill, .. (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
# font, border, number_format, fill,...
# (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
def xldate_as_datetime(xldate, datemode=0):
@ -86,6 +71,17 @@ def xldate_as_datetime(xldate, datemode=0):
return openpyxl.utils.datetime.from_ISO8601(xldate)
def adjust_sheetname(sheet_name):
"""Renvoie un nom convenable pour une feuille excel: < 31 cars, sans caractères spéciaux
Le / n'est pas autorisé par exemple.
Voir https://xlsxwriter.readthedocs.io/workbook.html#add_worksheet
"""
sheet_name = scu.make_filename(sheet_name)
# Le nom de la feuille ne peut faire plus de 31 caractères.
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
return sheet_name[:31]
class ScoExcelBook:
"""Permet la génération d'un classeur xlsx composé de plusieurs feuilles.
usage:
@ -98,13 +94,16 @@ class ScoExcelBook:
def __init__(self):
self.sheets = [] # list of sheets
self.wb = Workbook(write_only=True)
def create_sheet(self, sheet_name="feuille", default_style=None):
"""Crée une nouvelle feuille dans ce classeur
sheet_name -- le nom de la feuille
default_style -- le style par défaut
"""
sheet = ScoExcelSheet(sheet_name, default_style)
sheet_name = adjust_sheetname(sheet_name)
ws = self.wb.create_sheet(sheet_name)
sheet = ScoExcelSheet(sheet_name, default_style, ws)
self.sheets.append(sheet)
return sheet
@ -112,12 +111,12 @@ class ScoExcelBook:
"""génération d'un stream binaire représentant la totalité du classeur.
retourne le flux
"""
wb = Workbook(write_only=True)
for sheet in self.sheets:
sheet.generate(self)
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
sheet.prepare()
# construction d'un flux
# (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
with NamedTemporaryFile() as tmp:
wb.save(tmp.name)
self.wb.save(tmp.name)
tmp.seek(0)
return tmp.read()
@ -125,6 +124,7 @@ class ScoExcelBook:
def excel_make_style(
bold=False,
italic=False,
outline=False,
color: COLORS = COLORS.BLACK,
bgcolor: COLORS = None,
halign=None,
@ -145,7 +145,14 @@ def excel_make_style(
size -- taille de police
"""
style = {}
font = Font(name=font_name, bold=bold, italic=italic, color=color.value, size=size)
font = Font(
name=font_name,
bold=bold,
italic=italic,
outline=outline,
color=color.value,
size=size,
)
style["font"] = font
if bgcolor:
style["fill"] = PatternFill(fill_type="solid", fgColor=bgcolor.value)
@ -182,41 +189,93 @@ class ScoExcelSheet:
"""
def __init__(self, sheet_name="feuille", default_style=None, wb=None):
"""Création de la feuille.
sheet_name -- le nom de la feuille
default_style -- le style par défaut des cellules
wb -- le WorkBook dans laquelle se trouve la feuille. Si wb est None (cas d'un classeur mono-feuille),
un workbook est crée et associé à cette feuille.
"""Création de la feuille. sheet_name
-- le nom de la feuille default_style
-- le style par défaut des cellules ws
-- None si la feuille est autonome (dans ce cas ell crée son propre wb), sinon c'est la worksheet
créée par le workbook propriétaire un workbook est crée et associé à cette feuille.
"""
# Le nom de la feuille ne peut faire plus de 31 caractères.
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
self.sheet_name = sheet_name[
:31
] # if len(sheet_name) > 31: sheet_name = 'Feuille' ?
self.rows = [] # list of list of cells
# self.cells_styles_lico = {} # { (li,co) : style }
# self.cells_styles_li = {} # { li : style }
# self.cells_styles_co = {} # { co : style }
self.sheet_name = adjust_sheetname(sheet_name)
if default_style is None:
default_style = excel_make_style()
self.default_style = default_style
self.wb = wb or Workbook(write_only=True) # Création de workbook si nécessaire
self.ws = self.wb.create_sheet(title=self.sheet_name)
if wb is None:
self.wb = Workbook()
self.ws = self.wb.active
self.ws.title = self.sheet_name
else:
self.wb = None
self.ws = wb
# internal data
self.rows = [] # list of list of cells
self.column_dimensions = {}
self.row_dimensions = {}
def set_column_dimension_width(self, cle, value):
"""Détermine la largeur d'une colonne.
cle -- identifie la colonne ("A"n "B", ...)
value -- la dimension (unité : 7 pixels comme affiché dans Excel)
def excel_make_composite_style(
self,
alignment=None,
border=None,
fill=None,
number_format=None,
font=None,
):
style = {}
if font is not None:
style["font"] = font
if alignment is not None:
style["alignment"] = alignment
if border is not None:
style["border"] = border
if fill is not None:
style["fill"] = fill
if number_format is None:
style["number_format"] = FORMAT_GENERAL
else:
style["number_format"] = number_format
return style
@staticmethod
def i2col(idx):
if idx < 26: # one letter key
return chr(idx + 65)
else: # two letters AA..ZZ
first = (idx // 26) + 66
second = (idx % 26) + 65
return "" + chr(first) + chr(second)
def set_column_dimension_width(self, cle=None, value=21):
"""Détermine la largeur d'une colonne. cle -- identifie la colonne ("A" "B", ... ou 0, 1, 2, ...) si None,
value donne la liste des largeurs de colonnes depuis A, B, C, ... value -- la dimension (unité : 7 pixels
comme affiché dans Excel)
"""
self.ws.column_dimensions[cle].width = value
if cle is None:
for i, val in enumerate(value):
self.ws.column_dimensions[self.i2col(i)].width = val
# No keys: value is a list of widths
elif type(cle) == str: # accepts set_column_with("D", ...)
self.ws.column_dimensions[cle].width = value
else:
self.ws.column_dimensions[self.i2col(cle)].width = value
def set_column_dimension_hidden(self, cle, value):
"""Masque ou affiche une colonne.
cle -- identifie la colonne ("A"n "B", ...)
def set_row_dimension_height(self, cle=None, value=21):
"""Détermine la hauteur d'une ligne. cle -- identifie la ligne (1, 2, ...) si None,
value donne la liste des hauteurs de colonnes depuis 1, 2, 3, ... value -- la dimension
"""
if cle is None:
for i, val in enumerate(value, start=1):
self.ws.row_dimensions[i].height = val
# No keys: value is a list of widths
else:
self.ws.row_dimensions[cle].height = value
def set_row_dimension_hidden(self, cle, value):
"""Masque ou affiche une ligne.
cle -- identifie la colonne (1...)
value -- boolean (vrai = colonne cachée)
"""
self.ws.column_dimensions[cle].hidden = value
self.ws.row_dimensions[cle].hidden = value
def make_cell(self, value: any = None, style=None, comment=None):
"""Construit une cellule.
@ -232,8 +291,12 @@ class ScoExcelSheet:
style = self.default_style
if "font" in style:
cell.font = style["font"]
if "alignment" in style:
cell.alignment = style["alignment"]
if "border" in style:
cell.border = style["border"]
if "fill" in style:
cell.fill = style["fill"]
if "number_format" in style:
cell.number_format = style["number_format"]
if "fill" in style:
@ -272,73 +335,31 @@ class ScoExcelSheet:
"""ajoute une ligne déjà construite à la feuille."""
self.rows.append(row)
# def set_style(self, style=None, li=None, co=None):
# if li is not None and co is not None:
# self.cells_styles_lico[(li, co)] = style
# elif li is None:
# self.cells_styles_li[li] = style
# elif co is None:
# self.cells_styles_co[co] = style
#
# def get_cell_style(self, li, co):
# """Get style for specified cell"""
# return (
# self.cells_styles_lico.get((li, co), None)
# or self.cells_styles_li.get(li, None)
# or self.cells_styles_co.get(co, None)
# or self.default_style
# )
def _generate_ws(self):
def prepare(self):
"""génére un flux décrivant la feuille.
Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille)
ou pour la génération d'un classeur multi-feuilles
"""
for col in self.column_dimensions.keys():
self.ws.column_dimensions[col] = self.column_dimensions[col]
for row in self.column_dimensions.keys():
self.ws.column_dimensions[row] = self.column_dimensions[row]
for row in self.row_dimensions.keys():
self.ws.row_dimensions[row] = self.row_dimensions[row]
for row in self.rows:
self.ws.append(row)
def generate_standalone(self):
def generate(self):
"""génération d'un classeur mono-feuille"""
self._generate_ws()
# this method makes sense only if it is a standalone worksheet (else call workbook.generate()
if self.wb is None: # embeded sheet
raise ScoValueError("can't generate a single sheet from a ScoWorkbook")
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
self.prepare()
with NamedTemporaryFile() as tmp:
self.wb.save(tmp.name)
tmp.seek(0)
return tmp.read()
def generate_embeded(self):
"""generation d'une feuille include dans un classeur multi-feuilles"""
self._generate_ws()
def gen_workbook(self, wb=None):
"""TODO: à remplacer"""
"""Generates and returns a workbook from stored data.
If wb, add a sheet (tab) to the existing workbook (in this case, returns None).
"""
if wb is None:
wb = Workbook() # Création du fichier
sauvegarde = True
else:
sauvegarde = False
ws0 = wb.add_sheet(self.sheet_name)
li = 0
for row in self.rows:
co = 0
for c in row:
# safety net: allow only str, int and float
# #py3 #sco8 A revoir lors de la ré-écriture de ce module
# XXX if type(c) not in (IntType, FloatType):
# c = str(c).decode(scu.SCO_ENCODING)
ws0.write(li, co, c, self.get_cell_style(li, co))
co += 1
li += 1
if sauvegarde:
return wb.savetostr()
else:
return None
def excel_simple_table(
titles=None, lines=None, sheet_name=b"feuille", titles_styles=None, comments=None
@ -377,7 +398,7 @@ def excel_simple_table(
cell_style = text_style
cells.append(ws.make_cell(it, cell_style))
ws.append_row(cells)
return ws.generate_standalone()
return ws.generate()
def excel_feuille_saisie(e, titreannee, description, lines):
@ -538,16 +559,28 @@ def excel_feuille_saisie(e, titreannee, description, lines):
ws.make_cell("cellule vide -> note non modifiée", style_expl),
]
)
return ws.generate_standalone()
return ws.generate()
def excel_bytes_to_list(bytes_content):
filelike = io.BytesIO(bytes_content)
return _excel_to_list(filelike)
try:
filelike = io.BytesIO(bytes_content)
return _excel_to_list(filelike)
except:
raise ScoValueError("""
scolars_import_excel_file: un contenu xlsx semble corrompu!
peut-être avez vous fourni un fichier au mauvais format (txt, xls, ..)
""")
def excel_file_to_list(filename):
return _excel_to_list(filename)
try:
return _excel_to_list(filename)
except:
raise ScoValueError("""
scolars_import_excel_file: un contenu xlsx semble corrompu!
peut-être avez vous fourni un fichier au mauvais format (txt, xls, ..)
""")
def _excel_to_list(filelike): # we may need 'encoding' argument ?
@ -758,4 +791,4 @@ def excel_feuille_listeappel(
cell_2 = ws.make_cell(("Liste éditée le " + dt), style1i)
ws.append_row([None, cell_2])
return ws.generate_standalone()
return ws.generate()

View File

@ -55,11 +55,9 @@ class InvalidNoteValue(ScoException):
# Exception qui stoque dest_url, utilisee dans Zope standard_error_message
class ScoValueError(ScoException):
def __init__(self, msg, dest_url=None, REQUEST=None):
def __init__(self, msg, dest_url=None):
ScoException.__init__(self, msg)
self.dest_url = dest_url
if REQUEST and dest_url:
REQUEST.set("dest_url", dest_url)
class FormatError(ScoValueError):
@ -79,7 +77,7 @@ class ScoConfigurationError(ScoValueError):
class ScoLockedFormError(ScoException):
def __init__(self, msg="", REQUEST=None):
def __init__(self, msg=""):
msg = (
"Cette formation est verrouillée (car il y a un semestre verrouillé qui s'y réfère). "
+ str(msg)
@ -90,7 +88,7 @@ class ScoLockedFormError(ScoException):
class ScoGenError(ScoException):
"exception avec affichage d'une page explicative ad-hoc"
def __init__(self, msg="", REQUEST=None):
def __init__(self, msg=""):
ScoException.__init__(self, msg)

View File

@ -27,7 +27,7 @@
"""Export d'une table avec les résultats de tous les étudiants
"""
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -240,15 +240,13 @@ def scodoc_table_results(
start_date_iso, end_date_iso, types_parcours
)
tab.base_url = "%s?start_date=%s&end_date=%s&types_parcours=%s" % (
REQUEST.URL0,
request.base_url,
start_date,
end_date,
"&types_parcours=".join([str(x) for x in types_parcours]),
)
if format != "html":
return tab.make_page(
format=format, with_html_headers=False, REQUEST=REQUEST
)
return tab.make_page(format=format, with_html_headers=False)
tab_html = tab.html()
nb_rows = tab.get_nb_rows()
else:

View File

@ -404,6 +404,4 @@ def search_inscr_etud_by_nip(code_nip, REQUEST=None, format="json"):
)
tab = GenTable(columns_ids=columns_ids, rows=T)
return tab.make_page(
format=format, with_html_headers=False, REQUEST=REQUEST, publish=True
)
return tab.make_page(format=format, with_html_headers=False, publish=True)

View File

@ -31,7 +31,8 @@ from operator import itemgetter
import xml.dom.minidom
import flask
from flask import g, url_for
from flask import g, url_for, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
@ -92,9 +93,7 @@ def formation_has_locked_sems(formation_id):
return sems
def formation_export(
formation_id, export_ids=False, export_tags=True, format=None, REQUEST=None
):
def formation_export(formation_id, export_ids=False, export_tags=True, format=None):
"""Get a formation, with UE, matieres, modules
in desired format
"""
@ -131,9 +130,7 @@ def formation_export(
if mod["ects"] is None:
del mod["ects"]
return scu.sendResult(
REQUEST, F, name="formation", format=format, force_outer_xml_tag=False
)
return scu.sendResult(F, name="formation", format=format, force_outer_xml_tag=False, attached=True)
def formation_import_xml(doc: str, import_tags=True):
@ -162,20 +159,18 @@ def formation_import_xml(doc: str, import_tags=True):
D = sco_xml.xml_to_dicts(f)
assert D[0] == "formation"
F = D[1]
F_quoted = F.copy()
log("F=%s" % F)
ndb.quote_dict(F_quoted)
log("F_quoted=%s" % F_quoted)
# F_quoted = F.copy()
# ndb.quote_dict(F_quoted)
F["dept_id"] = g.scodoc_dept_id
# find new version number
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
log(
"select max(version) from notes_formations where acronyme=%(acronyme)s and titre=%(titre)s"
% F_quoted
)
cursor.execute(
"select max(version) from notes_formations where acronyme=%(acronyme)s and titre=%(titre)s",
F_quoted,
"""SELECT max(version)
FROM notes_formations
WHERE acronyme=%(acronyme)s and titre=%(titre)s and dept_id=%(dept_id)s
""",
F,
)
res = cursor.fetchall()
try:
@ -196,7 +191,7 @@ def formation_import_xml(doc: str, import_tags=True):
assert ue_info[0] == "ue"
ue_info[1]["formation_id"] = formation_id
if "ue_id" in ue_info[1]:
xml_ue_id = ue_info[1]["ue_id"]
xml_ue_id = int(ue_info[1]["ue_id"])
del ue_info[1]["ue_id"]
else:
xml_ue_id = None
@ -212,7 +207,7 @@ def formation_import_xml(doc: str, import_tags=True):
for mod_info in mat_info[2]:
assert mod_info[0] == "module"
if "module_id" in mod_info[1]:
xml_module_id = mod_info[1]["module_id"]
xml_module_id = int(mod_info[1]["module_id"])
del mod_info[1]["module_id"]
else:
xml_module_id = None
@ -230,7 +225,7 @@ def formation_import_xml(doc: str, import_tags=True):
return formation_id, modules_old2new, ues_old2new
def formation_list_table(formation_id=None, args={}, REQUEST=None):
def formation_list_table(formation_id=None, args={}):
"""List formation, grouped by titre and sorted by versions
and listing associated semestres
returns a table
@ -247,7 +242,7 @@ def formation_list_table(formation_id=None, args={}, REQUEST=None):
"edit_img", border="0", alt="modifier", title="Modifier titres et code"
)
editable = REQUEST.AUTHENTICATED_USER.has_permission(Permission.ScoChangeFormation)
editable = current_user.has_permission(Permission.ScoChangeFormation)
# Traduit/ajoute des champs à afficher:
for f in formations:
@ -347,17 +342,18 @@ def formation_list_table(formation_id=None, args={}, REQUEST=None):
html_class="formation_list_table table_leftalign",
html_with_td_classes=True,
html_sortable=True,
base_url="%s?formation_id=%s" % (REQUEST.URL0, formation_id),
base_url="%s?formation_id=%s" % (request.base_url, formation_id),
page_title=title,
pdf_title=title,
preferences=sco_preferences.SemPreferences(),
)
def formation_create_new_version(formation_id, redirect=True, REQUEST=None):
def formation_create_new_version(formation_id, redirect=True):
"duplicate formation, with new version number"
xml = formation_export(formation_id, export_ids=True, format="xml")
new_id, modules_old2new, ues_old2new = formation_import_xml(xml)
resp = formation_export(formation_id, export_ids=True, format="xml")
xml_data = resp.get_data(as_text=True)
new_id, modules_old2new, ues_old2new = formation_import_xml(xml_data)
# news
F = formation_list(args={"formation_id": new_id})[0]
sco_news.add(

View File

@ -31,7 +31,7 @@ from app.scodoc.sco_exceptions import ScoValueError
import time
from operator import itemgetter
from flask import g
from flask import g, request
import app
from app.models import Departement
@ -61,6 +61,7 @@ _formsemestreEditor = ndb.EditableTable(
"gestion_semestrielle",
"etat",
"bul_hide_xml",
"block_moyennes",
"bul_bgcolor",
"modalite",
"resp_can_edit",
@ -68,7 +69,6 @@ _formsemestreEditor = ndb.EditableTable(
"ens_can_edit_eval",
"elt_sem_apo",
"elt_annee_apo",
"scodoc7_id",
),
filter_dept=True,
sortkey="date_debut",
@ -82,6 +82,7 @@ _formsemestreEditor = ndb.EditableTable(
"etat": bool,
"gestion_compensation": bool,
"bul_hide_xml": bool,
"block_moyennes": bool,
"gestion_semestrielle": bool,
"gestion_compensation": bool,
"gestion_semestrielle": bool,
@ -95,6 +96,7 @@ _formsemestreEditor = ndb.EditableTable(
def get_formsemestre(formsemestre_id):
"list ONE formsemestre"
if not isinstance(formsemestre_id, int):
raise ValueError()
raise ScoValueError(
"""Semestre invalide, reprenez l'opération au départ ou si le problème persiste signalez l'erreur sur scodoc-devel@listes.univ-paris13.fr"""
)
@ -565,7 +567,7 @@ def list_formsemestre_by_etape(etape_apo=False, annee_scolaire=False):
return sems
def view_formsemestre_by_etape(etape_apo=None, format="html", REQUEST=None):
def view_formsemestre_by_etape(etape_apo=None, format="html"):
"""Affiche table des semestres correspondants à l'étape"""
if etape_apo:
html_title = (
@ -582,8 +584,8 @@ def view_formsemestre_by_etape(etape_apo=None, format="html", REQUEST=None):
Etape: <input name="etape_apo" type="text" size="8"></input>
</form>""",
)
tab.base_url = "%s?etape_apo=%s" % (REQUEST.URL0, etape_apo or "")
return tab.make_page(format=format, REQUEST=REQUEST)
tab.base_url = "%s?etape_apo=%s" % (request.base_url, etape_apo or "")
return tab.make_page(format=format)
def sem_has_etape(sem, code_etape):

View File

@ -28,7 +28,7 @@
"""Menu "custom" (défini par l'utilisateur) dans les semestres
"""
import flask
from flask import g, url_for
from flask import g, url_for, request
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -84,9 +84,7 @@ def formsemestre_custommenu_edit(formsemestre_id, REQUEST=None):
scu.NotesURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id
)
H = [
html_sco_header.html_sem_header(
REQUEST, "Modification du menu du semestre ", sem
),
html_sco_header.html_sem_header("Modification du menu du semestre ", sem),
"""<p class="help">Ce menu, spécifique à chaque semestre, peut être utilisé pour placer des liens vers vos applications préférées.</p>
<p class="help">Procédez en plusieurs fois si vous voulez ajouter plusieurs items.</p>""",
]
@ -119,7 +117,7 @@ def formsemestre_custommenu_edit(formsemestre_id, REQUEST=None):
initvalues["title_" + str(item["custommenu_id"])] = item["title"]
initvalues["url_" + str(item["custommenu_id"])] = item["url"]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
initvalues=initvalues,

View File

@ -28,7 +28,7 @@
"""Form choix modules / responsables et creation formsemestre
"""
import flask
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
from app.auth.models import User
@ -91,7 +91,6 @@ def formsemestre_editwithmodules(REQUEST, formsemestre_id):
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
html_sco_header.html_sem_header(
REQUEST,
"Modification du semestre",
sem,
javascripts=["libjs/AutoSuggest.js"],
@ -501,6 +500,14 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
"labels": [""],
},
),
(
"block_moyennes",
{
"input_type": "boolcheckbox",
"title": "Bloquer moyennes",
"explanation": "empêcher le calcul des moyennes d'UE et générale.",
},
),
(
"sep",
{
@ -653,7 +660,7 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
#
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
modform,
submitlabel=submitlabel,
@ -693,7 +700,6 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
tf[2]["bul_hide_xml"] = False
else:
tf[2]["bul_hide_xml"] = True
# remap les identifiants de responsables:
tf[2]["responsable_id"] = User.get_user_id_from_nomplogin(
tf[2]["responsable_id"]
@ -888,7 +894,6 @@ def formsemestre_clone(formsemestre_id, REQUEST=None):
H = [
html_sco_header.html_sem_header(
REQUEST,
"Copie du semestre",
sem,
javascripts=["libjs/AutoSuggest.js"],
@ -959,7 +964,7 @@ def formsemestre_clone(formsemestre_id, REQUEST=None):
),
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
submitlabel="Dupliquer ce semestre",
@ -1113,10 +1118,11 @@ def do_formsemestre_clone(
def formsemestre_associate_new_version(
formsemestre_id,
other_formsemestre_ids=[],
REQUEST=None,
dialog_confirmed=False,
):
"""Formulaire changement formation d'un semestre"""
formsemestre_id = int(formsemestre_id)
other_formsemestre_ids = [int(x) for x in other_formsemestre_ids]
if not dialog_confirmed:
# dresse le liste des semestres de la meme formation et version
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
@ -1161,15 +1167,19 @@ def formsemestre_associate_new_version(
)
else:
do_formsemestres_associate_new_version(
[formsemestre_id] + other_formsemestre_ids, REQUEST=REQUEST
[formsemestre_id] + other_formsemestre_ids
)
return flask.redirect(
"formsemestre_status?formsemestre_id=%s&head_message=Formation%%20dupliquée"
% formsemestre_id
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
head_message="Formation dupliquée",
)
)
def do_formsemestres_associate_new_version(formsemestre_ids, REQUEST=None):
def do_formsemestres_associate_new_version(formsemestre_ids):
"""Cree une nouvelle version de la formation du semestre, et y rattache les semestres.
Tous les moduleimpl sont -associés à la nouvelle formation, ainsi que les decisions de jury
si elles existent (codes d'UE validées).
@ -1179,9 +1189,11 @@ def do_formsemestres_associate_new_version(formsemestre_ids, REQUEST=None):
if not formsemestre_ids:
return
# Check: tous de la même formation
assert isinstance(formsemestre_ids[0], int)
sem = sco_formsemestre.get_formsemestre(formsemestre_ids[0])
formation_id = sem["formation_id"]
for formsemestre_id in formsemestre_ids[1:]:
assert isinstance(formsemestre_id, int)
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if formation_id != sem["formation_id"]:
raise ScoValueError("les semestres ne sont pas tous de la même formation !")
@ -1192,9 +1204,7 @@ def do_formsemestres_associate_new_version(formsemestre_ids, REQUEST=None):
formation_id,
modules_old2new,
ues_old2new,
) = sco_formations.formation_create_new_version(
formation_id, redirect=False, REQUEST=REQUEST
)
) = sco_formations.formation_create_new_version(formation_id, redirect=False)
for formsemestre_id in formsemestre_ids:
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
@ -1235,7 +1245,7 @@ def formsemestre_delete(formsemestre_id, REQUEST=None):
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
H = [
html_sco_header.html_sem_header(REQUEST, "Suppression du semestre", sem),
html_sco_header.html_sem_header("Suppression du semestre", sem),
"""<div class="ue_warning"><span>Attention !</span>
<p class="help">A n'utiliser qu'en cas d'erreur lors de la saisie d'une formation. Normalement,
<b>un semestre ne doit jamais être supprimé</b> (on perd la mémoire des notes et de tous les événements liés à ce semestre !).</p>
@ -1261,7 +1271,7 @@ def formsemestre_delete(formsemestre_id, REQUEST=None):
else:
submit_label = "Confirmer la suppression du semestre"
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(("formsemestre_id", {"input_type": "hidden"}),),
initvalues=F,
@ -1421,7 +1431,7 @@ def do_formsemestre_delete(formsemestre_id):
# ---------------------------------------------------------------------------------------
def formsemestre_edit_options(formsemestre_id, target_url=None, REQUEST=None):
def formsemestre_edit_options(formsemestre_id, REQUEST=None):
"""dialog to change formsemestre options
(accessible par ScoImplement ou dir. etudes)
"""
@ -1538,9 +1548,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
</p>
"""
H = [
html_sco_header.html_sem_header(
REQUEST, "Coefficients des UE du semestre", sem
),
html_sco_header.html_sem_header("Coefficients des UE du semestre", sem),
help,
]
#
@ -1576,7 +1584,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
form.append(("ue_" + str(ue["ue_id"]), descr))
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
form,
submitlabel="Changer les coefficients",
@ -1652,9 +1660,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
formsemestre_id=formsemestre_id
) # > modif coef UE cap (modifs notes de _certains_ etudiants)
header = html_sco_header.html_sem_header(
REQUEST, "Coefficients des UE du semestre", sem
)
header = html_sco_header.html_sem_header("Coefficients des UE du semestre", sem)
return (
header
+ "\n".join(z)

View File

@ -34,7 +34,7 @@ Ces semestres n'auront qu'un seul inscrit !
import time
import flask
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
@ -181,7 +181,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id, REQUEST=None):
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
cancelbutton="Annuler",
@ -231,7 +231,7 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid, REQUEST=None):
else:
initvalues = {}
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
cssclass="tf_ext_edit_ue_validations",

View File

@ -30,7 +30,7 @@
import time
import flask
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
from app import log
@ -334,7 +334,6 @@ def formsemestre_inscription_with_modules(
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
H = [
html_sco_header.html_sem_header(
REQUEST,
"Inscription de %s dans ce semestre" % etud["nomprenom"],
sem,
)
@ -415,7 +414,7 @@ def formsemestre_inscription_with_modules(
<input type="hidden" name="etudid" value="%s">
<input type="hidden" name="formsemestre_id" value="%s">
"""
% (REQUEST.URL0, etudid, formsemestre_id)
% (request.base_url, etudid, formsemestre_id)
)
H.append(sco_groups.form_group_choice(formsemestre_id, allow_none=True))
@ -533,7 +532,7 @@ function chkbx_select(field_id, state) {
"""
)
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
initvalues,
@ -763,7 +762,6 @@ def formsemestre_inscrits_ailleurs(formsemestre_id, REQUEST=None):
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
html_sco_header.html_sem_header(
REQUEST,
"Inscriptions multiples parmi les étudiants du semestre ",
sem,
)

View File

@ -436,7 +436,7 @@ def formsemestre_status_menubar(sem):
return "\n".join(H)
def retreive_formsemestre_from_request():
def retreive_formsemestre_from_request() -> int:
"""Cherche si on a de quoi déduire le semestre affiché à partir des
arguments de la requête:
formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id
@ -482,7 +482,7 @@ def retreive_formsemestre_from_request():
else:
return None # no current formsemestre
return formsemestre_id
return int(formsemestre_id)
# Element HTML decrivant un semestre (barre de menu et infos)
@ -698,10 +698,10 @@ def formsemestre_description_table(formsemestre_id, REQUEST=None, with_evals=Fal
html_caption=title,
html_class="table_leftalign formsemestre_description",
base_url="%s?formsemestre_id=%s&with_evals=%s"
% (REQUEST.URL0, formsemestre_id, with_evals),
% (request.base_url, formsemestre_id, with_evals),
page_title=title,
html_title=html_sco_header.html_sem_header(
REQUEST, "Description du semestre", sem, with_page_header=False
"Description du semestre", sem, with_page_header=False
),
pdf_title=title,
preferences=sco_preferences.SemPreferences(formsemestre_id),
@ -721,14 +721,14 @@ def formsemestre_description(
tab.html_before_table = """<form name="f" method="get" action="%s">
<input type="hidden" name="formsemestre_id" value="%s"></input>
<input type="checkbox" name="with_evals" value="1" onchange="document.f.submit()" """ % (
REQUEST.URL0,
request.base_url,
formsemestre_id,
)
if with_evals:
tab.html_before_table += "checked"
tab.html_before_table += ">indiquer les évaluations</input></form>"
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
# genere liste html pour accès aux groupes de ce semestre
@ -912,12 +912,12 @@ def formsemestre_status_head(formsemestre_id=None, REQUEST=None, page_title=None
H = [
html_sco_header.html_sem_header(
REQUEST, page_title, sem, with_page_header=False, with_h2=False
page_title, sem, with_page_header=False, with_h2=False
),
"""<table>
f"""<table>
<tr><td class="fichetitre2">Formation: </td><td>
<a href="Notes/ue_list?formation_id=%(formation_id)s" class="discretelink" title="Formation %(acronyme)s, v%(version)s">%(titre)s</a>"""
% F,
<a href="{url_for('notes.ue_list', scodoc_dept=g.scodoc_dept, formation_id=F['formation_id'])}"
class="discretelink" title="Formation {F['acronyme']}, v{F['version']}">{F['titre']}</a>""",
]
if sem["semestre_id"] >= 0:
H.append(", %s %s" % (parcours.SESSION_NAME, sem["semestre_id"]))
@ -948,10 +948,13 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
</td></tr>"""
)
H.append("</table>")
sem_warning = ""
if sem["bul_hide_xml"]:
H.append(
'<p class="fontorange"><em>Bulletins non publiés sur le portail</em></p>'
)
sem_warning += "Bulletins non publiés sur le portail. "
if sem["block_moyennes"]:
sem_warning += "Calcul des moyennes bloqué !"
if sem_warning:
H.append('<p class="fontorange"><em>' + sem_warning + "</em></p>")
if sem["semestre_id"] >= 0 and not sco_formsemestre.sem_une_annee(sem):
H.append(
'<p class="fontorange"><em>Attention: ce semestre couvre plusieurs années scolaires !</em></p>'

View File

@ -30,7 +30,7 @@
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error, time, datetime
import flask
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -149,9 +149,7 @@ def formsemestre_validation_etud_form(
'</td><td style="text-align: right;"><a href="%s">%s</a></td></tr></table>'
% (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_html(
etud, title="fiche de %s" % etud["nom"], REQUEST=REQUEST
),
sco_photos.etud_photo_html(etud, title="fiche de %s" % etud["nom"]),
)
)
@ -356,7 +354,7 @@ def formsemestre_validation_etud(
#
Se.valide_decision(selected_choice, REQUEST) # enregistre
return _redirect_valid_choice(
formsemestre_id, etudid, Se, selected_choice, desturl, sortcol, REQUEST
formsemestre_id, etudid, Se, selected_choice, desturl, sortcol
)
@ -402,19 +400,17 @@ def formsemestre_validation_etud_manu(
Se.valide_decision(choice, REQUEST) # enregistre
if redirect:
return _redirect_valid_choice(
formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST
formsemestre_id, etudid, Se, choice, desturl, sortcol
)
def _redirect_valid_choice(
formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST
):
def _redirect_valid_choice(formsemestre_id, etudid, Se, choice, desturl, sortcol):
adr = "formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1" % (
formsemestre_id,
etudid,
)
if sortcol:
adr += "&sortcol=" + sortcol
adr += "&sortcol=" + str(sortcol)
# if desturl:
# desturl += "&desturl=" + desturl
return flask.redirect(adr)
@ -826,7 +822,7 @@ def formsemestre_validation_auto(formsemestre_id, REQUEST):
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
html_sco_header.html_sem_header(
REQUEST, "Saisie automatique des décisions du semestre", sem
"Saisie automatique des décisions du semestre", sem
),
"""
<ul>
@ -994,9 +990,7 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid, REQUEST=None):
'</td><td style="text-align: right;"><a href="%s">%s</a></td></tr></table>'
% (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_html(
etud, title="fiche de %s" % etud["nom"], REQUEST=REQUEST
),
sco_photos.etud_photo_html(etud, title="fiche de %s" % etud["nom"]),
)
),
"""<p class="help">Utiliser cette page pour enregistrer une UE validée antérieurement,
@ -1017,7 +1011,7 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid, REQUEST=None):
ue_names = ["Choisir..."] + ["%(acronyme)s %(titre)s" % ue for ue in ues]
ue_ids = [""] + [ue["ue_id"] for ue in ues]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("etudid", {"input_type": "hidden"}),

View File

@ -42,7 +42,7 @@ from xml.etree import ElementTree
from xml.etree.ElementTree import Element
import flask
from flask import g
from flask import g, request
from flask import url_for
import app.scodoc.sco_utils as scu
@ -476,7 +476,7 @@ def formsemestre_partition_list(formsemestre_id, format="xml", REQUEST=None):
# Ajoute les groupes
for p in partitions:
p["group"] = get_partition_groups(p)
return scu.sendResult(REQUEST, partitions, name="partition", format=format)
return scu.sendResult(partitions, name="partition", format=format)
# Encore utilisé par groupmgr.js
@ -1079,7 +1079,7 @@ def partition_rename(partition_id, REQUEST=None):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
H = ["<h2>Renommer une partition</h2>"]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("partition_id", {"default": partition_id, "input_type": "hidden"}),
@ -1188,7 +1188,7 @@ def group_rename(group_id, REQUEST=None):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
H = ["<h2>Renommer un groupe de %s</h2>" % group["partition_name"]]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("group_id", {"default": group_id, "input_type": "hidden"}),
@ -1268,7 +1268,7 @@ def groups_auto_repartition(partition_id=None, REQUEST=None):
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
{},
@ -1499,7 +1499,7 @@ def _sortgroups(groups):
# Tri: place 'all' en tête, puis groupe par partition / nom de groupe
R = [g for g in groups if g["partition_name"] is None]
o = [g for g in groups if g["partition_name"] != None]
o.sort(key=lambda x: (x["numero"], x["group_name"]))
o.sort(key=lambda x: (x["numero"] or 0, x["group_name"]))
return R + o

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@
import datetime
from operator import itemgetter
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -81,6 +81,7 @@ def list_authorized_etuds_by_sem(sem, delai=274):
"title": src["titreannee"],
"title_target": "formsemestre_status?formsemestre_id=%s"
% src["formsemestre_id"],
"filename": "etud_autorises",
},
}
# ajoute attribut inscrit qui indique si l'étudiant est déjà inscrit dans le semestre dest.
@ -99,6 +100,7 @@ def list_authorized_etuds_by_sem(sem, delai=274):
% sem["formsemestre_id"],
"comment": " actuellement inscrits dans ce semestre",
"help": "Ces étudiants sont actuellement inscrits dans ce semestre. Si vous les décochez, il seront désinscrits.",
"filename": "etud_inscrits",
},
}
@ -146,16 +148,15 @@ def list_inscrits_date(sem):
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
cursor.execute(
"""SELECT I.etudid
FROM
notes_formsemestre_inscription ins,
notes_formsemestre S,
identite i
"""SELECT ins.etudid
FROM
notes_formsemestre_inscription ins,
notes_formsemestre S
WHERE ins.formsemestre_id = S.id
AND S.id != %(formsemestre_id)s
AND S.date_debut <= %(date_debut_iso)s
AND S.date_fin >= %(date_debut_iso)s
AND ins.dept_id = %(dept_id)
AND S.dept_id = %(dept_id)s
""",
sem,
)
@ -413,9 +414,9 @@ def build_page(
H = [
html_sco_header.html_sem_header(
REQUEST, "Passages dans le semestre", sem, with_page_header=False
"Passages dans le semestre", sem, with_page_header=False
),
"""<form method="post" action="%s">""" % REQUEST.URL0,
"""<form method="post" action="%s">""" % request.base_url,
"""<input type="hidden" name="formsemestre_id" value="%(formsemestre_id)s"/>
<input type="submit" name="submitted" value="Appliquer les modifications"/>
&nbsp;<a href="#help">aide</a>
@ -507,7 +508,12 @@ def etuds_select_boxes(
</script>
<div class="etuds_select_boxes">"""
] # "
# Élimine les boites vides:
auth_etuds_by_cat = {
k: auth_etuds_by_cat[k]
for k in auth_etuds_by_cat
if auth_etuds_by_cat[k]["etuds"]
}
for src_cat in auth_etuds_by_cat.keys():
infos = auth_etuds_by_cat[src_cat]["infos"]
infos["comment"] = infos.get("comment", "") # commentaire dans sous-titre boite
@ -550,10 +556,8 @@ def etuds_select_boxes(
if with_checkbox or sel_inscrits:
H.append(")")
if base_url and etuds:
H.append(
'<a href="%s&export_cat_xls=%s">%s</a>&nbsp;'
% (base_url, src_cat, scu.ICON_XLS)
)
url = scu.build_url_query(base_url, export_cat_xls=src_cat)
H.append(f'<a href="{url}">{scu.ICON_XLS}</a>&nbsp;')
H.append("</div>")
for etud in etuds:
if etud.get("inscrit", False):
@ -633,4 +637,4 @@ def etuds_select_box_xls(src_cat):
caption="%(title)s. %(help)s" % src_cat["infos"],
preferences=sco_preferences.SemPreferences(),
)
return tab.excel()
return tab.excel() # tab.make_page(filename=src_cat["infos"]["filename"])

View File

@ -27,11 +27,11 @@
"""Liste des notes d'une évaluation
"""
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
from operator import itemgetter
import urllib
import flask
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -177,7 +177,7 @@ def do_evaluation_listenotes(REQUEST):
),
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
cancelbutton=None,
@ -482,7 +482,7 @@ def _make_table_notes(
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
)
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
t = tab.make_page(format=format, with_html_headers=False)
if format != "html":
return t
@ -778,9 +778,7 @@ def evaluation_check_absences_html(
if with_header:
H = [
html_sco_header.html_sem_header(
REQUEST, "Vérification absences à l'évaluation"
),
html_sco_header.html_sem_header("Vérification absences à l'évaluation"),
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id),
"""<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.</p>""",
]
@ -817,8 +815,8 @@ def evaluation_check_absences_html(
'<a class="stdlink" href="Absences/doSignaleAbsence?etudid=%s&datedebut=%s&datefin=%s&demijournee=%s&moduleimpl_id=%s">signaler cette absence</a>'
% (
etud["etudid"],
six.moves.urllib.parse.quote(E["jour"]),
six.moves.urllib.parse.quote(E["jour"]),
urllib.parse.quote(E["jour"]),
urllib.parse.quote(E["jour"]),
demijournee,
E["moduleimpl_id"],
)
@ -866,7 +864,6 @@ def formsemestre_check_absences_html(formsemestre_id, REQUEST=None):
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
html_sco_header.html_sem_header(
REQUEST,
"Vérification absences aux évaluations de ce semestre",
sem,
),

View File

@ -31,7 +31,7 @@
"""
from operator import itemgetter
from flask import url_for, g
from flask import url_for, g, request
import app
import app.scodoc.sco_utils as scu
@ -84,8 +84,8 @@ def scodoc_table_etuds_lycees(format="html", REQUEST=None):
sco_preferences.SemPreferences(),
no_links=True,
)
tab.base_url = REQUEST.URL0
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
tab.base_url = request.base_url
t = tab.make_page(format=format, with_html_headers=False)
if format != "html":
return t
H = [
@ -187,12 +187,12 @@ def formsemestre_etuds_lycees(
tab, etuds_by_lycee = formsemestre_table_etuds_lycees(
formsemestre_id, only_primo=only_primo, group_lycees=not no_grouping
)
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
if only_primo:
tab.base_url += "&only_primo=1"
if no_grouping:
tab.base_url += "&no_grouping=1"
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
t = tab.make_page(format=format, with_html_headers=False)
if format != "html":
return t
F = [

View File

@ -30,7 +30,8 @@
from operator import itemgetter
import flask
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -137,7 +138,7 @@ def moduleimpl_inscriptions_edit(
</script>"""
)
H.append("""<form method="post" id="mi_form" action="%s">""" % REQUEST.URL0)
H.append("""<form method="post" id="mi_form" action="%s">""" % request.base_url)
H.append(
"""
<input type="hidden" name="moduleimpl_id" value="%(moduleimpl_id)s"/>
@ -250,7 +251,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id, REQUEST=None):
tous sauf <liste d'au plus 7 noms>
"""
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
@ -285,9 +286,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id, REQUEST=None):
mod["nb_inscrits"] = nb_inscrits
options.append(mod)
# Page HTML:
H = [
html_sco_header.html_sem_header(REQUEST, "Inscriptions aux modules du semestre")
]
H = [html_sco_header.html_sem_header("Inscriptions aux modules du semestre")]
H.append("<h3>Inscrits au semestre: %d étudiants</h3>" % len(inscrits))

View File

@ -28,7 +28,7 @@
"""Tableau de bord module
"""
import time
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
import urllib
from flask import g, url_for
from flask_login import current_user
@ -64,7 +64,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
if (
sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"], allow_ens=False
current_user, E["moduleimpl_id"], allow_ens=False
)
and nbnotes != 0
):
@ -80,7 +80,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"]
current_user, E["moduleimpl_id"]
),
},
{
@ -90,7 +90,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"], allow_ens=False
current_user, E["moduleimpl_id"], allow_ens=False
),
},
{
@ -101,7 +101,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
},
"enabled": nbnotes == 0
and sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"], allow_ens=False
current_user, E["moduleimpl_id"], allow_ens=False
),
},
{
@ -111,7 +111,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"], allow_ens=False
current_user, E["moduleimpl_id"], allow_ens=False
),
},
{
@ -128,16 +128,15 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
"args": {
"evaluation_id": evaluation_id,
},
"enabled": nbnotes == 0
and sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"]
"enabled": sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"]
),
},
{
"title": "Absences ce jour",
"endpoint": "absences.EtatAbsencesDate",
"args": {
"date": six.moves.urllib.parse.quote(E["jour"], safe=""),
"date": urllib.parse.quote(E["jour"], safe=""),
"group_ids": group_id,
},
"enabled": E["jour"],

View File

@ -31,6 +31,7 @@
"""
from flask import url_for, g
from flask_login import current_user
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -144,7 +145,7 @@ def _menuScolarite(authuser, sem, etudid):
def ficheEtud(etudid=None, REQUEST=None):
"fiche d'informations sur un etudiant"
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
cnx = ndb.GetDBConnexion()
if etudid and REQUEST:
# la sidebar est differente s'il y a ou pas un etudid
@ -167,7 +168,7 @@ def ficheEtud(etudid=None, REQUEST=None):
info["info_naissance"] += " à " + info["lieu_naissance"]
if info["dept_naissance"]:
info["info_naissance"] += " (%s)" % info["dept_naissance"]
info["etudfoto"] = sco_photos.etud_photo_html(etud, REQUEST=REQUEST)
info["etudfoto"] = sco_photos.etud_photo_html(etud)
if (
(not info["domicile"])
and (not info["codepostaldomicile"])
@ -491,7 +492,7 @@ def menus_etud(REQUEST=None):
"""Menu etudiant (operations sur l'etudiant)"""
if "etudid" not in REQUEST.form:
return ""
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
etud = sco_etud.get_etud_info(filled=True)[0]
@ -539,9 +540,7 @@ def etud_info_html(etudid, with_photo="1", REQUEST=None, debug=False):
formsemestre_id = sco_formsemestre_status.retreive_formsemestre_from_request()
with_photo = int(with_photo)
etud = sco_etud.get_etud_info(filled=True)[0]
photo_html = sco_photos.etud_photo_html(
etud, title="fiche de " + etud["nom"], REQUEST=REQUEST
)
photo_html = sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"])
# experimental: may be too slow to be here
etud["codeparcours"], etud["decisions_jury"] = sco_report.get_codeparcoursetud(
etud, prefix="S", separator=", "

View File

@ -588,7 +588,6 @@ class SituationEtudParcoursGeneric(object):
self.etudid,
decision.code_etat,
decision.assiduite,
REQUEST=REQUEST,
)
# -- modification du code du semestre precedent
if self.prev and decision.new_code_prev:
@ -619,7 +618,6 @@ class SituationEtudParcoursGeneric(object):
self.etudid,
decision.new_code_prev,
decision.assiduite, # attention: en toute rigueur il faudrait utiliser une indication de l'assiduite au sem. precedent, que nous n'avons pas...
REQUEST=REQUEST,
)
sco_cache.invalidate_formsemestre(
@ -897,9 +895,7 @@ def formsemestre_update_validation_sem(
return to_invalidate
def formsemestre_validate_ues(
formsemestre_id, etudid, code_etat_sem, assiduite, REQUEST=None
):
def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite):
"""Enregistre codes UE, selon état semestre.
Les codes UE sont toujours calculés ici, et non passés en paramètres
car ils ne dépendent que de la note d'UE et de la validation ou non du semestre.
@ -933,14 +929,13 @@ def formsemestre_validate_ues(
cnx, nt, formsemestre_id, etudid, ue_id, code_ue
)
if REQUEST:
logdb(
cnx,
method="validate_ue",
etudid=etudid,
msg="ue_id=%s code=%s" % (ue_id, code_ue),
commit=False,
)
logdb(
cnx,
method="validate_ue",
etudid=etudid,
msg="ue_id=%s code=%s" % (ue_id, code_ue),
commit=False,
)
cnx.commit()

View File

@ -43,6 +43,7 @@ Les images sont servies par ScoDoc, via la méthode getphotofile?etudid=xxx
"""
from app.scodoc.sco_exceptions import ScoGenError
import datetime
import glob
import io
@ -52,6 +53,7 @@ import requests
import time
import traceback
import PIL
from PIL import Image as PILImage
from flask import request, g
@ -172,7 +174,7 @@ def etud_photo_is_local(etud, size="small"):
return photo_pathname(etud, size=size)
def etud_photo_html(etud=None, etudid=None, title=None, size="small", REQUEST=None):
def etud_photo_html(etud=None, etudid=None, title=None, size="small"):
"""HTML img tag for the photo, either in small size (h90)
or original size (size=="orig")
"""
@ -209,9 +211,7 @@ def etud_photo_orig_html(etud=None, etudid=None, title=None, REQUEST=None):
Full-size images are always stored locally in the filesystem.
They are the original uploaded images, converted in jpeg.
"""
return etud_photo_html(
etud=etud, etudid=etudid, title=title, size="orig", REQUEST=REQUEST
)
return etud_photo_html(etud=etud, etudid=etudid, title=title, size="orig")
def photo_pathname(etud, size="orig"):
@ -246,7 +246,10 @@ def store_photo(etud, data):
filesize = len(data)
if filesize < 10 or filesize > MAX_FILE_SIZE:
return 0, "Fichier image de taille invalide ! (%d)" % filesize
filename = save_image(etud["etudid"], data)
try:
filename = save_image(etud["etudid"], data)
except PIL.UnidentifiedImageError:
raise ScoGenError(msg="Fichier d'image invalide ou non format non supporté")
# update database:
etud["photo_filename"] = filename
etud["foto"] = None
@ -298,6 +301,7 @@ def save_image(etudid, data):
filename = get_new_filename(etudid)
path = os.path.join(PHOTO_DIR, filename)
log("saving %dx%d jpeg to %s" % (img.size[0], img.size[1], path))
img = img.convert("RGB")
img.save(path + IMAGE_EXT, format="JPEG", quality=92)
# resize:
img = scale_height(img)
@ -341,7 +345,7 @@ def find_new_dir():
def copy_portal_photo_to_fs(etud):
"""Copy the photo from portal (distant website) to local fs.
Returns rel. path or None if copy failed, with a diagnotic message
Returns rel. path or None if copy failed, with a diagnostic message
"""
sco_etud.format_etud_ident(etud)
url = photo_portal_url(etud)
@ -353,11 +357,12 @@ def copy_portal_photo_to_fs(etud):
log("copy_portal_photo_to_fs: getting %s" % url)
r = requests.get(url, timeout=portal_timeout)
except:
log("download failed: exception:\n%s" % traceback.format_exc())
log("called from:\n" + "".join(traceback.format_stack()))
# log("download failed: exception:\n%s" % traceback.format_exc())
# log("called from:\n" + "".join(traceback.format_stack()))
log("copy_portal_photo_to_fs: error.")
return None, "%s: erreur chargement de %s" % (etud["nomprenom"], url)
if r.status_code != 200:
log("download failed")
log(f"copy_portal_photo_to_fs: download failed {r.status_code }")
return None, "%s: erreur chargement de %s" % (etud["nomprenom"], url)
data = r.content # image bytes
try:

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ Recapitule tous les semestres validés dans une feuille excel.
"""
import collections
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
from app.scodoc import sco_abs
@ -211,12 +211,11 @@ def formsemestre_poursuite_report(formsemestre_id, format="html", REQUEST=None):
)
tab.caption = "Récapitulatif %s." % sem["titreannee"]
tab.html_caption = "Récapitulatif %s." % sem["titreannee"]
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
return tab.make_page(
title="""<h2 class="formsemestre">Poursuite d'études</h2>""",
init_qtip=True,
javascripts=["js/etud_info.js"],
format=format,
REQUEST=REQUEST,
with_html_headers=True,
)

View File

@ -111,7 +111,8 @@ get_base_preferences(formsemestre_id)
"""
import flask
from flask import g, url_for
from flask import g, url_for, request
from flask_login import current_user
from app.models import Departement
from app.scodoc import sco_cache
@ -180,7 +181,7 @@ def _convert_pref_type(p, pref_spec):
def _get_pref_default_value_from_config(name, pref_spec):
"""get default value store in application level config.
If not found, use defalut value hardcoded in pref_spec.
If not found, use default value hardcoded in pref_spec.
"""
# XXX va changer avec la nouvelle base
# search in scu.CONFIG
@ -1408,7 +1409,7 @@ class BasePreferences(object):
{
"initvalue": 1,
"title": "Indique si les bulletins sont publiés",
"explanation": "décocher si vous n'avez pas de portal étudiant publiant les bulletins",
"explanation": "décocher si vous n'avez pas de portail étudiant publiant les bulletins",
"input_type": "boolcheckbox",
"labels": ["non", "oui"],
"category": "bul",
@ -1891,7 +1892,7 @@ class BasePreferences(object):
def get(self, formsemestre_id, name):
"""Returns preference value.
If global_lookup, when no value defined for this semestre, returns global value.
when no value defined for this semestre, returns global value.
"""
params = {
"dept_id": self.dept_id,
@ -1901,7 +1902,7 @@ class BasePreferences(object):
cnx = ndb.GetDBConnexion()
plist = self._editor.list(cnx, params)
if not plist:
del params["formsemestre_id"]
params["formsemestre_id"] = None
plist = self._editor.list(cnx, params)
if not plist:
return self.default[name]
@ -2022,14 +2023,16 @@ class BasePreferences(object):
html_sco_header.sco_header(page_title="Préférences"),
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
f"""<p><a href="{url_for("scolar.config_logos", scodoc_dept=g.scodoc_dept)
}">modification des logos du département (pour documents pdf)</a></p>""",
}">modification des logos du département (pour documents pdf)</a></p>"""
if current_user.is_administrator()
else "",
"""<p class="help">Ces paramètres s'appliquent par défaut à tous les semestres, sauf si ceux-ci définissent des valeurs spécifiques.</p>
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
""",
]
form = self.build_tf_form()
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
form,
initvalues=self.prefs[None],
@ -2151,7 +2154,7 @@ class SemPreferences(object):
) # a bug !
sem = sco_formsemestre.get_formsemestre(self.formsemestre_id)
H = [
html_sco_header.html_sem_header(REQUEST, "Préférences du semestre", sem),
html_sco_header.html_sem_header("Préférences du semestre", sem),
"""
<p class="help">Les paramètres définis ici ne s'appliqueront qu'à ce semestre.</p>
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
@ -2194,7 +2197,7 @@ function set_global_pref(el, pref_name) {
form.append(("destination", {"input_type": "hidden"}))
form.append(("formsemestre_id", {"input_type": "hidden"}))
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
form,
initvalues=self,
@ -2245,7 +2248,7 @@ function set_global_pref(el, pref_name) {
return flask.redirect(dest_url + "&head_message=Préférences modifiées")
elif destination == "again":
return flask.redirect(
REQUEST.URL0 + "?formsemestre_id=" + str(self.formsemestre_id)
request.base_url + "?formsemestre_id=" + str(self.formsemestre_id)
)
elif destination == "global":
return flask.redirect(scu.ScoURL() + "/edit_preferences")
@ -2253,7 +2256,7 @@ function set_global_pref(el, pref_name) {
#
def doc_preferences():
""" Liste les preferences en MarkDown, pour la documentation"""
"""Liste les preferences en MarkDown, pour la documentation"""
L = []
for cat, cat_descr in PREF_CATEGORIES:
L.append([""])

View File

@ -31,6 +31,9 @@ import time
from openpyxl.styles.numbers import FORMAT_NUMBER_00
from flask import request
from flask_login import current_user
import app.scodoc.sco_utils as scu
from app.scodoc import sco_abs
from app.scodoc import sco_groups
@ -318,9 +321,14 @@ def feuille_preparation_jury(formsemestre_id, REQUEST):
% (
sco_version.SCONAME,
time.strftime("%d/%m/%Y"),
REQUEST.BASE0,
REQUEST.AUTHENTICATED_USER,
request.url_root,
current_user,
)
)
xls = ws.generate_standalone()
return sco_excel.send_excel_file(REQUEST, xls, f"PrepaJury{sn}{scu.XLSX_SUFFIX}")
xls = ws.generate()
return scu.send_file(
xls,
f"PrepaJury{sn}",
scu.XLSX_SUFFIX,
mime=scu.XLSX_MIMETYPE,
)

View File

@ -52,7 +52,7 @@ from reportlab.platypus import Paragraph
from reportlab.lib import styles
import flask
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -535,13 +535,11 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True, REQUEST=No
return tab.make_page(
format=format,
with_html_headers=False,
REQUEST=REQUEST,
publish=publish,
)
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
H = [
html_sco_header.html_sem_header(
REQUEST,
"Décisions du jury pour le semestre",
sem,
init_qtip=True,
@ -628,7 +626,6 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=
H = [
html_sco_header.html_sem_header(
REQUEST,
"Edition du PV de jury %s" % etuddescr,
sem=sem,
javascripts=sco_groups_view.JAVASCRIPTS,
@ -658,7 +655,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=
else:
menu_choix_groupe = "" # un seul etudiant à editer
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
cancelbutton="Annuler",
@ -707,7 +704,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=
else:
groups_filename = ""
filename = "PV-%s%s-%s.pdf" % (sem["titre_num"], groups_filename, dt)
return scu.sendPDFFile(REQUEST, pdfdoc, filename)
return scu.sendPDFFile(pdfdoc, filename)
def descrform_pvjury(sem):
@ -806,8 +803,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
H = [
html_sco_header.html_sem_header(
REQUEST,
"Edition des lettres individuelles",
"Édition des lettres individuelles",
sem=sem,
javascripts=sco_groups_view.JAVASCRIPTS,
cssstyles=sco_groups_view.CSSSTYLES,
@ -827,7 +823,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
)
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
cancelbutton="Annuler",
@ -868,7 +864,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
dt = time.strftime("%Y-%m-%d")
groups_filename = "-" + groups_infos.groups_filename
filename = "lettres-%s%s-%s.pdf" % (sem["titre_num"], groups_filename, dt)
return scu.sendPDFFile(REQUEST, pdfdoc, filename)
return scu.sendPDFFile(pdfdoc, filename)
def descrform_lettres_individuelles():

View File

@ -27,10 +27,13 @@
"""Tableau recapitulatif des notes d'un semestre
"""
import time
import datetime
import json
import time
from xml.etree import ElementTree
from flask import request
import app.scodoc.sco_utils as scu
from app import log
from app.scodoc import html_sco_header
@ -100,7 +103,7 @@ def formsemestre_recapcomplet(
sco_formsemestre_status.formsemestre_status_head(
formsemestre_id=formsemestre_id, REQUEST=REQUEST
),
'<form name="f" method="get" action="%s">' % REQUEST.URL0,
'<form name="f" method="get" action="%s">' % request.base_url,
'<input type="hidden" name="formsemestre_id" value="%s"></input>'
% formsemestre_id,
'<input type="hidden" name="pref_override" value="0"></input>',
@ -227,11 +230,14 @@ def do_formsemestre_recapcomplet(
if format == "xml" or format == "html":
return data
elif format == "csv":
return scu.sendCSVFile(REQUEST, data, filename)
elif format[:3] == "xls":
return sco_excel.send_excel_file(REQUEST, data, filename)
return scu.send_file(data, filename=filename, mime=scu.CSV_MIMETYPE)
elif format.startswith("xls") or format.startswith("xlsx"):
return scu.send_file(data, filename=filename, mime=scu.XLSX_MIMETYPE)
elif format == "json":
return scu.sendJSON(REQUEST, data)
js = json.dumps(data, indent=1, cls=scu.ScoDocJSONEncoder)
return scu.send_file(
js, filename=filename, suffix=scu.JSON_SUFFIX, mime=scu.JSON_MIMETYPE
)
else:
raise ValueError("unknown format %s" % format)
@ -967,4 +973,4 @@ def formsemestres_bulletins(annee_scolaire, REQUEST=None):
)
jslist.append(J)
return scu.sendJSON(REQUEST, jslist)
return scu.sendJSON(jslist)

View File

@ -37,7 +37,7 @@ import time
import datetime
from operator import itemgetter
from flask import url_for, g
from flask import url_for, g, request
import pydot
import app.scodoc.sco_utils as scu
@ -247,7 +247,7 @@ def formsemestre_report(
sem["titreannee"],
)
tab.html_caption = "Répartition des résultats par %s." % category_name
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
if only_primo:
tab.base_url += "&only_primo=on"
return tab
@ -322,7 +322,7 @@ def formsemestre_report_counts(
F = [
"""<form name="f" method="get" action="%s"><p>
Colonnes: <select name="result" onchange="document.f.submit()">"""
% REQUEST.URL0
% request.base_url
]
for k in keys:
if k == result:
@ -355,7 +355,6 @@ def formsemestre_report_counts(
t = tab.make_page(
title="""<h2 class="formsemestre">Comptes croisés</h2>""",
format=format,
REQUEST=REQUEST,
with_html_headers=False,
)
if format != "html":
@ -718,15 +717,15 @@ def formsemestre_suivi_cohorte(
)
tab.base_url = (
"%s?formsemestre_id=%s&percent=%s&bac=%s&bacspecialite=%s&civilite=%s"
% (REQUEST.URL0, formsemestre_id, percent, bac, bacspecialite, civilite)
% (request.base_url, formsemestre_id, percent, bac, bacspecialite, civilite)
)
if only_primo:
tab.base_url += "&only_primo=on"
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
t = tab.make_page(format=format, with_html_headers=False)
if format != "html":
return t
base_url = REQUEST.URL0
base_url = request.base_url
burl = "%s?formsemestre_id=%s&bac=%s&bacspecialite=%s&civilite=%s&statut=%s" % (
base_url,
formsemestre_id,
@ -816,7 +815,7 @@ def _gen_form_selectetuds(
<p>Bac: <select name="bac" onchange="javascript: submit(this);">
<option value="" %s>tous</option>
"""
% (REQUEST.URL0, selected)
% (request.base_url, selected)
]
for b in bacs:
if bac == b:
@ -1167,7 +1166,7 @@ def table_suivi_parcours(formsemestre_id, only_primo=False, grouped_parcours=Tru
def tsp_form_primo_group(REQUEST, only_primo, no_grouping, formsemestre_id, format):
"""Element de formulaire pour choisir si restriction aux primos entrants et groupement par lycees"""
F = ["""<form name="f" method="get" action="%s">""" % REQUEST.URL0]
F = ["""<form name="f" method="get" action="%s">""" % request.base_url]
if only_primo:
checked = 'checked="1"'
else:
@ -1205,12 +1204,12 @@ def formsemestre_suivi_parcours(
only_primo=only_primo,
grouped_parcours=not no_grouping,
)
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
if only_primo:
tab.base_url += "&only_primo=1"
if no_grouping:
tab.base_url += "&no_grouping=1"
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
t = tab.make_page(format=format, with_html_headers=False)
if format != "html":
return t
F = [
@ -1492,7 +1491,7 @@ def formsemestre_graph_parcours(
statut=statut,
)
filename = scu.make_filename("flux " + sem["titreannee"])
return scu.sendPDFFile(REQUEST, doc, filename + ".pdf")
return scu.sendPDFFile(doc, filename + ".pdf")
elif format == "png":
#
(

View File

@ -9,48 +9,50 @@ from app.scodoc.sco_permissions import Permission as p
SCO_ROLES_DEFAULTS = {
"Observateur": (p.ScoObservateur,),
"Ens": (
p.ScoObservateur,
p.ScoView,
p.ScoEnsView,
p.ScoUsersView,
p.ScoEtudAddAnnotations,
p.ScoAbsChange,
p.ScoAbsAddBillet,
p.ScoAbsChange,
p.ScoEnsView,
p.ScoEntrepriseView,
p.ScoEtudAddAnnotations,
p.ScoObservateur,
p.ScoUsersView,
p.ScoView,
),
"Secr": (
p.ScoObservateur,
p.ScoView,
p.ScoUsersView,
p.ScoEtudAddAnnotations,
p.ScoAbsChange,
p.ScoAbsAddBillet,
p.ScoEntrepriseView,
p.ScoAbsChange,
p.ScoEditApo,
p.ScoEntrepriseChange,
p.ScoEntrepriseView,
p.ScoEtudAddAnnotations,
p.ScoEtudChangeAdr,
p.ScoObservateur,
p.ScoUsersView,
p.ScoView,
),
# Admin est le chef du département, pas le "super admin"
# on doit donc lister toutes ses permissions:
"Admin": (
p.ScoObservateur,
p.ScoView,
p.ScoEnsView,
p.ScoUsersView,
p.ScoEtudAddAnnotations,
p.ScoAbsChange,
p.ScoAbsAddBillet,
p.ScoEntrepriseView,
p.ScoEntrepriseChange,
p.ScoEtudChangeAdr,
p.ScoAbsChange,
p.ScoChangeFormation,
p.ScoEditFormationTags,
p.ScoEditAllNotes,
p.ScoChangePreferences,
p.ScoEditAllEvals,
p.ScoImplement,
p.ScoEditAllNotes,
p.ScoEditApo,
p.ScoEditFormationTags,
p.ScoEnsView,
p.ScoEntrepriseChange,
p.ScoEntrepriseView,
p.ScoEtudAddAnnotations,
p.ScoEtudChangeAdr,
p.ScoEtudChangeGroups,
p.ScoEtudInscrit,
p.ScoImplement,
p.ScoObservateur,
p.ScoUsersAdmin,
p.ScoChangePreferences,
p.ScoUsersView,
p.ScoView,
),
# RespPE est le responsable poursuites d'études
# il peut ajouter des tags sur les formations:

View File

@ -35,7 +35,7 @@ import datetime
import psycopg2
import flask
from flask import g, url_for
from flask import g, url_for, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
@ -168,7 +168,7 @@ def do_evaluation_upload_xls(REQUEST):
"""
Soumission d'un fichier XLS (evaluation_id, notefile)
"""
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
evaluation_id = int(REQUEST.form["evaluation_id"])
comment = REQUEST.form["comment"]
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
@ -494,9 +494,10 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
}
ndb.quote_dict(aa)
cursor.execute(
"""INSERT INTO notes_notes
(etudid,evaluation_id,value,comment,date,uid)
VALUES (%(etudid)s,%(evaluation_id)s,%(value)s,%(comment)s,%(date)s,%(uid)s)""",
"""INSERT INTO notes_notes
(etudid, evaluation_id, value, comment, date, uid)
VALUES (%(etudid)s,%(evaluation_id)s,%(value)s,%(comment)s,%(date)s,%(uid)s)
""",
aa,
)
changed = True
@ -515,10 +516,10 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
# recopie l'ancienne note dans notes_notes_log, puis update
if do_it:
cursor.execute(
"""INSERT INTO notes_notes_log
"""INSERT INTO notes_notes_log
(etudid,evaluation_id,value,comment,date,uid)
SELECT etudid, evaluation_id, value, comment, date, uid
FROM notes_notes
FROM notes_notes
WHERE etudid=%(etudid)s
and evaluation_id=%(evaluation_id)s
""",
@ -536,8 +537,8 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
if value != scu.NOTES_SUPPRESS:
if do_it:
cursor.execute(
"""UPDATE notes_notes
SET value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
"""UPDATE notes_notes
SET value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
WHERE etudid = %(etudid)s
and evaluation_id = %(evaluation_id)s
""",
@ -550,7 +551,7 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
% (evaluation_id, etudid, oldval)
)
cursor.execute(
"""DELETE FROM notes_notes
"""DELETE FROM notes_notes
WHERE etudid = %(etudid)s
AND evaluation_id = %(evaluation_id)s
""",
@ -589,18 +590,17 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
"""Saisie des notes via un fichier Excel"""
authuser = REQUEST.AUTHENTICATED_USER
authusername = str(authuser)
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
raise ScoValueError("invalid evaluation_id")
E = evals[0]
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
formsemestre_id = M["formsemestre_id"]
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
return (
html_sco_header.sco_header()
+ "<h2>Modification des notes impossible pour %s</h2>" % authusername
+ "<h2>Modification des notes impossible pour %s</h2>"
% current_user.user_name
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
avez l'autorisation d'effectuer cette opération)</p>
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
@ -657,7 +657,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
)
nf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
@ -711,7 +711,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
#
H.append("""</div><h3>Autres opérations</h3><ul>""")
if sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"], allow_ens=False
current_user, E["moduleimpl_id"], allow_ens=False
):
H.append(
"""
@ -829,7 +829,8 @@ def feuille_saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
filename = "notes_%s_%s.xlsx" % (evalname, gr_title_filename)
xls = sco_excel.excel_feuille_saisie(E, sem["titreannee"], description, lines=L)
return sco_excel.send_excel_file(REQUEST, xls, filename)
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
# return sco_excel.send_excel_file(REQUEST, xls, filename)
def has_existing_decision(M, E, etudid):
@ -858,9 +859,7 @@ def has_existing_decision(M, E, etudid):
def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
"""Formulaire saisie notes d'une évaluation pour un groupe"""
authuser = REQUEST.AUTHENTICATED_USER
authusername = str(authuser)
group_ids = [int(group_id) for group_id in group_ids]
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
raise ScoValueError("invalid evaluation_id")
@ -871,10 +870,11 @@ def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
formsemestre_id = M["formsemestre_id"]
# Check access
# (admin, respformation, and responsable_id)
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
return (
html_sco_header.sco_header()
+ "<h2>Modification des notes impossible pour %s</h2>" % authusername
+ "<h2>Modification des notes impossible pour %s</h2>"
% current_user.user_name
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
avez l'autorisation d'effectuer cette opération)</p>
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
@ -1154,9 +1154,9 @@ def _form_saisie_notes(E, M, group_ids, destination="", REQUEST=None):
"attributes": [
'class="note%s"' % classdem,
disabled_attr,
"data-last-saved-value=%s" % e["val"],
"data-orig-value=%s" % e["val"],
"data-etudid=%s" % etudid,
'data-last-saved-value="%s"' % e["val"],
'data-orig-value="%s"' % e["val"],
'data-etudid="%s"' % etudid,
],
"template": """<tr%(item_dom_attr)s class="etud_elem """
+ " ".join(etud_classes)
@ -1222,7 +1222,7 @@ def _form_saisie_notes(E, M, group_ids, destination="", REQUEST=None):
def save_note(etudid=None, evaluation_id=None, value=None, comment="", REQUEST=None):
"""Enregistre une note (ajax)"""
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
log(
"save_note: evaluation_id=%s etudid=%s uid=%s value=%s"
% (evaluation_id, etudid, authuser, value)
@ -1259,7 +1259,7 @@ def save_note(etudid=None, evaluation_id=None, value=None, comment="", REQUEST=N
else:
result["history_menu"] = "" # no update needed
result["status"] = "ok"
return scu.sendJSON(REQUEST, result)
return scu.sendJSON(result)
def get_note_history_menu(evaluation_id, etudid):
@ -1290,7 +1290,7 @@ def get_note_history_menu(evaluation_id, etudid):
nv = "" # ne repete pas la valeur de la note courante
else:
# ancienne valeur
nv = '<span class="histvalue">: %s</span>' % dispnote
nv = ": %s" % dispnote
first = False
if i["comment"]:
comment = ' <span class="histcomment">%s</span>' % i["comment"]

View File

@ -171,7 +171,7 @@ class SemSet(dict):
def remove(self, formsemestre_id):
ndb.SimpleQuery(
"""DELETE FROM notes_semset_formsemestre
WHERE id=%(semset_id)s
WHERE semset_id=%(semset_id)s
AND formsemestre_id=%(formsemestre_id)s
""",
{"formsemestre_id": formsemestre_id, "semset_id": self.semset_id},
@ -418,7 +418,7 @@ def do_semset_remove_sem(semset_id, formsemestre_id):
# ----------------------------------------
def semset_page(format="html", REQUEST=None):
def semset_page(format="html"):
"""Page avec liste semsets:
Table avec : date_debut date_fin titre liste des semestres
"""
@ -468,7 +468,7 @@ def semset_page(format="html", REQUEST=None):
preferences=sco_preferences.SemPreferences(),
)
if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
page_title = "Ensembles de semestres"
H = [

View File

@ -32,7 +32,7 @@ import time
import pprint
from operator import itemgetter
from flask import g, url_for, send_file
from flask import g, url_for
from flask_login import current_user
import app.scodoc.sco_utils as scu
@ -112,6 +112,7 @@ def formsemestre_synchro_etuds(
base_url = url_for(
"notes.formsemestre_synchro_etuds",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
anneeapogee=anneeapogee or None, # si None, le param n'est pas dans l'URL
)
@ -146,11 +147,11 @@ def formsemestre_synchro_etuds(
base_url=base_url,
read_only=read_only,
)
return send_file(
return scu.send_file(
xls,
mimetype=scu.XLS_MIMETYPE,
download_name=scu.sanitize_filename(filename + scu.XLSX_SUFFIX),
as_attachment=True,
mime=scu.XLS_MIMETYPE,
filename=filename,
suffix=scu.XLSX_SUFFIX,
)
H = [header]
@ -317,7 +318,8 @@ def build_page(
"""
% sem,
"""
Année Apogée: <select id="anneeapogee" name="anneeapogee" onchange="document.location='formsemestre_synchro_etuds?formsemestre_id=%s&anneeapogee='+document.getElementById('anneeapogee').value">"""
Année Apogée: <select id="anneeapogee" name="anneeapogee"
onchange="document.location='formsemestre_synchro_etuds?formsemestre_id=%s&anneeapogee='+document.getElementById('anneeapogee').value">"""
% (sem["formsemestre_id"]),
"\n".join(options),
"""
@ -430,17 +432,20 @@ def list_synch(sem, anneeapogee=None):
return etuds
#
r = {
"etuds_ok": {
"etuds": set_to_sorted_list(etuds_ok, is_inscrit=True),
boites = {
"etuds_a_importer": {
"etuds": set_to_sorted_list(a_importer, is_inscrit=True, etud_apo=True),
"infos": {
"id": "etuds_ok",
"title": "Etudiants dans Apogée et déjà inscrits",
"help": "Ces etudiants sont inscrits dans le semestre ScoDoc et sont présents dans Apogée: tout est donc correct. Décocher les étudiants que vous souhaitez désinscrire.",
"id": "etuds_a_importer",
"title": "Etudiants dans Apogée à importer",
"help": """Ces étudiants sont inscrits dans cette étape Apogée mais ne sont pas connus par ScoDoc:
cocher les noms à importer et inscrire puis appuyer sur le bouton "Appliquer".""",
"title_target": "",
"with_checkbox": True,
"etud_key": EKEY_SCO,
"etud_key": EKEY_APO, # clé à stocker dans le formulaire html
"filename": "etuds_a_importer",
},
"nomprenoms": etudsapo_ident,
},
"etuds_noninscrits": {
"etuds": set_to_sorted_list(etuds_noninscrits, is_inscrit=True),
@ -453,20 +458,9 @@ def list_synch(sem, anneeapogee=None):
"title_target": "",
"with_checkbox": True,
"etud_key": EKEY_SCO,
"filename": "etuds_non_inscrits",
},
},
"etuds_a_importer": {
"etuds": set_to_sorted_list(a_importer, is_inscrit=True, etud_apo=True),
"infos": {
"id": "etuds_a_importer",
"title": "Etudiants dans Apogée à importer",
"help": """Ces étudiants sont inscrits dans cette étape Apogée mais ne sont pas connus par ScoDoc: cocher les noms à importer et inscrire puis appuyer sur le bouton "Appliquer".""",
"title_target": "",
"with_checkbox": True,
"etud_key": EKEY_APO, # clé à stocker dans le formulaire html
},
"nomprenoms": etudsapo_ident,
},
"etuds_nonapogee": {
"etuds": set_to_sorted_list(etuds_nonapogee, is_inscrit=True),
"infos": {
@ -478,6 +472,7 @@ def list_synch(sem, anneeapogee=None):
"title_target": "",
"with_checkbox": True,
"etud_key": EKEY_SCO,
"filename": "etuds_non_apogee",
},
},
"inscrits_without_key": {
@ -489,11 +484,25 @@ def list_synch(sem, anneeapogee=None):
"title_target": "",
"with_checkbox": True,
"checkbox_name": "inscrits_without_key",
"filename": "inscrits_without_key",
},
},
"etuds_ok": {
"etuds": set_to_sorted_list(etuds_ok, is_inscrit=True),
"infos": {
"id": "etuds_ok",
"title": "Etudiants dans Apogée et déjà inscrits",
"help": """Ces etudiants sont inscrits dans le semestre ScoDoc et sont présents dans Apogée:
tout est donc correct. Décocher les étudiants que vous souhaitez désinscrire.""",
"title_target": "",
"with_checkbox": True,
"etud_key": EKEY_SCO,
"filename": "etuds_inscrits_ok_apo",
},
},
}
return (
r,
boites,
a_importer,
etuds_noninscrits,
inscrits_set,

View File

@ -214,7 +214,7 @@ def module_tag_search(term, REQUEST=None):
)
data = [x["title"] for x in r]
return scu.sendJSON(REQUEST, data)
return scu.sendJSON(data)
def module_tag_list(module_id=""):

View File

@ -44,7 +44,7 @@ from reportlab.lib import colors
from PIL import Image as PILImage
import flask
from flask import url_for, g, send_file
from flask import url_for, g, send_file, request
from app import log
import app.scodoc.sco_utils as scu
@ -104,7 +104,7 @@ def _trombino_html_header():
return html_sco_header.sco_header(javascripts=["js/trombino.js"])
def trombino_html(groups_infos, REQUEST=None):
def trombino_html(groups_infos):
"HTML snippet for trombino (with title and menu)"
menuTrombi = [
{
@ -150,7 +150,7 @@ def trombino_html(groups_infos, REQUEST=None):
% t["etudid"]
)
if sco_photos.etud_photo_is_local(t, size="small"):
foto = sco_photos.etud_photo_html(t, title="", REQUEST=REQUEST)
foto = sco_photos.etud_photo_html(t, title="")
else: # la photo n'est pas immédiatement dispo
foto = (
'<span class="unloaded_img" id="%s"><img border="0" height="90" alt="en cours" src="/ScoDoc/static/icons/loading.jpg"/></span>'
@ -466,7 +466,7 @@ def _listeappel_photos_pdf(groups_infos, REQUEST):
document.build(objects)
data = report.getvalue()
return scu.sendPDFFile(REQUEST, data, filename)
return scu.sendPDFFile(data, filename)
# --------------------- Upload des photos de tout un groupe
@ -486,7 +486,10 @@ def photos_generate_excel_sample(group_ids=[], REQUEST=None):
],
extra_cols=["fichier_photo"],
)
return sco_excel.send_excel_file(REQUEST, data, "ImportPhotos" + scu.XLSX_SUFFIX)
return scu.send_file(
data, "ImportPhotos", scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE, attached=True
)
# return sco_excel.send_excel_file(REQUEST, data, "ImportPhotos" + scu.XLSX_SUFFIX)
def photos_import_files_form(group_ids=[], REQUEST=None):
@ -516,7 +519,7 @@ def photos_import_files_form(group_ids=[], REQUEST=None):
F = html_sco_header.sco_footer()
REQUEST.form["group_ids"] = groups_infos.group_ids
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}),

View File

@ -272,7 +272,7 @@ def pdf_trombino_tours(
document.build(objects)
data = report.getvalue()
return scu.sendPDFFile(REQUEST, data, filename)
return scu.sendPDFFile(data, filename)
# Feuille d'absences en pdf avec photos:
@ -466,4 +466,4 @@ def pdf_feuille_releve_absences(
document.build(objects)
data = report.getvalue()
return scu.sendPDFFile(REQUEST, data, filename)
return scu.sendPDFFile(data, filename)

View File

@ -54,6 +54,7 @@ Solution proposée (nov 2014):
"""
import flask
from flask import request
from flask_login import current_user
import app.scodoc.notesdb as ndb
@ -168,7 +169,7 @@ def external_ue_inscrit_et_note(
)
# Saisie des notes
_, _, _ = sco_saisie_notes._notes_add(
REQUEST.AUTHENTICATED_USER,
current_user,
evaluation_id,
list(notes_etuds.items()),
do_it=True,
@ -221,7 +222,6 @@ def external_ue_create_form(formsemestre_id, etudid, REQUEST=None):
H = [
html_sco_header.html_sem_header(
REQUEST,
"Ajout d'une UE externe pour %(nomprenom)s" % etud,
sem,
javascripts=["js/sco_ue_external.js"],
@ -248,7 +248,7 @@ def external_ue_create_form(formsemestre_id, etudid, REQUEST=None):
default_label = "Aucune UE externe existante"
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("formsemestre_id", {"input_type": "hidden"}),

View File

@ -46,6 +46,8 @@ Opérations:
"""
import datetime
from flask import request
from app.scodoc.intervals import intervalmap
import app.scodoc.sco_utils as scu
@ -167,7 +169,7 @@ def evaluation_list_operations(evaluation_id, REQUEST=None):
% (E["description"], E["jour"]),
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
)
return tab.make_page(REQUEST=REQUEST)
return tab.make_page()
def formsemestre_list_saisies_notes(formsemestre_id, format="html", REQUEST=None):
@ -217,12 +219,12 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html", REQUEST=None
html_sortable=True,
caption="Saisies de notes dans %s" % sem["titreannee"],
preferences=sco_preferences.SemPreferences(formsemestre_id),
base_url="%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id),
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
origin="Généré par %s le " % sco_version.SCONAME
+ scu.timedate_human_repr()
+ "",
)
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
def get_note_history(evaluation_id, etudid, REQUEST=None, fmt=""):
@ -261,7 +263,7 @@ def get_note_history(evaluation_id, etudid, REQUEST=None, fmt=""):
x["user_name"] = sco_users.user_info(x["uid"])["nomcomplet"]
if fmt == "json":
return scu.sendJSON(REQUEST, history)
return scu.sendJSON(history)
else:
return history

View File

@ -30,7 +30,7 @@
# Anciennement ZScoUsers.py, fonctions de gestion des données réécrite avec flask/SQLAlchemy
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
import cracklib # pylint: disable=import-error
@ -51,11 +51,7 @@ import app.scodoc.sco_utils as scu
from app.scodoc.sco_exceptions import (
AccessDenied,
ScoException,
ScoValueError,
ScoInvalidDateError,
ScoLockedFormError,
ScoGenError,
)
@ -86,7 +82,7 @@ def index_html(REQUEST, all_depts=False, with_inactives=False, format="html"):
all_depts = int(all_depts)
with_inactives = int(with_inactives)
H = [html_sco_header.html_sem_header(REQUEST, "Gestion des utilisateurs")]
H = [html_sco_header.html_sem_header("Gestion des utilisateurs")]
if current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept):
H.append(
@ -117,7 +113,7 @@ def index_html(REQUEST, all_depts=False, with_inactives=False, format="html"):
<input type="checkbox" name="all_depts" value="1" onchange="document.f.submit();" %s>Tous les départements</input>
<input type="checkbox" name="with_inactives" value="1" onchange="document.f.submit();" %s>Avec anciens utilisateurs</input>
</form></p>"""
% (REQUEST.URL0, checked, olds_checked)
% (request.base_url, checked, olds_checked)
)
L = list_users(
@ -212,12 +208,12 @@ def list_users(
html_class="table_leftalign list_users",
html_with_td_classes=True,
html_sortable=True,
base_url="%s?all=%s" % (REQUEST.URL0, all),
base_url="%s?all=%s" % (request.base_url, all),
pdf_link=False, # table is too wide to fit in a paper page => disable pdf
preferences=sco_preferences.SemPreferences(),
)
return tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
return tab.make_page(format=format, with_html_headers=False)
def get_user_list(dept=None, with_inactives=False):

View File

@ -39,21 +39,18 @@ import os
import pydot
import re
import requests
import six
import six.moves._thread
import sys
import _thread
import time
import traceback
import types
import unicodedata
import urllib
from xml.etree import ElementTree
from urllib.parse import urlparse, parse_qsl, urlunparse, urlencode
from flask import g, current_app
from PIL import Image as PILImage
from flask import g, url_for, request
from flask import g, url_for, request, make_response
from werkzeug.wrappers import response
from config import Config
from app import log
@ -217,7 +214,7 @@ def group_by_key(d, key):
# ----- Global lock for critical sections (except notes_tables caches)
GSL = six.moves._thread.allocate_lock() # Global ScoDoc Lock
GSL = _thread.allocate_lock() # Global ScoDoc Lock
SCODOC_DIR = Config.SCODOC_DIR
@ -228,7 +225,7 @@ SCODOC_CFG_DIR = os.path.join(Config.SCODOC_VAR_DIR, "config")
SCODOC_VERSION_DIR = os.path.join(SCODOC_CFG_DIR, "version")
# ----- Repertoire tmp : /opt/scodoc-data/tmp
SCO_TMP_DIR = os.path.join(Config.SCODOC_VAR_DIR, "tmp")
if not os.path.exists(SCO_TMP_DIR):
if not os.path.exists(SCO_TMP_DIR) and os.path.exists(Config.SCODOC_VAR_DIR):
os.mkdir(SCO_TMP_DIR, 0o755)
# ----- Les logos: /opt/scodoc-data/config/logos
SCODOC_LOGOS_DIR = os.path.join(SCODOC_CFG_DIR, "logos")
@ -296,16 +293,39 @@ SCO_DEV_MAIL = "emmanuel.viennet@gmail.com" # SVP ne pas changer
# Adresse pour l'envoi des dumps (pour assistance technnique):
# ne pas changer (ou vous perdez le support)
SCO_DUMP_UP_URL = "https://scodoc.iutv.univ-paris13.fr/scodoc-installmgr/upload-dump"
# SCO_DUMP_UP_URL = "http://192.168.56.1:5000/upload_dump"
CSV_FIELDSEP = ";"
CSV_LINESEP = "\n"
CSV_MIMETYPE = "text/comma-separated-values"
CSV_SUFFIX = ".csv"
JSON_MIMETYPE = "application/json"
JSON_SUFFIX = ".json"
PDF_MIMETYPE = "application/pdf"
PDF_SUFFIX = ".pdf"
XLS_MIMETYPE = "application/vnd.ms-excel"
XLS_SUFFIX = ".xls"
XLSX_MIMETYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
XLSX_SUFFIX = ".xlsx"
PDF_MIMETYPE = "application/pdf"
XML_MIMETYPE = "text/xml"
JSON_MIMETYPE = "application/json"
XML_SUFFIX = ".xml"
def get_mime_suffix(format_code: str) -> tuple[str, str]:
"""Returns (MIME, SUFFIX) from format_code == "xls", "xml", ...
SUFFIX includes the dot: ".xlsx", ".xml", ...
"xls" and "xlsx" format codes give XLSX
"""
d = {
"csv": (CSV_MIMETYPE, CSV_SUFFIX),
"xls": (XLSX_MIMETYPE, XLSX_SUFFIX),
"xlsx": (XLSX_MIMETYPE, XLSX_SUFFIX),
"pdf": (PDF_MIMETYPE, PDF_SUFFIX),
"xml": (XML_MIMETYPE, XML_SUFFIX),
"json": (JSON_MIMETYPE, JSON_SUFFIX),
}
return d[format_code]
# Admissions des étudiants
# Différents types de voies d'admission:
@ -388,6 +408,18 @@ def unescape_html(s):
return s
def build_url_query(url: str, **params) -> str:
"""Add parameters to existing url, as a query string"""
url_parse = urlparse(url)
query = url_parse.query
url_dict = dict(parse_qsl(query))
url_dict.update(params)
url_new_query = urlencode(url_dict)
url_parse = url_parse._replace(query=url_new_query)
new_url = urlunparse(url_parse)
return new_url
# test if obj is iterable (but not a string)
isiterable = lambda obj: getattr(obj, "__iter__", False)
@ -457,14 +489,17 @@ def sanitize_string(s):
return suppress_accents(s.translate(trans)).replace(" ", "_").replace("\t", "_")
_BAD_FILENAME_CHARS = str.maketrans("", "", ":/\\")
_BAD_FILENAME_CHARS = str.maketrans("", "", ":/\\&[]*?'")
def make_filename(name):
"""Try to convert name to a reasonable filename
without spaces, (back)slashes, : and without accents
"""
return suppress_accents(name.translate(_BAD_FILENAME_CHARS)).replace(" ", "_")
return (
suppress_accents(name.translate(_BAD_FILENAME_CHARS)).replace(" ", "_")
or "scodoc"
)
VALID_CARS = (
@ -490,7 +525,15 @@ def is_valid_filename(filename):
return VALID_EXP.match(filename)
def sendCSVFile(REQUEST, data, filename):
def bul_filename(sem, etud, format):
"""Build a filename for this bulletin"""
dt = time.strftime("%Y-%m-%d")
filename = f"bul-{sem['titre_num']}-{dt}-{etud['nom']}.{format}"
filename = make_filename(filename)
return filename
def sendCSVFile(REQUEST, data, filename): # DEPRECATED ne plus utiliser
"""publication fichier.
(on ne doit rien avoir émis avant, car ici sont générés les entetes)
"""
@ -504,16 +547,9 @@ def sendCSVFile(REQUEST, data, filename):
return data
def sendPDFFile(REQUEST, data, filename):
filename = (
unescape_html(suppress_accents(filename)).replace("&", "").replace(" ", "_")
)
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", PDF_MIMETYPE)
REQUEST.RESPONSE.setHeader(
"content-disposition", 'attachment; filename="%s"' % filename
)
return data
def sendPDFFile(data, filename):
filename = make_filename(filename)
return send_file(data, filename=filename, mime=PDF_MIMETYPE, attached=True)
class ScoDocJSONEncoder(json.JSONEncoder):
@ -526,38 +562,54 @@ class ScoDocJSONEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, o)
def sendJSON(REQUEST, data):
def sendJSON(data, attached=False):
js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder)
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", JSON_MIMETYPE)
return js
return send_file(js, filename="sco_data.json", mime=JSON_MIMETYPE, attached=attached)
def sendXML(REQUEST, data, tagname=None, force_outer_xml_tag=True):
def sendXML(data, tagname=None, force_outer_xml_tag=True, attached=False):
if type(data) != list:
data = [data] # always list-of-dicts
if force_outer_xml_tag:
data = [{tagname: data}]
tagname += "_list"
doc = sco_xml.simple_dictlist2xml(data, tagname=tagname)
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
return doc
return send_file(doc, filename="sco_data.xml", mime=XML_MIMETYPE, attached=attached)
def sendResult(REQUEST, data, name=None, format=None, force_outer_xml_tag=True):
def sendResult(data, name=None, format=None, force_outer_xml_tag=True, attached=False):
if (format is None) or (format == "html"):
return data
elif format == "xml": # name is outer tagname
return sendXML(
REQUEST, data, tagname=name, force_outer_xml_tag=force_outer_xml_tag
)
return sendXML(data, tagname=name, force_outer_xml_tag=force_outer_xml_tag, attached=attached)
elif format == "json":
return sendJSON(REQUEST, data)
return sendJSON(data, attached=attached)
else:
raise ValueError("invalid format: %s" % format)
def send_file(data, filename="", suffix="", mime=None, attached=None):
"""Build Flask Response for file download of given type
By default (attached is None), json and xml are inlined and otrher types are atteched.
"""
if attached is None:
if mime == XML_MIMETYPE or mime == JSON_MIMETYPE:
attached = False
else:
attached = True
# if attached and not filename:
# raise ValueError("send_file: missing attachement filename")
if filename:
if suffix:
filename += suffix
filename = make_filename(filename)
response = make_response(data)
response.headers["Content-Type"] = mime
if attached and filename:
response.headers["Content-Disposition"] = 'attachment; filename="%s"' % filename
return response
def get_scodoc_version():
"return a string identifying ScoDoc version"
return sco_version.SCOVERSION
@ -759,42 +811,13 @@ def AnneeScolaire(sco_year=None):
return year
def log_unknown_etud(REQUEST=None, format="html"):
"""Log request: cas ou getEtudInfo n'a pas ramene de resultat"""
etudid = REQUEST.form.get("etudid", "?")
code_nip = REQUEST.form.get("code_nip", "?")
code_ine = REQUEST.form.get("code_ine", "?")
log(
"unknown student: etudid=%s code_nip=%s code_ine=%s"
% (etudid, code_nip, code_ine)
)
return _sco_error_response("unknown student", format=format, REQUEST=REQUEST)
# XXX #sco8 à tester ou ré-écrire
def _sco_error_response(msg, format="html", REQUEST=None):
"""Send an error message to the client, in html or xml format."""
REQUEST.RESPONSE.setStatus(404, reason=msg)
if format == "html" or format == "pdf":
raise sco_exceptions.ScoValueError(msg)
elif format == "xml":
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
doc = ElementTree.Element("error", msg=msg)
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(SCO_ENCODING)
elif format == "json":
REQUEST.RESPONSE.setHeader("content-type", JSON_MIMETYPE)
return "undefined" # XXX voir quoi faire en cas d'erreur json
else:
raise ValueError("ScoErrorResponse: invalid format")
def return_text_if_published(val, REQUEST):
"""Pour les méthodes publiées qui ramènent soit du texte (HTML) soit du JSON
sauf quand elles sont appellées depuis python.
La présence de l'argument REQUEST indique la publication.
"""
if REQUEST and not isinstance(val, str):
return sendJSON(REQUEST, val)
return sendJSON(val)
return val
@ -823,9 +846,10 @@ def confirm_dialog(
action = f'action="{dest_url}"'
H = [
f"""<form {action} method="post">""",
message,
"""<input type="submit" value="%s"/>""" % OK,
f"""<form {action} method="POST">
{message}
<input type="submit" value="{OK}"/>
""",
]
if cancel_url:
H.append(

View File

@ -154,7 +154,7 @@ div.scovalueerror {
p.footer {
font-size: 80%;
color: rgb(60,60,60);
margin-top: 10px;
margin-top: 15px;
border-top: 1px solid rgb(60,60,60);
}
@ -1092,13 +1092,13 @@ h2.formsemestre, .gtrcontent h2 {
#formnotes td.tf-fieldlabel {
border-bottom: 1px dotted #fdcaca;
}
/* Formulaires ScoDoc 9 */
form.sco-form {
margin-top: 1em;
.wtf-field li {
display: inline;
}.wtf-field ul {
padding-left: 0;
}
div.sco-submit {
margin-top: 2em;
.wtf-field .errors {
color: red ; font-weight: bold;
}
/*
.formsemestre_menubar {

View File

@ -11,7 +11,7 @@
ou écrire la liste "notes" <a href="mailto:notes@listes.univ-paris13.fr">notes@listes.univ-paris13.fr</a> en
indiquant la version du logiciel
<br />
(plus d'informations sur les listes de diffusion<a href="https://scodoc.org/ListesDeDiffusion/">voir
(plus d'informations sur les listes de diffusion <a href="https://scodoc.org/ListesDeDiffusion/">voir
cette page</a>).
</p>

View File

@ -5,13 +5,14 @@
<h2>Erreur !</h2>
<p>{{ exc }}</p>
{{ exc | safe }}
<p>
<p class="footer">
{% if g.scodoc_dept %}
<a href="{{ exc.dest_url or url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}">continuer</a>
<a href="{{ exc.dest_url or url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}">retour page d'accueil
departement {{ g.scodoc_dept }}</a>
{% else %}
<a href="{{ exc.dest_url or url_for('scodoc.index') }}">continuer</a>
<a href="{{ exc.dest_url or url_for('scodoc.index') }}">retour page d'accueil</a>
{% endif %}
</p>

View File

@ -0,0 +1,78 @@
{% import 'bootstrap/wtf.html' as wtf %}
{% macro render_field(field) %}
<tr>
<td class="wtf-field">{{ field.label }}</td>
<td class="wtf-field">{{ field()|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</td>
</tr>
{% endmacro %}
<div class="saisienote_etape1 form_placement">
<form method=post>
{{ form.evaluation_id }}
{{ form.csrf_token }}
<table class="tf">
<tbody>
{{ render_field(form.surveillants) }}
{{ render_field(form.batiment) }}
{{ render_field(form.salle) }}
{{ render_field(form.nb_rangs) }}
{{ render_field(form.etiquetage) }}
{% if form.has_groups %}
{# {{ render_field(form.groups) }}#}
{% for partition in form.groups_tree %}
<tr>
{% if partition == 'Tous' %}
<td rowspan="{{ form.nb_partitions }}">Groupes</td>
{% endif %}
<td>{{ partition }}</td>
<td>
{% for groupe in form.groups_tree[partition] %}
{{ groupe }}{{ form.get_checkbox(partition, groupe)() }}
{% endfor %}
</td>
</tr>
{% endfor %}
{% endif %}
{{ render_field(form.file_format) }}
</tbody>
</table>
<p>
<input id="gr_submit" type=submit value="Ok">
<input id="gr_cancel" type=submit value="Annuler">
</script>
</form>
<h3>Explications</h3>
<ul>
<li>préciser les surveillants et la localisation (bâtiment et salle) et indiquer la largeur de la salle (nombre
de colonnes);</li>
<li>deux types de placements sont possibles :
<ul>
<li>continue suppose que les tables ont toutes un numéro unique;</li>
<li>coordonnées localise chaque table via un numéro de colonne et un numéro de ligne (ou rangée).</li>
</ul>
</li>
<li>Il est possible de choisir un ou plusieurs groupes (shift/ctrl click) ou de choisir 'tous'.</li>
<li>Choisir le format du fichier résultat :
<ul>
<li>le format pdf consiste en un tableau précisant pour chaque étudiant la localisation de sa table;
</li>
<li>le format xls produit un classeur avec deux onglets:
<ul>
<li>le premier onglet donne une vue de la salle avec la localisation des étudiants et
peut servir de feuille d'émargement;</li>
<li>le second onglet est un tableau similaire à celui du fichier pdf;</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>

View File

@ -0,0 +1,12 @@
<h2 class="insidebar">Dépt. {{ prefs["DeptName"] }}</h2>
<a href="{{ url_for('scodoc.index') }}" class="sidebar">Accueil</a> <br/>
{% if prefs["DeptIntranetURL"] %}
<a href="{{ prefs["DeptIntranetURL"] }}" class="sidebar">
{{ prefs["DeptIntranetTitle"] }}</a>
{% endif %}
<br/>
{#
# Entreprises pas encore supporté en ScoDoc8
# <br/><a href="%(ScoURL)s/Entreprises" class="sidebar">Entreprises</a> <br/>
#}

View File

@ -47,20 +47,18 @@ L'API de plus bas niveau est en gros:
"""
import calendar
import cgi
import datetime
import dateutil
import dateutil.parser
import re
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
import string
import time
import urllib
from xml.etree import ElementTree
import flask
from flask import g
from flask import g, request
from flask import url_for
from flask import current_app
from flask_login import current_user
from app.decorators import (
scodoc,
@ -123,11 +121,11 @@ def sco_publish(route, function, permission, methods=["GET"]):
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def index_html(REQUEST=None):
def index_html():
"""Gestionnaire absences, page principale"""
# crude portage from 1999 DTML
sems = sco_formsemestre.do_formsemestre_list()
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
H = [
html_sco_header.sco_header(
@ -164,7 +162,7 @@ def index_html(REQUEST=None):
Saisie par semaine </span> - Choix du groupe:
<input name="datelundi" type="hidden" value="x"/>
"""
% REQUEST.URL0,
% request.base_url,
sco_abs_views.formChoixSemestreGroupe(),
"</p>",
cal_select_week(),
@ -270,7 +268,6 @@ def doSignaleAbsenceGrSemestre(
dates="",
etudids="",
destination=None,
REQUEST=None,
):
"""Enregistre absences aux dates indiquees (abslist et dates).
dates est une liste de dates ISO (séparées par des ',').
@ -295,10 +292,10 @@ def doSignaleAbsenceGrSemestre(
# 2- Ajoute les absences
if abslist:
sco_abs._add_abslist(abslist, REQUEST, moduleimpl_id)
sco_abs.add_abslist(abslist, moduleimpl_id)
return "Absences ajoutées"
return ""
return ("", 204)
# ------------ HTML Interfaces
@ -307,14 +304,14 @@ def doSignaleAbsenceGrSemestre(
@permission_required(Permission.ScoAbsChange)
@scodoc7func
def SignaleAbsenceGrHebdo(
datelundi, group_ids=[], destination="", moduleimpl_id=None, REQUEST=None
datelundi, group_ids=[], destination="", moduleimpl_id=None, formsemestre_id=None
):
"Saisie hebdomadaire des absences"
if not moduleimpl_id:
moduleimpl_id = None
groups_infos = sco_groups_view.DisplayedGroupsInfos(
group_ids, moduleimpl_id=moduleimpl_id
group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id
)
if not groups_infos.members:
return (
@ -326,7 +323,7 @@ def SignaleAbsenceGrHebdo(
base_url = "SignaleAbsenceGrHebdo?datelundi=%s&%s&destination=%s" % (
datelundi,
groups_infos.groups_query_args,
six.moves.urllib.parse.quote(destination),
urllib.parse.quote(destination),
)
formsemestre_id = groups_infos.formsemestre_id
@ -473,7 +470,6 @@ def SignaleAbsenceGrSemestre(
group_ids=[], # list of groups to display
nbweeks=4, # ne montre que les nbweeks dernieres semaines
moduleimpl_id=None,
REQUEST=None,
):
"""Saisie des absences sur une journée sur un semestre (ou intervalle de dates) entier"""
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
@ -510,7 +506,7 @@ def SignaleAbsenceGrSemestre(
datedebut,
datefin,
groups_infos.groups_query_args,
six.moves.urllib.parse.quote(destination),
urllib.parse.quote(destination),
)
)
base_url = base_url_noweeks + "&nbweeks=%s" % nbweeks # sans le moduleimpl_id
@ -810,7 +806,7 @@ def _gen_form_saisie_groupe(
H.append('<input type="hidden" name="dates" value="%s"/>' % ",".join(dates))
H.append(
'<input type="hidden" name="destination" value="%s"/>'
% six.moves.urllib.parse.quote(destination)
% urllib.parse.quote(destination)
)
#
# version pour formulaire avec AJAX (Yann LB)
@ -843,7 +839,6 @@ def EtatAbsencesGr(
fin="",
with_boursier=True, # colonne boursier
format="html",
REQUEST=None,
):
"""Liste les absences de groupes"""
datedebut = ndb.DateDMYtoISO(debut)
@ -938,7 +933,7 @@ def EtatAbsencesGr(
javascripts=["js/etud_info.js"],
),
html_title=html_sco_header.html_sem_header(
REQUEST, "%s" % title, sem, with_page_header=False
"%s" % title, sem, with_page_header=False
)
+ "<p>Période du %s au %s (nombre de <b>demi-journées</b>)<br/>" % (debut, fin),
base_url="%s&formsemestre_id=%s&debut=%s&fin=%s"
@ -964,9 +959,9 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
<input type="submit" name="" value="visualiser les absences">
</form></div>
"""
% (REQUEST.URL0, formsemestre_id, groups_infos.get_form_elem()),
% (request.base_url, formsemestre_id, groups_infos.get_form_elem()),
)
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
@bp.route("/EtatAbsencesDate")
@ -1062,7 +1057,6 @@ def AddBilletAbsence(
code_nip=None,
code_ine=None,
justified=True,
REQUEST=None,
xml_reply=True,
):
"""Memorise un "billet"
@ -1072,7 +1066,9 @@ def AddBilletAbsence(
# check etudid
etuds = sco_etud.get_etud_info(etudid=etudid, code_nip=code_nip, filled=True)
if not etuds:
return scu.log_unknown_etud(REQUEST=REQUEST)
sco_etud.log_unknown_etud()
raise ScoValueError("étudiant inconnu")
etud = etuds[0]
# check dates
begin_date = dateutil.parser.isoparse(begin) # may raises ValueError
@ -1096,13 +1092,10 @@ def AddBilletAbsence(
)
if xml_reply:
# Renvoie le nouveau billet en XML
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
tab = _tableBillets(billets, etud=etud)
log("AddBilletAbsence: new billet_id=%s (%gs)" % (billet_id, time.time() - t0))
return tab.make_page(REQUEST=REQUEST, format="xml")
return tab.make_page(format="xml")
else:
return billet_id
@ -1122,7 +1115,7 @@ def AddBilletAbsenceForm(etudid, REQUEST=None):
)
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("etudid", {"input_type": "hidden"}),
@ -1223,17 +1216,18 @@ def _tableBillets(billets, etud=None, title=""):
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def listeBilletsEtud(etudid=False, REQUEST=None, format="html"):
def listeBilletsEtud(etudid=False, format="html"):
"""Liste billets pour un etudiant"""
etuds = sco_etud.get_etud_info(filled=True, etudid=etudid)
if not etuds:
return scu.log_unknown_etud(format=format, REQUEST=REQUEST)
sco_etud.log_unknown_etud()
raise ScoValueError("étudiant inconnu")
etud = etuds[0]
cnx = ndb.GetDBConnexion()
billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]})
tab = _tableBillets(billets, etud=etud)
return tab.make_page(REQUEST=REQUEST, format=format)
return tab.make_page(format=format)
@bp.route(
@ -1242,12 +1236,12 @@ def listeBilletsEtud(etudid=False, REQUEST=None, format="html"):
@scodoc
@permission_required_compat_scodoc7(Permission.ScoView)
@scodoc7func
def XMLgetBilletsEtud(etudid=False, REQUEST=None):
def XMLgetBilletsEtud(etudid=False):
"""Liste billets pour un etudiant"""
if not sco_preferences.get_preference("handle_billets_abs"):
return ""
t0 = time.time()
r = listeBilletsEtud(etudid, REQUEST=REQUEST, format="xml")
r = listeBilletsEtud(etudid, format="xml")
log("XMLgetBilletsEtud (%gs)" % (time.time() - t0))
return r
@ -1259,7 +1253,7 @@ def XMLgetBilletsEtud(etudid=False, REQUEST=None):
def listeBillets(REQUEST=None):
"""Page liste des billets non traités et formulaire recherche d'un billet"""
cnx = ndb.GetDBConnexion()
billets = sco_abs.billet_absence_list(cnx, {"etat": 0})
billets = sco_abs.billet_absence_list(cnx, {"etat": False})
tab = _tableBillets(billets)
T = tab.html()
H = [
@ -1268,7 +1262,7 @@ def listeBillets(REQUEST=None):
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(("billet_id", {"input_type": "text", "title": "Numéro du billet"}),),
submitbutton=False,
@ -1281,11 +1275,11 @@ def listeBillets(REQUEST=None):
)
@bp.route("/deleteBilletAbsence")
@bp.route("/deleteBilletAbsence", methods=["POST", "GET"])
@scodoc
@permission_required(Permission.ScoAbsChange)
@scodoc7func
def deleteBilletAbsence(billet_id, REQUEST=None, dialog_confirmed=False):
def deleteBilletAbsence(billet_id, dialog_confirmed=False):
"""Supprime un billet."""
cnx = ndb.GetDBConnexion()
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
@ -1307,7 +1301,7 @@ def deleteBilletAbsence(billet_id, REQUEST=None, dialog_confirmed=False):
return flask.redirect("listeBillets?head_message=Billet%20supprimé")
def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
def _ProcessBilletAbsence(billet, estjust, description):
"""Traite un billet: ajoute absence(s) et éventuellement justificatifs,
et change l'état du billet à 1.
NB: actuellement, les heures ne sont utilisées que pour déterminer si matin et/ou après-midi.
@ -1329,7 +1323,6 @@ def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
dates[0],
0,
estjust,
REQUEST,
description=description,
)
n += 1
@ -1341,7 +1334,6 @@ def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
dates[-1],
1,
estjust,
REQUEST,
description=description,
)
n += 1
@ -1353,7 +1345,6 @@ def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
jour,
0,
estjust,
REQUEST,
description=description,
)
sco_abs.add_absence(
@ -1361,7 +1352,6 @@ def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
jour,
1,
estjust,
REQUEST,
description=description,
)
n += 2
@ -1372,7 +1362,7 @@ def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
return n
@bp.route("/ProcessBilletAbsenceForm")
@bp.route("/ProcessBilletAbsenceForm", methods=["POST", "GET"])
@scodoc
@permission_required(Permission.ScoAbsChange)
@scodoc7func
@ -1401,7 +1391,7 @@ def ProcessBilletAbsenceForm(billet_id, REQUEST=None):
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("billet_id", {"input_type": "hidden"}),
@ -1439,9 +1429,7 @@ def ProcessBilletAbsenceForm(billet_id, REQUEST=None):
elif tf[0] == -1:
return flask.redirect(scu.ScoURL())
else:
n = _ProcessBilletAbsence(
billet, tf[2]["estjust"], tf[2]["description"], REQUEST
)
n = _ProcessBilletAbsence(billet, tf[2]["estjust"], tf[2]["description"])
if tf[2]["estjust"]:
j = "justifiées"
else:
@ -1477,7 +1465,7 @@ def ProcessBilletAbsenceForm(billet_id, REQUEST=None):
@scodoc
@permission_required_compat_scodoc7(Permission.ScoView)
@scodoc7func
def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
def XMLgetAbsEtud(beg_date="", end_date=""):
"""returns list of absences in date interval"""
t0 = time.time()
etuds = sco_etud.get_etud_info(filled=False)
@ -1493,7 +1481,6 @@ def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
abs_list = sco_abs.list_abs_date(etud["etudid"], beg_date, end_date)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
doc = ElementTree.Element(
"absences", etudid=str(etud["etudid"]), beg_date=beg_date, end_date=end_date
)
@ -1509,4 +1496,5 @@ def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
)
)
log("XMLgetAbsEtud (%gs)" % (time.time() - t0))
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
data = sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
return scu.send_file(data, mime=scu.XML_MIMETYPE, attached=False)

View File

@ -38,6 +38,9 @@ import re
import time
import calendar
from flask import request
from flask_login import current_user
# MIGRATION EN COURS => MODULE DESACTIVE !
# A REVOIR
@ -79,7 +82,7 @@ def sidebar(REQUEST):
<ul class="insidebar">"""
% params,
]
if REQUEST.AUTHENTICATED_USER.has_permission(Permission.ScoEntrepriseChange):
if current_user.has_permission(Permission.ScoEntrepriseChange):
H.append(
"""<li class="insidebar"><a href="%(ScoURL)s/Entreprises/entreprise_create" class="sidebar">Nouvelle entreprise</a> </li>"""
% params
@ -104,9 +107,7 @@ def sidebar(REQUEST):
<li class="insidebar"><a href="%(ScoURL)s/Entreprises/entreprise_correspondant_list?entreprise_id=%(entreprise_id)s" class="sidebar">Corresp.</a></li>"""
% params
) # """
if REQUEST.AUTHENTICATED_USER.has_permission(
Permission.ScoEntrepriseChange
):
if current_user.has_permission(Permission.ScoEntrepriseChange):
H.append(
"""<li class="insidebar"><a href="%(ScoURL)s/Entreprises/entreprise_correspondant_create?entreprise_id=%(entreprise_id)s" class="sidebar">Nouveau Corresp.</a></li>"""
% params
@ -115,9 +116,7 @@ def sidebar(REQUEST):
"""<li class="insidebar"><a href="%(ScoURL)s/Entreprises/entreprise_contact_list?entreprise_id=%(entreprise_id)s" class="sidebar">Contacts</a></li>"""
% params
)
if REQUEST.AUTHENTICATED_USER.has_permission(
Permission.ScoEntrepriseChange
):
if current_user.has_permission(Permission.ScoEntrepriseChange):
H.append(
"""<li class="insidebar"><a href="%(ScoURL)s/Entreprises/entreprise_contact_create?entreprise_id=%(entreprise_id)s" class="sidebar">Nouveau "contact"</a></li>"""
% params
@ -126,7 +125,7 @@ def sidebar(REQUEST):
#
H.append("""<br/><br/>%s""" % scu.icontag("entreprise_side_img"))
if not REQUEST.AUTHENTICATED_USER.has_permission(Permission.ScoEntrepriseChange):
if not current_user.has_permission(Permission.ScoEntrepriseChange):
H.append("""<br/><em>(Lecture seule)</em>""")
H.append("""</div> </div> <!-- end of sidebar -->""")
return "".join(H)
@ -166,14 +165,14 @@ def index_html(REQUEST=None, etud_nom=None, limit=50, offset="", format="html"):
if offset:
webparams["offset"] = max((offset or 0) - limit, 0)
prev_lnk = '<a class="stdlink" href="%s">précédentes</a>' % (
REQUEST.URL0 + "?" + six.moves.urllib.parse.urlencode(webparams)
request.base_url + "?" + six.moves.urllib.parse.urlencode(webparams)
)
else:
prev_lnk = ""
if len(entreprises) >= limit:
webparams["offset"] = (offset or 0) + limit
next_lnk = '<a class="stdlink" href="%s">suivantes</a>' % (
REQUEST.URL0 + "?" + six.moves.urllib.parse.urlencode(webparams)
request.base_url + "?" + six.moves.urllib.parse.urlencode(webparams)
)
else:
next_lnk = ""
@ -220,11 +219,11 @@ def index_html(REQUEST=None, etud_nom=None, limit=50, offset="", format="html"):
html_class="entreprise_list table_leftalign",
html_with_td_classes=True,
html_next_section=table_navigation,
base_url=REQUEST.URL0 + "?",
base_url=request.base_url + "?",
preferences=context.get_preferences(),
)
if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
else:
H = [
entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"),
@ -293,15 +292,15 @@ def entreprise_contact_list(entreprise_id=None, format="html", REQUEST=None):
html_sortable=True,
html_class="contact_list table_leftalign",
html_with_td_classes=True,
base_url=REQUEST.URL0 + "?",
base_url=request.base_url + "?",
preferences=context.get_preferences(),
)
if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
H.append(tab.html())
if REQUEST.AUTHENTICATED_USER.has_permission(Permission.ScoEntrepriseChange):
if current_user.has_permission(Permission.ScoEntrepriseChange):
if entreprise_id:
H.append(
"""<p class="entreprise_create"><a class="entreprise_create" href="entreprise_contact_create?entreprise_id=%(entreprise_id)s">nouveau "contact"</a></p>
@ -399,15 +398,15 @@ def entreprise_correspondant_list(
html_sortable=True,
html_class="contact_list table_leftalign",
html_with_td_classes=True,
base_url=REQUEST.URL0 + "?",
base_url=request.base_url + "?",
preferences=context.get_preferences(),
)
if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
H.append(tab.html())
if REQUEST.AUTHENTICATED_USER.has_permission(Permission.ScoEntrepriseChange):
if current_user.has_permission(Permission.ScoEntrepriseChange):
H.append(
"""<p class="entreprise_create"><a class="entreprise_create" href="entreprise_correspondant_create?entreprise_id=%(entreprise_id)s">Ajouter un correspondant dans l'entreprise %(nom)s</a></p>
"""
@ -444,7 +443,7 @@ def entreprise_contact_edit(entreprise_contact_id, REQUEST=None):
% E,
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
(
@ -515,14 +514,12 @@ def entreprise_contact_edit(entreprise_contact_id, REQUEST=None):
cancelbutton="Annuler",
initvalues=c,
submitlabel="Modifier les valeurs",
readonly=not REQUEST.AUTHENTICATED_USER.has_permission(
Permission.ScoEntrepriseChange
),
readonly=not current_user.has_permission(Permission.ScoEntrepriseChange),
)
if tf[0] == 0:
H.append(tf[1])
if REQUEST.AUTHENTICATED_USER.has_permission(
if current_user.has_permission(
Permission.ScoEntrepriseChange,
):
H.append(
@ -560,7 +557,7 @@ def entreprise_correspondant_edit(entreprise_corresp_id, REQUEST=None):
"""<h2 class="entreprise_correspondant">Édition contact entreprise</h2>""",
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
(
@ -641,9 +638,7 @@ def entreprise_correspondant_edit(entreprise_corresp_id, REQUEST=None):
cancelbutton="Annuler",
initvalues=c,
submitlabel="Modifier les valeurs",
readonly=not REQUEST.AUTHENTICATED_USER.has_permission(
Permission.ScoEntrepriseChange
),
readonly=not current_user.has_permission(Permission.ScoEntrepriseChange),
)
if tf[0] == 0:
H.append(tf[1])
@ -684,7 +679,7 @@ def entreprise_contact_create(entreprise_id, REQUEST=None):
% E,
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("entreprise_id", {"input_type": "hidden", "default": entreprise_id}),
@ -750,9 +745,7 @@ def entreprise_contact_create(entreprise_id, REQUEST=None):
),
cancelbutton="Annuler",
submitlabel="Ajouter ce contact",
readonly=not REQUEST.AUTHENTICATED_USER.has_permission(
Permission.ScoEntrepriseChange
),
readonly=not current_user.has_permission(Permission.ScoEntrepriseChange),
)
if tf[0] == 0:
H.append(tf[1])
@ -783,13 +776,13 @@ def entreprise_contact_delete(entreprise_contact_id, REQUEST=None):
"""<h2>Suppression du contact</h2>""",
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(("entreprise_contact_id", {"input_type": "hidden"}),),
initvalues=c,
submitlabel="Confirmer la suppression",
cancelbutton="Annuler",
readonly=not REQUEST.AUTHENTICATED_USER.has_permission(ScoEntrepriseChange),
readonly=not current_user.has_permission(ScoEntrepriseChange),
)
if tf[0] == 0:
H.append(tf[1])
@ -814,7 +807,7 @@ def entreprise_correspondant_create(entreprise_id, REQUEST=None):
% E,
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("entreprise_id", {"input_type": "hidden", "default": entreprise_id}),
@ -892,9 +885,7 @@ def entreprise_correspondant_create(entreprise_id, REQUEST=None):
),
cancelbutton="Annuler",
submitlabel="Ajouter ce correspondant",
readonly=not REQUEST.AUTHENTICATED_USER.has_permission(
Permission.ScoEntrepriseChange
),
readonly=not current_user.has_permission(Permission.ScoEntrepriseChange),
)
if tf[0] == 0:
H.append(tf[1])
@ -920,15 +911,13 @@ def entreprise_correspondant_delete(entreprise_corresp_id, REQUEST=None):
"""<h2>Suppression du correspondant %(nom)s %(prenom)s</h2>""" % c,
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(("entreprise_corresp_id", {"input_type": "hidden"}),),
initvalues=c,
submitlabel="Confirmer la suppression",
cancelbutton="Annuler",
readonly=not REQUEST.AUTHENTICATED_USER.has_permission(
Permission.ScoEntrepriseChange
),
readonly=not current_user.has_permission(Permission.ScoEntrepriseChange),
)
if tf[0] == 0:
H.append(tf[1])
@ -976,15 +965,13 @@ def entreprise_delete(entreprise_id, REQUEST=None):
H.append("""<li>%(date)s %(description)s</li>""" % c)
H.append("""</ul>""")
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(("entreprise_id", {"input_type": "hidden"}),),
initvalues=E,
submitlabel="Confirmer la suppression",
cancelbutton="Annuler",
readonly=not REQUEST.AUTHENTICATED_USER.has_permission(
Permission.ScoEntrepriseChange
),
readonly=not current_user.has_permission(Permission.ScoEntrepriseChange),
)
if tf[0] == 0:
H.append(tf[1])
@ -1008,7 +995,7 @@ def entreprise_create(REQUEST=None):
"""<h2 class="entreprise_new">Création d'une entreprise</h2>""",
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("nom", {"size": 25, "title": "Nom de l'entreprise"}),
@ -1079,9 +1066,7 @@ def entreprise_create(REQUEST=None):
),
cancelbutton="Annuler",
submitlabel="Ajouter cette entreprise",
readonly=not REQUEST.AUTHENTICATED_USER.has_permission(
Permission.ScoEntrepriseChange
),
readonly=not current_user.has_permission(Permission.ScoEntrepriseChange),
)
if tf[0] == 0:
return "\n".join(H) + tf[1] + entreprise_footer(REQUEST)
@ -1097,7 +1082,7 @@ security.declareProtected(ScoEntrepriseView, "entreprise_edit")
def entreprise_edit(entreprise_id, REQUEST=None, start=1):
"""Form. edit entreprise"""
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
readonly = not authuser.has_permission(Permission.ScoEntrepriseChange)
F = sco_entreprises.do_entreprise_list(args={"entreprise_id": entreprise_id})[0]
H = [
@ -1105,7 +1090,7 @@ def entreprise_edit(entreprise_id, REQUEST=None, start=1):
"""<h2 class="entreprise">%(nom)s</h2>""" % F,
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("entreprise_id", {"default": entreprise_id, "input_type": "hidden"}),

View File

@ -38,8 +38,8 @@ from operator import itemgetter
from xml.etree import ElementTree
import flask
from flask import url_for, g
from flask import current_app
from flask import url_for
from flask import current_app, g, request
from flask_login import current_user
from config import Config
@ -146,23 +146,31 @@ def sco_publish(route, function, permission, methods=["GET"]):
# --------------------- Quelques essais élémentaires:
@bp.route("/essai")
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def essai(REQUEST=None):
return essai_(REQUEST)
# @bp.route("/essai")
# @scodoc
# @permission_required(Permission.ScoView)
# @scodoc7func
# def essai(REQUEST=None):
# return essai_(REQUEST)
def essai_(REQUEST):
return "<html><body><h2>essai !</h2><p>%s</p></body></html>" % (REQUEST,)
# def essai_(REQUEST):
# return "<html><body><h2>essai !</h2><p>%s</p></body></html>" % (REQUEST,)
def essai2():
return essai_("sans request")
# def essai2():
# err_page = f"""<h3>Destruction du module impossible car il est utilisé dans des semestres existants !</h3>
# <p class="help">Il faut d'abord supprimer le semestre. Mais il est peut être préférable de
# laisser ce programme intact et d'en créer une nouvelle version pour la modifier.
# </p>
# <a href="url_for('notes.ue_list', scodoc-dept=g.scodoc_dept, formation_id='XXX')">reprendre</a>
# """
# raise ScoGenError(err_page)
# # raise ScoGenError("une erreur banale")
# return essai_("sans request")
sco_publish("/essai2", essai2, Permission.ScoImplement)
# sco_publish("/essai2", essai2, Permission.ScoImplement)
# --------------------------------------------------------------------
@ -406,14 +414,14 @@ sco_publish(
def index_html(REQUEST=None):
"Page accueil formations"
editable = REQUEST.AUTHENTICATED_USER.has_permission(Permission.ScoChangeFormation)
editable = current_user.has_permission(Permission.ScoChangeFormation)
H = [
html_sco_header.sco_header(page_title="Programmes formations"),
"""<h2>Programmes pédagogiques</h2>
""",
]
T = sco_formations.formation_list_table(REQUEST=REQUEST)
T = sco_formations.formation_list_table()
H.append(T.html())
@ -456,22 +464,22 @@ sco_publish(
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def formation_list(format=None, REQUEST=None, formation_id=None, args={}):
def formation_list(format=None, formation_id=None, args={}):
"""List formation(s) with given id, or matching args
(when args is given, formation_id is ignored).
"""
r = sco_formations.formation_list(formation_id=formation_id, args=args)
return scu.sendResult(REQUEST, r, name="formation", format=format)
return scu.sendResult(r, name="formation", format=format)
@bp.route("/formation_export")
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def formation_export(formation_id, export_ids=False, format=None, REQUEST=None):
def formation_export(formation_id, export_ids=False, format=None):
"Export de la formation au format indiqué (xml ou json)"
return sco_formations.formation_export(
formation_id, export_ids=export_ids, format=format, REQUEST=REQUEST
formation_id, export_ids=export_ids, format=format
)
@ -501,7 +509,7 @@ def formation_import_xml_form(REQUEST):
]
footer = html_sco_header.sco_footer()
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(("xmlfile", {"input_type": "file", "title": "Fichier XML", "size": 30}),),
submitlabel="Importer",
@ -606,8 +614,7 @@ sco_publish("/ue_move", sco_edit_formation.ue_move, Permission.ScoChangeFormatio
@permission_required_compat_scodoc7(Permission.ScoView)
@scodoc7func
def formsemestre_list(
format=None,
REQUEST=None,
format="json",
formsemestre_id=None,
formation_id=None,
etape_apo=None,
@ -624,7 +631,7 @@ def formsemestre_list(
args[argname] = L[argname]
sems = sco_formsemestre.do_formsemestre_list(args=args)
# log('formsemestre_list: format="%s", %s semestres found' % (format,len(sems)))
return scu.sendResult(REQUEST, sems, name="formsemestre", format=format)
return scu.sendResult(sems, name="formsemestre", format=format)
@bp.route(
@ -637,7 +644,7 @@ def XMLgetFormsemestres(etape_apo=None, formsemestre_id=None, REQUEST=None):
"""List all formsemestres matching etape, XML format
DEPRECATED: use formsemestre_list()
"""
log("Warning: calling deprecated XMLgetFormsemestres")
current_app.logger.debug("Warning: calling deprecated XMLgetFormsemestres")
args = {}
if etape_apo:
args["etape_apo"] = etape_apo
@ -708,7 +715,6 @@ def edit_enseignants_form(REQUEST, moduleimpl_id):
M, sem = sco_moduleimpl.can_change_ens(moduleimpl_id)
# --
header = html_sco_header.html_sem_header(
REQUEST,
'Enseignants du <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
% (moduleimpl_id, M["module"]["titre"]),
page_title="Enseignants du module %s" % M["module"]["titre"],
@ -778,7 +784,7 @@ def edit_enseignants_form(REQUEST, moduleimpl_id):
),
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
modform,
submitlabel="Ajouter enseignant",
@ -830,7 +836,6 @@ def edit_moduleimpl_resp(REQUEST, moduleimpl_id):
M, sem = sco_moduleimpl.can_change_module_resp(REQUEST, moduleimpl_id)
H = [
html_sco_header.html_sem_header(
REQUEST,
'Modification du responsable du <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
% (moduleimpl_id, M["module"]["titre"]),
sem,
@ -875,7 +880,7 @@ def edit_moduleimpl_resp(REQUEST, moduleimpl_id):
),
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
form,
submitlabel="Changer responsable",
@ -952,7 +957,6 @@ def edit_moduleimpl_expr(REQUEST, moduleimpl_id):
M, sem = sco_moduleimpl.can_change_ens(moduleimpl_id)
H = [
html_sco_header.html_sem_header(
REQUEST,
'Modification règle de calcul du <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
% (moduleimpl_id, M["module"]["titre"]),
sem,
@ -979,7 +983,7 @@ def edit_moduleimpl_expr(REQUEST, moduleimpl_id):
),
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
form,
submitlabel="Modifier formule de calcul",
@ -1059,7 +1063,6 @@ def view_module_abs(REQUEST, moduleimpl_id, format="html"):
H = [
html_sco_header.html_sem_header(
REQUEST,
'Absences du <a href="moduleimpl_status?moduleimpl_id=%s">module %s</a>'
% (moduleimpl_id, M["module"]["titre"]),
page_title="Absences du module %s" % (M["module"]["titre"]),
@ -1083,14 +1086,14 @@ def view_module_abs(REQUEST, moduleimpl_id, format="html"):
columns_ids=("nomprenom", "just", "nojust", "total"),
rows=T,
html_class="table_leftalign",
base_url="%s?moduleimpl_id=%s" % (REQUEST.URL0, moduleimpl_id),
base_url="%s?moduleimpl_id=%s" % (request.base_url, moduleimpl_id),
filename="absmodule_" + scu.make_filename(M["module"]["titre"]),
caption="Absences dans le module %s" % M["module"]["titre"],
preferences=sco_preferences.SemPreferences(),
)
if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
return "\n".join(H) + tab.html() + html_sco_header.sco_footer()
@ -1110,7 +1113,6 @@ def edit_ue_expr(REQUEST, formsemestre_id, ue_id):
ue = sco_edit_ue.do_ue_list({"ue_id": ue_id})[0]
H = [
html_sco_header.html_sem_header(
REQUEST,
"Modification règle de calcul de l'UE %s (%s)"
% (ue["acronyme"], ue["titre"]),
sem,
@ -1139,7 +1141,7 @@ def edit_ue_expr(REQUEST, formsemestre_id, ue_id):
),
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
form,
submitlabel="Modifier formule de calcul",
@ -1251,13 +1253,13 @@ def formsemestre_enseignants_list(REQUEST, formsemestre_id, format="html"):
html_class="table_leftalign",
filename=scu.make_filename("Enseignants-" + sem["titreannee"]),
html_title=html_sco_header.html_sem_header(
REQUEST, "Enseignants du semestre", sem, with_page_header=False
"Enseignants du semestre", sem, with_page_header=False
),
base_url="%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id),
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
caption="Tous les enseignants (responsables ou associés aux modules de ce semestre) apparaissent. Le nombre de saisies d'absences est le nombre d'opérations d'ajout effectuées sur ce semestre, sans tenir compte des annulations ou double saisies.",
preferences=sco_preferences.SemPreferences(formsemestre_id),
)
return T.make_page(page_title=title, title=title, REQUEST=REQUEST, format=format)
return T.make_page(page_title=title, title=title, format=format)
@bp.route("/edit_enseignants_form_delete", methods=["GET", "POST"])
@ -1320,7 +1322,7 @@ def do_formsemestre_inscription_listinscrits(
r = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
formsemestre_id
)
return scu.sendResult(REQUEST, r, format=format, name="inscrits")
return scu.sendResult(r, format=format, name="inscrits")
@bp.route("/formsemestre_desinscription", methods=["GET", "POST"])
@ -1506,7 +1508,7 @@ def evaluation_delete(REQUEST, evaluation_id):
tit = "Suppression de l'évaluation %(description)s (%(jour)s)" % E
etat = sco_evaluations.do_evaluation_etat(evaluation_id)
H = [
html_sco_header.html_sem_header(REQUEST, tit, with_h2=False),
html_sco_header.html_sem_header(tit, with_h2=False),
"""<h2 class="formsemestre">Module <tt>%(code)s</tt> %(titre)s</h2>""" % Mod,
"""<h3>%s</h3>""" % tit,
"""<p class="help">Opération <span class="redboldtext">irréversible</span>. Si vous supprimez l'évaluation, vous ne pourrez pas retrouver les notes associées.</p>""",
@ -1538,7 +1540,7 @@ def evaluation_delete(REQUEST, evaluation_id):
H.append("""</div>""")
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(("evaluation_id", {"input_type": "hidden"}),),
initvalues=E,
@ -1645,8 +1647,8 @@ sco_publish(
"/placement_eval_selectetuds",
sco_placement.placement_eval_selectetuds,
Permission.ScoEnsView,
methods=["GET", "POST"],
)
sco_publish("/do_placement", sco_placement.do_placement, Permission.ScoEnsView)
# --- Saisie des notes
sco_publish(
@ -1691,7 +1693,7 @@ def formsemestre_bulletins_pdf(formsemestre_id, REQUEST, version="selectedevals"
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
formsemestre_id, REQUEST, version=version
)
return scu.sendPDFFile(REQUEST, pdfdoc, filename)
return scu.sendPDFFile(pdfdoc, filename)
_EXPL_BULL = """Versions des bulletins:<ul><li><bf>courte</bf>: moyennes des modules</li><li><bf>intermédiaire</bf>: moyennes des modules et notes des évaluations sélectionnées</li><li><bf>complète</bf>: toutes les notes</li><ul>"""
@ -1707,7 +1709,7 @@ def formsemestre_bulletins_pdf_choice(REQUEST, formsemestre_id, version=None):
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
formsemestre_id, REQUEST, version=version
)
return scu.sendPDFFile(REQUEST, pdfdoc, filename)
return scu.sendPDFFile(pdfdoc, filename)
return formsemestre_bulletins_choice(
REQUEST,
formsemestre_id,
@ -1725,7 +1727,7 @@ def etud_bulletins_pdf(etudid, REQUEST, version="selectedevals"):
pdfdoc, filename = sco_bulletins_pdf.get_etud_bulletins_pdf(
etudid, REQUEST, version=version
)
return scu.sendPDFFile(REQUEST, pdfdoc, filename)
return scu.sendPDFFile(pdfdoc, filename)
@bp.route("/formsemestre_bulletins_mailetuds_choice")
@ -1771,12 +1773,12 @@ def formsemestre_bulletins_choice(
"""Choix d'une version de bulletin"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
html_sco_header.html_sem_header(REQUEST, title, sem),
html_sco_header.html_sem_header(title, sem),
"""
<form name="f" method="GET" action="%s">
<input type="hidden" name="formsemestre_id" value="%s"></input>
"""
% (REQUEST.URL0, formsemestre_id),
% (request.base_url, formsemestre_id),
]
H.append("""<select name="version" class="noprint">""")
for (v, e) in (
@ -1928,7 +1930,7 @@ def appreciation_add_form(
else:
initvalues = {}
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
initvalues=initvalues,
@ -2010,8 +2012,7 @@ def formsemestre_validation_etud(
"Enregistre choix jury pour un étudiant"
if not sco_permissions_check.can_validate_sem(formsemestre_id):
return scu.confirm_dialog(
message="<p>Opération non autorisée pour %s</h2>"
% REQUEST.AUTHENTICATED_USER,
message="<p>Opération non autorisée pour %s</h2>" % current_user,
dest_url=scu.ScoURL(),
)
@ -2043,8 +2044,7 @@ def formsemestre_validation_etud_manu(
"Enregistre choix jury pour un étudiant"
if not sco_permissions_check.can_validate_sem(formsemestre_id):
return scu.confirm_dialog(
message="<p>Opération non autorisée pour %s</h2>"
% REQUEST.AUTHENTICATED_USER,
message="<p>Opération non autorisée pour %s</h2>" % current_user,
dest_url=scu.ScoURL(),
)
@ -2069,8 +2069,7 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid=None, REQUEST=None
"Form. saisie UE validée hors ScoDoc"
if not sco_permissions_check.can_validate_sem(formsemestre_id):
return scu.confirm_dialog(
message="<p>Opération non autorisée pour %s</h2>"
% REQUEST.AUTHENTICATED_USER,
message="<p>Opération non autorisée pour %s</h2>" % current_user,
dest_url=scu.ScoURL(),
)
return sco_formsemestre_validation.formsemestre_validate_previous_ue(
@ -2094,8 +2093,7 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid=None, REQUEST=N
"Form. edition UE semestre extérieur"
if not sco_permissions_check.can_validate_sem(formsemestre_id):
return scu.confirm_dialog(
message="<p>Opération non autorisée pour %s</h2>"
% REQUEST.AUTHENTICATED_USER,
message="<p>Opération non autorisée pour %s</h2>" % current_user,
dest_url=scu.ScoURL(),
)
return sco_formsemestre_exterieurs.formsemestre_ext_edit_ue_validations(
@ -2118,8 +2116,7 @@ def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id, REQUEST=None):
"""Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
if not sco_permissions_check.can_validate_sem(formsemestre_id):
return scu.confirm_dialog(
message="<p>Opération non autorisée pour %s</h2>"
% REQUEST.AUTHENTICATED_USER,
message="<p>Opération non autorisée pour %s</h2>" % current_user,
dest_url=scu.ScoURL(),
)
return sco_formsemestre_validation.etud_ue_suppress_validation(
@ -2135,8 +2132,7 @@ def formsemestre_validation_auto(formsemestre_id, REQUEST):
"Formulaire saisie automatisee des decisions d'un semestre"
if not sco_permissions_check.can_validate_sem(formsemestre_id):
return scu.confirm_dialog(
message="<p>Opération non autorisée pour %s</h2>"
% REQUEST.AUTHENTICATED_USER,
message="<p>Opération non autorisée pour %s</h2>" % current_user,
dest_url=scu.ScoURL(),
)
@ -2153,8 +2149,7 @@ def do_formsemestre_validation_auto(formsemestre_id, REQUEST):
"Formulaire saisie automatisee des decisions d'un semestre"
if not sco_permissions_check.can_validate_sem(formsemestre_id):
return scu.confirm_dialog(
message="<p>Opération non autorisée pour %s</h2>"
% REQUEST.AUTHENTICATED_USER,
message="<p>Opération non autorisée pour %s</h2>" % current_user,
dest_url=scu.ScoURL(),
)
@ -2173,8 +2168,7 @@ def formsemestre_validation_suppress_etud(
"""Suppression des decisions de jury pour un etudiant."""
if not sco_permissions_check.can_validate_sem(formsemestre_id):
return scu.confirm_dialog(
message="<p>Opération non autorisée pour %s</h2>"
% REQUEST.AUTHENTICATED_USER,
message="<p>Opération non autorisée pour %s</h2>" % current_user,
dest_url=scu.ScoURL(),
)
if not dialog_confirmed:
@ -2260,11 +2254,13 @@ sco_publish(
"/view_apo_csv_store",
sco_etape_apogee_view.view_apo_csv_store,
Permission.ScoEditApo,
methods=["GET", "POST"],
)
sco_publish(
"/view_apo_csv_download_and_store",
sco_etape_apogee_view.view_apo_csv_download_and_store,
Permission.ScoEditApo,
methods=["GET", "POST"],
)
sco_publish(
"/view_apo_csv_delete",
@ -2510,7 +2506,7 @@ def check_form_integrity(formation_id, fix=False, REQUEST=None):
log("check_form_integrity: formation_id=%s\ninconsistencies:" % formation_id)
log(txt)
# Notify by e-mail
sendAlarm("Notes: formation incoherente !", txt)
send_scodoc_alarm("Notes: formation incoherente !", txt)
else:
txth = "OK"
log("ok")

View File

@ -68,13 +68,8 @@ from app.scodoc.sco_exceptions import (
AccessDenied,
ScoException,
ScoValueError,
ScoInvalidDateError,
ScoLockedFormError,
ScoGenError,
ScoInvalidDept,
)
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
import sco_version
import app
from app.scodoc.gen_tables import GenTable
from app.scodoc import html_sco_header
@ -267,7 +262,7 @@ def showEtudLog(etudid, format="html", REQUEST=None):
rows=ops,
html_sortable=True,
html_class="table_leftalign",
base_url="%s?etudid=%s" % (REQUEST.URL0, etudid),
base_url="%s?etudid=%s" % (request.base_url, etudid),
page_title="Opérations sur %(nomprenom)s" % etud,
html_title="<h2>Opérations effectuées sur l'étudiant %(nomprenom)s</h2>" % etud,
filename="log_" + scu.make_filename(etud["nomprenom"]),
@ -279,7 +274,7 @@ def showEtudLog(etudid, format="html", REQUEST=None):
preferences=sco_preferences.SemPreferences(),
)
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
# ---------- PAGE ACCUEIL (listes) --------------
@ -341,7 +336,7 @@ def getEtudInfo(etudid=False, code_nip=False, filled=False, REQUEST=None, format
if format is None:
return etud
else:
return scu.sendResult(REQUEST, etud, name="etud", format=format)
return scu.sendResult(etud, name="etud", format=format)
sco_publish(
@ -396,7 +391,7 @@ def etud_info(etudid=None, format="xml", REQUEST=None):
"error": "code etudiant inconnu",
}
return scu.sendResult(
REQUEST, d, name="etudiant", format=format, force_outer_xml_tag=False
d, name="etudiant", format=format, force_outer_xml_tag=False
)
d = {}
etud = etuds[0]
@ -464,9 +459,7 @@ def etud_info(etudid=None, format="xml", REQUEST=None):
)
log("etud_info (%gs)" % (time.time() - t0))
return scu.sendResult(
REQUEST, d, name="etudiant", format=format, force_outer_xml_tag=False
)
return scu.sendResult(d, name="etudiant", format=format, force_outer_xml_tag=False)
# -------------------------- FICHE ETUDIANT --------------------------
@ -617,7 +610,7 @@ def formChangeCoordonnees(etudid, REQUEST):
)
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("adresse_id", {"input_type": "hidden"}),
@ -817,13 +810,13 @@ def formChangePhoto(etudid=None, REQUEST=None):
<p>Photo actuelle (%(photoloc)s):
"""
% etud,
sco_photos.etud_photo_html(etud, title="photo actuelle", REQUEST=REQUEST),
sco_photos.etud_photo_html(etud, title="photo actuelle"),
"""</p><p>Le fichier ne doit pas dépasser 500Ko (recadrer l'image, format "portrait" de préférence).</p>
<p>L'image sera automagiquement réduite pour obtenir une hauteur de 90 pixels.</p>
""",
]
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
("etudid", {"default": etudid, "input_type": "hidden"}),
@ -858,7 +851,7 @@ def formChangePhoto(etudid=None, REQUEST=None):
return "\n".join(H) + html_sco_header.sco_footer()
@bp.route("/formSuppressPhoto")
@bp.route("/formSuppressPhoto", methods=["POST", "GET"])
@scodoc
@permission_required(Permission.ScoEtudChangeAdr)
@scodoc7func
@ -1496,7 +1489,7 @@ def _etudident_create_or_edit_form(REQUEST, edit):
]
initvalues["dont_check_homonyms"] = False
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
submitlabel=submitlabel,
@ -1647,7 +1640,6 @@ def check_group_apogee(group_id, REQUEST=None, etat=None, fix=False, fixmail=Fal
cnx = ndb.GetDBConnexion()
H = [
html_sco_header.html_sem_header(
REQUEST,
"Etudiants du %s" % (group["group_name"] or "semestre"),
sem,
),
@ -1748,7 +1740,7 @@ def check_group_apogee(group_id, REQUEST=None, etat=None, fix=False, fixmail=Fal
<p><a href="Notes/formsemestre_status?formsemestre_id=%s"> Retour au semestre</a>
"""
% (
REQUEST.URL0,
request.base_url,
formsemestre_id,
scu.strnone(group_id),
scu.strnone(etat),
@ -1767,7 +1759,7 @@ def check_group_apogee(group_id, REQUEST=None, etat=None, fix=False, fixmail=Fal
<p><a href="Notes/formsemestre_status?formsemestre_id=%s"> Retour au semestre</a>
"""
% (
REQUEST.URL0,
request.base_url,
formsemestre_id,
scu.strnone(group_id),
scu.strnone(etat),
@ -1856,7 +1848,7 @@ def form_students_import_excel(REQUEST, formsemestre_id=None):
F = html_sco_header.sco_footer()
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
(
@ -1937,7 +1929,10 @@ def import_generate_excel_sample(REQUEST, with_codesemestre="1"):
data = sco_import_etuds.sco_import_generate_excel_sample(
format, with_codesemestre, exclude_cols=["photo_filename"]
)
return sco_excel.send_excel_file(REQUEST, data, "ImportEtudiants" + scu.XLSX_SUFFIX)
return scu.send_file(
data, "ImportEtudiants", scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE
)
# return sco_excel.send_excel_file(REQUEST, data, "ImportEtudiants" + scu.XLSX_SUFFIX)
# --- Données admission
@ -1955,9 +1950,10 @@ def import_generate_admission_sample(REQUEST, formsemestre_id):
exclude_cols=["nationalite", "foto", "photo_filename"],
group_ids=[group["group_id"]],
)
return sco_excel.send_excel_file(
REQUEST, data, "AdmissionEtudiants" + scu.XLSX_SUFFIX
return scu.send_file(
data, "AdmissionEtudiants", scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE
)
# return sco_excel.send_excel_file(REQUEST, data, "AdmissionEtudiants" + scu.XLSX_SUFFIX)
# --- Données admission depuis fichier excel (version nov 2016)
@ -1967,7 +1963,7 @@ def import_generate_admission_sample(REQUEST, formsemestre_id):
@scodoc7func
def form_students_import_infos_admissions(REQUEST, formsemestre_id=None):
"formulaire import xls"
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
F = html_sco_header.sco_footer()
if not authuser.has_permission(Permission.ScoEtudInscrit):
# autorise juste l'export
@ -2021,7 +2017,7 @@ def form_students_import_infos_admissions(REQUEST, formsemestre_id=None):
)
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
(
@ -2084,7 +2080,7 @@ def formsemestre_import_etud_admission(
formsemestre_id, import_identite=True, import_email=import_email
)
H = [
html_sco_header.html_sem_header(REQUEST, "Reimport données admission"),
html_sco_header.html_sem_header("Reimport données admission"),
"<h3>Opération effectuée</h3>",
]
if no_nip:

View File

@ -65,8 +65,6 @@ from app import log
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.sco_permissions_check import can_handle_passwd
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
from app.scodoc.sco_excel import send_excel_file
from app.scodoc.sco_import_users import generate_excel_sample
from app.views import users_bp as bp
@ -90,7 +88,7 @@ def index_html(REQUEST, all_depts=False, with_inactives=False, format="html"):
@scodoc7func
def user_info(user_name, format="json", REQUEST=None):
info = sco_users.user_info(user_name)
return scu.sendResult(REQUEST, info, name="user", format=format)
return scu.sendResult(info, name="user", format=format)
@bp.route("/create_user_form", methods=["GET", "POST"])
@ -344,7 +342,7 @@ def create_user_form(REQUEST, user_name=None, edit=0, all_roles=1):
)
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
descr,
initvalues=initvalues,
@ -490,9 +488,7 @@ def create_user_form(REQUEST, user_name=None, edit=0, all_roles=1):
def import_users_generate_excel_sample(REQUEST):
"une feuille excel pour importation utilisateurs"
data = sco_import_users.generate_excel_sample()
return sco_excel.send_excel_file(
REQUEST, data, "ImportUtilisateurs" + scu.XLSX_SUFFIX
)
return scu.send_file(data, "ImportUtilisateurs", scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
@bp.route("/import_users_form", methods=["GET", "POST"])
@ -532,7 +528,7 @@ def import_users_form(REQUEST=None):
)
F = html_sco_header.sco_footer()
tf = TrivialFormulator(
REQUEST.URL0,
request.base_url,
REQUEST.form,
(
(
@ -546,7 +542,7 @@ def import_users_form(REQUEST=None):
if tf[0] == 0:
return "\n".join(H) + tf[1] + "</li></ol>" + help + F
elif tf[0] == -1:
return flask.redirect(back_url)
return flask.redirect(url_for("scolar.index_html", docodc_dept=g.scodoc_dept))
else:
# IMPORT
ok, diag, nb_created = sco_import_users.import_excel_file(tf[2]["xlsfile"])
@ -654,8 +650,8 @@ def change_password(user_name, password, password2, REQUEST):
if not can_handle_passwd(u):
# access denied
log(
"change_password: access denied (authuser=%s, user_name=%s, ip=%s)"
% (REQUEST.AUTHENTICATED_USER, user_name, REQUEST.REMOTE_ADDR)
"change_password: access denied (authuser=%s, user_name=%s)"
% (current_user, user_name)
)
raise AccessDenied("vous n'avez pas la permission de changer ce mot de passe")
H = []

View File

@ -30,6 +30,8 @@ class Config:
SCODOC_DIR = os.environ.get("SCODOC_DIR", "/opt/scodoc")
SCODOC_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "/opt/scodoc-data")
SCODOC_LOG_FILE = os.path.join(SCODOC_VAR_DIR, "log", "scodoc.log")
# evite confusion avec le log nginx scodoc_error.log:
SCODOC_ERR_FILE = os.path.join(SCODOC_VAR_DIR, "log", "scodoc_exc.log")
#
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # Flask uploads
@ -53,7 +55,7 @@ class DevConfig(Config):
DEBUG = True
TESTING = False
SQLALCHEMY_DATABASE_URI = (
os.environ.get("SCODOC_DEV_DATABASE_URI") or "postgresql:///SCODOC_DEV"
os.environ.get("SCODOC_DATABASE_URI") or "postgresql:///SCODOC_DEV"
)
SECRET_KEY = os.environ.get("DEV_SECRET_KEY") or "bb3faec7d9a34eb68a8e3e710087d87a"

View File

@ -0,0 +1,28 @@
"""Flag bloquage calcul moyennes
Revision ID: 669065fb2d20
Revises: a217bf588f4c
Create Date: 2021-09-16 22:04:11.624632
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '669065fb2d20'
down_revision = 'a217bf588f4c'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('notes_formsemestre', sa.Column('block_moyennes', sa.Boolean(), server_default='false', nullable=False))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('notes_formsemestre', 'block_moyennes')
# ### end Alembic commands ###

View File

@ -0,0 +1,30 @@
"""modif contrainte sur formations
Revision ID: f86c013c9fbd
Revises: 669065fb2d20
Create Date: 2021-09-19 21:30:42.240422
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'f86c013c9fbd'
down_revision = '669065fb2d20'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('notes_formations_acronyme_titre_version_key', 'notes_formations', type_='unique')
op.create_unique_constraint(None, 'notes_formations', ['dept_id', 'acronyme', 'titre', 'version'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'notes_formations', type_='unique')
op.create_unique_constraint('notes_formations_acronyme_titre_version_key', 'notes_formations', ['acronyme', 'titre', 'version'])
# ### end Alembic commands ###

View File

@ -1,68 +1,69 @@
Notes sur la restauration de la base SQL complete
Notes sur la restauration de la base SQL complete (ScoDoc 9)
(dans le cas d'une réinstallation sur une autre machine, par exemple)
1) Sur la machine origine, faire un dump complet:
su postgres
cd /tmp # ou ailleurs...
pg_dumpall > scodoc.dump.txt
pg_dumpall | gzip > scodoc.dump.txt.gz
On obtient un fichier texte assez volumineux (on peut utiliser gzip pour le compresser avant transfert).
On obtient un fichier texte assez volumineux (comprimé par gzip ci-dessus)
Le copier sur la machine destination.
Le copier sur la machine destination, et le décompresser (gunzip).
2) Sur la machine destination:
Avant toute chose, stopper scodoc:
/etc/init.d/scodoc stop (ou systemctl stop scodoc))
systemctl stop scodoc9
1.1) Supprimer toutes les bases ScoDoc existantes s'il y en a:
su postgres
su scodoc
psql -l
liste les bases: celles de ScoDoc sont SCO*
Pour chaque base SCO*, faire dropdb
dropdb SCOUSERS
dropdb SCOGEII
dropdb SCODOC
dropdb SCODOC_DEV
...
Pour les feignants, voici un script (à lancer comme utilisateur postgres):
Pour les gens pressés, voici un script (à lancer comme utilisateur postgres):
for f in $(psql -l --no-align --field-separator . | grep SCO | cut -f 1 -d.); do
echo dropping $f
dropdb $f
done
1.2) Charger le dump (toujours comme utilisateur postgres):
psql -f scodoc.dump.txt postgres
1.2) Charger le dump (toujours comme utilisateur scodoc):
psql -f scodoc.dump.txt scodoc
1.3) Recopier les fichiers (photos, config, archives): copier le repertoire complet
/opt/scodoc/instance/var
/opt/scodoc-data
de la machine origine vers la nouvelle
Puis redemarrer ScoDoc:
en tant que root: /etc/init.d/scodoc start (ou systemctl start scodoc)
en tant que root: systemctl start scodoc9
NB: si la version des sources a changée, lancer imperativement le script de mise a jour
avant de redemarrer scodoc, afin qu'il change si besoin la base de donnees:
(en tant que root):
cd /opt/scodoc/instance/Products/ScoDoc/config
./upgrade.sh
apt-get update &&
----
Cas d'une seule base à copier: (eg un seul département, mais faire
attention aux utilisateurs definis dans la base SCOUSERS):
Cas d'une seule base à copier (base production ou dev. par exemple)
En tant qu'utilisateur "postgres":
Dump: (script avec commande de creation de la base)
pg_dump --create SCOINFO > /tmp/scoinfo.dump
En tant qu'utilisateur "scodoc":
Dump: permettant de la recharger en changeant son nom
pg_dump --format=custom --file=/tmp/SCODOC.dump SCODOC
Restore: (si necessaire, utiliser dropdb avant)
psql -f /tmp/scoinfo.dump postgres
createdb SCODOC_IUTV
pg_restore -d SCODOC_IUTV SCODOC.dump
(si on veut garder le même nom de base que l'origine, utiliser --create )
---
--- à revoir
Cas d'un dump via sco_dump_db (assistance):
createdb -E UTF-8 SCOXXX
zcat xxx | psql SCOXXX

View File

@ -1,5 +1,5 @@
-- Creation des tables pour gestion notes
-- Creation des tables pour gestion notes ScoDoc 7 (OBSOLETE !)
-- E. Viennet, Sep 2005

View File

@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.0.23"
SCOVERSION = "9.0.37"
SCONAME = "ScoDoc"

View File

@ -264,6 +264,17 @@ def create_dept(dept): # create-dept
return 0
@app.cli.command()
@click.argument("depts", nargs=-1)
def list_depts(depts=""): # list-dept
"""If dept exists, print it, else nothing.
Called without arguments, list all depts along with their ids.
"""
for dept in models.Departement.query.order_by(models.Departement.id):
if not depts or dept.acronym in depts:
print(f"{dept.id}\t{dept.acronym}")
@app.cli.command()
@with_appcontext
def import_scodoc7_users(): # import-scodoc7-users
@ -282,12 +293,20 @@ def import_scodoc7_users(): # import-scodoc7-users
@click.argument("dept")
@click.argument("dept_db_name")
@with_appcontext
def import_scodoc7_dept(dept: str, dept_db_name: str): # import-scodoc7-dept
def import_scodoc7_dept(dept: str, dept_db_name: str = ""): # import-scodoc7-dept
"""Import département ScoDoc 7: dept: InfoComm, dept_db_name: SCOINFOCOMM"""
dept_db_uri = f"postgresql:///{dept_db_name}"
tools.import_scodoc7_dept(dept, dept_db_uri)
@app.cli.command()
@click.argument("dept", default="")
@with_appcontext
def migrate_scodoc7_dept_archive(dept: str): # migrate-scodoc7-dept-archive
"""Post-migration: renomme les archives en fonction des id de ScoDoc 9"""
tools.migrate_scodoc7_dept_archive(dept)
@app.cli.command()
@with_appcontext
def clear_cache(): # clear-cache

View File

@ -1,62 +1,62 @@
# -*- coding: utf-8 -*-
"""
Scenario: préparation base de données pour tests Selenium
S'utilise comme un test avec pytest, mais n'est pas un test !
Modifie la base de données du département TEST00
Usage: pytest tests/scenarios/test_scenario1_formation.py
"""
# code écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en août 2021
import random
from tests.unit import sco_fake_gen
from app.scodoc import sco_edit_module
from app.scodoc import sco_formations
from app.scodoc import sco_moduleimpl
def test_scenario1(test_client):
"""Applique "scenario 1"""
run_scenario1()
def run_scenario1():
G = sco_fake_gen.ScoFake(verbose=False)
# Lecture fichier XML local:
with open("tests/unit/formation-exemple-1.xml") as f:
doc = f.read()
# --- Création de la formation
f = sco_formations.formation_import_xml(doc=doc)
# --- Création des semestres
formation_id = f[0]
# --- Mise en place de 4 semestres
sems = [
G.create_formsemestre(
formation_id=formation_id,
semestre_id=x[0],
date_debut=x[1],
date_fin=x[2],
)
for x in (
(1, "01/09/2020", "01/02/2021"),
(2, "02/02/2021", "01/06/2021"),
(3, "01/09/2020", "01/02/2021"),
(4, "02/02/2021", "01/06/2021"),
)
]
# --- Implémentation des modules
modules = sco_edit_module.do_module_list({"formation_id": formation_id})
mods_imp = []
for mod in modules:
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sems[mod["semestre_id"] - 1]["formsemestre_id"],
)
mods_imp.append(mi)
# -*- coding: utf-8 -*-
"""
Scenario: préparation base de données pour tests Selenium
S'utilise comme un test avec pytest, mais n'est pas un test !
Modifie la base de données du département TEST00
Usage: pytest tests/scenarios/test_scenario1_formation.py
"""
# code écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en août 2021
import random
from tests.unit import sco_fake_gen
from app.scodoc import sco_edit_module
from app.scodoc import sco_formations
from app.scodoc import sco_moduleimpl
def test_scenario1(test_client):
"""Applique "scenario 1"""
run_scenario1()
def run_scenario1():
G = sco_fake_gen.ScoFake(verbose=False)
# Lecture fichier XML local:
with open("tests/unit/formation-exemple-1.xml") as f:
doc = f.read()
# --- Création de la formation
f = sco_formations.formation_import_xml(doc=doc)
# --- Création des semestres
formation_id = f[0]
# --- Mise en place de 4 semestres
sems = [
G.create_formsemestre(
formation_id=formation_id,
semestre_id=x[0],
date_debut=x[1],
date_fin=x[2],
)
for x in (
(1, "01/09/2020", "01/02/2021"),
(2, "02/02/2021", "01/06/2021"),
(3, "01/09/2020", "01/02/2021"),
(4, "02/02/2021", "01/06/2021"),
)
]
# --- Implémentation des modules
modules = sco_edit_module.do_module_list({"formation_id": formation_id})
mods_imp = []
for mod in modules:
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sems[mod["semestre_id"] - 1]["formsemestre_id"],
)
mods_imp.append(mi)

View File

@ -219,6 +219,7 @@ class ScoFake(object):
etat=None,
gestion_compensation=None,
bul_hide_xml=None,
block_moyennes=None,
gestion_semestrielle=None,
bul_bgcolor=None,
modalite=NotesFormModalite.DEFAULT_MODALITE,

View File

@ -1,123 +1,123 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""
Comptage des absences
"""
# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021
import json
from tests.unit import sco_fake_gen
from app.scodoc import sco_abs
from app.scodoc import sco_abs_views
from app.scodoc import sco_groups
from app.views import absences
def test_abs_counts(test_client):
"""Comptage des absences"""
G = sco_fake_gen.ScoFake(verbose=False)
# --- Création d'étudiants
etud = G.create_etud(code_nip=None)
# --- Création d'une formation
f = G.create_formation(acronyme="")
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
mat = G.create_matiere(ue_id=ue["ue_id"], titre="matière test")
mod = G.create_module(
matiere_id=mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
# --- Mise place d'un semestre
sem = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=1,
date_debut="01/01/2021",
date_fin="30/06/2021",
)
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem["formsemestre_id"],
)
# --- Inscription des étudiants
G.inscrit_etudiant(sem, etud)
# --- Saisie absences
etudid = etud["etudid"]
for debut, fin, demijournee in [
("01/01/2020", "31/01/2020", 2), # hors semestre
("15/01/2021", "15/01/2021", 1),
("18/01/2021", "18/01/2021", 0),
("19/01/2021", "19/01/2021", 2),
("22/01/2021", "22/01/2021", 1),
("30/06/2021", "30/06/2021", 2), # dernier jour
]:
sco_abs_views.doSignaleAbsence(
datedebut=debut,
datefin=fin,
demijournee=demijournee,
etudid=etudid,
)
# --- Justification de certaines absences
for debut, fin, demijournee in [
("15/01/2021", "15/01/2021", 1),
("18/01/2021", "18/01/2021", 0),
("19/01/2021", "19/01/2021", 2),
]:
sco_abs_views.doJustifAbsence(
datedebut=debut,
datefin=fin,
demijournee=demijournee,
etudid=etudid,
)
# --- Utilisation de get_abs_count() de sco_abs
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
# --- Utilisation de sco_abs.count_abs()
nb_abs2 = sco_abs.count_abs(etudid=etudid, debut="2021-01-01", fin="2021-06-30")
nb_absj2 = sco_abs.count_abs_just(
etudid=etudid, debut="2021-01-01", fin="2021-06-30"
)
assert nbabs == nb_abs2 == 7
assert nbabsjust == nb_absj2 == 4
# --- Nombre de justificatifs:
justifs = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30")
assert len(justifs) == 4
# --- Suppression d'absence
_ = sco_abs_views.doAnnuleAbsence("19/01/2021", "19/01/2021", 2, etudid=etudid)
# --- Vérification
justifs_2 = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30")
assert len(justifs_2) == len(justifs)
new_nbabs, _ = sco_abs.get_abs_count(etudid, sem) # version cachée
new_nbabs2 = sco_abs.count_abs(etudid=etudid, debut="2021-01-01", fin="2021-06-30")
assert new_nbabs == new_nbabs2
assert new_nbabs == (nbabs - 2) # on a supprimé deux absences
# --- annulation absence sans supprimer le justificatif
sco_abs_views.AnnuleAbsencesDatesNoJust(etudid, ["2021-01-15"])
nbabs_3, nbjust_3 = sco_abs.get_abs_count(etudid, sem)
assert nbabs_3 == new_nbabs
justifs_3 = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30")
assert len(justifs_3) == len(justifs_2)
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""
Comptage des absences
"""
# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021
import json
from tests.unit import sco_fake_gen
from app.scodoc import sco_abs
from app.scodoc import sco_abs_views
from app.scodoc import sco_groups
from app.views import absences
def test_abs_counts(test_client):
"""Comptage des absences"""
G = sco_fake_gen.ScoFake(verbose=False)
# --- Création d'étudiants
etud = G.create_etud(code_nip=None)
# --- Création d'une formation
f = G.create_formation(acronyme="")
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
mat = G.create_matiere(ue_id=ue["ue_id"], titre="matière test")
mod = G.create_module(
matiere_id=mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
# --- Mise place d'un semestre
sem = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=1,
date_debut="01/01/2021",
date_fin="30/06/2021",
)
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem["formsemestre_id"],
)
# --- Inscription des étudiants
G.inscrit_etudiant(sem, etud)
# --- Saisie absences
etudid = etud["etudid"]
for debut, fin, demijournee in [
("01/01/2020", "31/01/2020", 2), # hors semestre
("15/01/2021", "15/01/2021", 1),
("18/01/2021", "18/01/2021", 0),
("19/01/2021", "19/01/2021", 2),
("22/01/2021", "22/01/2021", 1),
("30/06/2021", "30/06/2021", 2), # dernier jour
]:
sco_abs_views.doSignaleAbsence(
datedebut=debut,
datefin=fin,
demijournee=demijournee,
etudid=etudid,
)
# --- Justification de certaines absences
for debut, fin, demijournee in [
("15/01/2021", "15/01/2021", 1),
("18/01/2021", "18/01/2021", 0),
("19/01/2021", "19/01/2021", 2),
]:
sco_abs_views.doJustifAbsence(
datedebut=debut,
datefin=fin,
demijournee=demijournee,
etudid=etudid,
)
# --- Utilisation de get_abs_count() de sco_abs
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
# --- Utilisation de sco_abs.count_abs()
nb_abs2 = sco_abs.count_abs(etudid=etudid, debut="2021-01-01", fin="2021-06-30")
nb_absj2 = sco_abs.count_abs_just(
etudid=etudid, debut="2021-01-01", fin="2021-06-30"
)
assert nbabs == nb_abs2 == 7
assert nbabsjust == nb_absj2 == 4
# --- Nombre de justificatifs:
justifs = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30")
assert len(justifs) == 4
# --- Suppression d'absence
_ = sco_abs_views.doAnnuleAbsence("19/01/2021", "19/01/2021", 2, etudid=etudid)
# --- Vérification
justifs_2 = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30")
assert len(justifs_2) == len(justifs)
new_nbabs, _ = sco_abs.get_abs_count(etudid, sem) # version cachée
new_nbabs2 = sco_abs.count_abs(etudid=etudid, debut="2021-01-01", fin="2021-06-30")
assert new_nbabs == new_nbabs2
assert new_nbabs == (nbabs - 2) # on a supprimé deux absences
# --- annulation absence sans supprimer le justificatif
sco_abs_views.AnnuleAbsencesDatesNoJust(etudid, ["2021-01-15"])
nbabs_3, nbjust_3 = sco_abs.get_abs_count(etudid, sem)
assert nbabs_3 == new_nbabs
justifs_3 = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30")
assert len(justifs_3) == len(justifs_2)
# XXX à continuer

View File

@ -1,335 +1,339 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""
Créer et justifier des absences en utilisant le parametre demijournee
"""
# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021
import json
from tests.unit import sco_fake_gen
from app.scodoc import sco_abs
from app.scodoc import sco_abs_views
from app.scodoc import sco_groups
from app.views import absences
def test_abs_demijournee(test_client):
"""Opération élémentaires sur les absences, tests demi-journées
Travaille dans base TEST00 (defaut)
"""
G = sco_fake_gen.ScoFake(verbose=False)
# --- Création d'étudiants
etud = G.create_etud(code_nip=None)
# --- Création d'une formation
f = G.create_formation(acronyme="")
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
mat = G.create_matiere(ue_id=ue["ue_id"], titre="matière test")
mod = G.create_module(
matiere_id=mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
# --- Mise place d'un semestre
sem = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=1,
date_debut="01/01/2021",
date_fin="30/06/2021",
)
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem["formsemestre_id"],
)
# --- Inscription des étudiants
G.inscrit_etudiant(sem, etud)
# --- Saisie absences
etudid = etud["etudid"]
_ = sco_abs_views.doSignaleAbsence(
"15/01/2021",
"15/01/2021",
demijournee=2,
etudid=etudid,
)
_ = sco_abs_views.doSignaleAbsence(
"18/01/2021",
"18/01/2021",
demijournee=1,
etudid=etudid,
)
_ = sco_abs_views.doSignaleAbsence(
"19/01/2021",
"19/01/2021",
demijournee=0,
etudid=etudid,
)
# --- Justification de certaines absences
_ = sco_abs_views.doJustifAbsence(
"18/01/2021",
"18/01/2021",
demijournee=1,
etudid=etudid,
)
_ = sco_abs_views.doJustifAbsence(
"19/01/2021",
"19/01/2021",
demijournee=2,
etudid=etudid,
)
# NE JUSTIFIE QUE LE MATIN MALGRES LE PARAMETRE demijournee = 2
# --- Test
nbabs, nbabs_just = sco_abs.get_abs_count(etudid, sem)
assert (
nbabs == 4
) # l'étudiant a été absent le 15 journée compléte (2 abs : 1 matin, 1 apres midi) et le 18 (1 matin), et le 19 (1 apres midi).
assert nbabs_just == 2 # Justifie abs du matin + abs après midi
def test_abs_basic(test_client):
"""creation de 10 étudiants, formation, semestre, ue, module, absences le matin, l'apres midi, la journée compléte
et justification d'absences, supression d'absences, création d'une liste etat absences, creation d'un groupe afin
de tester la fonction EtatAbsencesGroupes
Fonctions de l'API utilisé :
- doSignaleAbsence
- doAnnuleAbsence
- doJustifAbsence
- get_partition_groups
- get_partitions_list
- sco_abs.get_abs_count(etudid, sem)
- ListeAbsEtud
- partition_create
- createGroup
- set_group
- EtatAbsenceGr
- AddBilletAbsence
- listeBilletsEtud
"""
G = sco_fake_gen.ScoFake(verbose=False)
# --- Création d'étudiants
etuds = [G.create_etud(code_nip=None) for _ in range(10)]
# --- Création d'une formation
f = G.create_formation(acronyme="")
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
mat = G.create_matiere(ue_id=ue["ue_id"], titre="matière test")
mod = G.create_module(
matiere_id=mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
# --- Mise place d'un semestre
sem = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=1,
date_debut="01/01/2021",
date_fin="30/06/2021",
)
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem["formsemestre_id"],
)
# --- Inscription des étudiants
for etud in etuds:
G.inscrit_etudiant(sem, etud)
# --- Création d'une évaluation
e = G.create_evaluation(
moduleimpl_id=mi["moduleimpl_id"],
jour="22/01/2021",
description="evaluation test",
coefficient=1.0,
)
# --- Saisie absences
etudid = etuds[0]["etudid"]
_ = sco_abs_views.doSignaleAbsence(
"15/01/2021",
"15/01/2021",
demijournee=1,
etudid=etudid,
)
_ = sco_abs_views.doSignaleAbsence(
"18/01/2021",
"18/01/2021",
demijournee=0,
etudid=etudid,
)
_ = sco_abs_views.doSignaleAbsence(
"19/01/2021",
"19/01/2021",
demijournee=2,
etudid=etudid,
)
_ = sco_abs_views.doSignaleAbsence(
"22/01/2021",
"22/01/2021",
demijournee=1,
etudid=etudid,
)
# --- Justification de certaines absences
_ = sco_abs_views.doJustifAbsence(
"15/01/2021",
"15/01/2021",
demijournee=1,
etudid=etudid,
)
_ = sco_abs_views.doJustifAbsence(
"18/01/2021",
"18/01/2021",
demijournee=0,
etudid=etudid,
)
_ = sco_abs_views.doJustifAbsence(
"19/01/2021",
"19/01/2021",
demijournee=2,
etudid=etudid,
)
# --- Test
b = sco_abs.is_work_saturday()
assert b == 0 # samedi ne sont pas compris
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
# l'étudiant a été absent le 15 (apres midi) , (16 et 17 we),
# 18 (matin) et 19 janvier (matin et apres midi), et 22 (matin)
assert nbabs == 5
# l'étudiant justifie ses abs du 15, 18 et 19
assert nbabsjust == 4
# --- Suppression d'une absence et d'une justification
_ = sco_abs_views.doAnnuleAbsence("19/01/2021", "19/01/2021", 2, etudid=etudid)
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
assert nbabs == 3
assert nbabsjust == 2
# --- suppression d'une justification pas encore disponible à l'aide de python.
# --- Création d'une liste d'abs
liste_abs = sco_abs_views.ListeAbsEtud(
etudid, format="json", absjust_only=1, sco_year="2020"
)
liste_abs2 = sco_abs_views.ListeAbsEtud(etudid, format="json", sco_year="2020")
load_liste_abs = json.loads(liste_abs)
load_liste_abs2 = json.loads(liste_abs2)
assert len(load_liste_abs2) == 1
assert len(load_liste_abs) == 2
assert load_liste_abs2[0]["ampm"] == 1
assert load_liste_abs2[0]["datedmy"] == "22/01/2021"
assert load_liste_abs2[0]["exams"] == mod["code"]
# absjust_only -> seulement les abs justifiés
# --- Création d'un groupe
_ = sco_groups.partition_create(
formsemestre_id=sem["formsemestre_id"],
partition_name="Eleve",
)
li1 = sco_groups.get_partitions_list(sem["formsemestre_id"])
_ = sco_groups.createGroup(li1[0]["partition_id"], "Groupe 1")
# --- Affectation des élèves dans des groupes
li_grp1 = sco_groups.get_partition_groups(li1[0])
for etud in etuds:
sco_groups.set_group(etud["etudid"], li_grp1[0]["group_id"])
# --- Test de EtatAbsencesGroupes
grp1_abs = absences.EtatAbsencesGr(
group_ids=[li_grp1[0]["group_id"]],
debut="01/01/2021",
fin="30/06/2021",
format="json",
)
# grp1_abs est une Response car on a appelé une vue (1er appel)
load_grp1_abs = json.loads(grp1_abs.get_data().decode("utf-8"))
assert len(load_grp1_abs) == 10
tab_id = [] # tab des id present dans load_grp1_abs
for un_etud in load_grp1_abs:
tab_id.append(un_etud["etudid"])
for (
etud
) in (
etuds
): # verification si tous les etudiants sont present dans la liste du groupe d'absence
assert etud["etudid"] in tab_id
for un_etud in load_grp1_abs:
if un_etud["etudid"] == etudid:
assert un_etud["nbabs"] == 3
assert un_etud["nbjustifs_noabs"] == 2
assert un_etud["nbabsjust"] == 2
assert un_etud["nbabsnonjust"] == 1
assert un_etud["nomprenom"] == etuds[0]["nomprenom"]
# --- Création de billets
b1 = absences.AddBilletAbsence(
begin="2021-01-22 00:00",
end="2021-01-22 23:59",
etudid=etudid,
description="abs du 22",
justified=False,
code_nip=etuds[0]["code_nip"],
code_ine=etuds[0]["code_ine"],
)
b2 = absences.AddBilletAbsence(
begin="2021-01-15 00:00",
end="2021-01-15 23:59",
etudid=etudid,
description="abs du 15",
code_nip=etuds[0]["code_nip"],
code_ine=etuds[0]["code_ine"],
)
li_bi = absences.listeBilletsEtud(etudid=etudid, format="json")
assert isinstance(li_bi, str)
load_li_bi = json.loads(li_bi)
assert len(load_li_bi) == 2
assert load_li_bi[1]["description"] == "abs du 22"
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""
Créer et justifier des absences en utilisant le parametre demijournee
"""
# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021
import json
from tests.unit import sco_fake_gen
from app.scodoc import sco_abs
from app.scodoc import sco_abs_views
from app.scodoc import sco_groups
from app.views import absences
def test_abs_demijournee(test_client):
"""Opération élémentaires sur les absences, tests demi-journées
Travaille dans base TEST00 (defaut)
"""
G = sco_fake_gen.ScoFake(verbose=False)
# --- Création d'étudiants
etud = G.create_etud(code_nip=None)
# --- Création d'une formation
f = G.create_formation(acronyme="")
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
mat = G.create_matiere(ue_id=ue["ue_id"], titre="matière test")
mod = G.create_module(
matiere_id=mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
# --- Mise place d'un semestre
sem = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=1,
date_debut="01/01/2021",
date_fin="30/06/2021",
)
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem["formsemestre_id"],
)
# --- Inscription des étudiants
G.inscrit_etudiant(sem, etud)
# --- Saisie absences
etudid = etud["etudid"]
_ = sco_abs_views.doSignaleAbsence(
"15/01/2021",
"15/01/2021",
demijournee=2,
etudid=etudid,
)
_ = sco_abs_views.doSignaleAbsence(
"18/01/2021",
"18/01/2021",
demijournee=1,
etudid=etudid,
)
_ = sco_abs_views.doSignaleAbsence(
"19/01/2021",
"19/01/2021",
demijournee=0,
etudid=etudid,
)
# --- Justification de certaines absences
_ = sco_abs_views.doJustifAbsence(
"18/01/2021",
"18/01/2021",
demijournee=1,
etudid=etudid,
)
_ = sco_abs_views.doJustifAbsence(
"19/01/2021",
"19/01/2021",
demijournee=2,
etudid=etudid,
)
# NE JUSTIFIE QUE LE MATIN MALGRES LE PARAMETRE demijournee = 2
# --- Test
nbabs, nbabs_just = sco_abs.get_abs_count(etudid, sem)
assert (
nbabs == 4
) # l'étudiant a été absent le 15 journée compléte (2 abs : 1 matin, 1 apres midi) et le 18 (1 matin), et le 19 (1 apres midi).
assert nbabs_just == 2 # Justifie abs du matin + abs après midi
def test_abs_basic(test_client):
"""creation de 10 étudiants, formation, semestre, ue, module, absences le matin, l'apres midi, la journée compléte
et justification d'absences, supression d'absences, création d'une liste etat absences, creation d'un groupe afin
de tester la fonction EtatAbsencesGroupes
Fonctions de l'API utilisé :
- doSignaleAbsence
- doAnnuleAbsence
- doJustifAbsence
- get_partition_groups
- get_partitions_list
- sco_abs.get_abs_count(etudid, sem)
- ListeAbsEtud
- partition_create
- createGroup
- set_group
- EtatAbsenceGr
- AddBilletAbsence
- listeBilletsEtud
"""
G = sco_fake_gen.ScoFake(verbose=False)
# --- Création d'étudiants
etuds = [G.create_etud(code_nip=None) for _ in range(10)]
# --- Création d'une formation
f = G.create_formation(acronyme="")
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
mat = G.create_matiere(ue_id=ue["ue_id"], titre="matière test")
mod = G.create_module(
matiere_id=mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
# --- Mise place d'un semestre
sem = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=1,
date_debut="01/01/2021",
date_fin="30/06/2021",
)
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem["formsemestre_id"],
)
# --- Inscription des étudiants
for etud in etuds:
G.inscrit_etudiant(sem, etud)
# --- Création d'une évaluation
e = G.create_evaluation(
moduleimpl_id=mi["moduleimpl_id"],
jour="22/01/2021",
description="evaluation test",
coefficient=1.0,
)
# --- Saisie absences
etudid = etuds[0]["etudid"]
_ = sco_abs_views.doSignaleAbsence(
"15/01/2021",
"15/01/2021",
demijournee=1,
etudid=etudid,
)
_ = sco_abs_views.doSignaleAbsence(
"18/01/2021",
"18/01/2021",
demijournee=0,
etudid=etudid,
)
_ = sco_abs_views.doSignaleAbsence(
"19/01/2021",
"19/01/2021",
demijournee=2,
etudid=etudid,
)
_ = sco_abs_views.doSignaleAbsence(
"22/01/2021",
"22/01/2021",
demijournee=1,
etudid=etudid,
)
# --- Justification de certaines absences
_ = sco_abs_views.doJustifAbsence(
"15/01/2021",
"15/01/2021",
demijournee=1,
etudid=etudid,
)
_ = sco_abs_views.doJustifAbsence(
"18/01/2021",
"18/01/2021",
demijournee=0,
etudid=etudid,
)
_ = sco_abs_views.doJustifAbsence(
"19/01/2021",
"19/01/2021",
demijournee=2,
etudid=etudid,
)
# --- Test
b = sco_abs.is_work_saturday()
assert b == 0 # samedi ne sont pas compris
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
# l'étudiant a été absent le 15 (apres midi) , (16 et 17 we),
# 18 (matin) et 19 janvier (matin et apres midi), et 22 (matin)
assert nbabs == 5
# l'étudiant justifie ses abs du 15, 18 et 19
assert nbabsjust == 4
# --- Suppression d'une absence et d'une justification
_ = sco_abs_views.doAnnuleAbsence("19/01/2021", "19/01/2021", 2, etudid=etudid)
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
assert nbabs == 3
assert nbabsjust == 2
# --- suppression d'une justification pas encore disponible à l'aide de python.
# --- Création d'une liste d'abs
liste_abs = sco_abs_views.ListeAbsEtud(
etudid, format="json", absjust_only=1, sco_year="2020"
).get_data(as_text=True)
liste_abs2 = sco_abs_views.ListeAbsEtud(
etudid, format="json", sco_year="2020"
).get_data(as_text=True)
load_liste_abs = json.loads(liste_abs)
load_liste_abs2 = json.loads(liste_abs2)
assert len(load_liste_abs2) == 1
assert len(load_liste_abs) == 2
assert load_liste_abs2[0]["ampm"] == 1
assert load_liste_abs2[0]["datedmy"] == "22/01/2021"
assert load_liste_abs2[0]["exams"] == mod["code"]
# absjust_only -> seulement les abs justifiés
# --- Création d'un groupe
_ = sco_groups.partition_create(
formsemestre_id=sem["formsemestre_id"],
partition_name="Eleve",
)
li1 = sco_groups.get_partitions_list(sem["formsemestre_id"])
_ = sco_groups.createGroup(li1[0]["partition_id"], "Groupe 1")
# --- Affectation des élèves dans des groupes
li_grp1 = sco_groups.get_partition_groups(li1[0])
for etud in etuds:
sco_groups.set_group(etud["etudid"], li_grp1[0]["group_id"])
# --- Test de EtatAbsencesGroupes
grp1_abs = absences.EtatAbsencesGr(
group_ids=[li_grp1[0]["group_id"]],
debut="01/01/2021",
fin="30/06/2021",
format="json",
)
# grp1_abs est une Response car on a appelé une vue (1er appel)
load_grp1_abs = json.loads(grp1_abs.get_data(as_text=True))
assert len(load_grp1_abs) == 10
tab_id = [] # tab des id present dans load_grp1_abs
for un_etud in load_grp1_abs:
tab_id.append(un_etud["etudid"])
for (
etud
) in (
etuds
): # verification si tous les etudiants sont present dans la liste du groupe d'absence
assert etud["etudid"] in tab_id
for un_etud in load_grp1_abs:
if un_etud["etudid"] == etudid:
assert un_etud["nbabs"] == 3
assert un_etud["nbjustifs_noabs"] == 2
assert un_etud["nbabsjust"] == 2
assert un_etud["nbabsnonjust"] == 1
assert un_etud["nomprenom"] == etuds[0]["nomprenom"]
# --- Création de billets
b1 = absences.AddBilletAbsence(
begin="2021-01-22 00:00",
end="2021-01-22 23:59",
etudid=etudid,
description="abs du 22",
justified=False,
code_nip=etuds[0]["code_nip"],
code_ine=etuds[0]["code_ine"],
)
b2 = absences.AddBilletAbsence(
begin="2021-01-15 00:00",
end="2021-01-15 23:59",
etudid=etudid,
description="abs du 15",
code_nip=etuds[0]["code_nip"],
code_ine=etuds[0]["code_ine"],
)
li_bi = absences.listeBilletsEtud(etudid=etudid, format="json").get_data(
as_text=True
)
assert isinstance(li_bi, str)
load_li_bi = json.loads(li_bi)
assert len(load_li_bi) == 2
assert load_li_bi[1]["description"] == "abs du 22"

View File

@ -1,375 +1,379 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
""" Test creation/edition/import/export formations
"""
# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021
# Créer 2 formations, une test et une normale. Créer 2 semestres dans la formation normale et un
# dans la formation test, créer 2 semestres dans la formation normale (un test et un normal),
# 2 ue (un test et un normal), 2 modules (un test et un normal) et 2 matieres (test et normal).
# Et dans la formations test, un semestre, un module, un ue et une matiere.
# Afficher la liste de tout ca puis supprimer les ue, mod, mat et sem test ainsi
# que la formation test. Afficher la liste des UE, formations et modules restante.
#
# Vérification :
#
# - Les listes initiales comprennent bien tout les éléments créés avec les bon noms etc
# - La supression s'est bien effectué au niveau de scodoc web et en python
# - Vérifier que les fonctions listes font bien la mise à jour après supression
#
# Fonction de l'API utilisé :
#
# - create_formation
# - create_ue
# - create_matiere
# - create_module
# - create_formsemestre
# - create_moduleimpl
# - formation_list
# - formation_export
# - formsemestre_list
# - do_moduleimpl_list
# - do_module_impl_with_module_list
# - do_formsemestre_delete
# - do_module_list
# - do_module_delete
# - do_matiere_list
# - do_matiere_delete
# - do_ue_list
# - do_ue_delete
# - do_formation_delete
import json
import xml.dom.minidom
import flask
from flask import g
from tests.unit import sco_fake_gen
from app.scodoc import sco_edit_formation
from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre_edit
from app.scodoc import sco_moduleimpl
from app.views import notes
def test_formations(test_client):
"""Test création/édition/import/export formations"""
G = sco_fake_gen.ScoFake(verbose=False)
# --- Création de formations
f = G.create_formation(
acronyme="F1", titre="Formation 1", titre_officiel="Titre officiel 1"
)
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
mat = G.create_matiere(ue_id=ue["ue_id"], titre="matière test")
mod = G.create_module(
matiere_id=mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
ue2 = G.create_ue(formation_id=f["formation_id"], acronyme="TST2", titre="ue test2")
mat2 = G.create_matiere(ue_id=ue2["ue_id"], titre="matière test2")
mod2 = G.create_module(
matiere_id=mat2["matiere_id"],
code="TSM2",
coefficient=1.0,
titre="module test",
ue_id=ue2["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
uet = G.create_ue(formation_id=f["formation_id"], acronyme="TSTt", titre="ue testt")
matt = G.create_matiere(ue_id=uet["ue_id"], titre="matière testt")
modt = G.create_module(
matiere_id=matt["matiere_id"],
code="TSMt",
coefficient=1.0,
titre="module test",
ue_id=uet["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
f2 = G.create_formation(acronyme="", titre="Formation test")
ue3 = G.create_ue(
formation_id=f2["formation_id"], acronyme="TST3", titre="ue test3"
)
mat3 = G.create_matiere(ue_id=ue3["ue_id"], titre="matière test3")
mod3 = G.create_module(
matiere_id=mat3["matiere_id"],
code="TSM3",
coefficient=1.0,
titre="module test3",
ue_id=ue3["ue_id"], # faiblesse de l'API
formation_id=f2["formation_id"], # faiblesse de l'API
)
# --- Création et implémentation des semestres
sem1 = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=1,
date_debut="01/01/2021",
date_fin="30/06/2021",
)
sem2 = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=2,
date_debut="01/09/2020",
date_fin="31/12/2020",
)
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem1["formsemestre_id"],
)
mi2 = G.create_moduleimpl(
module_id=mod2["module_id"],
formsemestre_id=sem1["formsemestre_id"],
)
mit = G.create_moduleimpl(
module_id=modt["module_id"],
formsemestre_id=sem2["formsemestre_id"],
)
semt = G.create_formsemestre(
formation_id=f2["formation_id"],
semestre_id=3,
date_debut="01/01/2021",
date_fin="30/06/2021",
)
mi3 = G.create_moduleimpl(
module_id=mod3["module_id"],
formsemestre_id=semt["formsemestre_id"],
)
# --- Afficher la liste des formations
lif = notes.formation_list(format="json", formation_id=f["formation_id"])
# lif est une Response car on a appelé une vue (1er appel)
assert isinstance(lif, flask.Response)
load_lif = json.loads(lif.get_data().decode("utf-8"))
assert len(load_lif) == 1
assert load_lif[0]["acronyme"] == f["acronyme"]
assert load_lif[0]["titre_officiel"] == f["titre_officiel"]
assert load_lif[0]["formation_id"] == f["formation_id"]
assert load_lif[0]["titre"] == f["titre"]
lif2 = notes.formation_list(format="json")
# lif2 est un chaine
assert isinstance(lif2, str)
load_lif2 = json.loads(lif2)
assert len(load_lif2) == 2
assert load_lif2[0] == load_lif[0]
assert load_lif2[1]["titre"] == f2["titre"]
# --- Export de formation_id
exp = sco_formations.formation_export(formation_id=f["formation_id"], format="json")
assert isinstance(exp, str)
load_exp = json.loads(exp)
assert load_exp["acronyme"] == "F1"
assert load_exp["titre_officiel"] == "Titre officiel 1"
assert load_exp["titre"] == "Formation 1"
assert load_exp["formation_code"] == f["formation_code"]
assert len(load_exp["ue"]) == 3
assert load_exp["ue"][0]["acronyme"] == "TST1"
assert load_exp["ue"][0]["titre"] == "ue test"
assert load_exp["formation_id"] == f["formation_id"]
assert load_exp["formation_code"] == f["formation_code"]
# --- Liste des semestres
li_sem1 = notes.formsemestre_list(
formsemestre_id=sem1["formsemestre_id"], format="json"
)
assert isinstance(li_sem1, str)
load_li_sem1 = json.loads(li_sem1) # uniquement le semestre 1 dans la liste
assert len(load_li_sem1) == 1
assert load_li_sem1[0]["date_fin"] == sem1["date_fin"]
assert load_li_sem1[0]["semestre_id"] == sem1["semestre_id"]
assert load_li_sem1[0]["formation_id"] == sem1["formation_id"]
li_semf = notes.formsemestre_list(
formation_id=f["formation_id"],
format="json",
)
assert isinstance(li_semf, str)
load_li_semf = json.loads(li_semf)
assert load_li_sem1[0] in load_li_semf
assert len(load_li_semf) == 2
assert load_li_semf[1]["semestre_id"] == sem2["semestre_id"]
li_sem = notes.formsemestre_list(format="json")
load_li_sem = json.loads(li_sem)
assert len(load_li_sem) == 3
assert load_li_semf[0] and load_li_semf[1] in load_li_sem
assert load_li_sem[0]["semestre_id"] == semt["semestre_id"]
# --- Liste des modules
lim_sem1 = sco_moduleimpl.do_moduleimpl_list(
formsemestre_id=sem1["formsemestre_id"]
)
assert len(lim_sem1) == 2
assert mod["module_id"] in (lim_sem1[0]["module_id"], lim_sem1[1]["module_id"])
assert mod2["module_id"] in (lim_sem1[0]["module_id"], lim_sem1[1]["module_id"])
lim_modid = sco_moduleimpl.do_moduleimpl_list(module_id=mod["module_id"])
assert len(lim_modid) == 1
lim_modimpl_id = sco_moduleimpl.do_moduleimpl_list(
moduleimpl_id=mi["moduleimpl_id"]
)
# print(lim_modimpl_id)
# ---- Test de do_moduleimpl_withmodule_list
assert lim_modid == lim_modimpl_id # doit etre le meme resultat
liimp_sem1 = sco_moduleimpl.do_moduleimpl_withmodule_list(
formsemestre_id=sem1["formsemestre_id"]
)
assert len(liimp_sem1) == 2
assert mod["module_id"] in (liimp_sem1[0]["module_id"], liimp_sem1[1]["module_id"])
assert mod2["module_id"] in (
liimp_sem1[0]["module_id"],
liimp_sem1[1]["module_id"],
)
liimp_sem2 = sco_moduleimpl.do_moduleimpl_withmodule_list(
formsemestre_id=sem2["formsemestre_id"]
)
assert modt["module_id"] == liimp_sem2[0]["module_id"]
liimp_modid = sco_moduleimpl.do_moduleimpl_withmodule_list(
module_id=mod["module_id"]
)
assert len(liimp_modid) == 1
liimp_modimplid = sco_moduleimpl.do_moduleimpl_withmodule_list(
moduleimpl_id=mi["moduleimpl_id"]
)
assert liimp_modid == liimp_modimplid
# --- Suppression du module, matiere et ue test du semestre 2
# on doit d'abbord supprimer le semestre
# sco_formsemestre_edit.formsemestre_delete( formsemestre_id=sem2["formsemestre_id"], REQUEST=REQUEST)
# sco_formsemestre_edit.formsemestre_createwithmodules( formsemestre_id=sem2["formsemestre_id"], REQUEST=REQUEST)
# RIEN NE SE PASSE AVEC CES FONCTIONS
sco_formsemestre_edit.do_formsemestre_delete(
formsemestre_id=sem2["formsemestre_id"]
)
# sco_edit_module.module_delete( module_id=modt["module_id"], REQUEST=REQUEST)
# sco_edit_matiere.matiere_delete( matiere_id=matt["matiere_id"], REQUEST=REQUEST)
# sco_edit_ue.ue_delete( ue_id=uet["ue_id"], REQUEST=REQUEST)
# RIEN NE SE PASSE AVEC CES FONCTIONS
li_module = sco_edit_module.do_module_list()
assert len(li_module) == 4
sco_edit_module.do_module_delete(oid=modt["module_id"]) # on supprime le semestre
# sco_formsemestre_edit.formsemestre_delete_moduleimpls( formsemestre_id=sem2["formsemestre_id"], module_ids_to_del=[modt["module_id"]])
# deuxieme methode de supression d'un module
li_module2 = sco_edit_module.do_module_list()
assert len(li_module2) == 3 # verification de la suppression du module
lim_sem2 = sco_moduleimpl.do_moduleimpl_list(
formsemestre_id=sem2["formsemestre_id"]
)
assert len(lim_sem2) == 0 # deuxieme vérification si le module s'est bien sup
li_mat = sco_edit_matiere.do_matiere_list()
assert len(li_mat) == 4
sco_edit_matiere.do_matiere_delete(oid=matt["matiere_id"]) # on supprime la matiere
li_mat2 = sco_edit_matiere.do_matiere_list()
assert len(li_mat2) == 3 # verification de la suppression de la matiere
li_ue = sco_edit_ue.do_ue_list()
assert len(li_ue) == 4
sco_edit_ue.ue_delete(ue_id=uet["ue_id"], dialog_confirmed=True)
li_ue2 = sco_edit_ue.do_ue_list()
assert len(li_ue2) == 3 # verification de la suppression de l'UE
# --- Suppression d'une formation
# Il faut d'abbord supprimer le semestre aussi.
sco_formsemestre_edit.do_formsemestre_delete(
formsemestre_id=semt["formsemestre_id"]
)
sco_edit_formation.do_formation_delete(oid=f2["formation_id"])
lif3 = notes.formation_list(format="json")
assert isinstance(lif3, str)
load_lif3 = json.loads(lif3)
assert len(load_lif3) == 1
def test_import_formation(test_client):
"""Test import/export formations"""
G = sco_fake_gen.ScoFake(verbose=False)
# Lecture fichier XML local:
with open("tests/unit/formation-exemple-1.xml") as f:
doc = f.read()
# --- Création de la formation
f = sco_formations.formation_import_xml(doc)
assert len(f) == 3 # 3-uple
formation_id = f[0]
# --- Mise en place de 4 semestres
sems = [
G.create_formsemestre(
formation_id=formation_id,
semestre_id=x[0],
date_debut=x[1],
date_fin=x[2],
)
for x in (
(1, "05/09/2019", "05/01/2020"),
(2, "06/01/2020", "30/06/2020"),
(3, "01/09/2020", "05/01/2021"),
(4, "06/01/2021", "30/06/2021"),
)
]
# et les modules
modules = sco_edit_module.do_module_list({"formation_id": formation_id})
for mod in modules:
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sems[mod["semestre_id"] - 1]["formsemestre_id"],
)
assert mi["ens"] == []
assert mi["module_id"] == mod["module_id"]
# --- Export formation en XML
doc1 = sco_formations.formation_export(formation_id, format="xml")
assert isinstance(doc1, str)
# -*- mode: python -*-
# -*- coding: utf-8 -*-
""" Test creation/edition/import/export formations
"""
# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021
# Créer 2 formations, une test et une normale. Créer 2 semestres dans la formation normale et un
# dans la formation test, créer 2 semestres dans la formation normale (un test et un normal),
# 2 ue (un test et un normal), 2 modules (un test et un normal) et 2 matieres (test et normal).
# Et dans la formations test, un semestre, un module, un ue et une matiere.
# Afficher la liste de tout ca puis supprimer les ue, mod, mat et sem test ainsi
# que la formation test. Afficher la liste des UE, formations et modules restante.
#
# Vérification :
#
# - Les listes initiales comprennent bien tout les éléments créés avec les bon noms etc
# - La supression s'est bien effectué au niveau de scodoc web et en python
# - Vérifier que les fonctions listes font bien la mise à jour après supression
#
# Fonction de l'API utilisé :
#
# - create_formation
# - create_ue
# - create_matiere
# - create_module
# - create_formsemestre
# - create_moduleimpl
# - formation_list
# - formation_export
# - formsemestre_list
# - do_moduleimpl_list
# - do_module_impl_with_module_list
# - do_formsemestre_delete
# - do_module_list
# - do_module_delete
# - do_matiere_list
# - do_matiere_delete
# - do_ue_list
# - do_ue_delete
# - do_formation_delete
import json
import xml.dom.minidom
import flask
from flask import g
from tests.unit import sco_fake_gen
from app.scodoc import sco_edit_formation
from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre_edit
from app.scodoc import sco_moduleimpl
from app.views import notes
def test_formations(test_client):
"""Test création/édition/import/export formations"""
G = sco_fake_gen.ScoFake(verbose=False)
# --- Création de formations
f = G.create_formation(
acronyme="F1", titre="Formation 1", titre_officiel="Titre officiel 1"
)
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
mat = G.create_matiere(ue_id=ue["ue_id"], titre="matière test")
mod = G.create_module(
matiere_id=mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
ue2 = G.create_ue(formation_id=f["formation_id"], acronyme="TST2", titre="ue test2")
mat2 = G.create_matiere(ue_id=ue2["ue_id"], titre="matière test2")
mod2 = G.create_module(
matiere_id=mat2["matiere_id"],
code="TSM2",
coefficient=1.0,
titre="module test",
ue_id=ue2["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
uet = G.create_ue(formation_id=f["formation_id"], acronyme="TSTt", titre="ue testt")
matt = G.create_matiere(ue_id=uet["ue_id"], titre="matière testt")
modt = G.create_module(
matiere_id=matt["matiere_id"],
code="TSMt",
coefficient=1.0,
titre="module test",
ue_id=uet["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
f2 = G.create_formation(acronyme="", titre="Formation test")
ue3 = G.create_ue(
formation_id=f2["formation_id"], acronyme="TST3", titre="ue test3"
)
mat3 = G.create_matiere(ue_id=ue3["ue_id"], titre="matière test3")
mod3 = G.create_module(
matiere_id=mat3["matiere_id"],
code="TSM3",
coefficient=1.0,
titre="module test3",
ue_id=ue3["ue_id"], # faiblesse de l'API
formation_id=f2["formation_id"], # faiblesse de l'API
)
# --- Création et implémentation des semestres
sem1 = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=1,
date_debut="01/01/2021",
date_fin="30/06/2021",
)
sem2 = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=2,
date_debut="01/09/2020",
date_fin="31/12/2020",
)
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem1["formsemestre_id"],
)
mi2 = G.create_moduleimpl(
module_id=mod2["module_id"],
formsemestre_id=sem1["formsemestre_id"],
)
mit = G.create_moduleimpl(
module_id=modt["module_id"],
formsemestre_id=sem2["formsemestre_id"],
)
semt = G.create_formsemestre(
formation_id=f2["formation_id"],
semestre_id=3,
date_debut="01/01/2021",
date_fin="30/06/2021",
)
mi3 = G.create_moduleimpl(
module_id=mod3["module_id"],
formsemestre_id=semt["formsemestre_id"],
)
# --- Afficher la liste des formations
lif = notes.formation_list(format="json", formation_id=f["formation_id"])
# lif est une Response car on a appelé une vue (1er appel)
assert isinstance(lif, flask.Response)
load_lif = json.loads(lif.get_data().decode("utf-8"))
assert len(load_lif) == 1
assert load_lif[0]["acronyme"] == f["acronyme"]
assert load_lif[0]["titre_officiel"] == f["titre_officiel"]
assert load_lif[0]["formation_id"] == f["formation_id"]
assert load_lif[0]["titre"] == f["titre"]
lif2 = notes.formation_list(format="json").get_data(as_text=True)
# lif2 est un chaine
assert isinstance(lif2, str)
load_lif2 = json.loads(lif2)
assert len(load_lif2) == 2
assert load_lif2[0] == load_lif[0]
assert load_lif2[1]["titre"] == f2["titre"]
# --- Export de formation_id
exp = sco_formations.formation_export(
formation_id=f["formation_id"], format="json"
).get_data(as_text=True)
assert isinstance(exp, str)
load_exp = json.loads(exp)
assert load_exp["acronyme"] == "F1"
assert load_exp["titre_officiel"] == "Titre officiel 1"
assert load_exp["titre"] == "Formation 1"
assert load_exp["formation_code"] == f["formation_code"]
assert len(load_exp["ue"]) == 3
assert load_exp["ue"][0]["acronyme"] == "TST1"
assert load_exp["ue"][0]["titre"] == "ue test"
assert load_exp["formation_id"] == f["formation_id"]
assert load_exp["formation_code"] == f["formation_code"]
# --- Liste des semestres
li_sem1 = notes.formsemestre_list(
formsemestre_id=sem1["formsemestre_id"], format="json"
).get_data(as_text=True)
assert isinstance(li_sem1, str)
load_li_sem1 = json.loads(li_sem1) # uniquement le semestre 1 dans la liste
assert len(load_li_sem1) == 1
assert load_li_sem1[0]["date_fin"] == sem1["date_fin"]
assert load_li_sem1[0]["semestre_id"] == sem1["semestre_id"]
assert load_li_sem1[0]["formation_id"] == sem1["formation_id"]
li_semf = notes.formsemestre_list(
formation_id=f["formation_id"],
format="json",
).get_data(as_text=True)
assert isinstance(li_semf, str)
load_li_semf = json.loads(li_semf)
assert load_li_sem1[0] in load_li_semf
assert len(load_li_semf) == 2
assert load_li_semf[1]["semestre_id"] == sem2["semestre_id"]
li_sem = notes.formsemestre_list(format="json").get_data(as_text=True)
load_li_sem = json.loads(li_sem)
assert len(load_li_sem) == 3
assert load_li_semf[0] and load_li_semf[1] in load_li_sem
assert load_li_sem[0]["semestre_id"] == semt["semestre_id"]
# --- Liste des modules
lim_sem1 = sco_moduleimpl.do_moduleimpl_list(
formsemestre_id=sem1["formsemestre_id"]
)
assert len(lim_sem1) == 2
assert mod["module_id"] in (lim_sem1[0]["module_id"], lim_sem1[1]["module_id"])
assert mod2["module_id"] in (lim_sem1[0]["module_id"], lim_sem1[1]["module_id"])
lim_modid = sco_moduleimpl.do_moduleimpl_list(module_id=mod["module_id"])
assert len(lim_modid) == 1
lim_modimpl_id = sco_moduleimpl.do_moduleimpl_list(
moduleimpl_id=mi["moduleimpl_id"]
)
# print(lim_modimpl_id)
# ---- Test de do_moduleimpl_withmodule_list
assert lim_modid == lim_modimpl_id # doit etre le meme resultat
liimp_sem1 = sco_moduleimpl.do_moduleimpl_withmodule_list(
formsemestre_id=sem1["formsemestre_id"]
)
assert len(liimp_sem1) == 2
assert mod["module_id"] in (liimp_sem1[0]["module_id"], liimp_sem1[1]["module_id"])
assert mod2["module_id"] in (
liimp_sem1[0]["module_id"],
liimp_sem1[1]["module_id"],
)
liimp_sem2 = sco_moduleimpl.do_moduleimpl_withmodule_list(
formsemestre_id=sem2["formsemestre_id"]
)
assert modt["module_id"] == liimp_sem2[0]["module_id"]
liimp_modid = sco_moduleimpl.do_moduleimpl_withmodule_list(
module_id=mod["module_id"]
)
assert len(liimp_modid) == 1
liimp_modimplid = sco_moduleimpl.do_moduleimpl_withmodule_list(
moduleimpl_id=mi["moduleimpl_id"]
)
assert liimp_modid == liimp_modimplid
# --- Suppression du module, matiere et ue test du semestre 2
# on doit d'abbord supprimer le semestre
# sco_formsemestre_edit.formsemestre_delete( formsemestre_id=sem2["formsemestre_id"], REQUEST=REQUEST)
# sco_formsemestre_edit.formsemestre_createwithmodules( formsemestre_id=sem2["formsemestre_id"], REQUEST=REQUEST)
# RIEN NE SE PASSE AVEC CES FONCTIONS
sco_formsemestre_edit.do_formsemestre_delete(
formsemestre_id=sem2["formsemestre_id"]
)
# sco_edit_module.module_delete( module_id=modt["module_id"], REQUEST=REQUEST)
# sco_edit_matiere.matiere_delete( matiere_id=matt["matiere_id"], REQUEST=REQUEST)
# sco_edit_ue.ue_delete( ue_id=uet["ue_id"], REQUEST=REQUEST)
# RIEN NE SE PASSE AVEC CES FONCTIONS
li_module = sco_edit_module.do_module_list()
assert len(li_module) == 4
sco_edit_module.do_module_delete(oid=modt["module_id"]) # on supprime le semestre
# sco_formsemestre_edit.formsemestre_delete_moduleimpls( formsemestre_id=sem2["formsemestre_id"], module_ids_to_del=[modt["module_id"]])
# deuxieme methode de supression d'un module
li_module2 = sco_edit_module.do_module_list()
assert len(li_module2) == 3 # verification de la suppression du module
lim_sem2 = sco_moduleimpl.do_moduleimpl_list(
formsemestre_id=sem2["formsemestre_id"]
)
assert len(lim_sem2) == 0 # deuxieme vérification si le module s'est bien sup
li_mat = sco_edit_matiere.do_matiere_list()
assert len(li_mat) == 4
sco_edit_matiere.do_matiere_delete(oid=matt["matiere_id"]) # on supprime la matiere
li_mat2 = sco_edit_matiere.do_matiere_list()
assert len(li_mat2) == 3 # verification de la suppression de la matiere
li_ue = sco_edit_ue.do_ue_list()
assert len(li_ue) == 4
sco_edit_ue.ue_delete(ue_id=uet["ue_id"], dialog_confirmed=True)
li_ue2 = sco_edit_ue.do_ue_list()
assert len(li_ue2) == 3 # verification de la suppression de l'UE
# --- Suppression d'une formation
# Il faut d'abbord supprimer le semestre aussi.
sco_formsemestre_edit.do_formsemestre_delete(
formsemestre_id=semt["formsemestre_id"]
)
sco_edit_formation.do_formation_delete(oid=f2["formation_id"])
lif3 = notes.formation_list(format="json").get_data(as_text=True)
assert isinstance(lif3, str)
load_lif3 = json.loads(lif3)
assert len(load_lif3) == 1
def test_import_formation(test_client):
"""Test import/export formations"""
G = sco_fake_gen.ScoFake(verbose=False)
# Lecture fichier XML local:
with open("tests/unit/formation-exemple-1.xml") as f:
doc = f.read()
# --- Création de la formation
f = sco_formations.formation_import_xml(doc)
assert len(f) == 3 # 3-uple
formation_id = f[0]
# --- Mise en place de 4 semestres
sems = [
G.create_formsemestre(
formation_id=formation_id,
semestre_id=x[0],
date_debut=x[1],
date_fin=x[2],
)
for x in (
(1, "05/09/2019", "05/01/2020"),
(2, "06/01/2020", "30/06/2020"),
(3, "01/09/2020", "05/01/2021"),
(4, "06/01/2021", "30/06/2021"),
)
]
# et les modules
modules = sco_edit_module.do_module_list({"formation_id": formation_id})
for mod in modules:
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sems[mod["semestre_id"] - 1]["formsemestre_id"],
)
assert mi["ens"] == []
assert mi["module_id"] == mod["module_id"]
# --- Export formation en XML
doc1 = sco_formations.formation_export(formation_id, format="xml").get_data(
as_text=True
)
assert isinstance(doc1, str)

View File

@ -6,3 +6,4 @@
from tools.import_scodoc7_user_db import import_scodoc7_user_db
from tools.import_scodoc7_dept import import_scodoc7_dept
from tools.migrate_scodoc7_archives import migrate_scodoc7_dept_archive

View File

@ -1,4 +1,4 @@
#!/opt/zope213/bin/python
#!/opt/scodoc/venv/bin/python
# -*- coding: utf-8 -*-
# -*- mode: python -*-
@ -68,42 +68,51 @@ anonymize_null = "NULL"
ANONYMIZED_FIELDS = {
"identite.nom": anonymize_name,
"identite.prenom": anonymize_name,
"identite.nom_usuel": anonymize_null,
"identite.civilite": "'X'",
"identite.date_naissance": anonymize_date,
"identite.lieu_naissance": anonymize_question_str,
"identite.dept_naissance": anonymize_question_str,
"identite.nationalite": anonymize_question_str,
"identite.foto": anonymize_null,
"identite.statut": anonymize_null,
"identite.boursier": anonymize_null,
"identite.photo_filename": anonymize_null,
"identite.code_nip": anonymize_null,
"identite.code_ine": anonymize_null,
"identite.nom_usuel": anonymize_null,
"identite.scodoc7_id": anonymize_null,
"adresse.email": "'ano@nyme.fr'",
"adresse.emailperso": anonymize_null,
"adresse.domicile": anonymize_null,
"adresse.codepostaldomicile": anonymize_null,
"adresse.villedomicile": anonymize_null,
"adresse.paysdomicile": anonymize_null,
"adresse.telephone": anonymize_null,
"adresse.telephonemobile": anonymize_null,
"adresse.fax": anonymize_null,
"admissions.nomlycee": anonymize_name,
"billet_absence.description": anonymize_null,
"etud_annotations.comment": anonymize_name,
"entreprises.nom": anonymize_name,
"entreprises.adresse": anonymize_null,
"entreprises.ville": anonymize_null,
"entreprises.codepostal": anonymize_null,
"entreprises.pays": anonymize_null,
"entreprises.contact_origine": anonymize_null,
"entreprises.secteur": anonymize_null,
"entreprises.note": anonymize_null,
"entreprises.privee": anonymize_null,
"entreprises.localisation": anonymize_null,
"entreprise_correspondant.nom": anonymize_name,
"entreprise_correspondant.prenom": anonymize_name,
"entreprise_correspondant.phone1": anonymize_null,
"entreprise_correspondant.phone2": anonymize_null,
"entreprise_correspondant.mobile": anonymize_null,
"entreprise_correspondant.mail1": anonymize_null,
"entreprise_correspondant.mail2": anonymize_null,
"entreprise_correspondant.note": anonymize_null,
"entreprise_correspondant.fax": anonymize_null,
"entreprise_contact.description": anonymize_null,
"entreprise_contact.enseignant": anonymize_null,
# "entreprises.nom": anonymize_name,
# "entreprises.adresse": anonymize_null,
# "entreprises.ville": anonymize_null,
# "entreprises.codepostal": anonymize_null,
# "entreprises.pays": anonymize_null,
# "entreprises.contact_origine": anonymize_null,
# "entreprises.secteur": anonymize_null,
# "entreprises.note": anonymize_null,
# "entreprises.privee": anonymize_null,
# "entreprises.localisation": anonymize_null,
# "entreprise_correspondant.nom": anonymize_name,
# "entreprise_correspondant.prenom": anonymize_name,
# "entreprise_correspondant.phone1": anonymize_null,
# "entreprise_correspondant.phone2": anonymize_null,
# "entreprise_correspondant.mobile": anonymize_null,
# "entreprise_correspondant.mail1": anonymize_null,
# "entreprise_correspondant.mail2": anonymize_null,
# "entreprise_correspondant.note": anonymize_null,
# "entreprise_correspondant.fax": anonymize_null,
# "entreprise_contact.description": anonymize_null,
# "entreprise_contact.enseignant": anonymize_null,
"notes_appreciations.comment": anonymize_name,
}

Some files were not shown because too many files have changed in this diff Show More