forked from ScoDoc/ScoDoc
Compare commits
353 Commits
master
...
WIP_logo_m
Author | SHA1 | Date | |
---|---|---|---|
d0fc8f1320 | |||
828cd35f29 | |||
74f68ee353 | |||
726814e6e9 | |||
0e047261a8 | |||
25bfcc7ed0 | |||
e97eca3fb4 | |||
958e96dce9 | |||
ee5b610da4 | |||
2f4be9b949 | |||
7fdb4c3cb6 | |||
cba292c1e8 | |||
8e01bad6cb | |||
893b523b17 | |||
e1f36d9484 | |||
f40c0d8a6e | |||
a9986e9522 | |||
d6b05b58f3 | |||
41fc0aea40 | |||
8a5419e774 | |||
074a2dcfbe | |||
139f3aefe5 | |||
272f20d447 | |||
5cd6956846 | |||
fa1a77caa6 | |||
adce970921 | |||
ff9c05c769 | |||
f519b2ee57 | |||
e61054cf23 | |||
902df8f886 | |||
f2db115fdf | |||
065fc460cd | |||
f105e1f331 | |||
3aa115e7a2 | |||
cd694a956d | |||
540a956fae | |||
baa5286dae | |||
8fc16dfeda | |||
21fa7112c8 | |||
480af81e0d | |||
d05ce6a93b | |||
f47cd46abc | |||
daa06651d5 | |||
1b7a28ac8d | |||
502f6a9277 | |||
5e2e36cfab | |||
6c747b1f0e | |||
adbbd51cf1 | |||
ea8598d411 | |||
08d4258a49 | |||
bfd60ffce4 | |||
fc6da6c976 | |||
e26dfd7042 | |||
673ac2fdc9 | |||
51430f82bd | |||
8dc98c64f1 | |||
7ebd6f31dc | |||
5d9513315b | |||
9d8b2d5071 | |||
|
9e8da925a5 | ||
52fa49d7f6 | |||
4d9d5293c1 | |||
5b534abf5f | |||
47d728376c | |||
ce4115eeef | |||
12c0af8bb6 | |||
c23a5abb6c | |||
090391300a | |||
ddf3d73c92 | |||
03cfcf3298 | |||
0573b259d9 | |||
a4e4c39797 | |||
ade1b4445c | |||
7f2e87e9d0 | |||
981f2c0e46 | |||
ae525fd267 | |||
a25ebe9a46 | |||
ac09c104e9 | |||
50115337b1 | |||
afb94cb011 | |||
f6dfa912d7 | |||
5f19931d63 | |||
bd5c4d8243 | |||
2f72401ba1 | |||
f534e9757f | |||
4bf983dbe4 | |||
23a59357e9 | |||
81720facff | |||
518b9c049c | |||
1c719b5c7c | |||
ddcc518807 | |||
5002afade1 | |||
39a9f353d2 | |||
7589d4cc34 | |||
01a84f3b12 | |||
9f9cb6cca2 | |||
d8e1c428b0 | |||
4fc31d8b47 | |||
e46c6a410f | |||
68ac7c293a | |||
8f1e465280 | |||
c248def7f2 | |||
2b91fd78df | |||
b1aa36b136 | |||
d2f41b6a21 | |||
db937ca7c5 | |||
461f14631b | |||
668210aaef | |||
0da60384a1 | |||
c29199eff4 | |||
5268ea4f13 | |||
1be2ba1498 | |||
66d443944a | |||
ad0cd6236c | |||
e8ce1e303e | |||
2fe9e5ec39 | |||
c49aecaa2f | |||
0f67ee33ae | |||
280f6cf1c1 | |||
92de66f734 | |||
f73e720de1 | |||
0c913dacdc | |||
66dbec86bf | |||
e56a97eaf6 | |||
3878d68b38 | |||
e249f45ce9 | |||
54ed09ed08 | |||
565055b4e5 | |||
63d73c9ecd | |||
d69a6c283f | |||
264c0d7d9e | |||
29ec51c001 | |||
a909a307c0 | |||
c96b114b08 | |||
390118226d | |||
bb7ed682c0 | |||
256e89605b | |||
2ca91fc4e9 | |||
c56a4257bd | |||
3c38ef4cc0 | |||
c658c7675e | |||
7cc9f6d1f4 | |||
c05e763900 | |||
4ce50927b0 | |||
177a891236 | |||
9c50b58d5f | |||
c2de33f7f5 | |||
c68633bf5b | |||
45d20789dd | |||
93a23ff112 | |||
e8e3423193 | |||
e243fe6bb0 | |||
46269fcebe | |||
a539061c1f | |||
9694ba61c4 | |||
9c528bec7f | |||
1b8186e69b | |||
f0d641a31e | |||
feb57c2ac6 | |||
071c15af79 | |||
dc26d1edea | |||
6e1bc9665d | |||
c1d13d6089 | |||
aed2d6ce10 | |||
3c5b721a3a | |||
165220e2f1 | |||
17cfd7ad79 | |||
179442aa69 | |||
e6e1835cca | |||
fdd7af6a8a | |||
7d5eff4f82 | |||
76bc957373 | |||
d980c6794a | |||
cd6fd10abf | |||
3e45762382 | |||
085ef05e01 | |||
7dda35d37e | |||
b38ee4ea25 | |||
47f1497e5e | |||
8667bd58ba | |||
1f688e2cd5 | |||
52e837dc81 | |||
190304043d | |||
9015780eb7 | |||
19586559ba | |||
5ac5f5eb19 | |||
ef6a6d6ec2 | |||
1fe814a674 | |||
8ab9a67fa6 | |||
bf83d8475a | |||
79b8520034 | |||
54f0b87d39 | |||
51bd6ba141 | |||
3e1136a077 | |||
0ab9a281a9 | |||
e32d7b1b4e | |||
f59308b863 | |||
dd8a07ef64 | |||
bc112efd76 | |||
1781548b66 | |||
1c927cb541 | |||
814a8dbc24 | |||
4a3e37d371 | |||
a447c6e5f9 | |||
8463d368a1 | |||
1f125d3a1d | |||
51fec2d301 | |||
8bfa936361 | |||
1c27ec7dc2 | |||
dffb369bb0 | |||
656c8a9f22 | |||
5dfdf4265e | |||
36c22a7ca7 | |||
4728e77a7b | |||
f79003186a | |||
11ef8857e2 | |||
f5529ec4a6 | |||
550a7888bf | |||
a8198f889a | |||
651f111839 | |||
76af0eb166 | |||
bf57f2bfa5 | |||
c7aba95015 | |||
f012fe6fcf | |||
d577066911 | |||
b1fa9b8ef8 | |||
2a1c541fbd | |||
1b89010b45 | |||
59e1fdc15e | |||
4429ffd3c8 | |||
057832c309 | |||
bb47e89e97 | |||
ccd1a0daba | |||
230c7d488e | |||
9ee7dec202 | |||
18d6324488 | |||
16b3701815 | |||
5ea4e74117 | |||
ce31d3148d | |||
fa5539fd75 | |||
ddf4bf788f | |||
14d533b38a | |||
671ef6a7fa | |||
edc6da3005 | |||
b015cf3f88 | |||
a2c16207cb | |||
00dbd25b42 | |||
4e59b9597b | |||
f1660e12e1 | |||
cb03cc962c | |||
81df68b491 | |||
1741e75f72 | |||
c41726c4a8 | |||
7879c176dd | |||
75f43bbdde | |||
0a50edc9f0 | |||
373feece76 | |||
6d1ffb122b | |||
92c401f17c | |||
36c7358eed | |||
9c5408f503 | |||
2add3e12cc | |||
d0ab9dc66a | |||
beeca54a94 | |||
73cf9a6f4d | |||
fede1ae7af | |||
845152afdd | |||
a4d091fa2d | |||
ffa7e07cd3 | |||
865192bc0d | |||
b56f205e89 | |||
eded2fffe9 | |||
13f1539282 | |||
ae51e4c17a | |||
7214627994 | |||
6cc1b60da4 | |||
4297d36dad | |||
2999199b19 | |||
f516ccdfe7 | |||
2c97349acf | |||
5dfc64a62d | |||
9dd8198c7b | |||
f18a9c7559 | |||
985c6df3b6 | |||
286e9cdc2f | |||
0381576750 | |||
7a0a04bdb3 | |||
35f23995aa | |||
29221666a4 | |||
d7e6a7d714 | |||
179be1baa0 | |||
a5ed9b815f | |||
13c027fc19 | |||
31505e1330 | |||
9a9dc4a483 | |||
11ba73d264 | |||
7daa49f2aa | |||
f7961a135a | |||
c955870e1e | |||
80f5536de5 | |||
2519d08e40 | |||
987800c30e | |||
2a72fb881b | |||
87ecd09f0e | |||
6e7a104fb0 | |||
b03eee12a1 | |||
44117fb0e2 | |||
42ef9f795f | |||
bd2e0ccde5 | |||
5f0f437f2e | |||
b6cc251c94 | |||
5f6c434497 | |||
45352d9248 | |||
b3225e07f7 | |||
0ef822cfd8 | |||
a23ae38014 | |||
7d59b52018 | |||
80238545f3 | |||
72e075530c | |||
91cc421ef8 | |||
8b6a569a31 | |||
c8949e870f | |||
30481e4729 | |||
085aff657a | |||
3666f8b1ec | |||
bec7deb581 | |||
6dbbcde454 | |||
9578c789dc | |||
0fedb7771c | |||
6dba8933c4 | |||
5efc493542 | |||
a34dd656be | |||
2ec2be4234 | |||
49609fa657 | |||
8a16216d4b | |||
96f457260f | |||
0f9b52bc9b | |||
83174f2f5e | |||
3fbda90a2f | |||
de206674d9 | |||
b06f37b18e | |||
3496cc7beb | |||
01c264c3c7 | |||
c44aa808df | |||
c8872bd220 | |||
7f63ab222b | |||
ed07e42222 | |||
35768e9241 | |||
050e54de3e | |||
37484b7fc9 | |||
f828134ea2 | |||
a4d0205cc7 | |||
770ccb4d6e |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -170,3 +170,4 @@ Thumbs.db
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
|
||||||
|
|
||||||
|
copy
|
||||||
|
|
9
API.yaml
Normal file
9
API.yaml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: API Scodoc
|
||||||
|
description: API Scodoc
|
||||||
|
version: 1.0.0
|
||||||
|
servers:
|
||||||
|
- url: 'https://192.168.1.24:5000'
|
||||||
|
paths:
|
||||||
|
|
45
README.md
45
README.md
|
@ -3,16 +3,12 @@
|
||||||
|
|
||||||
(c) Emmanuel Viennet 1999 - 2021 (voir LICENCE.txt)
|
(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>
|
Installation: voir instructions à jour sur <https://scodoc.org/GuideInstallDebian11>
|
||||||
|
|
||||||
Documentation utilisateur: <https://scodoc.org>
|
Documentation utilisateur: <https://scodoc.org>
|
||||||
|
|
||||||
## Version ScoDoc 9
|
## 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
|
La version ScoDoc 9 est basée sur Flask (au lieu de Zope) et sur
|
||||||
**python 3.9+**.
|
**python 3.9+**.
|
||||||
|
|
||||||
|
@ -22,13 +18,13 @@ Flask, SQLAlchemy, au lien de Python2/Zope dans les versions précédentes).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### État actuel (27 août 21)
|
### État actuel (26 sept 21)
|
||||||
|
|
||||||
- Tests en cours, notamment système d'installation et de migration.
|
- 9.0 reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
|
||||||
|
|
||||||
**Fonctionnalités non intégrées:**
|
**Fonctionnalités non intégrées:**
|
||||||
|
|
||||||
- feuille "placement" (en cours)
|
- génération LaTeX des avis de poursuite d'études
|
||||||
|
|
||||||
- ancien module "Entreprises" (obsolete)
|
- ancien module "Entreprises" (obsolete)
|
||||||
|
|
||||||
|
@ -97,7 +93,7 @@ Certains tests ont besoin d'un département déjà créé, qui n'est pas créé
|
||||||
scripts de tests:
|
scripts de tests:
|
||||||
Lancer au préalable:
|
Lancer au préalable:
|
||||||
|
|
||||||
flask sco-delete-dept TEST00 && flask sco-create-dept TEST00
|
flask delete-dept TEST00 && flask create-dept TEST00
|
||||||
|
|
||||||
Puis dérouler les tests unitaires:
|
Puis dérouler les tests unitaires:
|
||||||
|
|
||||||
|
@ -110,13 +106,15 @@ Ou avec couverture (`pip install pytest-cov`)
|
||||||
|
|
||||||
#### Utilisation des tests unitaires pour initialiser la base de dev
|
#### Utilisation des tests unitaires pour initialiser la base de dev
|
||||||
On peut aussi utiliser les tests unitaires pour mettre la base
|
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.
|
de données de développement dans un état connu, par exemple pour éviter de
|
||||||
|
recréer à la main étudiants et semestres quand on développe.
|
||||||
|
|
||||||
Il suffit de positionner une variable d'environnement indiquant la BD utilisée par les tests:
|
Il suffit de positionner une variable d'environnement indiquant la BD utilisée par les tests:
|
||||||
|
|
||||||
export SCODOC_TEST_DATABASE_URI=postgresql:///SCODOC_DEV
|
export SCODOC_TEST_DATABASE_URI=postgresql:///SCODOC_DEV
|
||||||
|
|
||||||
puis de les lancer normalement, par exemple:
|
(si elle n'existe pas, voir plus loin pour la créer) puis de les lancer
|
||||||
|
normalement, par exemple:
|
||||||
|
|
||||||
pytest tests/unit/test_sco_basic.py
|
pytest tests/unit/test_sco_basic.py
|
||||||
|
|
||||||
|
@ -132,12 +130,13 @@ base de données (tous les départements, et les utilisateurs) avant de commence
|
||||||
|
|
||||||
On utilise SQLAlchemy avec Alembic et Flask-Migrate.
|
On utilise SQLAlchemy avec Alembic et Flask-Migrate.
|
||||||
|
|
||||||
flask db migrate -m "ScoDoc 9.0.x: ..." # ajuster le message !
|
flask db migrate -m "message explicatif....."
|
||||||
flask db upgrade
|
flask db upgrade
|
||||||
|
|
||||||
Ne pas oublier de commiter les migrations (`git add migrations` ...).
|
Ne pas oublier de d'ajouter le script de migration à git (`git add migrations/...`).
|
||||||
|
|
||||||
Mémo pour développeurs: séquence re-création d'une base:
|
**Mémo**: séquence re-création d'une base (vérifiez votre `.env`
|
||||||
|
ou variables d'environnement pour interroger la bonne base !).
|
||||||
|
|
||||||
dropdb SCODOC_DEV
|
dropdb SCODOC_DEV
|
||||||
tools/create_database.sh SCODOC_DEV # créé base SQL
|
tools/create_database.sh SCODOC_DEV # créé base SQL
|
||||||
|
@ -152,7 +151,25 @@ 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
|
migration, utiliser les commandes `flask db history`et `flask db stamp`pour se
|
||||||
positionner à la bonne étape.
|
positionner à la bonne étape.
|
||||||
|
|
||||||
# Paquet debian 11
|
### Profiling
|
||||||
|
|
||||||
|
Sur une machine de DEV, lancer
|
||||||
|
|
||||||
|
flask profile --host 0.0.0.0 --length 32 --profile-dir /opt/scodoc-data
|
||||||
|
|
||||||
|
le fichier `.prof` sera alors écrit dans `/opt/scodoc-data` (on peut aussi utiliser `/tmp`).
|
||||||
|
|
||||||
|
Pour la visualisation, [snakeviz](https://jiffyclub.github.io/snakeviz/) est bien:
|
||||||
|
|
||||||
|
pip install snakeviz
|
||||||
|
|
||||||
|
puis
|
||||||
|
|
||||||
|
snakeviz -s --hostname 0.0.0.0 -p 5555 /opt/scodoc-data/GET.ScoDoc......prof
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Paquet Debian 11
|
||||||
|
|
||||||
Les scripts associés au paquet Debian (.deb) sont dans `tools/debian`. Le plus
|
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
|
important est `postinst`qui se charge de configurer le système (install ou
|
||||||
|
|
120
app/__init__.py
120
app/__init__.py
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: UTF-8 -*
|
# -*- coding: UTF-8 -*
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
|
|
||||||
|
import datetime
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
@ -17,16 +18,22 @@ from flask import render_template
|
||||||
from flask.logging import default_handler
|
from flask.logging import default_handler
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from flask_login import LoginManager
|
from flask_login import LoginManager, current_user
|
||||||
from flask_mail import Mail
|
from flask_mail import Mail
|
||||||
from flask_bootstrap import Bootstrap
|
from flask_bootstrap import Bootstrap
|
||||||
from flask_moment import Moment
|
from flask_moment import Moment
|
||||||
from flask_caching import Cache
|
from flask_caching import Cache
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
from app.scodoc.sco_exceptions import ScoValueError, APIInvalidParams
|
from app.scodoc.sco_exceptions import (
|
||||||
|
AccessDenied,
|
||||||
|
ScoGenError,
|
||||||
|
ScoValueError,
|
||||||
|
APIInvalidParams,
|
||||||
|
)
|
||||||
from config import DevConfig
|
from config import DevConfig
|
||||||
import sco_version
|
import sco_version
|
||||||
|
from flask_debugtoolbar import DebugToolbarExtension
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
migrate = Migrate(compare_type=True)
|
migrate = Migrate(compare_type=True)
|
||||||
|
@ -50,10 +57,21 @@ def handle_sco_value_error(exc):
|
||||||
return render_template("sco_value_error.html", exc=exc), 404
|
return render_template("sco_value_error.html", exc=exc), 404
|
||||||
|
|
||||||
|
|
||||||
|
def handle_access_denied(exc):
|
||||||
|
return render_template("error_access_denied.html", exc=exc), 403
|
||||||
|
|
||||||
|
|
||||||
def internal_server_error(e):
|
def internal_server_error(e):
|
||||||
"""Bugs scodoc, erreurs 500"""
|
"""Bugs scodoc, erreurs 500"""
|
||||||
# note that we set the 500 status explicitly
|
# note that we set the 500 status explicitly
|
||||||
return render_template("error_500.html", SCOVERSION=sco_version.SCOVERSION), 500
|
return (
|
||||||
|
render_template(
|
||||||
|
"error_500.html",
|
||||||
|
SCOVERSION=sco_version.SCOVERSION,
|
||||||
|
date=datetime.datetime.now().isoformat(),
|
||||||
|
),
|
||||||
|
500,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def handle_invalid_usage(error):
|
def handle_invalid_usage(error):
|
||||||
|
@ -82,7 +100,7 @@ def postgresql_server_error(e):
|
||||||
return render_raw_html("error_503.html", SCOVERSION=sco_version.SCOVERSION), 503
|
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"""
|
"""Ajoute URL et remote_addr for logging"""
|
||||||
|
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
|
@ -92,10 +110,46 @@ class RequestFormatter(logging.Formatter):
|
||||||
else:
|
else:
|
||||||
record.url = None
|
record.url = None
|
||||||
record.remote_addr = None
|
record.remote_addr = None
|
||||||
|
record.sco_user = current_user
|
||||||
|
if has_request_context():
|
||||||
|
record.sco_admin_mail = current_app.config["SCODOC_ADMIN_MAIL"]
|
||||||
|
else:
|
||||||
|
record.sco_admin_mail = "(pas de requête)"
|
||||||
|
|
||||||
return super().format(record)
|
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:
|
||||||
|
# rep = reprlib.Repr() # abbrège
|
||||||
|
record.http_params = str(request.form)[:2048]
|
||||||
|
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
|
||||||
|
|
||||||
|
if has_request_context():
|
||||||
|
record.sco_admin_mail = current_app.config["SCODOC_ADMIN_MAIL"]
|
||||||
|
else:
|
||||||
|
record.sco_admin_mail = "(pas de requête)"
|
||||||
|
return super().format(record)
|
||||||
|
|
||||||
|
|
||||||
class ScoSMTPHandler(SMTPHandler):
|
class ScoSMTPHandler(SMTPHandler):
|
||||||
def getSubject(self, record: logging.LogRecord) -> str:
|
def getSubject(self, record: logging.LogRecord) -> str:
|
||||||
stack_summary = traceback.extract_tb(record.exc_info[2])
|
stack_summary = traceback.extract_tb(record.exc_info[2])
|
||||||
|
@ -105,8 +159,24 @@ class ScoSMTPHandler(SMTPHandler):
|
||||||
return subject
|
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):
|
def create_app(config_class=DevConfig):
|
||||||
app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static")
|
app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static")
|
||||||
|
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||||
app.logger.setLevel(logging.DEBUG)
|
app.logger.setLevel(logging.DEBUG)
|
||||||
app.config.from_object(config_class)
|
app.config.from_object(config_class)
|
||||||
|
|
||||||
|
@ -118,8 +188,11 @@ def create_app(config_class=DevConfig):
|
||||||
moment.init_app(app)
|
moment.init_app(app)
|
||||||
cache.init_app(app)
|
cache.init_app(app)
|
||||||
sco_cache.CACHE = cache
|
sco_cache.CACHE = cache
|
||||||
|
toolbar = DebugToolbarExtension(app)
|
||||||
|
|
||||||
|
app.register_error_handler(ScoGenError, handle_sco_value_error)
|
||||||
app.register_error_handler(ScoValueError, handle_sco_value_error)
|
app.register_error_handler(ScoValueError, handle_sco_value_error)
|
||||||
|
app.register_error_handler(AccessDenied, handle_access_denied)
|
||||||
app.register_error_handler(500, internal_server_error)
|
app.register_error_handler(500, internal_server_error)
|
||||||
app.register_error_handler(503, postgresql_server_error)
|
app.register_error_handler(503, postgresql_server_error)
|
||||||
app.register_error_handler(APIInvalidParams, handle_invalid_usage)
|
app.register_error_handler(APIInvalidParams, handle_invalid_usage)
|
||||||
|
@ -148,9 +221,18 @@ def create_app(config_class=DevConfig):
|
||||||
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
|
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
|
||||||
)
|
)
|
||||||
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
|
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
|
||||||
scodoc_exc_formatter = RequestFormatter(
|
scodoc_log_formatter = LogRequestFormatter(
|
||||||
"[%(asctime)s] %(remote_addr)s requested %(url)s\n"
|
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
|
||||||
"%(levelname)s in %(module)s: %(message)s"
|
"%(levelname)s: %(message)s"
|
||||||
|
)
|
||||||
|
# les champs additionnels sont définis dans LogRequestFormatter
|
||||||
|
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"
|
||||||
|
"Admin mail: %(sco_admin_mail)s\n"
|
||||||
)
|
)
|
||||||
if not app.testing:
|
if not app.testing:
|
||||||
if not app.debug:
|
if not app.debug:
|
||||||
|
@ -179,7 +261,7 @@ def create_app(config_class=DevConfig):
|
||||||
app.logger.addHandler(mail_handler)
|
app.logger.addHandler(mail_handler)
|
||||||
else:
|
else:
|
||||||
# Pour logs en DEV uniquement:
|
# Pour logs en DEV uniquement:
|
||||||
default_handler.setFormatter(scodoc_exc_formatter)
|
default_handler.setFormatter(scodoc_log_formatter)
|
||||||
|
|
||||||
# Config logs pour DEV et PRODUCTION
|
# Config logs pour DEV et PRODUCTION
|
||||||
# Configuration des logs (actifs aussi en mode development)
|
# Configuration des logs (actifs aussi en mode development)
|
||||||
|
@ -188,9 +270,17 @@ def create_app(config_class=DevConfig):
|
||||||
file_handler = WatchedFileHandler(
|
file_handler = WatchedFileHandler(
|
||||||
app.config["SCODOC_LOG_FILE"], encoding="utf-8"
|
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)
|
file_handler.setLevel(logging.INFO)
|
||||||
app.logger.addHandler(file_handler)
|
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.setLevel(logging.INFO)
|
||||||
app.logger.info(f"{sco_version.SCONAME} {sco_version.SCOVERSION} startup")
|
app.logger.info(f"{sco_version.SCONAME} {sco_version.SCOVERSION} startup")
|
||||||
|
@ -199,15 +289,19 @@ def create_app(config_class=DevConfig):
|
||||||
)
|
)
|
||||||
# ---- INITIALISATION SPECIFIQUES A SCODOC
|
# ---- INITIALISATION SPECIFIQUES A SCODOC
|
||||||
from app.scodoc import sco_bulletins_generator
|
from app.scodoc import sco_bulletins_generator
|
||||||
from app.scodoc.sco_bulletins_example import BulletinGeneratorExample
|
|
||||||
from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy
|
from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy
|
||||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||||
from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC
|
from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC
|
||||||
|
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorExample)
|
# l'ordre est important, le premeir sera le "défaut" pour les nouveaux départements.
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
|
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
|
||||||
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
|
||||||
|
if app.testing or app.debug:
|
||||||
|
from app.scodoc.sco_bulletins_example import BulletinGeneratorExample
|
||||||
|
|
||||||
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorExample)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
@ -225,6 +319,8 @@ def set_sco_dept(scodoc_dept: str):
|
||||||
g.scodoc_dept_id = dept.id # l'id
|
g.scodoc_dept_id = dept.id # l'id
|
||||||
if not hasattr(g, "db_conn"):
|
if not hasattr(g, "db_conn"):
|
||||||
ndb.open_db_connection()
|
ndb.open_db_connection()
|
||||||
|
if not hasattr(g, "stored_get_formsemestre"):
|
||||||
|
g.stored_get_formsemestre = {}
|
||||||
|
|
||||||
|
|
||||||
def user_db_init():
|
def user_db_init():
|
||||||
|
|
|
@ -6,3 +6,4 @@ from flask import Blueprint
|
||||||
bp = Blueprint("api", __name__)
|
bp = Blueprint("api", __name__)
|
||||||
|
|
||||||
from app.api import sco_api
|
from app.api import sco_api
|
||||||
|
from app.api import tokens
|
||||||
|
|
|
@ -33,7 +33,8 @@ token_auth = HTTPTokenAuth()
|
||||||
|
|
||||||
@basic_auth.verify_password
|
@basic_auth.verify_password
|
||||||
def verify_password(username, password):
|
def verify_password(username, password):
|
||||||
user = User.query.filter_by(username=username).first()
|
# breakpoint()
|
||||||
|
user = User.query.filter_by(user_name=username).first()
|
||||||
if user and user.check_password(password):
|
if user and user.check_password(password):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
@ -51,3 +52,17 @@ def verify_token(token):
|
||||||
@token_auth.error_handler
|
@token_auth.error_handler
|
||||||
def token_auth_error(status):
|
def token_auth_error(status):
|
||||||
return error_response(status)
|
return error_response(status)
|
||||||
|
|
||||||
|
|
||||||
|
def token_permission_required(permission):
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
scodoc_dept = getattr(g, "scodoc_dept", None)
|
||||||
|
if not current_user.has_permission(permission, scodoc_dept):
|
||||||
|
abort(403)
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
return login_required(decorated_function)
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
|
@ -20,7 +20,9 @@
|
||||||
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
# 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
|
# 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
|
# 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
|
from werkzeug.http import HTTP_STATUS_CODES
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
"""
|
"""
|
||||||
# PAS ENCORE IMPLEMENTEE, juste un essai
|
# PAS ENCORE IMPLEMENTEE, juste un essai
|
||||||
# Pour P. Bouron, il faudrait en priorité l'équivalent de
|
# Pour P. Bouron, il faudrait en priorité l'équivalent de
|
||||||
# Scolarite/Notes/do_moduleimpl_withmodule_list
|
# Scolarite/Notes/moduleimpl_withmodule_list (alias scodoc7 do_moduleimpl_withmodule_list)
|
||||||
# Scolarite/Notes/evaluation_create
|
# Scolarite/Notes/evaluation_create
|
||||||
# Scolarite/Notes/evaluation_delete
|
# Scolarite/Notes/evaluation_delete
|
||||||
# Scolarite/Notes/formation_list
|
# Scolarite/Notes/formation_list
|
||||||
|
@ -48,9 +48,9 @@ from app.api.errors import bad_request
|
||||||
from app import models
|
from app import models
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ScoDoc/api/list_depts", methods=["GET"])
|
@bp.route("list_depts", methods=["GET"])
|
||||||
@token_auth.login_required
|
@token_auth.login_required
|
||||||
def list_depts():
|
def list_depts():
|
||||||
depts = models.Departement.query.filter_by(visible=True).all()
|
depts = models.Departement.query.filter_by(visible=True).all()
|
||||||
data = {"items": [d.to_dict() for d in depts]}
|
data = [d.to_dict() for d in depts]
|
||||||
return jsonify(data)
|
return jsonify(data)
|
||||||
|
|
|
@ -8,7 +8,7 @@ TODO: à revoir complètement pour reprendre ZScoUsers et les pages d'authentifi
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
||||||
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
|
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
|
||||||
from app.auth.models import User
|
from app.auth.models import User, is_valid_password
|
||||||
|
|
||||||
|
|
||||||
_ = lambda x: x # sans babel
|
_ = lambda x: x # sans babel
|
||||||
|
@ -43,8 +43,11 @@ class UserCreationForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class ResetPasswordRequestForm(FlaskForm):
|
class ResetPasswordRequestForm(FlaskForm):
|
||||||
email = StringField(_l("Email"), validators=[DataRequired(), Email()])
|
email = StringField(
|
||||||
submit = SubmitField(_l("Request Password Reset"))
|
_l("Adresse email associée à votre compte ScoDoc:"),
|
||||||
|
validators=[DataRequired(), Email()],
|
||||||
|
)
|
||||||
|
submit = SubmitField(_l("Envoyer"))
|
||||||
|
|
||||||
|
|
||||||
class ResetPasswordForm(FlaskForm):
|
class ResetPasswordForm(FlaskForm):
|
||||||
|
@ -52,7 +55,11 @@ class ResetPasswordForm(FlaskForm):
|
||||||
password2 = PasswordField(
|
password2 = PasswordField(
|
||||||
_l("Répéter"), validators=[DataRequired(), EqualTo("password")]
|
_l("Répéter"), validators=[DataRequired(), EqualTo("password")]
|
||||||
)
|
)
|
||||||
submit = SubmitField(_l("Request Password Reset"))
|
submit = SubmitField(_l("Valider ce mot de passe"))
|
||||||
|
|
||||||
|
def validate_password(self, password):
|
||||||
|
if not is_valid_password(password.data):
|
||||||
|
raise ValidationError(f"Mot de passe trop simple, recommencez")
|
||||||
|
|
||||||
|
|
||||||
class DeactivateUserForm(FlaskForm):
|
class DeactivateUserForm(FlaskForm):
|
||||||
|
|
|
@ -10,6 +10,7 @@ import re
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
import cracklib # pylint: disable=import-error
|
||||||
from flask import current_app, url_for, g
|
from flask import current_app, url_for, g
|
||||||
from flask_login import UserMixin, AnonymousUserMixin
|
from flask_login import UserMixin, AnonymousUserMixin
|
||||||
|
|
||||||
|
@ -25,7 +26,24 @@ from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_etud # a deplacer dans scu
|
from app.scodoc import sco_etud # a deplacer dans scu
|
||||||
|
|
||||||
VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\\\.]+$")
|
VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\.]+$")
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_password(cleartxt):
|
||||||
|
"""Check password.
|
||||||
|
returns True if OK.
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
hasattr(scu.CONFIG, "MIN_PASSWORD_LENGTH")
|
||||||
|
and scu.CONFIG.MIN_PASSWORD_LENGTH > 0
|
||||||
|
and len(cleartxt) < scu.CONFIG.MIN_PASSWORD_LENGTH
|
||||||
|
):
|
||||||
|
return False # invalid: too short
|
||||||
|
try:
|
||||||
|
_ = cracklib.FascistCheck(cleartxt)
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class User(UserMixin, db.Model):
|
class User(UserMixin, db.Model):
|
||||||
|
@ -177,8 +195,9 @@ class User(UserMixin, db.Model):
|
||||||
if "roles_string" in data:
|
if "roles_string" in data:
|
||||||
self.user_roles = []
|
self.user_roles = []
|
||||||
for r_d in data["roles_string"].split(","):
|
for r_d in data["roles_string"].split(","):
|
||||||
role, dept = UserRole.role_dept_from_string(r_d)
|
if r_d:
|
||||||
self.add_role(role, dept)
|
role, dept = UserRole.role_dept_from_string(r_d)
|
||||||
|
self.add_role(role, dept)
|
||||||
|
|
||||||
def get_token(self, expires_in=3600):
|
def get_token(self, expires_in=3600):
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
|
@ -194,6 +213,9 @@ class User(UserMixin, db.Model):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_token(token):
|
def check_token(token):
|
||||||
|
"""Retreive user for given token, chek token's validity
|
||||||
|
and returns the user object.
|
||||||
|
"""
|
||||||
user = User.query.filter_by(token=token).first()
|
user = User.query.filter_by(token=token).first()
|
||||||
if user is None or user.token_expiration < datetime.utcnow():
|
if user is None or user.token_expiration < datetime.utcnow():
|
||||||
return None
|
return None
|
||||||
|
@ -329,7 +351,7 @@ class Role(db.Model):
|
||||||
"""Roles for ScoDoc"""
|
"""Roles for ScoDoc"""
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(64), unique=True)
|
name = db.Column(db.String(64), unique=True) # TODO: , nullable=False))
|
||||||
default = db.Column(db.Boolean, default=False, index=True)
|
default = db.Column(db.Boolean, default=False, index=True)
|
||||||
permissions = db.Column(db.BigInteger) # 64 bits
|
permissions = db.Column(db.BigInteger) # 64 bits
|
||||||
users = db.relationship("User", secondary="user_role", viewonly=True)
|
users = db.relationship("User", secondary="user_role", viewonly=True)
|
||||||
|
@ -388,7 +410,7 @@ class UserRole(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||||
role_id = db.Column(db.Integer, db.ForeignKey("role.id"))
|
role_id = db.Column(db.Integer, db.ForeignKey("role.id"))
|
||||||
dept = db.Column(db.String(64)) # dept acronym
|
dept = db.Column(db.String(64)) # dept acronym ou NULL
|
||||||
user = db.relationship(
|
user = db.relationship(
|
||||||
User, backref=db.backref("user_roles", cascade="all, delete-orphan")
|
User, backref=db.backref("user_roles", cascade="all, delete-orphan")
|
||||||
)
|
)
|
||||||
|
@ -407,6 +429,9 @@ class UserRole(db.Model):
|
||||||
"""
|
"""
|
||||||
fields = role_dept.split("_", 1) # maxsplit=1, le dept peut contenir un "_"
|
fields = role_dept.split("_", 1) # maxsplit=1, le dept peut contenir un "_"
|
||||||
if len(fields) != 2:
|
if len(fields) != 2:
|
||||||
|
current_app.logger.warning(
|
||||||
|
f"role_dept_from_string: Invalid role_dept '{role_dept}'"
|
||||||
|
)
|
||||||
raise ScoValueError("Invalid role_dept")
|
raise ScoValueError("Invalid role_dept")
|
||||||
role_name, dept = fields
|
role_name, dept = fields
|
||||||
if dept == "":
|
if dept == "":
|
||||||
|
@ -418,7 +443,7 @@ class UserRole(db.Model):
|
||||||
|
|
||||||
|
|
||||||
def get_super_admin():
|
def get_super_admin():
|
||||||
"""L'utilisateur admin (où le premier, s'il y en a plusieurs).
|
"""L'utilisateur admin (ou le premier, s'il y en a plusieurs).
|
||||||
Utilisé par les tests unitaires et le script de migration.
|
Utilisé par les tests unitaires et le script de migration.
|
||||||
"""
|
"""
|
||||||
admin_role = Role.query.filter_by(name="SuperAdmin").first()
|
admin_role = Role.query.filter_by(name="SuperAdmin").first()
|
||||||
|
|
|
@ -46,7 +46,10 @@ def login():
|
||||||
if not next_page or url_parse(next_page).netloc != "":
|
if not next_page or url_parse(next_page).netloc != "":
|
||||||
next_page = url_for("scodoc.index")
|
next_page = url_for("scodoc.index")
|
||||||
return redirect(next_page)
|
return redirect(next_page)
|
||||||
return render_template("auth/login.html", title=_("Sign In"), form=form)
|
message = request.args.get("message", "")
|
||||||
|
return render_template(
|
||||||
|
"auth/login.html", title=_("Sign In"), form=form, message=message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/logout")
|
@bp.route("/logout")
|
||||||
|
@ -95,7 +98,9 @@ def reset_password_request():
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
"reset_password_request: for unkown user '{}'".format(form.email.data)
|
"reset_password_request: for unkown user '{}'".format(form.email.data)
|
||||||
)
|
)
|
||||||
flash(_("Voir les instructions envoyées par mail"))
|
flash(
|
||||||
|
_("Voir les instructions envoyées par mail (pensez à regarder vos spams)")
|
||||||
|
)
|
||||||
return redirect(url_for("auth.login"))
|
return redirect(url_for("auth.login"))
|
||||||
return render_template(
|
return render_template(
|
||||||
"auth/reset_password_request.html", title=_("Reset Password"), form=form
|
"auth/reset_password_request.html", title=_("Reset Password"), form=form
|
||||||
|
@ -113,6 +118,6 @@ def reset_password(token):
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
user.set_password(form.password.data)
|
user.set_password(form.password.data)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(_("Your password has been reset."))
|
flash(_("Votre mot de passe a été changé."))
|
||||||
return redirect(url_for("auth.login"))
|
return redirect(url_for("auth.login"))
|
||||||
return render_template("auth/reset_password.html", form=form)
|
return render_template("auth/reset_password.html", form=form, user=user)
|
||||||
|
|
|
@ -10,16 +10,15 @@ import logging
|
||||||
import werkzeug
|
import werkzeug
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
import flask
|
import flask
|
||||||
from flask import g
|
from flask import g, current_app, request
|
||||||
from flask import abort, current_app
|
from flask import abort, url_for, redirect
|
||||||
from flask import request
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
from flask import current_app
|
|
||||||
import flask_login
|
import flask_login
|
||||||
|
|
||||||
import app
|
import app
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
class ZUser(object):
|
class ZUser(object):
|
||||||
|
@ -39,69 +38,6 @@ class ZUser(object):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
# 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
|
|
||||||
if request.method == "POST":
|
|
||||||
# request.form is a werkzeug.datastructures.ImmutableMultiDict
|
|
||||||
# must copy to get a mutable version (needed by TrivialFormulator)
|
|
||||||
self.form = request.form.copy()
|
|
||||||
if request.files:
|
|
||||||
# Add files in form:
|
|
||||||
self.form.update(request.files)
|
|
||||||
for k in request.form:
|
|
||||||
if k.endswith(":list"):
|
|
||||||
self.form[k[:-5]] = request.form.getlist(k)
|
|
||||||
elif request.method == "GET":
|
|
||||||
self.form = {}
|
|
||||||
for k in request.args:
|
|
||||||
# current_app.logger.debug("%s\t%s" % (k, request.args.getlist(k)))
|
|
||||||
if k.endswith(":list"):
|
|
||||||
self.form[k[:-5]] = request.args.getlist(k)
|
|
||||||
else:
|
|
||||||
self.form[k] = request.args[k]
|
|
||||||
# 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}
|
|
||||||
form={r.form}
|
|
||||||
""".format(
|
|
||||||
r=self
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ZResponse(object):
|
|
||||||
"Emulating Zope 2 RESPONSE"
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.headers = {}
|
|
||||||
|
|
||||||
def redirect(self, url):
|
|
||||||
# current_app.logger.debug("ZResponse redirect to:" + str(url))
|
|
||||||
return flask.redirect(url) # http 302
|
|
||||||
|
|
||||||
def setHeader(self, header, value):
|
|
||||||
self.headers[header.lower()] = value
|
|
||||||
|
|
||||||
|
|
||||||
def scodoc(func):
|
def scodoc(func):
|
||||||
"""Décorateur pour toutes les fonctions ScoDoc
|
"""Décorateur pour toutes les fonctions ScoDoc
|
||||||
Affecte le département à g
|
Affecte le département à g
|
||||||
|
@ -114,6 +50,25 @@ def scodoc(func):
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def scodoc_function(*args, **kwargs):
|
def scodoc_function(*args, **kwargs):
|
||||||
|
# print("@scodoc")
|
||||||
|
# interdit les POST si pas loggué
|
||||||
|
if (
|
||||||
|
request.method == "POST"
|
||||||
|
and not current_user.is_authenticated
|
||||||
|
and not request.form.get(
|
||||||
|
"__ac_password"
|
||||||
|
) # exception pour compat API ScoDoc7
|
||||||
|
):
|
||||||
|
current_app.logger.info(
|
||||||
|
"POST by non authenticated user (request.form=%s)",
|
||||||
|
str(request.form)[:2048],
|
||||||
|
)
|
||||||
|
return redirect(
|
||||||
|
url_for(
|
||||||
|
"auth.login",
|
||||||
|
message="La page a expiré. Identifiez-vous et recommencez l'opération",
|
||||||
|
)
|
||||||
|
)
|
||||||
if "scodoc_dept" in kwargs:
|
if "scodoc_dept" in kwargs:
|
||||||
dept_acronym = kwargs["scodoc_dept"]
|
dept_acronym = kwargs["scodoc_dept"]
|
||||||
# current_app.logger.info("setting dept to " + dept_acronym)
|
# current_app.logger.info("setting dept to " + dept_acronym)
|
||||||
|
@ -123,6 +78,7 @@ def scodoc(func):
|
||||||
# current_app.logger.info("setting dept to None")
|
# current_app.logger.info("setting dept to None")
|
||||||
g.scodoc_dept = None
|
g.scodoc_dept = None
|
||||||
g.scodoc_dept_id = -1 # invalide
|
g.scodoc_dept_id = -1 # invalide
|
||||||
|
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
return scodoc_function
|
return scodoc_function
|
||||||
|
@ -132,7 +88,6 @@ def permission_required(permission):
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
# current_app.logger.info("PERMISSION; kwargs=%s" % str(kwargs))
|
|
||||||
scodoc_dept = getattr(g, "scodoc_dept", None)
|
scodoc_dept = getattr(g, "scodoc_dept", None)
|
||||||
if not current_user.has_permission(permission, scodoc_dept):
|
if not current_user.has_permission(permission, scodoc_dept):
|
||||||
abort(403)
|
abort(403)
|
||||||
|
@ -144,7 +99,7 @@ def permission_required(permission):
|
||||||
|
|
||||||
|
|
||||||
def permission_required_compat_scodoc7(permission):
|
def permission_required_compat_scodoc7(permission):
|
||||||
"""Décorateur pour les fonctions utilisée comme API dans ScoDoc 7
|
"""Décorateur pour les fonctions utilisées comme API dans ScoDoc 7
|
||||||
Comme @permission_required mais autorise de passer directement
|
Comme @permission_required mais autorise de passer directement
|
||||||
les informations d'auth en paramètres:
|
les informations d'auth en paramètres:
|
||||||
__ac_name, __ac_password
|
__ac_name, __ac_password
|
||||||
|
@ -153,8 +108,8 @@ def permission_required_compat_scodoc7(permission):
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
# current_app.logger.warning("PERMISSION; kwargs=%s" % str(kwargs))
|
|
||||||
# cherche les paramètre d'auth:
|
# cherche les paramètre d'auth:
|
||||||
|
# print("@permission_required_compat_scodoc7")
|
||||||
auth_ok = False
|
auth_ok = False
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
user_name = request.args.get("__ac_name")
|
user_name = request.args.get("__ac_name")
|
||||||
|
@ -169,7 +124,6 @@ def permission_required_compat_scodoc7(permission):
|
||||||
if u and u.check_password(user_password):
|
if u and u.check_password(user_password):
|
||||||
auth_ok = True
|
auth_ok = True
|
||||||
flask_login.login_user(u)
|
flask_login.login_user(u)
|
||||||
|
|
||||||
# reprend le chemin classique:
|
# reprend le chemin classique:
|
||||||
scodoc_dept = getattr(g, "scodoc_dept", None)
|
scodoc_dept = getattr(g, "scodoc_dept", None)
|
||||||
|
|
||||||
|
@ -193,7 +147,6 @@ def admin_required(f):
|
||||||
|
|
||||||
def scodoc7func(func):
|
def scodoc7func(func):
|
||||||
"""Décorateur pour intégrer les fonctions Zope 2 de ScoDoc 7.
|
"""Décorateur pour intégrer les fonctions Zope 2 de ScoDoc 7.
|
||||||
Ajoute l'argument REQUEST s'il est dans la signature de la fonction.
|
|
||||||
Les paramètres de la query string deviennent des (keywords) paramètres de la fonction.
|
Les paramètres de la query string deviennent des (keywords) paramètres de la fonction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -206,19 +159,21 @@ def scodoc7func(func):
|
||||||
1. via a Flask route ("top level call")
|
1. via a Flask route ("top level call")
|
||||||
2. or be called directly from Python.
|
2. or be called directly from Python.
|
||||||
|
|
||||||
If called via a route, this decorator setups a REQUEST object (emulating Zope2 REQUEST)
|
|
||||||
"""
|
"""
|
||||||
|
# print("@scodoc7func")
|
||||||
# Détermine si on est appelé via une route ("toplevel")
|
# Détermine si on est appelé via une route ("toplevel")
|
||||||
# ou par un appel de fonction python normal.
|
# ou par un appel de fonction python normal.
|
||||||
top_level = not hasattr(g, "zrequest")
|
top_level = not hasattr(g, "scodoc7_decorated")
|
||||||
if not top_level:
|
if not top_level:
|
||||||
# ne "redécore" pas
|
# ne "redécore" pas
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
g.scodoc7_decorated = True
|
||||||
# --- Emulate Zope's REQUEST
|
# --- Emulate Zope's REQUEST
|
||||||
REQUEST = ZRequest()
|
# REQUEST = ZRequest()
|
||||||
g.zrequest = REQUEST
|
# g.zrequest = REQUEST
|
||||||
req_args = REQUEST.form # args from query string (get) or form (post)
|
# args from query string (get) or form (post)
|
||||||
# --- Add positional arguments
|
req_args = scu.get_request_args()
|
||||||
|
## --- Add positional arguments
|
||||||
pos_arg_values = []
|
pos_arg_values = []
|
||||||
argspec = inspect.getfullargspec(func)
|
argspec = inspect.getfullargspec(func)
|
||||||
# current_app.logger.info("argspec=%s" % str(argspec))
|
# current_app.logger.info("argspec=%s" % str(argspec))
|
||||||
|
@ -227,10 +182,12 @@ def scodoc7func(func):
|
||||||
arg_names = argspec.args[:-nb_default_args]
|
arg_names = argspec.args[:-nb_default_args]
|
||||||
else:
|
else:
|
||||||
arg_names = argspec.args
|
arg_names = argspec.args
|
||||||
for arg_name in arg_names:
|
for arg_name in arg_names: # pour chaque arg de la fonction vue
|
||||||
if arg_name == "REQUEST": # special case
|
if arg_name == "REQUEST": # ne devrait plus arriver !
|
||||||
pos_arg_values.append(REQUEST)
|
# debug check, TODO remove after tests
|
||||||
|
raise ValueError("invalid REQUEST parameter !")
|
||||||
else:
|
else:
|
||||||
|
# peut produire une KeyError s'il manque un argument attendu:
|
||||||
v = req_args[arg_name]
|
v = req_args[arg_name]
|
||||||
# try to convert all arguments to INTEGERS
|
# try to convert all arguments to INTEGERS
|
||||||
# necessary for db ids and boolean values
|
# necessary for db ids and boolean values
|
||||||
|
@ -244,9 +201,9 @@ def scodoc7func(func):
|
||||||
# Add keyword arguments
|
# Add keyword arguments
|
||||||
if nb_default_args:
|
if nb_default_args:
|
||||||
for arg_name in argspec.args[-nb_default_args:]:
|
for arg_name in argspec.args[-nb_default_args:]:
|
||||||
if arg_name == "REQUEST": # special case
|
# if arg_name == "REQUEST": # special case
|
||||||
kwargs[arg_name] = REQUEST
|
# kwargs[arg_name] = REQUEST
|
||||||
elif arg_name in req_args:
|
if arg_name in req_args:
|
||||||
# set argument kw optionnel
|
# set argument kw optionnel
|
||||||
v = req_args[arg_name]
|
v = req_args[arg_name]
|
||||||
# try to convert all arguments to INTEGERS
|
# try to convert all arguments to INTEGERS
|
||||||
|
@ -270,13 +227,13 @@ def scodoc7func(func):
|
||||||
# Build response, adding collected http headers:
|
# Build response, adding collected http headers:
|
||||||
headers = []
|
headers = []
|
||||||
kw = {"response": value, "status": 200}
|
kw = {"response": value, "status": 200}
|
||||||
if g.zrequest:
|
# if g.zrequest:
|
||||||
headers = g.zrequest.RESPONSE.headers
|
# headers = g.zrequest.RESPONSE.headers
|
||||||
if not headers:
|
# if not headers:
|
||||||
# no customized header, speedup:
|
# # no customized header, speedup:
|
||||||
return value
|
# return value
|
||||||
if "content-type" in headers:
|
# if "content-type" in headers:
|
||||||
kw["mimetype"] = headers["content-type"]
|
# kw["mimetype"] = headers["content-type"]
|
||||||
r = flask.Response(**kw)
|
r = flask.Response(**kw)
|
||||||
for h in headers:
|
for h in headers:
|
||||||
r.headers[h] = headers[h]
|
r.headers[h] = headers[h]
|
||||||
|
|
|
@ -49,7 +49,7 @@ class AbsenceNotification(db.Model):
|
||||||
nbabsjust = db.Column(db.Integer)
|
nbabsjust = db.Column(db.Integer)
|
||||||
formsemestre_id = db.Column(
|
formsemestre_id = db.Column(
|
||||||
db.Integer,
|
db.Integer,
|
||||||
db.ForeignKey("notes_formsemestre.id"),
|
db.ForeignKey("notes_formsemestre.id", ondelete="CASCADE"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,3 +73,17 @@ class BilletAbsence(db.Model):
|
||||||
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
# true si l'absence _pourrait_ etre justifiée
|
# true si l'absence _pourrait_ etre justifiée
|
||||||
justified = db.Column(db.Boolean(), default=False, server_default="false")
|
justified = db.Column(db.Boolean(), default=False, server_default="false")
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
data = {
|
||||||
|
"id": self.id,
|
||||||
|
"billet_id": self.id,
|
||||||
|
"etudid": self.etudid,
|
||||||
|
"abs_begin": self.abs_begin,
|
||||||
|
"abs_end": self.abs_begin,
|
||||||
|
"description": self.description,
|
||||||
|
"etat": self.etat,
|
||||||
|
"entry_date": self.entry_date,
|
||||||
|
"justified": self.justified,
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
|
4
app/models/but_pn.py
Normal file
4
app/models/but_pn.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""ScoDoc 9 models : Formation BUT 2021
|
||||||
|
"""
|
||||||
|
|
||||||
|
# insérer ici idk
|
|
@ -33,7 +33,7 @@ class Departement(db.Model):
|
||||||
semsets = db.relationship("NotesSemSet", lazy="dynamic", backref="departement")
|
semsets = db.relationship("NotesSemSet", lazy="dynamic", backref="departement")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Departement {self.acronym}>"
|
return f"<{self.__class__.__name__}(id={self.id}, acronym='{self.acronym}')>"
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
data = {
|
data = {
|
||||||
|
|
|
@ -41,7 +41,10 @@ class Identite(db.Model):
|
||||||
code_nip = db.Column(db.Text())
|
code_nip = db.Column(db.Text())
|
||||||
code_ine = db.Column(db.Text())
|
code_ine = db.Column(db.Text())
|
||||||
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
||||||
|
# ne pas utiliser après migrate_scodoc7_dept_archives
|
||||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||||
|
#
|
||||||
|
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
|
||||||
|
|
||||||
|
|
||||||
class Adresse(db.Model):
|
class Adresse(db.Model):
|
||||||
|
@ -146,10 +149,13 @@ class ItemSuiviTag(db.Model):
|
||||||
# Association tag <-> module
|
# Association tag <-> module
|
||||||
itemsuivi_tags_assoc = db.Table(
|
itemsuivi_tags_assoc = db.Table(
|
||||||
"itemsuivi_tags_assoc",
|
"itemsuivi_tags_assoc",
|
||||||
db.Column("tag_id", db.Integer, db.ForeignKey("itemsuivi_tags.id")),
|
db.Column(
|
||||||
db.Column("itemsuivi_id", db.Integer, db.ForeignKey("itemsuivi.id")),
|
"tag_id", db.Integer, db.ForeignKey("itemsuivi_tags.id", ondelete="CASCADE")
|
||||||
|
),
|
||||||
|
db.Column(
|
||||||
|
"itemsuivi_id", db.Integer, db.ForeignKey("itemsuivi.id", ondelete="CASCADE")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
# ON DELETE CASCADE ?
|
|
||||||
|
|
||||||
|
|
||||||
class EtudAnnotation(db.Model):
|
class EtudAnnotation(db.Model):
|
||||||
|
|
|
@ -11,7 +11,7 @@ class NotesFormation(db.Model):
|
||||||
"""Programme pédagogique d'une formation"""
|
"""Programme pédagogique d'une formation"""
|
||||||
|
|
||||||
__tablename__ = "notes_formations"
|
__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)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
formation_id = db.synonym("id")
|
formation_id = db.synonym("id")
|
||||||
|
@ -30,7 +30,12 @@ class NotesFormation(db.Model):
|
||||||
type_parcours = db.Column(db.Integer, default=0, server_default="0")
|
type_parcours = db.Column(db.Integer, default=0, server_default="0")
|
||||||
code_specialite = db.Column(db.String(SHORT_STR_LEN))
|
code_specialite = db.Column(db.String(SHORT_STR_LEN))
|
||||||
|
|
||||||
|
ues = db.relationship("NotesUE", backref="formation", lazy="dynamic")
|
||||||
formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation")
|
formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation")
|
||||||
|
ues = db.relationship("NotesUE", lazy="dynamic", backref="formation")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme}')>"
|
||||||
|
|
||||||
|
|
||||||
class NotesUE(db.Model):
|
class NotesUE(db.Model):
|
||||||
|
@ -61,6 +66,13 @@ class NotesUE(db.Model):
|
||||||
# coef UE, utilise seulement si l'option use_ue_coefs est activée:
|
# coef UE, utilise seulement si l'option use_ue_coefs est activée:
|
||||||
coefficient = db.Column(db.Float)
|
coefficient = db.Column(db.Float)
|
||||||
|
|
||||||
|
# relations
|
||||||
|
matieres = db.relationship("NotesMatiere", lazy="dynamic", backref="ue")
|
||||||
|
modules = db.relationship("NotesModule", lazy="dynamic", backref="ue")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__}(id={self.id}, formation_id={self.formation_id}, acronyme='{self.acronyme}')>"
|
||||||
|
|
||||||
|
|
||||||
class NotesMatiere(db.Model):
|
class NotesMatiere(db.Model):
|
||||||
"""Matières: regroupe les modules d'une UE
|
"""Matières: regroupe les modules d'une UE
|
||||||
|
@ -77,6 +89,8 @@ class NotesMatiere(db.Model):
|
||||||
titre = db.Column(db.Text())
|
titre = db.Column(db.Text())
|
||||||
numero = db.Column(db.Integer) # ordre de présentation
|
numero = db.Column(db.Integer) # ordre de présentation
|
||||||
|
|
||||||
|
modules = db.relationship("NotesModule", lazy="dynamic", backref="matiere")
|
||||||
|
|
||||||
|
|
||||||
class NotesModule(db.Model):
|
class NotesModule(db.Model):
|
||||||
"""Module"""
|
"""Module"""
|
||||||
|
@ -103,6 +117,8 @@ class NotesModule(db.Model):
|
||||||
# id de l'element pedagogique Apogee correspondant:
|
# id de l'element pedagogique Apogee correspondant:
|
||||||
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
|
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
|
||||||
module_type = db.Column(db.Integer) # NULL ou 0:defaut, 1: malus (NOTES_MALUS)
|
module_type = db.Column(db.Integer) # NULL ou 0:defaut, 1: malus (NOTES_MALUS)
|
||||||
|
# Relations:
|
||||||
|
modimpls = db.relationship("NotesModuleImpl", backref="module", lazy="dynamic")
|
||||||
|
|
||||||
|
|
||||||
class NotesTag(db.Model):
|
class NotesTag(db.Model):
|
||||||
|
@ -121,6 +137,12 @@ class NotesTag(db.Model):
|
||||||
# Association tag <-> module
|
# Association tag <-> module
|
||||||
notes_modules_tags = db.Table(
|
notes_modules_tags = db.Table(
|
||||||
"notes_modules_tags",
|
"notes_modules_tags",
|
||||||
db.Column("tag_id", db.Integer, db.ForeignKey("notes_tags.id")),
|
db.Column(
|
||||||
db.Column("module_id", db.Integer, db.ForeignKey("notes_modules.id")),
|
"tag_id",
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_tags.id", ondelete="CASCADE"),
|
||||||
|
),
|
||||||
|
db.Column(
|
||||||
|
"module_id", db.Integer, db.ForeignKey("notes_modules.id", ondelete="CASCADE")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -41,6 +41,10 @@ class FormSemestre(db.Model):
|
||||||
bul_hide_xml = db.Column(
|
bul_hide_xml = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
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):
|
# semestres decales (pour gestion jurys):
|
||||||
gestion_semestrielle = db.Column(
|
gestion_semestrielle = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
|
@ -66,10 +70,16 @@ class FormSemestre(db.Model):
|
||||||
# code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'
|
# code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'
|
||||||
elt_annee_apo = db.Column(db.Text())
|
elt_annee_apo = db.Column(db.Text())
|
||||||
|
|
||||||
|
# Relations:
|
||||||
etapes = db.relationship(
|
etapes = db.relationship(
|
||||||
"NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
|
"NotesFormsemestreEtape", cascade="all,delete", backref="formsemestre"
|
||||||
)
|
)
|
||||||
|
formsemestres = db.relationship(
|
||||||
|
"NotesModuleImpl", backref="formsemestre", lazy="dynamic"
|
||||||
|
)
|
||||||
|
|
||||||
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
||||||
|
# ne pas utiliser après migrate_scodoc7_dept_archives
|
||||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
@ -268,6 +278,7 @@ class NotesModuleImplInscription(db.Model):
|
||||||
"""Inscription à un module (etudiants,moduleimpl)"""
|
"""Inscription à un module (etudiants,moduleimpl)"""
|
||||||
|
|
||||||
__tablename__ = "notes_moduleimpl_inscription"
|
__tablename__ = "notes_moduleimpl_inscription"
|
||||||
|
__table_args__ = (db.UniqueConstraint("moduleimpl_id", "etudid"),)
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
moduleimpl_inscription_id = db.synonym("id")
|
moduleimpl_inscription_id = db.synonym("id")
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"""
|
"""
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.scodoc import bonus_sport
|
from app.scodoc import bonus_sport
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
class ScoPreference(db.Model):
|
class ScoPreference(db.Model):
|
||||||
|
@ -94,7 +95,7 @@ class ScoDocSiteConfig(db.Model):
|
||||||
"""returns bonus func with specified name.
|
"""returns bonus func with specified name.
|
||||||
If name not specified, return the configured function.
|
If name not specified, return the configured function.
|
||||||
None if no bonus function configured.
|
None if no bonus function configured.
|
||||||
Raises NameError if func_name not found in module bonus_sport.
|
Raises ScoValueError if func_name not found in module bonus_sport.
|
||||||
"""
|
"""
|
||||||
if func_name is None:
|
if func_name is None:
|
||||||
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
||||||
|
@ -103,7 +104,13 @@ class ScoDocSiteConfig(db.Model):
|
||||||
func_name = c.value
|
func_name = c.value
|
||||||
if func_name == "": # pas de bonus défini
|
if func_name == "": # pas de bonus défini
|
||||||
return None
|
return None
|
||||||
return getattr(bonus_sport, func_name)
|
try:
|
||||||
|
return getattr(bonus_sport, func_name)
|
||||||
|
except AttributeError:
|
||||||
|
raise ScoValueError(
|
||||||
|
f"""Fonction de calcul maison inexistante: {func_name}.
|
||||||
|
(contacter votre administrateur local)."""
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_bonus_sport_func_names(cls):
|
def get_bonus_sport_func_names(cls):
|
||||||
|
|
8
app/pe/README.md
Normal file
8
app/pe/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Module "Avis de poursuite d'étude"
|
||||||
|
|
||||||
|
Conçu et développé sur ScoDoc 7 par Cléo Baras (IUT de Grenoble) pour le DUT.
|
||||||
|
|
||||||
|
Actuellement non opérationnel dans ScoDoc 9.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,9 +33,9 @@
|
||||||
import os
|
import os
|
||||||
import codecs
|
import codecs
|
||||||
import re
|
import re
|
||||||
from app.scodoc import pe_jurype
|
from app.pe import pe_tagtable
|
||||||
from app.scodoc import pe_tagtable
|
from app.pe import pe_jurype
|
||||||
from app.scodoc import pe_tools
|
from app.pe import pe_tools
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
@ -48,7 +48,7 @@ from app.scodoc import sco_etud
|
||||||
DEBUG = False # Pour debug et repérage des prints à changer en Log
|
DEBUG = False # Pour debug et repérage des prints à changer en Log
|
||||||
|
|
||||||
DONNEE_MANQUANTE = (
|
DONNEE_MANQUANTE = (
|
||||||
u"" # Caractère de remplacement des données manquantes dans un avis PE
|
"" # Caractère de remplacement des données manquantes dans un avis PE
|
||||||
)
|
)
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------
|
||||||
|
@ -102,17 +102,17 @@ def comp_latex_parcourstimeline(etudiant, promo, taille=17):
|
||||||
result: chaine unicode (EV:)
|
result: chaine unicode (EV:)
|
||||||
"""
|
"""
|
||||||
codelatexDebut = (
|
codelatexDebut = (
|
||||||
u"""
|
""""
|
||||||
\\begin{parcourstimeline}{**debut**}{**fin**}{**nbreSemestres**}{%d}
|
\\begin{parcourstimeline}{**debut**}{**fin**}{**nbreSemestres**}{%d}
|
||||||
"""
|
"""
|
||||||
% taille
|
% taille
|
||||||
)
|
)
|
||||||
|
|
||||||
modeleEvent = u"""
|
modeleEvent = """
|
||||||
\\parcoursevent{**nosem**}{**nomsem**}{**descr**}
|
\\parcoursevent{**nosem**}{**nomsem**}{**descr**}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
codelatexFin = u"""
|
codelatexFin = """
|
||||||
\\end{parcourstimeline}
|
\\end{parcourstimeline}
|
||||||
"""
|
"""
|
||||||
reslatex = codelatexDebut
|
reslatex = codelatexDebut
|
||||||
|
@ -125,13 +125,13 @@ def comp_latex_parcourstimeline(etudiant, promo, taille=17):
|
||||||
for no_sem in range(etudiant["nbSemestres"]):
|
for no_sem in range(etudiant["nbSemestres"]):
|
||||||
descr = modeleEvent
|
descr = modeleEvent
|
||||||
nom_semestre_dans_parcours = parcours[no_sem]["nom_semestre_dans_parcours"]
|
nom_semestre_dans_parcours = parcours[no_sem]["nom_semestre_dans_parcours"]
|
||||||
descr = descr.replace(u"**nosem**", str(no_sem + 1))
|
descr = descr.replace("**nosem**", str(no_sem + 1))
|
||||||
if no_sem % 2 == 0:
|
if no_sem % 2 == 0:
|
||||||
descr = descr.replace(u"**nomsem**", nom_semestre_dans_parcours)
|
descr = descr.replace("**nomsem**", nom_semestre_dans_parcours)
|
||||||
descr = descr.replace(u"**descr**", u"")
|
descr = descr.replace("**descr**", "")
|
||||||
else:
|
else:
|
||||||
descr = descr.replace(u"**nomsem**", u"")
|
descr = descr.replace("**nomsem**", "")
|
||||||
descr = descr.replace(u"**descr**", nom_semestre_dans_parcours)
|
descr = descr.replace("**descr**", nom_semestre_dans_parcours)
|
||||||
reslatex += descr
|
reslatex += descr
|
||||||
reslatex += codelatexFin
|
reslatex += codelatexFin
|
||||||
return reslatex
|
return reslatex
|
||||||
|
@ -166,7 +166,7 @@ def get_code_latex_avis_etudiant(
|
||||||
result: chaine unicode
|
result: chaine unicode
|
||||||
"""
|
"""
|
||||||
if not donnees_etudiant or not un_avis_latex: # Cas d'un template vide
|
if not donnees_etudiant or not un_avis_latex: # Cas d'un template vide
|
||||||
return annotationPE if annotationPE else u""
|
return annotationPE if annotationPE else ""
|
||||||
|
|
||||||
# Le template latex (corps + footer)
|
# Le template latex (corps + footer)
|
||||||
code = un_avis_latex + "\n\n" + footer_latex
|
code = un_avis_latex + "\n\n" + footer_latex
|
||||||
|
@ -189,17 +189,17 @@ def get_code_latex_avis_etudiant(
|
||||||
)
|
)
|
||||||
|
|
||||||
# La macro parcourstimeline
|
# La macro parcourstimeline
|
||||||
elif tag_latex == u"parcourstimeline":
|
elif tag_latex == "parcourstimeline":
|
||||||
valeur = comp_latex_parcourstimeline(
|
valeur = comp_latex_parcourstimeline(
|
||||||
donnees_etudiant, donnees_etudiant["promo"]
|
donnees_etudiant, donnees_etudiant["promo"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Le tag annotationPE
|
# Le tag annotationPE
|
||||||
elif tag_latex == u"annotation":
|
elif tag_latex == "annotation":
|
||||||
valeur = annotationPE
|
valeur = annotationPE
|
||||||
|
|
||||||
# Le tag bilanParTag
|
# Le tag bilanParTag
|
||||||
elif tag_latex == u"bilanParTag":
|
elif tag_latex == "bilanParTag":
|
||||||
valeur = get_bilanParTag(donnees_etudiant)
|
valeur = get_bilanParTag(donnees_etudiant)
|
||||||
|
|
||||||
# Les tags "simples": par ex. nom, prenom, civilite, ...
|
# Les tags "simples": par ex. nom, prenom, civilite, ...
|
||||||
|
@ -249,14 +249,14 @@ def get_annotation_PE(etudid, tag_annotation_pe):
|
||||||
]["comment_u"]
|
]["comment_u"]
|
||||||
|
|
||||||
annotationPE = exp.sub(
|
annotationPE = exp.sub(
|
||||||
u"", annotationPE
|
"", annotationPE
|
||||||
) # Suppression du tag d'annotation PE
|
) # Suppression du tag d'annotation PE
|
||||||
annotationPE = annotationPE.replace(u"\r", u"") # Suppression des \r
|
annotationPE = annotationPE.replace("\r", "") # Suppression des \r
|
||||||
annotationPE = annotationPE.replace(
|
annotationPE = annotationPE.replace(
|
||||||
u"<br/>", u"\n\n"
|
"<br/>", "\n\n"
|
||||||
) # Interprète les retours chariots html
|
) # Interprète les retours chariots html
|
||||||
return annotationPE
|
return annotationPE
|
||||||
return u"" # pas d'annotations
|
return "" # pas d'annotations
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------
|
||||||
|
@ -282,7 +282,7 @@ def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ)
|
||||||
):
|
):
|
||||||
donnees_numeriques = donnees_etudiant[aggregat][groupe][tag_scodoc]
|
donnees_numeriques = donnees_etudiant[aggregat][groupe][tag_scodoc]
|
||||||
if champ == "rang":
|
if champ == "rang":
|
||||||
valeur = u"%s/%d" % (
|
valeur = "%s/%d" % (
|
||||||
donnees_numeriques[
|
donnees_numeriques[
|
||||||
pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS.index("rang")
|
pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS.index("rang")
|
||||||
],
|
],
|
||||||
|
@ -303,9 +303,9 @@ def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ)
|
||||||
if isinstance(
|
if isinstance(
|
||||||
donnees_numeriques[indice_champ], float
|
donnees_numeriques[indice_champ], float
|
||||||
): # valeur numérique avec formattage unicode
|
): # valeur numérique avec formattage unicode
|
||||||
valeur = u"%2.2f" % donnees_numeriques[indice_champ]
|
valeur = "%2.2f" % donnees_numeriques[indice_champ]
|
||||||
else:
|
else:
|
||||||
valeur = u"%s" % donnees_numeriques[indice_champ]
|
valeur = "%s" % donnees_numeriques[indice_champ]
|
||||||
|
|
||||||
return valeur
|
return valeur
|
||||||
|
|
||||||
|
@ -356,29 +356,27 @@ def get_bilanParTag(donnees_etudiant, groupe="groupe"):
|
||||||
("\\textit{" + rang + "}") if note else ""
|
("\\textit{" + rang + "}") if note else ""
|
||||||
) # rang masqué si pas de notes
|
) # rang masqué si pas de notes
|
||||||
|
|
||||||
code_latex = u"\\begin{tabular}{|c|" + "|c" * (len(entete)) + "|}\n"
|
code_latex = "\\begin{tabular}{|c|" + "|c" * (len(entete)) + "|}\n"
|
||||||
code_latex += u"\\hline \n"
|
code_latex += "\\hline \n"
|
||||||
code_latex += (
|
code_latex += (
|
||||||
u" & "
|
" & "
|
||||||
+ " & ".join(["\\textbf{" + intitule + "}" for (agg, intitule, _) in entete])
|
+ " & ".join(["\\textbf{" + intitule + "}" for (agg, intitule, _) in entete])
|
||||||
+ " \\\\ \n"
|
+ " \\\\ \n"
|
||||||
)
|
)
|
||||||
code_latex += u"\\hline"
|
code_latex += "\\hline"
|
||||||
code_latex += u"\\hline \n"
|
code_latex += "\\hline \n"
|
||||||
for (i, ligne_val) in enumerate(valeurs["note"]):
|
for (i, ligne_val) in enumerate(valeurs["note"]):
|
||||||
titre = lignes[i] # règle le pb d'encodage
|
titre = lignes[i] # règle le pb d'encodage
|
||||||
|
code_latex += "\\textbf{" + titre + "} & " + " & ".join(ligne_val) + "\\\\ \n"
|
||||||
code_latex += (
|
code_latex += (
|
||||||
u"\\textbf{" + titre + u"} & " + " & ".join(ligne_val) + u"\\\\ \n"
|
" & "
|
||||||
)
|
+ " & ".join(
|
||||||
code_latex += (
|
["{\\scriptsize " + clsmt + "}" for clsmt in valeurs["rang"][i]]
|
||||||
u" & "
|
|
||||||
+ u" & ".join(
|
|
||||||
[u"{\\scriptsize " + clsmt + u"}" for clsmt in valeurs["rang"][i]]
|
|
||||||
)
|
)
|
||||||
+ u"\\\\ \n"
|
+ "\\\\ \n"
|
||||||
)
|
)
|
||||||
code_latex += u"\\hline \n"
|
code_latex += "\\hline \n"
|
||||||
code_latex += u"\\end{tabular}"
|
code_latex += "\\end{tabular}"
|
||||||
|
|
||||||
return code_latex
|
return code_latex
|
||||||
|
|
||||||
|
@ -397,21 +395,15 @@ def get_avis_poursuite_par_etudiant(
|
||||||
nom = jury.syntheseJury[etudid]["nom"].replace(" ", "-")
|
nom = jury.syntheseJury[etudid]["nom"].replace(" ", "-")
|
||||||
prenom = jury.syntheseJury[etudid]["prenom"].replace(" ", "-")
|
prenom = jury.syntheseJury[etudid]["prenom"].replace(" ", "-")
|
||||||
|
|
||||||
nom_fichier = (
|
nom_fichier = scu.sanitize_filename(
|
||||||
u"avis_poursuite_"
|
"avis_poursuite_%s_%s_%s" % (nom, prenom, etudid)
|
||||||
+ pe_tools.remove_accents(nom)
|
|
||||||
+ "_"
|
|
||||||
+ pe_tools.remove_accents(prenom)
|
|
||||||
+ "_"
|
|
||||||
+ str(etudid)
|
|
||||||
)
|
)
|
||||||
if pe_tools.PE_DEBUG:
|
if pe_tools.PE_DEBUG:
|
||||||
pe_tools.pe_print("fichier latex =" + nom_fichier, type(nom_fichier))
|
pe_tools.pe_print("fichier latex =" + nom_fichier, type(nom_fichier))
|
||||||
|
|
||||||
# Entete (commentaire)
|
# Entete (commentaire)
|
||||||
|
|
||||||
contenu_latex = (
|
contenu_latex = (
|
||||||
u"%% ---- Etudiant: " + civilite_str + " " + nom + " " + prenom + u"\n"
|
"%% ---- Etudiant: " + civilite_str + " " + nom + " " + prenom + "\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# les annnotations
|
# les annnotations
|
|
@ -52,10 +52,10 @@ from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_codes_parcours # sco_codes_parcours.NEXT -> sem suivant
|
from app.scodoc import sco_codes_parcours # sco_codes_parcours.NEXT -> sem suivant
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import pe_tagtable
|
from app.pe import pe_tagtable
|
||||||
from app.scodoc import pe_tools
|
from app.pe import pe_tools
|
||||||
from app.scodoc import pe_semestretag
|
from app.pe import pe_semestretag
|
||||||
from app.scodoc import pe_settag
|
from app.pe import pe_settag
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------
|
||||||
def comp_nom_semestre_dans_parcours(sem):
|
def comp_nom_semestre_dans_parcours(sem):
|
||||||
|
@ -946,7 +946,7 @@ class JuryPE(object):
|
||||||
return list(taglist)
|
return list(taglist)
|
||||||
|
|
||||||
def get_allTagInSyntheseJury(self):
|
def get_allTagInSyntheseJury(self):
|
||||||
"""Extrait tous les tags du dictionnaire syntheseJury trié par ordre alphabétique. [] si aucun tag """
|
"""Extrait tous les tags du dictionnaire syntheseJury trié par ordre alphabétique. [] si aucun tag"""
|
||||||
allTags = set()
|
allTags = set()
|
||||||
for nom in JuryPE.PARCOURS.keys():
|
for nom in JuryPE.PARCOURS.keys():
|
||||||
allTags = allTags.union(set(self.get_allTagForAggregat(nom)))
|
allTags = allTags.union(set(self.get_allTagForAggregat(nom)))
|
|
@ -40,7 +40,7 @@ from app import log
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_tag_module
|
from app.scodoc import sco_tag_module
|
||||||
from app.scodoc import pe_tagtable
|
from app.pe import pe_tagtable
|
||||||
|
|
||||||
|
|
||||||
class SemestreTag(pe_tagtable.TableTag):
|
class SemestreTag(pe_tagtable.TableTag):
|
|
@ -36,8 +36,8 @@ Created on Fri Sep 9 09:15:05 2016
|
||||||
@author: barasc
|
@author: barasc
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from app.scodoc.pe_tools import pe_print, PE_DEBUG
|
from app.pe.pe_tools import pe_print, PE_DEBUG
|
||||||
from app.scodoc import pe_tagtable
|
from app.pe import pe_tagtable
|
||||||
|
|
||||||
|
|
||||||
class SetTag(pe_tagtable.TableTag):
|
class SetTag(pe_tagtable.TableTag):
|
|
@ -44,7 +44,7 @@ import unicodedata
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
import six
|
from app.scodoc.sco_logos import find_logo
|
||||||
|
|
||||||
PE_DEBUG = 0
|
PE_DEBUG = 0
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ def escape_for_latex(s):
|
||||||
}
|
}
|
||||||
exp = re.compile(
|
exp = re.compile(
|
||||||
"|".join(
|
"|".join(
|
||||||
re.escape(six.text_type(key))
|
re.escape(key)
|
||||||
for key in sorted(list(conv.keys()), key=lambda item: -len(item))
|
for key in sorted(list(conv.keys()), key=lambda item: -len(item))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -167,8 +167,19 @@ def list_directory_filenames(path):
|
||||||
def add_local_file_to_zip(zipfile, ziproot, pathname, path_in_zip):
|
def add_local_file_to_zip(zipfile, ziproot, pathname, path_in_zip):
|
||||||
"""Read pathname server file and add content to zip under path_in_zip"""
|
"""Read pathname server file and add content to zip under path_in_zip"""
|
||||||
rooted_path_in_zip = os.path.join(ziproot, path_in_zip)
|
rooted_path_in_zip = os.path.join(ziproot, path_in_zip)
|
||||||
data = open(pathname).read()
|
zipfile.write(filename=pathname, arcname=rooted_path_in_zip)
|
||||||
zipfile.writestr(rooted_path_in_zip, data)
|
# data = open(pathname).read()
|
||||||
|
# zipfile.writestr(rooted_path_in_zip, data)
|
||||||
|
|
||||||
|
|
||||||
|
def add_refs_to_register(register, directory):
|
||||||
|
"""Ajoute les fichiers trouvés dans directory au registre (dictionaire) sous la forme
|
||||||
|
filename => pathname
|
||||||
|
"""
|
||||||
|
length = len(directory)
|
||||||
|
for pathname in list_directory_filenames(directory):
|
||||||
|
filename = pathname[length + 1 :]
|
||||||
|
register[filename] = pathname
|
||||||
|
|
||||||
|
|
||||||
def add_pe_stuff_to_zip(zipfile, ziproot):
|
def add_pe_stuff_to_zip(zipfile, ziproot):
|
||||||
|
@ -179,37 +190,23 @@ def add_pe_stuff_to_zip(zipfile, ziproot):
|
||||||
|
|
||||||
Also copy logos
|
Also copy logos
|
||||||
"""
|
"""
|
||||||
|
register = {}
|
||||||
|
# first add standard (distrib references)
|
||||||
distrib_dir = os.path.join(REP_DEFAULT_AVIS, "distrib")
|
distrib_dir = os.path.join(REP_DEFAULT_AVIS, "distrib")
|
||||||
distrib_pathnames = list_directory_filenames(
|
add_refs_to_register(register=register, directory=distrib_dir)
|
||||||
distrib_dir
|
# then add local references (some oh them may overwrite distrib refs)
|
||||||
) # eg /opt/scodoc/tools/doc_poursuites_etudes/distrib/modeles/toto.tex
|
|
||||||
l = len(distrib_dir)
|
|
||||||
distrib_filenames = {x[l + 1 :] for x in distrib_pathnames} # eg modeles/toto.tex
|
|
||||||
|
|
||||||
local_dir = os.path.join(REP_LOCAL_AVIS, "local")
|
local_dir = os.path.join(REP_LOCAL_AVIS, "local")
|
||||||
local_pathnames = list_directory_filenames(local_dir)
|
add_refs_to_register(register=register, directory=local_dir)
|
||||||
l = len(local_dir)
|
# at this point register contains all refs (filename, pathname) to be saved
|
||||||
local_filenames = {x[l + 1 :] for x in local_pathnames}
|
for filename, pathname in register.items():
|
||||||
|
add_local_file_to_zip(zipfile, ziproot, pathname, "avis/" + filename)
|
||||||
for filename in distrib_filenames | local_filenames:
|
|
||||||
if filename in local_filenames:
|
|
||||||
add_local_file_to_zip(
|
|
||||||
zipfile, ziproot, os.path.join(local_dir, filename), "avis/" + filename
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
add_local_file_to_zip(
|
|
||||||
zipfile,
|
|
||||||
ziproot,
|
|
||||||
os.path.join(distrib_dir, filename),
|
|
||||||
"avis/" + filename,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Logos: (add to logos/ directory in zip)
|
# Logos: (add to logos/ directory in zip)
|
||||||
logos_names = ["logo_header.jpg", "logo_footer.jpg"]
|
logos_names = ["header", "footer"]
|
||||||
for f in logos_names:
|
for name in logos_names:
|
||||||
logo = os.path.join(scu.SCODOC_LOGOS_DIR, f)
|
logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id)
|
||||||
if os.path.isfile(logo):
|
if logo is not None:
|
||||||
add_local_file_to_zip(zipfile, ziproot, logo, "avis/logos/" + f)
|
add_local_file_to_zip(zipfile, ziproot, logo, "avis/logos/" + logo.filename)
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------
|
|
@ -42,10 +42,9 @@ from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
|
|
||||||
from app.scodoc import pe_tools
|
from app.pe import pe_tools
|
||||||
from app.scodoc.pe_tools import PE_LATEX_ENCODING
|
from app.pe import pe_jurype
|
||||||
from app.scodoc import pe_jurype
|
from app.pe import pe_avislatex
|
||||||
from app.scodoc import pe_avislatex
|
|
||||||
|
|
||||||
|
|
||||||
def _pe_view_sem_recap_form(formsemestre_id):
|
def _pe_view_sem_recap_form(formsemestre_id):
|
||||||
|
@ -90,7 +89,6 @@ def pe_view_sem_recap(
|
||||||
semBase = sco_formsemestre.get_formsemestre(formsemestre_id)
|
semBase = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
|
|
||||||
jury = pe_jurype.JuryPE(semBase)
|
jury = pe_jurype.JuryPE(semBase)
|
||||||
|
|
||||||
# Ajout avis LaTeX au même zip:
|
# Ajout avis LaTeX au même zip:
|
||||||
etudids = list(jury.syntheseJury.keys())
|
etudids = list(jury.syntheseJury.keys())
|
||||||
|
|
||||||
|
@ -150,18 +148,14 @@ def pe_view_sem_recap(
|
||||||
footer_latex,
|
footer_latex,
|
||||||
prefs,
|
prefs,
|
||||||
)
|
)
|
||||||
|
jury.add_file_to_zip("avis/" + nom_fichier + ".tex", contenu_latex)
|
||||||
jury.add_file_to_zip(
|
|
||||||
("avis/" + nom_fichier + ".tex").encode(PE_LATEX_ENCODING),
|
|
||||||
contenu_latex.encode(PE_LATEX_ENCODING),
|
|
||||||
)
|
|
||||||
latex_pages[nom_fichier] = contenu_latex # Sauvegarde dans un dico
|
latex_pages[nom_fichier] = contenu_latex # Sauvegarde dans un dico
|
||||||
|
|
||||||
# Nouvelle version : 1 fichier par étudiant avec 1 fichier appelant créée ci-dessous
|
# Nouvelle version : 1 fichier par étudiant avec 1 fichier appelant créée ci-dessous
|
||||||
doc_latex = "\n% -----\n".join(
|
doc_latex = "\n% -----\n".join(
|
||||||
["\\include{" + nom + "}" for nom in sorted(latex_pages.keys())]
|
["\\include{" + nom + "}" for nom in sorted(latex_pages.keys())]
|
||||||
)
|
)
|
||||||
jury.add_file_to_zip("avis/avis_poursuite.tex", doc_latex.encode(PE_LATEX_ENCODING))
|
jury.add_file_to_zip("avis/avis_poursuite.tex", doc_latex)
|
||||||
|
|
||||||
# Ajoute image, LaTeX class file(s) and modeles
|
# Ajoute image, LaTeX class file(s) and modeles
|
||||||
pe_tools.add_pe_stuff_to_zip(jury.zipfile, jury.NOM_EXPORT_ZIP)
|
pe_tools.add_pe_stuff_to_zip(jury.zipfile, jury.NOM_EXPORT_ZIP)
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
v 1.3 (python3)
|
v 1.3 (python3)
|
||||||
"""
|
"""
|
||||||
|
import html
|
||||||
|
|
||||||
|
|
||||||
def TrivialFormulator(
|
def TrivialFormulator(
|
||||||
|
@ -134,7 +135,7 @@ class TF(object):
|
||||||
is_submitted=False,
|
is_submitted=False,
|
||||||
):
|
):
|
||||||
self.form_url = form_url
|
self.form_url = form_url
|
||||||
self.values = values
|
self.values = values.copy()
|
||||||
self.formdescription = list(formdescription)
|
self.formdescription = list(formdescription)
|
||||||
self.initvalues = initvalues
|
self.initvalues = initvalues
|
||||||
self.method = method
|
self.method = method
|
||||||
|
@ -722,7 +723,9 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
||||||
if str(descr["allowed_values"][i]) == str(self.values[field]):
|
if str(descr["allowed_values"][i]) == str(self.values[field]):
|
||||||
R.append('<span class="tf-ro-value">%s</span>' % labels[i])
|
R.append('<span class="tf-ro-value">%s</span>' % labels[i])
|
||||||
elif input_type == "textarea":
|
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":
|
elif input_type == "separator" or input_type == "hidden":
|
||||||
pass
|
pass
|
||||||
elif input_type == "file":
|
elif input_type == "file":
|
||||||
|
|
|
@ -167,6 +167,23 @@ def bonus_iutlh(notes_sport, coefs, infos=None):
|
||||||
return bonus
|
return bonus
|
||||||
|
|
||||||
|
|
||||||
|
def bonus_nantes(notes_sport, coefs, infos=None):
|
||||||
|
"""IUT de Nantes (Septembre 2018)
|
||||||
|
Nous avons différents types de bonification
|
||||||
|
bonfication Sport / Culture / engagement citoyen
|
||||||
|
Nous ajoutons sur le bulletin une bonification de 0,2 pour chaque item
|
||||||
|
la bonification totale ne doit pas excéder les 0,5 point.
|
||||||
|
Sur le bulletin nous ne mettons pas une note sur 20 mais directement les bonifications.
|
||||||
|
|
||||||
|
Dans ScoDoc: on a déclaré une UE "sport&culture" dans laquelle on aura des modules
|
||||||
|
pour chaque activité (Sport, Associations, ...)
|
||||||
|
avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20, mais en fait ce sera la
|
||||||
|
valeur de la bonification: entrer 0,1/20 signifiera un bonus de 0,1 point la moyenne générale)
|
||||||
|
"""
|
||||||
|
bonus = min(0.5, sum([x for x in notes_sport])) # plafonnement à 0.5 points
|
||||||
|
return bonus
|
||||||
|
|
||||||
|
|
||||||
# Bonus sport IUT Tours
|
# Bonus sport IUT Tours
|
||||||
def bonus_tours(notes_sport, coefs, infos=None):
|
def bonus_tours(notes_sport, coefs, infos=None):
|
||||||
"""Calcul bonus sport & culture IUT Tours sur moyenne generale
|
"""Calcul bonus sport & culture IUT Tours sur moyenne generale
|
||||||
|
@ -177,7 +194,8 @@ def bonus_tours(notes_sport, coefs, infos=None):
|
||||||
|
|
||||||
|
|
||||||
def bonus_iutr(notes_sport, coefs, infos=None):
|
def bonus_iutr(notes_sport, coefs, infos=None):
|
||||||
"""Calcul du bonus , regle de l'IUT de Roanne (contribuée par Raphael C., nov 2012)
|
"""Calcul du bonus , règle de l'IUT de Roanne
|
||||||
|
(contribuée par Raphael C., nov 2012)
|
||||||
|
|
||||||
Le bonus est compris entre 0 et 0.35 point.
|
Le bonus est compris entre 0 et 0.35 point.
|
||||||
cette procédure modifie la moyenne de chaque UE capitalisable.
|
cette procédure modifie la moyenne de chaque UE capitalisable.
|
||||||
|
@ -379,6 +397,25 @@ def bonus_iutbethune(notes_sport, coefs, infos=None):
|
||||||
return bonus
|
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):
|
def bonus_demo(notes_sport, coefs, infos=None):
|
||||||
"""Fausse fonction "bonus" pour afficher les informations disponibles
|
"""Fausse fonction "bonus" pour afficher les informations disponibles
|
||||||
et aider les développeurs.
|
et aider les développeurs.
|
||||||
|
@ -386,8 +423,8 @@ def bonus_demo(notes_sport, coefs, infos=None):
|
||||||
qui est ECRASE à chaque appel.
|
qui est ECRASE à chaque appel.
|
||||||
*** Ne pas utiliser en production !!! ***
|
*** Ne pas utiliser en production !!! ***
|
||||||
"""
|
"""
|
||||||
f = open("/tmp/scodoc_bonus.log", "w") # mettre 'a' pour ajouter en fin
|
with open("/tmp/scodoc_bonus.log", "w") as f: # mettre 'a' pour ajouter en fin
|
||||||
f.write("\n---------------\n" + pprint.pformat(infos) + "\n")
|
f.write("\n---------------\n" + pprint.pformat(infos) + "\n")
|
||||||
# Statut de chaque UE
|
# Statut de chaque UE
|
||||||
# for ue_id in infos['moy_ues']:
|
# for ue_id in infos['moy_ues']:
|
||||||
# ue_status = infos['moy_ues'][ue_id]
|
# ue_status = infos['moy_ues'][ue_id]
|
||||||
|
|
|
@ -185,6 +185,9 @@ class GenTable(object):
|
||||||
else:
|
else:
|
||||||
self.preferences = DEFAULT_TABLE_PREFERENCES()
|
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):
|
def get_nb_cols(self):
|
||||||
return len(self.columns_ids)
|
return len(self.columns_ids)
|
||||||
|
|
||||||
|
@ -468,7 +471,10 @@ class GenTable(object):
|
||||||
|
|
||||||
def excel(self, wb=None):
|
def excel(self, wb=None):
|
||||||
"""Simple Excel representation of the table"""
|
"""Simple Excel representation of the table"""
|
||||||
ses = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name, wb=wb)
|
if wb is None:
|
||||||
|
ses = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name, wb=wb)
|
||||||
|
else:
|
||||||
|
ses = wb.create_sheet(sheet_name=self.xls_sheet_name)
|
||||||
ses.rows += self.xls_before_table
|
ses.rows += self.xls_before_table
|
||||||
style_bold = sco_excel.excel_make_style(bold=True)
|
style_bold = sco_excel.excel_make_style(bold=True)
|
||||||
style_base = sco_excel.excel_make_style()
|
style_base = sco_excel.excel_make_style()
|
||||||
|
@ -482,9 +488,7 @@ class GenTable(object):
|
||||||
ses.append_blank_row() # empty line
|
ses.append_blank_row() # empty line
|
||||||
ses.append_single_cell_row(self.origin, style_base)
|
ses.append_single_cell_row(self.origin, style_base)
|
||||||
if wb is None:
|
if wb is None:
|
||||||
return ses.generate_standalone()
|
return ses.generate()
|
||||||
else:
|
|
||||||
ses.generate_embeded()
|
|
||||||
|
|
||||||
def text(self):
|
def text(self):
|
||||||
"raw text representation of the table"
|
"raw text representation of the table"
|
||||||
|
@ -494,7 +498,7 @@ class GenTable(object):
|
||||||
headline = []
|
headline = []
|
||||||
return "\n".join(
|
return "\n".join(
|
||||||
[
|
[
|
||||||
self.text_fields_separator.join([x for x in line])
|
self.text_fields_separator.join([str(x) for x in line])
|
||||||
for line in headline + self.get_data_list()
|
for line in headline + self.get_data_list()
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -573,7 +577,7 @@ class GenTable(object):
|
||||||
"""
|
"""
|
||||||
doc = ElementTree.Element(
|
doc = ElementTree.Element(
|
||||||
self.xml_outer_tag,
|
self.xml_outer_tag,
|
||||||
id=self.table_id,
|
id=str(self.table_id),
|
||||||
origin=self.origin or "",
|
origin=self.origin or "",
|
||||||
caption=self.caption or "",
|
caption=self.caption or "",
|
||||||
)
|
)
|
||||||
|
@ -587,7 +591,7 @@ class GenTable(object):
|
||||||
v = row.get(cid, "")
|
v = row.get(cid, "")
|
||||||
if v is None:
|
if v is None:
|
||||||
v = ""
|
v = ""
|
||||||
x_cell = ElementTree.Element(cid, value=str(v))
|
x_cell = ElementTree.Element(str(cid), value=str(v))
|
||||||
x_row.append(x_cell)
|
x_row.append(x_cell)
|
||||||
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
|
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
|
||||||
|
|
||||||
|
@ -610,7 +614,6 @@ class GenTable(object):
|
||||||
format="html",
|
format="html",
|
||||||
page_title="",
|
page_title="",
|
||||||
filename=None,
|
filename=None,
|
||||||
REQUEST=None,
|
|
||||||
javascripts=[],
|
javascripts=[],
|
||||||
with_html_headers=True,
|
with_html_headers=True,
|
||||||
publish=True,
|
publish=True,
|
||||||
|
@ -643,35 +646,53 @@ class GenTable(object):
|
||||||
H.append(html_sco_header.sco_footer())
|
H.append(html_sco_header.sco_footer())
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
elif format == "pdf":
|
elif format == "pdf":
|
||||||
objects = self.pdf()
|
pdf_objs = self.pdf()
|
||||||
doc = sco_pdf.pdf_basic_page(
|
pdf_doc = sco_pdf.pdf_basic_page(
|
||||||
objects, title=title, preferences=self.preferences
|
pdf_objs, title=title, preferences=self.preferences
|
||||||
)
|
)
|
||||||
if publish:
|
if publish:
|
||||||
return scu.sendPDFFile(REQUEST, doc, filename + ".pdf")
|
return scu.send_file(
|
||||||
|
pdf_doc,
|
||||||
|
filename,
|
||||||
|
suffix=".pdf",
|
||||||
|
mime=scu.PDF_MIMETYPE,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return doc
|
return pdf_doc
|
||||||
elif format == "xls" or format == "xlsx":
|
elif format == "xls" or format == "xlsx": # dans les 2 cas retourne du xlsx
|
||||||
xls = self.excel()
|
xls = self.excel()
|
||||||
if publish:
|
if publish:
|
||||||
return sco_excel.send_excel_file(
|
return scu.send_file(
|
||||||
REQUEST, xls, filename + scu.XLSX_SUFFIX
|
xls,
|
||||||
|
filename,
|
||||||
|
suffix=scu.XLSX_SUFFIX,
|
||||||
|
mime=scu.XLSX_MIMETYPE,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return xls
|
return xls
|
||||||
elif format == "text":
|
elif format == "text":
|
||||||
return self.text()
|
return self.text()
|
||||||
elif format == "csv":
|
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":
|
elif format == "xml":
|
||||||
xml = self.xml()
|
xml = self.xml()
|
||||||
if REQUEST and publish:
|
if publish:
|
||||||
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
|
return scu.send_file(
|
||||||
|
xml, filename, suffix=".xml", mime=scu.XML_MIMETYPE
|
||||||
|
)
|
||||||
return xml
|
return xml
|
||||||
elif format == "json":
|
elif format == "json":
|
||||||
js = self.json()
|
js = self.json()
|
||||||
if REQUEST and publish:
|
if publish:
|
||||||
REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE)
|
return scu.send_file(
|
||||||
|
js, filename, suffix=".json", mime=scu.JSON_MIMETYPE
|
||||||
|
)
|
||||||
return js
|
return js
|
||||||
else:
|
else:
|
||||||
log("make_page: format=%s" % format)
|
log("make_page: format=%s" % format)
|
||||||
|
@ -731,6 +752,8 @@ if __name__ == "__main__":
|
||||||
)
|
)
|
||||||
document.build(objects)
|
document.build(objects)
|
||||||
data = doc.getvalue()
|
data = doc.getvalue()
|
||||||
open("/tmp/gen_table.pdf", "wb").write(data)
|
with open("/tmp/gen_table.pdf", "wb") as f:
|
||||||
p = T.make_page(format="pdf", REQUEST=None)
|
f.write(data)
|
||||||
open("toto.pdf", "wb").write(p)
|
p = T.make_page(format="pdf")
|
||||||
|
with open("toto.pdf", "wb") as f:
|
||||||
|
f.write(p)
|
||||||
|
|
|
@ -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"?>
|
_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">
|
<!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">
|
<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/scodoc.css" rel="stylesheet" type="text/css" />
|
||||||
<link href="/ScoDoc/static/css/menu.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 src="/ScoDoc/static/libjs/menu.js"></script>
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/sorttable.js"></script>
|
<script src="/ScoDoc/static/libjs/sorttable.js"></script>
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
|
<script src="/ScoDoc/static/libjs/bubble.js"></script>
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
window.onload=function(){enableTooltips("gtrcontent")};
|
window.onload=function(){enableTooltips("gtrcontent")};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script>
|
<script 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 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/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" />
|
<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 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/etud_info.js"></script>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
|
def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
|
||||||
H = [
|
H = [
|
||||||
_HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING},
|
_HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING},
|
||||||
_TOP_LEVEL_CSS,
|
|
||||||
"""</head><body class="gtrcontent" id="gtrcontent">""",
|
"""</head><body class="gtrcontent" id="gtrcontent">""",
|
||||||
scu.CUSTOM_HTML_HEADER_CNX,
|
scu.CUSTOM_HTML_HEADER_CNX,
|
||||||
]
|
]
|
||||||
|
@ -185,13 +180,10 @@ def sco_header(
|
||||||
init_jquery = True
|
init_jquery = True
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
"""<?xml version="1.0" encoding="%(encoding)s"?>
|
"""<!DOCTYPE html><html lang="fr">
|
||||||
<!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">
|
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
<title>%(page_title)s</title>
|
<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="LANG" content="fr" />
|
||||||
<meta name="DESCRIPTION" content="ScoDoc" />
|
<meta name="DESCRIPTION" content="ScoDoc" />
|
||||||
|
|
||||||
|
@ -206,9 +198,7 @@ def sco_header(
|
||||||
)
|
)
|
||||||
if init_google_maps:
|
if init_google_maps:
|
||||||
# It may be necessary to add an API key:
|
# It may be necessary to add an API key:
|
||||||
H.append(
|
H.append('<script src="https://maps.google.com/maps/api/js"></script>')
|
||||||
'<script type="text/javascript" src="https://maps.google.com/maps/api/js"></script>'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Feuilles de style additionnelles:
|
# Feuilles de style additionnelles:
|
||||||
for cssstyle in cssstyles:
|
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/menu.css" rel="stylesheet" type="text/css" />
|
||||||
<link href="/ScoDoc/static/css/gt_table.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 src="/ScoDoc/static/libjs/menu.js"></script>
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
|
<script src="/ScoDoc/static/libjs/bubble.js"></script>
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
window.onload=function(){enableTooltips("gtrcontent")};
|
window.onload=function(){enableTooltips("gtrcontent")};
|
||||||
|
|
||||||
var SCO_URL="%(ScoURL)s";
|
var SCO_URL="%(ScoURL)s";
|
||||||
|
@ -236,16 +226,14 @@ def sco_header(
|
||||||
# jQuery
|
# jQuery
|
||||||
if init_jquery:
|
if init_jquery:
|
||||||
H.append(
|
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(
|
H.append('<script src="/ScoDoc/static/libjs/jquery.field.min.js"></script>')
|
||||||
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>'
|
|
||||||
)
|
|
||||||
# qTip
|
# qTip
|
||||||
if init_qtip:
|
if init_qtip:
|
||||||
H.append(
|
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(
|
H.append(
|
||||||
'<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />'
|
'<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:
|
if init_jquery_ui:
|
||||||
H.append(
|
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>'
|
'<script 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>'
|
|
||||||
)
|
)
|
||||||
|
# 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:
|
if init_google_maps:
|
||||||
H.append(
|
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:
|
if init_datatables:
|
||||||
H.append(
|
H.append(
|
||||||
'<link rel="stylesheet" type="text/css" href="/ScoDoc/static/DataTables/datatables.min.css"/>'
|
'<link rel="stylesheet" type="text/css" href="/ScoDoc/static/DataTables/datatables.min.css"/>'
|
||||||
)
|
)
|
||||||
H.append(
|
H.append('<script src="/ScoDoc/static/DataTables/datatables.min.js"></script>')
|
||||||
'<script type="text/javascript" src="/ScoDoc/static/DataTables/datatables.min.js"></script>'
|
|
||||||
)
|
|
||||||
# JS additionels
|
# JS additionels
|
||||||
for js in javascripts:
|
for js in javascripts:
|
||||||
H.append(
|
H.append("""<script src="/ScoDoc/static/%s"></script>\n""" % js)
|
||||||
"""<script language="javascript" type="text/javascript" src="/ScoDoc/static/%s"></script>\n"""
|
|
||||||
% js
|
|
||||||
)
|
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
"""<style type="text/css">
|
"""<style>
|
||||||
.gtrcontent {
|
.gtrcontent {
|
||||||
margin-left: %(margin_left)s;
|
margin-left: %(margin_left)s;
|
||||||
height: 100%%;
|
height: 100%%;
|
||||||
|
@ -290,7 +271,7 @@ def sco_header(
|
||||||
)
|
)
|
||||||
# Scripts de la page:
|
# Scripts de la page:
|
||||||
if scripts:
|
if scripts:
|
||||||
H.append("""<script language="javascript" type="text/javascript">""")
|
H.append("""<script>""")
|
||||||
for script in scripts:
|
for script in scripts:
|
||||||
H.append(script)
|
H.append(script)
|
||||||
H.append("""</script>""")
|
H.append("""</script>""")
|
||||||
|
@ -337,13 +318,7 @@ def sco_footer():
|
||||||
|
|
||||||
|
|
||||||
def html_sem_header(
|
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"
|
"Titre d'une page semestre avec lien vers tableau de bord"
|
||||||
# sem now unused and thus optional...
|
# sem now unused and thus optional...
|
||||||
|
|
|
@ -28,9 +28,8 @@
|
||||||
"""
|
"""
|
||||||
Génération de la "sidebar" (marge gauche des pages HTML)
|
Génération de la "sidebar" (marge gauche des pages HTML)
|
||||||
"""
|
"""
|
||||||
from flask import url_for
|
from flask import render_template, url_for
|
||||||
from flask import g
|
from flask import g, request
|
||||||
from flask import request
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -40,17 +39,11 @@ from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
def sidebar_common():
|
def sidebar_common():
|
||||||
"partie commune à toutes les sidebar"
|
"partie commune à toutes les sidebar"
|
||||||
params = {
|
|
||||||
"ScoURL": scu.ScoURL(),
|
|
||||||
"UsersURL": scu.UsersURL(),
|
|
||||||
"NotesURL": scu.NotesURL(),
|
|
||||||
"AbsencesURL": scu.AbsencesURL(),
|
|
||||||
"authuser": current_user.user_name,
|
|
||||||
}
|
|
||||||
H = [
|
H = [
|
||||||
f"""<a class="scodoc_title" href="about">ScoDoc 9</a>
|
f"""<a class="scodoc_title" href="{url_for("scodoc.index", scodoc_dept=g.scodoc_dept)}">ScoDoc 9</a>
|
||||||
<div id="authuser"><a id="authuserlink" href="{
|
<div id="authuser"><a id="authuserlink" href="{
|
||||||
url_for("users.user_info_page", scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
url_for("users.user_info_page",
|
||||||
|
scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
||||||
}">{current_user.user_name}</a>
|
}">{current_user.user_name}</a>
|
||||||
<br/><a id="deconnectlink" href="{url_for("auth.logout")}">déconnexion</a>
|
<br/><a id="deconnectlink" href="{url_for("auth.logout")}">déconnexion</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,7 +64,8 @@ def sidebar_common():
|
||||||
|
|
||||||
if current_user.has_permission(Permission.ScoChangePreferences):
|
if current_user.has_permission(Permission.ScoChangePreferences):
|
||||||
H.append(
|
H.append(
|
||||||
f"""<a href="{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}" class="sidebar">Paramétrage</a> <br/>"""
|
f"""<a href="{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}"
|
||||||
|
class="sidebar">Paramétrage</a> <br/>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
return "".join(H)
|
return "".join(H)
|
||||||
|
@ -97,11 +91,12 @@ def sidebar():
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
# ---- Il y-a-t-il un etudiant selectionné ?
|
# ---- Il y-a-t-il un etudiant selectionné ?
|
||||||
etudid = None
|
etudid = g.get("etudid", None)
|
||||||
if request.method == "GET":
|
if not etudid:
|
||||||
etudid = request.args.get("etudid", None)
|
if request.method == "GET":
|
||||||
elif request.method == "POST":
|
etudid = request.args.get("etudid", None)
|
||||||
etudid = request.form.get("etudid", None)
|
elif request.method == "POST":
|
||||||
|
etudid = request.form.get("etudid", None)
|
||||||
|
|
||||||
if etudid:
|
if etudid:
|
||||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||||
|
@ -155,8 +150,9 @@ def sidebar():
|
||||||
<div class="sidebar-bottom"><a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }" class="sidebar">À propos</a><br/>
|
<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>
|
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div class="logo-logo"><a href= { url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }
|
<div class="logo-logo">
|
||||||
">{ scu.icontag("scologo_img", no_size=True) }</a>
|
<a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }">
|
||||||
|
{ scu.icontag("scologo_img", no_size=True) }</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- end of sidebar -->
|
<!-- end of sidebar -->
|
||||||
|
@ -167,19 +163,7 @@ def sidebar():
|
||||||
|
|
||||||
def sidebar_dept():
|
def sidebar_dept():
|
||||||
"""Partie supérieure de la marge de gauche"""
|
"""Partie supérieure de la marge de gauche"""
|
||||||
H = [
|
return render_template(
|
||||||
f"""<h2 class="insidebar">Dépt. {sco_preferences.get_preference("DeptName")}</h2>
|
"sidebar_dept.html",
|
||||||
<a href="{url_for("scodoc.index")}" class="sidebar">Accueil</a> <br/> """
|
prefs=sco_preferences.SemPreferences(),
|
||||||
]
|
)
|
||||||
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)
|
|
||||||
|
|
|
@ -27,9 +27,12 @@
|
||||||
|
|
||||||
"""Various HTML generation functions
|
"""Various HTML generation functions
|
||||||
"""
|
"""
|
||||||
|
from html.parser import HTMLParser
|
||||||
|
from html.entities import name2codepoint
|
||||||
|
import re
|
||||||
|
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
|
||||||
from . import listhistogram
|
from . import listhistogram
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,6 +107,8 @@ def make_menu(title, items, css_class="", alone=False):
|
||||||
item["urlq"] = url_for(
|
item["urlq"] = url_for(
|
||||||
item["endpoint"], scodoc_dept=g.scodoc_dept, **args
|
item["endpoint"], scodoc_dept=g.scodoc_dept, **args
|
||||||
)
|
)
|
||||||
|
elif "url" in item:
|
||||||
|
item["urlq"] = item["url"]
|
||||||
else:
|
else:
|
||||||
item["urlq"] = "#"
|
item["urlq"] = "#"
|
||||||
item["attr"] = item.get("attr", "")
|
item["attr"] = item.get("attr", "")
|
||||||
|
@ -128,3 +133,63 @@ def make_menu(title, items, css_class="", alone=False):
|
||||||
if alone:
|
if alone:
|
||||||
H.append("</ul>")
|
H.append("</ul>")
|
||||||
return "".join(H)
|
return "".join(H)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
HTML <-> text conversions.
|
||||||
|
http://stackoverflow.com/questions/328356/extracting-text-from-html-file-using-python
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class _HTMLToText(HTMLParser):
|
||||||
|
def __init__(self):
|
||||||
|
HTMLParser.__init__(self)
|
||||||
|
self._buf = []
|
||||||
|
self.hide_output = False
|
||||||
|
|
||||||
|
def handle_starttag(self, tag, attrs):
|
||||||
|
if tag in ("p", "br") and not self.hide_output:
|
||||||
|
self._buf.append("\n")
|
||||||
|
elif tag in ("script", "style"):
|
||||||
|
self.hide_output = True
|
||||||
|
|
||||||
|
def handle_startendtag(self, tag, attrs):
|
||||||
|
if tag == "br":
|
||||||
|
self._buf.append("\n")
|
||||||
|
|
||||||
|
def handle_endtag(self, tag):
|
||||||
|
if tag == "p":
|
||||||
|
self._buf.append("\n")
|
||||||
|
elif tag in ("script", "style"):
|
||||||
|
self.hide_output = False
|
||||||
|
|
||||||
|
def handle_data(self, text):
|
||||||
|
if text and not self.hide_output:
|
||||||
|
self._buf.append(re.sub(r"\s+", " ", text))
|
||||||
|
|
||||||
|
def handle_entityref(self, name):
|
||||||
|
if name in name2codepoint and not self.hide_output:
|
||||||
|
c = chr(name2codepoint[name])
|
||||||
|
self._buf.append(c)
|
||||||
|
|
||||||
|
def handle_charref(self, name):
|
||||||
|
if not self.hide_output:
|
||||||
|
n = int(name[1:], 16) if name.startswith("x") else int(name)
|
||||||
|
self._buf.append(chr(n))
|
||||||
|
|
||||||
|
def get_text(self):
|
||||||
|
return re.sub(r" +", " ", "".join(self._buf))
|
||||||
|
|
||||||
|
|
||||||
|
def html_to_text(html):
|
||||||
|
"""
|
||||||
|
Given a piece of HTML, return the plain text it contains.
|
||||||
|
This handles entities and char refs, but not javascript and stylesheets.
|
||||||
|
"""
|
||||||
|
parser = _HTMLToText()
|
||||||
|
try:
|
||||||
|
parser.feed(html)
|
||||||
|
parser.close()
|
||||||
|
except: # HTMLParseError: No good replacement?
|
||||||
|
pass
|
||||||
|
return parser.get_text()
|
||||||
|
|
|
@ -4,11 +4,8 @@
|
||||||
|
|
||||||
# Code from http://code.activestate.com/recipes/457411/
|
# Code from http://code.activestate.com/recipes/457411/
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
from bisect import bisect_left, bisect_right
|
from bisect import bisect_left, bisect_right
|
||||||
|
|
||||||
from six.moves import zip
|
|
||||||
|
|
||||||
|
|
||||||
class intervalmap(object):
|
class intervalmap(object):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -27,10 +27,7 @@
|
||||||
|
|
||||||
"""Calculs sur les notes et cache des resultats
|
"""Calculs sur les notes et cache des resultats
|
||||||
"""
|
"""
|
||||||
import inspect
|
|
||||||
import os
|
|
||||||
import pdb
|
|
||||||
import time
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
|
@ -40,12 +37,8 @@ import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_formulas import NoteVector
|
from app.scodoc.sco_formulas import NoteVector
|
||||||
from app.scodoc.sco_exceptions import (
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
AccessDenied,
|
|
||||||
NoteProcessError,
|
|
||||||
ScoException,
|
|
||||||
ScoValueError,
|
|
||||||
)
|
|
||||||
from app.scodoc.sco_formsemestre import (
|
from app.scodoc.sco_formsemestre import (
|
||||||
formsemestre_uecoef_list,
|
formsemestre_uecoef_list,
|
||||||
formsemestre_uecoef_create,
|
formsemestre_uecoef_create,
|
||||||
|
@ -109,15 +102,13 @@ def get_sem_ues_modimpls(formsemestre_id, modimpls=None):
|
||||||
(utilisé quand on ne peut pas construire nt et faire nt.get_ues())
|
(utilisé quand on ne peut pas construire nt et faire nt.get_ues())
|
||||||
"""
|
"""
|
||||||
if modimpls is None:
|
if modimpls is None:
|
||||||
modimpls = sco_moduleimpl.do_moduleimpl_list(formsemestre_id=formsemestre_id)
|
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
uedict = {}
|
uedict = {}
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
mod = sco_edit_module.do_module_list(args={"module_id": modimpl["module_id"]})[
|
mod = sco_edit_module.module_list(args={"module_id": modimpl["module_id"]})[0]
|
||||||
0
|
|
||||||
]
|
|
||||||
modimpl["module"] = mod
|
modimpl["module"] = mod
|
||||||
if not mod["ue_id"] in uedict:
|
if not mod["ue_id"] in uedict:
|
||||||
ue = sco_edit_ue.do_ue_list(args={"ue_id": mod["ue_id"]})[0]
|
ue = sco_edit_ue.ue_list(args={"ue_id": mod["ue_id"]})[0]
|
||||||
uedict[ue["ue_id"]] = ue
|
uedict[ue["ue_id"]] = ue
|
||||||
ues = list(uedict.values())
|
ues = list(uedict.values())
|
||||||
ues.sort(key=lambda u: u["numero"])
|
ues.sort(key=lambda u: u["numero"])
|
||||||
|
@ -186,6 +177,8 @@ class NotesTable(object):
|
||||||
self.use_ue_coefs = sco_preferences.get_preference(
|
self.use_ue_coefs = sco_preferences.get_preference(
|
||||||
"use_ue_coefs", formsemestre_id
|
"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
|
# Infos sur les etudiants
|
||||||
self.inscrlist = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
self.inscrlist = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
args={"formsemestre_id": formsemestre_id}
|
args={"formsemestre_id": formsemestre_id}
|
||||||
|
@ -217,26 +210,29 @@ class NotesTable(object):
|
||||||
valid_evals,
|
valid_evals,
|
||||||
mods_att,
|
mods_att,
|
||||||
self.expr_diagnostics,
|
self.expr_diagnostics,
|
||||||
) = sco_compute_moy.do_formsemestre_moyennes(self, formsemestre_id)
|
) = sco_compute_moy.formsemestre_compute_modimpls_moyennes(
|
||||||
|
self, formsemestre_id
|
||||||
|
)
|
||||||
self._mods_att = mods_att # liste des modules avec des notes en attente
|
self._mods_att = mods_att # liste des modules avec des notes en attente
|
||||||
self._matmoys = {} # moyennes par matieres
|
self._matmoys = {} # moyennes par matieres
|
||||||
self._valid_evals = {} # { evaluation_id : eval }
|
self._valid_evals = {} # { evaluation_id : eval }
|
||||||
for e in valid_evals:
|
for e in valid_evals:
|
||||||
self._valid_evals[e["evaluation_id"]] = e # Liste des modules et UE
|
self._valid_evals[e["evaluation_id"]] = e # Liste des modules et UE
|
||||||
uedict = {} # public member: { ue_id : ue }
|
uedict = {} # public member: { ue_id : ue }
|
||||||
self.uedict = uedict
|
self.uedict = uedict # les ues qui ont un modimpl dans ce semestre
|
||||||
for modimpl in self._modimpls:
|
for modimpl in self._modimpls:
|
||||||
mod = modimpl["module"] # has been added here by do_formsemestre_moyennes
|
# module has been added by formsemestre_compute_modimpls_moyennes
|
||||||
|
mod = modimpl["module"]
|
||||||
if not mod["ue_id"] in uedict:
|
if not mod["ue_id"] in uedict:
|
||||||
ue = sco_edit_ue.do_ue_list(args={"ue_id": mod["ue_id"]})[0]
|
ue = sco_edit_ue.ue_list(args={"ue_id": mod["ue_id"]})[0]
|
||||||
uedict[ue["ue_id"]] = ue
|
uedict[ue["ue_id"]] = ue
|
||||||
else:
|
else:
|
||||||
ue = uedict[mod["ue_id"]]
|
ue = uedict[mod["ue_id"]]
|
||||||
modimpl["ue"] = ue # add ue dict to moduleimpl
|
modimpl["ue"] = ue # add ue dict to moduleimpl
|
||||||
self._matmoys[mod["matiere_id"]] = {}
|
self._matmoys[mod["matiere_id"]] = {}
|
||||||
mat = sco_edit_matiere.do_matiere_list(
|
mat = sco_edit_matiere.matiere_list(args={"matiere_id": mod["matiere_id"]})[
|
||||||
args={"matiere_id": mod["matiere_id"]}
|
0
|
||||||
)[0]
|
]
|
||||||
modimpl["mat"] = mat # add matiere dict to moduleimpl
|
modimpl["mat"] = mat # add matiere dict to moduleimpl
|
||||||
# calcul moyennes du module et stocke dans le module
|
# calcul moyennes du module et stocke dans le module
|
||||||
# nb_inscrits, nb_notes, nb_abs, nb_neutre, moy, median, last_modif=
|
# nb_inscrits, nb_notes, nb_abs, nb_neutre, moy, median, last_modif=
|
||||||
|
@ -634,7 +630,8 @@ class NotesTable(object):
|
||||||
matiere_sum_notes += val * coef
|
matiere_sum_notes += val * coef
|
||||||
matiere_sum_coefs += coef
|
matiere_sum_coefs += coef
|
||||||
matiere_id_last = matiere_id
|
matiere_id_last = matiere_id
|
||||||
except:
|
except TypeError: # val == "NI" "NA"
|
||||||
|
assert val == "NI" or val == "NA" or val == "ERR"
|
||||||
nb_missing = nb_missing + 1
|
nb_missing = nb_missing + 1
|
||||||
coefs.append(0)
|
coefs.append(0)
|
||||||
coefs_mask.append(0)
|
coefs_mask.append(0)
|
||||||
|
@ -732,12 +729,11 @@ class NotesTable(object):
|
||||||
|
|
||||||
Prend toujours en compte les UE capitalisées.
|
Prend toujours en compte les UE capitalisées.
|
||||||
"""
|
"""
|
||||||
# log('comp_etud_moy_gen(etudid=%s)' % etudid)
|
# Si l'étudiant a Démissionné ou est DEFaillant, on n'enregistre pas ses moyennes
|
||||||
|
|
||||||
# Si l'étudiant a Demissionné ou est DEFaillant, on n'enregistre pas ses moyennes
|
|
||||||
block_computation = (
|
block_computation = (
|
||||||
self.inscrdict[etudid]["etat"] == "D"
|
self.inscrdict[etudid]["etat"] == "D"
|
||||||
or self.inscrdict[etudid]["etat"] == DEF
|
or self.inscrdict[etudid]["etat"] == DEF
|
||||||
|
or self.block_moyennes
|
||||||
)
|
)
|
||||||
|
|
||||||
moy_ues = {}
|
moy_ues = {}
|
||||||
|
@ -1056,7 +1052,7 @@ class NotesTable(object):
|
||||||
"Warning: %s capitalized an UE %s which is not part of current sem %s"
|
"Warning: %s capitalized an UE %s which is not part of current sem %s"
|
||||||
% (etudid, ue_id, self.formsemestre_id)
|
% (etudid, ue_id, self.formsemestre_id)
|
||||||
)
|
)
|
||||||
ue = sco_edit_ue.do_ue_list(args={"ue_id": ue_id})[0]
|
ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
|
||||||
self.uedict[ue_id] = ue # record this UE
|
self.uedict[ue_id] = ue # record this UE
|
||||||
if ue_id not in self._uecoef:
|
if ue_id not in self._uecoef:
|
||||||
cl = formsemestre_uecoef_list(
|
cl = formsemestre_uecoef_list(
|
||||||
|
@ -1262,7 +1258,7 @@ class NotesTable(object):
|
||||||
),
|
),
|
||||||
self.get_nom_long(etudid),
|
self.get_nom_long(etudid),
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.formsemestre_edit_uecoefs",
|
"notes.formsemestre_edit_uecoefs",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=self.formsemestre_id,
|
formsemestre_id=self.formsemestre_id,
|
||||||
err_ue_id=ue["ue_id"],
|
err_ue_id=ue["ue_id"],
|
||||||
|
|
|
@ -88,7 +88,15 @@ def SimpleDictFetch(query, args, cursor=None):
|
||||||
return cursor.dictfetchall()
|
return cursor.dictfetchall()
|
||||||
|
|
||||||
|
|
||||||
def DBInsertDict(cnx, table, vals, commit=0, convert_empty_to_nulls=1, return_id=True):
|
def DBInsertDict(
|
||||||
|
cnx,
|
||||||
|
table,
|
||||||
|
vals,
|
||||||
|
commit=0,
|
||||||
|
convert_empty_to_nulls=1,
|
||||||
|
return_id=True,
|
||||||
|
ignore_conflicts=False,
|
||||||
|
) -> int:
|
||||||
"""insert into table values in dict 'vals'
|
"""insert into table values in dict 'vals'
|
||||||
Return: id de l'object créé
|
Return: id de l'object créé
|
||||||
"""
|
"""
|
||||||
|
@ -103,13 +111,18 @@ def DBInsertDict(cnx, table, vals, commit=0, convert_empty_to_nulls=1, return_id
|
||||||
fmt = ",".join(["%%(%s)s" % col for col in cols])
|
fmt = ",".join(["%%(%s)s" % col for col in cols])
|
||||||
# print 'insert into %s (%s) values (%s)' % (table,colnames,fmt)
|
# print 'insert into %s (%s) values (%s)' % (table,colnames,fmt)
|
||||||
oid = None
|
oid = None
|
||||||
|
if ignore_conflicts:
|
||||||
|
ignore = " ON CONFLICT DO NOTHING"
|
||||||
|
else:
|
||||||
|
ignore = ""
|
||||||
try:
|
try:
|
||||||
if vals:
|
if vals:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"insert into %s (%s) values (%s)" % (table, colnames, fmt), vals
|
"insert into %s (%s) values (%s)%s" % (table, colnames, fmt, ignore),
|
||||||
|
vals,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
cursor.execute("insert into %s default values" % table)
|
cursor.execute("insert into %s default values%s" % (table, ignore))
|
||||||
if return_id:
|
if return_id:
|
||||||
cursor.execute(f"SELECT CURRVAL('{table}_id_seq')") # id créé
|
cursor.execute(f"SELECT CURRVAL('{table}_id_seq')") # id créé
|
||||||
oid = cursor.fetchone()[0]
|
oid = cursor.fetchone()[0]
|
||||||
|
@ -291,6 +304,7 @@ class EditableTable(object):
|
||||||
fields_creators={}, # { field : [ sql_command_to_create_it ] }
|
fields_creators={}, # { field : [ sql_command_to_create_it ] }
|
||||||
filter_nulls=True, # dont allow to set fields to null
|
filter_nulls=True, # dont allow to set fields to null
|
||||||
filter_dept=False, # ajoute selection sur g.scodoc_dept_id
|
filter_dept=False, # ajoute selection sur g.scodoc_dept_id
|
||||||
|
insert_ignore_conflicts=False,
|
||||||
):
|
):
|
||||||
self.table_name = table_name
|
self.table_name = table_name
|
||||||
self.id_name = id_name
|
self.id_name = id_name
|
||||||
|
@ -311,8 +325,9 @@ class EditableTable(object):
|
||||||
self.filter_nulls = filter_nulls
|
self.filter_nulls = filter_nulls
|
||||||
self.filter_dept = filter_dept
|
self.filter_dept = filter_dept
|
||||||
self.sql_default_values = None
|
self.sql_default_values = None
|
||||||
|
self.insert_ignore_conflicts = insert_ignore_conflicts
|
||||||
|
|
||||||
def create(self, cnx, args):
|
def create(self, cnx, args) -> int:
|
||||||
"create object in table"
|
"create object in table"
|
||||||
vals = dictfilter(args, self.dbfields, self.filter_nulls)
|
vals = dictfilter(args, self.dbfields, self.filter_nulls)
|
||||||
if self.id_name in vals:
|
if self.id_name in vals:
|
||||||
|
@ -336,6 +351,7 @@ class EditableTable(object):
|
||||||
vals,
|
vals,
|
||||||
commit=True,
|
commit=True,
|
||||||
return_id=(self.id_name is not None),
|
return_id=(self.id_name is not None),
|
||||||
|
ignore_conflicts=self.insert_ignore_conflicts,
|
||||||
)
|
)
|
||||||
return new_id
|
return new_id
|
||||||
|
|
||||||
|
@ -581,6 +597,22 @@ def float_null_is_null(x):
|
||||||
return float(x)
|
return float(x)
|
||||||
|
|
||||||
|
|
||||||
|
BOOL_STR = {
|
||||||
|
"": False,
|
||||||
|
"false": False,
|
||||||
|
"0": False,
|
||||||
|
"1": True,
|
||||||
|
"true": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def bool_or_str(x) -> bool:
|
||||||
|
"""a boolean, may also be encoded as a string "0", "False", "1", "True" """
|
||||||
|
if isinstance(x, str):
|
||||||
|
return BOOL_STR[x.lower()]
|
||||||
|
return bool(x)
|
||||||
|
|
||||||
|
|
||||||
# post filtering
|
# post filtering
|
||||||
#
|
#
|
||||||
def UniqListofDicts(L, key):
|
def UniqListofDicts(L, key):
|
||||||
|
|
|
@ -474,7 +474,7 @@ def _get_abs_description(a, cursor=None):
|
||||||
desc = a["description"]
|
desc = a["description"]
|
||||||
if a["moduleimpl_id"] and a["moduleimpl_id"] != "NULL":
|
if a["moduleimpl_id"] and a["moduleimpl_id"] != "NULL":
|
||||||
# Trouver le nom du module
|
# Trouver le nom du module
|
||||||
Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||||
moduleimpl_id=a["moduleimpl_id"]
|
moduleimpl_id=a["moduleimpl_id"]
|
||||||
)
|
)
|
||||||
if Mlist:
|
if Mlist:
|
||||||
|
@ -626,7 +626,6 @@ def add_absence(
|
||||||
jour,
|
jour,
|
||||||
matin,
|
matin,
|
||||||
estjust,
|
estjust,
|
||||||
REQUEST,
|
|
||||||
description=None,
|
description=None,
|
||||||
moduleimpl_id=None,
|
moduleimpl_id=None,
|
||||||
):
|
):
|
||||||
|
@ -656,7 +655,7 @@ def add_absence(
|
||||||
sco_abs_notification.abs_notify(etudid, jour)
|
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"
|
"Ajoute un justificatif dans la base"
|
||||||
# unpublished
|
# unpublished
|
||||||
if _isFarFutur(jour):
|
if _isFarFutur(jour):
|
||||||
|
@ -665,7 +664,9 @@ def add_justif(etudid, jour, matin, REQUEST, description=None):
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
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(),
|
vars(),
|
||||||
)
|
)
|
||||||
logdb(
|
logdb(
|
||||||
|
@ -678,7 +679,7 @@ def add_justif(etudid, jour, matin, REQUEST, description=None):
|
||||||
invalidate_abs_etud_date(etudid, jour)
|
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:
|
for a in abslist:
|
||||||
etudid, jour, ampm = a.split(":")
|
etudid, jour, ampm = a.split(":")
|
||||||
if ampm == "am":
|
if ampm == "am":
|
||||||
|
@ -689,7 +690,7 @@ def _add_abslist(abslist, REQUEST, moduleimpl_id=None):
|
||||||
raise ValueError("invalid ampm !")
|
raise ValueError("invalid ampm !")
|
||||||
# ajoute abs si pas deja absent
|
# ajoute abs si pas deja absent
|
||||||
if count_abs(etudid, jour, jour, matin, moduleimpl_id) == 0:
|
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):
|
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)
|
invalidate_abs_etud_date(etudid, jour)
|
||||||
|
|
||||||
|
|
||||||
def annule_justif(etudid, jour, matin, REQUEST=None):
|
def annule_justif(etudid, jour, matin):
|
||||||
"Annule un justificatif"
|
"Annule un justificatif"
|
||||||
# unpublished
|
# unpublished
|
||||||
matin = _toboolean(matin)
|
matin = _toboolean(matin)
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from flask import url_for, g
|
from flask import url_for, g, request
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
|
@ -58,7 +58,6 @@ def doSignaleAbsence(
|
||||||
estjust=False,
|
estjust=False,
|
||||||
description=None,
|
description=None,
|
||||||
etudid=False,
|
etudid=False,
|
||||||
REQUEST=None,
|
|
||||||
): # etudid implied
|
): # etudid implied
|
||||||
"""Signalement d'une absence.
|
"""Signalement d'une absence.
|
||||||
|
|
||||||
|
@ -69,7 +68,8 @@ def doSignaleAbsence(
|
||||||
demijournee: 2 si journée complète, 1 matin, 0 après-midi
|
demijournee: 2 si journée complète, 1 matin, 0 après-midi
|
||||||
estjust: absence justifiée
|
estjust: absence justifiée
|
||||||
description: str
|
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]
|
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||||
etudid = etud["etudid"]
|
etudid = etud["etudid"]
|
||||||
|
@ -86,7 +86,6 @@ def doSignaleAbsence(
|
||||||
jour,
|
jour,
|
||||||
False,
|
False,
|
||||||
estjust,
|
estjust,
|
||||||
REQUEST,
|
|
||||||
description_abs,
|
description_abs,
|
||||||
moduleimpl_id,
|
moduleimpl_id,
|
||||||
)
|
)
|
||||||
|
@ -95,7 +94,6 @@ def doSignaleAbsence(
|
||||||
jour,
|
jour,
|
||||||
True,
|
True,
|
||||||
estjust,
|
estjust,
|
||||||
REQUEST,
|
|
||||||
description_abs,
|
description_abs,
|
||||||
moduleimpl_id,
|
moduleimpl_id,
|
||||||
)
|
)
|
||||||
|
@ -106,7 +104,6 @@ def doSignaleAbsence(
|
||||||
jour,
|
jour,
|
||||||
demijournee,
|
demijournee,
|
||||||
estjust,
|
estjust,
|
||||||
REQUEST,
|
|
||||||
description_abs,
|
description_abs,
|
||||||
moduleimpl_id,
|
moduleimpl_id,
|
||||||
)
|
)
|
||||||
|
@ -118,7 +115,7 @@ def doSignaleAbsence(
|
||||||
J = "NON "
|
J = "NON "
|
||||||
M = ""
|
M = ""
|
||||||
if moduleimpl_id and moduleimpl_id != "NULL":
|
if moduleimpl_id and moduleimpl_id != "NULL":
|
||||||
mod = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
formsemestre_id = mod["formsemestre_id"]
|
formsemestre_id = mod["formsemestre_id"]
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
ues = nt.get_ues(etudid=etudid)
|
ues = nt.get_ues(etudid=etudid)
|
||||||
|
@ -156,7 +153,7 @@ def doSignaleAbsence(
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def SignaleAbsenceEtud(REQUEST=None): # etudid implied
|
def SignaleAbsenceEtud(): # etudid implied
|
||||||
"""Formulaire individuel simple de signalement d'une absence"""
|
"""Formulaire individuel simple de signalement d'une absence"""
|
||||||
# brute-force portage from very old dtml code ...
|
# brute-force portage from very old dtml code ...
|
||||||
etud = sco_etud.get_etud_info(filled=True)[0]
|
etud = sco_etud.get_etud_info(filled=True)[0]
|
||||||
|
@ -228,7 +225,6 @@ def SignaleAbsenceEtud(REQUEST=None): # etudid implied
|
||||||
sco_photos.etud_photo_html(
|
sco_photos.etud_photo_html(
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
title="fiche de " + etud["nomprenom"],
|
title="fiche de " + etud["nomprenom"],
|
||||||
REQUEST=REQUEST,
|
|
||||||
),
|
),
|
||||||
"""</a></td></tr></table>""",
|
"""</a></td></tr></table>""",
|
||||||
"""
|
"""
|
||||||
|
@ -281,7 +277,6 @@ def doJustifAbsence(
|
||||||
demijournee,
|
demijournee,
|
||||||
description=None,
|
description=None,
|
||||||
etudid=False,
|
etudid=False,
|
||||||
REQUEST=None,
|
|
||||||
): # etudid implied
|
): # etudid implied
|
||||||
"""Justification d'une absence
|
"""Justification d'une absence
|
||||||
|
|
||||||
|
@ -291,7 +286,8 @@ def doJustifAbsence(
|
||||||
demijournee: 2 si journée complète, 1 matin, 0 après-midi
|
demijournee: 2 si journée complète, 1 matin, 0 après-midi
|
||||||
estjust: absence justifiée
|
estjust: absence justifiée
|
||||||
description: str
|
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]
|
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||||
etudid = etud["etudid"]
|
etudid = etud["etudid"]
|
||||||
|
@ -305,14 +301,12 @@ def doJustifAbsence(
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
jour=jour,
|
jour=jour,
|
||||||
matin=False,
|
matin=False,
|
||||||
REQUEST=REQUEST,
|
|
||||||
description=description_abs,
|
description=description_abs,
|
||||||
)
|
)
|
||||||
sco_abs.add_justif(
|
sco_abs.add_justif(
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
jour=jour,
|
jour=jour,
|
||||||
matin=True,
|
matin=True,
|
||||||
REQUEST=REQUEST,
|
|
||||||
description=description_abs,
|
description=description_abs,
|
||||||
)
|
)
|
||||||
nbadded += 2
|
nbadded += 2
|
||||||
|
@ -321,7 +315,6 @@ def doJustifAbsence(
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
jour=jour,
|
jour=jour,
|
||||||
matin=demijournee,
|
matin=demijournee,
|
||||||
REQUEST=REQUEST,
|
|
||||||
description=description_abs,
|
description=description_abs,
|
||||||
)
|
)
|
||||||
nbadded += 1
|
nbadded += 1
|
||||||
|
@ -357,7 +350,7 @@ def doJustifAbsence(
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def JustifAbsenceEtud(REQUEST=None): # etudid implied
|
def JustifAbsenceEtud(): # etudid implied
|
||||||
"""Formulaire individuel simple de justification d'une absence"""
|
"""Formulaire individuel simple de justification d'une absence"""
|
||||||
# brute-force portage from very old dtml code ...
|
# brute-force portage from very old dtml code ...
|
||||||
etud = sco_etud.get_etud_info(filled=True)[0]
|
etud = sco_etud.get_etud_info(filled=True)[0]
|
||||||
|
@ -376,7 +369,6 @@ def JustifAbsenceEtud(REQUEST=None): # etudid implied
|
||||||
sco_photos.etud_photo_html(
|
sco_photos.etud_photo_html(
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
title="fiche de " + etud["nomprenom"],
|
title="fiche de " + etud["nomprenom"],
|
||||||
REQUEST=REQUEST,
|
|
||||||
),
|
),
|
||||||
"""</a></td></tr></table>""",
|
"""</a></td></tr></table>""",
|
||||||
"""
|
"""
|
||||||
|
@ -412,9 +404,7 @@ Raison: <input type="text" name="description" size="42"/> (optionnel)
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def doAnnuleAbsence(
|
def doAnnuleAbsence(datedebut, datefin, demijournee, etudid=False): # etudid implied
|
||||||
datedebut, datefin, demijournee, etudid=False, REQUEST=None
|
|
||||||
): # etudid implied
|
|
||||||
"""Annulation des absences pour une demi journée"""
|
"""Annulation des absences pour une demi journée"""
|
||||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||||
etudid = etud["etudid"]
|
etudid = etud["etudid"]
|
||||||
|
@ -462,7 +452,7 @@ autre absence pour <b>%(nomprenom)s</b></a></li>
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def AnnuleAbsenceEtud(REQUEST=None): # etudid implied
|
def AnnuleAbsenceEtud(): # etudid implied
|
||||||
"""Formulaire individuel simple d'annulation d'une absence"""
|
"""Formulaire individuel simple d'annulation d'une absence"""
|
||||||
# brute-force portage from very old dtml code ...
|
# brute-force portage from very old dtml code ...
|
||||||
etud = sco_etud.get_etud_info(filled=True)[0]
|
etud = sco_etud.get_etud_info(filled=True)[0]
|
||||||
|
@ -482,7 +472,6 @@ def AnnuleAbsenceEtud(REQUEST=None): # etudid implied
|
||||||
sco_photos.etud_photo_html(
|
sco_photos.etud_photo_html(
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
title="fiche de " + etud["nomprenom"],
|
title="fiche de " + etud["nomprenom"],
|
||||||
REQUEST=REQUEST,
|
|
||||||
),
|
),
|
||||||
"""</a></td></tr></table>""",
|
"""</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>
|
"""<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)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def doAnnuleJustif(datedebut0, datefin0, demijournee, REQUEST=None): # etudid implied
|
def doAnnuleJustif(datedebut0, datefin0, demijournee): # etudid implied
|
||||||
"""Annulation d'une justification"""
|
"""Annulation d'une justification"""
|
||||||
etud = sco_etud.get_etud_info(filled=True)[0]
|
etud = sco_etud.get_etud_info(filled=True)[0]
|
||||||
etudid = etud["etudid"]
|
etudid = etud["etudid"]
|
||||||
|
@ -558,11 +547,11 @@ def doAnnuleJustif(datedebut0, datefin0, demijournee, REQUEST=None): # etudid i
|
||||||
for jour in dates:
|
for jour in dates:
|
||||||
# Attention: supprime matin et après-midi
|
# Attention: supprime matin et après-midi
|
||||||
if demijournee == 2:
|
if demijournee == 2:
|
||||||
sco_abs.annule_justif(etudid, jour, False, REQUEST=REQUEST)
|
sco_abs.annule_justif(etudid, jour, False)
|
||||||
sco_abs.annule_justif(etudid, jour, True, REQUEST=REQUEST)
|
sco_abs.annule_justif(etudid, jour, True)
|
||||||
nbadded += 2
|
nbadded += 2
|
||||||
else:
|
else:
|
||||||
sco_abs.annule_justif(etudid, jour, demijournee, REQUEST=REQUEST)
|
sco_abs.annule_justif(etudid, jour, demijournee)
|
||||||
nbadded += 1
|
nbadded += 1
|
||||||
#
|
#
|
||||||
H = [
|
H = [
|
||||||
|
@ -716,7 +705,6 @@ def formChoixSemestreGroupe(all=False):
|
||||||
def CalAbs(etudid, sco_year=None):
|
def CalAbs(etudid, sco_year=None):
|
||||||
"""Calendrier des absences d'un etudiant"""
|
"""Calendrier des absences d'un etudiant"""
|
||||||
# crude portage from 1999 DTML
|
# crude portage from 1999 DTML
|
||||||
REQUEST = None # XXX
|
|
||||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||||
etudid = etud["etudid"]
|
etudid = etud["etudid"]
|
||||||
anneescolaire = int(scu.AnneeScolaire(sco_year))
|
anneescolaire = int(scu.AnneeScolaire(sco_year))
|
||||||
|
@ -766,7 +754,6 @@ def CalAbs(etudid, sco_year=None):
|
||||||
sco_photos.etud_photo_html(
|
sco_photos.etud_photo_html(
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
title="fiche de " + etud["nomprenom"],
|
title="fiche de " + etud["nomprenom"],
|
||||||
REQUEST=REQUEST,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
CalHTML,
|
CalHTML,
|
||||||
|
@ -791,7 +778,6 @@ def ListeAbsEtud(
|
||||||
format="html",
|
format="html",
|
||||||
absjust_only=0,
|
absjust_only=0,
|
||||||
sco_year=None,
|
sco_year=None,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Liste des absences d'un étudiant sur l'année en cours
|
"""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).
|
En format 'html': page avec deux tableaux (non justifiées et justifiées).
|
||||||
|
@ -804,18 +790,19 @@ def ListeAbsEtud(
|
||||||
absjust_only: si vrai, renvoie table absences justifiées
|
absjust_only: si vrai, renvoie table absences justifiées
|
||||||
sco_year: année scolaire à utiliser. Si non spécifier, utilie l'année en cours. e.g. "2005"
|
sco_year: année scolaire à utiliser. Si non spécifier, utilie l'année en cours. e.g. "2005"
|
||||||
"""
|
"""
|
||||||
absjust_only = int(absjust_only) # si vrai, table absjust seule (export xls ou pdf)
|
# si absjust_only, table absjust seule (export xls ou pdf)
|
||||||
|
absjust_only = ndb.bool_or_str(absjust_only)
|
||||||
datedebut = "%s-08-01" % scu.AnneeScolaire(sco_year=sco_year)
|
datedebut = "%s-08-01" % scu.AnneeScolaire(sco_year=sco_year)
|
||||||
|
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
|
|
||||||
# Liste des absences et titres colonnes tables:
|
# 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
|
etudid, datedebut, with_evals=with_evals, format=format
|
||||||
)
|
)
|
||||||
if REQUEST:
|
if request.base_url:
|
||||||
base_url_nj = "%s?etudid=%s&absjust_only=0" % (REQUEST.URL0, etudid)
|
base_url_nj = "%s?etudid=%s&absjust_only=0" % (request.base_url, etudid)
|
||||||
base_url_j = "%s?etudid=%s&absjust_only=1" % (REQUEST.URL0, etudid)
|
base_url_j = "%s?etudid=%s&absjust_only=1" % (request.base_url, etudid)
|
||||||
else:
|
else:
|
||||||
base_url_nj = base_url_j = ""
|
base_url_nj = base_url_j = ""
|
||||||
tab_absnonjust = GenTable(
|
tab_absnonjust = GenTable(
|
||||||
|
@ -844,9 +831,9 @@ def ListeAbsEtud(
|
||||||
# Formats non HTML et demande d'une seule table:
|
# Formats non HTML et demande d'une seule table:
|
||||||
if format != "html" and format != "text":
|
if format != "html" and format != "text":
|
||||||
if absjust_only == 1:
|
if absjust_only == 1:
|
||||||
return tab_absjust.make_page(format=format, REQUEST=REQUEST)
|
return tab_absjust.make_page(format=format)
|
||||||
else:
|
else:
|
||||||
return tab_absnonjust.make_page(format=format, REQUEST=REQUEST)
|
return tab_absnonjust.make_page(format=format)
|
||||||
|
|
||||||
if format == "html":
|
if format == "html":
|
||||||
# Mise en forme HTML:
|
# Mise en forme HTML:
|
||||||
|
@ -896,13 +883,12 @@ def ListeAbsEtud(
|
||||||
raise ValueError("Invalid format !")
|
raise ValueError("Invalid format !")
|
||||||
|
|
||||||
|
|
||||||
def _TablesAbsEtud(
|
def _tables_abs_etud(
|
||||||
etudid,
|
etudid,
|
||||||
datedebut,
|
datedebut,
|
||||||
with_evals=True,
|
with_evals=True,
|
||||||
format="html",
|
format="html",
|
||||||
absjust_only=0,
|
absjust_only=0,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Tables des absences justifiees et non justifiees d'un étudiant
|
"""Tables des absences justifiees et non justifiees d'un étudiant
|
||||||
sur l'année en cours
|
sur l'année en cours
|
||||||
|
@ -928,11 +914,11 @@ def _TablesAbsEtud(
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""SELECT mi.moduleimpl_id
|
"""SELECT mi.moduleimpl_id
|
||||||
FROM absences abs, notes_moduleimpl_inscription mi, notes_moduleimpl m
|
FROM absences abs, notes_moduleimpl_inscription mi, notes_moduleimpl m
|
||||||
WHERE abs.matin = %(matin)s
|
WHERE abs.matin = %(matin)s
|
||||||
and abs.jour = %(jour)s
|
and abs.jour = %(jour)s
|
||||||
and abs.etudid = %(etudid)s
|
and abs.etudid = %(etudid)s
|
||||||
and abs.moduleimpl_id = mi.moduleimpl_id
|
and abs.moduleimpl_id = mi.moduleimpl_id
|
||||||
and mi.moduleimpl_id = m.id
|
and mi.moduleimpl_id = m.id
|
||||||
and mi.etudid = %(etudid)s
|
and mi.etudid = %(etudid)s
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
|
@ -954,13 +940,14 @@ def _TablesAbsEtud(
|
||||||
return ""
|
return ""
|
||||||
ex = []
|
ex = []
|
||||||
for ev in a["evals"]:
|
for ev in a["evals"]:
|
||||||
mod = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
mod = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||||
moduleimpl_id=ev["moduleimpl_id"]
|
moduleimpl_id=ev["moduleimpl_id"]
|
||||||
)[0]
|
)[0]
|
||||||
if format == "html":
|
if format == "html":
|
||||||
ex.append(
|
ex.append(
|
||||||
'<a href="Notes/moduleimpl_status?moduleimpl_id=%s">%s</a>'
|
f"""<a href="{url_for('notes.moduleimpl_status',
|
||||||
% (mod["moduleimpl_id"], mod["module"]["code"])
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
|
||||||
|
">{mod["module"]["code"]}</a>"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
ex.append(mod["module"]["code"])
|
ex.append(mod["module"]["code"])
|
||||||
|
@ -971,13 +958,14 @@ def _TablesAbsEtud(
|
||||||
def descr_abs(a):
|
def descr_abs(a):
|
||||||
ex = []
|
ex = []
|
||||||
for ev in a.get("absent", []):
|
for ev in a.get("absent", []):
|
||||||
mod = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
mod = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||||
moduleimpl_id=ev["moduleimpl_id"]
|
moduleimpl_id=ev["moduleimpl_id"]
|
||||||
)[0]
|
)[0]
|
||||||
if format == "html":
|
if format == "html":
|
||||||
ex.append(
|
ex.append(
|
||||||
'<a href="Notes/moduleimpl_status?moduleimpl_id=%s">%s</a>'
|
f"""<a href="{url_for('notes.moduleimpl_status',
|
||||||
% (mod["moduleimpl_id"], mod["module"]["code"])
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
|
||||||
|
">{mod["module"]["code"]}</a>"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
ex.append(mod["module"]["code"])
|
ex.append(mod["module"]["code"])
|
||||||
|
|
|
@ -29,37 +29,40 @@
|
||||||
|
|
||||||
|
|
||||||
Archives are plain files, stored in
|
Archives are plain files, stored in
|
||||||
<SCODOC_VAR_DIR>/archives/<deptid>
|
<SCODOC_VAR_DIR>/archives/<dept_id>
|
||||||
(where <SCODOC_VAR_DIR> is usually /opt/scodoc-data, and <deptid> a departement 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
|
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>
|
<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
|
Les documents liés à l'étudiant sont dans
|
||||||
<archivedir>/docetuds/<dept>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
|
<archivedir>/docetuds/<dept_id>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
|
||||||
(etudid est ici soit Identite.scodoc7id, soit à défaut Identite.id)
|
(etudid est ici Identite.id)
|
||||||
|
|
||||||
Les maquettes Apogée pour l'export des notes sont dans
|
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
|
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.
|
qui est une description (humaine, format libre) de l'archive.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import glob
|
||||||
|
import mimetypes
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import glob
|
import time
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import g
|
from flask import g, request
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from config import Config
|
from config import Config
|
||||||
from app import log
|
from app import log
|
||||||
|
from app.models import Departement
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
from app.scodoc.sco_exceptions import (
|
from app.scodoc.sco_exceptions import (
|
||||||
AccessDenied,
|
AccessDenied,
|
||||||
|
@ -108,7 +111,8 @@ class BaseArchiver(object):
|
||||||
If directory does not yet exist, create it.
|
If directory does not yet exist, create it.
|
||||||
"""
|
"""
|
||||||
self.initialize()
|
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:
|
try:
|
||||||
scu.GSL.acquire()
|
scu.GSL.acquire()
|
||||||
if not os.path.isdir(dept_dir):
|
if not os.path.isdir(dept_dir):
|
||||||
|
@ -127,7 +131,8 @@ class BaseArchiver(object):
|
||||||
:return: list of archive oids
|
:return: list of archive oids
|
||||||
"""
|
"""
|
||||||
self.initialize()
|
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 + "*")
|
dirs = glob.glob(base + "*")
|
||||||
return [os.path.split(x)[1] for x in dirs]
|
return [os.path.split(x)[1] for x in dirs]
|
||||||
|
|
||||||
|
@ -198,7 +203,9 @@ class BaseArchiver(object):
|
||||||
def get_archive_description(self, archive_id):
|
def get_archive_description(self, archive_id):
|
||||||
"""Return description of archive"""
|
"""Return description of archive"""
|
||||||
self.initialize()
|
self.initialize()
|
||||||
return open(os.path.join(archive_id, "_description.txt")).read()
|
with open(os.path.join(archive_id, "_description.txt")) as f:
|
||||||
|
descr = f.read()
|
||||||
|
return descr
|
||||||
|
|
||||||
def create_obj_archive(self, oid: int, description: str):
|
def create_obj_archive(self, oid: int, description: str):
|
||||||
"""Creates a new archive for this object and returns its id."""
|
"""Creates a new archive for this object and returns its id."""
|
||||||
|
@ -227,9 +234,8 @@ class BaseArchiver(object):
|
||||||
try:
|
try:
|
||||||
scu.GSL.acquire()
|
scu.GSL.acquire()
|
||||||
fname = os.path.join(archive_id, filename)
|
fname = os.path.join(archive_id, filename)
|
||||||
f = open(fname, "wb")
|
with open(fname, "wb") as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
f.close()
|
|
||||||
finally:
|
finally:
|
||||||
scu.GSL.release()
|
scu.GSL.release()
|
||||||
return filename
|
return filename
|
||||||
|
@ -242,33 +248,19 @@ class BaseArchiver(object):
|
||||||
raise ValueError("invalid filename")
|
raise ValueError("invalid filename")
|
||||||
fname = os.path.join(archive_id, filename)
|
fname = os.path.join(archive_id, filename)
|
||||||
log("reading archive file %s" % fname)
|
log("reading archive file %s" % fname)
|
||||||
return open(fname, "rb").read()
|
with open(fname, "rb") as f:
|
||||||
|
data = f.read()
|
||||||
|
return data
|
||||||
|
|
||||||
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"""
|
"""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)
|
archive_id = self.get_id_from_name(oid, archive_name)
|
||||||
data = self.get(archive_id, filename)
|
data = self.get(archive_id, filename)
|
||||||
ext = os.path.splitext(filename.lower())[1]
|
mime = mimetypes.guess_type(filename)[0]
|
||||||
if ext == ".html" or ext == ".htm":
|
if mime is None:
|
||||||
return data
|
mime = "application/octet-stream"
|
||||||
elif ext == ".xml":
|
|
||||||
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
|
return scu.send_file(data, filename, mime=mime)
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class SemsArchiver(BaseArchiver):
|
class SemsArchiver(BaseArchiver):
|
||||||
|
@ -283,7 +275,6 @@ PVArchive = SemsArchiver()
|
||||||
|
|
||||||
|
|
||||||
def do_formsemestre_archive(
|
def do_formsemestre_archive(
|
||||||
REQUEST,
|
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
group_ids=[], # si indiqué, ne prend que ces groupes
|
group_ids=[], # si indiqué, ne prend que ces groupes
|
||||||
description="",
|
description="",
|
||||||
|
@ -305,7 +296,7 @@ def do_formsemestre_archive(
|
||||||
from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet
|
from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet
|
||||||
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
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)
|
archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
|
||||||
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
|
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
|
||||||
|
|
||||||
|
@ -351,14 +342,12 @@ def do_formsemestre_archive(
|
||||||
data = data.encode(scu.SCO_ENCODING)
|
data = data.encode(scu.SCO_ENCODING)
|
||||||
PVArchive.store(archive_id, "Bulletins.xml", data)
|
PVArchive.store(archive_id, "Bulletins.xml", data)
|
||||||
# Decisions de jury, en XLS
|
# Decisions de jury, en XLS
|
||||||
data = sco_pvjury.formsemestre_pvjury(
|
data = sco_pvjury.formsemestre_pvjury(formsemestre_id, format="xls", publish=False)
|
||||||
formsemestre_id, format="xls", REQUEST=REQUEST, publish=False
|
|
||||||
)
|
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, "Decisions_Jury" + scu.XLSX_SUFFIX, data)
|
PVArchive.store(archive_id, "Decisions_Jury" + scu.XLSX_SUFFIX, data)
|
||||||
# Classeur bulletins (PDF)
|
# Classeur bulletins (PDF)
|
||||||
data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
||||||
formsemestre_id, REQUEST, version=bulVersion
|
formsemestre_id, version=bulVersion
|
||||||
)
|
)
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, "Bulletins.pdf", data)
|
PVArchive.store(archive_id, "Bulletins.pdf", data)
|
||||||
|
@ -389,14 +378,12 @@ def do_formsemestre_archive(
|
||||||
PVArchive.store(archive_id, "PV_Jury%s.pdf" % groups_filename, data)
|
PVArchive.store(archive_id, "PV_Jury%s.pdf" % groups_filename, data)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]):
|
def formsemestre_archive(formsemestre_id, group_ids=[]):
|
||||||
"""Make and store new archive for this formsemestre.
|
"""Make and store new archive for this formsemestre.
|
||||||
(all students or only selected groups)
|
(all students or only selected groups)
|
||||||
"""
|
"""
|
||||||
if not sco_permissions_check.can_edit_pv(formsemestre_id):
|
if not sco_permissions_check.can_edit_pv(formsemestre_id):
|
||||||
raise AccessDenied(
|
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
|
||||||
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
|
|
||||||
)
|
|
||||||
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
if not group_ids:
|
if not group_ids:
|
||||||
|
@ -408,7 +395,6 @@ def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]):
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
REQUEST,
|
|
||||||
"Archiver les PV et résultats du semestre",
|
"Archiver les PV et résultats du semestre",
|
||||||
sem=sem,
|
sem=sem,
|
||||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||||
|
@ -469,8 +455,8 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||||
)
|
)
|
||||||
|
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
descr,
|
descr,
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
method="POST",
|
method="POST",
|
||||||
|
@ -492,7 +478,6 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||||
else:
|
else:
|
||||||
tf[2]["anonymous"] = False
|
tf[2]["anonymous"] = False
|
||||||
do_formsemestre_archive(
|
do_formsemestre_archive(
|
||||||
REQUEST,
|
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
group_ids=group_ids,
|
group_ids=group_ids,
|
||||||
description=tf[2]["description"],
|
description=tf[2]["description"],
|
||||||
|
@ -516,10 +501,10 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_list_archives(REQUEST, formsemestre_id):
|
def formsemestre_list_archives(formsemestre_id):
|
||||||
"""Page listing archives"""
|
"""Page listing archives"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
|
sem_archive_id = formsemestre_id
|
||||||
L = []
|
L = []
|
||||||
for archive_id in PVArchive.list_obj_archives(sem_archive_id):
|
for archive_id in PVArchive.list_obj_archives(sem_archive_id):
|
||||||
a = {
|
a = {
|
||||||
|
@ -530,7 +515,7 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
|
||||||
}
|
}
|
||||||
L.append(a)
|
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:
|
if not L:
|
||||||
H.append("<p>aucune archive enregistrée</p>")
|
H.append("<p>aucune archive enregistrée</p>")
|
||||||
else:
|
else:
|
||||||
|
@ -559,23 +544,19 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
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."""
|
"""Send file to client."""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
|
sem_archive_id = formsemestre_id
|
||||||
return PVArchive.get_archived_file(REQUEST, sem_archive_id, archive_name, filename)
|
return PVArchive.get_archived_file(sem_archive_id, archive_name, filename)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_delete_archive(
|
def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=False):
|
||||||
REQUEST, formsemestre_id, archive_name, dialog_confirmed=False
|
|
||||||
):
|
|
||||||
"""Delete an archive"""
|
"""Delete an archive"""
|
||||||
if not sco_permissions_check.can_edit_pv(formsemestre_id):
|
if not sco_permissions_check.can_edit_pv(formsemestre_id):
|
||||||
raise AccessDenied(
|
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
|
||||||
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
|
|
||||||
)
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
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)
|
archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
|
||||||
|
|
||||||
dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id)
|
dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id)
|
||||||
|
|
|
@ -30,7 +30,9 @@
|
||||||
les dossiers d'admission et autres pièces utiles.
|
les dossiers d'admission et autres pièces utiles.
|
||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g
|
from flask import url_for, render_template
|
||||||
|
from flask import g, request
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_import_etuds
|
from app.scodoc import sco_import_etuds
|
||||||
|
@ -58,14 +60,14 @@ def can_edit_etud_archive(authuser):
|
||||||
return authuser.has_permission(Permission.ScoEtudAddAnnotations)
|
return authuser.has_permission(Permission.ScoEtudAddAnnotations)
|
||||||
|
|
||||||
|
|
||||||
def etud_list_archives_html(REQUEST, etudid):
|
def etud_list_archives_html(etudid):
|
||||||
"""HTML snippet listing archives"""
|
"""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)
|
etuds = sco_etud.get_etud_info(etudid=etudid)
|
||||||
if not etuds:
|
if not etuds:
|
||||||
raise ScoValueError("étudiant inexistant")
|
raise ScoValueError("étudiant inexistant")
|
||||||
etud = etuds[0]
|
etud = etuds[0]
|
||||||
etud_archive_id = etud["scodoc7_id"] or etudid
|
etud_archive_id = etudid
|
||||||
L = []
|
L = []
|
||||||
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
||||||
a = {
|
a = {
|
||||||
|
@ -118,7 +120,7 @@ def add_archives_info_to_etud_list(etuds):
|
||||||
"""
|
"""
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
l = []
|
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):
|
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
||||||
l.append(
|
l.append(
|
||||||
"%s (%s)"
|
"%s (%s)"
|
||||||
|
@ -130,13 +132,11 @@ def add_archives_info_to_etud_list(etuds):
|
||||||
etud["etudarchive"] = ", ".join(l)
|
etud["etudarchive"] = ", ".join(l)
|
||||||
|
|
||||||
|
|
||||||
def etud_upload_file_form(REQUEST, etudid):
|
def etud_upload_file_form(etudid):
|
||||||
"""Page with a form to choose and upload a file, with a description."""
|
"""Page with a form to choose and upload a file, with a description."""
|
||||||
# check permission
|
# check permission
|
||||||
if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER):
|
if not can_edit_etud_archive(current_user):
|
||||||
raise AccessDenied(
|
raise AccessDenied("opération non autorisée pour %s" % current_user)
|
||||||
"opération non autorisée pour %s" % REQUEST.AUTHENTICATED_USER
|
|
||||||
)
|
|
||||||
etuds = sco_etud.get_etud_info(filled=True)
|
etuds = sco_etud.get_etud_info(filled=True)
|
||||||
if not etuds:
|
if not etuds:
|
||||||
raise ScoValueError("étudiant inexistant")
|
raise ScoValueError("étudiant inexistant")
|
||||||
|
@ -153,8 +153,8 @@ def etud_upload_file_form(REQUEST, etudid):
|
||||||
% (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)),
|
% (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)),
|
||||||
]
|
]
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(
|
(
|
||||||
("etudid", {"default": etudid, "input_type": "hidden"}),
|
("etudid", {"default": etudid, "input_type": "hidden"}),
|
||||||
("datafile", {"input_type": "file", "title": "Fichier", "size": 30}),
|
("datafile", {"input_type": "file", "title": "Fichier", "size": 30}),
|
||||||
|
@ -181,7 +181,7 @@ def etud_upload_file_form(REQUEST, etudid):
|
||||||
data = tf[2]["datafile"].read()
|
data = tf[2]["datafile"].read()
|
||||||
descr = tf[2]["description"]
|
descr = tf[2]["description"]
|
||||||
filename = tf[2]["datafile"].filename
|
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(
|
_store_etud_file_to_new_archive(
|
||||||
etud_archive_id, data, filename, description=descr
|
etud_archive_id, data, filename, description=descr
|
||||||
)
|
)
|
||||||
|
@ -199,18 +199,16 @@ def _store_etud_file_to_new_archive(etud_archive_id, data, filename, description
|
||||||
EtudsArchive.store(archive_id, filename, data)
|
EtudsArchive.store(archive_id, filename, data)
|
||||||
|
|
||||||
|
|
||||||
def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
|
def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
||||||
"""Delete an archive"""
|
"""Delete an archive"""
|
||||||
# check permission
|
# check permission
|
||||||
if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER):
|
if not can_edit_etud_archive(current_user):
|
||||||
raise AccessDenied(
|
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
|
||||||
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
|
|
||||||
)
|
|
||||||
etuds = sco_etud.get_etud_info(filled=True)
|
etuds = sco_etud.get_etud_info(filled=True)
|
||||||
if not etuds:
|
if not etuds:
|
||||||
raise ScoValueError("étudiant inexistant")
|
raise ScoValueError("étudiant inexistant")
|
||||||
etud = etuds[0]
|
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)
|
archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
|
@ -242,20 +240,18 @@ 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."""
|
"""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:
|
if not etuds:
|
||||||
raise ScoValueError("étudiant inexistant")
|
raise ScoValueError("étudiant inexistant")
|
||||||
etud = etuds[0]
|
etud = etuds[0]
|
||||||
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
|
etud_archive_id = etud["etudid"]
|
||||||
return EtudsArchive.get_archived_file(
|
return EtudsArchive.get_archived_file(etud_archive_id, archive_name, filename)
|
||||||
REQUEST, etud_archive_id, archive_name, filename
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
|
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
|
||||||
def etudarchive_generate_excel_sample(group_id=None, REQUEST=None):
|
def etudarchive_generate_excel_sample(group_id=None):
|
||||||
"""Feuille excel pour import fichiers etudiants (utilisé pour admissions)"""
|
"""Feuille excel pour import fichiers etudiants (utilisé pour admissions)"""
|
||||||
fmt = sco_import_etuds.sco_import_format()
|
fmt = sco_import_etuds.sco_import_format()
|
||||||
data = sco_import_etuds.sco_import_generate_excel_sample(
|
data = sco_import_etuds.sco_import_generate_excel_sample(
|
||||||
|
@ -271,12 +267,15 @@ def etudarchive_generate_excel_sample(group_id=None, REQUEST=None):
|
||||||
],
|
],
|
||||||
extra_cols=["fichier_a_charger"],
|
extra_cols=["fichier_a_charger"],
|
||||||
)
|
)
|
||||||
return sco_excel.send_excel_file(
|
return scu.send_file(
|
||||||
REQUEST, data, "ImportFichiersEtudiants" + scu.XLSX_SUFFIX
|
data,
|
||||||
|
"ImportFichiersEtudiants",
|
||||||
|
suffix=scu.XLSX_SUFFIX,
|
||||||
|
mime=scu.XLSX_MIMETYPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def etudarchive_import_files_form(group_id, REQUEST=None):
|
def etudarchive_import_files_form(group_id):
|
||||||
"""Formulaire pour importation fichiers d'un groupe"""
|
"""Formulaire pour importation fichiers d'un groupe"""
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
|
@ -310,8 +309,8 @@ def etudarchive_import_files_form(group_id, REQUEST=None):
|
||||||
]
|
]
|
||||||
F = html_sco_header.sco_footer()
|
F = html_sco_header.sco_footer()
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(
|
(
|
||||||
("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}),
|
("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}),
|
||||||
("zipfile", {"title": "Fichier zip:", "input_type": "file", "size": 40}),
|
("zipfile", {"title": "Fichier zip:", "input_type": "file", "size": 40}),
|
||||||
|
@ -330,9 +329,9 @@ def etudarchive_import_files_form(group_id, REQUEST=None):
|
||||||
|
|
||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
return "\n".join(H) + tf[1] + "</li></ol>" + F
|
return "\n".join(H) + tf[1] + "</li></ol>" + F
|
||||||
elif tf[0] == -1:
|
# retrouve le semestre à partir du groupe:
|
||||||
# retrouve le semestre à partir du groupe:
|
group = sco_groups.get_group(group_id)
|
||||||
group = sco_groups.get_group(group_id)
|
if tf[0] == -1:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.formsemestre_status",
|
"notes.formsemestre_status",
|
||||||
|
@ -342,21 +341,41 @@ def etudarchive_import_files_form(group_id, REQUEST=None):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return etudarchive_import_files(
|
return etudarchive_import_files(
|
||||||
group_id=tf[2]["group_id"],
|
formsemestre_id=group["formsemestre_id"],
|
||||||
xlsfile=tf[2]["xlsfile"],
|
xlsfile=tf[2]["xlsfile"],
|
||||||
zipfile=tf[2]["zipfile"],
|
zipfile=tf[2]["zipfile"],
|
||||||
description=tf[2]["description"],
|
description=tf[2]["description"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def etudarchive_import_files(group_id=None, xlsfile=None, zipfile=None, description=""):
|
def etudarchive_import_files(
|
||||||
|
formsemestre_id=None, xlsfile=None, zipfile=None, description=""
|
||||||
|
):
|
||||||
|
"Importe des fichiers"
|
||||||
|
|
||||||
def callback(etud, data, filename):
|
def callback(etud, data, filename):
|
||||||
_store_etud_file_to_new_archive(etud["etudid"], data, filename, description)
|
_store_etud_file_to_new_archive(etud["etudid"], data, filename, description)
|
||||||
|
|
||||||
filename_title = "fichier_a_charger"
|
# Utilise la fontion developpée au depart pour les photos
|
||||||
page_title = "Téléchargement de fichiers associés aux étudiants"
|
(
|
||||||
# Utilise la fontion au depart developpee pour les photos
|
ignored_zipfiles,
|
||||||
r = sco_trombino.zip_excel_import_files(
|
unmatched_files,
|
||||||
xlsfile, zipfile, callback, filename_title, page_title
|
stored_etud_filename,
|
||||||
|
) = sco_trombino.zip_excel_import_files(
|
||||||
|
xlsfile=xlsfile,
|
||||||
|
zipfile=zipfile,
|
||||||
|
callback=callback,
|
||||||
|
filename_title="fichier_a_charger",
|
||||||
|
)
|
||||||
|
return render_template(
|
||||||
|
"scolar/photos_import_files.html",
|
||||||
|
page_title="Téléchargement de fichiers associés aux étudiants",
|
||||||
|
ignored_zipfiles=ignored_zipfiles,
|
||||||
|
unmatched_files=unmatched_files,
|
||||||
|
stored_etud_filename=stored_etud_filename,
|
||||||
|
next_page=url_for(
|
||||||
|
"scolar.groups_view",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
return r + html_sco_header.sco_footer()
|
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
"""Génération des bulletins de notes
|
"""Génération des bulletins de notes
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from app.models import formsemestre
|
||||||
import time
|
import time
|
||||||
import pprint
|
import pprint
|
||||||
import email
|
import email
|
||||||
|
@ -35,11 +36,10 @@ from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.mime.base import MIMEBase
|
from email.mime.base import MIMEBase
|
||||||
from email.header import Header
|
from email.header import Header
|
||||||
|
|
||||||
from reportlab.lib.colors import Color
|
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 import url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_mail import Message
|
from flask_mail import Message
|
||||||
|
@ -48,7 +48,7 @@ import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_permissions import Permission
|
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 html_sco_header
|
||||||
from app.scodoc import htmlutils
|
from app.scodoc import htmlutils
|
||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_abs
|
||||||
|
@ -121,9 +121,7 @@ def make_context_dict(sem, etud):
|
||||||
return C
|
return C
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_bulletinetud_dict(
|
def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||||
formsemestre_id, etudid, version="long", REQUEST=None
|
|
||||||
):
|
|
||||||
"""Collecte informations pour bulletin de notes
|
"""Collecte informations pour bulletin de notes
|
||||||
Retourne un dictionnaire (avec valeur par défaut chaine vide).
|
Retourne un dictionnaire (avec valeur par défaut chaine vide).
|
||||||
Le contenu du dictionnaire dépend des options (rangs, ...)
|
Le contenu du dictionnaire dépend des options (rangs, ...)
|
||||||
|
@ -138,15 +136,13 @@ def formsemestre_bulletinetud_dict(
|
||||||
|
|
||||||
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
||||||
|
if not nt.get_etud_etat(etudid):
|
||||||
|
raise ScoValueError("Etudiant non inscrit à ce semestre")
|
||||||
I = scu.DictDefault(defaultvalue="")
|
I = scu.DictDefault(defaultvalue="")
|
||||||
I["etudid"] = etudid
|
I["etudid"] = etudid
|
||||||
I["formsemestre_id"] = formsemestre_id
|
I["formsemestre_id"] = formsemestre_id
|
||||||
I["sem"] = nt.sem
|
I["sem"] = nt.sem
|
||||||
if REQUEST:
|
I["server_name"] = request.url_root
|
||||||
I["server_name"] = REQUEST.BASE0
|
|
||||||
else:
|
|
||||||
I["server_name"] = ""
|
|
||||||
|
|
||||||
# Formation et parcours
|
# Formation et parcours
|
||||||
I["formation"] = sco_formations.formation_list(
|
I["formation"] = sco_formations.formation_list(
|
||||||
|
@ -771,14 +767,16 @@ def formsemestre_bulletinetud(
|
||||||
xml_with_decisions=False,
|
xml_with_decisions=False,
|
||||||
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
||||||
prefer_mail_perso=False,
|
prefer_mail_perso=False,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"page bulletin de notes"
|
"page bulletin de notes"
|
||||||
try:
|
try:
|
||||||
etud = sco_etud.get_etud_info(filled=True)[0]
|
etud = sco_etud.get_etud_info(filled=True)[0]
|
||||||
etudid = etud["etudid"]
|
etudid = etud["etudid"]
|
||||||
except:
|
except:
|
||||||
return scu.log_unknown_etud(REQUEST, format=format)
|
sco_etud.log_unknown_etud()
|
||||||
|
raise ScoValueError("étudiant inconnu")
|
||||||
|
# API, donc erreurs admises en ScoValueError
|
||||||
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||||
|
|
||||||
bulletin = do_formsemestre_bulletinetud(
|
bulletin = do_formsemestre_bulletinetud(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
|
@ -788,15 +786,15 @@ def formsemestre_bulletinetud(
|
||||||
xml_with_decisions=xml_with_decisions,
|
xml_with_decisions=xml_with_decisions,
|
||||||
force_publishing=force_publishing,
|
force_publishing=force_publishing,
|
||||||
prefer_mail_perso=prefer_mail_perso,
|
prefer_mail_perso=prefer_mail_perso,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)[0]
|
)[0]
|
||||||
if format not in {"html", "pdfmail"}:
|
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)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
H = [
|
H = [
|
||||||
_formsemestre_bulletinetud_header_html(
|
_formsemestre_bulletinetud_header_html(
|
||||||
etud, etudid, sem, formsemestre_id, format, version, REQUEST
|
etud, etudid, sem, formsemestre_id, format, version
|
||||||
),
|
),
|
||||||
bulletin,
|
bulletin,
|
||||||
]
|
]
|
||||||
|
@ -854,7 +852,6 @@ def do_formsemestre_bulletinetud(
|
||||||
etudid,
|
etudid,
|
||||||
version="long", # short, long, selectedevals
|
version="long", # short, long, selectedevals
|
||||||
format="html",
|
format="html",
|
||||||
REQUEST=None,
|
|
||||||
nohtml=False,
|
nohtml=False,
|
||||||
xml_with_decisions=False, # force decisions dans XML
|
xml_with_decisions=False, # force decisions dans XML
|
||||||
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
||||||
|
@ -862,14 +859,13 @@ def do_formsemestre_bulletinetud(
|
||||||
):
|
):
|
||||||
"""Génère le bulletin au format demandé.
|
"""Génère le bulletin au format demandé.
|
||||||
Retourne: (bul, filigranne)
|
Retourne: (bul, filigranne)
|
||||||
où bul est au format demandé (html, pdf, pdfmail, pdfpart, xml)
|
où bul est str ou bytes au format demandé (html, pdf, pdfmail, pdfpart, xml, json)
|
||||||
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
|
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
|
||||||
"""
|
"""
|
||||||
if format == "xml":
|
if format == "xml":
|
||||||
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
|
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etudid,
|
etudid,
|
||||||
REQUEST=REQUEST,
|
|
||||||
xml_with_decisions=xml_with_decisions,
|
xml_with_decisions=xml_with_decisions,
|
||||||
force_publishing=force_publishing,
|
force_publishing=force_publishing,
|
||||||
version=version,
|
version=version,
|
||||||
|
@ -881,19 +877,18 @@ def do_formsemestre_bulletinetud(
|
||||||
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
|
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etudid,
|
etudid,
|
||||||
REQUEST=REQUEST,
|
|
||||||
xml_with_decisions=xml_with_decisions,
|
xml_with_decisions=xml_with_decisions,
|
||||||
force_publishing=force_publishing,
|
force_publishing=force_publishing,
|
||||||
version=version,
|
version=version,
|
||||||
)
|
)
|
||||||
return bul, ""
|
return bul, ""
|
||||||
|
|
||||||
I = formsemestre_bulletinetud_dict(formsemestre_id, etudid, REQUEST=REQUEST)
|
I = formsemestre_bulletinetud_dict(formsemestre_id, etudid)
|
||||||
etud = I["etud"]
|
etud = I["etud"]
|
||||||
|
|
||||||
if format == "html":
|
if format == "html":
|
||||||
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
|
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
|
||||||
I, version=version, format="html", REQUEST=REQUEST
|
I, version=version, format="html"
|
||||||
)
|
)
|
||||||
return htm, I["filigranne"]
|
return htm, I["filigranne"]
|
||||||
|
|
||||||
|
@ -903,11 +898,10 @@ def do_formsemestre_bulletinetud(
|
||||||
version=version,
|
version=version,
|
||||||
format="pdf",
|
format="pdf",
|
||||||
stand_alone=(format != "pdfpart"),
|
stand_alone=(format != "pdfpart"),
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
if format == "pdf":
|
if format == "pdf":
|
||||||
return (
|
return (
|
||||||
scu.sendPDFFile(REQUEST, bul, filename),
|
scu.sendPDFFile(bul, filename),
|
||||||
I["filigranne"],
|
I["filigranne"],
|
||||||
) # unused ret. value
|
) # unused ret. value
|
||||||
else:
|
else:
|
||||||
|
@ -923,11 +917,11 @@ def do_formsemestre_bulletinetud(
|
||||||
htm = "" # speed up if html version not needed
|
htm = "" # speed up if html version not needed
|
||||||
else:
|
else:
|
||||||
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
|
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(
|
pdfdata, filename = sco_bulletins_generator.make_formsemestre_bulletinetud(
|
||||||
I, version=version, format="pdf", REQUEST=REQUEST
|
I, version=version, format="pdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
if prefer_mail_perso:
|
if prefer_mail_perso:
|
||||||
|
@ -993,12 +987,11 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
|
||||||
bcc = copy_addr.strip()
|
bcc = copy_addr.strip()
|
||||||
else:
|
else:
|
||||||
bcc = ""
|
bcc = ""
|
||||||
msg = Message(subject, sender=sender, recipients=recipients, bcc=bcc)
|
msg = Message(subject, sender=sender, recipients=recipients, bcc=[bcc])
|
||||||
msg.body = hea
|
msg.body = hea
|
||||||
|
|
||||||
# Attach pdf
|
# Attach pdf
|
||||||
msg.attach(filename, scu.PDF_MIMETYPE, pdfdata)
|
msg.attach(filename, scu.PDF_MIMETYPE, pdfdata)
|
||||||
|
|
||||||
log("mail bulletin a %s" % recipient_addr)
|
log("mail bulletin a %s" % recipient_addr)
|
||||||
email.send_message(msg)
|
email.send_message(msg)
|
||||||
|
|
||||||
|
@ -1010,7 +1003,6 @@ def _formsemestre_bulletinetud_header_html(
|
||||||
formsemestre_id=None,
|
formsemestre_id=None,
|
||||||
format=None,
|
format=None,
|
||||||
version=None,
|
version=None,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
|
@ -1033,7 +1025,7 @@ def _formsemestre_bulletinetud_header_html(
|
||||||
),
|
),
|
||||||
"""
|
"""
|
||||||
<form name="f" method="GET" action="%s">"""
|
<form name="f" method="GET" action="%s">"""
|
||||||
% REQUEST.URL0,
|
% request.base_url,
|
||||||
f"""Bulletin <span class="bull_liensemestre"><a href="{
|
f"""Bulletin <span class="bull_liensemestre"><a href="{
|
||||||
url_for("notes.formsemestre_status",
|
url_for("notes.formsemestre_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
@ -1063,14 +1055,20 @@ def _formsemestre_bulletinetud_header_html(
|
||||||
H.append("""</select></td>""")
|
H.append("""</select></td>""")
|
||||||
# Menu
|
# Menu
|
||||||
endpoint = "notes.formsemestre_bulletinetud"
|
endpoint = "notes.formsemestre_bulletinetud"
|
||||||
url = REQUEST.URL0
|
|
||||||
qurl = six.moves.urllib.parse.quote_plus(url + "?" + REQUEST.QUERY_STRING)
|
|
||||||
|
|
||||||
menuBul = [
|
menuBul = [
|
||||||
{
|
{
|
||||||
"title": "Réglages bulletins",
|
"title": "Réglages bulletins",
|
||||||
"endpoint": "notes.formsemestre_edit_options",
|
"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"])
|
"enabled": (current_user.id in sem["responsables"])
|
||||||
or current_user.has_permission(Permission.ScoImplement),
|
or current_user.has_permission(Permission.ScoImplement),
|
||||||
},
|
},
|
||||||
|
@ -1113,6 +1111,16 @@ def _formsemestre_bulletinetud_header_html(
|
||||||
"enabled": etud["emailperso"]
|
"enabled": etud["emailperso"]
|
||||||
and can_send_bulletin_by_mail(formsemestre_id),
|
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",
|
"title": "Version XML",
|
||||||
"endpoint": endpoint,
|
"endpoint": endpoint,
|
||||||
|
@ -1188,9 +1196,14 @@ def _formsemestre_bulletinetud_header_html(
|
||||||
H.append(
|
H.append(
|
||||||
'<td> <a href="%s">%s</a></td>'
|
'<td> <a href="%s">%s</a></td>'
|
||||||
% (
|
% (
|
||||||
url
|
url_for(
|
||||||
+ "?formsemestre_id=%s&etudid=%s&format=pdf&version=%s"
|
"notes.formsemestre_bulletinetud",
|
||||||
% (formsemestre_id, etudid, version),
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
etudid=etudid,
|
||||||
|
format="pdf",
|
||||||
|
version=version,
|
||||||
|
),
|
||||||
scu.ICON_PDF,
|
scu.ICON_PDF,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -1201,9 +1214,7 @@ def _formsemestre_bulletinetud_header_html(
|
||||||
"""
|
"""
|
||||||
% (
|
% (
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||||
sco_photos.etud_photo_html(
|
sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"]),
|
||||||
etud, title="fiche de " + etud["nom"], REQUEST=REQUEST
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
|
|
|
@ -52,6 +52,9 @@ import reportlab
|
||||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
|
||||||
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
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 import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import NoteProcessError
|
from app.scodoc.sco_exceptions import NoteProcessError
|
||||||
from app import log
|
from app import log
|
||||||
|
@ -148,14 +151,7 @@ class BulletinGenerator(object):
|
||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
"""Build a filename to be proposed to the web client"""
|
"""Build a filename to be proposed to the web client"""
|
||||||
sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"])
|
sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"])
|
||||||
dt = time.strftime("%Y-%m-%d")
|
return scu.bul_filename(sem, self.infos["etud"], "pdf")
|
||||||
filename = "bul-%s-%s-%s.pdf" % (
|
|
||||||
sem["titre_num"],
|
|
||||||
dt,
|
|
||||||
self.infos["etud"]["nom"],
|
|
||||||
)
|
|
||||||
filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "")
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def generate(self, format="", stand_alone=True):
|
def generate(self, format="", stand_alone=True):
|
||||||
"""Return bulletin in specified format"""
|
"""Return bulletin in specified format"""
|
||||||
|
@ -260,7 +256,6 @@ def make_formsemestre_bulletinetud(
|
||||||
version="long", # short, long, selectedevals
|
version="long", # short, long, selectedevals
|
||||||
format="pdf", # html, pdf
|
format="pdf", # html, pdf
|
||||||
stand_alone=True,
|
stand_alone=True,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Bulletin de notes
|
"""Bulletin de notes
|
||||||
|
|
||||||
|
@ -286,10 +281,10 @@ def make_formsemestre_bulletinetud(
|
||||||
PDFLOCK.acquire()
|
PDFLOCK.acquire()
|
||||||
bul_generator = gen_class(
|
bul_generator = gen_class(
|
||||||
infos,
|
infos,
|
||||||
authuser=REQUEST.AUTHENTICATED_USER,
|
authuser=current_user,
|
||||||
version=version,
|
version=version,
|
||||||
filigranne=infos["filigranne"],
|
filigranne=infos["filigranne"],
|
||||||
server_name=REQUEST.BASE0,
|
server_name=request.url_root,
|
||||||
)
|
)
|
||||||
if format not in bul_generator.supported_formats:
|
if format not in bul_generator.supported_formats:
|
||||||
# use standard generator
|
# use standard generator
|
||||||
|
@ -301,10 +296,10 @@ def make_formsemestre_bulletinetud(
|
||||||
gen_class = bulletin_get_class(bul_class_name)
|
gen_class = bulletin_get_class(bul_class_name)
|
||||||
bul_generator = gen_class(
|
bul_generator = gen_class(
|
||||||
infos,
|
infos,
|
||||||
authuser=REQUEST.AUTHENTICATED_USER,
|
authuser=current_user,
|
||||||
version=version,
|
version=version,
|
||||||
filigranne=infos["filigranne"],
|
filigranne=infos["filigranne"],
|
||||||
server_name=REQUEST.BASE0,
|
server_name=request.url_root,
|
||||||
)
|
)
|
||||||
|
|
||||||
data = bul_generator.generate(format=format, stand_alone=stand_alone)
|
data = bul_generator.generate(format=format, stand_alone=stand_alone)
|
||||||
|
|
|
@ -47,27 +47,22 @@ from app.scodoc import sco_etud
|
||||||
|
|
||||||
|
|
||||||
def make_json_formsemestre_bulletinetud(
|
def make_json_formsemestre_bulletinetud(
|
||||||
formsemestre_id,
|
formsemestre_id: int,
|
||||||
etudid,
|
etudid: int,
|
||||||
REQUEST=None,
|
|
||||||
xml_with_decisions=False,
|
xml_with_decisions=False,
|
||||||
version="long",
|
version="long",
|
||||||
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
||||||
):
|
) -> str:
|
||||||
"""Renvoie bulletin en chaine JSON"""
|
"""Renvoie bulletin en chaine JSON"""
|
||||||
|
|
||||||
d = formsemestre_bulletinetud_published_dict(
|
d = formsemestre_bulletinetud_published_dict(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etudid,
|
etudid,
|
||||||
force_publishing=force_publishing,
|
force_publishing=force_publishing,
|
||||||
REQUEST=REQUEST,
|
|
||||||
xml_with_decisions=xml_with_decisions,
|
xml_with_decisions=xml_with_decisions,
|
||||||
version=version,
|
version=version,
|
||||||
)
|
)
|
||||||
|
|
||||||
if REQUEST:
|
|
||||||
REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE)
|
|
||||||
|
|
||||||
return json.dumps(d, cls=scu.ScoDocJSONEncoder)
|
return json.dumps(d, cls=scu.ScoDocJSONEncoder)
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,7 +74,6 @@ def formsemestre_bulletinetud_published_dict(
|
||||||
etudid,
|
etudid,
|
||||||
force_publishing=False,
|
force_publishing=False,
|
||||||
xml_nodate=False,
|
xml_nodate=False,
|
||||||
REQUEST=None,
|
|
||||||
xml_with_decisions=False, # inclue les decisions même si non publiées
|
xml_with_decisions=False, # inclue les decisions même si non publiées
|
||||||
version="long",
|
version="long",
|
||||||
):
|
):
|
||||||
|
@ -366,7 +360,7 @@ def formsemestre_bulletinetud_published_dict(
|
||||||
"decisions_ue"
|
"decisions_ue"
|
||||||
]: # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): always publish (car utile pour export Apogee)
|
]: # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): always publish (car utile pour export Apogee)
|
||||||
for ue_id in decision["decisions_ue"].keys():
|
for ue_id in decision["decisions_ue"].keys():
|
||||||
ue = sco_edit_ue.do_ue_list({"ue_id": ue_id})[0]
|
ue = sco_edit_ue.ue_list({"ue_id": ue_id})[0]
|
||||||
d["decision_ue"].append(
|
d["decision_ue"].append(
|
||||||
dict(
|
dict(
|
||||||
ue_id=ue["ue_id"],
|
ue_id=ue["ue_id"],
|
||||||
|
|
|
@ -51,23 +51,24 @@ Chaque semestre peut si nécessaire utiliser un type de bulletin différent.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import io
|
import io
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
from pydoc import html
|
||||||
|
|
||||||
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
|
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
|
||||||
|
|
||||||
from flask import g, url_for
|
from flask import g, request
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log, ScoValueError
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_pdf
|
from app.scodoc import sco_pdf
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
import sco_version
|
import sco_version
|
||||||
|
from app.scodoc.sco_logos import find_logo
|
||||||
|
|
||||||
|
|
||||||
def pdfassemblebulletins(
|
def pdfassemblebulletins(
|
||||||
|
@ -110,6 +111,17 @@ def pdfassemblebulletins(
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def replacement_function(match):
|
||||||
|
balise = match.group(1)
|
||||||
|
name = match.group(3)
|
||||||
|
logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id)
|
||||||
|
if logo is not None:
|
||||||
|
return r'<img %s src="%s"%s/>' % (match.group(2), logo.filepath, match.group(4))
|
||||||
|
raise ScoValueError(
|
||||||
|
'balise "%s": logo "%s" introuvable' % (html.escape(balise), html.escape(name))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
|
def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
|
||||||
"""Process a field given in preferences, returns
|
"""Process a field given in preferences, returns
|
||||||
- if format = 'pdf': a list of Platypus objects
|
- if format = 'pdf': a list of Platypus objects
|
||||||
|
@ -141,30 +153,24 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
|
||||||
return text
|
return text
|
||||||
# --- PDF format:
|
# --- PDF format:
|
||||||
# handle logos:
|
# handle logos:
|
||||||
image_dir = scu.SCODOC_LOGOS_DIR + "/logos_" + g.scodoc_dept + "/"
|
|
||||||
if not os.path.exists(image_dir):
|
|
||||||
image_dir = scu.SCODOC_LOGOS_DIR + "/" # use global logos
|
|
||||||
if not os.path.exists(image_dir):
|
|
||||||
log(f"Warning: missing global logo directory ({image_dir})")
|
|
||||||
image_dir = None
|
|
||||||
|
|
||||||
text = re.sub(
|
text = re.sub(
|
||||||
r"<(\s*)logo(.*?)src\s*=\s*(.*?)>", r"<\1logo\2\3>", text
|
r"<(\s*)logo(.*?)src\s*=\s*(.*?)>", r"<\1logo\2\3>", text
|
||||||
) # remove forbidden src attribute
|
) # remove forbidden src attribute
|
||||||
if image_dir is not None:
|
text = re.sub(
|
||||||
text = re.sub(
|
r'(<\s*logo(.*?)name\s*=\s*"(\w*?)"(.*?)/?>)',
|
||||||
r'<\s*logo(.*?)name\s*=\s*"(\w*?)"(.*?)/?>',
|
replacement_function,
|
||||||
r'<img\1src="%s/logo_\2.jpg"\3/>' % image_dir,
|
text,
|
||||||
text,
|
)
|
||||||
)
|
# nota: le match sur \w*? donne le nom du logo et interdit les .. et autres
|
||||||
# nota: le match sur \w*? donne le nom du logo et interdit les .. et autres
|
# tentatives d'acceder à d'autres fichiers !
|
||||||
# tentatives d'acceder à d'autres fichiers !
|
# la protection contre des noms malveillants est aussi assurée par l'utilisation de
|
||||||
|
# secure_filename dans la classe Logo
|
||||||
|
|
||||||
# log('field: %s' % (text))
|
# log('field: %s' % (text))
|
||||||
return sco_pdf.makeParas(text, style, suppress_empty=suppress_empty_pars)
|
return sco_pdf.makeParas(text, style, suppress_empty=suppress_empty_pars)
|
||||||
|
|
||||||
|
|
||||||
def get_formsemestre_bulletins_pdf(formsemestre_id, REQUEST, version="selectedevals"):
|
def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
||||||
"document pdf et filename"
|
"document pdf et filename"
|
||||||
from app.scodoc import sco_bulletins
|
from app.scodoc import sco_bulletins
|
||||||
|
|
||||||
|
@ -184,7 +190,6 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, REQUEST, version="selectedev
|
||||||
etudid,
|
etudid,
|
||||||
format="pdfpart",
|
format="pdfpart",
|
||||||
version=version,
|
version=version,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
fragments += frag
|
fragments += frag
|
||||||
filigrannes[i] = filigranne
|
filigrannes[i] = filigranne
|
||||||
|
@ -192,8 +197,8 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, REQUEST, version="selectedev
|
||||||
i = i + 1
|
i = i + 1
|
||||||
#
|
#
|
||||||
infos = {"DeptName": sco_preferences.get_preference("DeptName", formsemestre_id)}
|
infos = {"DeptName": sco_preferences.get_preference("DeptName", formsemestre_id)}
|
||||||
if REQUEST:
|
if request:
|
||||||
server_name = REQUEST.BASE0
|
server_name = request.url_root
|
||||||
else:
|
else:
|
||||||
server_name = ""
|
server_name = ""
|
||||||
try:
|
try:
|
||||||
|
@ -220,7 +225,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, REQUEST, version="selectedev
|
||||||
return pdfdoc, filename
|
return pdfdoc, filename
|
||||||
|
|
||||||
|
|
||||||
def get_etud_bulletins_pdf(etudid, REQUEST, version="selectedevals"):
|
def get_etud_bulletins_pdf(etudid, version="selectedevals"):
|
||||||
"Bulletins pdf de tous les semestres de l'étudiant, et filename"
|
"Bulletins pdf de tous les semestres de l'étudiant, et filename"
|
||||||
from app.scodoc import sco_bulletins
|
from app.scodoc import sco_bulletins
|
||||||
|
|
||||||
|
@ -235,15 +240,14 @@ def get_etud_bulletins_pdf(etudid, REQUEST, version="selectedevals"):
|
||||||
etudid,
|
etudid,
|
||||||
format="pdfpart",
|
format="pdfpart",
|
||||||
version=version,
|
version=version,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
fragments += frag
|
fragments += frag
|
||||||
filigrannes[i] = filigranne
|
filigrannes[i] = filigranne
|
||||||
bookmarks[i] = sem["session_id"] # eg RT-DUT-FI-S1-2015
|
bookmarks[i] = sem["session_id"] # eg RT-DUT-FI-S1-2015
|
||||||
i = i + 1
|
i = i + 1
|
||||||
infos = {"DeptName": sco_preferences.get_preference("DeptName")}
|
infos = {"DeptName": sco_preferences.get_preference("DeptName")}
|
||||||
if REQUEST:
|
if request:
|
||||||
server_name = REQUEST.BASE0
|
server_name = request.url_root
|
||||||
else:
|
else:
|
||||||
server_name = ""
|
server_name = ""
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -56,7 +56,7 @@ et sur page "réglages bulletin" (avec formsemestre_id)
|
||||||
# import os
|
# import os
|
||||||
|
|
||||||
|
|
||||||
# def form_change_bul_sig(side, formsemestre_id=None, REQUEST=None):
|
# def form_change_bul_sig(side, formsemestre_id=None):
|
||||||
# """Change pdf signature"""
|
# """Change pdf signature"""
|
||||||
# filename = _get_sig_existing_filename(
|
# filename = _get_sig_existing_filename(
|
||||||
# side, formsemestre_id=formsemestre_id
|
# side, formsemestre_id=formsemestre_id
|
||||||
|
@ -69,7 +69,7 @@ et sur page "réglages bulletin" (avec formsemestre_id)
|
||||||
# raise ValueError("invalid value for 'side' parameter")
|
# raise ValueError("invalid value for 'side' parameter")
|
||||||
# signatureloc = get_bul_sig_img()
|
# signatureloc = get_bul_sig_img()
|
||||||
# H = [
|
# H = [
|
||||||
# self.sco_header(REQUEST, page_title="Changement de signature"),
|
# self.sco_header(page_title="Changement de signature"),
|
||||||
# """<h2>Changement de la signature bulletin de %(sidetxt)s</h2>
|
# """<h2>Changement de la signature bulletin de %(sidetxt)s</h2>
|
||||||
# """
|
# """
|
||||||
# % (sidetxt,),
|
# % (sidetxt,),
|
||||||
|
|
|
@ -69,16 +69,13 @@ def make_xml_formsemestre_bulletinetud(
|
||||||
doc=None, # XML document
|
doc=None, # XML document
|
||||||
force_publishing=False,
|
force_publishing=False,
|
||||||
xml_nodate=False,
|
xml_nodate=False,
|
||||||
REQUEST=None,
|
|
||||||
xml_with_decisions=False, # inclue les decisions même si non publiées
|
xml_with_decisions=False, # inclue les decisions même si non publiées
|
||||||
version="long",
|
version="long",
|
||||||
):
|
) -> str:
|
||||||
"bulletin au format XML"
|
"bulletin au format XML"
|
||||||
from app.scodoc import sco_bulletins
|
from app.scodoc import sco_bulletins
|
||||||
|
|
||||||
log("xml_bulletin( formsemestre_id=%s, etudid=%s )" % (formsemestre_id, etudid))
|
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)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
if (not sem["bul_hide_xml"]) or force_publishing:
|
if (not sem["bul_hide_xml"]) or force_publishing:
|
||||||
|
@ -388,7 +385,7 @@ def make_xml_formsemestre_bulletinetud(
|
||||||
"decisions_ue"
|
"decisions_ue"
|
||||||
]: # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): always publish (car utile pour export Apogee)
|
]: # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): always publish (car utile pour export Apogee)
|
||||||
for ue_id in decision["decisions_ue"].keys():
|
for ue_id in decision["decisions_ue"].keys():
|
||||||
ue = sco_edit_ue.do_ue_list({"ue_id": ue_id})[0]
|
ue = sco_edit_ue.ue_list({"ue_id": ue_id})[0]
|
||||||
doc.append(
|
doc.append(
|
||||||
Element(
|
Element(
|
||||||
"decision_ue",
|
"decision_ue",
|
||||||
|
|
|
@ -46,9 +46,9 @@
|
||||||
# sco_cache.NotesTableCache.delete_many(formsemestre_id_list)
|
# sco_cache.NotesTableCache.delete_many(formsemestre_id_list)
|
||||||
#
|
#
|
||||||
# Bulletins PDF:
|
# Bulletins PDF:
|
||||||
# sco_cache.PDFBulCache.get(formsemestre_id, version)
|
# sco_cache.SemBulletinsPDFCache.get(formsemestre_id, version)
|
||||||
# sco_cache.PDFBulCache.set(formsemestre_id, version, filename, pdfdoc)
|
# sco_cache.SemBulletinsPDFCache.set(formsemestre_id, version, filename, pdfdoc)
|
||||||
# sco_cache.PDFBulCache.delete(formsemestre_id) suppr. toutes les versions
|
# sco_cache.SemBulletinsPDFCache.delete(formsemestre_id) suppr. toutes les versions
|
||||||
|
|
||||||
# Evaluations:
|
# Evaluations:
|
||||||
# sco_cache.EvaluationCache.get(evaluation_id), set(evaluation_id, value), delete(evaluation_id),
|
# sco_cache.EvaluationCache.get(evaluation_id), set(evaluation_id, value), delete(evaluation_id),
|
||||||
|
@ -157,7 +157,7 @@ class EvaluationCache(ScoDocCache):
|
||||||
|
|
||||||
class AbsSemEtudCache(ScoDocCache):
|
class AbsSemEtudCache(ScoDocCache):
|
||||||
"""Cache pour les comptes d'absences d'un étudiant dans un semestre.
|
"""Cache pour les comptes d'absences d'un étudiant dans un semestre.
|
||||||
Ce cache étant indépendant des semestre, le compte peut être faux lorsqu'on
|
Ce cache étant indépendant des semestres, le compte peut être faux lorsqu'on
|
||||||
change les dates début/fin d'un semestre.
|
change les dates début/fin d'un semestre.
|
||||||
C'est pourquoi il expire après timeout secondes.
|
C'est pourquoi il expire après timeout secondes.
|
||||||
Le timeout evite aussi d'éliminer explicitement ces éléments cachés lors
|
Le timeout evite aussi d'éliminer explicitement ces éléments cachés lors
|
||||||
|
@ -292,7 +292,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
||||||
|
|
||||||
|
|
||||||
class DefferedSemCacheManager:
|
class DefferedSemCacheManager:
|
||||||
"""Experimental: pour effectuer des opérations indépendantes dans la
|
"""Contexte pour effectuer des opérations indépendantes dans la
|
||||||
même requete qui invalident le cache. Par exemple, quand on inscrit
|
même requete qui invalident le cache. Par exemple, quand on inscrit
|
||||||
des étudiants un par un à un semestre, chaque inscription va invalider
|
des étudiants un par un à un semestre, chaque inscription va invalider
|
||||||
le cache, et la suivante va le reconstruire... pour l'invalider juste après.
|
le cache, et la suivante va le reconstruire... pour l'invalider juste après.
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"""Semestres: Codes gestion parcours (constantes)
|
"""Semestres: Codes gestion parcours (constantes)
|
||||||
"""
|
"""
|
||||||
import collections
|
import collections
|
||||||
from six.moves import range
|
from app import log
|
||||||
|
|
||||||
NOTES_TOLERANCE = 0.00499999999999 # si note >= (BARRE-TOLERANCE), considere ok
|
NOTES_TOLERANCE = 0.00499999999999 # si note >= (BARRE-TOLERANCE), considere ok
|
||||||
# (permet d'eviter d'afficher 10.00 sous barre alors que la moyenne vaut 9.999)
|
# (permet d'eviter d'afficher 10.00 sous barre alors que la moyenne vaut 9.999)
|
||||||
|
@ -44,6 +44,7 @@ UE_STAGE_LP = 2 # ue "projet tuteuré et stage" dans les Lic. Pro.
|
||||||
UE_STAGE_10 = 3 # ue "stage" avec moyenne requise > 10
|
UE_STAGE_10 = 3 # ue "stage" avec moyenne requise > 10
|
||||||
UE_ELECTIVE = 4 # UE "élective" dans certains parcours (UCAC?, ISCID)
|
UE_ELECTIVE = 4 # UE "élective" dans certains parcours (UCAC?, ISCID)
|
||||||
UE_PROFESSIONNELLE = 5 # UE "professionnelle" (ISCID, ...)
|
UE_PROFESSIONNELLE = 5 # UE "professionnelle" (ISCID, ...)
|
||||||
|
UE_OPTIONNELLE = 6 # UE non fondamentales (ILEPS, ...)
|
||||||
|
|
||||||
|
|
||||||
def UE_is_fondamentale(ue_type):
|
def UE_is_fondamentale(ue_type):
|
||||||
|
@ -62,7 +63,8 @@ UE_TYPE_NAME = {
|
||||||
UE_STAGE_LP: "Projet tuteuré et stage (Lic. Pro.)",
|
UE_STAGE_LP: "Projet tuteuré et stage (Lic. Pro.)",
|
||||||
UE_STAGE_10: "Stage (moyenne min. 10/20)",
|
UE_STAGE_10: "Stage (moyenne min. 10/20)",
|
||||||
UE_ELECTIVE: "Elective (ISCID)",
|
UE_ELECTIVE: "Elective (ISCID)",
|
||||||
UE_PROFESSIONNELLE: "Professionnelle (ISCID)"
|
UE_PROFESSIONNELLE: "Professionnelle (ISCID)",
|
||||||
|
UE_OPTIONNELLE: "Optionnelle",
|
||||||
# UE_FONDAMENTALE : '"Fondamentale" (eg UCAC)',
|
# UE_FONDAMENTALE : '"Fondamentale" (eg UCAC)',
|
||||||
# UE_OPTIONNELLE : '"Optionnelle" (UCAC)'
|
# UE_OPTIONNELLE : '"Optionnelle" (UCAC)'
|
||||||
}
|
}
|
||||||
|
@ -520,6 +522,34 @@ class ParcoursMasterISCID4(ParcoursISCID):
|
||||||
register_parcours(ParcoursMasterISCID4())
|
register_parcours(ParcoursMasterISCID4())
|
||||||
|
|
||||||
|
|
||||||
|
class ParcoursILEPS(TypeParcours):
|
||||||
|
"""Superclasse pour les parcours de l'ILEPS"""
|
||||||
|
|
||||||
|
# SESSION_NAME = "année"
|
||||||
|
# SESSION_NAME_A = "de l'"
|
||||||
|
# SESSION_ABBRV = 'A' # A1, A2, ...
|
||||||
|
COMPENSATION_UE = False
|
||||||
|
UNUSED_CODES = set((ADC, ATT, ATB, ATJ))
|
||||||
|
ALLOWED_UE_TYPES = [UE_STANDARD, UE_OPTIONNELLE]
|
||||||
|
# Barre moy gen. pour validation semestre:
|
||||||
|
BARRE_MOY = 10.0
|
||||||
|
# Barre pour UE ILEPS: 8/20 pour UE standards ("fondamentales")
|
||||||
|
# et pas de barre (-1.) pour UE élective.
|
||||||
|
BARRE_UE = {UE_STANDARD: 8.0, UE_OPTIONNELLE: 0.0}
|
||||||
|
BARRE_UE_DEFAULT = 0.0 # pas de barre sur les autres UE
|
||||||
|
|
||||||
|
|
||||||
|
class ParcoursLicenceILEPS6(ParcoursILEPS):
|
||||||
|
"""ILEPS: Licence 6 semestres"""
|
||||||
|
|
||||||
|
TYPE_PARCOURS = 1010
|
||||||
|
NAME = "LicenceILEPS6"
|
||||||
|
NB_SEM = 6
|
||||||
|
|
||||||
|
|
||||||
|
register_parcours(ParcoursLicenceILEPS6())
|
||||||
|
|
||||||
|
|
||||||
class ParcoursUCAC(TypeParcours):
|
class ParcoursUCAC(TypeParcours):
|
||||||
"""Règles de validation UCAC"""
|
"""Règles de validation UCAC"""
|
||||||
|
|
||||||
|
@ -673,4 +703,9 @@ FORMATION_PARCOURS_TYPES = [p[0] for p in _tp] # codes numeriques (TYPE_PARCOUR
|
||||||
|
|
||||||
|
|
||||||
def get_parcours_from_code(code_parcours):
|
def get_parcours_from_code(code_parcours):
|
||||||
return TYPES_PARCOURS[code_parcours]
|
parcours = TYPES_PARCOURS.get(code_parcours)
|
||||||
|
if parcours is None:
|
||||||
|
log(f"Warning: invalid code_parcours: {code_parcours}")
|
||||||
|
# default to legacy
|
||||||
|
parcours = TYPES_PARCOURS.get(0)
|
||||||
|
return parcours
|
||||||
|
|
|
@ -27,10 +27,10 @@
|
||||||
|
|
||||||
"""Calcul des moyennes de module
|
"""Calcul des moyennes de module
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import traceback
|
|
||||||
import pprint
|
import pprint
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from flask import url_for, g
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc.sco_utils import (
|
from app.scodoc.sco_utils import (
|
||||||
|
@ -40,7 +40,7 @@ from app.scodoc.sco_utils import (
|
||||||
EVALUATION_RATTRAPAGE,
|
EVALUATION_RATTRAPAGE,
|
||||||
EVALUATION_SESSION2,
|
EVALUATION_SESSION2,
|
||||||
)
|
)
|
||||||
from app.scodoc.sco_exceptions import ScoException
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_abs
|
||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
|
@ -65,7 +65,8 @@ def moduleimpl_has_expression(mod):
|
||||||
|
|
||||||
def formsemestre_expressions_use_abscounts(formsemestre_id):
|
def formsemestre_expressions_use_abscounts(formsemestre_id):
|
||||||
"""True si les notes de ce semestre dépendent des compteurs d'absences.
|
"""True si les notes de ce semestre dépendent des compteurs d'absences.
|
||||||
Cela n'est normalement pas le cas, sauf si des formules utilisateur utilisent ces compteurs.
|
Cela n'est normalement pas le cas, sauf si des formules utilisateur
|
||||||
|
utilisent ces compteurs.
|
||||||
"""
|
"""
|
||||||
# check presence of 'nbabs' in expressions
|
# check presence of 'nbabs' in expressions
|
||||||
ab = "nb_abs" # chaine recherchée
|
ab = "nb_abs" # chaine recherchée
|
||||||
|
@ -79,7 +80,7 @@ def formsemestre_expressions_use_abscounts(formsemestre_id):
|
||||||
if expr and expr[0] != "#" and ab in expr:
|
if expr and expr[0] != "#" and ab in expr:
|
||||||
return True
|
return True
|
||||||
# 2- moyennes de modules
|
# 2- moyennes de modules
|
||||||
for mod in sco_moduleimpl.do_moduleimpl_list(formsemestre_id=formsemestre_id):
|
for mod in sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id):
|
||||||
if moduleimpl_has_expression(mod) and ab in mod["computation_expr"]:
|
if moduleimpl_has_expression(mod) and ab in mod["computation_expr"]:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -128,7 +129,7 @@ def compute_user_formula(
|
||||||
coefs,
|
coefs,
|
||||||
coefs_mask,
|
coefs_mask,
|
||||||
formula,
|
formula,
|
||||||
diag_info={}, # infos supplementaires a placer ds messages d'erreur
|
diag_info=None, # infos supplementaires a placer ds messages d'erreur
|
||||||
use_abs=True,
|
use_abs=True,
|
||||||
):
|
):
|
||||||
"""Calcul moyenne a partir des notes et coefs, en utilisant la formule utilisateur (une chaine).
|
"""Calcul moyenne a partir des notes et coefs, en utilisant la formule utilisateur (une chaine).
|
||||||
|
@ -159,14 +160,19 @@ def compute_user_formula(
|
||||||
# log('expression : %s\nvariables=%s\n' % (formula, variables)) # debug
|
# log('expression : %s\nvariables=%s\n' % (formula, variables)) # debug
|
||||||
user_moy = sco_formulas.eval_user_expression(formula, variables)
|
user_moy = sco_formulas.eval_user_expression(formula, variables)
|
||||||
# log('user_moy=%s' % user_moy)
|
# log('user_moy=%s' % user_moy)
|
||||||
if user_moy != "NA0" and user_moy != "NA":
|
if user_moy != "NA":
|
||||||
user_moy = float(user_moy)
|
user_moy = float(user_moy)
|
||||||
if (user_moy > 20) or (user_moy < 0):
|
if (user_moy > 20) or (user_moy < 0):
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
|
|
||||||
raise ScoException(
|
raise ScoValueError(
|
||||||
"""valeur moyenne %s hors limite pour <a href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s">%s</a>"""
|
f"""
|
||||||
% (user_moy, sem["formsemestre_id"], etudid, etud["nomprenom"])
|
Valeur moyenne {user_moy} hors limite pour
|
||||||
|
<a href="{url_for('notes.formsemestre_bulletinetud',
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=sem["formsemestre_id"],
|
||||||
|
etudid=etudid
|
||||||
|
)}">{etud["nomprenom"]}</a>"""
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
log(
|
log(
|
||||||
|
@ -183,7 +189,7 @@ def compute_user_formula(
|
||||||
return user_moy
|
return user_moy
|
||||||
|
|
||||||
|
|
||||||
def do_moduleimpl_moyennes(nt, mod):
|
def compute_moduleimpl_moyennes(nt, modimpl):
|
||||||
"""Retourne dict { etudid : note_moyenne } pour tous les etuds inscrits
|
"""Retourne dict { etudid : note_moyenne } pour tous les etuds inscrits
|
||||||
au moduleimpl mod, la liste des evaluations "valides" (toutes notes entrées
|
au moduleimpl mod, la liste des evaluations "valides" (toutes notes entrées
|
||||||
ou en attente), et att (vrai s'il y a des notes en attente dans ce module).
|
ou en attente), et att (vrai s'il y a des notes en attente dans ce module).
|
||||||
|
@ -193,13 +199,13 @@ def do_moduleimpl_moyennes(nt, mod):
|
||||||
S'il manque des notes et que le coef n'est pas nul,
|
S'il manque des notes et que le coef n'est pas nul,
|
||||||
la moyenne n'est pas calculée: NA
|
la moyenne n'est pas calculée: NA
|
||||||
Ne prend en compte que les evaluations où toutes les notes sont entrées.
|
Ne prend en compte que les evaluations où toutes les notes sont entrées.
|
||||||
Le résultat est une note sur 20.
|
Le résultat note_moyenne est une note sur 20.
|
||||||
"""
|
"""
|
||||||
diag_info = {} # message d'erreur formule
|
diag_info = {} # message d'erreur formule
|
||||||
moduleimpl_id = mod["moduleimpl_id"]
|
moduleimpl_id = modimpl["moduleimpl_id"]
|
||||||
is_malus = mod["module"]["module_type"] == scu.MODULE_MALUS
|
is_malus = modimpl["module"]["module_type"] == scu.MODULE_MALUS
|
||||||
sem = sco_formsemestre.get_formsemestre(mod["formsemestre_id"])
|
sem = sco_formsemestre.get_formsemestre(modimpl["formsemestre_id"])
|
||||||
etudids = sco_moduleimpl.do_moduleimpl_listeetuds(
|
etudids = sco_moduleimpl.moduleimpl_listeetuds(
|
||||||
moduleimpl_id
|
moduleimpl_id
|
||||||
) # tous, y compris demissions
|
) # tous, y compris demissions
|
||||||
# Inscrits au semestre (pour traiter les demissions):
|
# Inscrits au semestre (pour traiter les demissions):
|
||||||
|
@ -207,7 +213,7 @@ def do_moduleimpl_moyennes(nt, mod):
|
||||||
[
|
[
|
||||||
x["etudid"]
|
x["etudid"]
|
||||||
for x in sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
|
for x in sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
|
||||||
mod["formsemestre_id"]
|
modimpl["formsemestre_id"]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -218,7 +224,7 @@ def do_moduleimpl_moyennes(nt, mod):
|
||||||
key=lambda x: (x["numero"], x["jour"], x["heure_debut"])
|
key=lambda x: (x["numero"], x["jour"], x["heure_debut"])
|
||||||
) # la plus ancienne en tête
|
) # la plus ancienne en tête
|
||||||
|
|
||||||
user_expr = moduleimpl_has_expression(mod)
|
user_expr = moduleimpl_has_expression(modimpl)
|
||||||
attente = False
|
attente = False
|
||||||
# recupere les notes de toutes les evaluations
|
# recupere les notes de toutes les evaluations
|
||||||
eval_rattr = None
|
eval_rattr = None
|
||||||
|
@ -268,7 +274,7 @@ def do_moduleimpl_moyennes(nt, mod):
|
||||||
]
|
]
|
||||||
#
|
#
|
||||||
R = {}
|
R = {}
|
||||||
formula = scu.unescape_html(mod["computation_expr"])
|
formula = scu.unescape_html(modimpl["computation_expr"])
|
||||||
formula_use_abs = "abs" in formula
|
formula_use_abs = "abs" in formula
|
||||||
|
|
||||||
for etudid in insmod_set: # inscrits au semestre et au module
|
for etudid in insmod_set: # inscrits au semestre et au module
|
||||||
|
@ -289,15 +295,17 @@ def do_moduleimpl_moyennes(nt, mod):
|
||||||
# il manque une note ! (si publish_incomplete, cela peut arriver, on ignore)
|
# il manque une note ! (si publish_incomplete, cela peut arriver, on ignore)
|
||||||
if e["coefficient"] > 0 and not e["publish_incomplete"]:
|
if e["coefficient"] > 0 and not e["publish_incomplete"]:
|
||||||
nb_missing += 1
|
nb_missing += 1
|
||||||
|
# ne devrait pas arriver ?
|
||||||
|
log("\nXXX SCM298\n")
|
||||||
if nb_missing == 0 and sum_coefs > 0:
|
if nb_missing == 0 and sum_coefs > 0:
|
||||||
if sum_coefs > 0:
|
if sum_coefs > 0:
|
||||||
R[etudid] = sum_notes / sum_coefs
|
R[etudid] = sum_notes / sum_coefs
|
||||||
moy_valid = True
|
moy_valid = True
|
||||||
else:
|
else:
|
||||||
R[etudid] = "na"
|
R[etudid] = "NA"
|
||||||
moy_valid = False
|
moy_valid = False
|
||||||
else:
|
else:
|
||||||
R[etudid] = "NA%d" % nb_missing
|
R[etudid] = "NA"
|
||||||
moy_valid = False
|
moy_valid = False
|
||||||
|
|
||||||
if user_expr:
|
if user_expr:
|
||||||
|
@ -348,14 +356,14 @@ def do_moduleimpl_moyennes(nt, mod):
|
||||||
if etudid in eval_rattr["notes"]:
|
if etudid in eval_rattr["notes"]:
|
||||||
note = eval_rattr["notes"][etudid]["value"]
|
note = eval_rattr["notes"][etudid]["value"]
|
||||||
if note != None and note != NOTES_NEUTRALISE and note != NOTES_ATTENTE:
|
if note != None and note != NOTES_NEUTRALISE and note != NOTES_ATTENTE:
|
||||||
if isinstance(R[etudid], float):
|
if not isinstance(R[etudid], float):
|
||||||
R[etudid] = note
|
R[etudid] = note
|
||||||
else:
|
else:
|
||||||
note_sur_20 = note * 20.0 / eval_rattr["note_max"]
|
note_sur_20 = note * 20.0 / eval_rattr["note_max"]
|
||||||
if eval_rattr["evaluation_type"] == EVALUATION_RATTRAPAGE:
|
if eval_rattr["evaluation_type"] == EVALUATION_RATTRAPAGE:
|
||||||
# rattrapage classique: prend la meilleure note entre moyenne
|
# rattrapage classique: prend la meilleure note entre moyenne
|
||||||
# module et note eval rattrapage
|
# module et note eval rattrapage
|
||||||
if (R[etudid] == "NA0") or (note_sur_20 > R[etudid]):
|
if (R[etudid] == "NA") or (note_sur_20 > R[etudid]):
|
||||||
# log('note_sur_20=%s' % note_sur_20)
|
# log('note_sur_20=%s' % note_sur_20)
|
||||||
R[etudid] = note_sur_20
|
R[etudid] = note_sur_20
|
||||||
elif eval_rattr["evaluation_type"] == EVALUATION_SESSION2:
|
elif eval_rattr["evaluation_type"] == EVALUATION_SESSION2:
|
||||||
|
@ -365,7 +373,7 @@ def do_moduleimpl_moyennes(nt, mod):
|
||||||
return R, valid_evals, attente, diag_info
|
return R, valid_evals, attente, diag_info
|
||||||
|
|
||||||
|
|
||||||
def do_formsemestre_moyennes(nt, formsemestre_id):
|
def formsemestre_compute_modimpls_moyennes(nt, formsemestre_id):
|
||||||
"""retourne dict { moduleimpl_id : { etudid, note_moyenne_dans_ce_module } },
|
"""retourne dict { moduleimpl_id : { etudid, note_moyenne_dans_ce_module } },
|
||||||
la liste des moduleimpls, la liste des evaluations valides,
|
la liste des moduleimpls, la liste des evaluations valides,
|
||||||
liste des moduleimpls avec notes en attente.
|
liste des moduleimpls avec notes en attente.
|
||||||
|
@ -375,7 +383,7 @@ def do_formsemestre_moyennes(nt, formsemestre_id):
|
||||||
# args={"formsemestre_id": formsemestre_id}
|
# args={"formsemestre_id": formsemestre_id}
|
||||||
# )
|
# )
|
||||||
# etudids = [x["etudid"] for x in inscr]
|
# etudids = [x["etudid"] for x in inscr]
|
||||||
modimpls = sco_moduleimpl.do_moduleimpl_list(formsemestre_id=formsemestre_id)
|
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
# recupere les moyennes des etudiants de tous les modules
|
# recupere les moyennes des etudiants de tous les modules
|
||||||
D = {}
|
D = {}
|
||||||
valid_evals = []
|
valid_evals = []
|
||||||
|
@ -383,15 +391,16 @@ def do_formsemestre_moyennes(nt, formsemestre_id):
|
||||||
mods_att = []
|
mods_att = []
|
||||||
expr_diags = []
|
expr_diags = []
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
mod = sco_edit_module.do_module_list(args={"module_id": modimpl["module_id"]})[
|
mod = sco_edit_module.module_list(args={"module_id": modimpl["module_id"]})[0]
|
||||||
0
|
|
||||||
]
|
|
||||||
modimpl["module"] = mod # add module dict to moduleimpl (used by nt)
|
modimpl["module"] = mod # add module dict to moduleimpl (used by nt)
|
||||||
moduleimpl_id = modimpl["moduleimpl_id"]
|
moduleimpl_id = modimpl["moduleimpl_id"]
|
||||||
assert moduleimpl_id not in D
|
assert moduleimpl_id not in D
|
||||||
D[moduleimpl_id], valid_evals_mod, attente, expr_diag = do_moduleimpl_moyennes(
|
(
|
||||||
nt, modimpl
|
D[moduleimpl_id],
|
||||||
)
|
valid_evals_mod,
|
||||||
|
attente,
|
||||||
|
expr_diag,
|
||||||
|
) = compute_moduleimpl_moyennes(nt, modimpl)
|
||||||
valid_evals_per_mod[moduleimpl_id] = valid_evals_mod
|
valid_evals_per_mod[moduleimpl_id] = valid_evals_mod
|
||||||
valid_evals += valid_evals_mod
|
valid_evals += valid_evals_mod
|
||||||
if attente:
|
if attente:
|
||||||
|
|
181
app/scodoc/sco_config_actions.py
Normal file
181
app/scodoc/sco_config_actions.py
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# ScoDoc
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
Module main: page d'accueil, avec liste des départements
|
||||||
|
|
||||||
|
Emmanuel Viennet, 2021
|
||||||
|
"""
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
|
from app.scodoc.sco_logos import write_logo, find_logo, delete_logo
|
||||||
|
import app
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
|
||||||
|
class Action:
|
||||||
|
"""Base class for all classes describing an action from from config form."""
|
||||||
|
|
||||||
|
def __init__(self, message, parameters):
|
||||||
|
self.message = message
|
||||||
|
self.parameters = parameters
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_action(parameters, stream=None):
|
||||||
|
"""Check (from parameters) if some action has to be done and
|
||||||
|
then return list of action (or else return empty list)."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def display(self):
|
||||||
|
"""return a str describing the action to be done"""
|
||||||
|
return self.message.format_map(self.parameters)
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
"""Executes the action"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
GLOBAL = "_"
|
||||||
|
|
||||||
|
|
||||||
|
class LogoUpdate(Action):
|
||||||
|
"""Action: change a logo
|
||||||
|
dept_id: dept_id or '_',
|
||||||
|
logo_id: logo_id,
|
||||||
|
upload: image file replacement
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parameters):
|
||||||
|
super().__init__(
|
||||||
|
f"Modification du logo {parameters['logo_id']} pour le département {parameters['dept_id']}",
|
||||||
|
parameters,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_action(parameters):
|
||||||
|
dept_id = parameters["dept_key"]
|
||||||
|
if dept_id == GLOBAL:
|
||||||
|
dept_id = None
|
||||||
|
parameters["dept_id"] = dept_id
|
||||||
|
if parameters["upload"] is not None:
|
||||||
|
return LogoUpdate(parameters)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
current_app.logger.info(self.message)
|
||||||
|
write_logo(
|
||||||
|
stream=self.parameters["upload"],
|
||||||
|
dept_id=self.parameters["dept_id"],
|
||||||
|
name=self.parameters["logo_id"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LogoDelete(Action):
|
||||||
|
"""Action: Delete an existing logo
|
||||||
|
dept_id: dept_id or '_',
|
||||||
|
logo_id: logo_id
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parameters):
|
||||||
|
super().__init__(
|
||||||
|
f"Suppression du logo {parameters['logo_id']} pour le département {parameters['dept_id']}.",
|
||||||
|
parameters,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_action(parameters):
|
||||||
|
parameters["dept_id"] = parameters["dept_key"]
|
||||||
|
if parameters["dept_key"] == GLOBAL:
|
||||||
|
parameters["dept_id"] = None
|
||||||
|
if parameters["do_delete"]:
|
||||||
|
return LogoDelete(parameters)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
current_app.logger.info(self.message)
|
||||||
|
delete_logo(name=self.parameters["logo_id"], dept_id=self.parameters["dept_id"])
|
||||||
|
|
||||||
|
|
||||||
|
class LogoInsert(Action):
|
||||||
|
"""Action: add a new logo
|
||||||
|
dept_key: dept_id or '_',
|
||||||
|
logo_id: logo_id,
|
||||||
|
upload: image file replacement
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parameters):
|
||||||
|
super().__init__(
|
||||||
|
f"Ajout du logo {parameters['name']} pour le département {parameters['dept_key']} ({parameters['upload']}).",
|
||||||
|
parameters,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_action(parameters):
|
||||||
|
if parameters["dept_key"] == GLOBAL:
|
||||||
|
parameters["dept_id"] = None
|
||||||
|
if parameters["upload"] and parameters["name"]:
|
||||||
|
logo = find_logo(
|
||||||
|
logoname=parameters["name"], dept_id=parameters["dept_key"]
|
||||||
|
)
|
||||||
|
if logo is None:
|
||||||
|
return LogoInsert(parameters)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
dept_id = self.parameters["dept_key"]
|
||||||
|
if dept_id == GLOBAL:
|
||||||
|
dept_id = None
|
||||||
|
current_app.logger.info(self.message)
|
||||||
|
write_logo(
|
||||||
|
stream=self.parameters["upload"],
|
||||||
|
name=self.parameters["name"],
|
||||||
|
dept_id=dept_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BonusSportUpdate(Action):
|
||||||
|
"""Action: Change bonus_sport_function_name.
|
||||||
|
bonus_sport_function_name: the new value"""
|
||||||
|
|
||||||
|
def __init__(self, parameters):
|
||||||
|
super().__init__(
|
||||||
|
f"Changement du calcul de bonus sport pour ({parameters['bonus_sport_func_name']}).",
|
||||||
|
parameters,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_action(parameters):
|
||||||
|
if (
|
||||||
|
parameters["bonus_sport_func_name"]
|
||||||
|
!= ScoDocSiteConfig.get_bonus_sport_func_name()
|
||||||
|
):
|
||||||
|
return [BonusSportUpdate(parameters)]
|
||||||
|
return []
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
current_app.logger.info(self.message)
|
||||||
|
ScoDocSiteConfig.set_bonus_sport_func(self.parameters["bonus_sport_func_name"])
|
||||||
|
app.clear_scodoc_cache()
|
402
app/scodoc/sco_config_form.py
Normal file
402
app/scodoc/sco_config_form.py
Normal file
|
@ -0,0 +1,402 @@
|
||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# ScoDoc
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
Module main: page d'accueil, avec liste des départements
|
||||||
|
|
||||||
|
Emmanuel Viennet, 2021
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
from flask import flash, url_for, redirect, render_template
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_wtf.file import FileField, FileAllowed
|
||||||
|
from wtforms import SelectField, SubmitField, FormField, validators, FieldList
|
||||||
|
from wtforms.fields.simple import BooleanField, StringField, HiddenField
|
||||||
|
|
||||||
|
from app import AccessDenied
|
||||||
|
from app.models import Departement
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
|
from app.scodoc import sco_logos, html_sco_header
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.scodoc.sco_config_actions import (
|
||||||
|
LogoDelete,
|
||||||
|
LogoUpdate,
|
||||||
|
LogoInsert,
|
||||||
|
BonusSportUpdate,
|
||||||
|
)
|
||||||
|
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from app.scodoc.sco_logos import find_logo
|
||||||
|
|
||||||
|
JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + []
|
||||||
|
|
||||||
|
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
||||||
|
|
||||||
|
# class ItemForm(FlaskForm):
|
||||||
|
# """Unused Generic class to document common behavior for classes
|
||||||
|
# * ScoConfigurationForm
|
||||||
|
# * DeptForm
|
||||||
|
# * LogoForm
|
||||||
|
# Some or all of these implements:
|
||||||
|
# * Composite design pattern (ScoConfigurationForm and DeptForm)
|
||||||
|
# - a FieldList(FormField(ItemForm))
|
||||||
|
# - FieldListItem are created by browsing the model
|
||||||
|
# - index dictionnary to provide direct access to a SubItemForm
|
||||||
|
# - the direct access method (get_form)
|
||||||
|
# * have some information added to be displayed
|
||||||
|
# - information are collected from a model object
|
||||||
|
# Common methods:
|
||||||
|
# * build(model) (not for LogoForm who has no child)
|
||||||
|
# for each child:
|
||||||
|
# * create en entry in the FieldList for each subitem found
|
||||||
|
# * update self.index
|
||||||
|
# * fill_in additional information into the form
|
||||||
|
# * recursively calls build for each chid
|
||||||
|
# some spécific information may be added after standard processing
|
||||||
|
# (typically header/footer description)
|
||||||
|
# * preview(data)
|
||||||
|
# check the data from a post and build a list of operations that has to be done.
|
||||||
|
# for a two phase process:
|
||||||
|
# * phase 1 (list all opérations)
|
||||||
|
# * phase 2 (may be confirmation and execure)
|
||||||
|
# - if no op found: return to the form with a message 'Aucune modification trouvée'
|
||||||
|
# - only one operation found: execute and go to main page
|
||||||
|
# - more than 1 operation found. asked form confirmation (and execution if confirmed)
|
||||||
|
#
|
||||||
|
# Someday we'll have time to refactor as abstract classes but Abstract FieldList makes this a bit complicated
|
||||||
|
# """
|
||||||
|
|
||||||
|
# Terminology:
|
||||||
|
# dept_id : identifies a dept in modele (= list_logos()). None designates globals logos
|
||||||
|
# dept_key : identifies a dept in this form only (..index[dept_key], and fields 'dept_key').
|
||||||
|
# 'GLOBAL' designates globals logos (we need a string value to set up HiddenField
|
||||||
|
GLOBAL = "_"
|
||||||
|
|
||||||
|
|
||||||
|
def dept_id_to_key(dept_id):
|
||||||
|
if dept_id is None:
|
||||||
|
return GLOBAL
|
||||||
|
return dept_id
|
||||||
|
|
||||||
|
|
||||||
|
def dept_key_to_id(dept_key):
|
||||||
|
if dept_key == GLOBAL:
|
||||||
|
return None
|
||||||
|
return dept_key
|
||||||
|
|
||||||
|
|
||||||
|
class AddLogoForm(FlaskForm):
|
||||||
|
"""Formulaire permettant l'ajout d'un logo (dans un département)"""
|
||||||
|
|
||||||
|
dept_key = HiddenField()
|
||||||
|
name = StringField(
|
||||||
|
label="Nom",
|
||||||
|
validators=[
|
||||||
|
validators.regexp(
|
||||||
|
r"^[a-zA-Z0-9-]*$",
|
||||||
|
re.IGNORECASE,
|
||||||
|
"Ne doit comporter que lettres, chiffres ou -",
|
||||||
|
),
|
||||||
|
validators.Length(
|
||||||
|
max=20, message="Un nom ne doit pas dépasser 20 caractères"
|
||||||
|
),
|
||||||
|
validators.required("Nom de logo requis (alphanumériques ou '-')"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
upload = FileField(
|
||||||
|
label="Sélectionner l'image",
|
||||||
|
validators=[
|
||||||
|
FileAllowed(
|
||||||
|
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
||||||
|
f"n'accepte que les fichiers image {', '.join(scu.LOGOS_IMAGES_ALLOWED_TYPES)}",
|
||||||
|
),
|
||||||
|
validators.required("Fichier image manquant"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
do_insert = SubmitField("ajouter une image")
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs["meta"] = {"csrf": False}
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def validate_name(self, name):
|
||||||
|
dept_id = dept_key_to_id(self.dept_key.data)
|
||||||
|
if dept_id == GLOBAL:
|
||||||
|
dept_id = None
|
||||||
|
if find_logo(logoname=name.data, dept_id=dept_id) is not None:
|
||||||
|
raise validators.ValidationError("Un logo de même nom existe déjà")
|
||||||
|
|
||||||
|
def select_action(self):
|
||||||
|
if self.data["do_insert"]:
|
||||||
|
if self.validate():
|
||||||
|
return LogoInsert.build_action(self.data)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class LogoForm(FlaskForm):
|
||||||
|
"""Embed both presentation of a logo (cf. template file configuration.html)
|
||||||
|
and all its data and UI action (change, delete)"""
|
||||||
|
|
||||||
|
dept_key = HiddenField()
|
||||||
|
logo_id = HiddenField()
|
||||||
|
upload = FileField(
|
||||||
|
label="Remplacer l'image",
|
||||||
|
validators=[
|
||||||
|
FileAllowed(
|
||||||
|
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
||||||
|
f"n'accepte que les fichiers image {', '.join(scu.LOGOS_IMAGES_ALLOWED_TYPES)}",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
do_delete = SubmitField("Supprimer l'image")
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs["meta"] = {"csrf": False}
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.logo = find_logo(
|
||||||
|
logoname=self.logo_id.data, dept_id=dept_key_to_id(self.dept_key.data)
|
||||||
|
).select()
|
||||||
|
self.description = None
|
||||||
|
self.titre = None
|
||||||
|
self.can_delete = True
|
||||||
|
if self.dept_key.data == GLOBAL:
|
||||||
|
if self.logo_id.data == "header":
|
||||||
|
self.can_delete = False
|
||||||
|
self.description = ""
|
||||||
|
self.titre = "Logo en-tête"
|
||||||
|
if self.logo_id.data == "footer":
|
||||||
|
self.can_delete = False
|
||||||
|
self.titre = "Logo pied de page"
|
||||||
|
self.description = ""
|
||||||
|
else:
|
||||||
|
if self.logo_id.data == "header":
|
||||||
|
self.description = "Se substitue au header défini au niveau global"
|
||||||
|
self.titre = "Logo en-tête"
|
||||||
|
if self.logo_id.data == "footer":
|
||||||
|
self.description = "Se substitue au footer défini au niveau global"
|
||||||
|
self.titre = "Logo pied de page"
|
||||||
|
|
||||||
|
def select_action(self):
|
||||||
|
if self.do_delete.data and self.can_delete:
|
||||||
|
return LogoDelete.build_action(self.data)
|
||||||
|
if self.upload.data and self.validate():
|
||||||
|
return LogoUpdate.build_action(self.data)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class DeptForm(FlaskForm):
|
||||||
|
dept_key = HiddenField()
|
||||||
|
dept_name = HiddenField()
|
||||||
|
add_logo = FormField(AddLogoForm)
|
||||||
|
logos = FieldList(FormField(LogoForm))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs["meta"] = {"csrf": False}
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def is_local(self):
|
||||||
|
if self.dept_key.data == GLOBAL:
|
||||||
|
return None
|
||||||
|
return True
|
||||||
|
|
||||||
|
def select_action(self):
|
||||||
|
action = self.add_logo.form.select_action()
|
||||||
|
if action:
|
||||||
|
return action
|
||||||
|
for logo_entry in self.logos.entries:
|
||||||
|
logo_form = logo_entry.form
|
||||||
|
action = logo_form.select_action()
|
||||||
|
if action:
|
||||||
|
return action
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_form(self, logoname=None):
|
||||||
|
"""Retourne le formulaire associé à un logo. None si pas trouvé"""
|
||||||
|
if logoname is None: # recherche de département
|
||||||
|
return self
|
||||||
|
return self.index.get(logoname, None)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_dept_id_name():
|
||||||
|
"""Cette section assute que tous les départements sont traités (y compris ceux qu'ont pas de logo au départ)
|
||||||
|
et détermine l'ordre d'affichage des DeptForm (GLOBAL d'abord, puis par ordre alpha de nom de département)
|
||||||
|
-> [ (None, None), (dept_id, dept_name)... ]"""
|
||||||
|
depts = [(None, GLOBAL)]
|
||||||
|
for dept in (
|
||||||
|
Departement.query.filter_by(visible=True).order_by(Departement.acronym).all()
|
||||||
|
):
|
||||||
|
depts.append((dept.id, dept.acronym))
|
||||||
|
return depts
|
||||||
|
|
||||||
|
|
||||||
|
def _ordered_logos(modele):
|
||||||
|
"""sort logoname alphabetically but header and footer moved at start. (since there is no space in logoname)"""
|
||||||
|
|
||||||
|
def sort(name):
|
||||||
|
if name == "header":
|
||||||
|
return " 0"
|
||||||
|
if name == "footer":
|
||||||
|
return " 1"
|
||||||
|
return name
|
||||||
|
|
||||||
|
order = sorted(modele.keys(), key=sort)
|
||||||
|
return order
|
||||||
|
|
||||||
|
|
||||||
|
def _make_dept_data(dept_id, dept_name, modele):
|
||||||
|
dept_key = dept_id_to_key(dept_id)
|
||||||
|
data = {
|
||||||
|
"dept_key": dept_key,
|
||||||
|
"dept_name": dept_name,
|
||||||
|
"add_logo": {"dept_key": dept_key},
|
||||||
|
}
|
||||||
|
logos = []
|
||||||
|
if modele is not None:
|
||||||
|
for name in _ordered_logos(modele):
|
||||||
|
logos.append({"dept_key": dept_key, "logo_id": name})
|
||||||
|
data["logos"] = logos
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _make_depts_data(modele):
|
||||||
|
data = []
|
||||||
|
for dept_id, dept_name in _make_dept_id_name():
|
||||||
|
data.append(
|
||||||
|
_make_dept_data(
|
||||||
|
dept_id=dept_id, dept_name=dept_name, modele=modele.get(dept_id, None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _make_data(bonus_sport, modele):
|
||||||
|
data = {
|
||||||
|
"bonus_sport_func_name": bonus_sport,
|
||||||
|
"depts": _make_depts_data(modele=modele),
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class ScoDocConfigurationForm(FlaskForm):
|
||||||
|
"Panneau de configuration général"
|
||||||
|
bonus_sport_func_name = SelectField(
|
||||||
|
label="Fonction de calcul des bonus sport&culture",
|
||||||
|
choices=[
|
||||||
|
(x, x if x else "Aucune")
|
||||||
|
for x in ScoDocSiteConfig.get_bonus_sport_func_names()
|
||||||
|
],
|
||||||
|
)
|
||||||
|
depts = FieldList(FormField(DeptForm))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# def _set_global_logos_infos(self):
|
||||||
|
# "specific processing for globals items"
|
||||||
|
# global_header = self.get_form(logoname="header")
|
||||||
|
# global_header.description = (
|
||||||
|
# "image placée en haut de certains documents documents PDF."
|
||||||
|
# )
|
||||||
|
# global_header.titre = "Logo en-tête"
|
||||||
|
# global_header.can_delete = False
|
||||||
|
# global_footer = self.get_form(logoname="footer")
|
||||||
|
# global_footer.description = (
|
||||||
|
# "image placée en pied de page de certains documents documents PDF."
|
||||||
|
# )
|
||||||
|
# global_footer.titre = "Logo pied de page"
|
||||||
|
# global_footer.can_delete = False
|
||||||
|
|
||||||
|
# def _build_dept(self, dept_id, dept_name, modele):
|
||||||
|
# dept_key = dept_id or GLOBAL
|
||||||
|
# data = {"dept_key": dept_key}
|
||||||
|
# entry = self.depts.append_entry(data)
|
||||||
|
# entry.form.build(dept_name, modele.get(dept_id, {}))
|
||||||
|
# self.index[str(dept_key)] = entry.form
|
||||||
|
|
||||||
|
# def build(self, modele):
|
||||||
|
# "Build the Form hierachy (DeptForm, LogoForm) and add extra data (from modele)"
|
||||||
|
# # if entries already initialized (POST). keep subforms
|
||||||
|
# self.index = {}
|
||||||
|
# # create entries in FieldList (one entry per dept
|
||||||
|
# for dept_id, dept_name in self.dept_id_name:
|
||||||
|
# self._build_dept(dept_id=dept_id, dept_name=dept_name, modele=modele)
|
||||||
|
# self._set_global_logos_infos()
|
||||||
|
|
||||||
|
def get_form(self, dept_key=GLOBAL, logoname=None):
|
||||||
|
"""Retourne un formulaire:
|
||||||
|
* pour un département (get_form(dept_id)) ou à un logo (get_form(dept_id, logname))
|
||||||
|
* propre à un département (get_form(dept_id, logoname) ou global (get_form(logoname))
|
||||||
|
retourne None si le formulaire cherché ne peut être trouvé
|
||||||
|
"""
|
||||||
|
dept_form = self.index.get(dept_key, None)
|
||||||
|
if dept_form is None: # département non trouvé
|
||||||
|
return None
|
||||||
|
return dept_form.get_form(logoname)
|
||||||
|
|
||||||
|
def select_action(self):
|
||||||
|
if (
|
||||||
|
self.data["bonus_sport_func_name"]
|
||||||
|
!= ScoDocSiteConfig.get_bonus_sport_func_name()
|
||||||
|
):
|
||||||
|
return BonusSportUpdate(self.data)
|
||||||
|
for dept_entry in self.depts:
|
||||||
|
dept_form = dept_entry.form
|
||||||
|
action = dept_form.select_action()
|
||||||
|
if action:
|
||||||
|
return action
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def configuration():
|
||||||
|
"""Panneau de configuration général"""
|
||||||
|
auth_name = str(current_user)
|
||||||
|
if not current_user.is_administrator():
|
||||||
|
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
||||||
|
form = ScoDocConfigurationForm(
|
||||||
|
data=_make_data(
|
||||||
|
bonus_sport=ScoDocSiteConfig.get_bonus_sport_func_name(),
|
||||||
|
modele=sco_logos.list_logos(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if form.is_submitted():
|
||||||
|
action = form.select_action()
|
||||||
|
if action:
|
||||||
|
action.execute()
|
||||||
|
flash(action.message)
|
||||||
|
return redirect(
|
||||||
|
url_for(
|
||||||
|
"scodoc.configuration",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return render_template(
|
||||||
|
"configuration.html",
|
||||||
|
scodoc_dept=None,
|
||||||
|
title="Configuration ScoDoc",
|
||||||
|
form=form,
|
||||||
|
)
|
|
@ -30,6 +30,8 @@
|
||||||
|
|
||||||
(coût théorique en heures équivalent TD)
|
(coût théorique en heures équivalent TD)
|
||||||
"""
|
"""
|
||||||
|
from flask import request
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
|
@ -45,7 +47,6 @@ def formsemestre_table_estim_cost(
|
||||||
n_group_tp=1,
|
n_group_tp=1,
|
||||||
coef_tp=1,
|
coef_tp=1,
|
||||||
coef_cours=1.5,
|
coef_cours=1.5,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Rapports estimation coût de formation basé sur le programme pédagogique
|
Rapports estimation coût de formation basé sur le programme pédagogique
|
||||||
|
@ -58,9 +59,7 @@ def formsemestre_table_estim_cost(
|
||||||
"""
|
"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
sco_formsemestre_status.fill_formsemestre(sem)
|
sco_formsemestre_status.fill_formsemestre(sem)
|
||||||
Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||||
formsemestre_id=formsemestre_id
|
|
||||||
)
|
|
||||||
T = []
|
T = []
|
||||||
for M in Mlist:
|
for M in Mlist:
|
||||||
Mod = M["module"]
|
Mod = M["module"]
|
||||||
|
@ -156,7 +155,6 @@ def formsemestre_estim_cost(
|
||||||
coef_tp=1,
|
coef_tp=1,
|
||||||
coef_cours=1.5,
|
coef_cours=1.5,
|
||||||
format="html",
|
format="html",
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Page (formulaire) estimation coûts"""
|
"""Page (formulaire) estimation coûts"""
|
||||||
|
|
||||||
|
@ -171,7 +169,6 @@ def formsemestre_estim_cost(
|
||||||
n_group_tp=n_group_tp,
|
n_group_tp=n_group_tp,
|
||||||
coef_tp=coef_tp,
|
coef_tp=coef_tp,
|
||||||
coef_cours=coef_cours,
|
coef_cours=coef_cours,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
h = """
|
h = """
|
||||||
<form name="f" method="get" action="%s">
|
<form name="f" method="get" action="%s">
|
||||||
|
@ -182,7 +179,7 @@ def formsemestre_estim_cost(
|
||||||
<br/>
|
<br/>
|
||||||
</form>
|
</form>
|
||||||
""" % (
|
""" % (
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
n_group_td,
|
n_group_td,
|
||||||
n_group_tp,
|
n_group_tp,
|
||||||
|
@ -190,11 +187,11 @@ def formsemestre_estim_cost(
|
||||||
)
|
)
|
||||||
tab.html_before_table = h
|
tab.html_before_table = h
|
||||||
tab.base_url = "%s?formsemestre_id=%s&n_group_td=%s&n_group_tp=%s&coef_tp=%s" % (
|
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,
|
formsemestre_id,
|
||||||
n_group_td,
|
n_group_td,
|
||||||
n_group_tp,
|
n_group_tp,
|
||||||
coef_tp,
|
coef_tp,
|
||||||
)
|
)
|
||||||
|
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudiant
|
Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudiant
|
||||||
"""
|
"""
|
||||||
import http
|
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.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
@ -47,10 +47,20 @@ from app.scodoc import sco_etud
|
||||||
import sco_version
|
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."""
|
"""Rapport (table) pour les débouchés des étudiants sortis
|
||||||
|
à partir de l'année indiquée.
|
||||||
|
"""
|
||||||
if not start_year:
|
if not start_year:
|
||||||
return report_debouche_ask_date(REQUEST=REQUEST)
|
return report_debouche_ask_date("Année de début de la recherche")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
start_year = int(start_year)
|
||||||
|
except ValueError:
|
||||||
|
return report_debouche_ask_date(
|
||||||
|
"Année invalide. Année de début de la recherche"
|
||||||
|
)
|
||||||
|
|
||||||
if format == "xls":
|
if format == "xls":
|
||||||
keep_numeric = True # pas de conversion des notes en strings
|
keep_numeric = True # pas de conversion des notes en strings
|
||||||
else:
|
else:
|
||||||
|
@ -64,13 +74,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() + ""
|
"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.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(
|
return tab.make_page(
|
||||||
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
|
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
javascripts=["js/etud_info.js"],
|
javascripts=["js/etud_info.js"],
|
||||||
format=format,
|
format=format,
|
||||||
REQUEST=REQUEST,
|
|
||||||
with_html_headers=True,
|
with_html_headers=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -97,8 +106,9 @@ def get_etudids_with_debouche(start_year):
|
||||||
FROM notes_formsemestre_inscription i, notes_formsemestre s, itemsuivi it
|
FROM notes_formsemestre_inscription i, notes_formsemestre s, itemsuivi it
|
||||||
WHERE i.etudid = it.etudid
|
WHERE i.etudid = it.etudid
|
||||||
AND i.formsemestre_id = s.id AND s.date_fin >= %(start_date)s
|
AND i.formsemestre_id = s.id AND s.date_fin >= %(start_date)s
|
||||||
|
AND s.dept_id = %(dept_id)s
|
||||||
""",
|
""",
|
||||||
{"start_date": start_date},
|
{"start_date": start_date, "dept_id": g.scodoc_dept_id},
|
||||||
)
|
)
|
||||||
|
|
||||||
return [x["etudid"] for x in r]
|
return [x["etudid"] for x in r]
|
||||||
|
@ -194,15 +204,16 @@ def table_debouche_etudids(etudids, keep_numeric=True):
|
||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
|
||||||
def report_debouche_ask_date(REQUEST=None):
|
def report_debouche_ask_date(msg: str) -> str:
|
||||||
"""Formulaire demande date départ"""
|
"""Formulaire demande date départ"""
|
||||||
return (
|
return f"""{html_sco_header.sco_header()}
|
||||||
html_sco_header.sco_header()
|
<h2>Table des débouchés des étudiants</h2>
|
||||||
+ """<form method="GET">
|
<form method="GET">
|
||||||
Date de départ de la recherche: <input type="text" name="start_year" value="" size=10/>
|
{msg}
|
||||||
</form>"""
|
<input type="text" name="start_year" value="" size=10/>
|
||||||
+ html_sco_header.sco_footer()
|
</form>
|
||||||
)
|
{html_sco_header.sco_footer()}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
|
@ -249,7 +260,7 @@ def itemsuivi_get(cnx, itemsuivi_id, ignore_errors=False):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def itemsuivi_suppress(itemsuivi_id, REQUEST=None):
|
def itemsuivi_suppress(itemsuivi_id):
|
||||||
"""Suppression d'un item"""
|
"""Suppression d'un item"""
|
||||||
if not sco_permissions_check.can_edit_suivi():
|
if not sco_permissions_check.can_edit_suivi():
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
|
@ -259,9 +270,10 @@ def itemsuivi_suppress(itemsuivi_id, REQUEST=None):
|
||||||
_itemsuivi_delete(cnx, itemsuivi_id)
|
_itemsuivi_delete(cnx, itemsuivi_id)
|
||||||
logdb(cnx, method="itemsuivi_suppress", etudid=item["etudid"])
|
logdb(cnx, method="itemsuivi_suppress", etudid=item["etudid"])
|
||||||
log("suppressed itemsuivi %s" % (itemsuivi_id,))
|
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"""
|
"""Creation d'un item"""
|
||||||
if not sco_permissions_check.can_edit_suivi():
|
if not sco_permissions_check.can_edit_suivi():
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
|
@ -273,11 +285,11 @@ def itemsuivi_create(etudid, item_date=None, situation="", REQUEST=None, format=
|
||||||
log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
|
log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
|
||||||
item = itemsuivi_get(cnx, itemsuivi_id)
|
item = itemsuivi_get(cnx, itemsuivi_id)
|
||||||
if format == "json":
|
if format == "json":
|
||||||
return scu.sendJSON(REQUEST, item)
|
return scu.sendJSON(item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
def itemsuivi_set_date(itemsuivi_id, item_date, REQUEST=None):
|
def itemsuivi_set_date(itemsuivi_id, item_date):
|
||||||
"""set item date
|
"""set item date
|
||||||
item_date is a string dd/mm/yyyy
|
item_date is a string dd/mm/yyyy
|
||||||
"""
|
"""
|
||||||
|
@ -288,9 +300,10 @@ def itemsuivi_set_date(itemsuivi_id, item_date, REQUEST=None):
|
||||||
item = itemsuivi_get(cnx, itemsuivi_id)
|
item = itemsuivi_get(cnx, itemsuivi_id)
|
||||||
item["item_date"] = item_date
|
item["item_date"] = item_date
|
||||||
_itemsuivi_edit(cnx, item)
|
_itemsuivi_edit(cnx, item)
|
||||||
|
return ("", 204)
|
||||||
|
|
||||||
|
|
||||||
def itemsuivi_set_situation(object, value, REQUEST=None):
|
def itemsuivi_set_situation(object, value):
|
||||||
"""set situation"""
|
"""set situation"""
|
||||||
if not sco_permissions_check.can_edit_suivi():
|
if not sco_permissions_check.can_edit_suivi():
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
|
@ -304,14 +317,14 @@ def itemsuivi_set_situation(object, value, REQUEST=None):
|
||||||
return situation or scu.IT_SITUATION_MISSING_STR
|
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"""
|
"""Liste des items pour cet étudiant, avec tags"""
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
items = _itemsuivi_list(cnx, {"etudid": etudid})
|
items = _itemsuivi_list(cnx, {"etudid": etudid})
|
||||||
for it in items:
|
for it in items:
|
||||||
it["tags"] = ", ".join(itemsuivi_tag_list(it["itemsuivi_id"]))
|
it["tags"] = ", ".join(itemsuivi_tag_list(it["itemsuivi_id"]))
|
||||||
if format == "json":
|
if format == "json":
|
||||||
return scu.sendJSON(REQUEST, items)
|
return scu.sendJSON(items)
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
@ -328,7 +341,7 @@ def itemsuivi_tag_list(itemsuivi_id):
|
||||||
return [x["title"] for x in r]
|
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)"""
|
"""List all used tag names (for auto-completion)"""
|
||||||
# restrict charset to avoid injections
|
# restrict charset to avoid injections
|
||||||
if not scu.ALPHANUM_EXP.match(term):
|
if not scu.ALPHANUM_EXP.match(term):
|
||||||
|
@ -343,10 +356,10 @@ def itemsuivi_tag_search(term, REQUEST=None):
|
||||||
)
|
)
|
||||||
data = [x["title"] for x in r]
|
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:
|
"""taglist may either be:
|
||||||
a string with tag names separated by commas ("un;deux")
|
a string with tag names separated by commas ("un;deux")
|
||||||
or a list of strings (["un", "deux"])
|
or a list of strings (["un", "deux"])
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"""Page accueil département (liste des semestres, etc)
|
"""Page accueil département (liste des semestres, etc)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import g
|
from flask import g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
import app
|
import app
|
||||||
|
@ -46,8 +46,9 @@ from app.scodoc import sco_up_to_date
|
||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
|
|
||||||
|
|
||||||
def index_html(REQUEST=None, showcodes=0, showsemtable=0):
|
def index_html(showcodes=0, showsemtable=0):
|
||||||
"Page accueil département (liste des semestres)"
|
"Page accueil département (liste des semestres)"
|
||||||
|
showcodes = int(showcodes)
|
||||||
showsemtable = int(showsemtable)
|
showsemtable = int(showsemtable)
|
||||||
H = []
|
H = []
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@ def index_html(REQUEST=None, showcodes=0, showsemtable=0):
|
||||||
# Responsable de formation:
|
# Responsable de formation:
|
||||||
sco_formsemestre.sem_set_responsable_name(sem)
|
sco_formsemestre.sem_set_responsable_name(sem)
|
||||||
|
|
||||||
if showcodes == "1":
|
if showcodes:
|
||||||
sem["tmpcode"] = "<td><tt>%s</tt></td>" % sem["formsemestre_id"]
|
sem["tmpcode"] = "<td><tt>%s</tt></td>" % sem["formsemestre_id"]
|
||||||
else:
|
else:
|
||||||
sem["tmpcode"] = ""
|
sem["tmpcode"] = ""
|
||||||
|
@ -126,12 +127,12 @@ def index_html(REQUEST=None, showcodes=0, showsemtable=0):
|
||||||
"""
|
"""
|
||||||
% sco_preferences.get_preference("DeptName")
|
% sco_preferences.get_preference("DeptName")
|
||||||
)
|
)
|
||||||
H.append(_sem_table_gt(sems).html())
|
H.append(_sem_table_gt(sems, showcodes=showcodes).html())
|
||||||
H.append("</table>")
|
H.append("</table>")
|
||||||
if not showsemtable:
|
if not showsemtable:
|
||||||
H.append(
|
H.append(
|
||||||
'<hr/><p><a href="%s?showsemtable=1">Voir tous les semestres</a></p>'
|
'<hr/><p><a href="%s?showsemtable=1">Voir tous les semestres</a></p>'
|
||||||
% REQUEST.URL0
|
% request.base_url
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -242,7 +243,7 @@ def _sem_table_gt(sems, showcodes=False):
|
||||||
rows=sems,
|
rows=sems,
|
||||||
html_class="table_leftalign semlist",
|
html_class="table_leftalign semlist",
|
||||||
html_sortable=True,
|
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',
|
# caption='Maquettes enregistrées',
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -51,6 +51,7 @@ import fcntl
|
||||||
import subprocess
|
import subprocess
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -64,7 +65,7 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||||
SCO_DUMP_LOCK = "/tmp/scodump.lock"
|
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"""
|
"""Dump base de données et l'envoie anonymisée pour debug"""
|
||||||
H = [html_sco_header.sco_header(page_title="Assistance technique")]
|
H = [html_sco_header.sco_header(page_title="Assistance technique")]
|
||||||
# get currect (dept) DB name:
|
# get currect (dept) DB name:
|
||||||
|
@ -93,7 +94,7 @@ def sco_dump_and_send_db(REQUEST=None):
|
||||||
_anonymize_db(ano_db_name)
|
_anonymize_db(ano_db_name)
|
||||||
|
|
||||||
# Send
|
# Send
|
||||||
r = _send_db(REQUEST, ano_db_name)
|
r = _send_db(ano_db_name)
|
||||||
if (
|
if (
|
||||||
r.status_code
|
r.status_code
|
||||||
== requests.codes.INSUFFICIENT_STORAGE # pylint: disable=no-member
|
== requests.codes.INSUFFICIENT_STORAGE # pylint: disable=no-member
|
||||||
|
@ -166,34 +167,33 @@ def _anonymize_db(ano_db_name):
|
||||||
|
|
||||||
def _get_scodoc_serial():
|
def _get_scodoc_serial():
|
||||||
try:
|
try:
|
||||||
return int(open(os.path.join(scu.SCODOC_VERSION_DIR, "scodoc.sn")).read())
|
with open(os.path.join(scu.SCODOC_VERSION_DIR, "scodoc.sn")) as f:
|
||||||
|
return int(f.read())
|
||||||
except:
|
except:
|
||||||
return 0
|
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"""
|
"""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:
|
try:
|
||||||
data = subprocess.check_output("pg_dump {} | gzip".format(ano_db_name), shell=1)
|
dump = subprocess.check_output(
|
||||||
except subprocess.CalledProcessError as e:
|
f"pg_dump --format=custom {ano_db_name}", shell=1
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
|
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...")
|
log("uploading anonymized dump...")
|
||||||
files = {"file": (ano_db_name + ".gz", data)}
|
files = {"file": (ano_db_name + ".dump", dump)}
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
scu.SCO_DUMP_UP_URL,
|
scu.SCO_DUMP_UP_URL,
|
||||||
files=files,
|
files=files,
|
||||||
data={
|
data={
|
||||||
"dept_name": sco_preferences.get_preference("DeptName"),
|
"dept_name": sco_preferences.get_preference("DeptName"),
|
||||||
"serial": _get_scodoc_serial(),
|
"serial": _get_scodoc_serial(),
|
||||||
"sco_user": str(REQUEST.AUTHENTICATED_USER),
|
"sco_user": str(current_user),
|
||||||
"sent_by": sco_users.user_info(str(REQUEST.AUTHENTICATED_USER))[
|
"sent_by": sco_users.user_info(str(current_user))["nomcomplet"],
|
||||||
"nomcomplet"
|
|
||||||
],
|
|
||||||
"sco_version": sco_version.SCOVERSION,
|
"sco_version": sco_version.SCOVERSION,
|
||||||
"sco_fullversion": scu.get_scodoc_version(),
|
"sco_fullversion": scu.get_scodoc_version(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
(portage from DTML)
|
(portage from DTML)
|
||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import g, url_for
|
from flask import g, url_for, request
|
||||||
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -47,7 +47,7 @@ from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_news
|
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"""
|
"""Delete a formation"""
|
||||||
F = sco_formations.formation_list(args={"formation_id": formation_id})
|
F = sco_formations.formation_list(args={"formation_id": formation_id})
|
||||||
if not F:
|
if not F:
|
||||||
|
@ -104,7 +104,7 @@ def do_formation_delete(oid):
|
||||||
raise ScoLockedFormError()
|
raise ScoLockedFormError()
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
# delete all UE in this formation
|
# delete all UE in this formation
|
||||||
ues = sco_edit_ue.do_ue_list({"formation_id": oid})
|
ues = sco_edit_ue.ue_list({"formation_id": oid})
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
sco_edit_ue.do_ue_delete(ue["ue_id"], force=True)
|
sco_edit_ue.do_ue_delete(ue["ue_id"], force=True)
|
||||||
|
|
||||||
|
@ -119,12 +119,12 @@ def do_formation_delete(oid):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def formation_create(REQUEST=None):
|
def formation_create():
|
||||||
"""Creation d'une formation"""
|
"""Creation d'une formation"""
|
||||||
return formation_edit(create=True, REQUEST=REQUEST)
|
return formation_edit(create=True)
|
||||||
|
|
||||||
|
|
||||||
def formation_edit(formation_id=None, create=False, REQUEST=None):
|
def formation_edit(formation_id=None, create=False):
|
||||||
"""Edit or create a formation"""
|
"""Edit or create a formation"""
|
||||||
if create:
|
if create:
|
||||||
H = [
|
H = [
|
||||||
|
@ -159,8 +159,8 @@ def formation_edit(formation_id=None, create=False, REQUEST=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(
|
(
|
||||||
("formation_id", {"default": formation_id, "input_type": "hidden"}),
|
("formation_id", {"default": formation_id, "input_type": "hidden"}),
|
||||||
(
|
(
|
||||||
|
@ -252,7 +252,7 @@ def formation_edit(formation_id=None, create=False, REQUEST=None):
|
||||||
do_formation_edit(tf[2])
|
do_formation_edit(tf[2])
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.ue_list", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -311,15 +311,15 @@ def invalidate_sems_in_formation(formation_id):
|
||||||
) # > formation modif.
|
) # > 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)"""
|
"""Move before/after previous one (decrement/increment numero)"""
|
||||||
module = sco_edit_module.do_module_list({"module_id": module_id})[0]
|
module = sco_edit_module.module_list({"module_id": module_id})[0]
|
||||||
redirect = int(redirect)
|
redirect = int(redirect)
|
||||||
after = int(after) # 0: deplace avant, 1 deplace apres
|
after = int(after) # 0: deplace avant, 1 deplace apres
|
||||||
if after not in (0, 1):
|
if after not in (0, 1):
|
||||||
raise ValueError('invalid value for "after"')
|
raise ValueError('invalid value for "after"')
|
||||||
formation_id = module["formation_id"]
|
formation_id = module["formation_id"]
|
||||||
others = sco_edit_module.do_module_list({"matiere_id": module["matiere_id"]})
|
others = sco_edit_module.module_list({"matiere_id": module["matiere_id"]})
|
||||||
# log('others=%s' % others)
|
# log('others=%s' % others)
|
||||||
if len(others) > 1:
|
if len(others) > 1:
|
||||||
idx = [p["module_id"] for p in others].index(module_id)
|
idx = [p["module_id"] for p in others].index(module_id)
|
||||||
|
@ -343,21 +343,21 @@ def module_move(module_id, after=0, REQUEST=None, redirect=1):
|
||||||
if redirect:
|
if redirect:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.ue_list", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def ue_move(ue_id, after=0, redirect=1):
|
def ue_move(ue_id, after=0, redirect=1):
|
||||||
"""Move UE before/after previous one (decrement/increment numero)"""
|
"""Move UE before/after previous one (decrement/increment numero)"""
|
||||||
o = sco_edit_ue.do_ue_list({"ue_id": ue_id})[0]
|
o = sco_edit_ue.ue_list({"ue_id": ue_id})[0]
|
||||||
# log('ue_move %s (#%s) after=%s' % (ue_id, o['numero'], after))
|
# log('ue_move %s (#%s) after=%s' % (ue_id, o['numero'], after))
|
||||||
redirect = int(redirect)
|
redirect = int(redirect)
|
||||||
after = int(after) # 0: deplace avant, 1 deplace apres
|
after = int(after) # 0: deplace avant, 1 deplace apres
|
||||||
if after not in (0, 1):
|
if after not in (0, 1):
|
||||||
raise ValueError('invalid value for "after"')
|
raise ValueError('invalid value for "after"')
|
||||||
formation_id = o["formation_id"]
|
formation_id = o["formation_id"]
|
||||||
others = sco_edit_ue.do_ue_list({"formation_id": formation_id})
|
others = sco_edit_ue.ue_list({"formation_id": formation_id})
|
||||||
if len(others) > 1:
|
if len(others) > 1:
|
||||||
idx = [p["ue_id"] for p in others].index(ue_id)
|
idx = [p["ue_id"] for p in others].index(ue_id)
|
||||||
neigh = None # object to swap with
|
neigh = None # object to swap with
|
||||||
|
@ -378,7 +378,7 @@ def ue_move(ue_id, after=0, redirect=1):
|
||||||
if redirect:
|
if redirect:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.ue_list",
|
"notes.ue_table",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=o["formation_id"],
|
formation_id=o["formation_id"],
|
||||||
)
|
)
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
(portage from DTML)
|
(portage from DTML)
|
||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import g, url_for
|
from flask import g, url_for, request
|
||||||
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -47,7 +47,7 @@ _matiereEditor = ndb.EditableTable(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def do_matiere_list(*args, **kw):
|
def matiere_list(*args, **kw):
|
||||||
"list matieres"
|
"list matieres"
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
return _matiereEditor.list(cnx, *args, **kw)
|
return _matiereEditor.list(cnx, *args, **kw)
|
||||||
|
@ -60,12 +60,12 @@ def do_matiere_edit(*args, **kw):
|
||||||
|
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
# check
|
# check
|
||||||
mat = do_matiere_list({"matiere_id": args[0]["matiere_id"]})[0]
|
mat = matiere_list({"matiere_id": args[0]["matiere_id"]})[0]
|
||||||
if matiere_is_locked(mat["matiere_id"]):
|
if matiere_is_locked(mat["matiere_id"]):
|
||||||
raise ScoLockedFormError()
|
raise ScoLockedFormError()
|
||||||
# edit
|
# edit
|
||||||
_matiereEditor.edit(cnx, *args, **kw)
|
_matiereEditor.edit(cnx, *args, **kw)
|
||||||
formation_id = sco_edit_ue.do_ue_list({"ue_id": mat["ue_id"]})[0]["formation_id"]
|
formation_id = sco_edit_ue.ue_list({"ue_id": mat["ue_id"]})[0]["formation_id"]
|
||||||
sco_edit_formation.invalidate_sems_in_formation(formation_id)
|
sco_edit_formation.invalidate_sems_in_formation(formation_id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ def do_matiere_create(args):
|
||||||
|
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
# check
|
# check
|
||||||
ue = sco_edit_ue.do_ue_list({"ue_id": args["ue_id"]})[0]
|
ue = sco_edit_ue.ue_list({"ue_id": args["ue_id"]})[0]
|
||||||
# create matiere
|
# create matiere
|
||||||
r = _matiereEditor.create(cnx, args)
|
r = _matiereEditor.create(cnx, args)
|
||||||
|
|
||||||
|
@ -92,11 +92,11 @@ def do_matiere_create(args):
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def matiere_create(ue_id=None, REQUEST=None):
|
def matiere_create(ue_id=None):
|
||||||
"""Creation d'une matiere"""
|
"""Creation d'une matiere"""
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
|
|
||||||
UE = sco_edit_ue.do_ue_list(args={"ue_id": ue_id})[0]
|
UE = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Création d'une matière"),
|
html_sco_header.sco_header(page_title="Création d'une matière"),
|
||||||
"""<h2>Création d'une matière dans l'UE %(titre)s (%(acronyme)s)</h2>""" % UE,
|
"""<h2>Création d'une matière dans l'UE %(titre)s (%(acronyme)s)</h2>""" % UE,
|
||||||
|
@ -116,8 +116,8 @@ associé.
|
||||||
</p>""",
|
</p>""",
|
||||||
]
|
]
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(
|
(
|
||||||
("ue_id", {"input_type": "hidden", "default": ue_id}),
|
("ue_id", {"input_type": "hidden", "default": ue_id}),
|
||||||
("titre", {"size": 30, "explanation": "nom de la matière."}),
|
("titre", {"size": 30, "explanation": "nom de la matière."}),
|
||||||
|
@ -134,7 +134,7 @@ associé.
|
||||||
)
|
)
|
||||||
|
|
||||||
dest_url = url_for(
|
dest_url = url_for(
|
||||||
"notes.ue_list", scodoc_dept=g.scodoc_dept, formation_id=UE["formation_id"]
|
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=UE["formation_id"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
|
@ -143,7 +143,7 @@ associé.
|
||||||
return flask.redirect(dest_url)
|
return flask.redirect(dest_url)
|
||||||
else:
|
else:
|
||||||
# check unicity
|
# check unicity
|
||||||
mats = do_matiere_list(args={"ue_id": ue_id, "titre": tf[2]["titre"]})
|
mats = matiere_list(args={"ue_id": ue_id, "titre": tf[2]["titre"]})
|
||||||
if mats:
|
if mats:
|
||||||
return (
|
return (
|
||||||
"\n".join(H)
|
"\n".join(H)
|
||||||
|
@ -164,8 +164,8 @@ def do_matiere_delete(oid):
|
||||||
|
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
# check
|
# check
|
||||||
mat = do_matiere_list({"matiere_id": oid})[0]
|
mat = matiere_list({"matiere_id": oid})[0]
|
||||||
ue = sco_edit_ue.do_ue_list({"ue_id": mat["ue_id"]})[0]
|
ue = sco_edit_ue.ue_list({"ue_id": mat["ue_id"]})[0]
|
||||||
locked = matiere_is_locked(mat["matiere_id"])
|
locked = matiere_is_locked(mat["matiere_id"])
|
||||||
if locked:
|
if locked:
|
||||||
log("do_matiere_delete: mat=%s" % mat)
|
log("do_matiere_delete: mat=%s" % mat)
|
||||||
|
@ -174,7 +174,7 @@ def do_matiere_delete(oid):
|
||||||
raise ScoLockedFormError()
|
raise ScoLockedFormError()
|
||||||
log("do_matiere_delete: matiere_id=%s" % oid)
|
log("do_matiere_delete: matiere_id=%s" % oid)
|
||||||
# delete all modules in this matiere
|
# delete all modules in this matiere
|
||||||
mods = sco_edit_module.do_module_list({"matiere_id": oid})
|
mods = sco_edit_module.module_list({"matiere_id": oid})
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
sco_edit_module.do_module_delete(mod["module_id"])
|
sco_edit_module.do_module_delete(mod["module_id"])
|
||||||
_matiereEditor.delete(cnx, oid)
|
_matiereEditor.delete(cnx, oid)
|
||||||
|
@ -189,21 +189,25 @@ def do_matiere_delete(oid):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def matiere_delete(matiere_id=None, REQUEST=None):
|
def matiere_delete(matiere_id=None):
|
||||||
"""Delete an UE"""
|
"""Delete matière"""
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
|
|
||||||
M = do_matiere_list(args={"matiere_id": matiere_id})[0]
|
M = matiere_list(args={"matiere_id": matiere_id})[0]
|
||||||
UE = sco_edit_ue.do_ue_list(args={"ue_id": M["ue_id"]})[0]
|
UE = sco_edit_ue.ue_list(args={"ue_id": M["ue_id"]})[0]
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Suppression d'une matière"),
|
html_sco_header.sco_header(page_title="Suppression d'une matière"),
|
||||||
"<h2>Suppression de la matière %(titre)s" % M,
|
"<h2>Suppression de la matière %(titre)s" % M,
|
||||||
" dans l'UE (%(acronyme)s))</h2>" % UE,
|
" dans l'UE (%(acronyme)s))</h2>" % UE,
|
||||||
]
|
]
|
||||||
dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(UE["formation_id"])
|
dest_url = url_for(
|
||||||
|
"notes.ue_table",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formation_id=str(UE["formation_id"]),
|
||||||
|
)
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(("matiere_id", {"input_type": "hidden"}),),
|
(("matiere_id", {"input_type": "hidden"}),),
|
||||||
initvalues=M,
|
initvalues=M,
|
||||||
submitlabel="Confirmer la suppression",
|
submitlabel="Confirmer la suppression",
|
||||||
|
@ -218,22 +222,22 @@ def matiere_delete(matiere_id=None, REQUEST=None):
|
||||||
return flask.redirect(dest_url)
|
return flask.redirect(dest_url)
|
||||||
|
|
||||||
|
|
||||||
def matiere_edit(matiere_id=None, REQUEST=None):
|
def matiere_edit(matiere_id=None):
|
||||||
"""Edit matiere"""
|
"""Edit matiere"""
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
|
|
||||||
F = do_matiere_list(args={"matiere_id": matiere_id})
|
F = matiere_list(args={"matiere_id": matiere_id})
|
||||||
if not F:
|
if not F:
|
||||||
raise ScoValueError("Matière inexistante !")
|
raise ScoValueError("Matière inexistante !")
|
||||||
F = F[0]
|
F = F[0]
|
||||||
U = sco_edit_ue.do_ue_list(args={"ue_id": F["ue_id"]})
|
ues = sco_edit_ue.ue_list(args={"ue_id": F["ue_id"]})
|
||||||
if not F:
|
if not ues:
|
||||||
raise ScoValueError("UE inexistante !")
|
raise ScoValueError("UE inexistante !")
|
||||||
U = U[0]
|
ue = ues[0]
|
||||||
Fo = sco_formations.formation_list(args={"formation_id": U["formation_id"]})[0]
|
Fo = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
|
||||||
|
|
||||||
ues = sco_edit_ue.do_ue_list(args={"formation_id": U["formation_id"]})
|
ues = sco_edit_ue.ue_list(args={"formation_id": ue["formation_id"]})
|
||||||
ue_names = ["%(acronyme)s (%(titre)s)" % u for u in ues]
|
ue_names = ["%(acronyme)s (%(titre)s)" % u for u in ues]
|
||||||
ue_ids = [u["ue_id"] for u in ues]
|
ue_ids = [u["ue_id"] for u in ues]
|
||||||
H = [
|
H = [
|
||||||
|
@ -256,8 +260,8 @@ des notes.</em>
|
||||||
associé.
|
associé.
|
||||||
</p>"""
|
</p>"""
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(
|
(
|
||||||
("matiere_id", {"input_type": "hidden"}),
|
("matiere_id", {"input_type": "hidden"}),
|
||||||
(
|
(
|
||||||
|
@ -278,15 +282,18 @@ associé.
|
||||||
submitlabel="Modifier les valeurs",
|
submitlabel="Modifier les valeurs",
|
||||||
)
|
)
|
||||||
|
|
||||||
dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(U["formation_id"])
|
dest_url = url_for(
|
||||||
|
"notes.ue_table",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formation_id=str(ue["formation_id"]),
|
||||||
|
)
|
||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
return "\n".join(H) + tf[1] + help + html_sco_header.sco_footer()
|
return "\n".join(H) + tf[1] + help + html_sco_header.sco_footer()
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return flask.redirect(dest_url)
|
return flask.redirect(dest_url)
|
||||||
else:
|
else:
|
||||||
# check unicity
|
# check unicity
|
||||||
mats = do_matiere_list(args={"ue_id": tf[2]["ue_id"], "titre": tf[2]["titre"]})
|
mats = matiere_list(args={"ue_id": tf[2]["ue_id"], "titre": tf[2]["titre"]})
|
||||||
if len(mats) > 1 or (len(mats) == 1 and mats[0]["matiere_id"] != matiere_id):
|
if len(mats) > 1 or (len(mats) == 1 and mats[0]["matiere_id"] != matiere_id):
|
||||||
return (
|
return (
|
||||||
"\n".join(H)
|
"\n".join(H)
|
||||||
|
@ -323,4 +330,4 @@ def matiere_is_locked(matiere_id):
|
||||||
""",
|
""",
|
||||||
{"matiere_id": matiere_id},
|
{"matiere_id": matiere_id},
|
||||||
)
|
)
|
||||||
return len(r) > 0
|
return len(r) > 0
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
(portage from DTML)
|
(portage from DTML)
|
||||||
"""
|
"""
|
||||||
import flask
|
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.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -93,7 +94,7 @@ _moduleEditor = ndb.EditableTable(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def do_module_list(*args, **kw):
|
def module_list(*args, **kw):
|
||||||
"list modules"
|
"list modules"
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
return _moduleEditor.list(cnx, *args, **kw)
|
return _moduleEditor.list(cnx, *args, **kw)
|
||||||
|
@ -118,15 +119,15 @@ def do_module_create(args) -> int:
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def module_create(matiere_id=None, REQUEST=None):
|
def module_create(matiere_id=None):
|
||||||
"""Creation d'un module"""
|
"""Creation d'un module"""
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
|
|
||||||
if matiere_id is None:
|
if matiere_id is None:
|
||||||
raise ScoValueError("invalid matiere !")
|
raise ScoValueError("invalid matiere !")
|
||||||
M = sco_edit_matiere.do_matiere_list(args={"matiere_id": matiere_id})[0]
|
M = sco_edit_matiere.matiere_list(args={"matiere_id": matiere_id})[0]
|
||||||
UE = sco_edit_ue.do_ue_list(args={"ue_id": M["ue_id"]})[0]
|
UE = sco_edit_ue.ue_list(args={"ue_id": M["ue_id"]})[0]
|
||||||
Fo = sco_formations.formation_list(args={"formation_id": UE["formation_id"]})[0]
|
Fo = sco_formations.formation_list(args={"formation_id": UE["formation_id"]})[0]
|
||||||
parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
|
parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"])
|
||||||
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
||||||
|
@ -137,14 +138,14 @@ def module_create(matiere_id=None, REQUEST=None):
|
||||||
_MODULE_HELP,
|
_MODULE_HELP,
|
||||||
]
|
]
|
||||||
# cherche le numero adequat (pour placer le module en fin de liste)
|
# cherche le numero adequat (pour placer le module en fin de liste)
|
||||||
Mods = do_module_list(args={"matiere_id": matiere_id})
|
Mods = module_list(args={"matiere_id": matiere_id})
|
||||||
if Mods:
|
if Mods:
|
||||||
default_num = max([m["numero"] for m in Mods]) + 10
|
default_num = max([m["numero"] for m in Mods]) + 10
|
||||||
else:
|
else:
|
||||||
default_num = 10
|
default_num = 10
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
"code",
|
"code",
|
||||||
|
@ -240,7 +241,7 @@ def module_create(matiere_id=None, REQUEST=None):
|
||||||
do_module_create(tf[2])
|
do_module_create(tf[2])
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.ue_list",
|
"notes.ue_table",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=UE["formation_id"],
|
formation_id=UE["formation_id"],
|
||||||
)
|
)
|
||||||
|
@ -251,19 +252,20 @@ def do_module_delete(oid):
|
||||||
"delete module"
|
"delete module"
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
|
|
||||||
mod = do_module_list({"module_id": oid})[0]
|
mod = module_list({"module_id": oid})[0]
|
||||||
if module_is_locked(mod["module_id"]):
|
if module_is_locked(mod["module_id"]):
|
||||||
raise ScoLockedFormError()
|
raise ScoLockedFormError()
|
||||||
|
|
||||||
# S'il y a des moduleimpls, on ne peut pas detruire le module !
|
# S'il y a des moduleimpls, on ne peut pas detruire le module !
|
||||||
mods = sco_moduleimpl.do_moduleimpl_list(module_id=oid)
|
mods = sco_moduleimpl.moduleimpl_list(module_id=oid)
|
||||||
if mods:
|
if mods:
|
||||||
err_page = scu.confirm_dialog(
|
err_page = f"""<h3>Destruction du module impossible car il est utilisé dans des semestres existants !</h3>
|
||||||
message="""<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
|
||||||
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.""",
|
laisser ce programme intact et d'en créer une nouvelle version pour la modifier.
|
||||||
dest_url="ue_list",
|
</p>
|
||||||
parameters={"formation_id": mod["formation_id"]},
|
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
|
||||||
)
|
formation_id=mod["formation_id"])}">reprendre</a>
|
||||||
|
"""
|
||||||
raise ScoGenError(err_page)
|
raise ScoGenError(err_page)
|
||||||
# delete
|
# delete
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
|
@ -279,25 +281,29 @@ def do_module_delete(oid):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def module_delete(module_id=None, REQUEST=None):
|
def module_delete(module_id=None):
|
||||||
"""Delete a module"""
|
"""Delete a module"""
|
||||||
if not module_id:
|
if not module_id:
|
||||||
raise ScoValueError("invalid module !")
|
raise ScoValueError("invalid module !")
|
||||||
Mods = do_module_list(args={"module_id": module_id})
|
modules = module_list(args={"module_id": module_id})
|
||||||
if not Mods:
|
if not modules:
|
||||||
raise ScoValueError("Module inexistant !")
|
raise ScoValueError("Module inexistant !")
|
||||||
Mod = Mods[0]
|
mod = modules[0]
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Suppression d'un module"),
|
html_sco_header.sco_header(page_title="Suppression d'un module"),
|
||||||
"""<h2>Suppression du module %(titre)s (%(code)s)</h2>""" % Mod,
|
"""<h2>Suppression du module %(titre)s (%(code)s)</h2>""" % mod,
|
||||||
]
|
]
|
||||||
|
|
||||||
dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(Mod["formation_id"])
|
dest_url = url_for(
|
||||||
|
"notes.ue_table",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formation_id=str(mod["formation_id"]),
|
||||||
|
)
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(("module_id", {"input_type": "hidden"}),),
|
(("module_id", {"input_type": "hidden"}),),
|
||||||
initvalues=Mod,
|
initvalues=mod,
|
||||||
submitlabel="Confirmer la suppression",
|
submitlabel="Confirmer la suppression",
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
)
|
)
|
||||||
|
@ -315,7 +321,7 @@ def do_module_edit(val):
|
||||||
from app.scodoc import sco_edit_formation
|
from app.scodoc import sco_edit_formation
|
||||||
|
|
||||||
# check
|
# check
|
||||||
mod = do_module_list({"module_id": val["module_id"]})[0]
|
mod = module_list({"module_id": val["module_id"]})[0]
|
||||||
if module_is_locked(mod["module_id"]):
|
if module_is_locked(mod["module_id"]):
|
||||||
# formation verrouillée: empeche de modifier certains champs:
|
# formation verrouillée: empeche de modifier certains champs:
|
||||||
protected_fields = ("coefficient", "ue_id", "matiere_id", "semestre_id")
|
protected_fields = ("coefficient", "ue_id", "matiere_id", "semestre_id")
|
||||||
|
@ -330,21 +336,21 @@ def do_module_edit(val):
|
||||||
|
|
||||||
def check_module_code_unicity(code, field, formation_id, module_id=None):
|
def check_module_code_unicity(code, field, formation_id, module_id=None):
|
||||||
"true si code module unique dans la formation"
|
"true si code module unique dans la formation"
|
||||||
Mods = do_module_list(args={"code": code, "formation_id": formation_id})
|
Mods = module_list(args={"code": code, "formation_id": formation_id})
|
||||||
if module_id: # edition: supprime le module en cours
|
if module_id: # edition: supprime le module en cours
|
||||||
Mods = [m for m in Mods if m["module_id"] != module_id]
|
Mods = [m for m in Mods if m["module_id"] != module_id]
|
||||||
|
|
||||||
return len(Mods) == 0
|
return len(Mods) == 0
|
||||||
|
|
||||||
|
|
||||||
def module_edit(module_id=None, REQUEST=None):
|
def module_edit(module_id=None):
|
||||||
"""Edit a module"""
|
"""Edit a module"""
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
from app.scodoc import sco_tag_module
|
from app.scodoc import sco_tag_module
|
||||||
|
|
||||||
if not module_id:
|
if not module_id:
|
||||||
raise ScoValueError("invalid module !")
|
raise ScoValueError("invalid module !")
|
||||||
Mod = do_module_list(args={"module_id": module_id})
|
Mod = module_list(args={"module_id": module_id})
|
||||||
if not Mod:
|
if not Mod:
|
||||||
raise ScoValueError("invalid module !")
|
raise ScoValueError("invalid module !")
|
||||||
Mod = Mod[0]
|
Mod = Mod[0]
|
||||||
|
@ -365,9 +371,11 @@ def module_edit(module_id=None, REQUEST=None):
|
||||||
Mod["ue_matiere_id"] = "%s!%s" % (Mod["ue_id"], Mod["matiere_id"])
|
Mod["ue_matiere_id"] = "%s!%s" % (Mod["ue_id"], Mod["matiere_id"])
|
||||||
|
|
||||||
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
||||||
|
dest_url = url_for(
|
||||||
dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(Mod["formation_id"])
|
"notes.ue_table",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formation_id=str(Mod["formation_id"]),
|
||||||
|
)
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title="Modification du module %(titre)s" % Mod,
|
page_title="Modification du module %(titre)s" % Mod,
|
||||||
|
@ -388,8 +396,8 @@ def module_edit(module_id=None, REQUEST=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
"code",
|
"code",
|
||||||
|
@ -513,13 +521,13 @@ def module_edit(module_id=None, REQUEST=None):
|
||||||
|
|
||||||
|
|
||||||
# Edition en ligne du code Apogee
|
# Edition en ligne du code Apogee
|
||||||
def edit_module_set_code_apogee(id=None, value=None, REQUEST=None):
|
def edit_module_set_code_apogee(id=None, value=None):
|
||||||
"Set UE code apogee"
|
"Set UE code apogee"
|
||||||
module_id = id
|
module_id = id
|
||||||
value = value.strip("-_ \t")
|
value = value.strip("-_ \t")
|
||||||
log("edit_module_set_code_apogee: module_id=%s code_apogee=%s" % (module_id, value))
|
log("edit_module_set_code_apogee: module_id=%s code_apogee=%s" % (module_id, value))
|
||||||
|
|
||||||
modules = do_module_list(args={"module_id": module_id})
|
modules = module_list(args={"module_id": module_id})
|
||||||
if not modules:
|
if not modules:
|
||||||
return "module invalide" # should not occur
|
return "module invalide" # should not occur
|
||||||
|
|
||||||
|
@ -529,7 +537,7 @@ def edit_module_set_code_apogee(id=None, value=None, REQUEST=None):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def module_list(formation_id, REQUEST=None):
|
def module_table(formation_id):
|
||||||
"""Liste des modules de la formation
|
"""Liste des modules de la formation
|
||||||
(XXX inutile ou a revoir)
|
(XXX inutile ou a revoir)
|
||||||
"""
|
"""
|
||||||
|
@ -544,9 +552,9 @@ def module_list(formation_id, REQUEST=None):
|
||||||
% F,
|
% F,
|
||||||
'<ul class="notes_module_list">',
|
'<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}):
|
for Mod in module_list(args={"formation_id": formation_id}):
|
||||||
H.append('<li class="notes_module_list">%s' % Mod)
|
H.append('<li class="notes_module_list">%s' % Mod)
|
||||||
if editable:
|
if editable:
|
||||||
H.append('<a href="module_edit?module_id=%(module_id)s">modifier</a>' % Mod)
|
H.append('<a href="module_edit?module_id=%(module_id)s">modifier</a>' % Mod)
|
||||||
|
@ -578,37 +586,41 @@ def module_is_locked(module_id):
|
||||||
|
|
||||||
def module_count_moduleimpls(module_id):
|
def module_count_moduleimpls(module_id):
|
||||||
"Number of moduleimpls using this module"
|
"Number of moduleimpls using this module"
|
||||||
mods = sco_moduleimpl.do_moduleimpl_list(module_id=module_id)
|
mods = sco_moduleimpl.moduleimpl_list(module_id=module_id)
|
||||||
return len(mods)
|
return len(mods)
|
||||||
|
|
||||||
|
|
||||||
def formation_add_malus_modules(formation_id, titre=None, REQUEST=None):
|
def formation_add_malus_modules(formation_id, titre=None, redirect=True):
|
||||||
"""Création d'un module de "malus" dans chaque UE d'une formation"""
|
"""Création d'un module de "malus" dans chaque UE d'une formation"""
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
|
|
||||||
ue_list = sco_edit_ue.do_ue_list(args={"formation_id": formation_id})
|
ues = sco_edit_ue.ue_list(args={"formation_id": formation_id})
|
||||||
|
|
||||||
for ue in ue_list:
|
for ue in ues:
|
||||||
# Un seul module de malus par UE:
|
# Un seul module de malus par UE:
|
||||||
nb_mod_malus = len(
|
nb_mod_malus = len(
|
||||||
[
|
[
|
||||||
mod
|
mod
|
||||||
for mod in do_module_list(args={"ue_id": ue["ue_id"]})
|
for mod in module_list(args={"ue_id": ue["ue_id"]})
|
||||||
if mod["module_type"] == scu.MODULE_MALUS
|
if mod["module_type"] == scu.MODULE_MALUS
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if nb_mod_malus == 0:
|
if nb_mod_malus == 0:
|
||||||
ue_add_malus_module(ue["ue_id"], titre=titre, REQUEST=REQUEST)
|
ue_add_malus_module(ue["ue_id"], titre=titre)
|
||||||
|
|
||||||
if REQUEST:
|
if redirect:
|
||||||
return flask.redirect("ue_list?formation_id=" + str(formation_id))
|
return flask.redirect(
|
||||||
|
url_for(
|
||||||
|
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def ue_add_malus_module(ue_id, titre=None, code=None, REQUEST=None):
|
def ue_add_malus_module(ue_id, titre=None, code=None):
|
||||||
"""Add a malus module in this ue"""
|
"""Add a malus module in this ue"""
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
|
|
||||||
ue = sco_edit_ue.do_ue_list(args={"ue_id": ue_id})[0]
|
ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
|
||||||
|
|
||||||
if titre is None:
|
if titre is None:
|
||||||
titre = ""
|
titre = ""
|
||||||
|
@ -627,7 +639,7 @@ def ue_add_malus_module(ue_id, titre=None, code=None, REQUEST=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Matiere pour placer le module malus
|
# Matiere pour placer le module malus
|
||||||
Matlist = sco_edit_matiere.do_matiere_list(args={"ue_id": ue_id})
|
Matlist = sco_edit_matiere.matiere_list(args={"ue_id": ue_id})
|
||||||
numero = max([mat["numero"] for mat in Matlist]) + 10
|
numero = max([mat["numero"] for mat in Matlist]) + 10
|
||||||
matiere_id = sco_edit_matiere.do_matiere_create(
|
matiere_id = sco_edit_matiere.do_matiere_create(
|
||||||
{"ue_id": ue_id, "titre": "Malus", "numero": numero}
|
{"ue_id": ue_id, "titre": "Malus", "numero": numero}
|
||||||
|
|
|
@ -29,9 +29,10 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import g, url_for
|
from flask import g, url_for, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from app.models.formations import NotesUE
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
|
@ -74,7 +75,7 @@ _ueEditor = ndb.EditableTable(
|
||||||
sortkey="numero",
|
sortkey="numero",
|
||||||
input_formators={
|
input_formators={
|
||||||
"type": ndb.int_null_is_zero,
|
"type": ndb.int_null_is_zero,
|
||||||
"is_external": bool,
|
"is_external": ndb.bool_or_str,
|
||||||
},
|
},
|
||||||
output_formators={
|
output_formators={
|
||||||
"numero": ndb.int_null_is_zero,
|
"numero": ndb.int_null_is_zero,
|
||||||
|
@ -84,7 +85,7 @@ _ueEditor = ndb.EditableTable(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def do_ue_list(*args, **kw):
|
def ue_list(*args, **kw):
|
||||||
"list UEs"
|
"list UEs"
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
return _ueEditor.list(cnx, *args, **kw)
|
return _ueEditor.list(cnx, *args, **kw)
|
||||||
|
@ -96,9 +97,7 @@ def do_ue_create(args):
|
||||||
|
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
# check duplicates
|
# check duplicates
|
||||||
ues = do_ue_list(
|
ues = ue_list({"formation_id": args["formation_id"], "acronyme": args["acronyme"]})
|
||||||
{"formation_id": args["formation_id"], "acronyme": args["acronyme"]}
|
|
||||||
)
|
|
||||||
if ues:
|
if ues:
|
||||||
raise ScoValueError('Acronyme d\'UE "%s" déjà utilisé !' % args["acronyme"])
|
raise ScoValueError('Acronyme d\'UE "%s" déjà utilisé !' % args["acronyme"])
|
||||||
# create
|
# create
|
||||||
|
@ -123,7 +122,7 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue_id, delete_validations))
|
log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue_id, delete_validations))
|
||||||
# check
|
# check
|
||||||
ue = do_ue_list({"ue_id": ue_id})
|
ue = ue_list({"ue_id": ue_id})
|
||||||
if not ue:
|
if not ue:
|
||||||
raise ScoValueError("UE inexistante !")
|
raise ScoValueError("UE inexistante !")
|
||||||
ue = ue[0]
|
ue = ue[0]
|
||||||
|
@ -140,7 +139,11 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
|
||||||
% (len(validations), ue["acronyme"], ue["titre"]),
|
% (len(validations), ue["acronyme"], ue["titre"]),
|
||||||
dest_url="",
|
dest_url="",
|
||||||
target_variable="delete_validations",
|
target_variable="delete_validations",
|
||||||
cancel_url="ue_list?formation_id=%s" % ue["formation_id"],
|
cancel_url=url_for(
|
||||||
|
"notes.ue_table",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formation_id=str(ue["formation_id"]),
|
||||||
|
),
|
||||||
parameters={"ue_id": ue_id, "dialog_confirmed": 1},
|
parameters={"ue_id": ue_id, "dialog_confirmed": 1},
|
||||||
)
|
)
|
||||||
if delete_validations:
|
if delete_validations:
|
||||||
|
@ -151,7 +154,7 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
|
||||||
)
|
)
|
||||||
|
|
||||||
# delete all matiere in this UE
|
# delete all matiere in this UE
|
||||||
mats = sco_edit_matiere.do_matiere_list({"ue_id": ue_id})
|
mats = sco_edit_matiere.matiere_list({"ue_id": ue_id})
|
||||||
for mat in mats:
|
for mat in mats:
|
||||||
sco_edit_matiere.do_matiere_delete(mat["matiere_id"])
|
sco_edit_matiere.do_matiere_delete(mat["matiere_id"])
|
||||||
# delete uecoef and events
|
# delete uecoef and events
|
||||||
|
@ -176,7 +179,7 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
|
||||||
if not force:
|
if not force:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.ue_list",
|
"notes.ue_table",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=ue["formation_id"],
|
formation_id=ue["formation_id"],
|
||||||
)
|
)
|
||||||
|
@ -185,18 +188,18 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def ue_create(formation_id=None, REQUEST=None):
|
def ue_create(formation_id=None):
|
||||||
"""Creation d'une UE"""
|
"""Creation d'une UE"""
|
||||||
return ue_edit(create=True, formation_id=formation_id, REQUEST=REQUEST)
|
return ue_edit(create=True, formation_id=formation_id)
|
||||||
|
|
||||||
|
|
||||||
def ue_edit(ue_id=None, create=False, formation_id=None, REQUEST=None):
|
def ue_edit(ue_id=None, create=False, formation_id=None):
|
||||||
"""Modification ou creation d'une UE"""
|
"""Modification ou creation d'une UE"""
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
|
|
||||||
create = int(create)
|
create = int(create)
|
||||||
if not create:
|
if not create:
|
||||||
U = do_ue_list(args={"ue_id": ue_id})
|
U = ue_list(args={"ue_id": ue_id})
|
||||||
if not U:
|
if not U:
|
||||||
raise ScoValueError("UE inexistante !")
|
raise ScoValueError("UE inexistante !")
|
||||||
U = U[0]
|
U = U[0]
|
||||||
|
@ -295,6 +298,14 @@ def ue_edit(ue_id=None, create=False, formation_id=None, REQUEST=None):
|
||||||
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
|
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"is_external",
|
||||||
|
{
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"title": "UE externe",
|
||||||
|
"explanation": "réservé pour les capitalisations d'UE effectuées à l'extérieur de l'établissement",
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
if parcours.UE_IS_MODULE:
|
if parcours.UE_IS_MODULE:
|
||||||
# demande le semestre pour creer le module immediatement:
|
# demande le semestre pour creer le module immediatement:
|
||||||
|
@ -326,7 +337,11 @@ def ue_edit(ue_id=None, create=False, formation_id=None, REQUEST=None):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0, REQUEST.form, fw, initvalues=initvalues, submitlabel=submitlabel
|
request.base_url,
|
||||||
|
scu.get_request_args(),
|
||||||
|
fw,
|
||||||
|
initvalues=initvalues,
|
||||||
|
submitlabel=submitlabel,
|
||||||
)
|
)
|
||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
X = """<div id="ue_list_code"></div>
|
X = """<div id="ue_list_code"></div>
|
||||||
|
@ -366,18 +381,18 @@ def ue_edit(ue_id=None, create=False, formation_id=None, REQUEST=None):
|
||||||
do_ue_edit(tf[2])
|
do_ue_edit(tf[2])
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.ue_list", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _add_ue_semestre_id(ue_list):
|
def _add_ue_semestre_id(ues):
|
||||||
"""ajoute semestre_id dans les ue, en regardant le premier module de chacune.
|
"""ajoute semestre_id dans les ue, en regardant le premier module de chacune.
|
||||||
Les UE sans modules se voient attribuer le numero UE_SEM_DEFAULT (1000000),
|
Les UE sans modules se voient attribuer le numero UE_SEM_DEFAULT (1000000),
|
||||||
qui les place à la fin de la liste.
|
qui les place à la fin de la liste.
|
||||||
"""
|
"""
|
||||||
for ue in ue_list:
|
for ue in ues:
|
||||||
Modlist = sco_edit_module.do_module_list(args={"ue_id": ue["ue_id"]})
|
Modlist = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
|
||||||
if Modlist:
|
if Modlist:
|
||||||
ue["semestre_id"] = Modlist[0]["semestre_id"]
|
ue["semestre_id"] = Modlist[0]["semestre_id"]
|
||||||
else:
|
else:
|
||||||
|
@ -388,42 +403,46 @@ def next_ue_numero(formation_id, semestre_id=None):
|
||||||
"""Numero d'une nouvelle UE dans cette formation.
|
"""Numero d'une nouvelle UE dans cette formation.
|
||||||
Si le semestre est specifie, cherche les UE ayant des modules de ce semestre
|
Si le semestre est specifie, cherche les UE ayant des modules de ce semestre
|
||||||
"""
|
"""
|
||||||
ue_list = do_ue_list(args={"formation_id": formation_id})
|
ues = ue_list(args={"formation_id": formation_id})
|
||||||
if not ue_list:
|
if not ues:
|
||||||
return 0
|
return 0
|
||||||
if semestre_id is None:
|
if semestre_id is None:
|
||||||
return ue_list[-1]["numero"] + 1000
|
return ues[-1]["numero"] + 1000
|
||||||
else:
|
else:
|
||||||
# Avec semestre: (prend le semestre du 1er module de l'UE)
|
# Avec semestre: (prend le semestre du 1er module de l'UE)
|
||||||
_add_ue_semestre_id(ue_list)
|
_add_ue_semestre_id(ues)
|
||||||
ue_list_semestre = [ue for ue in ue_list if ue["semestre_id"] == semestre_id]
|
ue_list_semestre = [ue for ue in ues if ue["semestre_id"] == semestre_id]
|
||||||
if ue_list_semestre:
|
if ue_list_semestre:
|
||||||
return ue_list_semestre[-1]["numero"] + 10
|
return ue_list_semestre[-1]["numero"] + 10
|
||||||
else:
|
else:
|
||||||
return ue_list[-1]["numero"] + 1000
|
return ues[-1]["numero"] + 1000
|
||||||
|
|
||||||
|
|
||||||
def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
|
def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
|
||||||
"""Delete an UE"""
|
"""Delete an UE"""
|
||||||
ue = do_ue_list(args={"ue_id": ue_id})
|
ues = ue_list(args={"ue_id": ue_id})
|
||||||
if not ue:
|
if not ues:
|
||||||
raise ScoValueError("UE inexistante !")
|
raise ScoValueError("UE inexistante !")
|
||||||
ue = ue[0]
|
ue = ues[0]
|
||||||
|
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
"<h2>Suppression de l'UE %(titre)s (%(acronyme)s))</h2>" % ue,
|
"<h2>Suppression de l'UE %(titre)s (%(acronyme)s))</h2>" % ue,
|
||||||
dest_url="",
|
dest_url="",
|
||||||
parameters={"ue_id": ue_id},
|
parameters={"ue_id": ue_id},
|
||||||
cancel_url="ue_list?formation_id=%s" % ue["formation_id"],
|
cancel_url=url_for(
|
||||||
|
"notes.ue_table",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formation_id=str(ue["formation_id"]),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return do_ue_delete(ue_id, delete_validations=delete_validations)
|
return do_ue_delete(ue_id, delete_validations=delete_validations)
|
||||||
|
|
||||||
|
|
||||||
def ue_list(formation_id=None, msg=""):
|
def ue_table(formation_id=None, msg=""): # was ue_list
|
||||||
"""Liste des matières et modules d'une formation, avec liens pour
|
"""Liste des matières et modules d'une formation, avec liens pour
|
||||||
editer (si non verrouillée).
|
éditer (si non verrouillée).
|
||||||
"""
|
"""
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
from app.scodoc import sco_formsemestre_validation
|
from app.scodoc import sco_formsemestre_validation
|
||||||
|
@ -435,28 +454,31 @@ def ue_list(formation_id=None, msg=""):
|
||||||
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
||||||
locked = sco_formations.formation_has_locked_sems(formation_id)
|
locked = sco_formations.formation_has_locked_sems(formation_id)
|
||||||
|
|
||||||
ue_list = do_ue_list(args={"formation_id": formation_id})
|
ues = ue_list(args={"formation_id": formation_id, "is_external": False})
|
||||||
|
ues_externes = ue_list(args={"formation_id": formation_id, "is_external": True})
|
||||||
# tri par semestre et numero:
|
# tri par semestre et numero:
|
||||||
_add_ue_semestre_id(ue_list)
|
_add_ue_semestre_id(ues)
|
||||||
ue_list.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
_add_ue_semestre_id(ues_externes)
|
||||||
has_duplicate_ue_codes = len(set([ue["ue_code"] for ue in ue_list])) != len(ue_list)
|
ues.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
||||||
|
ues_externes.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
||||||
|
has_duplicate_ue_codes = len(set([ue["ue_code"] for ue in ues])) != len(ues)
|
||||||
|
|
||||||
perm_change = current_user.has_permission(Permission.ScoChangeFormation)
|
has_perm_change = current_user.has_permission(Permission.ScoChangeFormation)
|
||||||
# editable = (not locked) and perm_change
|
# editable = (not locked) and has_perm_change
|
||||||
# On autorise maintanant la modification des formations qui ont des semestres verrouillés,
|
# On autorise maintanant la modification des formations qui ont des semestres verrouillés,
|
||||||
# sauf si cela affect les notes passées (verrouillées):
|
# sauf si cela affect les notes passées (verrouillées):
|
||||||
# - pas de modif des modules utilisés dans des semestres verrouillés
|
# - pas de modif des modules utilisés dans des semestres verrouillés
|
||||||
# - pas de changement des codes d'UE utilisés dans des semestres verrouillés
|
# - pas de changement des codes d'UE utilisés dans des semestres verrouillés
|
||||||
editable = perm_change
|
editable = has_perm_change
|
||||||
tag_editable = (
|
tag_editable = (
|
||||||
current_user.has_permission(Permission.ScoEditFormationTags) or perm_change
|
current_user.has_permission(Permission.ScoEditFormationTags) or has_perm_change
|
||||||
)
|
)
|
||||||
if locked:
|
if locked:
|
||||||
lockicon = scu.icontag("lock32_img", title="verrouillé")
|
lockicon = scu.icontag("lock32_img", title="verrouillé")
|
||||||
else:
|
else:
|
||||||
lockicon = ""
|
lockicon = ""
|
||||||
|
|
||||||
arrow_up, arrow_down, arrow_none = sco_groups.getArrowIconsTags()
|
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
|
||||||
delete_icon = scu.icontag(
|
delete_icon = scu.icontag(
|
||||||
"delete_small_img", title="Supprimer (module inutilisé)", alt="supprimer"
|
"delete_small_img", title="Supprimer (module inutilisé)", alt="supprimer"
|
||||||
)
|
)
|
||||||
|
@ -553,213 +575,20 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||||
H.append(
|
H.append(
|
||||||
'<form><input type="checkbox" class="sco_tag_checkbox">montrer les tags</input></form>'
|
'<form><input type="checkbox" class="sco_tag_checkbox">montrer les tags</input></form>'
|
||||||
)
|
)
|
||||||
|
H.append(
|
||||||
cur_ue_semestre_id = None
|
_ue_table_ues(
|
||||||
iue = 0
|
parcours,
|
||||||
for UE in ue_list:
|
ues,
|
||||||
if UE["ects"]:
|
editable,
|
||||||
UE["ects_str"] = ", %g ECTS" % UE["ects"]
|
tag_editable,
|
||||||
else:
|
has_perm_change,
|
||||||
UE["ects_str"] = ""
|
arrow_up,
|
||||||
if editable:
|
arrow_down,
|
||||||
klass = "span_apo_edit"
|
arrow_none,
|
||||||
else:
|
delete_icon,
|
||||||
klass = ""
|
delete_disabled_icon,
|
||||||
UE["code_apogee_str"] = (
|
|
||||||
""", Apo: <span class="%s" data-url="edit_ue_set_code_apogee" id="%s" data-placeholder="%s">"""
|
|
||||||
% (klass, UE["ue_id"], scu.APO_MISSING_CODE_STR)
|
|
||||||
+ (UE["code_apogee"] or "")
|
|
||||||
+ "</span>"
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
if cur_ue_semestre_id != UE["semestre_id"]:
|
|
||||||
cur_ue_semestre_id = UE["semestre_id"]
|
|
||||||
if iue > 0:
|
|
||||||
H.append("</ul>")
|
|
||||||
if UE["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT:
|
|
||||||
lab = "Pas d'indication de semestre:"
|
|
||||||
else:
|
|
||||||
lab = "Semestre %s:" % UE["semestre_id"]
|
|
||||||
H.append('<div class="ue_list_tit_sem">%s</div>' % lab)
|
|
||||||
H.append('<ul class="notes_ue_list">')
|
|
||||||
H.append('<li class="notes_ue_list">')
|
|
||||||
if iue != 0 and editable:
|
|
||||||
H.append(
|
|
||||||
'<a href="ue_move?ue_id=%s&after=0" class="aud">%s</a>'
|
|
||||||
% (UE["ue_id"], arrow_up)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
H.append(arrow_none)
|
|
||||||
if iue < len(ue_list) - 1 and editable:
|
|
||||||
H.append(
|
|
||||||
'<a href="ue_move?ue_id=%s&after=1" class="aud">%s</a>'
|
|
||||||
% (UE["ue_id"], arrow_down)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
H.append(arrow_none)
|
|
||||||
iue += 1
|
|
||||||
UE["acro_titre"] = str(UE["acronyme"])
|
|
||||||
if UE["titre"] != UE["acronyme"]:
|
|
||||||
UE["acro_titre"] += " " + str(UE["titre"])
|
|
||||||
H.append(
|
|
||||||
"""%(acro_titre)s <span class="ue_code">(code %(ue_code)s%(ects_str)s, coef. %(coefficient)3.2f%(code_apogee_str)s)</span>
|
|
||||||
<span class="ue_coef"></span>
|
|
||||||
"""
|
|
||||||
% UE
|
|
||||||
)
|
|
||||||
|
|
||||||
if UE["type"] != sco_codes_parcours.UE_STANDARD:
|
|
||||||
H.append(
|
|
||||||
'<span class="ue_type">%s</span>'
|
|
||||||
% sco_codes_parcours.UE_TYPE_NAME[UE["type"]]
|
|
||||||
)
|
|
||||||
ue_editable = editable and not ue_is_locked(UE["ue_id"])
|
|
||||||
if ue_editable:
|
|
||||||
H.append(
|
|
||||||
'<a class="stdlink" href="ue_edit?ue_id=%(ue_id)s">modifier</a>' % UE
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
H.append('<span class="locked">[verrouillé]</span>')
|
|
||||||
if not parcours.UE_IS_MODULE:
|
|
||||||
H.append('<ul class="notes_matiere_list">')
|
|
||||||
Matlist = sco_edit_matiere.do_matiere_list(args={"ue_id": UE["ue_id"]})
|
|
||||||
for Mat in Matlist:
|
|
||||||
if not parcours.UE_IS_MODULE:
|
|
||||||
H.append('<li class="notes_matiere_list">')
|
|
||||||
if editable and not sco_edit_matiere.matiere_is_locked(
|
|
||||||
Mat["matiere_id"]
|
|
||||||
):
|
|
||||||
H.append(
|
|
||||||
f"""<a class="stdlink" href="{
|
|
||||||
url_for("notes.matiere_edit",
|
|
||||||
scodoc_dept=g.scodoc_dept, matiere_id=Mat["matiere_id"])
|
|
||||||
}">
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
H.append("%(titre)s" % Mat)
|
|
||||||
if editable and not sco_edit_matiere.matiere_is_locked(
|
|
||||||
Mat["matiere_id"]
|
|
||||||
):
|
|
||||||
H.append("</a>")
|
|
||||||
|
|
||||||
H.append('<ul class="notes_module_list">')
|
|
||||||
Modlist = sco_edit_module.do_module_list(
|
|
||||||
args={"matiere_id": Mat["matiere_id"]}
|
|
||||||
)
|
|
||||||
im = 0
|
|
||||||
for Mod in Modlist:
|
|
||||||
Mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls(
|
|
||||||
Mod["module_id"]
|
|
||||||
)
|
|
||||||
klass = "notes_module_list"
|
|
||||||
if Mod["module_type"] == scu.MODULE_MALUS:
|
|
||||||
klass += " module_malus"
|
|
||||||
H.append('<li class="%s">' % klass)
|
|
||||||
|
|
||||||
H.append('<span class="notes_module_list_buts">')
|
|
||||||
if im != 0 and editable:
|
|
||||||
H.append(
|
|
||||||
'<a href="module_move?module_id=%s&after=0" class="aud">%s</a>'
|
|
||||||
% (Mod["module_id"], arrow_up)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
H.append(arrow_none)
|
|
||||||
if im < len(Modlist) - 1 and editable:
|
|
||||||
H.append(
|
|
||||||
'<a href="module_move?module_id=%s&after=1" class="aud">%s</a>'
|
|
||||||
% (Mod["module_id"], arrow_down)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
H.append(arrow_none)
|
|
||||||
im += 1
|
|
||||||
if Mod["nb_moduleimpls"] == 0 and editable:
|
|
||||||
H.append(
|
|
||||||
'<a class="smallbutton" href="module_delete?module_id=%s">%s</a>'
|
|
||||||
% (Mod["module_id"], delete_icon)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
H.append(delete_disabled_icon)
|
|
||||||
H.append("</span>")
|
|
||||||
|
|
||||||
mod_editable = editable # and not sco_edit_module.module_is_locked( Mod['module_id'])
|
|
||||||
if mod_editable:
|
|
||||||
H.append(
|
|
||||||
'<a class="discretelink" title="Modifier le module numéro %(numero)s, utilisé par %(nb_moduleimpls)d sessions" href="module_edit?module_id=%(module_id)s">'
|
|
||||||
% Mod
|
|
||||||
)
|
|
||||||
H.append(
|
|
||||||
'<span class="formation_module_tit">%s</span>'
|
|
||||||
% scu.join_words(Mod["code"], Mod["titre"])
|
|
||||||
)
|
|
||||||
if mod_editable:
|
|
||||||
H.append("</a>")
|
|
||||||
heurescoef = (
|
|
||||||
"%(heures_cours)s/%(heures_td)s/%(heures_tp)s, coef. %(coefficient)s"
|
|
||||||
% Mod
|
|
||||||
)
|
|
||||||
if mod_editable:
|
|
||||||
klass = "span_apo_edit"
|
|
||||||
else:
|
|
||||||
klass = ""
|
|
||||||
heurescoef += (
|
|
||||||
', Apo: <span class="%s" data-url="edit_module_set_code_apogee" id="%s" data-placeholder="%s">'
|
|
||||||
% (klass, Mod["module_id"], scu.APO_MISSING_CODE_STR)
|
|
||||||
+ (Mod["code_apogee"] or "")
|
|
||||||
+ "</span>"
|
|
||||||
)
|
|
||||||
if tag_editable:
|
|
||||||
tag_cls = "module_tag_editor"
|
|
||||||
else:
|
|
||||||
tag_cls = "module_tag_editor_ro"
|
|
||||||
tag_mk = """<span class="sco_tag_edit"><form><textarea data-module_id="{}" class="{}">{}</textarea></form></span>"""
|
|
||||||
tag_edit = tag_mk.format(
|
|
||||||
Mod["module_id"],
|
|
||||||
tag_cls,
|
|
||||||
",".join(sco_tag_module.module_tag_list(Mod["module_id"])),
|
|
||||||
)
|
|
||||||
H.append(
|
|
||||||
" %s %s" % (parcours.SESSION_NAME, Mod["semestre_id"])
|
|
||||||
+ " (%s)" % heurescoef
|
|
||||||
+ tag_edit
|
|
||||||
)
|
|
||||||
H.append("</li>")
|
|
||||||
if not Modlist:
|
|
||||||
H.append("<li>Aucun module dans cette matière !")
|
|
||||||
if editable:
|
|
||||||
H.append(
|
|
||||||
f"""<a class="stdlink" href="{
|
|
||||||
url_for("notes.matiere_delete",
|
|
||||||
scodoc_dept=g.scodoc_dept, matiere_id=Mat["matiere_id"])}"
|
|
||||||
>supprimer cette matière</a>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
H.append("</li>")
|
|
||||||
if editable: # and ((not parcours.UE_IS_MODULE) or len(Modlist) == 0):
|
|
||||||
H.append(
|
|
||||||
f"""<li> <a class="stdlink" href="{
|
|
||||||
url_for("notes.module_create",
|
|
||||||
scodoc_dept=g.scodoc_dept, matiere_id=Mat["matiere_id"])}"
|
|
||||||
>créer un module</a></li>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
H.append("</ul>")
|
|
||||||
H.append("</li>")
|
|
||||||
if not Matlist:
|
|
||||||
H.append("<li>Aucune matière dans cette UE ! ")
|
|
||||||
if editable:
|
|
||||||
H.append(
|
|
||||||
"""<a class="stdlink" href="ue_delete?ue_id=%(ue_id)s">supprimer l'UE</a>"""
|
|
||||||
% UE
|
|
||||||
)
|
|
||||||
H.append("</li>")
|
|
||||||
if editable and not parcours.UE_IS_MODULE:
|
|
||||||
H.append(
|
|
||||||
'<li><a class="stdlink" href="matiere_create?ue_id=%(ue_id)s">créer une matière</a> </li>'
|
|
||||||
% UE
|
|
||||||
)
|
|
||||||
if not parcours.UE_IS_MODULE:
|
|
||||||
H.append("</ul>")
|
|
||||||
H.append("</ul>")
|
|
||||||
if editable:
|
if editable:
|
||||||
H.append(
|
H.append(
|
||||||
'<ul><li><a class="stdlink" href="ue_create?formation_id=%s">Ajouter une UE</a></li>'
|
'<ul><li><a class="stdlink" href="ue_create?formation_id=%s">Ajouter une UE</a></li>'
|
||||||
|
@ -771,6 +600,27 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||||
)
|
)
|
||||||
H.append("</div>") # formation_ue_list
|
H.append("</div>") # formation_ue_list
|
||||||
|
|
||||||
|
if ues_externes:
|
||||||
|
H.append('<div class="formation_ue_list formation_ue_list_externes">')
|
||||||
|
H.append(
|
||||||
|
'<div class="ue_list_tit">UE externes déclarées (pour information):</div>'
|
||||||
|
)
|
||||||
|
H.append(
|
||||||
|
_ue_table_ues(
|
||||||
|
parcours,
|
||||||
|
ues_externes,
|
||||||
|
editable,
|
||||||
|
tag_editable,
|
||||||
|
has_perm_change,
|
||||||
|
arrow_up,
|
||||||
|
arrow_down,
|
||||||
|
arrow_none,
|
||||||
|
delete_icon,
|
||||||
|
delete_disabled_icon,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
H.append("</div>") # formation_ue_list
|
||||||
|
|
||||||
H.append("<p><ul>")
|
H.append("<p><ul>")
|
||||||
if editable:
|
if editable:
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -792,7 +642,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||||
</p>"""
|
</p>"""
|
||||||
% F
|
% F
|
||||||
)
|
)
|
||||||
if perm_change:
|
if has_perm_change:
|
||||||
H.append(
|
H.append(
|
||||||
"""
|
"""
|
||||||
<h3> <a name="sems">Semestres ou sessions de cette formation</a></h3>
|
<h3> <a name="sems">Semestres ou sessions de cette formation</a></h3>
|
||||||
|
@ -833,6 +683,294 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||||
return "".join(H)
|
return "".join(H)
|
||||||
|
|
||||||
|
|
||||||
|
def _ue_table_ues(
|
||||||
|
parcours,
|
||||||
|
ues,
|
||||||
|
editable,
|
||||||
|
tag_editable,
|
||||||
|
has_perm_change,
|
||||||
|
arrow_up,
|
||||||
|
arrow_down,
|
||||||
|
arrow_none,
|
||||||
|
delete_icon,
|
||||||
|
delete_disabled_icon,
|
||||||
|
):
|
||||||
|
"""Édition de programme: liste des UEs (avec leurs matières et modules)."""
|
||||||
|
H = []
|
||||||
|
cur_ue_semestre_id = None
|
||||||
|
iue = 0
|
||||||
|
for ue in ues:
|
||||||
|
if ue["ects"]:
|
||||||
|
ue["ects_str"] = ", %g ECTS" % ue["ects"]
|
||||||
|
else:
|
||||||
|
ue["ects_str"] = ""
|
||||||
|
if editable:
|
||||||
|
klass = "span_apo_edit"
|
||||||
|
else:
|
||||||
|
klass = ""
|
||||||
|
ue["code_apogee_str"] = (
|
||||||
|
""", Apo: <span class="%s" data-url="edit_ue_set_code_apogee" id="%s" data-placeholder="%s">"""
|
||||||
|
% (klass, ue["ue_id"], scu.APO_MISSING_CODE_STR)
|
||||||
|
+ (ue["code_apogee"] or "")
|
||||||
|
+ "</span>"
|
||||||
|
)
|
||||||
|
|
||||||
|
if cur_ue_semestre_id != ue["semestre_id"]:
|
||||||
|
cur_ue_semestre_id = ue["semestre_id"]
|
||||||
|
if iue > 0:
|
||||||
|
H.append("</ul>")
|
||||||
|
if ue["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT:
|
||||||
|
lab = "Pas d'indication de semestre:"
|
||||||
|
else:
|
||||||
|
lab = "Semestre %s:" % ue["semestre_id"]
|
||||||
|
H.append('<div class="ue_list_tit_sem">%s</div>' % lab)
|
||||||
|
H.append('<ul class="notes_ue_list">')
|
||||||
|
H.append('<li class="notes_ue_list">')
|
||||||
|
if iue != 0 and editable:
|
||||||
|
H.append(
|
||||||
|
'<a href="ue_move?ue_id=%s&after=0" class="aud">%s</a>'
|
||||||
|
% (ue["ue_id"], arrow_up)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append(arrow_none)
|
||||||
|
if iue < len(ues) - 1 and editable:
|
||||||
|
H.append(
|
||||||
|
'<a href="ue_move?ue_id=%s&after=1" class="aud">%s</a>'
|
||||||
|
% (ue["ue_id"], arrow_down)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append(arrow_none)
|
||||||
|
iue += 1
|
||||||
|
ue["acro_titre"] = str(ue["acronyme"])
|
||||||
|
if ue["titre"] != ue["acronyme"]:
|
||||||
|
ue["acro_titre"] += " " + str(ue["titre"])
|
||||||
|
H.append(
|
||||||
|
"""%(acro_titre)s <span class="ue_code">(code %(ue_code)s%(ects_str)s, coef. %(coefficient)3.2f%(code_apogee_str)s)</span>
|
||||||
|
<span class="ue_coef"></span>
|
||||||
|
"""
|
||||||
|
% ue
|
||||||
|
)
|
||||||
|
if ue["type"] != sco_codes_parcours.UE_STANDARD:
|
||||||
|
H.append(
|
||||||
|
'<span class="ue_type">%s</span>'
|
||||||
|
% sco_codes_parcours.UE_TYPE_NAME[ue["type"]]
|
||||||
|
)
|
||||||
|
if ue["is_external"]:
|
||||||
|
# Cas spécial: si l'UE externe a plus d'un module, c'est peut être une UE
|
||||||
|
# qui a été déclarée externe par erreur (ou suite à un bug d'import/export xml)
|
||||||
|
# Dans ce cas, propose de changer le type (même si verrouillée)
|
||||||
|
if len(sco_moduleimpl.moduleimpls_in_external_ue(ue["ue_id"])) > 1:
|
||||||
|
H.append('<span class="ue_is_external">')
|
||||||
|
if has_perm_change:
|
||||||
|
H.append(
|
||||||
|
f"""<a class="stdlink" href="{
|
||||||
|
url_for("notes.ue_set_internal", scodoc_dept=g.scodoc_dept, ue_id=ue["ue_id"])
|
||||||
|
}">transformer en UE ordinaire</a> """
|
||||||
|
)
|
||||||
|
H.append("</span>")
|
||||||
|
ue_editable = editable and not ue_is_locked(ue["ue_id"])
|
||||||
|
if ue_editable:
|
||||||
|
H.append(
|
||||||
|
'<a class="stdlink" href="ue_edit?ue_id=%(ue_id)s">modifier</a>' % ue
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append('<span class="locked">[verrouillé]</span>')
|
||||||
|
H.append(
|
||||||
|
_ue_table_matieres(
|
||||||
|
parcours,
|
||||||
|
ue,
|
||||||
|
editable,
|
||||||
|
tag_editable,
|
||||||
|
arrow_up,
|
||||||
|
arrow_down,
|
||||||
|
arrow_none,
|
||||||
|
delete_icon,
|
||||||
|
delete_disabled_icon,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
|
def _ue_table_matieres(
|
||||||
|
parcours,
|
||||||
|
ue,
|
||||||
|
editable,
|
||||||
|
tag_editable,
|
||||||
|
arrow_up,
|
||||||
|
arrow_down,
|
||||||
|
arrow_none,
|
||||||
|
delete_icon,
|
||||||
|
delete_disabled_icon,
|
||||||
|
):
|
||||||
|
"""Édition de programme: liste des matières (et leurs modules) d'une UE."""
|
||||||
|
H = []
|
||||||
|
if not parcours.UE_IS_MODULE:
|
||||||
|
H.append('<ul class="notes_matiere_list">')
|
||||||
|
matieres = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
|
||||||
|
for mat in matieres:
|
||||||
|
if not parcours.UE_IS_MODULE:
|
||||||
|
H.append('<li class="notes_matiere_list">')
|
||||||
|
if editable and not sco_edit_matiere.matiere_is_locked(mat["matiere_id"]):
|
||||||
|
H.append(
|
||||||
|
f"""<a class="stdlink" href="{
|
||||||
|
url_for("notes.matiere_edit",
|
||||||
|
scodoc_dept=g.scodoc_dept, matiere_id=mat["matiere_id"])
|
||||||
|
}">
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
H.append("%(titre)s" % mat)
|
||||||
|
if editable and not sco_edit_matiere.matiere_is_locked(mat["matiere_id"]):
|
||||||
|
H.append("</a>")
|
||||||
|
|
||||||
|
modules = sco_edit_module.module_list(args={"matiere_id": mat["matiere_id"]})
|
||||||
|
H.append(
|
||||||
|
_ue_table_modules(
|
||||||
|
parcours,
|
||||||
|
mat,
|
||||||
|
modules,
|
||||||
|
editable,
|
||||||
|
tag_editable,
|
||||||
|
arrow_up,
|
||||||
|
arrow_down,
|
||||||
|
arrow_none,
|
||||||
|
delete_icon,
|
||||||
|
delete_disabled_icon,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not matieres:
|
||||||
|
H.append("<li>Aucune matière dans cette UE ! ")
|
||||||
|
if editable:
|
||||||
|
H.append(
|
||||||
|
"""<a class="stdlink" href="ue_delete?ue_id=%(ue_id)s">supprimer l'UE</a>"""
|
||||||
|
% ue
|
||||||
|
)
|
||||||
|
H.append("</li>")
|
||||||
|
if editable and not parcours.UE_IS_MODULE:
|
||||||
|
H.append(
|
||||||
|
'<li><a class="stdlink" href="matiere_create?ue_id=%(ue_id)s">créer une matière</a> </li>'
|
||||||
|
% ue
|
||||||
|
)
|
||||||
|
if not parcours.UE_IS_MODULE:
|
||||||
|
H.append("</ul>")
|
||||||
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
|
def _ue_table_modules(
|
||||||
|
parcours,
|
||||||
|
mat,
|
||||||
|
modules,
|
||||||
|
editable,
|
||||||
|
tag_editable,
|
||||||
|
arrow_up,
|
||||||
|
arrow_down,
|
||||||
|
arrow_none,
|
||||||
|
delete_icon,
|
||||||
|
delete_disabled_icon,
|
||||||
|
):
|
||||||
|
"""Édition de programme: liste des modules d'une matière d'une UE"""
|
||||||
|
H = ['<ul class="notes_module_list">']
|
||||||
|
im = 0
|
||||||
|
for mod in modules:
|
||||||
|
mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls(
|
||||||
|
mod["module_id"]
|
||||||
|
)
|
||||||
|
klass = "notes_module_list"
|
||||||
|
if mod["module_type"] == scu.MODULE_MALUS:
|
||||||
|
klass += " module_malus"
|
||||||
|
H.append('<li class="%s">' % klass)
|
||||||
|
|
||||||
|
H.append('<span class="notes_module_list_buts">')
|
||||||
|
if im != 0 and editable:
|
||||||
|
H.append(
|
||||||
|
'<a href="module_move?module_id=%s&after=0" class="aud">%s</a>'
|
||||||
|
% (mod["module_id"], arrow_up)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append(arrow_none)
|
||||||
|
if im < len(modules) - 1 and editable:
|
||||||
|
H.append(
|
||||||
|
'<a href="module_move?module_id=%s&after=1" class="aud">%s</a>'
|
||||||
|
% (mod["module_id"], arrow_down)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append(arrow_none)
|
||||||
|
im += 1
|
||||||
|
if mod["nb_moduleimpls"] == 0 and editable:
|
||||||
|
H.append(
|
||||||
|
'<a class="smallbutton" href="module_delete?module_id=%s">%s</a>'
|
||||||
|
% (mod["module_id"], delete_icon)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append(delete_disabled_icon)
|
||||||
|
H.append("</span>")
|
||||||
|
|
||||||
|
mod_editable = (
|
||||||
|
editable # and not sco_edit_module.module_is_locked( Mod['module_id'])
|
||||||
|
)
|
||||||
|
if mod_editable:
|
||||||
|
H.append(
|
||||||
|
'<a class="discretelink" title="Modifier le module numéro %(numero)s, utilisé par %(nb_moduleimpls)d sessions" href="module_edit?module_id=%(module_id)s">'
|
||||||
|
% mod
|
||||||
|
)
|
||||||
|
H.append(
|
||||||
|
'<span class="formation_module_tit">%s</span>'
|
||||||
|
% scu.join_words(mod["code"], mod["titre"])
|
||||||
|
)
|
||||||
|
if mod_editable:
|
||||||
|
H.append("</a>")
|
||||||
|
heurescoef = (
|
||||||
|
"%(heures_cours)s/%(heures_td)s/%(heures_tp)s, coef. %(coefficient)s" % mod
|
||||||
|
)
|
||||||
|
if mod_editable:
|
||||||
|
klass = "span_apo_edit"
|
||||||
|
else:
|
||||||
|
klass = ""
|
||||||
|
heurescoef += (
|
||||||
|
', Apo: <span class="%s" data-url="edit_module_set_code_apogee" id="%s" data-placeholder="%s">'
|
||||||
|
% (klass, mod["module_id"], scu.APO_MISSING_CODE_STR)
|
||||||
|
+ (mod["code_apogee"] or "")
|
||||||
|
+ "</span>"
|
||||||
|
)
|
||||||
|
if tag_editable:
|
||||||
|
tag_cls = "module_tag_editor"
|
||||||
|
else:
|
||||||
|
tag_cls = "module_tag_editor_ro"
|
||||||
|
tag_mk = """<span class="sco_tag_edit"><form><textarea data-module_id="{}" class="{}">{}</textarea></form></span>"""
|
||||||
|
tag_edit = tag_mk.format(
|
||||||
|
mod["module_id"],
|
||||||
|
tag_cls,
|
||||||
|
",".join(sco_tag_module.module_tag_list(mod["module_id"])),
|
||||||
|
)
|
||||||
|
H.append(
|
||||||
|
" %s %s" % (parcours.SESSION_NAME, mod["semestre_id"])
|
||||||
|
+ " (%s)" % heurescoef
|
||||||
|
+ tag_edit
|
||||||
|
)
|
||||||
|
H.append("</li>")
|
||||||
|
if not modules:
|
||||||
|
H.append("<li>Aucun module dans cette matière ! ")
|
||||||
|
if editable:
|
||||||
|
H.append(
|
||||||
|
f"""<a class="stdlink" href="{
|
||||||
|
url_for("notes.matiere_delete",
|
||||||
|
scodoc_dept=g.scodoc_dept, matiere_id=mat["matiere_id"])}"
|
||||||
|
>la supprimer</a>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
H.append("</li>")
|
||||||
|
if editable: # and ((not parcours.UE_IS_MODULE) or len(Modlist) == 0):
|
||||||
|
H.append(
|
||||||
|
f"""<li> <a class="stdlink" href="{
|
||||||
|
url_for("notes.module_create",
|
||||||
|
scodoc_dept=g.scodoc_dept, matiere_id=mat["matiere_id"])}"
|
||||||
|
>créer un module</a></li>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
H.append("</ul>")
|
||||||
|
H.append("</li>")
|
||||||
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
|
def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
|
||||||
"""HTML list of UE sharing this code
|
"""HTML list of UE sharing this code
|
||||||
Either ue_code or ue_id may be specified.
|
Either ue_code or ue_id may be specified.
|
||||||
|
@ -840,31 +978,32 @@ def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
|
||||||
"""
|
"""
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
|
|
||||||
|
ue_code = str(ue_code)
|
||||||
if ue_id:
|
if ue_id:
|
||||||
ue = do_ue_list(args={"ue_id": ue_id})[0]
|
ue = ue_list(args={"ue_id": ue_id})[0]
|
||||||
if not ue_code:
|
if not ue_code:
|
||||||
ue_code = ue["ue_code"]
|
ue_code = ue["ue_code"]
|
||||||
F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
|
F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
|
||||||
formation_code = F["formation_code"]
|
formation_code = F["formation_code"]
|
||||||
|
# UE du même code, code formation et departement:
|
||||||
ue_list_all = do_ue_list(args={"ue_code": ue_code})
|
q_ues = (
|
||||||
if ue_id:
|
NotesUE.query.filter_by(ue_code=ue_code)
|
||||||
# retire les UE d'autres formations:
|
.join(NotesUE.formation, aliased=True)
|
||||||
# log('checking ucode %s formation %s' % (ue_code, formation_code))
|
.filter_by(dept_id=g.scodoc_dept_id, formation_code=formation_code)
|
||||||
ue_list = []
|
)
|
||||||
for ue in ue_list_all:
|
|
||||||
F = sco_formations.formation_list(
|
|
||||||
args={"formation_id": ue["formation_id"]}
|
|
||||||
)[0]
|
|
||||||
if formation_code == F["formation_code"]:
|
|
||||||
ue_list.append(ue)
|
|
||||||
else:
|
else:
|
||||||
ue_list = ue_list_all
|
# Toutes les UE du departement avec ce code:
|
||||||
|
q_ues = (
|
||||||
|
NotesUE.query.filter_by(ue_code=ue_code)
|
||||||
|
.join(NotesUE.formation, aliased=True)
|
||||||
|
.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
)
|
||||||
|
|
||||||
if hide_ue_id: # enlève l'ue de depart
|
if hide_ue_id: # enlève l'ue de depart
|
||||||
ue_list = [ue for ue in ue_list if ue["ue_id"] != hide_ue_id]
|
q_ues = q_ues.filter(NotesUE.id != hide_ue_id)
|
||||||
|
|
||||||
if not ue_list:
|
ues = q_ues.all()
|
||||||
|
if not ues:
|
||||||
if ue_id:
|
if ue_id:
|
||||||
return """<span class="ue_share">Seule UE avec code %s</span>""" % ue_code
|
return """<span class="ue_share">Seule UE avec code %s</span>""" % ue_code
|
||||||
else:
|
else:
|
||||||
|
@ -875,18 +1014,13 @@ def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
|
||||||
else:
|
else:
|
||||||
H.append('<span class="ue_share">UE avec le code %s:</span>' % ue_code)
|
H.append('<span class="ue_share">UE avec le code %s:</span>' % ue_code)
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
for ue in ue_list:
|
for ue in ues:
|
||||||
F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
|
|
||||||
H.append(
|
H.append(
|
||||||
'<li>%s (%s) dans <a class="stdlink" href="ue_list?formation_id=%s">%s (%s)</a>, version %s</li>'
|
f"""<li>{ue.acronyme} ({ue.titre}) dans <a class="stdlink"
|
||||||
% (
|
href="{url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=ue.formation.id)}"
|
||||||
ue["acronyme"],
|
>{ue.formation.acronyme} ({ue.formation.titre})</a>, version {ue.formation.version}
|
||||||
ue["titre"],
|
</li>
|
||||||
F["formation_id"],
|
"""
|
||||||
F["acronyme"],
|
|
||||||
F["titre"],
|
|
||||||
F["version"],
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
H.append("</ul>")
|
H.append("</ul>")
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
@ -896,13 +1030,13 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False):
|
||||||
"edit an UE"
|
"edit an UE"
|
||||||
# check
|
# check
|
||||||
ue_id = args["ue_id"]
|
ue_id = args["ue_id"]
|
||||||
ue = do_ue_list({"ue_id": ue_id})[0]
|
ue = ue_list({"ue_id": ue_id})[0]
|
||||||
if (not bypass_lock) and ue_is_locked(ue["ue_id"]):
|
if (not bypass_lock) and ue_is_locked(ue["ue_id"]):
|
||||||
raise ScoLockedFormError()
|
raise ScoLockedFormError()
|
||||||
# check: acronyme unique dans cette formation
|
# check: acronyme unique dans cette formation
|
||||||
if "acronyme" in args:
|
if "acronyme" in args:
|
||||||
new_acro = args["acronyme"]
|
new_acro = args["acronyme"]
|
||||||
ues = do_ue_list({"formation_id": ue["formation_id"], "acronyme": new_acro})
|
ues = ue_list({"formation_id": ue["formation_id"], "acronyme": new_acro})
|
||||||
if ues and ues[0]["ue_id"] != ue_id:
|
if ues and ues[0]["ue_id"] != ue_id:
|
||||||
raise ScoValueError('Acronyme d\'UE "%s" déjà utilisé !' % args["acronyme"])
|
raise ScoValueError('Acronyme d\'UE "%s" déjà utilisé !' % args["acronyme"])
|
||||||
|
|
||||||
|
@ -925,7 +1059,7 @@ def edit_ue_set_code_apogee(id=None, value=None):
|
||||||
value = value.strip("-_ \t")
|
value = value.strip("-_ \t")
|
||||||
log("edit_ue_set_code_apogee: ue_id=%s code_apogee=%s" % (ue_id, value))
|
log("edit_ue_set_code_apogee: ue_id=%s code_apogee=%s" % (ue_id, value))
|
||||||
|
|
||||||
ues = do_ue_list(args={"ue_id": ue_id})
|
ues = ue_list(args={"ue_id": ue_id})
|
||||||
if not ues:
|
if not ues:
|
||||||
return "ue invalide"
|
return "ue invalide"
|
||||||
|
|
||||||
|
@ -956,7 +1090,7 @@ def ue_is_locked(ue_id):
|
||||||
|
|
||||||
|
|
||||||
# ---- Table recap formation
|
# ---- Table recap formation
|
||||||
def formation_table_recap(formation_id, format="html", REQUEST=None):
|
def formation_table_recap(formation_id, format="html"):
|
||||||
"""Table recapitulant formation."""
|
"""Table recapitulant formation."""
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
|
|
||||||
|
@ -965,11 +1099,11 @@ def formation_table_recap(formation_id, format="html", REQUEST=None):
|
||||||
raise ScoValueError("invalid formation_id")
|
raise ScoValueError("invalid formation_id")
|
||||||
F = F[0]
|
F = F[0]
|
||||||
T = []
|
T = []
|
||||||
ue_list = do_ue_list(args={"formation_id": formation_id})
|
ues = ue_list(args={"formation_id": formation_id})
|
||||||
for UE in ue_list:
|
for ue in ues:
|
||||||
Matlist = sco_edit_matiere.do_matiere_list(args={"ue_id": UE["ue_id"]})
|
Matlist = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
|
||||||
for Mat in Matlist:
|
for Mat in Matlist:
|
||||||
Modlist = sco_edit_module.do_module_list(
|
Modlist = sco_edit_module.module_list(
|
||||||
args={"matiere_id": Mat["matiere_id"]}
|
args={"matiere_id": Mat["matiere_id"]}
|
||||||
)
|
)
|
||||||
for Mod in Modlist:
|
for Mod in Modlist:
|
||||||
|
@ -979,7 +1113,7 @@ def formation_table_recap(formation_id, format="html", REQUEST=None):
|
||||||
#
|
#
|
||||||
T.append(
|
T.append(
|
||||||
{
|
{
|
||||||
"UE_acro": UE["acronyme"],
|
"UE_acro": ue["acronyme"],
|
||||||
"Mat_tit": Mat["titre"],
|
"Mat_tit": Mat["titre"],
|
||||||
"Mod_tit": Mod["abbrev"] or Mod["titre"],
|
"Mod_tit": Mod["abbrev"] or Mod["titre"],
|
||||||
"Mod_code": Mod["code"],
|
"Mod_code": Mod["code"],
|
||||||
|
@ -1033,13 +1167,13 @@ def formation_table_recap(formation_id, format="html", REQUEST=None):
|
||||||
caption=title,
|
caption=title,
|
||||||
html_caption=title,
|
html_caption=title,
|
||||||
html_class="table_leftalign",
|
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,
|
page_title=title,
|
||||||
html_title="<h2>" + title + "</h2>",
|
html_title="<h2>" + title + "</h2>",
|
||||||
pdf_title=title,
|
pdf_title=title,
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
|
|
||||||
def ue_list_semestre_ids(ue):
|
def ue_list_semestre_ids(ue):
|
||||||
|
@ -1048,5 +1182,5 @@ def ue_list_semestre_ids(ue):
|
||||||
Mais cela n'a pas toujours été le cas dans les programmes pédagogiques officiels,
|
Mais cela n'a pas toujours été le cas dans les programmes pédagogiques officiels,
|
||||||
aussi ScoDoc laisse le choix.
|
aussi ScoDoc laisse le choix.
|
||||||
"""
|
"""
|
||||||
Modlist = sco_edit_module.do_module_list(args={"ue_id": ue["ue_id"]})
|
Modlist = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
|
||||||
return sorted(list(set([mod["semestre_id"] for mod in Modlist])))
|
return sorted(list(set([mod["semestre_id"] for mod in Modlist])))
|
||||||
|
|
|
@ -33,10 +33,10 @@ XXX incompatible avec les ics HyperPlanning Paris 13 (était pour GPU).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import six.moves.urllib.request, six.moves.urllib.error, six.moves.urllib.parse
|
|
||||||
import traceback
|
|
||||||
import icalendar
|
import icalendar
|
||||||
import pprint
|
import pprint
|
||||||
|
import traceback
|
||||||
|
import urllib
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
|
@ -80,7 +80,7 @@ def formsemestre_load_ics(sem):
|
||||||
ics_data = ""
|
ics_data = ""
|
||||||
else:
|
else:
|
||||||
log("Loading edt from %s" % ics_url)
|
log("Loading edt from %s" % ics_url)
|
||||||
f = six.moves.urllib.request.urlopen(
|
f = urllib.request.urlopen(
|
||||||
ics_url, timeout=5
|
ics_url, timeout=5
|
||||||
) # 5s TODO: add config parameter, eg for slow networks
|
) # 5s TODO: add config parameter, eg for slow networks
|
||||||
ics_data = f.read()
|
ics_data = f.read()
|
||||||
|
@ -123,7 +123,7 @@ def get_edt_transcodage_groups(formsemestre_id):
|
||||||
return edt2sco, sco2edt, msg
|
return edt2sco, sco2edt, msg
|
||||||
|
|
||||||
|
|
||||||
def group_edt_json(group_id, start="", end="", REQUEST=None): # actuellement inutilisé
|
def group_edt_json(group_id, start="", end=""): # actuellement inutilisé
|
||||||
"""EDT complet du semestre, au format JSON
|
"""EDT complet du semestre, au format JSON
|
||||||
TODO: indiquer un groupe
|
TODO: indiquer un groupe
|
||||||
TODO: utiliser start et end (2 dates au format ISO YYYY-MM-DD)
|
TODO: utiliser start et end (2 dates au format ISO YYYY-MM-DD)
|
||||||
|
@ -149,7 +149,7 @@ def group_edt_json(group_id, start="", end="", REQUEST=None): # actuellement in
|
||||||
}
|
}
|
||||||
J.append(d)
|
J.append(d)
|
||||||
|
|
||||||
return scu.sendJSON(REQUEST, J)
|
return scu.sendJSON(J)
|
||||||
|
|
||||||
|
|
||||||
"""XXX
|
"""XXX
|
||||||
|
@ -159,9 +159,7 @@ for e in events:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def experimental_calendar(
|
def experimental_calendar(group_id=None, formsemestre_id=None): # inutilisé
|
||||||
group_id=None, formsemestre_id=None, REQUEST=None
|
|
||||||
): # inutilisé
|
|
||||||
"""experimental page"""
|
"""experimental page"""
|
||||||
return "\n".join(
|
return "\n".join(
|
||||||
[
|
[
|
||||||
|
|
|
@ -32,11 +32,11 @@
|
||||||
Voir sco_apogee_csv.py pour la structure du fichier Apogée.
|
Voir sco_apogee_csv.py pour la structure du fichier Apogée.
|
||||||
|
|
||||||
Stockage: utilise sco_archive.py
|
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
|
pour une maquette de l'année scolaire 2016, semestre 1, etape V3ASR
|
||||||
|
|
||||||
ou bien (à partir de ScoDoc 1678) :
|
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.
|
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.
|
La version VDI sera ignorée sauf si elle est indiquée dans l'étape du semestre.
|
||||||
|
|
|
@ -32,7 +32,7 @@ import io
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
import flask
|
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
|
# from werkzeug.utils import send_file
|
||||||
|
|
||||||
|
@ -62,7 +62,6 @@ def apo_semset_maq_status(
|
||||||
block_export_res_ues=False,
|
block_export_res_ues=False,
|
||||||
block_export_res_modules=False,
|
block_export_res_modules=False,
|
||||||
block_export_res_sdj=True,
|
block_export_res_sdj=True,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Page statut / tableau de bord"""
|
"""Page statut / tableau de bord"""
|
||||||
if not semset_id:
|
if not semset_id:
|
||||||
|
@ -83,7 +82,7 @@ def apo_semset_maq_status(
|
||||||
|
|
||||||
prefs = sco_preferences.SemPreferences()
|
prefs = sco_preferences.SemPreferences()
|
||||||
|
|
||||||
tab_archives = table_apo_csv_list(semset, REQUEST=REQUEST)
|
tab_archives = table_apo_csv_list(semset)
|
||||||
|
|
||||||
(
|
(
|
||||||
ok_for_export,
|
ok_for_export,
|
||||||
|
@ -250,7 +249,7 @@ def apo_semset_maq_status(
|
||||||
"""<form name="f" method="get" action="%s">
|
"""<form name="f" method="get" action="%s">
|
||||||
<input type="hidden" name="semset_id" value="%s"></input>
|
<input type="hidden" name="semset_id" value="%s"></input>
|
||||||
<div><input type="checkbox" name="allow_missing_apo" value="1" onchange="document.f.submit()" """
|
<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:
|
if allow_missing_apo:
|
||||||
H.append("checked")
|
H.append("checked")
|
||||||
|
@ -357,7 +356,7 @@ def apo_semset_maq_status(
|
||||||
H.append(
|
H.append(
|
||||||
", ".join(
|
", ".join(
|
||||||
[
|
[
|
||||||
'<a class="stdlink" href="ue_list?formation_id=%(formation_id)s">%(acronyme)s v%(version)s</a>'
|
'<a class="stdlink" href="ue_table?formation_id=%(formation_id)s">%(acronyme)s v%(version)s</a>'
|
||||||
% f
|
% f
|
||||||
for f in formations
|
for f in formations
|
||||||
]
|
]
|
||||||
|
@ -430,7 +429,7 @@ def apo_semset_maq_status(
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def table_apo_csv_list(semset, REQUEST=None):
|
def table_apo_csv_list(semset):
|
||||||
"""Table des archives (triée par date d'archivage)"""
|
"""Table des archives (triée par date d'archivage)"""
|
||||||
annee_scolaire = semset["annee_scolaire"]
|
annee_scolaire = semset["annee_scolaire"]
|
||||||
sem_id = semset["sem_id"]
|
sem_id = semset["sem_id"]
|
||||||
|
@ -476,7 +475,7 @@ def table_apo_csv_list(semset, REQUEST=None):
|
||||||
rows=T,
|
rows=T,
|
||||||
html_class="table_leftalign apo_maq_list",
|
html_class="table_leftalign apo_maq_list",
|
||||||
html_sortable=True,
|
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',
|
# caption='Maquettes enregistrées',
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
|
@ -484,7 +483,7 @@ def table_apo_csv_list(semset, REQUEST=None):
|
||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
|
||||||
def view_apo_etuds(semset_id, title="", nip_list="", format="html", REQUEST=None):
|
def view_apo_etuds(semset_id, title="", nip_list="", format="html"):
|
||||||
"""Table des étudiants Apogée par nips
|
"""Table des étudiants Apogée par nips
|
||||||
nip_list est une chaine, codes nip séparés par des ,
|
nip_list est une chaine, codes nip séparés par des ,
|
||||||
"""
|
"""
|
||||||
|
@ -517,11 +516,10 @@ def view_apo_etuds(semset_id, title="", nip_list="", format="html", REQUEST=None
|
||||||
etuds=list(etuds.values()),
|
etuds=list(etuds.values()),
|
||||||
keys=("nip", "etape_apo", "nom", "prenom", "inscriptions_scodoc"),
|
keys=("nip", "etape_apo", "nom", "prenom", "inscriptions_scodoc"),
|
||||||
format=format,
|
format=format,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def view_scodoc_etuds(semset_id, title="", nip_list="", format="html", REQUEST=None):
|
def view_scodoc_etuds(semset_id, title="", nip_list="", format="html"):
|
||||||
"""Table des étudiants ScoDoc par nips ou etudids"""
|
"""Table des étudiants ScoDoc par nips ou etudids"""
|
||||||
if not isinstance(nip_list, str):
|
if not isinstance(nip_list, str):
|
||||||
nip_list = str(nip_list)
|
nip_list = str(nip_list)
|
||||||
|
@ -541,13 +539,10 @@ def view_scodoc_etuds(semset_id, title="", nip_list="", format="html", REQUEST=N
|
||||||
etuds=etuds,
|
etuds=etuds,
|
||||||
keys=("code_nip", "nom", "prenom"),
|
keys=("code_nip", "nom", "prenom"),
|
||||||
format=format,
|
format=format,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _view_etuds_page(
|
def _view_etuds_page(semset_id, title="", etuds=[], keys=(), format="html"):
|
||||||
semset_id, title="", etuds=[], keys=(), format="html", REQUEST=None
|
|
||||||
):
|
|
||||||
# Tri les étudiants par nom:
|
# Tri les étudiants par nom:
|
||||||
if etuds:
|
if etuds:
|
||||||
etuds.sort(key=lambda x: (x["nom"], x["prenom"]))
|
etuds.sort(key=lambda x: (x["nom"], x["prenom"]))
|
||||||
|
@ -578,7 +573,7 @@ def _view_etuds_page(
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
H.append(tab.html())
|
H.append(tab.html())
|
||||||
|
|
||||||
|
@ -590,9 +585,7 @@ def _view_etuds_page(
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
def view_apo_csv_store(
|
def view_apo_csv_store(semset_id="", csvfile=None, data="", autodetect=False):
|
||||||
semset_id="", csvfile=None, data="", autodetect=False, REQUEST=None
|
|
||||||
):
|
|
||||||
"""Store CSV data
|
"""Store CSV data
|
||||||
Le semset identifie l'annee scolaire et le semestre
|
Le semset identifie l'annee scolaire et le semestre
|
||||||
Si csvfile, lit depuis FILE, sinon utilise data
|
Si csvfile, lit depuis FILE, sinon utilise data
|
||||||
|
@ -627,7 +620,7 @@ def view_apo_csv_store(
|
||||||
return flask.redirect("apo_semset_maq_status?semset_id=" + semset_id)
|
return flask.redirect("apo_semset_maq_status?semset_id=" + semset_id)
|
||||||
|
|
||||||
|
|
||||||
def view_apo_csv_download_and_store(etape_apo="", semset_id="", REQUEST=None):
|
def view_apo_csv_download_and_store(etape_apo="", semset_id=""):
|
||||||
"""Download maquette and store it"""
|
"""Download maquette and store it"""
|
||||||
if not semset_id:
|
if not semset_id:
|
||||||
raise ValueError("invalid null semset_id")
|
raise ValueError("invalid null semset_id")
|
||||||
|
@ -639,17 +632,15 @@ def view_apo_csv_download_and_store(etape_apo="", semset_id="", REQUEST=None):
|
||||||
# here, data is utf8
|
# here, data is utf8
|
||||||
# but we store and generate latin1 files, to ease further import in Apogée
|
# but we store and generate latin1 files, to ease further import in Apogée
|
||||||
data = data.decode(APO_PORTAL_ENCODING).encode(APO_INPUT_ENCODING) # XXX #py3
|
data = data.decode(APO_PORTAL_ENCODING).encode(APO_INPUT_ENCODING) # XXX #py3
|
||||||
return view_apo_csv_store(semset_id, data=data, autodetect=False, REQUEST=REQUEST)
|
return view_apo_csv_store(semset_id, data=data, autodetect=False)
|
||||||
|
|
||||||
|
|
||||||
def view_apo_csv_delete(
|
def view_apo_csv_delete(etape_apo="", semset_id="", dialog_confirmed=False):
|
||||||
etape_apo="", semset_id="", dialog_confirmed=False, REQUEST=None
|
|
||||||
):
|
|
||||||
"""Delete CSV file"""
|
"""Delete CSV file"""
|
||||||
if not semset_id:
|
if not semset_id:
|
||||||
raise ValueError("invalid null semset_id")
|
raise ValueError("invalid null semset_id")
|
||||||
semset = sco_semset.SemSet(semset_id=semset_id)
|
semset = sco_semset.SemSet(semset_id=semset_id)
|
||||||
dest_url = "apo_semset_maq_status?semset_id=" + semset_id
|
dest_url = f"apo_semset_maq_status?semset_id={semset_id}"
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
"""<h2>Confirmer la suppression du fichier étape <tt>%s</tt>?</h2>
|
"""<h2>Confirmer la suppression du fichier étape <tt>%s</tt>?</h2>
|
||||||
|
@ -667,7 +658,7 @@ def view_apo_csv_delete(
|
||||||
return flask.redirect(dest_url + "&head_message=Archive%20supprimée")
|
return flask.redirect(dest_url + "&head_message=Archive%20supprimée")
|
||||||
|
|
||||||
|
|
||||||
def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
|
def view_apo_csv(etape_apo="", semset_id="", format="html"):
|
||||||
"""Visualise une maquette stockée
|
"""Visualise une maquette stockée
|
||||||
Si format="raw", renvoie le fichier maquette tel quel
|
Si format="raw", renvoie le fichier maquette tel quel
|
||||||
"""
|
"""
|
||||||
|
@ -678,7 +669,8 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
|
||||||
sem_id = semset["sem_id"]
|
sem_id = semset["sem_id"]
|
||||||
csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
|
csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
|
||||||
if format == "raw":
|
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"])
|
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
|
||||||
|
|
||||||
(
|
(
|
||||||
|
@ -746,14 +738,15 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
|
||||||
rows=etuds,
|
rows=etuds,
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
html_class="table_leftalign apo_maq_table",
|
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,
|
filename="students_" + etape_apo,
|
||||||
caption="Etudiants Apogée en " + etape_apo,
|
caption="Etudiants Apogée en " + etape_apo,
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
H += [
|
H += [
|
||||||
tab.html(),
|
tab.html(),
|
||||||
|
@ -768,7 +761,7 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
# called from Web
|
# called from Web (GET)
|
||||||
def apo_csv_export_results(
|
def apo_csv_export_results(
|
||||||
semset_id,
|
semset_id,
|
||||||
block_export_res_etape=False,
|
block_export_res_etape=False,
|
||||||
|
|
|
@ -31,27 +31,21 @@
|
||||||
# Ancien module "scolars"
|
# Ancien module "scolars"
|
||||||
import os
|
import os
|
||||||
import time
|
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 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
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import SCO_ENCODING
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
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 safehtml
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
from flask_mail import Message
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
from app import mail
|
|
||||||
|
|
||||||
MONTH_NAMES_ABBREV = [
|
MONTH_NAMES_ABBREV = [
|
||||||
"Jan ",
|
"Jan ",
|
||||||
|
@ -158,7 +152,7 @@ def format_nom(s, uppercase=True):
|
||||||
def input_civilite(s):
|
def input_civilite(s):
|
||||||
"""Converts external representation of civilite to internal:
|
"""Converts external representation of civilite to internal:
|
||||||
'M', 'F', or 'X' (and nothing else).
|
'M', 'F', or 'X' (and nothing else).
|
||||||
Raises valueError if conversion fails.
|
Raises ScoValueError if conversion fails.
|
||||||
"""
|
"""
|
||||||
s = s.upper().strip()
|
s = s.upper().strip()
|
||||||
if s in ("M", "M.", "MR", "H"):
|
if s in ("M", "M.", "MR", "H"):
|
||||||
|
@ -167,12 +161,13 @@ def input_civilite(s):
|
||||||
return "F"
|
return "F"
|
||||||
elif s == "X" or not s:
|
elif s == "X" or not s:
|
||||||
return "X"
|
return "X"
|
||||||
raise ValueError("valeur invalide pour la civilité: %s" % s)
|
raise ScoValueError("valeur invalide pour la civilité: %s" % s)
|
||||||
|
|
||||||
|
|
||||||
def format_civilite(civilite):
|
def format_civilite(civilite):
|
||||||
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
||||||
personne ne souhaitant pas d'affichage)
|
personne ne souhaitant pas d'affichage).
|
||||||
|
Raises ScoValueError if conversion fails.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return {
|
return {
|
||||||
|
@ -181,7 +176,7 @@ def format_civilite(civilite):
|
||||||
"X": "",
|
"X": "",
|
||||||
}[civilite]
|
}[civilite]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError("valeur invalide pour la civilité: %s" % civilite)
|
raise ScoValueError("valeur invalide pour la civilité: %s" % civilite)
|
||||||
|
|
||||||
|
|
||||||
def format_lycee(nomlycee):
|
def format_lycee(nomlycee):
|
||||||
|
@ -256,7 +251,6 @@ _identiteEditor = ndb.EditableTable(
|
||||||
"photo_filename",
|
"photo_filename",
|
||||||
"code_ine",
|
"code_ine",
|
||||||
"code_nip",
|
"code_nip",
|
||||||
"scodoc7_id",
|
|
||||||
),
|
),
|
||||||
filter_dept=True,
|
filter_dept=True,
|
||||||
sortkey="nom",
|
sortkey="nom",
|
||||||
|
@ -321,9 +315,7 @@ def check_nom_prenom(cnx, nom="", prenom="", etudid=None):
|
||||||
return True, len(res)
|
return True, len(res)
|
||||||
|
|
||||||
|
|
||||||
def _check_duplicate_code(
|
def _check_duplicate_code(cnx, args, code_name, disable_notify=False, edit=True):
|
||||||
cnx, args, code_name, disable_notify=False, edit=True, REQUEST=None
|
|
||||||
):
|
|
||||||
etudid = args.get("etudid", None)
|
etudid = args.get("etudid", None)
|
||||||
if args.get(code_name, None):
|
if args.get(code_name, None):
|
||||||
etuds = identite_list(cnx, {code_name: str(args[code_name])})
|
etuds = identite_list(cnx, {code_name: str(args[code_name])})
|
||||||
|
@ -345,31 +337,33 @@ def _check_duplicate_code(
|
||||||
)
|
)
|
||||||
if etudid:
|
if etudid:
|
||||||
OK = "retour à la fiche étudiant"
|
OK = "retour à la fiche étudiant"
|
||||||
dest_url = "ficheEtud"
|
dest_endpoint = "scolar.ficheEtud"
|
||||||
parameters = {"etudid": etudid}
|
parameters = {"etudid": etudid}
|
||||||
else:
|
else:
|
||||||
if "tf_submitted" in args:
|
if "tf_submitted" in args:
|
||||||
del args["tf_submitted"]
|
del args["tf_submitted"]
|
||||||
OK = "Continuer"
|
OK = "Continuer"
|
||||||
dest_url = "etudident_create_form"
|
dest_endpoint = "scolar.etudident_create_form"
|
||||||
parameters = args
|
parameters = args
|
||||||
else:
|
else:
|
||||||
OK = "Annuler"
|
OK = "Annuler"
|
||||||
dest_url = ""
|
dest_endpoint = "notes.index_html"
|
||||||
parameters = {}
|
parameters = {}
|
||||||
if not disable_notify:
|
if not disable_notify:
|
||||||
err_page = scu.confirm_dialog(
|
err_page = f"""<h3><h3>Code étudiant ({code_name}) dupliqué !</h3>
|
||||||
message="""<h3>Code étudiant (%s) dupliqué !</h3>""" % code_name,
|
<p class="help">Le {code_name} {args[code_name]} est déjà utilisé: un seul étudiant peut avoir
|
||||||
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>"""
|
ce code. Vérifier votre valeur ou supprimer l'autre étudiant avec cette valeur.
|
||||||
% (code_name, args[code_name])
|
</p>
|
||||||
+ "</li><li>".join(listh)
|
<ul><li>
|
||||||
+ "</li></ul><p>",
|
{ '</li><li>'.join(listh) }
|
||||||
OK=OK,
|
</li></ul>
|
||||||
dest_url=dest_url,
|
<p>
|
||||||
parameters=parameters,
|
<a href="{ url_for(dest_endpoint, scodoc_dept=g.scodoc_dept, **parameters) }
|
||||||
)
|
">{OK}</a>
|
||||||
|
</p>
|
||||||
|
"""
|
||||||
else:
|
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]))
|
log("*** error: code %s duplique: %s" % (code_name, args[code_name]))
|
||||||
raise ScoGenError(err_page)
|
raise ScoGenError(err_page)
|
||||||
|
|
||||||
|
@ -379,15 +373,15 @@ def _check_civilite(args):
|
||||||
args["civilite"] = input_civilite(civilite) # TODO: A faire valider
|
args["civilite"] = input_civilite(civilite) # TODO: A faire valider
|
||||||
|
|
||||||
|
|
||||||
def identite_edit(cnx, args, disable_notify=False, REQUEST=None):
|
def identite_edit(cnx, args, disable_notify=False):
|
||||||
"""Modifie l'identite d'un étudiant.
|
"""Modifie l'identite d'un étudiant.
|
||||||
Si pref notification et difference, envoie message notification, sauf si disable_notify
|
Si pref notification et difference, envoie message notification, sauf si disable_notify
|
||||||
"""
|
"""
|
||||||
_check_duplicate_code(
|
_check_duplicate_code(
|
||||||
cnx, args, "code_nip", disable_notify=disable_notify, edit=True, REQUEST=REQUEST
|
cnx, args, "code_nip", disable_notify=disable_notify, edit=True
|
||||||
)
|
)
|
||||||
_check_duplicate_code(
|
_check_duplicate_code(
|
||||||
cnx, args, "code_ine", disable_notify=disable_notify, edit=True, REQUEST=REQUEST
|
cnx, args, "code_ine", disable_notify=disable_notify, edit=True
|
||||||
)
|
)
|
||||||
notify_to = None
|
notify_to = None
|
||||||
if not disable_notify:
|
if not disable_notify:
|
||||||
|
@ -415,10 +409,10 @@ def identite_edit(cnx, args, disable_notify=False, REQUEST=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def identite_create(cnx, args, REQUEST=None):
|
def identite_create(cnx, args):
|
||||||
"check unique etudid, then create"
|
"check unique etudid, then create"
|
||||||
_check_duplicate_code(cnx, args, "code_nip", edit=False, REQUEST=REQUEST)
|
_check_duplicate_code(cnx, args, "code_nip", edit=False)
|
||||||
_check_duplicate_code(cnx, args, "code_ine", edit=False, REQUEST=REQUEST)
|
_check_duplicate_code(cnx, args, "code_ine", edit=False)
|
||||||
_check_civilite(args)
|
_check_civilite(args)
|
||||||
|
|
||||||
if "etudid" in args:
|
if "etudid" in args:
|
||||||
|
@ -456,7 +450,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: sending notification to %s" % email_addr)
|
||||||
log("notify_etud_change: subject: %s" % subject)
|
log("notify_etud_change: subject: %s" % subject)
|
||||||
log(txt)
|
log(txt)
|
||||||
mail.send_email(
|
email.send_email(
|
||||||
subject, sco_preferences.get_preference("email_from_addr"), [email_addr], txt
|
subject, sco_preferences.get_preference("email_from_addr"), [email_addr], txt
|
||||||
)
|
)
|
||||||
return txt
|
return txt
|
||||||
|
@ -559,7 +553,6 @@ _admissionEditor = ndb.EditableTable(
|
||||||
"villelycee",
|
"villelycee",
|
||||||
"codepostallycee",
|
"codepostallycee",
|
||||||
"codelycee",
|
"codelycee",
|
||||||
"debouche",
|
|
||||||
"type_admission",
|
"type_admission",
|
||||||
"boursier_prec",
|
"boursier_prec",
|
||||||
),
|
),
|
||||||
|
@ -583,8 +576,8 @@ admission_edit = _admissionEditor.edit
|
||||||
|
|
||||||
# Edition simultanee de identite et admission
|
# Edition simultanee de identite et admission
|
||||||
class EtudIdentEditor(object):
|
class EtudIdentEditor(object):
|
||||||
def create(self, cnx, args, REQUEST=None):
|
def create(self, cnx, args):
|
||||||
etudid = identite_create(cnx, args, REQUEST)
|
etudid = identite_create(cnx, args)
|
||||||
args["etudid"] = etudid
|
args["etudid"] = etudid
|
||||||
admission_create(cnx, args)
|
admission_create(cnx, args)
|
||||||
return etudid
|
return etudid
|
||||||
|
@ -615,8 +608,8 @@ class EtudIdentEditor(object):
|
||||||
res.sort(key=itemgetter("nom", "prenom"))
|
res.sort(key=itemgetter("nom", "prenom"))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def edit(self, cnx, args, disable_notify=False, REQUEST=None):
|
def edit(self, cnx, args, disable_notify=False):
|
||||||
identite_edit(cnx, args, disable_notify=disable_notify, REQUEST=REQUEST)
|
identite_edit(cnx, args, disable_notify=disable_notify)
|
||||||
if "adm_id" in args: # safety net
|
if "adm_id" in args: # safety net
|
||||||
admission_edit(cnx, args)
|
admission_edit(cnx, args)
|
||||||
|
|
||||||
|
@ -656,11 +649,17 @@ def make_etud_args(etudid=None, code_nip=None, use_request=True, raise_exc=True)
|
||||||
return args
|
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:
|
def get_etud_info(etudid=False, code_nip=False, filled=False) -> list:
|
||||||
"""infos sur un etudiant (API). If not foud, returns empty list.
|
"""infos sur un etudiant (API). If not found, returns empty list.
|
||||||
On peut specifier etudid ou code_nip
|
On peut specifier etudid ou code_nip
|
||||||
ou bien cherche dans REQUEST.form: etudid, code_nip, code_ine
|
ou bien cherche dans les argumenst de la requête courante:
|
||||||
(dans cet ordre).
|
etudid, code_nip, code_ine (dans cet ordre).
|
||||||
"""
|
"""
|
||||||
if etudid is None:
|
if etudid is None:
|
||||||
return []
|
return []
|
||||||
|
@ -673,7 +672,20 @@ def get_etud_info(etudid=False, code_nip=False, filled=False) -> list:
|
||||||
return etud
|
return etud
|
||||||
|
|
||||||
|
|
||||||
def create_etud(cnx, args={}, REQUEST=None):
|
# Optim par cache local, utilité non prouvée mais
|
||||||
|
# on s'oriente vers un cahce persistent dans Redis ou bien l'utilisation de NT
|
||||||
|
# def get_etud_info_filled_by_etudid(etudid, cnx=None) -> dict:
|
||||||
|
# """Infos sur un étudiant, avec cache local à la requête"""
|
||||||
|
# if etudid in g.stored_etud_info:
|
||||||
|
# return g.stored_etud_info[etudid]
|
||||||
|
# cnx = cnx or ndb.GetDBConnexion()
|
||||||
|
# etud = etudident_list(cnx, args={"etudid": etudid})
|
||||||
|
# fill_etuds_info(etud)
|
||||||
|
# g.stored_etud_info[etudid] = etud[0]
|
||||||
|
# return etud[0]
|
||||||
|
|
||||||
|
|
||||||
|
def create_etud(cnx, args={}):
|
||||||
"""Creation d'un étudiant. génère aussi évenement et "news".
|
"""Creation d'un étudiant. génère aussi évenement et "news".
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -685,7 +697,7 @@ def create_etud(cnx, args={}, REQUEST=None):
|
||||||
from app.scodoc import sco_news
|
from app.scodoc import sco_news
|
||||||
|
|
||||||
# creation d'un etudiant
|
# creation d'un etudiant
|
||||||
etudid = etudident_create(cnx, args, REQUEST=REQUEST)
|
etudid = etudident_create(cnx, args)
|
||||||
# crée une adresse vide (chaque etudiant doit etre dans la table "adresse" !)
|
# crée une adresse vide (chaque etudiant doit etre dans la table "adresse" !)
|
||||||
_ = adresse_create(
|
_ = adresse_create(
|
||||||
cnx,
|
cnx,
|
||||||
|
@ -819,8 +831,8 @@ appreciations_edit = _appreciationsEditor.edit
|
||||||
def read_etablissements():
|
def read_etablissements():
|
||||||
filename = os.path.join(scu.SCO_TOOLS_DIR, scu.CONFIG.ETABL_FILENAME)
|
filename = os.path.join(scu.SCO_TOOLS_DIR, scu.CONFIG.ETABL_FILENAME)
|
||||||
log("reading %s" % filename)
|
log("reading %s" % filename)
|
||||||
f = open(filename)
|
with open(filename) as f:
|
||||||
L = [x[:-1].split(";") for x in f]
|
L = [x[:-1].split(";") for x in f]
|
||||||
E = {}
|
E = {}
|
||||||
for l in L[1:]:
|
for l in L[1:]:
|
||||||
E[l[0]] = {
|
E[l[0]] = {
|
||||||
|
|
|
@ -31,7 +31,7 @@ import datetime
|
||||||
import operator
|
import operator
|
||||||
import pprint
|
import pprint
|
||||||
import time
|
import time
|
||||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
import urllib
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
@ -179,7 +179,7 @@ def do_evaluation_list(args, sortkey=None):
|
||||||
|
|
||||||
def do_evaluation_list_in_formsemestre(formsemestre_id):
|
def do_evaluation_list_in_formsemestre(formsemestre_id):
|
||||||
"list evaluations in this formsemestre"
|
"list evaluations in this formsemestre"
|
||||||
mods = sco_moduleimpl.do_moduleimpl_list(formsemestre_id=formsemestre_id)
|
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
evals = []
|
evals = []
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
evals += do_evaluation_list(args={"moduleimpl_id": mod["moduleimpl_id"]})
|
evals += do_evaluation_list(args={"moduleimpl_id": mod["moduleimpl_id"]})
|
||||||
|
@ -213,7 +213,7 @@ def _check_evaluation_args(args):
|
||||||
jour = args.get("jour", None)
|
jour = args.get("jour", None)
|
||||||
args["jour"] = jour
|
args["jour"] = jour
|
||||||
if jour:
|
if jour:
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||||
d, m, y = [int(x) for x in sem["date_debut"].split("/")]
|
d, m, y = [int(x) for x in sem["date_debut"].split("/")]
|
||||||
date_debut = datetime.date(y, m, d)
|
date_debut = datetime.date(y, m, d)
|
||||||
|
@ -250,7 +250,6 @@ def do_evaluation_create(
|
||||||
publish_incomplete=None,
|
publish_incomplete=None,
|
||||||
evaluation_type=None,
|
evaluation_type=None,
|
||||||
numero=None,
|
numero=None,
|
||||||
REQUEST=None,
|
|
||||||
**kw, # ceci pour absorber les arguments excedentaires de tf #sco8
|
**kw, # ceci pour absorber les arguments excedentaires de tf #sco8
|
||||||
):
|
):
|
||||||
"""Create an evaluation"""
|
"""Create an evaluation"""
|
||||||
|
@ -274,13 +273,13 @@ def do_evaluation_create(
|
||||||
if args["jour"]:
|
if args["jour"]:
|
||||||
next_eval = None
|
next_eval = None
|
||||||
t = (
|
t = (
|
||||||
ndb.DateDMYtoISO(args["jour"]),
|
ndb.DateDMYtoISO(args["jour"], null_is_empty=True),
|
||||||
ndb.TimetoISO8601(args["heure_debut"]),
|
ndb.TimetoISO8601(args["heure_debut"], null_is_empty=True),
|
||||||
)
|
)
|
||||||
for e in ModEvals:
|
for e in ModEvals:
|
||||||
if (
|
if (
|
||||||
ndb.DateDMYtoISO(e["jour"]),
|
ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
|
||||||
ndb.TimetoISO8601(e["heure_debut"]),
|
ndb.TimetoISO8601(e["heure_debut"], null_is_empty=True),
|
||||||
) > t:
|
) > t:
|
||||||
next_eval = e
|
next_eval = e
|
||||||
break
|
break
|
||||||
|
@ -302,8 +301,8 @@ def do_evaluation_create(
|
||||||
r = _evaluationEditor.create(cnx, args)
|
r = _evaluationEditor.create(cnx, args)
|
||||||
|
|
||||||
# news
|
# news
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
|
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||||
mod["moduleimpl_id"] = M["moduleimpl_id"]
|
mod["moduleimpl_id"] = M["moduleimpl_id"]
|
||||||
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
|
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
|
||||||
sco_news.add(
|
sco_news.add(
|
||||||
|
@ -333,7 +332,7 @@ def do_evaluation_edit(args):
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
_evaluationEditor.edit(cnx, args)
|
_evaluationEditor.edit(cnx, args)
|
||||||
# inval cache pour ce semestre
|
# inval cache pour ce semestre
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
|
sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
|
||||||
|
|
||||||
|
|
||||||
|
@ -358,10 +357,10 @@ def do_evaluation_delete(evaluation_id):
|
||||||
|
|
||||||
_evaluationEditor.delete(cnx, evaluation_id)
|
_evaluationEditor.delete(cnx, evaluation_id)
|
||||||
# inval cache pour ce semestre
|
# inval cache pour ce semestre
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
|
sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
|
||||||
# news
|
# news
|
||||||
mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
|
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||||
mod["moduleimpl_id"] = M["moduleimpl_id"]
|
mod["moduleimpl_id"] = M["moduleimpl_id"]
|
||||||
mod["url"] = (
|
mod["url"] = (
|
||||||
scu.NotesURL() + "/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
|
scu.NotesURL() + "/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
|
||||||
|
@ -374,9 +373,6 @@ def do_evaluation_delete(evaluation_id):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_DEE_TOT = 0
|
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition=False):
|
def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition=False):
|
||||||
"""donne infos sur l'etat du evaluation
|
"""donne infos sur l'etat du evaluation
|
||||||
{ nb_inscrits, nb_notes, nb_abs, nb_neutre, nb_att,
|
{ nb_inscrits, nb_notes, nb_abs, nb_neutre, nb_att,
|
||||||
|
@ -413,8 +409,8 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition=
|
||||||
last_modif = None
|
last_modif = None
|
||||||
# ---- Liste des groupes complets et incomplets
|
# ---- Liste des groupes complets et incomplets
|
||||||
E = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
|
E = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
Mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
|
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||||
is_malus = Mod["module_type"] == scu.MODULE_MALUS # True si module de malus
|
is_malus = Mod["module_type"] == scu.MODULE_MALUS # True si module de malus
|
||||||
formsemestre_id = M["formsemestre_id"]
|
formsemestre_id = M["formsemestre_id"]
|
||||||
# Si partition_id is None, prend 'all' ou bien la premiere:
|
# Si partition_id is None, prend 'all' ou bien la premiere:
|
||||||
|
@ -713,7 +709,7 @@ def do_evaluation_etat_in_mod(nt, moduleimpl_id):
|
||||||
return etat
|
return etat
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_evaluations_cal(formsemestre_id, REQUEST=None):
|
def formsemestre_evaluations_cal(formsemestre_id):
|
||||||
"""Page avec calendrier de toutes les evaluations de ce semestre"""
|
"""Page avec calendrier de toutes les evaluations de ce semestre"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > liste evaluations
|
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > liste evaluations
|
||||||
|
@ -736,7 +732,7 @@ def formsemestre_evaluations_cal(formsemestre_id, REQUEST=None):
|
||||||
if not e["jour"]:
|
if not e["jour"]:
|
||||||
continue
|
continue
|
||||||
day = e["jour"].strftime("%Y-%m-%d")
|
day = e["jour"].strftime("%Y-%m-%d")
|
||||||
mod = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
mod = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||||
moduleimpl_id=e["moduleimpl_id"]
|
moduleimpl_id=e["moduleimpl_id"]
|
||||||
)[0]
|
)[0]
|
||||||
txt = mod["module"]["code"] or mod["module"]["abbrev"] or "eval"
|
txt = mod["module"]["code"] or mod["module"]["abbrev"] or "eval"
|
||||||
|
@ -780,7 +776,6 @@ def formsemestre_evaluations_cal(formsemestre_id, REQUEST=None):
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
REQUEST,
|
|
||||||
"Evaluations du semestre",
|
"Evaluations du semestre",
|
||||||
sem,
|
sem,
|
||||||
cssstyles=["css/calabs.css"],
|
cssstyles=["css/calabs.css"],
|
||||||
|
@ -814,7 +809,7 @@ def evaluation_date_first_completion(evaluation_id):
|
||||||
# (pour avoir l'etat et le groupe) et aussi les inscriptions
|
# (pour avoir l'etat et le groupe) et aussi les inscriptions
|
||||||
# au module (pour gerer les modules optionnels correctement)
|
# au module (pour gerer les modules optionnels correctement)
|
||||||
# E = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
|
# E = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
|
||||||
# M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
# M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
# formsemestre_id = M["formsemestre_id"]
|
# formsemestre_id = M["formsemestre_id"]
|
||||||
# insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits( formsemestre_id)
|
# insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits( formsemestre_id)
|
||||||
# insmod = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=E["moduleimpl_id"])
|
# insmod = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=E["moduleimpl_id"])
|
||||||
|
@ -844,9 +839,7 @@ def evaluation_date_first_completion(evaluation_id):
|
||||||
return max(date_premiere_note.values())
|
return max(date_premiere_note.values())
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_evaluations_delai_correction(
|
def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
|
||||||
formsemestre_id, format="html", REQUEST=None
|
|
||||||
):
|
|
||||||
"""Experimental: un tableau indiquant pour chaque évaluation
|
"""Experimental: un tableau indiquant pour chaque évaluation
|
||||||
le nombre de jours avant la publication des notes.
|
le nombre de jours avant la publication des notes.
|
||||||
|
|
||||||
|
@ -858,8 +851,8 @@ def formsemestre_evaluations_delai_correction(
|
||||||
evals = nt.get_sem_evaluation_etat_list()
|
evals = nt.get_sem_evaluation_etat_list()
|
||||||
T = []
|
T = []
|
||||||
for e in evals:
|
for e in evals:
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=e["moduleimpl_id"])[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=e["moduleimpl_id"])[0]
|
||||||
Mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
|
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||||
if (e["evaluation_type"] != scu.EVALUATION_NORMALE) or (
|
if (e["evaluation_type"] != scu.EVALUATION_NORMALE) or (
|
||||||
Mod["module_type"] == scu.MODULE_MALUS
|
Mod["module_type"] == scu.MODULE_MALUS
|
||||||
):
|
):
|
||||||
|
@ -915,13 +908,13 @@ def formsemestre_evaluations_delai_correction(
|
||||||
html_title="<h2>Correction des évaluations du semestre</h2>",
|
html_title="<h2>Correction des évaluations du semestre</h2>",
|
||||||
caption="Correction des évaluations du semestre",
|
caption="Correction des évaluations du semestre",
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
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
|
origin="Généré par %s le " % sco_version.SCONAME
|
||||||
+ scu.timedate_human_repr()
|
+ scu.timedate_human_repr()
|
||||||
+ "",
|
+ "",
|
||||||
filename=scu.make_filename("evaluations_delais_" + sem["titreannee"]),
|
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):
|
def module_evaluation_insert_before(ModEvals, next_eval):
|
||||||
|
@ -1039,8 +1032,8 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
|
||||||
|
|
||||||
E = do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
E = do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||||
moduleimpl_id = E["moduleimpl_id"]
|
moduleimpl_id = E["moduleimpl_id"]
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
Mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
|
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||||
formsemestre_id = M["formsemestre_id"]
|
formsemestre_id = M["formsemestre_id"]
|
||||||
u = sco_users.user_info(M["responsable_id"])
|
u = sco_users.user_info(M["responsable_id"])
|
||||||
resp = u["prenomnom"]
|
resp = u["prenomnom"]
|
||||||
|
@ -1069,7 +1062,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
|
||||||
]
|
]
|
||||||
if Mod["module_type"] == scu.MODULE_MALUS:
|
if Mod["module_type"] == scu.MODULE_MALUS:
|
||||||
# Indique l'UE
|
# Indique l'UE
|
||||||
ue = sco_edit_ue.do_ue_list(args={"ue_id": Mod["ue_id"]})[0]
|
ue = sco_edit_ue.ue_list(args={"ue_id": Mod["ue_id"]})[0]
|
||||||
H.append("<p><b>UE : %(acronyme)s</b></p>" % ue)
|
H.append("<p><b>UE : %(acronyme)s</b></p>" % ue)
|
||||||
# store min/max values used by JS client-side checks:
|
# store min/max values used by JS client-side checks:
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -1077,21 +1070,34 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# date et absences (pas pour evals de malus)
|
# date et absences (pas pour evals de malus)
|
||||||
jour = E["jour"] or "<em>pas de date</em>"
|
|
||||||
H.append(
|
|
||||||
"<p>Réalisée le <b>%s</b> de %s à %s "
|
|
||||||
% (jour, E["heure_debut"], E["heure_fin"])
|
|
||||||
)
|
|
||||||
if E["jour"]:
|
if E["jour"]:
|
||||||
|
jour = E["jour"]
|
||||||
|
H.append(
|
||||||
|
"<p>Réalisée le <b>%s</b> "
|
||||||
|
% (jour)
|
||||||
|
)
|
||||||
|
if E["heure_debut"] != E["heure_fin"]:
|
||||||
|
H.append(
|
||||||
|
"de %s à %s "
|
||||||
|
% (E["heure_debut"], E["heure_fin"])
|
||||||
|
)
|
||||||
group_id = sco_groups.get_default_group(formsemestre_id)
|
group_id = sco_groups.get_default_group(formsemestre_id)
|
||||||
H.append(
|
H.append(
|
||||||
'<span class="noprint"><a href="%s/Absences/EtatAbsencesDate?group_ids=%s&date=%s">(absences ce jour)</a></span>'
|
f"""<span class="noprint"><a href="{url_for(
|
||||||
% (
|
'absences.EtatAbsencesDate',
|
||||||
scu.ScoURL(),
|
scodoc_dept=g.scodoc_dept,
|
||||||
group_id,
|
group_ids=group_id,
|
||||||
six.moves.urllib.parse.quote(E["jour"], safe=""),
|
date=E["jour"]
|
||||||
)
|
)
|
||||||
|
}">(absences ce jour)</a></span>"""
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
jour = "<em>pas de date</em>"
|
||||||
|
H.append(
|
||||||
|
"<p>Réalisée le <b>%s</b> "
|
||||||
|
% (jour)
|
||||||
|
)
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
'</p><p>Coefficient dans le module: <b>%s</b>, notes sur <span id="eval_note_max">%g</span> '
|
'</p><p>Coefficient dans le module: <b>%s</b>, notes sur <span id="eval_note_max">%g</span> '
|
||||||
% (E["coefficient"], E["note_max"])
|
% (E["coefficient"], E["note_max"])
|
||||||
|
@ -1110,7 +1116,6 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
|
||||||
def evaluation_create_form(
|
def evaluation_create_form(
|
||||||
moduleimpl_id=None,
|
moduleimpl_id=None,
|
||||||
evaluation_id=None,
|
evaluation_id=None,
|
||||||
REQUEST=None,
|
|
||||||
edit=False,
|
edit=False,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
page_title="Evaluation",
|
page_title="Evaluation",
|
||||||
|
@ -1120,7 +1125,7 @@ def evaluation_create_form(
|
||||||
the_eval = do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
the_eval = do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||||
moduleimpl_id = the_eval["moduleimpl_id"]
|
moduleimpl_id = the_eval["moduleimpl_id"]
|
||||||
#
|
#
|
||||||
M = sco_moduleimpl.do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
is_malus = M["module"]["module_type"] == scu.MODULE_MALUS # True si module de malus
|
is_malus = M["module"]["module_type"] == scu.MODULE_MALUS # True si module de malus
|
||||||
formsemestre_id = M["formsemestre_id"]
|
formsemestre_id = M["formsemestre_id"]
|
||||||
min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible
|
min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible
|
||||||
|
@ -1179,7 +1184,7 @@ def evaluation_create_form(
|
||||||
else:
|
else:
|
||||||
min_note_max_str = "0"
|
min_note_max_str = "0"
|
||||||
#
|
#
|
||||||
Mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
|
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||||
#
|
#
|
||||||
help = """<div class="help"><p class="help">
|
help = """<div class="help"><p class="help">
|
||||||
Le coefficient d'une évaluation n'est utilisé que pour pondérer les évaluations au sein d'un module.
|
Le coefficient d'une évaluation n'est utilisé que pour pondérer les évaluations au sein d'un module.
|
||||||
|
@ -1232,11 +1237,9 @@ def evaluation_create_form(
|
||||||
initvalues["visibulletinlist"] = ["X"]
|
initvalues["visibulletinlist"] = ["X"]
|
||||||
else:
|
else:
|
||||||
initvalues["visibulletinlist"] = []
|
initvalues["visibulletinlist"] = []
|
||||||
if (
|
vals = scu.get_request_args()
|
||||||
REQUEST.form.get("tf_submitted", False)
|
if vals.get("tf_submitted", False) and "visibulletinlist" not in vals:
|
||||||
and "visibulletinlist" not in REQUEST.form
|
vals["visibulletinlist"] = []
|
||||||
):
|
|
||||||
REQUEST.form["visibulletinlist"] = []
|
|
||||||
#
|
#
|
||||||
form = [
|
form = [
|
||||||
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
|
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
|
||||||
|
@ -1346,8 +1349,8 @@ def evaluation_create_form(
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
vals,
|
||||||
form,
|
form,
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
submitlabel=submitlabel,
|
submitlabel=submitlabel,
|
||||||
|
@ -1369,7 +1372,7 @@ def evaluation_create_form(
|
||||||
tf[2]["visibulletin"] = False
|
tf[2]["visibulletin"] = False
|
||||||
if not edit:
|
if not edit:
|
||||||
# creation d'une evaluation
|
# creation d'une evaluation
|
||||||
evaluation_id = do_evaluation_create(REQUEST=REQUEST, **tf[2])
|
evaluation_id = do_evaluation_create(**tf[2])
|
||||||
return flask.redirect(dest_url)
|
return flask.redirect(dest_url)
|
||||||
else:
|
else:
|
||||||
do_evaluation_edit(tf[2])
|
do_evaluation_edit(tf[2])
|
||||||
|
|
|
@ -35,11 +35,11 @@ from enum import Enum
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
import openpyxl.utils.datetime
|
import openpyxl.utils.datetime
|
||||||
|
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL, FORMAT_DATE_DDMMYY
|
||||||
|
from openpyxl.comments import Comment
|
||||||
from openpyxl import Workbook, load_workbook
|
from openpyxl import Workbook, load_workbook
|
||||||
from openpyxl.cell import WriteOnlyCell
|
from openpyxl.cell import WriteOnlyCell
|
||||||
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
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
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import notesdb
|
from app.scodoc import notesdb
|
||||||
|
@ -59,31 +59,33 @@ class COLORS(Enum):
|
||||||
LIGHT_YELLOW = "FFFFFF99"
|
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:
|
# 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):
|
def xldate_as_datetime(xldate, datemode=0):
|
||||||
"""Conversion d'une date Excel en date
|
"""Conversion d'une date Excel en datetime python
|
||||||
|
Deux formats de chaîne acceptés:
|
||||||
|
* JJ/MM/YYYY (chaîne naïve)
|
||||||
|
* Date ISO (valeur de type date lue dans la feuille)
|
||||||
Peut lever une ValueError
|
Peut lever une ValueError
|
||||||
"""
|
"""
|
||||||
return openpyxl.utils.datetime.from_ISO8601(xldate)
|
try:
|
||||||
|
return datetime.datetime.strptime(xldate, "%d/%m/%Y")
|
||||||
|
except:
|
||||||
|
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:
|
class ScoExcelBook:
|
||||||
|
@ -98,13 +100,16 @@ class ScoExcelBook:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.sheets = [] # list of sheets
|
self.sheets = [] # list of sheets
|
||||||
|
self.wb = Workbook(write_only=True)
|
||||||
|
|
||||||
def create_sheet(self, sheet_name="feuille", default_style=None):
|
def create_sheet(self, sheet_name="feuille", default_style=None):
|
||||||
"""Crée une nouvelle feuille dans ce classeur
|
"""Crée une nouvelle feuille dans ce classeur
|
||||||
sheet_name -- le nom de la feuille
|
sheet_name -- le nom de la feuille
|
||||||
default_style -- le style par défaut
|
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)
|
self.sheets.append(sheet)
|
||||||
return sheet
|
return sheet
|
||||||
|
|
||||||
|
@ -112,12 +117,12 @@ class ScoExcelBook:
|
||||||
"""génération d'un stream binaire représentant la totalité du classeur.
|
"""génération d'un stream binaire représentant la totalité du classeur.
|
||||||
retourne le flux
|
retourne le flux
|
||||||
"""
|
"""
|
||||||
wb = Workbook(write_only=True)
|
|
||||||
for sheet in self.sheets:
|
for sheet in self.sheets:
|
||||||
sheet.generate(self)
|
sheet.prepare()
|
||||||
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
|
# construction d'un flux
|
||||||
|
# (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
|
||||||
with NamedTemporaryFile() as tmp:
|
with NamedTemporaryFile() as tmp:
|
||||||
wb.save(tmp.name)
|
self.wb.save(tmp.name)
|
||||||
tmp.seek(0)
|
tmp.seek(0)
|
||||||
return tmp.read()
|
return tmp.read()
|
||||||
|
|
||||||
|
@ -125,6 +130,7 @@ class ScoExcelBook:
|
||||||
def excel_make_style(
|
def excel_make_style(
|
||||||
bold=False,
|
bold=False,
|
||||||
italic=False,
|
italic=False,
|
||||||
|
outline=False,
|
||||||
color: COLORS = COLORS.BLACK,
|
color: COLORS = COLORS.BLACK,
|
||||||
bgcolor: COLORS = None,
|
bgcolor: COLORS = None,
|
||||||
halign=None,
|
halign=None,
|
||||||
|
@ -145,7 +151,14 @@ def excel_make_style(
|
||||||
size -- taille de police
|
size -- taille de police
|
||||||
"""
|
"""
|
||||||
style = {}
|
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
|
style["font"] = font
|
||||||
if bgcolor:
|
if bgcolor:
|
||||||
style["fill"] = PatternFill(fill_type="solid", fgColor=bgcolor.value)
|
style["fill"] = PatternFill(fill_type="solid", fgColor=bgcolor.value)
|
||||||
|
@ -182,58 +195,125 @@ class ScoExcelSheet:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, sheet_name="feuille", default_style=None, wb=None):
|
def __init__(self, sheet_name="feuille", default_style=None, wb=None):
|
||||||
"""Création de la feuille.
|
"""Création de la feuille. sheet_name
|
||||||
sheet_name -- le nom de la feuille
|
-- le nom de la feuille default_style
|
||||||
default_style -- le style par défaut des cellules
|
-- le style par défaut des cellules ws
|
||||||
wb -- le WorkBook dans laquelle se trouve la feuille. Si wb est None (cas d'un classeur mono-feuille),
|
-- None si la feuille est autonome (dans ce cas ell crée son propre wb), sinon c'est la worksheet
|
||||||
un workbook est crée et associé à cette feuille.
|
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.
|
# 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' ?)
|
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
|
||||||
self.sheet_name = sheet_name[
|
self.sheet_name = adjust_sheetname(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 }
|
|
||||||
if default_style is None:
|
if default_style is None:
|
||||||
default_style = excel_make_style()
|
default_style = excel_make_style()
|
||||||
self.default_style = default_style
|
self.default_style = default_style
|
||||||
self.wb = wb or Workbook(write_only=True) # Création de workbook si nécessaire
|
if wb is None:
|
||||||
self.ws = self.wb.create_sheet(title=self.sheet_name)
|
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.column_dimensions = {}
|
||||||
|
self.row_dimensions = {}
|
||||||
|
|
||||||
def set_column_dimension_width(self, cle, value):
|
def excel_make_composite_style(
|
||||||
"""Détermine la largeur d'une colonne.
|
self,
|
||||||
cle -- identifie la colonne ("A"n "B", ...)
|
alignment=None,
|
||||||
value -- la dimension (unité : 7 pixels comme affiché dans Excel)
|
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):
|
def set_row_dimension_height(self, cle=None, value=21):
|
||||||
"""Masque ou affiche une colonne.
|
"""Détermine la hauteur d'une ligne. cle -- identifie la ligne (1, 2, ...) si None,
|
||||||
cle -- identifie la colonne ("A"n "B", ...)
|
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)
|
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):
|
def make_cell(self, value: any = None, style=None, comment=None):
|
||||||
"""Construit une cellule.
|
"""Construit une cellule.
|
||||||
value -- contenu de la cellule (texte ou numérique)
|
value -- contenu de la cellule (texte, numérique, booléen ou date)
|
||||||
style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
|
style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
|
||||||
"""
|
"""
|
||||||
cell = WriteOnlyCell(self.ws, value or "")
|
# adapatation des valeurs si nécessaire
|
||||||
if not (isinstance(value, int) or isinstance(value, float)):
|
if value is None:
|
||||||
cell.data_type = "s"
|
value = ""
|
||||||
# if style is not None and "fill" in style:
|
elif value is True:
|
||||||
# toto()
|
value = 1
|
||||||
|
elif value is False:
|
||||||
|
value = 0
|
||||||
|
elif isinstance(value, datetime.datetime):
|
||||||
|
value = value.replace(
|
||||||
|
tzinfo=None
|
||||||
|
) # make date naive (cf https://openpyxl.readthedocs.io/en/latest/datetime.html#timezones)
|
||||||
|
|
||||||
|
# création de la cellule
|
||||||
|
cell = WriteOnlyCell(self.ws, value)
|
||||||
|
|
||||||
|
# recopie des styles
|
||||||
if style is None:
|
if style is None:
|
||||||
style = self.default_style
|
style = self.default_style
|
||||||
if "font" in style:
|
if "font" in style:
|
||||||
cell.font = style["font"]
|
cell.font = style["font"]
|
||||||
|
if "alignment" in style:
|
||||||
|
cell.alignment = style["alignment"]
|
||||||
if "border" in style:
|
if "border" in style:
|
||||||
cell.border = style["border"]
|
cell.border = style["border"]
|
||||||
|
if "fill" in style:
|
||||||
|
cell.fill = style["fill"]
|
||||||
if "number_format" in style:
|
if "number_format" in style:
|
||||||
cell.number_format = style["number_format"]
|
cell.number_format = style["number_format"]
|
||||||
if "fill" in style:
|
if "fill" in style:
|
||||||
|
@ -245,6 +325,16 @@ class ScoExcelSheet:
|
||||||
lines = comment.splitlines()
|
lines = comment.splitlines()
|
||||||
cell.comment.width = 7 * max([len(line) for line in lines])
|
cell.comment.width = 7 * max([len(line) for line in lines])
|
||||||
cell.comment.height = 20 * len(lines)
|
cell.comment.height = 20 * len(lines)
|
||||||
|
|
||||||
|
# test datatype to overwrite datetime format
|
||||||
|
if isinstance(value, datetime.date):
|
||||||
|
cell.data_type = "d"
|
||||||
|
cell.number_format = FORMAT_DATE_DDMMYY
|
||||||
|
elif isinstance(value, int) or isinstance(value, float):
|
||||||
|
cell.data_type = "n"
|
||||||
|
else:
|
||||||
|
cell.data_type = "s"
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
|
|
||||||
def make_row(self, values: list, style=None, comments=None):
|
def make_row(self, values: list, style=None, comments=None):
|
||||||
|
@ -272,73 +362,31 @@ class ScoExcelSheet:
|
||||||
"""ajoute une ligne déjà construite à la feuille."""
|
"""ajoute une ligne déjà construite à la feuille."""
|
||||||
self.rows.append(row)
|
self.rows.append(row)
|
||||||
|
|
||||||
# def set_style(self, style=None, li=None, co=None):
|
def prepare(self):
|
||||||
# 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):
|
|
||||||
"""génére un flux décrivant la feuille.
|
"""génére un flux décrivant la feuille.
|
||||||
Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille)
|
Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille)
|
||||||
ou pour la génération d'un classeur multi-feuilles
|
ou pour la génération d'un classeur multi-feuilles
|
||||||
"""
|
"""
|
||||||
for col in self.column_dimensions.keys():
|
for row in self.column_dimensions.keys():
|
||||||
self.ws.column_dimensions[col] = self.column_dimensions[col]
|
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:
|
for row in self.rows:
|
||||||
self.ws.append(row)
|
self.ws.append(row)
|
||||||
|
|
||||||
def generate_standalone(self):
|
def generate(self):
|
||||||
"""génération d'un classeur mono-feuille"""
|
"""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)
|
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
|
||||||
|
self.prepare()
|
||||||
with NamedTemporaryFile() as tmp:
|
with NamedTemporaryFile() as tmp:
|
||||||
self.wb.save(tmp.name)
|
self.wb.save(tmp.name)
|
||||||
tmp.seek(0)
|
tmp.seek(0)
|
||||||
return tmp.read()
|
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(
|
def excel_simple_table(
|
||||||
titles=None, lines=None, sheet_name=b"feuille", titles_styles=None, comments=None
|
titles=None, lines=None, sheet_name=b"feuille", titles_styles=None, comments=None
|
||||||
|
@ -377,7 +425,7 @@ def excel_simple_table(
|
||||||
cell_style = text_style
|
cell_style = text_style
|
||||||
cells.append(ws.make_cell(it, cell_style))
|
cells.append(ws.make_cell(it, cell_style))
|
||||||
ws.append_row(cells)
|
ws.append_row(cells)
|
||||||
return ws.generate_standalone()
|
return ws.generate()
|
||||||
|
|
||||||
|
|
||||||
def excel_feuille_saisie(e, titreannee, description, lines):
|
def excel_feuille_saisie(e, titreannee, description, lines):
|
||||||
|
@ -538,19 +586,33 @@ def excel_feuille_saisie(e, titreannee, description, lines):
|
||||||
ws.make_cell("cellule vide -> note non modifiée", style_expl),
|
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):
|
def excel_bytes_to_list(bytes_content):
|
||||||
filelike = io.BytesIO(bytes_content)
|
try:
|
||||||
return _excel_to_list(filelike)
|
filelike = io.BytesIO(bytes_content)
|
||||||
|
return _excel_to_list(filelike)
|
||||||
|
except:
|
||||||
|
raise ScoValueError(
|
||||||
|
"""Le fichier xlsx attendu n'est pas lisible !
|
||||||
|
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ..)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def excel_file_to_list(filename):
|
def excel_file_to_list(filename):
|
||||||
return _excel_to_list(filename)
|
try:
|
||||||
|
return _excel_to_list(filename)
|
||||||
|
except:
|
||||||
|
raise ScoValueError(
|
||||||
|
"""Le fichier xlsx attendu n'est pas lisible !
|
||||||
|
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _excel_to_list(filelike): # we may need 'encoding' argument ?
|
def _excel_to_list(filelike):
|
||||||
"""returns list of list
|
"""returns list of list
|
||||||
convert_to_string is a conversion function applied to all non-string values (ie numbers)
|
convert_to_string is a conversion function applied to all non-string values (ie numbers)
|
||||||
"""
|
"""
|
||||||
|
@ -558,7 +620,8 @@ def _excel_to_list(filelike): # we may need 'encoding' argument ?
|
||||||
wb = load_workbook(filename=filelike, read_only=True, data_only=True)
|
wb = load_workbook(filename=filelike, read_only=True, data_only=True)
|
||||||
except:
|
except:
|
||||||
log("Excel_to_list: failure to import document")
|
log("Excel_to_list: failure to import document")
|
||||||
open("/tmp/last_scodoc_import_failure" + scu.XLSX_SUFFIX, "wb").write(filelike)
|
with open("/tmp/last_scodoc_import_failure" + scu.XLSX_SUFFIX, "wb") as f:
|
||||||
|
f.write(filelike)
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"Fichier illisible: assurez-vous qu'il s'agit bien d'un document Excel !"
|
"Fichier illisible: assurez-vous qu'il s'agit bien d'un document Excel !"
|
||||||
)
|
)
|
||||||
|
@ -758,4 +821,4 @@ def excel_feuille_listeappel(
|
||||||
cell_2 = ws.make_cell(("Liste éditée le " + dt), style1i)
|
cell_2 = ws.make_cell(("Liste éditée le " + dt), style1i)
|
||||||
ws.append_row([None, cell_2])
|
ws.append_row([None, cell_2])
|
||||||
|
|
||||||
return ws.generate_standalone()
|
return ws.generate()
|
||||||
|
|
|
@ -45,21 +45,15 @@ class InvalidEtudId(NoteProcessError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AccessDenied(ScoException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidNoteValue(ScoException):
|
class InvalidNoteValue(ScoException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Exception qui stoque dest_url, utilisee dans Zope standard_error_message
|
# Exception qui stoque dest_url, utilisee dans Zope standard_error_message
|
||||||
class ScoValueError(ScoException):
|
class ScoValueError(ScoException):
|
||||||
def __init__(self, msg, dest_url=None, REQUEST=None):
|
def __init__(self, msg, dest_url=None):
|
||||||
ScoException.__init__(self, msg)
|
ScoException.__init__(self, msg)
|
||||||
self.dest_url = dest_url
|
self.dest_url = dest_url
|
||||||
if REQUEST and dest_url:
|
|
||||||
REQUEST.set("dest_url", dest_url)
|
|
||||||
|
|
||||||
|
|
||||||
class FormatError(ScoValueError):
|
class FormatError(ScoValueError):
|
||||||
|
@ -79,7 +73,7 @@ class ScoConfigurationError(ScoValueError):
|
||||||
|
|
||||||
|
|
||||||
class ScoLockedFormError(ScoException):
|
class ScoLockedFormError(ScoException):
|
||||||
def __init__(self, msg="", REQUEST=None):
|
def __init__(self, msg=""):
|
||||||
msg = (
|
msg = (
|
||||||
"Cette formation est verrouillée (car il y a un semestre verrouillé qui s'y réfère). "
|
"Cette formation est verrouillée (car il y a un semestre verrouillé qui s'y réfère). "
|
||||||
+ str(msg)
|
+ str(msg)
|
||||||
|
@ -90,10 +84,14 @@ class ScoLockedFormError(ScoException):
|
||||||
class ScoGenError(ScoException):
|
class ScoGenError(ScoException):
|
||||||
"exception avec affichage d'une page explicative ad-hoc"
|
"exception avec affichage d'une page explicative ad-hoc"
|
||||||
|
|
||||||
def __init__(self, msg="", REQUEST=None):
|
def __init__(self, msg=""):
|
||||||
ScoException.__init__(self, msg)
|
ScoException.__init__(self, msg)
|
||||||
|
|
||||||
|
|
||||||
|
class AccessDenied(ScoGenError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ScoInvalidDateError(ScoValueError):
|
class ScoInvalidDateError(ScoValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
"""Export d'une table avec les résultats de tous les étudiants
|
"""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.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -216,9 +216,7 @@ def get_set_formsemestre_id_dates(start_date, end_date):
|
||||||
return {x["id"] for x in s}
|
return {x["id"] for x in s}
|
||||||
|
|
||||||
|
|
||||||
def scodoc_table_results(
|
def scodoc_table_results(start_date="", end_date="", types_parcours=[], format="html"):
|
||||||
start_date="", end_date="", types_parcours=[], format="html", REQUEST=None
|
|
||||||
):
|
|
||||||
"""Page affichant la table des résultats
|
"""Page affichant la table des résultats
|
||||||
Les dates sont en dd/mm/yyyy (datepicker javascript)
|
Les dates sont en dd/mm/yyyy (datepicker javascript)
|
||||||
types_parcours est la liste des types de parcours à afficher
|
types_parcours est la liste des types de parcours à afficher
|
||||||
|
@ -240,15 +238,13 @@ def scodoc_table_results(
|
||||||
start_date_iso, end_date_iso, types_parcours
|
start_date_iso, end_date_iso, types_parcours
|
||||||
)
|
)
|
||||||
tab.base_url = "%s?start_date=%s&end_date=%s&types_parcours=%s" % (
|
tab.base_url = "%s?start_date=%s&end_date=%s&types_parcours=%s" % (
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
start_date,
|
start_date,
|
||||||
end_date,
|
end_date,
|
||||||
"&types_parcours=".join([str(x) for x in types_parcours]),
|
"&types_parcours=".join([str(x) for x in types_parcours]),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return tab.make_page(
|
return tab.make_page(format=format, with_html_headers=False)
|
||||||
format=format, with_html_headers=False, REQUEST=REQUEST
|
|
||||||
)
|
|
||||||
tab_html = tab.html()
|
tab_html = tab.html()
|
||||||
nb_rows = tab.get_nb_rows()
|
nb_rows = tab.get_nb_rows()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -136,11 +136,11 @@ def search_etud_in_dept(expnom=""):
|
||||||
vals = {}
|
vals = {}
|
||||||
|
|
||||||
url_args = {"scodoc_dept": g.scodoc_dept}
|
url_args = {"scodoc_dept": g.scodoc_dept}
|
||||||
if "dest_url" in request.form:
|
if "dest_url" in vals:
|
||||||
endpoint = request.form["dest_url"]
|
endpoint = vals["dest_url"]
|
||||||
else:
|
else:
|
||||||
endpoint = "scolar.ficheEtud"
|
endpoint = "scolar.ficheEtud"
|
||||||
if "parameters_keys" in request.form:
|
if "parameters_keys" in vals:
|
||||||
for key in vals["parameters_keys"].split(","):
|
for key in vals["parameters_keys"].split(","):
|
||||||
url_args[key] = vals[key]
|
url_args[key] = vals[key]
|
||||||
|
|
||||||
|
@ -362,7 +362,7 @@ def table_etud_in_accessible_depts(expnom=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def search_inscr_etud_by_nip(code_nip, REQUEST=None, format="json"):
|
def search_inscr_etud_by_nip(code_nip, format="json"):
|
||||||
"""Recherche multi-departement d'un étudiant par son code NIP
|
"""Recherche multi-departement d'un étudiant par son code NIP
|
||||||
Seuls les départements accessibles par l'utilisateur sont cherchés.
|
Seuls les départements accessibles par l'utilisateur sont cherchés.
|
||||||
|
|
||||||
|
@ -404,6 +404,4 @@ def search_inscr_etud_by_nip(code_nip, REQUEST=None, format="json"):
|
||||||
)
|
)
|
||||||
tab = GenTable(columns_ids=columns_ids, rows=T)
|
tab = GenTable(columns_ids=columns_ids, rows=T)
|
||||||
|
|
||||||
return tab.make_page(
|
return tab.make_page(format=format, with_html_headers=False, publish=True)
|
||||||
format=format, with_html_headers=False, REQUEST=REQUEST, publish=True
|
|
||||||
)
|
|
||||||
|
|
|
@ -31,7 +31,8 @@ from operator import itemgetter
|
||||||
import xml.dom.minidom
|
import xml.dom.minidom
|
||||||
|
|
||||||
import flask
|
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
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
|
@ -93,13 +94,20 @@ def formation_has_locked_sems(formation_id):
|
||||||
|
|
||||||
|
|
||||||
def formation_export(
|
def formation_export(
|
||||||
formation_id, export_ids=False, export_tags=True, format=None, REQUEST=None
|
formation_id,
|
||||||
|
export_ids=False,
|
||||||
|
export_tags=True,
|
||||||
|
export_external_ues=False,
|
||||||
|
format=None,
|
||||||
):
|
):
|
||||||
"""Get a formation, with UE, matieres, modules
|
"""Get a formation, with UE, matieres, modules
|
||||||
in desired format
|
in desired format
|
||||||
"""
|
"""
|
||||||
F = formation_list(args={"formation_id": formation_id})[0]
|
F = formation_list(args={"formation_id": formation_id})[0]
|
||||||
ues = sco_edit_ue.do_ue_list({"formation_id": formation_id})
|
selector = {"formation_id": formation_id}
|
||||||
|
if not export_external_ues:
|
||||||
|
selector["is_external"] = False
|
||||||
|
ues = sco_edit_ue.ue_list(selector)
|
||||||
F["ue"] = ues
|
F["ue"] = ues
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
ue_id = ue["ue_id"]
|
ue_id = ue["ue_id"]
|
||||||
|
@ -108,14 +116,14 @@ def formation_export(
|
||||||
del ue["formation_id"]
|
del ue["formation_id"]
|
||||||
if ue["ects"] is None:
|
if ue["ects"] is None:
|
||||||
del ue["ects"]
|
del ue["ects"]
|
||||||
mats = sco_edit_matiere.do_matiere_list({"ue_id": ue_id})
|
mats = sco_edit_matiere.matiere_list({"ue_id": ue_id})
|
||||||
ue["matiere"] = mats
|
ue["matiere"] = mats
|
||||||
for mat in mats:
|
for mat in mats:
|
||||||
matiere_id = mat["matiere_id"]
|
matiere_id = mat["matiere_id"]
|
||||||
if not export_ids:
|
if not export_ids:
|
||||||
del mat["matiere_id"]
|
del mat["matiere_id"]
|
||||||
del mat["ue_id"]
|
del mat["ue_id"]
|
||||||
mods = sco_edit_module.do_module_list({"matiere_id": matiere_id})
|
mods = sco_edit_module.module_list({"matiere_id": matiere_id})
|
||||||
mat["module"] = mods
|
mat["module"] = mods
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
if export_tags:
|
if export_tags:
|
||||||
|
@ -132,7 +140,7 @@ def formation_export(
|
||||||
del mod["ects"]
|
del mod["ects"]
|
||||||
|
|
||||||
return scu.sendResult(
|
return scu.sendResult(
|
||||||
REQUEST, F, name="formation", format=format, force_outer_xml_tag=False
|
F, name="formation", format=format, force_outer_xml_tag=False, attached=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -162,20 +170,18 @@ def formation_import_xml(doc: str, import_tags=True):
|
||||||
D = sco_xml.xml_to_dicts(f)
|
D = sco_xml.xml_to_dicts(f)
|
||||||
assert D[0] == "formation"
|
assert D[0] == "formation"
|
||||||
F = D[1]
|
F = D[1]
|
||||||
F_quoted = F.copy()
|
# F_quoted = F.copy()
|
||||||
log("F=%s" % F)
|
# ndb.quote_dict(F_quoted)
|
||||||
ndb.quote_dict(F_quoted)
|
F["dept_id"] = g.scodoc_dept_id
|
||||||
log("F_quoted=%s" % F_quoted)
|
|
||||||
# find new version number
|
# find new version number
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
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(
|
cursor.execute(
|
||||||
"select max(version) from notes_formations where acronyme=%(acronyme)s and titre=%(titre)s",
|
"""SELECT max(version)
|
||||||
F_quoted,
|
FROM notes_formations
|
||||||
|
WHERE acronyme=%(acronyme)s and titre=%(titre)s and dept_id=%(dept_id)s
|
||||||
|
""",
|
||||||
|
F,
|
||||||
)
|
)
|
||||||
res = cursor.fetchall()
|
res = cursor.fetchall()
|
||||||
try:
|
try:
|
||||||
|
@ -196,7 +202,7 @@ def formation_import_xml(doc: str, import_tags=True):
|
||||||
assert ue_info[0] == "ue"
|
assert ue_info[0] == "ue"
|
||||||
ue_info[1]["formation_id"] = formation_id
|
ue_info[1]["formation_id"] = formation_id
|
||||||
if "ue_id" in ue_info[1]:
|
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"]
|
del ue_info[1]["ue_id"]
|
||||||
else:
|
else:
|
||||||
xml_ue_id = None
|
xml_ue_id = None
|
||||||
|
@ -212,7 +218,7 @@ def formation_import_xml(doc: str, import_tags=True):
|
||||||
for mod_info in mat_info[2]:
|
for mod_info in mat_info[2]:
|
||||||
assert mod_info[0] == "module"
|
assert mod_info[0] == "module"
|
||||||
if "module_id" in mod_info[1]:
|
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"]
|
del mod_info[1]["module_id"]
|
||||||
else:
|
else:
|
||||||
xml_module_id = None
|
xml_module_id = None
|
||||||
|
@ -230,7 +236,7 @@ def formation_import_xml(doc: str, import_tags=True):
|
||||||
return formation_id, modules_old2new, ues_old2new
|
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
|
"""List formation, grouped by titre and sorted by versions
|
||||||
and listing associated semestres
|
and listing associated semestres
|
||||||
returns a table
|
returns a table
|
||||||
|
@ -247,7 +253,7 @@ def formation_list_table(formation_id=None, args={}, REQUEST=None):
|
||||||
"edit_img", border="0", alt="modifier", title="Modifier titres et code"
|
"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:
|
# Traduit/ajoute des champs à afficher:
|
||||||
for f in formations:
|
for f in formations:
|
||||||
|
@ -257,7 +263,11 @@ def formation_list_table(formation_id=None, args={}, REQUEST=None):
|
||||||
).NAME
|
).NAME
|
||||||
except:
|
except:
|
||||||
f["parcours_name"] = ""
|
f["parcours_name"] = ""
|
||||||
f["_titre_target"] = "ue_list?formation_id=%(formation_id)s" % f
|
f["_titre_target"] = url_for(
|
||||||
|
"notes.ue_table",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formation_id=str(f["formation_id"]),
|
||||||
|
)
|
||||||
f["_titre_link_class"] = "stdlink"
|
f["_titre_link_class"] = "stdlink"
|
||||||
f["_titre_id"] = "titre-%s" % f["acronyme"].lower().replace(" ", "-")
|
f["_titre_id"] = "titre-%s" % f["acronyme"].lower().replace(" ", "-")
|
||||||
# Ajoute les semestres associés à chaque formation:
|
# Ajoute les semestres associés à chaque formation:
|
||||||
|
@ -347,17 +357,18 @@ def formation_list_table(formation_id=None, args={}, REQUEST=None):
|
||||||
html_class="formation_list_table table_leftalign",
|
html_class="formation_list_table table_leftalign",
|
||||||
html_with_td_classes=True,
|
html_with_td_classes=True,
|
||||||
html_sortable=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,
|
page_title=title,
|
||||||
pdf_title=title,
|
pdf_title=title,
|
||||||
preferences=sco_preferences.SemPreferences(),
|
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"
|
"duplicate formation, with new version number"
|
||||||
xml = formation_export(formation_id, export_ids=True, format="xml")
|
resp = formation_export(formation_id, export_ids=True, format="xml")
|
||||||
new_id, modules_old2new, ues_old2new = formation_import_xml(xml)
|
xml_data = resp.get_data(as_text=True)
|
||||||
|
new_id, modules_old2new, ues_old2new = formation_import_xml(xml_data)
|
||||||
# news
|
# news
|
||||||
F = formation_list(args={"formation_id": new_id})[0]
|
F = formation_list(args={"formation_id": new_id})[0]
|
||||||
sco_news.add(
|
sco_news.add(
|
||||||
|
@ -368,7 +379,7 @@ def formation_create_new_version(formation_id, redirect=True, REQUEST=None):
|
||||||
if redirect:
|
if redirect:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.ue_list",
|
"notes.ue_table",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=new_id,
|
formation_id=new_id,
|
||||||
msg="Nouvelle version !",
|
msg="Nouvelle version !",
|
||||||
|
|
|
@ -31,7 +31,7 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||||
import time
|
import time
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from flask import g
|
from flask import g, request
|
||||||
|
|
||||||
import app
|
import app
|
||||||
from app.models import Departement
|
from app.models import Departement
|
||||||
|
@ -61,6 +61,7 @@ _formsemestreEditor = ndb.EditableTable(
|
||||||
"gestion_semestrielle",
|
"gestion_semestrielle",
|
||||||
"etat",
|
"etat",
|
||||||
"bul_hide_xml",
|
"bul_hide_xml",
|
||||||
|
"block_moyennes",
|
||||||
"bul_bgcolor",
|
"bul_bgcolor",
|
||||||
"modalite",
|
"modalite",
|
||||||
"resp_can_edit",
|
"resp_can_edit",
|
||||||
|
@ -68,7 +69,6 @@ _formsemestreEditor = ndb.EditableTable(
|
||||||
"ens_can_edit_eval",
|
"ens_can_edit_eval",
|
||||||
"elt_sem_apo",
|
"elt_sem_apo",
|
||||||
"elt_annee_apo",
|
"elt_annee_apo",
|
||||||
"scodoc7_id",
|
|
||||||
),
|
),
|
||||||
filter_dept=True,
|
filter_dept=True,
|
||||||
sortkey="date_debut",
|
sortkey="date_debut",
|
||||||
|
@ -82,6 +82,7 @@ _formsemestreEditor = ndb.EditableTable(
|
||||||
"etat": bool,
|
"etat": bool,
|
||||||
"gestion_compensation": bool,
|
"gestion_compensation": bool,
|
||||||
"bul_hide_xml": bool,
|
"bul_hide_xml": bool,
|
||||||
|
"block_moyennes": bool,
|
||||||
"gestion_semestrielle": bool,
|
"gestion_semestrielle": bool,
|
||||||
"gestion_compensation": bool,
|
"gestion_compensation": bool,
|
||||||
"gestion_semestrielle": bool,
|
"gestion_semestrielle": bool,
|
||||||
|
@ -92,18 +93,21 @@ _formsemestreEditor = ndb.EditableTable(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_formsemestre(formsemestre_id):
|
def get_formsemestre(formsemestre_id, raise_soft_exc=False):
|
||||||
"list ONE formsemestre"
|
"list ONE formsemestre"
|
||||||
|
if formsemestre_id in g.stored_get_formsemestre:
|
||||||
|
return g.stored_get_formsemestre[formsemestre_id]
|
||||||
if not isinstance(formsemestre_id, int):
|
if not isinstance(formsemestre_id, int):
|
||||||
raise ScoValueError(
|
raise ValueError("formsemestre_id must be an integer !")
|
||||||
"""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"""
|
sems = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})
|
||||||
)
|
if not sems:
|
||||||
try:
|
|
||||||
sem = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})[0]
|
|
||||||
return sem
|
|
||||||
except:
|
|
||||||
log("get_formsemestre: invalid formsemestre_id (%s)" % formsemestre_id)
|
log("get_formsemestre: invalid formsemestre_id (%s)" % formsemestre_id)
|
||||||
raise
|
if raise_soft_exc:
|
||||||
|
raise ScoValueError(f"semestre {formsemestre_id} inconnu !")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"semestre {formsemestre_id} inconnu !")
|
||||||
|
g.stored_get_formsemestre[formsemestre_id] = sems[0]
|
||||||
|
return sems[0]
|
||||||
|
|
||||||
|
|
||||||
def do_formsemestre_list(*a, **kw):
|
def do_formsemestre_list(*a, **kw):
|
||||||
|
@ -242,7 +246,7 @@ def do_formsemestre_create(args, silent=False):
|
||||||
default=True,
|
default=True,
|
||||||
redirect=0,
|
redirect=0,
|
||||||
)
|
)
|
||||||
_group_id = sco_groups.createGroup(partition_id, default=True)
|
_group_id = sco_groups.create_group(partition_id, default=True)
|
||||||
|
|
||||||
# news
|
# news
|
||||||
if "titre" not in args:
|
if "titre" not in args:
|
||||||
|
@ -565,7 +569,7 @@ def list_formsemestre_by_etape(etape_apo=False, annee_scolaire=False):
|
||||||
return sems
|
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"""
|
"""Affiche table des semestres correspondants à l'étape"""
|
||||||
if etape_apo:
|
if etape_apo:
|
||||||
html_title = (
|
html_title = (
|
||||||
|
@ -582,8 +586,8 @@ def view_formsemestre_by_etape(etape_apo=None, format="html", REQUEST=None):
|
||||||
Etape: <input name="etape_apo" type="text" size="8"></input>
|
Etape: <input name="etape_apo" type="text" size="8"></input>
|
||||||
</form>""",
|
</form>""",
|
||||||
)
|
)
|
||||||
tab.base_url = "%s?etape_apo=%s" % (REQUEST.URL0, etape_apo or "")
|
tab.base_url = "%s?etape_apo=%s" % (request.base_url, etape_apo or "")
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
|
|
||||||
def sem_has_etape(sem, code_etape):
|
def sem_has_etape(sem, code_etape):
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"""Menu "custom" (défini par l'utilisateur) dans les semestres
|
"""Menu "custom" (défini par l'utilisateur) dans les semestres
|
||||||
"""
|
"""
|
||||||
import flask
|
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.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
@ -77,16 +77,14 @@ def formsemestre_custommenu_html(formsemestre_id):
|
||||||
return htmlutils.make_menu("Liens", menu)
|
return htmlutils.make_menu("Liens", menu)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_custommenu_edit(formsemestre_id, REQUEST=None):
|
def formsemestre_custommenu_edit(formsemestre_id):
|
||||||
"""Dialog to edit the custom menu"""
|
"""Dialog to edit the custom menu"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
dest_url = (
|
dest_url = (
|
||||||
scu.NotesURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
scu.NotesURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
||||||
)
|
)
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header("Modification du menu du semestre ", sem),
|
||||||
REQUEST, "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">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>""",
|
<p class="help">Procédez en plusieurs fois si vous voulez ajouter plusieurs items.</p>""",
|
||||||
]
|
]
|
||||||
|
@ -119,8 +117,8 @@ def formsemestre_custommenu_edit(formsemestre_id, REQUEST=None):
|
||||||
initvalues["title_" + str(item["custommenu_id"])] = item["title"]
|
initvalues["title_" + str(item["custommenu_id"])] = item["title"]
|
||||||
initvalues["url_" + str(item["custommenu_id"])] = item["url"]
|
initvalues["url_" + str(item["custommenu_id"])] = item["url"]
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
descr,
|
descr,
|
||||||
initvalues=initvalues,
|
initvalues=initvalues,
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"""Form choix modules / responsables et creation formsemestre
|
"""Form choix modules / responsables et creation formsemestre
|
||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g
|
from flask import url_for, g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
|
from app.scodoc import sco_groups_copy
|
||||||
from app.scodoc import sco_modalites
|
from app.scodoc import sco_modalites
|
||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_parcours_dut
|
from app.scodoc import sco_parcours_dut
|
||||||
|
@ -58,7 +59,6 @@ from app.scodoc import sco_permissions_check
|
||||||
from app.scodoc import sco_portal_apogee
|
from app.scodoc import sco_portal_apogee
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
def _default_sem_title(F):
|
def _default_sem_title(F):
|
||||||
|
@ -66,7 +66,7 @@ def _default_sem_title(F):
|
||||||
return F["titre"]
|
return F["titre"]
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_createwithmodules(REQUEST=None):
|
def formsemestre_createwithmodules():
|
||||||
"""Page création d'un semestre"""
|
"""Page création d'un semestre"""
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
|
@ -77,7 +77,7 @@ def formsemestre_createwithmodules(REQUEST=None):
|
||||||
),
|
),
|
||||||
"""<h2>Mise en place d'un semestre de formation</h2>""",
|
"""<h2>Mise en place d'un semestre de formation</h2>""",
|
||||||
]
|
]
|
||||||
r = do_formsemestre_createwithmodules(REQUEST=REQUEST)
|
r = do_formsemestre_createwithmodules()
|
||||||
if isinstance(r, str):
|
if isinstance(r, str):
|
||||||
H.append(r)
|
H.append(r)
|
||||||
else:
|
else:
|
||||||
|
@ -85,13 +85,12 @@ def formsemestre_createwithmodules(REQUEST=None):
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_editwithmodules(REQUEST, formsemestre_id):
|
def formsemestre_editwithmodules(formsemestre_id):
|
||||||
"""Page modification semestre"""
|
"""Page modification semestre"""
|
||||||
# portage from dtml
|
# portage from dtml
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
REQUEST,
|
|
||||||
"Modification du semestre",
|
"Modification du semestre",
|
||||||
sem,
|
sem,
|
||||||
javascripts=["libjs/AutoSuggest.js"],
|
javascripts=["libjs/AutoSuggest.js"],
|
||||||
|
@ -105,12 +104,13 @@ def formsemestre_editwithmodules(REQUEST, formsemestre_id):
|
||||||
% scu.icontag("lock_img", border="0", title="Semestre verrouillé")
|
% scu.icontag("lock_img", border="0", title="Semestre verrouillé")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
r = do_formsemestre_createwithmodules(REQUEST=REQUEST, edit=1)
|
r = do_formsemestre_createwithmodules(edit=1)
|
||||||
if isinstance(r, str):
|
if isinstance(r, str):
|
||||||
H.append(r)
|
H.append(r)
|
||||||
else:
|
else:
|
||||||
return r # response redirect
|
return r # response redirect
|
||||||
if not REQUEST.form.get("tf_submitted", False):
|
vals = scu.get_request_args()
|
||||||
|
if not vals.get("tf_submitted", False):
|
||||||
H.append(
|
H.append(
|
||||||
"""<p class="help">Seuls les modules cochés font partie de ce semestre. Pour les retirer, les décocher et appuyer sur le bouton "modifier".
|
"""<p class="help">Seuls les modules cochés font partie de ce semestre. Pour les retirer, les décocher et appuyer sur le bouton "modifier".
|
||||||
</p>
|
</p>
|
||||||
|
@ -121,7 +121,7 @@ def formsemestre_editwithmodules(REQUEST, formsemestre_id):
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
def can_edit_sem(REQUEST, formsemestre_id="", sem=None):
|
def can_edit_sem(formsemestre_id="", sem=None):
|
||||||
"""Return sem if user can edit it, False otherwise"""
|
"""Return sem if user can edit it, False otherwise"""
|
||||||
sem = sem or sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sem or sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
if not current_user.has_permission(Permission.ScoImplement): # pas chef
|
if not current_user.has_permission(Permission.ScoImplement): # pas chef
|
||||||
|
@ -130,11 +130,12 @@ def can_edit_sem(REQUEST, formsemestre_id="", sem=None):
|
||||||
return sem
|
return sem
|
||||||
|
|
||||||
|
|
||||||
def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
def do_formsemestre_createwithmodules(edit=False):
|
||||||
"Form choix modules / responsables et creation formsemestre"
|
"Form choix modules / responsables et creation formsemestre"
|
||||||
# Fonction accessible à tous, controle acces à la main:
|
# Fonction accessible à tous, controle acces à la main:
|
||||||
|
vals = scu.get_request_args()
|
||||||
if edit:
|
if edit:
|
||||||
formsemestre_id = int(REQUEST.form["formsemestre_id"])
|
formsemestre_id = int(vals["formsemestre_id"])
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
if not current_user.has_permission(Permission.ScoImplement):
|
if not current_user.has_permission(Permission.ScoImplement):
|
||||||
if not edit:
|
if not edit:
|
||||||
|
@ -156,21 +157,21 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
uid2display[u.id] = u.get_nomplogin()
|
uid2display[u.id] = u.get_nomplogin()
|
||||||
allowed_user_names = list(uid2display.values()) + [""]
|
allowed_user_names = list(uid2display.values()) + [""]
|
||||||
#
|
#
|
||||||
formation_id = int(REQUEST.form["formation_id"])
|
formation_id = int(vals["formation_id"])
|
||||||
F = sco_formations.formation_list(args={"formation_id": formation_id})
|
F = sco_formations.formation_list(args={"formation_id": formation_id})
|
||||||
if not F:
|
if not F:
|
||||||
raise ScoValueError("Formation inexistante !")
|
raise ScoValueError("Formation inexistante !")
|
||||||
F = F[0]
|
F = F[0]
|
||||||
if not edit:
|
if not edit:
|
||||||
initvalues = {"titre": _default_sem_title(F)}
|
initvalues = {"titre": _default_sem_title(F)}
|
||||||
semestre_id = int(REQUEST.form["semestre_id"])
|
semestre_id = int(vals["semestre_id"])
|
||||||
sem_module_ids = set()
|
sem_module_ids = set()
|
||||||
else:
|
else:
|
||||||
# setup form init values
|
# setup form init values
|
||||||
initvalues = sem
|
initvalues = sem
|
||||||
semestre_id = initvalues["semestre_id"]
|
semestre_id = initvalues["semestre_id"]
|
||||||
# add associated modules to tf-checked:
|
# add associated modules to tf-checked:
|
||||||
ams = sco_moduleimpl.do_moduleimpl_list(formsemestre_id=formsemestre_id)
|
ams = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
sem_module_ids = set([x["module_id"] for x in ams])
|
sem_module_ids = set([x["module_id"] for x in ams])
|
||||||
initvalues["tf-checked"] = ["MI" + str(x["module_id"]) for x in ams]
|
initvalues["tf-checked"] = ["MI" + str(x["module_id"]) for x in ams]
|
||||||
for x in ams:
|
for x in ams:
|
||||||
|
@ -204,11 +205,11 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
# on pourrait faire un simple module_list( )
|
# on pourrait faire un simple module_list( )
|
||||||
# mais si on veut l'ordre du PPN (groupe par UE et matieres) il faut:
|
# mais si on veut l'ordre du PPN (groupe par UE et matieres) il faut:
|
||||||
mods = [] # liste de dicts
|
mods = [] # liste de dicts
|
||||||
uelist = sco_edit_ue.do_ue_list({"formation_id": formation_id})
|
uelist = sco_edit_ue.ue_list({"formation_id": formation_id})
|
||||||
for ue in uelist:
|
for ue in uelist:
|
||||||
matlist = sco_edit_matiere.do_matiere_list({"ue_id": ue["ue_id"]})
|
matlist = sco_edit_matiere.matiere_list({"ue_id": ue["ue_id"]})
|
||||||
for mat in matlist:
|
for mat in matlist:
|
||||||
modsmat = sco_edit_module.do_module_list({"matiere_id": mat["matiere_id"]})
|
modsmat = sco_edit_module.module_list({"matiere_id": mat["matiere_id"]})
|
||||||
# XXX debug checks
|
# XXX debug checks
|
||||||
for m in modsmat:
|
for m in modsmat:
|
||||||
if m["ue_id"] != ue["ue_id"]:
|
if m["ue_id"] != ue["ue_id"]:
|
||||||
|
@ -309,7 +310,9 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
{
|
{
|
||||||
"size": 40,
|
"size": 40,
|
||||||
"title": "Nom de ce semestre",
|
"title": "Nom de ce semestre",
|
||||||
"explanation": """n'indiquez pas les dates, ni le semestre, ni la modalité dans le titre: ils seront automatiquement ajoutés <input type="button" value="remettre titre par défaut" onClick="document.tf.titre.value='%s';"/>"""
|
"explanation": """n'indiquez pas les dates, ni le semestre, ni la modalité dans
|
||||||
|
le titre: ils seront automatiquement ajoutés <input type="button"
|
||||||
|
value="remettre titre par défaut" onClick="document.tf.titre.value='%s';"/>"""
|
||||||
% _default_sem_title(F),
|
% _default_sem_title(F),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -501,6 +504,14 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
"labels": [""],
|
"labels": [""],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"block_moyennes",
|
||||||
|
{
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"title": "Bloquer moyennes",
|
||||||
|
"explanation": "empêcher le calcul des moyennes d'UE et générale.",
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"sep",
|
"sep",
|
||||||
{
|
{
|
||||||
|
@ -534,7 +545,7 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
select_name = "%s!group_id" % mod["module_id"]
|
select_name = "%s!group_id" % mod["module_id"]
|
||||||
|
|
||||||
def opt_selected(gid):
|
def opt_selected(gid):
|
||||||
if gid == REQUEST.form.get(select_name):
|
if gid == vals.get(select_name):
|
||||||
return "selected"
|
return "selected"
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
@ -623,38 +634,29 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
initvalues["gestion_compensation_lst"] = ["X"]
|
initvalues["gestion_compensation_lst"] = ["X"]
|
||||||
else:
|
else:
|
||||||
initvalues["gestion_compensation_lst"] = []
|
initvalues["gestion_compensation_lst"] = []
|
||||||
if (
|
if vals.get("tf_submitted", False) and "gestion_compensation_lst" not in vals:
|
||||||
REQUEST.form.get("tf_submitted", False)
|
vals["gestion_compensation_lst"] = []
|
||||||
and "gestion_compensation_lst" not in REQUEST.form
|
|
||||||
):
|
|
||||||
REQUEST.form["gestion_compensation_lst"] = []
|
|
||||||
|
|
||||||
initvalues["gestion_semestrielle"] = initvalues.get("gestion_semestrielle", False)
|
initvalues["gestion_semestrielle"] = initvalues.get("gestion_semestrielle", False)
|
||||||
if initvalues["gestion_semestrielle"]:
|
if initvalues["gestion_semestrielle"]:
|
||||||
initvalues["gestion_semestrielle_lst"] = ["X"]
|
initvalues["gestion_semestrielle_lst"] = ["X"]
|
||||||
else:
|
else:
|
||||||
initvalues["gestion_semestrielle_lst"] = []
|
initvalues["gestion_semestrielle_lst"] = []
|
||||||
if (
|
if vals.get("tf_submitted", False) and "gestion_semestrielle_lst" not in vals:
|
||||||
REQUEST.form.get("tf_submitted", False)
|
vals["gestion_semestrielle_lst"] = []
|
||||||
and "gestion_semestrielle_lst" not in REQUEST.form
|
|
||||||
):
|
|
||||||
REQUEST.form["gestion_semestrielle_lst"] = []
|
|
||||||
|
|
||||||
initvalues["bul_hide_xml"] = initvalues.get("bul_hide_xml", False)
|
initvalues["bul_hide_xml"] = initvalues.get("bul_hide_xml", False)
|
||||||
if not initvalues["bul_hide_xml"]:
|
if not initvalues["bul_hide_xml"]:
|
||||||
initvalues["bul_publish_xml_lst"] = ["X"]
|
initvalues["bul_publish_xml_lst"] = ["X"]
|
||||||
else:
|
else:
|
||||||
initvalues["bul_publish_xml_lst"] = []
|
initvalues["bul_publish_xml_lst"] = []
|
||||||
if (
|
if vals.get("tf_submitted", False) and "bul_publish_xml_lst" not in vals:
|
||||||
REQUEST.form.get("tf_submitted", False)
|
vals["bul_publish_xml_lst"] = []
|
||||||
and "bul_publish_xml_lst" not in REQUEST.form
|
|
||||||
):
|
|
||||||
REQUEST.form["bul_publish_xml_lst"] = []
|
|
||||||
|
|
||||||
#
|
#
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
vals,
|
||||||
modform,
|
modform,
|
||||||
submitlabel=submitlabel,
|
submitlabel=submitlabel,
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
|
@ -673,7 +675,7 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
|
|
||||||
if tf[0] == 0 or msg:
|
if tf[0] == 0 or msg:
|
||||||
return (
|
return (
|
||||||
'<p>Formation <a class="discretelink" href="ue_list?formation_id=%(formation_id)s"><em>%(titre)s</em> (%(acronyme)s), version %(version)s, code %(formation_code)s</a></p>'
|
'<p>Formation <a class="discretelink" href="ue_table?formation_id=%(formation_id)s"><em>%(titre)s</em> (%(acronyme)s), version %(version)s, code %(formation_code)s</a></p>'
|
||||||
% F
|
% F
|
||||||
+ msg
|
+ msg
|
||||||
+ str(tf[1])
|
+ str(tf[1])
|
||||||
|
@ -693,7 +695,6 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
tf[2]["bul_hide_xml"] = False
|
tf[2]["bul_hide_xml"] = False
|
||||||
else:
|
else:
|
||||||
tf[2]["bul_hide_xml"] = True
|
tf[2]["bul_hide_xml"] = True
|
||||||
|
|
||||||
# remap les identifiants de responsables:
|
# remap les identifiants de responsables:
|
||||||
tf[2]["responsable_id"] = User.get_user_id_from_nomplogin(
|
tf[2]["responsable_id"] = User.get_user_id_from_nomplogin(
|
||||||
tf[2]["responsable_id"]
|
tf[2]["responsable_id"]
|
||||||
|
@ -750,7 +751,7 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
# (retire le "MI" du début du nom de champs)
|
# (retire le "MI" du début du nom de champs)
|
||||||
checkedmods = [int(x[2:]) for x in tf[2]["tf-checked"]]
|
checkedmods = [int(x[2:]) for x in tf[2]["tf-checked"]]
|
||||||
sco_formsemestre.do_formsemestre_edit(tf[2])
|
sco_formsemestre.do_formsemestre_edit(tf[2])
|
||||||
ams = sco_moduleimpl.do_moduleimpl_list(formsemestre_id=formsemestre_id)
|
ams = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
existingmods = [x["module_id"] for x in ams]
|
existingmods = [x["module_id"] for x in ams]
|
||||||
mods_tocreate = [x for x in checkedmods if not x in existingmods]
|
mods_tocreate = [x for x in checkedmods if not x in existingmods]
|
||||||
# modules a existants a modifier
|
# modules a existants a modifier
|
||||||
|
@ -766,7 +767,7 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
"responsable_id": tf[2]["MI" + str(module_id)],
|
"responsable_id": tf[2]["MI" + str(module_id)],
|
||||||
}
|
}
|
||||||
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(modargs)
|
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(modargs)
|
||||||
mod = sco_edit_module.do_module_list({"module_id": module_id})[0]
|
mod = sco_edit_module.module_list({"module_id": module_id})[0]
|
||||||
msg += ["création de %s (%s)" % (mod["code"], mod["titre"])]
|
msg += ["création de %s (%s)" % (mod["code"], mod["titre"])]
|
||||||
# INSCRIPTIONS DES ETUDIANTS
|
# INSCRIPTIONS DES ETUDIANTS
|
||||||
log(
|
log(
|
||||||
|
@ -786,7 +787,6 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
moduleimpl_id,
|
moduleimpl_id,
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etudids,
|
etudids,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
msg += [
|
msg += [
|
||||||
"inscription de %d étudiants au module %s"
|
"inscription de %d étudiants au module %s"
|
||||||
|
@ -801,7 +801,7 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
ok, diag = formsemestre_delete_moduleimpls(formsemestre_id, mods_todelete)
|
ok, diag = formsemestre_delete_moduleimpls(formsemestre_id, mods_todelete)
|
||||||
msg += diag
|
msg += diag
|
||||||
for module_id in mods_toedit:
|
for module_id in mods_toedit:
|
||||||
moduleimpl_id = sco_moduleimpl.do_moduleimpl_list(
|
moduleimpl_id = sco_moduleimpl.moduleimpl_list(
|
||||||
formsemestre_id=formsemestre_id, module_id=module_id
|
formsemestre_id=formsemestre_id, module_id=module_id
|
||||||
)[0]["moduleimpl_id"]
|
)[0]["moduleimpl_id"]
|
||||||
modargs = {
|
modargs = {
|
||||||
|
@ -813,7 +813,7 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||||
sco_moduleimpl.do_moduleimpl_edit(
|
sco_moduleimpl.do_moduleimpl_edit(
|
||||||
modargs, formsemestre_id=formsemestre_id
|
modargs, formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
mod = sco_edit_module.do_module_list({"module_id": module_id})[0]
|
mod = sco_edit_module.module_list({"module_id": module_id})[0]
|
||||||
|
|
||||||
if msg:
|
if msg:
|
||||||
msg_html = (
|
msg_html = (
|
||||||
|
@ -846,10 +846,10 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
|
||||||
msg = []
|
msg = []
|
||||||
for module_id in module_ids_to_del:
|
for module_id in module_ids_to_del:
|
||||||
# get id
|
# get id
|
||||||
moduleimpl_id = sco_moduleimpl.do_moduleimpl_list(
|
moduleimpl_id = sco_moduleimpl.moduleimpl_list(
|
||||||
formsemestre_id=formsemestre_id, module_id=module_id
|
formsemestre_id=formsemestre_id, module_id=module_id
|
||||||
)[0]["moduleimpl_id"]
|
)[0]["moduleimpl_id"]
|
||||||
mod = sco_edit_module.do_module_list({"module_id": module_id})[0]
|
mod = sco_edit_module.module_list({"module_id": module_id})[0]
|
||||||
# Evaluations dans ce module ?
|
# Evaluations dans ce module ?
|
||||||
evals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
|
evals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
|
||||||
if evals:
|
if evals:
|
||||||
|
@ -867,7 +867,7 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
|
||||||
return ok, msg
|
return ok, msg
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_clone(formsemestre_id, REQUEST=None):
|
def formsemestre_clone(formsemestre_id):
|
||||||
"""
|
"""
|
||||||
Formulaire clonage d'un semestre
|
Formulaire clonage d'un semestre
|
||||||
"""
|
"""
|
||||||
|
@ -888,7 +888,6 @@ def formsemestre_clone(formsemestre_id, REQUEST=None):
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
REQUEST,
|
|
||||||
"Copie du semestre",
|
"Copie du semestre",
|
||||||
sem,
|
sem,
|
||||||
javascripts=["libjs/AutoSuggest.js"],
|
javascripts=["libjs/AutoSuggest.js"],
|
||||||
|
@ -959,8 +958,8 @@ def formsemestre_clone(formsemestre_id, REQUEST=None):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
descr,
|
descr,
|
||||||
submitlabel="Dupliquer ce semestre",
|
submitlabel="Dupliquer ce semestre",
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
|
@ -985,7 +984,6 @@ def formsemestre_clone(formsemestre_id, REQUEST=None):
|
||||||
tf[2]["date_fin"],
|
tf[2]["date_fin"],
|
||||||
clone_evaluations=tf[2]["clone_evaluations"],
|
clone_evaluations=tf[2]["clone_evaluations"],
|
||||||
clone_partitions=tf[2]["clone_partitions"],
|
clone_partitions=tf[2]["clone_partitions"],
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé"
|
"formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé"
|
||||||
|
@ -1000,7 +998,6 @@ def do_formsemestre_clone(
|
||||||
date_fin, # 'dd/mm/yyyy'
|
date_fin, # 'dd/mm/yyyy'
|
||||||
clone_evaluations=False,
|
clone_evaluations=False,
|
||||||
clone_partitions=False,
|
clone_partitions=False,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Clone a semestre: make copy, same modules, same options, same resps, same partitions.
|
"""Clone a semestre: make copy, same modules, same options, same resps, same partitions.
|
||||||
New dates, responsable_id
|
New dates, responsable_id
|
||||||
|
@ -1018,7 +1015,7 @@ def do_formsemestre_clone(
|
||||||
formsemestre_id = sco_formsemestre.do_formsemestre_create(args)
|
formsemestre_id = sco_formsemestre.do_formsemestre_create(args)
|
||||||
log("created formsemestre %s" % formsemestre_id)
|
log("created formsemestre %s" % formsemestre_id)
|
||||||
# 2- create moduleimpls
|
# 2- create moduleimpls
|
||||||
mods_orig = sco_moduleimpl.do_moduleimpl_list(formsemestre_id=orig_formsemestre_id)
|
mods_orig = sco_moduleimpl.moduleimpl_list(formsemestre_id=orig_formsemestre_id)
|
||||||
for mod_orig in mods_orig:
|
for mod_orig in mods_orig:
|
||||||
args = mod_orig.copy()
|
args = mod_orig.copy()
|
||||||
args["formsemestre_id"] = formsemestre_id
|
args["formsemestre_id"] = formsemestre_id
|
||||||
|
@ -1040,7 +1037,7 @@ def do_formsemestre_clone(
|
||||||
args = e.copy()
|
args = e.copy()
|
||||||
del args["jour"] # erase date
|
del args["jour"] # erase date
|
||||||
args["moduleimpl_id"] = mid
|
args["moduleimpl_id"] = mid
|
||||||
_ = sco_evaluations.do_evaluation_create(REQUEST=REQUEST, **args)
|
_ = sco_evaluations.do_evaluation_create(**args)
|
||||||
|
|
||||||
# 3- copy uecoefs
|
# 3- copy uecoefs
|
||||||
objs = sco_formsemestre.formsemestre_uecoef_list(
|
objs = sco_formsemestre.formsemestre_uecoef_list(
|
||||||
|
@ -1077,32 +1074,11 @@ def do_formsemestre_clone(
|
||||||
args["formsemestre_id"] = formsemestre_id
|
args["formsemestre_id"] = formsemestre_id
|
||||||
_ = sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, args)
|
_ = sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, args)
|
||||||
|
|
||||||
# 5- Copy partitions
|
# 5- Copy partitions and groups
|
||||||
if clone_partitions:
|
if clone_partitions:
|
||||||
listgroups = []
|
sco_groups_copy.clone_partitions_and_groups(
|
||||||
listnamegroups = []
|
orig_formsemestre_id, formsemestre_id
|
||||||
# Création des partitions:
|
)
|
||||||
for part in sco_groups.get_partitions_list(orig_formsemestre_id):
|
|
||||||
if part["partition_name"] != None:
|
|
||||||
partname = part["partition_name"]
|
|
||||||
new_partition_id = sco_groups.partition_create(
|
|
||||||
formsemestre_id,
|
|
||||||
partition_name=partname,
|
|
||||||
redirect=0,
|
|
||||||
)
|
|
||||||
for g in sco_groups.get_partition_groups(part):
|
|
||||||
if g["group_name"] != None:
|
|
||||||
listnamegroups.append(g["group_name"])
|
|
||||||
listgroups.append([new_partition_id, listnamegroups])
|
|
||||||
listnamegroups = []
|
|
||||||
|
|
||||||
# Création des groupes dans les nouvelles partitions:
|
|
||||||
for newpart in sco_groups.get_partitions_list(formsemestre_id):
|
|
||||||
for g in listgroups:
|
|
||||||
if newpart["partition_id"] == g[0]:
|
|
||||||
part_id = g[0]
|
|
||||||
for group_name in g[1]:
|
|
||||||
_ = sco_groups.createGroup(part_id, group_name=group_name)
|
|
||||||
|
|
||||||
return formsemestre_id
|
return formsemestre_id
|
||||||
|
|
||||||
|
@ -1113,10 +1089,11 @@ def do_formsemestre_clone(
|
||||||
def formsemestre_associate_new_version(
|
def formsemestre_associate_new_version(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
other_formsemestre_ids=[],
|
other_formsemestre_ids=[],
|
||||||
REQUEST=None,
|
|
||||||
dialog_confirmed=False,
|
dialog_confirmed=False,
|
||||||
):
|
):
|
||||||
"""Formulaire changement formation d'un semestre"""
|
"""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:
|
if not dialog_confirmed:
|
||||||
# dresse le liste des semestres de la meme formation et version
|
# dresse le liste des semestres de la meme formation et version
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
|
@ -1161,15 +1138,19 @@ def formsemestre_associate_new_version(
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
do_formsemestres_associate_new_version(
|
do_formsemestres_associate_new_version(
|
||||||
[formsemestre_id] + other_formsemestre_ids, REQUEST=REQUEST
|
[formsemestre_id] + other_formsemestre_ids
|
||||||
)
|
)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"formsemestre_status?formsemestre_id=%s&head_message=Formation%%20dupliquée"
|
url_for(
|
||||||
% formsemestre_id
|
"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.
|
"""Cree une nouvelle version de la formation du semestre, et y rattache les semestres.
|
||||||
Tous les moduleimpl sont ré-associés à la nouvelle formation, ainsi que les decisions de jury
|
Tous les moduleimpl sont ré-associés à la nouvelle formation, ainsi que les decisions de jury
|
||||||
si elles existent (codes d'UE validées).
|
si elles existent (codes d'UE validées).
|
||||||
|
@ -1179,9 +1160,11 @@ def do_formsemestres_associate_new_version(formsemestre_ids, REQUEST=None):
|
||||||
if not formsemestre_ids:
|
if not formsemestre_ids:
|
||||||
return
|
return
|
||||||
# Check: tous de la même formation
|
# Check: tous de la même formation
|
||||||
|
assert isinstance(formsemestre_ids[0], int)
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_ids[0])
|
sem = sco_formsemestre.get_formsemestre(formsemestre_ids[0])
|
||||||
formation_id = sem["formation_id"]
|
formation_id = sem["formation_id"]
|
||||||
for formsemestre_id in formsemestre_ids[1:]:
|
for formsemestre_id in formsemestre_ids[1:]:
|
||||||
|
assert isinstance(formsemestre_id, int)
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
if formation_id != sem["formation_id"]:
|
if formation_id != sem["formation_id"]:
|
||||||
raise ScoValueError("les semestres ne sont pas tous de la même formation !")
|
raise ScoValueError("les semestres ne sont pas tous de la même formation !")
|
||||||
|
@ -1192,9 +1175,7 @@ def do_formsemestres_associate_new_version(formsemestre_ids, REQUEST=None):
|
||||||
formation_id,
|
formation_id,
|
||||||
modules_old2new,
|
modules_old2new,
|
||||||
ues_old2new,
|
ues_old2new,
|
||||||
) = sco_formations.formation_create_new_version(
|
) = sco_formations.formation_create_new_version(formation_id, redirect=False)
|
||||||
formation_id, redirect=False, REQUEST=REQUEST
|
|
||||||
)
|
|
||||||
|
|
||||||
for formsemestre_id in formsemestre_ids:
|
for formsemestre_id in formsemestre_ids:
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
|
@ -1210,7 +1191,7 @@ def _reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new)
|
||||||
et met à jour les décisions de jury (validations d'UE).
|
et met à jour les décisions de jury (validations d'UE).
|
||||||
"""
|
"""
|
||||||
# re-associate moduleimpls to new modules:
|
# re-associate moduleimpls to new modules:
|
||||||
modimpls = sco_moduleimpl.do_moduleimpl_list(formsemestre_id=formsemestre_id)
|
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
for mod in modimpls:
|
for mod in modimpls:
|
||||||
mod["module_id"] = modules_old2new[mod["module_id"]]
|
mod["module_id"] = modules_old2new[mod["module_id"]]
|
||||||
sco_moduleimpl.do_moduleimpl_edit(mod, formsemestre_id=formsemestre_id)
|
sco_moduleimpl.do_moduleimpl_edit(mod, formsemestre_id=formsemestre_id)
|
||||||
|
@ -1230,12 +1211,12 @@ def _reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new)
|
||||||
sco_parcours_dut.scolar_formsemestre_validation_edit(cnx, e)
|
sco_parcours_dut.scolar_formsemestre_validation_edit(cnx, e)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_delete(formsemestre_id, REQUEST=None):
|
def formsemestre_delete(formsemestre_id):
|
||||||
"""Delete a formsemestre (affiche avertissements)"""
|
"""Delete a formsemestre (affiche avertissements)"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||||
H = [
|
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>
|
"""<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,
|
<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>
|
<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,8 +1242,8 @@ def formsemestre_delete(formsemestre_id, REQUEST=None):
|
||||||
else:
|
else:
|
||||||
submit_label = "Confirmer la suppression du semestre"
|
submit_label = "Confirmer la suppression du semestre"
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(("formsemestre_id", {"input_type": "hidden"}),),
|
(("formsemestre_id", {"input_type": "hidden"}),),
|
||||||
initvalues=F,
|
initvalues=F,
|
||||||
submitlabel=submit_label,
|
submitlabel=submit_label,
|
||||||
|
@ -1327,7 +1308,7 @@ def do_formsemestre_delete(formsemestre_id):
|
||||||
sco_cache.EvaluationCache.invalidate_sem(formsemestre_id)
|
sco_cache.EvaluationCache.invalidate_sem(formsemestre_id)
|
||||||
|
|
||||||
# --- Destruction des modules de ce semestre
|
# --- Destruction des modules de ce semestre
|
||||||
mods = sco_moduleimpl.do_moduleimpl_list(formsemestre_id=formsemestre_id)
|
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
# evaluations
|
# evaluations
|
||||||
evals = sco_evaluations.do_evaluation_list(
|
evals = sco_evaluations.do_evaluation_list(
|
||||||
|
@ -1421,7 +1402,7 @@ def do_formsemestre_delete(formsemestre_id):
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------
|
||||||
def formsemestre_edit_options(formsemestre_id, target_url=None, REQUEST=None):
|
def formsemestre_edit_options(formsemestre_id):
|
||||||
"""dialog to change formsemestre options
|
"""dialog to change formsemestre options
|
||||||
(accessible par ScoImplement ou dir. etudes)
|
(accessible par ScoImplement ou dir. etudes)
|
||||||
"""
|
"""
|
||||||
|
@ -1429,12 +1410,10 @@ def formsemestre_edit_options(formsemestre_id, target_url=None, REQUEST=None):
|
||||||
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
||||||
if not ok:
|
if not ok:
|
||||||
return err
|
return err
|
||||||
return sco_preferences.SemPreferences(formsemestre_id).edit(
|
return sco_preferences.SemPreferences(formsemestre_id).edit(categories=["bul"])
|
||||||
REQUEST=REQUEST, categories=["bul"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_change_lock(formsemestre_id, REQUEST=None, dialog_confirmed=False):
|
def formsemestre_change_lock(formsemestre_id) -> None:
|
||||||
"""Change etat (verrouille si ouvert, déverrouille si fermé)
|
"""Change etat (verrouille si ouvert, déverrouille si fermé)
|
||||||
nota: etat (1 ouvert, 0 fermé)
|
nota: etat (1 ouvert, 0 fermé)
|
||||||
"""
|
"""
|
||||||
|
@ -1444,34 +1423,12 @@ def formsemestre_change_lock(formsemestre_id, REQUEST=None, dialog_confirmed=Fal
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
etat = not sem["etat"]
|
etat = not sem["etat"]
|
||||||
|
|
||||||
if REQUEST and not dialog_confirmed:
|
|
||||||
if etat:
|
|
||||||
msg = "déverrouillage"
|
|
||||||
else:
|
|
||||||
msg = "verrouillage"
|
|
||||||
return scu.confirm_dialog(
|
|
||||||
"<h2>Confirmer le %s du semestre ?</h2>" % msg,
|
|
||||||
helpmsg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées.
|
|
||||||
Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment
|
|
||||||
(par son responsable ou un administrateur).
|
|
||||||
<br/>
|
|
||||||
Le programme d'une formation qui a un semestre verrouillé ne peut plus être modifié.
|
|
||||||
""",
|
|
||||||
dest_url="",
|
|
||||||
cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id,
|
|
||||||
parameters={"formsemestre_id": formsemestre_id},
|
|
||||||
)
|
|
||||||
|
|
||||||
args = {"formsemestre_id": formsemestre_id, "etat": etat}
|
args = {"formsemestre_id": formsemestre_id, "etat": etat}
|
||||||
sco_formsemestre.do_formsemestre_edit(args)
|
sco_formsemestre.do_formsemestre_edit(args)
|
||||||
if REQUEST:
|
|
||||||
return flask.redirect(
|
|
||||||
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_change_publication_bul(
|
def formsemestre_change_publication_bul(
|
||||||
formsemestre_id, REQUEST=None, dialog_confirmed=False
|
formsemestre_id, dialog_confirmed=False, redirect=True
|
||||||
):
|
):
|
||||||
"""Change etat publication bulletins sur portail"""
|
"""Change etat publication bulletins sur portail"""
|
||||||
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
||||||
|
@ -1480,7 +1437,7 @@ def formsemestre_change_publication_bul(
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
etat = not sem["bul_hide_xml"]
|
etat = not sem["bul_hide_xml"]
|
||||||
|
|
||||||
if REQUEST and not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
if etat:
|
if etat:
|
||||||
msg = "non"
|
msg = "non"
|
||||||
else:
|
else:
|
||||||
|
@ -1499,14 +1456,14 @@ def formsemestre_change_publication_bul(
|
||||||
|
|
||||||
args = {"formsemestre_id": formsemestre_id, "bul_hide_xml": etat}
|
args = {"formsemestre_id": formsemestre_id, "bul_hide_xml": etat}
|
||||||
sco_formsemestre.do_formsemestre_edit(args)
|
sco_formsemestre.do_formsemestre_edit(args)
|
||||||
if REQUEST:
|
if redirect:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
|
def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||||
"""Changement manuel des coefficients des UE capitalisées."""
|
"""Changement manuel des coefficients des UE capitalisées."""
|
||||||
from app.scodoc import notes_table
|
from app.scodoc import notes_table
|
||||||
|
|
||||||
|
@ -1538,9 +1495,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
|
||||||
</p>
|
</p>
|
||||||
"""
|
"""
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header("Coefficients des UE du semestre", sem),
|
||||||
REQUEST, "Coefficients des UE du semestre", sem
|
|
||||||
),
|
|
||||||
help,
|
help,
|
||||||
]
|
]
|
||||||
#
|
#
|
||||||
|
@ -1576,8 +1531,8 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
|
||||||
form.append(("ue_" + str(ue["ue_id"]), descr))
|
form.append(("ue_" + str(ue["ue_id"]), descr))
|
||||||
|
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
form,
|
form,
|
||||||
submitlabel="Changer les coefficients",
|
submitlabel="Changer les coefficients",
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
|
@ -1652,9 +1607,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
|
||||||
formsemestre_id=formsemestre_id
|
formsemestre_id=formsemestre_id
|
||||||
) # > modif coef UE cap (modifs notes de _certains_ etudiants)
|
) # > modif coef UE cap (modifs notes de _certains_ etudiants)
|
||||||
|
|
||||||
header = html_sco_header.html_sem_header(
|
header = html_sco_header.html_sem_header("Coefficients des UE du semestre", sem)
|
||||||
REQUEST, "Coefficients des UE du semestre", sem
|
|
||||||
)
|
|
||||||
return (
|
return (
|
||||||
header
|
header
|
||||||
+ "\n".join(z)
|
+ "\n".join(z)
|
||||||
|
|
|
@ -34,7 +34,7 @@ Ces semestres n'auront qu'un seul inscrit !
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g
|
from flask import url_for, g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -52,7 +52,7 @@ from app.scodoc import sco_parcours_dut
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_ext_create(etudid, sem_params, REQUEST=None):
|
def formsemestre_ext_create(etudid, sem_params):
|
||||||
"""Crée un formsemestre exterieur et y inscrit l'étudiant.
|
"""Crée un formsemestre exterieur et y inscrit l'étudiant.
|
||||||
sem_params: dict nécessaire à la création du formsemestre
|
sem_params: dict nécessaire à la création du formsemestre
|
||||||
"""
|
"""
|
||||||
|
@ -79,7 +79,7 @@ def formsemestre_ext_create(etudid, sem_params, REQUEST=None):
|
||||||
return formsemestre_id
|
return formsemestre_id
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_ext_create_form(etudid, formsemestre_id, REQUEST=None):
|
def formsemestre_ext_create_form(etudid, formsemestre_id):
|
||||||
"""Formulaire creation/inscription à un semestre extérieur"""
|
"""Formulaire creation/inscription à un semestre extérieur"""
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
H = [
|
H = [
|
||||||
|
@ -181,8 +181,8 @@ def formsemestre_ext_create_form(etudid, formsemestre_id, REQUEST=None):
|
||||||
]
|
]
|
||||||
|
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
descr,
|
descr,
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
method="post",
|
method="post",
|
||||||
|
@ -204,13 +204,13 @@ def formsemestre_ext_create_form(etudid, formsemestre_id, REQUEST=None):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tf[2]["formation_id"] = orig_sem["formation_id"]
|
tf[2]["formation_id"] = orig_sem["formation_id"]
|
||||||
formsemestre_ext_create(etudid, tf[2], REQUEST=REQUEST)
|
formsemestre_ext_create(etudid, tf[2])
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid, REQUEST=None):
|
def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid):
|
||||||
"""Edition des validations d'UE et de semestre (jury)
|
"""Edition des validations d'UE et de semestre (jury)
|
||||||
pour un semestre extérieur.
|
pour un semestre extérieur.
|
||||||
On peut saisir pour chaque UE du programme de formation
|
On peut saisir pour chaque UE du programme de formation
|
||||||
|
@ -221,18 +221,17 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid, REQUEST=None):
|
||||||
"""
|
"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
ue_list = _list_ue_with_coef_and_validations(sem, etudid)
|
ues = _list_ue_with_coef_and_validations(sem, etudid)
|
||||||
descr = _ue_form_description(ue_list, REQUEST.form)
|
descr = _ue_form_description(ues, scu.get_request_args())
|
||||||
if REQUEST and REQUEST.method == "GET":
|
if request.method == "GET":
|
||||||
initvalues = {
|
initvalues = {
|
||||||
"note_" + str(ue["ue_id"]): ue["validation"].get("moy_ue", "")
|
"note_" + str(ue["ue_id"]): ue["validation"].get("moy_ue", "") for ue in ues
|
||||||
for ue in ue_list
|
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
initvalues = {}
|
initvalues = {}
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
descr,
|
descr,
|
||||||
cssclass="tf_ext_edit_ue_validations",
|
cssclass="tf_ext_edit_ue_validations",
|
||||||
submitlabel="Enregistrer ces validations",
|
submitlabel="Enregistrer ces validations",
|
||||||
|
@ -242,27 +241,25 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid, REQUEST=None):
|
||||||
if tf[0] == -1:
|
if tf[0] == -1:
|
||||||
return "<h4>annulation</h4>"
|
return "<h4>annulation</h4>"
|
||||||
else:
|
else:
|
||||||
H = _make_page(etud, sem, tf, REQUEST=REQUEST)
|
H = _make_page(etud, sem, tf)
|
||||||
if tf[0] == 0: # premier affichage
|
if tf[0] == 0: # premier affichage
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
else: # soumission
|
else: # soumission
|
||||||
# simule erreur
|
# simule erreur
|
||||||
ok, message = _check_values(ue_list, tf[2])
|
ok, message = _check_values(ues, tf[2])
|
||||||
if not ok:
|
if not ok:
|
||||||
H = _make_page(etud, sem, tf, message=message, REQUEST=REQUEST)
|
H = _make_page(etud, sem, tf, message=message)
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
else:
|
else:
|
||||||
# Submit
|
# Submit
|
||||||
_record_ue_validations_and_coefs(
|
_record_ue_validations_and_coefs(formsemestre_id, etudid, ues, tf[2])
|
||||||
formsemestre_id, etudid, ue_list, tf[2], REQUEST=REQUEST
|
|
||||||
)
|
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s"
|
"formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s"
|
||||||
% (formsemestre_id, etudid)
|
% (formsemestre_id, etudid)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _make_page(etud, sem, tf, message="", REQUEST=None):
|
def _make_page(etud, sem, tf, message=""):
|
||||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||||
moy_gen = nt.get_etud_moy_gen(etud["etudid"])
|
moy_gen = nt.get_etud_moy_gen(etud["etudid"])
|
||||||
H = [
|
H = [
|
||||||
|
@ -303,7 +300,7 @@ _UE_VALID_CODES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _ue_form_description(ue_list, values):
|
def _ue_form_description(ues, values):
|
||||||
"""Description du formulaire de saisie des UE / validations
|
"""Description du formulaire de saisie des UE / validations
|
||||||
Pour chaque UE, on peut saisir: son code jury, sa note, son coefficient.
|
Pour chaque UE, on peut saisir: son code jury, sa note, son coefficient.
|
||||||
"""
|
"""
|
||||||
|
@ -320,7 +317,7 @@ def _ue_form_description(ue_list, values):
|
||||||
("formsemestre_id", {"input_type": "hidden"}),
|
("formsemestre_id", {"input_type": "hidden"}),
|
||||||
("etudid", {"input_type": "hidden"}),
|
("etudid", {"input_type": "hidden"}),
|
||||||
]
|
]
|
||||||
for ue in ue_list:
|
for ue in ues:
|
||||||
# Menu pour code validation UE:
|
# Menu pour code validation UE:
|
||||||
# Ne propose que ADM, CMP et "Non inscrit"
|
# Ne propose que ADM, CMP et "Non inscrit"
|
||||||
select_name = "valid_" + str(ue["ue_id"])
|
select_name = "valid_" + str(ue["ue_id"])
|
||||||
|
@ -439,8 +436,8 @@ def _list_ue_with_coef_and_validations(sem, etudid):
|
||||||
"""
|
"""
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
formsemestre_id = sem["formsemestre_id"]
|
formsemestre_id = sem["formsemestre_id"]
|
||||||
ue_list = sco_edit_ue.do_ue_list({"formation_id": sem["formation_id"]})
|
ues = sco_edit_ue.ue_list({"formation_id": sem["formation_id"]})
|
||||||
for ue in ue_list:
|
for ue in ues:
|
||||||
# add coefficient
|
# add coefficient
|
||||||
uecoef = sco_formsemestre.formsemestre_uecoef_list(
|
uecoef = sco_formsemestre.formsemestre_uecoef_list(
|
||||||
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]}
|
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]}
|
||||||
|
@ -462,13 +459,11 @@ def _list_ue_with_coef_and_validations(sem, etudid):
|
||||||
ue["validation"] = validation[0]
|
ue["validation"] = validation[0]
|
||||||
else:
|
else:
|
||||||
ue["validation"] = {}
|
ue["validation"] = {}
|
||||||
return ue_list
|
return ues
|
||||||
|
|
||||||
|
|
||||||
def _record_ue_validations_and_coefs(
|
def _record_ue_validations_and_coefs(formsemestre_id, etudid, ues, values):
|
||||||
formsemestre_id, etudid, ue_list, values, REQUEST=None
|
for ue in ues:
|
||||||
):
|
|
||||||
for ue in ue_list:
|
|
||||||
code = values.get("valid_" + str(ue["ue_id"]), False)
|
code = values.get("valid_" + str(ue["ue_id"]), False)
|
||||||
if code == "None":
|
if code == "None":
|
||||||
code = None
|
code = None
|
||||||
|
@ -492,5 +487,4 @@ def _record_ue_validations_and_coefs(
|
||||||
now_dmy,
|
now_dmy,
|
||||||
code=code,
|
code=code,
|
||||||
ue_coefficient=coef,
|
ue_coefficient=coef,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -30,12 +30,12 @@
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import flask
|
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.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoException, ScoValueError
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_codes_parcours import UE_STANDARD, UE_SPORT, UE_TYPE_NAME
|
from app.scodoc.sco_codes_parcours import UE_STANDARD, UE_SPORT, UE_TYPE_NAME
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
@ -55,6 +55,7 @@ _formsemestre_inscriptionEditor = ndb.EditableTable(
|
||||||
"formsemestre_inscription_id",
|
"formsemestre_inscription_id",
|
||||||
("formsemestre_inscription_id", "etudid", "formsemestre_id", "etat", "etape"),
|
("formsemestre_inscription_id", "etudid", "formsemestre_id", "etat", "etape"),
|
||||||
sortkey="formsemestre_id",
|
sortkey="formsemestre_id",
|
||||||
|
insert_ignore_conflicts=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,6 +127,42 @@ def do_formsemestre_inscription_delete(oid, formsemestre_id=None):
|
||||||
) # > desinscription du semestre
|
) # > desinscription du semestre
|
||||||
|
|
||||||
|
|
||||||
|
def do_formsemestre_demission(
|
||||||
|
etudid,
|
||||||
|
formsemestre_id,
|
||||||
|
event_date=None,
|
||||||
|
etat_new="D", # 'D' or DEF
|
||||||
|
operation_method="demEtudiant",
|
||||||
|
event_type="DEMISSION",
|
||||||
|
):
|
||||||
|
"Démission ou défaillance d'un étudiant"
|
||||||
|
# marque 'D' ou DEF dans l'inscription au semestre et ajoute
|
||||||
|
# un "evenement" scolarite
|
||||||
|
cnx = ndb.GetDBConnexion()
|
||||||
|
# check lock
|
||||||
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
|
if not sem["etat"]:
|
||||||
|
raise ScoValueError("Modification impossible: semestre verrouille")
|
||||||
|
#
|
||||||
|
ins = do_formsemestre_inscription_list(
|
||||||
|
{"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||||
|
)[0]
|
||||||
|
if not ins:
|
||||||
|
raise ScoException("etudiant non inscrit ?!")
|
||||||
|
ins["etat"] = etat_new
|
||||||
|
do_formsemestre_inscription_edit(args=ins, formsemestre_id=formsemestre_id)
|
||||||
|
logdb(cnx, method=operation_method, etudid=etudid)
|
||||||
|
sco_etud.scolar_events_create(
|
||||||
|
cnx,
|
||||||
|
args={
|
||||||
|
"etudid": etudid,
|
||||||
|
"event_date": event_date,
|
||||||
|
"formsemestre_id": formsemestre_id,
|
||||||
|
"event_type": event_type,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def do_formsemestre_inscription_edit(args=None, formsemestre_id=None):
|
def do_formsemestre_inscription_edit(args=None, formsemestre_id=None):
|
||||||
"edit a formsemestre_inscription"
|
"edit a formsemestre_inscription"
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
|
@ -236,7 +273,7 @@ def do_formsemestre_inscription_with_modules(
|
||||||
gdone[group_id] = 1
|
gdone[group_id] = 1
|
||||||
|
|
||||||
# inscription a tous les modules de ce semestre
|
# inscription a tous les modules de ce semestre
|
||||||
modimpls = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||||
formsemestre_id=formsemestre_id
|
formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
for mod in modimpls:
|
for mod in modimpls:
|
||||||
|
@ -248,7 +285,7 @@ def do_formsemestre_inscription_with_modules(
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_inscription_with_modules_etud(
|
def formsemestre_inscription_with_modules_etud(
|
||||||
formsemestre_id, etudid=None, group_ids=None, REQUEST=None
|
formsemestre_id, etudid=None, group_ids=None
|
||||||
):
|
):
|
||||||
"""Form. inscription d'un étudiant au semestre.
|
"""Form. inscription d'un étudiant au semestre.
|
||||||
Si etudid n'est pas specifié, form. choix etudiant.
|
Si etudid n'est pas specifié, form. choix etudiant.
|
||||||
|
@ -263,7 +300,7 @@ def formsemestre_inscription_with_modules_etud(
|
||||||
)
|
)
|
||||||
|
|
||||||
return formsemestre_inscription_with_modules(
|
return formsemestre_inscription_with_modules(
|
||||||
etudid, formsemestre_id, REQUEST=REQUEST, group_ids=group_ids
|
etudid, formsemestre_id, group_ids=group_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -318,7 +355,7 @@ def formsemestre_inscription_with_modules_form(etudid, only_ext=False):
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_inscription_with_modules(
|
def formsemestre_inscription_with_modules(
|
||||||
etudid, formsemestre_id, group_ids=None, multiple_ok=False, REQUEST=None
|
etudid, formsemestre_id, group_ids=None, multiple_ok=False
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Inscription de l'etud dans ce semestre.
|
Inscription de l'etud dans ce semestre.
|
||||||
|
@ -334,7 +371,6 @@ def formsemestre_inscription_with_modules(
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
REQUEST,
|
|
||||||
"Inscription de %s dans ce semestre" % etud["nomprenom"],
|
"Inscription de %s dans ce semestre" % etud["nomprenom"],
|
||||||
sem,
|
sem,
|
||||||
)
|
)
|
||||||
|
@ -415,7 +451,7 @@ def formsemestre_inscription_with_modules(
|
||||||
<input type="hidden" name="etudid" value="%s">
|
<input type="hidden" name="etudid" value="%s">
|
||||||
<input type="hidden" name="formsemestre_id" 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))
|
H.append(sco_groups.form_group_choice(formsemestre_id, allow_none=True))
|
||||||
|
@ -431,7 +467,7 @@ def formsemestre_inscription_with_modules(
|
||||||
return "\n".join(H) + F
|
return "\n".join(H) + F
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_inscription_option(etudid, formsemestre_id, REQUEST=None):
|
def formsemestre_inscription_option(etudid, formsemestre_id):
|
||||||
"""Dialogue pour (dés)inscription à des modules optionnels."""
|
"""Dialogue pour (dés)inscription à des modules optionnels."""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
if not sem["etat"]:
|
if not sem["etat"]:
|
||||||
|
@ -448,7 +484,7 @@ def formsemestre_inscription_option(etudid, formsemestre_id, REQUEST=None):
|
||||||
]
|
]
|
||||||
|
|
||||||
# Cherche les moduleimpls et les inscriptions
|
# Cherche les moduleimpls et les inscriptions
|
||||||
mods = sco_moduleimpl.do_moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
mods = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||||
inscr = sco_moduleimpl.do_moduleimpl_inscription_list(etudid=etudid)
|
inscr = sco_moduleimpl.do_moduleimpl_inscription_list(etudid=etudid)
|
||||||
# Formulaire
|
# Formulaire
|
||||||
modimpls_by_ue_ids = scu.DictDefault(defaultvalue=[]) # ue_id : [ moduleimpl_id ]
|
modimpls_by_ue_ids = scu.DictDefault(defaultvalue=[]) # ue_id : [ moduleimpl_id ]
|
||||||
|
@ -468,7 +504,8 @@ def formsemestre_inscription_option(etudid, formsemestre_id, REQUEST=None):
|
||||||
modimpls_by_ue_names[ue_id].append(
|
modimpls_by_ue_names[ue_id].append(
|
||||||
"%s %s" % (mod["module"]["code"], mod["module"]["titre"])
|
"%s %s" % (mod["module"]["code"], mod["module"]["titre"])
|
||||||
)
|
)
|
||||||
if not REQUEST.form.get("tf_submitted", False):
|
vals = scu.get_request_args()
|
||||||
|
if not vals.get("tf_submitted", False):
|
||||||
# inscrit ?
|
# inscrit ?
|
||||||
for ins in inscr:
|
for ins in inscr:
|
||||||
if ins["moduleimpl_id"] == mod["moduleimpl_id"]:
|
if ins["moduleimpl_id"] == mod["moduleimpl_id"]:
|
||||||
|
@ -533,8 +570,8 @@ function chkbx_select(field_id, state) {
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
descr,
|
descr,
|
||||||
initvalues,
|
initvalues,
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
|
@ -659,7 +696,7 @@ function chkbx_select(field_id, state) {
|
||||||
|
|
||||||
|
|
||||||
def do_moduleimpl_incription_options(
|
def do_moduleimpl_incription_options(
|
||||||
etudid, modulesimpls_ainscrire, modulesimpls_adesinscrire, REQUEST=None
|
etudid, modulesimpls_ainscrire, modulesimpls_adesinscrire
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Effectue l'inscription et la description aux modules optionnels
|
Effectue l'inscription et la description aux modules optionnels
|
||||||
|
@ -679,7 +716,7 @@ def do_moduleimpl_incription_options(
|
||||||
# inscriptions
|
# inscriptions
|
||||||
for moduleimpl_id in a_inscrire:
|
for moduleimpl_id in a_inscrire:
|
||||||
# verifie que ce module existe bien
|
# verifie que ce module existe bien
|
||||||
mods = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
||||||
if len(mods) != 1:
|
if len(mods) != 1:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"inscription: invalid moduleimpl_id: %s" % moduleimpl_id
|
"inscription: invalid moduleimpl_id: %s" % moduleimpl_id
|
||||||
|
@ -692,7 +729,7 @@ def do_moduleimpl_incription_options(
|
||||||
# desinscriptions
|
# desinscriptions
|
||||||
for moduleimpl_id in a_desinscrire:
|
for moduleimpl_id in a_desinscrire:
|
||||||
# verifie que ce module existe bien
|
# verifie que ce module existe bien
|
||||||
mods = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
||||||
if len(mods) != 1:
|
if len(mods) != 1:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"desinscription: invalid moduleimpl_id: %s" % moduleimpl_id
|
"desinscription: invalid moduleimpl_id: %s" % moduleimpl_id
|
||||||
|
@ -711,17 +748,16 @@ def do_moduleimpl_incription_options(
|
||||||
oid, formsemestre_id=mod["formsemestre_id"]
|
oid, formsemestre_id=mod["formsemestre_id"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if REQUEST:
|
H = [
|
||||||
H = [
|
html_sco_header.sco_header(),
|
||||||
html_sco_header.sco_header(),
|
"""<h3>Modifications effectuées</h3>
|
||||||
"""<h3>Modifications effectuées</h3>
|
<p><a class="stdlink" href="%s">
|
||||||
<p><a class="stdlink" href="%s">
|
Retour à la fiche étudiant</a></p>
|
||||||
Retour à la fiche étudiant</a></p>
|
"""
|
||||||
"""
|
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||||
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
html_sco_header.sco_footer(),
|
||||||
html_sco_header.sco_footer(),
|
]
|
||||||
]
|
return "\n".join(H)
|
||||||
return "\n".join(H)
|
|
||||||
|
|
||||||
|
|
||||||
def est_inscrit_ailleurs(etudid, formsemestre_id):
|
def est_inscrit_ailleurs(etudid, formsemestre_id):
|
||||||
|
@ -756,14 +792,13 @@ def list_inscrits_ailleurs(formsemestre_id):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_inscrits_ailleurs(formsemestre_id, REQUEST=None):
|
def formsemestre_inscrits_ailleurs(formsemestre_id):
|
||||||
"""Page listant les étudiants inscrits dans un autre semestre
|
"""Page listant les étudiants inscrits dans un autre semestre
|
||||||
dont les dates recouvrent le semestre indiqué.
|
dont les dates recouvrent le semestre indiqué.
|
||||||
"""
|
"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
REQUEST,
|
|
||||||
"Inscriptions multiples parmi les étudiants du semestre ",
|
"Inscriptions multiples parmi les étudiants du semestre ",
|
||||||
sem,
|
sem,
|
||||||
)
|
)
|
||||||
|
|
|
@ -105,7 +105,7 @@ def _build_menu_stats(formsemestre_id):
|
||||||
"title": "Documents Avis Poursuite Etudes",
|
"title": "Documents Avis Poursuite Etudes",
|
||||||
"endpoint": "notes.pe_view_sem_recap",
|
"endpoint": "notes.pe_view_sem_recap",
|
||||||
"args": {"formsemestre_id": formsemestre_id},
|
"args": {"formsemestre_id": formsemestre_id},
|
||||||
"enabled": True,
|
"enabled": current_app.config["TESTING"] or current_app.config["DEBUG"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": 'Table "débouchés"',
|
"title": 'Table "débouchés"',
|
||||||
|
@ -141,7 +141,7 @@ def formsemestre_status_menubar(sem):
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Voir la formation %(acronyme)s (v%(version)s)" % F,
|
"title": "Voir la formation %(acronyme)s (v%(version)s)" % F,
|
||||||
"endpoint": "notes.ue_list",
|
"endpoint": "notes.ue_table",
|
||||||
"args": {"formation_id": sem["formation_id"]},
|
"args": {"formation_id": sem["formation_id"]},
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"helpmsg": "Tableau de bord du semestre",
|
"helpmsg": "Tableau de bord du semestre",
|
||||||
|
@ -337,7 +337,7 @@ def formsemestre_status_menubar(sem):
|
||||||
submenu.append(
|
submenu.append(
|
||||||
{
|
{
|
||||||
"title": "%s" % partition["partition_name"],
|
"title": "%s" % partition["partition_name"],
|
||||||
"endpoint": "scolar.affectGroups",
|
"endpoint": "scolar.affect_groups",
|
||||||
"args": {"partition_id": partition["partition_id"]},
|
"args": {"partition_id": partition["partition_id"]},
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
}
|
}
|
||||||
|
@ -436,7 +436,7 @@ def formsemestre_status_menubar(sem):
|
||||||
return "\n".join(H)
|
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
|
"""Cherche si on a de quoi déduire le semestre affiché à partir des
|
||||||
arguments de la requête:
|
arguments de la requête:
|
||||||
formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id
|
formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id
|
||||||
|
@ -447,12 +447,13 @@ def retreive_formsemestre_from_request():
|
||||||
args = request.form
|
args = request.form
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
formsemestre_id = None
|
||||||
# Search formsemestre
|
# Search formsemestre
|
||||||
group_ids = args.get("group_ids", [])
|
group_ids = args.get("group_ids", [])
|
||||||
if "formsemestre_id" in args:
|
if "formsemestre_id" in args:
|
||||||
formsemestre_id = args["formsemestre_id"]
|
formsemestre_id = args["formsemestre_id"]
|
||||||
elif "moduleimpl_id" in args and args["moduleimpl_id"]:
|
elif "moduleimpl_id" in args and args["moduleimpl_id"]:
|
||||||
modimpl = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=args["moduleimpl_id"])
|
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=args["moduleimpl_id"])
|
||||||
if not modimpl:
|
if not modimpl:
|
||||||
return None # suppressed ?
|
return None # suppressed ?
|
||||||
modimpl = modimpl[0]
|
modimpl = modimpl[0]
|
||||||
|
@ -462,7 +463,7 @@ def retreive_formsemestre_from_request():
|
||||||
if not E:
|
if not E:
|
||||||
return None # evaluation suppressed ?
|
return None # evaluation suppressed ?
|
||||||
E = E[0]
|
E = E[0]
|
||||||
modimpl = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
formsemestre_id = modimpl["formsemestre_id"]
|
formsemestre_id = modimpl["formsemestre_id"]
|
||||||
elif "group_id" in args:
|
elif "group_id" in args:
|
||||||
group = sco_groups.get_group(args["group_id"])
|
group = sco_groups.get_group(args["group_id"])
|
||||||
|
@ -479,16 +480,17 @@ def retreive_formsemestre_from_request():
|
||||||
elif "partition_id" in args:
|
elif "partition_id" in args:
|
||||||
partition = sco_groups.get_partition(args["partition_id"])
|
partition = sco_groups.get_partition(args["partition_id"])
|
||||||
formsemestre_id = partition["formsemestre_id"]
|
formsemestre_id = partition["formsemestre_id"]
|
||||||
else:
|
|
||||||
|
if not formsemestre_id:
|
||||||
return None # no current formsemestre
|
return None # no current formsemestre
|
||||||
|
|
||||||
return formsemestre_id
|
return int(formsemestre_id)
|
||||||
|
|
||||||
|
|
||||||
# Element HTML decrivant un semestre (barre de menu et infos)
|
# Element HTML decrivant un semestre (barre de menu et infos)
|
||||||
def formsemestre_page_title():
|
def formsemestre_page_title():
|
||||||
"""Element HTML decrivant un semestre (barre de menu et infos)
|
"""Element HTML decrivant un semestre (barre de menu et infos)
|
||||||
Cherche dans REQUEST si un semestre est défini (formsemestre_id ou moduleimpl ou evaluation ou group)
|
Cherche dans la requete si un semestre est défini (formsemestre_id ou moduleimpl ou evaluation ou group)
|
||||||
"""
|
"""
|
||||||
formsemestre_id = retreive_formsemestre_from_request()
|
formsemestre_id = retreive_formsemestre_from_request()
|
||||||
#
|
#
|
||||||
|
@ -503,15 +505,29 @@ def formsemestre_page_title():
|
||||||
|
|
||||||
fill_formsemestre(sem)
|
fill_formsemestre(sem)
|
||||||
|
|
||||||
H = [
|
h = f"""<div class="formsemestre_page_title">
|
||||||
"""<div class="formsemestre_page_title">""",
|
<div class="infos">
|
||||||
"""<div class="infos">
|
<span class="semtitle"><a class="stdlink" title="{sem['session_id']}"
|
||||||
<span class="semtitle"><a class="stdlink" title="%(session_id)s" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre)s</a><a title="%(etape_apo_str)s">%(num_sem)s</a>%(modalitestr)s</span><span class="dates"><a title="du %(date_debut)s au %(date_fin)s ">%(mois_debut)s - %(mois_fin)s</a></span><span class="resp"><a title="%(nomcomplet)s">%(resp)s</a></span><span class="nbinscrits"><a class="discretelink" href="%(notes_url)s/formsemestre_lists?formsemestre_id=%(formsemestre_id)s">%(nbinscrits)d inscrits</a></span><span class="lock">%(locklink)s</span><span class="eye">%(eyelink)s</span></div>"""
|
href="{url_for('notes.formsemestre_status',
|
||||||
% sem,
|
scodoc_dept=g.scodoc_dept, formsemestre_id=sem['formsemestre_id'])}"
|
||||||
formsemestre_status_menubar(sem),
|
>{sem['titre']}</a><a
|
||||||
"""</div>""",
|
title="{sem['etape_apo_str']}">{sem['num_sem']}</a>{sem['modalitestr']}</span><span
|
||||||
]
|
class="dates"><a
|
||||||
return "\n".join(H)
|
title="du {sem['date_debut']} au {sem['date_fin']} "
|
||||||
|
>{sem['mois_debut']} - {sem['mois_fin']}</a></span><span
|
||||||
|
class="resp"><a title="{sem['nomcomplet']}">{sem['resp']}</a></span><span
|
||||||
|
class="nbinscrits"><a class="discretelink"
|
||||||
|
href="{url_for("scolar.groups_view",
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=sem['formsemestre_id'])}"
|
||||||
|
>{sem['nbinscrits']} inscrits</a></span><span
|
||||||
|
class="lock">{sem['locklink']}</span><span
|
||||||
|
class="eye">{sem['eyelink']}</span>
|
||||||
|
</div>
|
||||||
|
{formsemestre_status_menubar(sem)}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
return h
|
||||||
|
|
||||||
|
|
||||||
def fill_formsemestre(sem):
|
def fill_formsemestre(sem):
|
||||||
|
@ -568,7 +584,7 @@ def fill_formsemestre(sem):
|
||||||
|
|
||||||
|
|
||||||
# Description du semestre sous forme de table exportable
|
# Description du semestre sous forme de table exportable
|
||||||
def formsemestre_description_table(formsemestre_id, REQUEST=None, with_evals=False):
|
def formsemestre_description_table(formsemestre_id, with_evals=False):
|
||||||
"""Description du semestre sous forme de table exportable
|
"""Description du semestre sous forme de table exportable
|
||||||
Liste des modules et de leurs coefficients
|
Liste des modules et de leurs coefficients
|
||||||
"""
|
"""
|
||||||
|
@ -577,9 +593,7 @@ def formsemestre_description_table(formsemestre_id, REQUEST=None, with_evals=Fal
|
||||||
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
|
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
|
||||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||||
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
||||||
Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||||
formsemestre_id=formsemestre_id
|
|
||||||
)
|
|
||||||
|
|
||||||
R = []
|
R = []
|
||||||
sum_coef = 0
|
sum_coef = 0
|
||||||
|
@ -610,7 +624,7 @@ def formsemestre_description_table(formsemestre_id, REQUEST=None, with_evals=Fal
|
||||||
moduleimpl_id=M["moduleimpl_id"]
|
moduleimpl_id=M["moduleimpl_id"]
|
||||||
)
|
)
|
||||||
enseignants = ", ".join(
|
enseignants = ", ".join(
|
||||||
[sco_users.user_info(m["ens_id"], REQUEST)["nomprenom"] for m in M["ens"]]
|
[sco_users.user_info(m["ens_id"])["nomprenom"] for m in M["ens"]]
|
||||||
)
|
)
|
||||||
l = {
|
l = {
|
||||||
"UE": M["ue"]["acronyme"],
|
"UE": M["ue"]["acronyme"],
|
||||||
|
@ -698,41 +712,37 @@ def formsemestre_description_table(formsemestre_id, REQUEST=None, with_evals=Fal
|
||||||
html_caption=title,
|
html_caption=title,
|
||||||
html_class="table_leftalign formsemestre_description",
|
html_class="table_leftalign formsemestre_description",
|
||||||
base_url="%s?formsemestre_id=%s&with_evals=%s"
|
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,
|
page_title=title,
|
||||||
html_title=html_sco_header.html_sem_header(
|
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,
|
pdf_title=title,
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_description(
|
def formsemestre_description(formsemestre_id, format="html", with_evals=False):
|
||||||
formsemestre_id, format="html", with_evals=False, REQUEST=None
|
|
||||||
):
|
|
||||||
"""Description du semestre sous forme de table exportable
|
"""Description du semestre sous forme de table exportable
|
||||||
Liste des modules et de leurs coefficients
|
Liste des modules et de leurs coefficients
|
||||||
"""
|
"""
|
||||||
with_evals = int(with_evals)
|
with_evals = int(with_evals)
|
||||||
tab = formsemestre_description_table(
|
tab = formsemestre_description_table(formsemestre_id, with_evals=with_evals)
|
||||||
formsemestre_id, REQUEST, with_evals=with_evals
|
|
||||||
)
|
|
||||||
tab.html_before_table = """<form name="f" method="get" action="%s">
|
tab.html_before_table = """<form name="f" method="get" action="%s">
|
||||||
<input type="hidden" name="formsemestre_id" value="%s"></input>
|
<input type="hidden" name="formsemestre_id" value="%s"></input>
|
||||||
<input type="checkbox" name="with_evals" value="1" onchange="document.f.submit()" """ % (
|
<input type="checkbox" name="with_evals" value="1" onchange="document.f.submit()" """ % (
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
)
|
)
|
||||||
if with_evals:
|
if with_evals:
|
||||||
tab.html_before_table += "checked"
|
tab.html_before_table += "checked"
|
||||||
tab.html_before_table += ">indiquer les évaluations</input></form>"
|
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
|
# genere liste html pour accès aux groupes de ce semestre
|
||||||
def _make_listes_sem(sem, REQUEST=None, with_absences=True):
|
def _make_listes_sem(sem, with_absences=True):
|
||||||
# construit l'URL "destination"
|
# construit l'URL "destination"
|
||||||
# (a laquelle on revient apres saisie absences)
|
# (a laquelle on revient apres saisie absences)
|
||||||
destination = url_for(
|
destination = url_for(
|
||||||
|
@ -827,7 +837,6 @@ def _make_listes_sem(sem, REQUEST=None, with_absences=True):
|
||||||
url_for("scolar.groups_view",
|
url_for("scolar.groups_view",
|
||||||
curtab="tab-photos",
|
curtab="tab-photos",
|
||||||
group_ids=group["group_id"],
|
group_ids=group["group_id"],
|
||||||
etat="I",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
)
|
)
|
||||||
}">Photos</a>
|
}">Photos</a>
|
||||||
|
@ -845,7 +854,7 @@ def _make_listes_sem(sem, REQUEST=None, with_absences=True):
|
||||||
H.append('<p class="help indent">Aucun groupe dans cette partition')
|
H.append('<p class="help indent">Aucun groupe dans cette partition')
|
||||||
if sco_groups.sco_permissions_check.can_change_groups(formsemestre_id):
|
if sco_groups.sco_permissions_check.can_change_groups(formsemestre_id):
|
||||||
H.append(
|
H.append(
|
||||||
f""" (<a href="{url_for("scolar.affectGroups",
|
f""" (<a href="{url_for("scolar.affect_groups",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
partition_id=partition["partition_id"])
|
partition_id=partition["partition_id"])
|
||||||
}" class="stdlink">créer</a>)"""
|
}" class="stdlink">créer</a>)"""
|
||||||
|
@ -873,7 +882,7 @@ def html_expr_diagnostic(diagnostics):
|
||||||
last_id, last_msg = None, None
|
last_id, last_msg = None, None
|
||||||
for diag in diagnostics:
|
for diag in diagnostics:
|
||||||
if "moduleimpl_id" in diag:
|
if "moduleimpl_id" in diag:
|
||||||
mod = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
mod = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||||
moduleimpl_id=diag["moduleimpl_id"]
|
moduleimpl_id=diag["moduleimpl_id"]
|
||||||
)[0]
|
)[0]
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -886,7 +895,7 @@ def html_expr_diagnostic(diagnostics):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if diag["ue_id"] != last_id or diag["msg"] != last_msg:
|
if diag["ue_id"] != last_id or diag["msg"] != last_msg:
|
||||||
ue = sco_edit_ue.do_ue_list({"ue_id": diag["ue_id"]})[0]
|
ue = sco_edit_ue.ue_list({"ue_id": diag["ue_id"]})[0]
|
||||||
H.append(
|
H.append(
|
||||||
'<li>UE "%s": %s</li>'
|
'<li>UE "%s": %s</li>'
|
||||||
% (ue["acronyme"] or ue["titre"] or "?", diag["msg"])
|
% (ue["acronyme"] or ue["titre"] or "?", diag["msg"])
|
||||||
|
@ -897,7 +906,7 @@ def html_expr_diagnostic(diagnostics):
|
||||||
return "".join(H)
|
return "".join(H)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_status_head(formsemestre_id=None, REQUEST=None, page_title=None):
|
def formsemestre_status_head(formsemestre_id=None, page_title=None):
|
||||||
"""En-tête HTML des pages "semestre" """
|
"""En-tête HTML des pages "semestre" """
|
||||||
semlist = sco_formsemestre.do_formsemestre_list(
|
semlist = sco_formsemestre.do_formsemestre_list(
|
||||||
args={"formsemestre_id": formsemestre_id}
|
args={"formsemestre_id": formsemestre_id}
|
||||||
|
@ -912,12 +921,12 @@ def formsemestre_status_head(formsemestre_id=None, REQUEST=None, page_title=None
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
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>
|
<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>"""
|
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept, formation_id=F['formation_id'])}"
|
||||||
% F,
|
class="discretelink" title="Formation {F['acronyme']}, v{F['version']}">{F['titre']}</a>""",
|
||||||
]
|
]
|
||||||
if sem["semestre_id"] >= 0:
|
if sem["semestre_id"] >= 0:
|
||||||
H.append(", %s %s" % (parcours.SESSION_NAME, sem["semestre_id"]))
|
H.append(", %s %s" % (parcours.SESSION_NAME, sem["semestre_id"]))
|
||||||
|
@ -948,10 +957,13 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
|
||||||
</td></tr>"""
|
</td></tr>"""
|
||||||
)
|
)
|
||||||
H.append("</table>")
|
H.append("</table>")
|
||||||
|
sem_warning = ""
|
||||||
if sem["bul_hide_xml"]:
|
if sem["bul_hide_xml"]:
|
||||||
H.append(
|
sem_warning += "Bulletins non publiés sur le portail. "
|
||||||
'<p class="fontorange"><em>Bulletins non publiés sur le portail</em></p>'
|
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):
|
if sem["semestre_id"] >= 0 and not sco_formsemestre.sem_une_annee(sem):
|
||||||
H.append(
|
H.append(
|
||||||
'<p class="fontorange"><em>Attention: ce semestre couvre plusieurs années scolaires !</em></p>'
|
'<p class="fontorange"><em>Attention: ce semestre couvre plusieurs années scolaires !</em></p>'
|
||||||
|
@ -962,20 +974,18 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
|
||||||
return "".join(H)
|
return "".join(H)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_status(formsemestre_id=None, REQUEST=None):
|
def formsemestre_status(formsemestre_id=None):
|
||||||
"""Tableau de bord semestre HTML"""
|
"""Tableau de bord semestre HTML"""
|
||||||
# porté du DTML
|
# porté du DTML
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||||
Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||||
formsemestre_id=formsemestre_id
|
|
||||||
)
|
|
||||||
# inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
# inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
# args={"formsemestre_id": formsemestre_id}
|
# args={"formsemestre_id": formsemestre_id}
|
||||||
# )
|
# )
|
||||||
prev_ue_id = None
|
prev_ue_id = None
|
||||||
|
|
||||||
can_edit = sco_formsemestre_edit.can_edit_sem(REQUEST, formsemestre_id, sem=sem)
|
can_edit = sco_formsemestre_edit.can_edit_sem(formsemestre_id, sem=sem)
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Semestre %s" % sem["titreannee"]),
|
html_sco_header.sco_header(page_title="Semestre %s" % sem["titreannee"]),
|
||||||
|
@ -1018,11 +1028,9 @@ def formsemestre_status(formsemestre_id=None, REQUEST=None):
|
||||||
ModInscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
|
ModInscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
|
||||||
moduleimpl_id=M["moduleimpl_id"]
|
moduleimpl_id=M["moduleimpl_id"]
|
||||||
)
|
)
|
||||||
mails_enseignants.add(
|
mails_enseignants.add(sco_users.user_info(M["responsable_id"])["email"])
|
||||||
sco_users.user_info(M["responsable_id"], REQUEST)["email"]
|
|
||||||
)
|
|
||||||
mails_enseignants |= set(
|
mails_enseignants |= set(
|
||||||
[sco_users.user_info(m["ens_id"], REQUEST)["email"] for m in M["ens"]]
|
[sco_users.user_info(m["ens_id"])["email"] for m in M["ens"]]
|
||||||
)
|
)
|
||||||
ue = M["ue"]
|
ue = M["ue"]
|
||||||
if prev_ue_id != ue["ue_id"]:
|
if prev_ue_id != ue["ue_id"]:
|
||||||
|
@ -1147,7 +1155,7 @@ def formsemestre_status(formsemestre_id=None, REQUEST=None):
|
||||||
# --- LISTE DES ETUDIANTS
|
# --- LISTE DES ETUDIANTS
|
||||||
H += [
|
H += [
|
||||||
'<div id="groupes">',
|
'<div id="groupes">',
|
||||||
_make_listes_sem(sem, REQUEST),
|
_make_listes_sem(sem),
|
||||||
"</div>",
|
"</div>",
|
||||||
]
|
]
|
||||||
# --- Lien mail enseignants:
|
# --- Lien mail enseignants:
|
||||||
|
|
|
@ -27,10 +27,10 @@
|
||||||
|
|
||||||
"""Semestres: validation semestre et UE dans parcours
|
"""Semestres: validation semestre et UE dans parcours
|
||||||
"""
|
"""
|
||||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error, time, datetime
|
import time
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g
|
from flask import url_for, g, request
|
||||||
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -64,7 +64,6 @@ def formsemestre_validation_etud_form(
|
||||||
desturl=None,
|
desturl=None,
|
||||||
sortcol=None,
|
sortcol=None,
|
||||||
readonly=True,
|
readonly=True,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
nt = sco_cache.NotesTableCache.get(
|
nt = sco_cache.NotesTableCache.get(
|
||||||
formsemestre_id
|
formsemestre_id
|
||||||
|
@ -149,9 +148,7 @@ def formsemestre_validation_etud_form(
|
||||||
'</td><td style="text-align: right;"><a href="%s">%s</a></td></tr></table>'
|
'</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),
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||||
sco_photos.etud_photo_html(
|
sco_photos.etud_photo_html(etud, title="fiche de %s" % etud["nom"]),
|
||||||
etud, title="fiche de %s" % etud["nom"], REQUEST=REQUEST
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -163,10 +160,11 @@ def formsemestre_validation_etud_form(
|
||||||
if etud_etat != "I":
|
if etud_etat != "I":
|
||||||
H.append(
|
H.append(
|
||||||
tf_error_message(
|
tf_error_message(
|
||||||
"""Impossible de statuer sur cet étudiant:
|
f"""Impossible de statuer sur cet étudiant:
|
||||||
il est démissionnaire ou défaillant (voir <a href="%s">sa fiche</a>)
|
il est démissionnaire ou défaillant (voir <a href="{
|
||||||
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
|
}">sa fiche</a>)
|
||||||
"""
|
"""
|
||||||
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return "\n".join(H + Footer)
|
return "\n".join(H + Footer)
|
||||||
|
@ -178,16 +176,19 @@ def formsemestre_validation_etud_form(
|
||||||
)
|
)
|
||||||
if check:
|
if check:
|
||||||
if not desturl:
|
if not desturl:
|
||||||
desturl = (
|
desturl = url_for(
|
||||||
"formsemestre_recapcomplet?modejury=1&hidemodules=1&hidebac=1&pref_override=0&formsemestre_id="
|
"notes.formsemestre_recapcomplet",
|
||||||
+ str(formsemestre_id)
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
modejury=1,
|
||||||
|
hidemodules=1,
|
||||||
|
hidebac=1,
|
||||||
|
pref_override=0,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
sortcol=sortcol
|
||||||
|
or None, # pour refaire tri sorttable du tableau de notes
|
||||||
|
_anchor="etudid%s" % etudid, # va a la bonne ligne
|
||||||
)
|
)
|
||||||
if sortcol:
|
H.append(f'<ul><li><a href="{desturl}">Continuer</a></li></ul>')
|
||||||
desturl += (
|
|
||||||
"&sortcol=" + sortcol
|
|
||||||
) # pour refaire tri sorttable du tableau de notes
|
|
||||||
desturl += "#etudid%s" % etudid # va a la bonne ligne
|
|
||||||
H.append('<ul><li><a href="%s">Continuer</a></li></ul>' % desturl)
|
|
||||||
|
|
||||||
return "\n".join(H + Footer)
|
return "\n".join(H + Footer)
|
||||||
|
|
||||||
|
@ -197,8 +198,12 @@ def formsemestre_validation_etud_form(
|
||||||
if nt.etud_has_notes_attente(etudid):
|
if nt.etud_has_notes_attente(etudid):
|
||||||
H.append(
|
H.append(
|
||||||
tf_error_message(
|
tf_error_message(
|
||||||
"""Impossible de statuer sur cet étudiant: il a des notes en attente dans des évaluations de ce semestre (voir <a href="formsemestre_status?formsemestre_id=%s">tableau de bord</a>)"""
|
f"""Impossible de statuer sur cet étudiant: il a des notes en
|
||||||
% formsemestre_id
|
attente dans des évaluations de ce semestre (voir <a href="{
|
||||||
|
url_for( "notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||||
|
}">tableau de bord</a>)
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return "\n".join(H + Footer)
|
return "\n".join(H + Footer)
|
||||||
|
@ -213,14 +218,24 @@ def formsemestre_validation_etud_form(
|
||||||
if not Se.prev_decision:
|
if not Se.prev_decision:
|
||||||
H.append(
|
H.append(
|
||||||
tf_error_message(
|
tf_error_message(
|
||||||
"""Le jury n\'a pas statué sur le semestre précédent ! (<a href="formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s">le faire maintenant</a>)"""
|
f"""Le jury n'a pas statué sur le semestre précédent ! (<a href="{
|
||||||
% (Se.prev["formsemestre_id"], etudid)
|
url_for("notes.formsemestre_validation_etud_form",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=Se.prev["formsemestre_id"],
|
||||||
|
etudid=etudid)
|
||||||
|
}">le faire maintenant</a>)
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if decision_jury:
|
if decision_jury:
|
||||||
H.append(
|
H.append(
|
||||||
'<a href="formsemestre_validation_suppress_etud?etudid=%s&formsemestre_id=%s" class="stdlink">Supprimer décision existante</a>'
|
f"""<a href="{
|
||||||
% (etudid, formsemestre_id)
|
url_for("notes.formsemestre_validation_suppress_etud",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
etudid=etudid, formsemestre_id=formsemestre_id
|
||||||
|
)
|
||||||
|
}" class="stdlink">Supprimer décision existante</a>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
H.append(html_sco_header.sco_footer())
|
H.append(html_sco_header.sco_footer())
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
@ -338,7 +353,6 @@ def formsemestre_validation_etud(
|
||||||
codechoice=None, # required
|
codechoice=None, # required
|
||||||
desturl="",
|
desturl="",
|
||||||
sortcol=None,
|
sortcol=None,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Enregistre validation"""
|
"""Enregistre validation"""
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
|
@ -354,9 +368,9 @@ def formsemestre_validation_etud(
|
||||||
if not selected_choice:
|
if not selected_choice:
|
||||||
raise ValueError("code choix invalide ! (%s)" % codechoice)
|
raise ValueError("code choix invalide ! (%s)" % codechoice)
|
||||||
#
|
#
|
||||||
Se.valide_decision(selected_choice, REQUEST) # enregistre
|
Se.valide_decision(selected_choice) # enregistre
|
||||||
return _redirect_valid_choice(
|
return _redirect_valid_choice(
|
||||||
formsemestre_id, etudid, Se, selected_choice, desturl, sortcol, REQUEST
|
formsemestre_id, etudid, Se, selected_choice, desturl, sortcol
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -369,7 +383,6 @@ def formsemestre_validation_etud_manu(
|
||||||
assidu=False,
|
assidu=False,
|
||||||
desturl="",
|
desturl="",
|
||||||
sortcol=None,
|
sortcol=None,
|
||||||
REQUEST=None,
|
|
||||||
redirect=True,
|
redirect=True,
|
||||||
):
|
):
|
||||||
"""Enregistre validation"""
|
"""Enregistre validation"""
|
||||||
|
@ -399,22 +412,20 @@ def formsemestre_validation_etud_manu(
|
||||||
formsemestre_id_utilise_pour_compenser=formsemestre_id_utilise_pour_compenser,
|
formsemestre_id_utilise_pour_compenser=formsemestre_id_utilise_pour_compenser,
|
||||||
)
|
)
|
||||||
#
|
#
|
||||||
Se.valide_decision(choice, REQUEST) # enregistre
|
Se.valide_decision(choice) # enregistre
|
||||||
if redirect:
|
if redirect:
|
||||||
return _redirect_valid_choice(
|
return _redirect_valid_choice(
|
||||||
formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST
|
formsemestre_id, etudid, Se, choice, desturl, sortcol
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _redirect_valid_choice(
|
def _redirect_valid_choice(formsemestre_id, etudid, Se, choice, desturl, sortcol):
|
||||||
formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST
|
|
||||||
):
|
|
||||||
adr = "formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1" % (
|
adr = "formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1" % (
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etudid,
|
etudid,
|
||||||
)
|
)
|
||||||
if sortcol:
|
if sortcol:
|
||||||
adr += "&sortcol=" + sortcol
|
adr += "&sortcol=" + str(sortcol)
|
||||||
# if desturl:
|
# if desturl:
|
||||||
# desturl += "&desturl=" + desturl
|
# desturl += "&desturl=" + desturl
|
||||||
return flask.redirect(adr)
|
return flask.redirect(adr)
|
||||||
|
@ -483,7 +494,7 @@ def formsemestre_recap_parcours_table(
|
||||||
with_links=False,
|
with_links=False,
|
||||||
with_all_columns=True,
|
with_all_columns=True,
|
||||||
a_url="",
|
a_url="",
|
||||||
sem_info={},
|
sem_info=None,
|
||||||
show_details=False,
|
show_details=False,
|
||||||
):
|
):
|
||||||
"""Tableau HTML recap parcours
|
"""Tableau HTML recap parcours
|
||||||
|
@ -491,6 +502,7 @@ def formsemestre_recap_parcours_table(
|
||||||
sem_info = { formsemestre_id : txt } permet d'ajouter des informations associées à chaque semestre
|
sem_info = { formsemestre_id : txt } permet d'ajouter des informations associées à chaque semestre
|
||||||
with_all_columns: si faux, pas de colonne "assiduité".
|
with_all_columns: si faux, pas de colonne "assiduité".
|
||||||
"""
|
"""
|
||||||
|
sem_info = sem_info or {}
|
||||||
H = []
|
H = []
|
||||||
linktmpl = '<span onclick="toggle_vis(this);" class="toggle_sem sem_%%s">%s</span>'
|
linktmpl = '<span onclick="toggle_vis(this);" class="toggle_sem sem_%%s">%s</span>'
|
||||||
minuslink = linktmpl % scu.icontag("minus_img", border="0", alt="-")
|
minuslink = linktmpl % scu.icontag("minus_img", border="0", alt="-")
|
||||||
|
@ -821,12 +833,12 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
|
||||||
|
|
||||||
|
|
||||||
# -----------
|
# -----------
|
||||||
def formsemestre_validation_auto(formsemestre_id, REQUEST):
|
def formsemestre_validation_auto(formsemestre_id):
|
||||||
"Formulaire saisie automatisee des decisions d'un semestre"
|
"Formulaire saisie automatisee des decisions d'un semestre"
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
REQUEST, "Saisie automatique des décisions du semestre", sem
|
"Saisie automatique des décisions du semestre", sem
|
||||||
),
|
),
|
||||||
"""
|
"""
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -851,7 +863,7 @@ def formsemestre_validation_auto(formsemestre_id, REQUEST):
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def do_formsemestre_validation_auto(formsemestre_id, REQUEST):
|
def do_formsemestre_validation_auto(formsemestre_id):
|
||||||
"Saisie automatisee des decisions d'un semestre"
|
"Saisie automatisee des decisions d'un semestre"
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
next_semestre_id = sem["semestre_id"] + 1
|
next_semestre_id = sem["semestre_id"] + 1
|
||||||
|
@ -907,7 +919,6 @@ def do_formsemestre_validation_auto(formsemestre_id, REQUEST):
|
||||||
code_etat=ADM,
|
code_etat=ADM,
|
||||||
devenir="NEXT",
|
devenir="NEXT",
|
||||||
assidu=True,
|
assidu=True,
|
||||||
REQUEST=REQUEST,
|
|
||||||
redirect=False,
|
redirect=False,
|
||||||
)
|
)
|
||||||
nb_valid += 1
|
nb_valid += 1
|
||||||
|
@ -972,7 +983,7 @@ def formsemestre_validation_suppress_etud(formsemestre_id, etudid):
|
||||||
) # > suppr. decision jury (peut affecter de plusieurs semestres utilisant UE capitalisée)
|
) # > suppr. decision jury (peut affecter de plusieurs semestres utilisant UE capitalisée)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_validate_previous_ue(formsemestre_id, etudid, REQUEST=None):
|
def formsemestre_validate_previous_ue(formsemestre_id, etudid):
|
||||||
"""Form. saisie UE validée hors ScoDoc
|
"""Form. saisie UE validée hors ScoDoc
|
||||||
(pour étudiants arrivant avec un UE antérieurement validée).
|
(pour étudiants arrivant avec un UE antérieurement validée).
|
||||||
"""
|
"""
|
||||||
|
@ -994,9 +1005,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>'
|
'</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),
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||||
sco_photos.etud_photo_html(
|
sco_photos.etud_photo_html(etud, title="fiche de %s" % etud["nom"]),
|
||||||
etud, title="fiche de %s" % etud["nom"], REQUEST=REQUEST
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"""<p class="help">Utiliser cette page pour enregistrer une UE validée antérieurement,
|
"""<p class="help">Utiliser cette page pour enregistrer une UE validée antérieurement,
|
||||||
|
@ -1013,12 +1022,12 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid, REQUEST=None):
|
||||||
]
|
]
|
||||||
|
|
||||||
# Toutes les UE de cette formation sont présentées (même celles des autres semestres)
|
# Toutes les UE de cette formation sont présentées (même celles des autres semestres)
|
||||||
ues = sco_edit_ue.do_ue_list({"formation_id": Fo["formation_id"]})
|
ues = sco_edit_ue.ue_list({"formation_id": Fo["formation_id"]})
|
||||||
ue_names = ["Choisir..."] + ["%(acronyme)s %(titre)s" % ue for ue in ues]
|
ue_names = ["Choisir..."] + ["%(acronyme)s %(titre)s" % ue for ue in ues]
|
||||||
ue_ids = [""] + [ue["ue_id"] for ue in ues]
|
ue_ids = [""] + [ue["ue_id"] for ue in ues]
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(
|
(
|
||||||
("etudid", {"input_type": "hidden"}),
|
("etudid", {"input_type": "hidden"}),
|
||||||
("formsemestre_id", {"input_type": "hidden"}),
|
("formsemestre_id", {"input_type": "hidden"}),
|
||||||
|
@ -1091,7 +1100,6 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid, REQUEST=None):
|
||||||
tf[2]["moy_ue"],
|
tf[2]["moy_ue"],
|
||||||
tf[2]["date"],
|
tf[2]["date"],
|
||||||
semestre_id=semestre_id,
|
semestre_id=semestre_id,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
scu.ScoURL()
|
scu.ScoURL()
|
||||||
|
@ -1109,7 +1117,6 @@ def do_formsemestre_validate_previous_ue(
|
||||||
code=ADM,
|
code=ADM,
|
||||||
semestre_id=None,
|
semestre_id=None,
|
||||||
ue_coefficient=None,
|
ue_coefficient=None,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Enregistre (ou modifie) validation d'UE (obtenue hors ScoDoc).
|
"""Enregistre (ou modifie) validation d'UE (obtenue hors ScoDoc).
|
||||||
Si le coefficient est spécifié, modifie le coefficient de
|
Si le coefficient est spécifié, modifie le coefficient de
|
||||||
|
@ -1165,7 +1172,7 @@ def _invalidate_etud_formation_caches(etudid, formation_id):
|
||||||
) # > modif decision UE (inval tous semestres avec cet etudiant, ok mais conservatif)
|
) # > modif decision UE (inval tous semestres avec cet etudiant, ok mais conservatif)
|
||||||
|
|
||||||
|
|
||||||
def get_etud_ue_cap_html(etudid, formsemestre_id, ue_id, REQUEST=None):
|
def get_etud_ue_cap_html(etudid, formsemestre_id, ue_id):
|
||||||
"""Ramene bout de HTML pour pouvoir supprimer une validation de cette UE"""
|
"""Ramene bout de HTML pour pouvoir supprimer une validation de cette UE"""
|
||||||
valids = ndb.SimpleDictFetch(
|
valids = ndb.SimpleDictFetch(
|
||||||
"""SELECT SFV.*
|
"""SELECT SFV.*
|
||||||
|
@ -1201,7 +1208,7 @@ def get_etud_ue_cap_html(etudid, formsemestre_id, ue_id, REQUEST=None):
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id, REQUEST=None):
|
def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id):
|
||||||
"""Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
|
"""Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
|
||||||
log("etud_ue_suppress_validation( %s, %s, %s)" % (etudid, formsemestre_id, ue_id))
|
log("etud_ue_suppress_validation( %s, %s, %s)" % (etudid, formsemestre_id, ue_id))
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
|
@ -1227,7 +1234,7 @@ def check_formation_ues(formation_id):
|
||||||
définition du programme: cette fonction retourne un bout de HTML
|
définition du programme: cette fonction retourne un bout de HTML
|
||||||
à afficher pour prévenir l'utilisateur, ou '' si tout est ok.
|
à afficher pour prévenir l'utilisateur, ou '' si tout est ok.
|
||||||
"""
|
"""
|
||||||
ues = sco_edit_ue.do_ue_list({"formation_id": formation_id})
|
ues = sco_edit_ue.ue_list({"formation_id": formation_id})
|
||||||
ue_multiples = {} # { ue_id : [ liste des formsemestre ] }
|
ue_multiples = {} # { ue_id : [ liste des formsemestre ] }
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
# formsemestres utilisant cette ue ?
|
# formsemestres utilisant cette ue ?
|
||||||
|
|
|
@ -42,12 +42,12 @@ from xml.etree import ElementTree
|
||||||
from xml.etree.ElementTree import Element
|
from xml.etree.ElementTree import Element
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import g
|
from flask import g, request
|
||||||
from flask import url_for
|
from flask import url_for, make_response
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
from app import log, cache
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
|
@ -59,20 +59,6 @@ from app.scodoc.sco_exceptions import ScoException, AccessDenied, ScoValueError
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
def checkGroupName(
|
|
||||||
groupName,
|
|
||||||
): # XXX unused: now allow any string as a group or partition name
|
|
||||||
"Raises exception if not a valid group name"
|
|
||||||
if groupName and (
|
|
||||||
not re.match(r"^\w+$", groupName)
|
|
||||||
or (scu.simplesqlquote(groupName) != groupName)
|
|
||||||
):
|
|
||||||
log("!!! invalid group name: " + groupName)
|
|
||||||
raise ValueError("invalid group name: " + groupName)
|
|
||||||
|
|
||||||
|
|
||||||
partitionEditor = ndb.EditableTable(
|
partitionEditor = ndb.EditableTable(
|
||||||
"partition",
|
"partition",
|
||||||
|
@ -103,8 +89,8 @@ def get_group(group_id):
|
||||||
"""Returns group object, with partition"""
|
"""Returns group object, with partition"""
|
||||||
r = ndb.SimpleDictFetch(
|
r = ndb.SimpleDictFetch(
|
||||||
"""SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.*
|
"""SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.*
|
||||||
FROM group_descr gd, partition p
|
FROM group_descr gd, partition p
|
||||||
WHERE gd.id=%(group_id)s
|
WHERE gd.id=%(group_id)s
|
||||||
AND p.id = gd.partition_id
|
AND p.id = gd.partition_id
|
||||||
""",
|
""",
|
||||||
{"group_id": group_id},
|
{"group_id": group_id},
|
||||||
|
@ -126,8 +112,8 @@ def group_delete(group, force=False):
|
||||||
|
|
||||||
def get_partition(partition_id):
|
def get_partition(partition_id):
|
||||||
r = ndb.SimpleDictFetch(
|
r = ndb.SimpleDictFetch(
|
||||||
"""SELECT p.id AS partition_id, p.*
|
"""SELECT p.id AS partition_id, p.*
|
||||||
FROM partition p
|
FROM partition p
|
||||||
WHERE p.id = %(partition_id)s
|
WHERE p.id = %(partition_id)s
|
||||||
""",
|
""",
|
||||||
{"partition_id": partition_id},
|
{"partition_id": partition_id},
|
||||||
|
@ -140,7 +126,7 @@ def get_partition(partition_id):
|
||||||
def get_partitions_list(formsemestre_id, with_default=True):
|
def get_partitions_list(formsemestre_id, with_default=True):
|
||||||
"""Liste des partitions pour ce semestre (list of dicts)"""
|
"""Liste des partitions pour ce semestre (list of dicts)"""
|
||||||
partitions = ndb.SimpleDictFetch(
|
partitions = ndb.SimpleDictFetch(
|
||||||
"""SELECT p.id AS partition_id, p.*
|
"""SELECT p.id AS partition_id, p.*
|
||||||
FROM partition p
|
FROM partition p
|
||||||
WHERE formsemestre_id=%(formsemestre_id)s
|
WHERE formsemestre_id=%(formsemestre_id)s
|
||||||
ORDER BY numero""",
|
ORDER BY numero""",
|
||||||
|
@ -157,7 +143,7 @@ def get_default_partition(formsemestre_id):
|
||||||
"""Get partition for 'all' students (this one always exists, with NULL name)"""
|
"""Get partition for 'all' students (this one always exists, with NULL name)"""
|
||||||
r = ndb.SimpleDictFetch(
|
r = ndb.SimpleDictFetch(
|
||||||
"""SELECT p.id AS partition_id, p.* FROM partition p
|
"""SELECT p.id AS partition_id, p.* FROM partition p
|
||||||
WHERE formsemestre_id=%(formsemestre_id)s
|
WHERE formsemestre_id=%(formsemestre_id)s
|
||||||
AND partition_name is NULL
|
AND partition_name is NULL
|
||||||
""",
|
""",
|
||||||
{"formsemestre_id": formsemestre_id},
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
@ -184,10 +170,10 @@ def get_partition_groups(partition):
|
||||||
"""List of groups in this partition (list of dicts).
|
"""List of groups in this partition (list of dicts).
|
||||||
Some groups may be empty."""
|
Some groups may be empty."""
|
||||||
return ndb.SimpleDictFetch(
|
return ndb.SimpleDictFetch(
|
||||||
"""SELECT gd.id AS group_id, p.id AS partition_id, gd.*, p.*
|
"""SELECT gd.id AS group_id, p.id AS partition_id, gd.*, p.*
|
||||||
FROM group_descr gd, partition p
|
FROM group_descr gd, partition p
|
||||||
WHERE gd.partition_id=%(partition_id)s
|
WHERE gd.partition_id=%(partition_id)s
|
||||||
AND gd.partition_id=p.id
|
AND gd.partition_id=p.id
|
||||||
ORDER BY group_name
|
ORDER BY group_name
|
||||||
""",
|
""",
|
||||||
partition,
|
partition,
|
||||||
|
@ -198,9 +184,9 @@ def get_default_group(formsemestre_id, fix_if_missing=False):
|
||||||
"""Returns group_id for default ('tous') group"""
|
"""Returns group_id for default ('tous') group"""
|
||||||
r = ndb.SimpleDictFetch(
|
r = ndb.SimpleDictFetch(
|
||||||
"""SELECT gd.id AS group_id
|
"""SELECT gd.id AS group_id
|
||||||
FROM group_descr gd, partition p
|
FROM group_descr gd, partition p
|
||||||
WHERE p.formsemestre_id=%(formsemestre_id)s
|
WHERE p.formsemestre_id=%(formsemestre_id)s
|
||||||
AND p.partition_name is NULL
|
AND p.partition_name is NULL
|
||||||
AND p.id = gd.partition_id
|
AND p.id = gd.partition_id
|
||||||
""",
|
""",
|
||||||
{"formsemestre_id": formsemestre_id},
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
@ -219,7 +205,7 @@ def get_default_group(formsemestre_id, fix_if_missing=False):
|
||||||
partition_id = partition_create(
|
partition_id = partition_create(
|
||||||
formsemestre_id, default=True, redirect=False
|
formsemestre_id, default=True, redirect=False
|
||||||
)
|
)
|
||||||
group_id = createGroup(partition_id, default=True)
|
group_id = create_group(partition_id, default=True)
|
||||||
return group_id
|
return group_id
|
||||||
# debug check
|
# debug check
|
||||||
if len(r) != 1:
|
if len(r) != 1:
|
||||||
|
@ -232,8 +218,8 @@ def get_sem_groups(formsemestre_id):
|
||||||
"""Returns groups for this sem (in all partitions)."""
|
"""Returns groups for this sem (in all partitions)."""
|
||||||
return ndb.SimpleDictFetch(
|
return ndb.SimpleDictFetch(
|
||||||
"""SELECT gd.id AS group_id, p.id AS partition_id, gd.*, p.*
|
"""SELECT gd.id AS group_id, p.id AS partition_id, gd.*, p.*
|
||||||
FROM group_descr gd, partition p
|
FROM group_descr gd, partition p
|
||||||
WHERE p.formsemestre_id=%(formsemestre_id)s
|
WHERE p.formsemestre_id=%(formsemestre_id)s
|
||||||
AND p.id = gd.partition_id
|
AND p.id = gd.partition_id
|
||||||
""",
|
""",
|
||||||
{"formsemestre_id": formsemestre_id},
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
@ -354,7 +340,7 @@ def get_etud_groups(etudid, sem, exclude_default=False):
|
||||||
"""Infos sur groupes de l'etudiant dans ce semestre
|
"""Infos sur groupes de l'etudiant dans ce semestre
|
||||||
[ group + partition_name ]
|
[ group + partition_name ]
|
||||||
"""
|
"""
|
||||||
req = """SELECT p.id AS partition_id, p.*, g.id AS group_id, g.*
|
req = """SELECT p.id AS partition_id, p.*, g.id AS group_id, g.*
|
||||||
FROM group_descr g, partition p, group_membership gm
|
FROM group_descr g, partition p, group_membership gm
|
||||||
WHERE gm.etudid=%(etudid)s
|
WHERE gm.etudid=%(etudid)s
|
||||||
and gm.group_id = g.id
|
and gm.group_id = g.id
|
||||||
|
@ -391,11 +377,18 @@ def formsemestre_get_etud_groupnames(formsemestre_id, attr="group_name"):
|
||||||
{ etudid : { partition_id : group_name }} (attr=group_name or group_id)
|
{ etudid : { partition_id : group_name }} (attr=group_name or group_id)
|
||||||
"""
|
"""
|
||||||
infos = ndb.SimpleDictFetch(
|
infos = ndb.SimpleDictFetch(
|
||||||
"""SELECT i.id AS etudid, p.id AS partition_id,
|
"""SELECT
|
||||||
gd.group_name, gd.id AS group_id
|
i.etudid AS etudid,
|
||||||
FROM notes_formsemestre_inscription i, partition p,
|
p.id AS partition_id,
|
||||||
group_descr gd, group_membership gm
|
gd.group_name,
|
||||||
WHERE i.formsemestre_id=%(formsemestre_id)s
|
gd.id AS group_id
|
||||||
|
FROM
|
||||||
|
notes_formsemestre_inscription i,
|
||||||
|
partition p,
|
||||||
|
group_descr gd,
|
||||||
|
group_membership gm
|
||||||
|
WHERE
|
||||||
|
i.formsemestre_id=%(formsemestre_id)s
|
||||||
and i.formsemestre_id = p.formsemestre_id
|
and i.formsemestre_id = p.formsemestre_id
|
||||||
and p.id = gd.partition_id
|
and p.id = gd.partition_id
|
||||||
and gm.etudid = i.etudid
|
and gm.etudid = i.etudid
|
||||||
|
@ -427,7 +420,7 @@ def etud_add_group_infos(etud, sem, sep=" "):
|
||||||
FROM group_descr g, partition p, group_membership gm WHERE gm.etudid=%(etudid)s
|
FROM group_descr g, partition p, group_membership gm WHERE gm.etudid=%(etudid)s
|
||||||
and gm.group_id = g.id
|
and gm.group_id = g.id
|
||||||
and g.partition_id = p.id
|
and g.partition_id = p.id
|
||||||
and p.formsemestre_id = %(formsemestre_id)s
|
and p.formsemestre_id = %(formsemestre_id)s
|
||||||
ORDER BY p.numero
|
ORDER BY p.numero
|
||||||
""",
|
""",
|
||||||
{"etudid": etud["etudid"], "formsemestre_id": sem["formsemestre_id"]},
|
{"etudid": etud["etudid"], "formsemestre_id": sem["formsemestre_id"]},
|
||||||
|
@ -452,6 +445,7 @@ def etud_add_group_infos(etud, sem, sep=" "):
|
||||||
return etud
|
return etud
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize(timeout=50) # seconds
|
||||||
def get_etud_groups_in_partition(partition_id):
|
def get_etud_groups_in_partition(partition_id):
|
||||||
"""Returns { etudid : group }, with all students in this partition"""
|
"""Returns { etudid : group }, with all students in this partition"""
|
||||||
infos = ndb.SimpleDictFetch(
|
infos = ndb.SimpleDictFetch(
|
||||||
|
@ -468,7 +462,7 @@ def get_etud_groups_in_partition(partition_id):
|
||||||
return R
|
return R
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_partition_list(formsemestre_id, format="xml", REQUEST=None):
|
def formsemestre_partition_list(formsemestre_id, format="xml"):
|
||||||
"""Get partitions and groups in this semestre
|
"""Get partitions and groups in this semestre
|
||||||
Supported formats: xml, json
|
Supported formats: xml, json
|
||||||
"""
|
"""
|
||||||
|
@ -476,11 +470,11 @@ def formsemestre_partition_list(formsemestre_id, format="xml", REQUEST=None):
|
||||||
# Ajoute les groupes
|
# Ajoute les groupes
|
||||||
for p in partitions:
|
for p in partitions:
|
||||||
p["group"] = get_partition_groups(p)
|
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
|
# Encore utilisé par groupmgr.js
|
||||||
def XMLgetGroupsInPartition(partition_id, REQUEST=None): # was XMLgetGroupesTD
|
def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
|
||||||
"""
|
"""
|
||||||
Deprecated: use group_list
|
Deprecated: use group_list
|
||||||
Liste des étudiants dans chaque groupe de cette partition.
|
Liste des étudiants dans chaque groupe de cette partition.
|
||||||
|
@ -492,6 +486,8 @@ def XMLgetGroupsInPartition(partition_id, REQUEST=None): # was XMLgetGroupesTD
|
||||||
"""
|
"""
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
|
|
||||||
|
cnx = ndb.GetDBConnexion()
|
||||||
|
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
partition = get_partition(partition_id)
|
partition = get_partition(partition_id)
|
||||||
formsemestre_id = partition["formsemestre_id"]
|
formsemestre_id = partition["formsemestre_id"]
|
||||||
|
@ -499,8 +495,8 @@ def XMLgetGroupsInPartition(partition_id, REQUEST=None): # was XMLgetGroupesTD
|
||||||
groups = get_partition_groups(partition)
|
groups = get_partition_groups(partition)
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > inscrdict
|
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > inscrdict
|
||||||
etuds_set = set(nt.inscrdict)
|
etuds_set = set(nt.inscrdict)
|
||||||
# XML response:
|
# Build XML:
|
||||||
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
|
t1 = time.time()
|
||||||
doc = Element("ajax-response")
|
doc = Element("ajax-response")
|
||||||
x_response = Element("response", type="object", id="MyUpdater")
|
x_response = Element("response", type="object", id="MyUpdater")
|
||||||
doc.append(x_response)
|
doc.append(x_response)
|
||||||
|
@ -514,7 +510,8 @@ def XMLgetGroupsInPartition(partition_id, REQUEST=None): # was XMLgetGroupesTD
|
||||||
)
|
)
|
||||||
x_response.append(x_group)
|
x_response.append(x_group)
|
||||||
for e in get_group_members(group["group_id"]):
|
for e in get_group_members(group["group_id"]):
|
||||||
etud = sco_etud.get_etud_info(etudid=e["etudid"], filled=1)[0]
|
etud = sco_etud.get_etud_info(etudid=e["etudid"], filled=True)[0]
|
||||||
|
# etud = sco_etud.get_etud_info_filled_by_etudid(e["etudid"], cnx)
|
||||||
x_group.append(
|
x_group.append(
|
||||||
Element(
|
Element(
|
||||||
"etud",
|
"etud",
|
||||||
|
@ -541,6 +538,7 @@ def XMLgetGroupsInPartition(partition_id, REQUEST=None): # was XMLgetGroupesTD
|
||||||
doc.append(x_group)
|
doc.append(x_group)
|
||||||
for etudid in etuds_set:
|
for etudid in etuds_set:
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
|
# etud = sco_etud.get_etud_info_filled_by_etudid(etudid, cnx)
|
||||||
x_group.append(
|
x_group.append(
|
||||||
Element(
|
Element(
|
||||||
"etud",
|
"etud",
|
||||||
|
@ -551,8 +549,13 @@ def XMLgetGroupsInPartition(partition_id, REQUEST=None): # was XMLgetGroupesTD
|
||||||
origin=comp_origin(etud, sem),
|
origin=comp_origin(etud, sem),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
log("XMLgetGroupsInPartition: %s seconds" % (time.time() - t0))
|
t2 = time.time()
|
||||||
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
|
log(f"XMLgetGroupsInPartition: {t2-t0} seconds ({t1-t0}+{t2-t1})")
|
||||||
|
# XML response:
|
||||||
|
data = sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
|
||||||
|
response = make_response(data)
|
||||||
|
response.headers["Content-Type"] = scu.XML_MIMETYPE
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def comp_origin(etud, cur_sem):
|
def comp_origin(etud, cur_sem):
|
||||||
|
@ -652,7 +655,6 @@ def setGroups(
|
||||||
groupsLists="", # members of each existing group
|
groupsLists="", # members of each existing group
|
||||||
groupsToCreate="", # name and members of new groups
|
groupsToCreate="", # name and members of new groups
|
||||||
groupsToDelete="", # groups to delete
|
groupsToDelete="", # groups to delete
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Affect groups (Ajax request)
|
"""Affect groups (Ajax request)
|
||||||
groupsLists: lignes de la forme "group_id;etudid;...\n"
|
groupsLists: lignes de la forme "group_id;etudid;...\n"
|
||||||
|
@ -716,7 +718,7 @@ def setGroups(
|
||||||
|
|
||||||
# Supprime les groupes indiqués comme supprimés:
|
# Supprime les groupes indiqués comme supprimés:
|
||||||
for group_id in groupsToDelete:
|
for group_id in groupsToDelete:
|
||||||
suppressGroup(group_id, partition_id=partition_id, REQUEST=REQUEST)
|
delete_group(group_id, partition_id=partition_id)
|
||||||
|
|
||||||
# Crée les nouveaux groupes
|
# Crée les nouveaux groupes
|
||||||
for line in groupsToCreate.split("\n"): # for each group_name (one per line)
|
for line in groupsToCreate.split("\n"): # for each group_name (one per line)
|
||||||
|
@ -724,22 +726,20 @@ def setGroups(
|
||||||
group_name = fs[0].strip()
|
group_name = fs[0].strip()
|
||||||
if not group_name:
|
if not group_name:
|
||||||
continue
|
continue
|
||||||
# ajax arguments are encoded in utf-8:
|
group_id = create_group(partition_id, group_name)
|
||||||
# group_name = six.text_type(group_name, "utf-8").encode(
|
|
||||||
# scu.SCO_ENCODING
|
|
||||||
# ) # #py3 #sco8
|
|
||||||
group_id = createGroup(partition_id, group_name)
|
|
||||||
# Place dans ce groupe les etudiants indiqués:
|
# Place dans ce groupe les etudiants indiqués:
|
||||||
for etudid in fs[1:-1]:
|
for etudid in fs[1:-1]:
|
||||||
change_etud_group_in_partition(etudid, group_id, partition)
|
change_etud_group_in_partition(etudid, group_id, partition)
|
||||||
|
|
||||||
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
|
data = (
|
||||||
return (
|
|
||||||
'<?xml version="1.0" encoding="utf-8"?><response>Groupes enregistrés</response>'
|
'<?xml version="1.0" encoding="utf-8"?><response>Groupes enregistrés</response>'
|
||||||
)
|
)
|
||||||
|
response = make_response(data)
|
||||||
|
response.headers["Content-Type"] = scu.XML_MIMETYPE
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def createGroup(partition_id, group_name="", default=False):
|
def create_group(partition_id, group_name="", default=False) -> int:
|
||||||
"""Create a new group in this partition"""
|
"""Create a new group in this partition"""
|
||||||
partition = get_partition(partition_id)
|
partition = get_partition(partition_id)
|
||||||
formsemestre_id = partition["formsemestre_id"]
|
formsemestre_id = partition["formsemestre_id"]
|
||||||
|
@ -759,12 +759,12 @@ def createGroup(partition_id, group_name="", default=False):
|
||||||
group_id = groupEditor.create(
|
group_id = groupEditor.create(
|
||||||
cnx, {"partition_id": partition_id, "group_name": group_name}
|
cnx, {"partition_id": partition_id, "group_name": group_name}
|
||||||
)
|
)
|
||||||
log("createGroup: created group_id=%s" % group_id)
|
log("create_group: created group_id=%s" % group_id)
|
||||||
#
|
#
|
||||||
return group_id
|
return group_id
|
||||||
|
|
||||||
|
|
||||||
def suppressGroup(group_id, partition_id=None, REQUEST=None):
|
def delete_group(group_id, partition_id=None):
|
||||||
"""form suppression d'un groupe.
|
"""form suppression d'un groupe.
|
||||||
(ne desinscrit pas les etudiants, change juste leur
|
(ne desinscrit pas les etudiants, change juste leur
|
||||||
affectation aux groupes)
|
affectation aux groupes)
|
||||||
|
@ -781,7 +781,7 @@ def suppressGroup(group_id, partition_id=None, REQUEST=None):
|
||||||
if not sco_permissions_check.can_change_groups(partition["formsemestre_id"]):
|
if not sco_permissions_check.can_change_groups(partition["formsemestre_id"]):
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
log(
|
log(
|
||||||
"suppressGroup: group_id=%s group_name=%s partition_name=%s"
|
"delete_group: group_id=%s group_name=%s partition_name=%s"
|
||||||
% (group_id, group["group_name"], partition["partition_name"])
|
% (group_id, group["group_name"], partition["partition_name"])
|
||||||
)
|
)
|
||||||
group_delete(group)
|
group_delete(group)
|
||||||
|
@ -798,7 +798,7 @@ def partition_create(
|
||||||
if not sco_permissions_check.can_change_groups(formsemestre_id):
|
if not sco_permissions_check.can_change_groups(formsemestre_id):
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
if partition_name:
|
if partition_name:
|
||||||
partition_name = partition_name.strip()
|
partition_name = str(partition_name).strip()
|
||||||
if default:
|
if default:
|
||||||
partition_name = None
|
partition_name = None
|
||||||
if not partition_name and not default:
|
if not partition_name and not default:
|
||||||
|
@ -813,8 +813,21 @@ def partition_create(
|
||||||
)
|
)
|
||||||
|
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
|
if numero is None:
|
||||||
|
numero = (
|
||||||
|
ndb.SimpleQuery(
|
||||||
|
"SELECT MAX(id) FROM partition WHERE formsemestre_id=%(formsemestre_id)s",
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
).fetchone()[0]
|
||||||
|
or 0
|
||||||
|
)
|
||||||
partition_id = partitionEditor.create(
|
partition_id = partitionEditor.create(
|
||||||
cnx, {"formsemestre_id": formsemestre_id, "partition_name": partition_name}
|
cnx,
|
||||||
|
{
|
||||||
|
"formsemestre_id": formsemestre_id,
|
||||||
|
"partition_name": partition_name,
|
||||||
|
"numero": numero,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
log("createPartition: created partition_id=%s" % partition_id)
|
log("createPartition: created partition_id=%s" % partition_id)
|
||||||
#
|
#
|
||||||
|
@ -830,7 +843,7 @@ def partition_create(
|
||||||
return partition_id
|
return partition_id
|
||||||
|
|
||||||
|
|
||||||
def getArrowIconsTags():
|
def get_arrow_icons_tags():
|
||||||
"""returns html tags for arrows"""
|
"""returns html tags for arrows"""
|
||||||
#
|
#
|
||||||
arrow_up = scu.icontag("arrow_up", title="remonter")
|
arrow_up = scu.icontag("arrow_up", title="remonter")
|
||||||
|
@ -840,13 +853,13 @@ def getArrowIconsTags():
|
||||||
return arrow_up, arrow_down, arrow_none
|
return arrow_up, arrow_down, arrow_none
|
||||||
|
|
||||||
|
|
||||||
def editPartitionForm(formsemestre_id=None, REQUEST=None):
|
def editPartitionForm(formsemestre_id=None):
|
||||||
"""Form to create/suppress partitions"""
|
"""Form to create/suppress partitions"""
|
||||||
# ad-hoc form
|
# ad-hoc form
|
||||||
if not sco_permissions_check.can_change_groups(formsemestre_id):
|
if not sco_permissions_check.can_change_groups(formsemestre_id):
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
partitions = get_partitions_list(formsemestre_id)
|
partitions = get_partitions_list(formsemestre_id)
|
||||||
arrow_up, arrow_down, arrow_none = getArrowIconsTags()
|
arrow_up, arrow_down, arrow_none = get_arrow_icons_tags()
|
||||||
suppricon = scu.icontag(
|
suppricon = scu.icontag(
|
||||||
"delete_small_img", border="0", alt="supprimer", title="Supprimer"
|
"delete_small_img", border="0", alt="supprimer", title="Supprimer"
|
||||||
)
|
)
|
||||||
|
@ -907,7 +920,7 @@ def editPartitionForm(formsemestre_id=None, REQUEST=None):
|
||||||
H.append(", ".join(lg))
|
H.append(", ".join(lg))
|
||||||
H.append(
|
H.append(
|
||||||
f"""</td><td><a class="stdlink" href="{
|
f"""</td><td><a class="stdlink" href="{
|
||||||
url_for("scolar.affectGroups",
|
url_for("scolar.affect_groups",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
partition_id=p["partition_id"])
|
partition_id=p["partition_id"])
|
||||||
}">répartir</a></td>
|
}">répartir</a></td>
|
||||||
|
@ -968,7 +981,7 @@ def editPartitionForm(formsemestre_id=None, REQUEST=None):
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
def partition_set_attr(partition_id, attr, value, REQUEST=None):
|
def partition_set_attr(partition_id, attr, value):
|
||||||
"""Set partition attribute: bul_show_rank or show_in_lists"""
|
"""Set partition attribute: bul_show_rank or show_in_lists"""
|
||||||
if attr not in {"bul_show_rank", "show_in_lists"}:
|
if attr not in {"bul_show_rank", "show_in_lists"}:
|
||||||
raise ValueError("invalid partition attribute: %s" % attr)
|
raise ValueError("invalid partition attribute: %s" % attr)
|
||||||
|
@ -991,9 +1004,7 @@ def partition_set_attr(partition_id, attr, value, REQUEST=None):
|
||||||
return "enregistré"
|
return "enregistré"
|
||||||
|
|
||||||
|
|
||||||
def partition_delete(
|
def partition_delete(partition_id, force=False, redirect=1, dialog_confirmed=False):
|
||||||
partition_id, REQUEST=None, force=False, redirect=1, dialog_confirmed=False
|
|
||||||
):
|
|
||||||
"""Suppress a partition (and all groups within).
|
"""Suppress a partition (and all groups within).
|
||||||
default partition cannot be suppressed (unless force)"""
|
default partition cannot be suppressed (unless force)"""
|
||||||
partition = get_partition(partition_id)
|
partition = get_partition(partition_id)
|
||||||
|
@ -1036,7 +1047,7 @@ def partition_delete(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def partition_move(partition_id, after=0, REQUEST=None, redirect=1):
|
def partition_move(partition_id, after=0, redirect=1):
|
||||||
"""Move before/after previous one (decrement/increment numero)"""
|
"""Move before/after previous one (decrement/increment numero)"""
|
||||||
partition = get_partition(partition_id)
|
partition = get_partition(partition_id)
|
||||||
formsemestre_id = partition["formsemestre_id"]
|
formsemestre_id = partition["formsemestre_id"]
|
||||||
|
@ -1050,7 +1061,7 @@ def partition_move(partition_id, after=0, REQUEST=None, redirect=1):
|
||||||
others = get_partitions_list(formsemestre_id)
|
others = get_partitions_list(formsemestre_id)
|
||||||
if len(others) > 1:
|
if len(others) > 1:
|
||||||
pidx = [p["partition_id"] for p in others].index(partition_id)
|
pidx = [p["partition_id"] for p in others].index(partition_id)
|
||||||
log("partition_move: after=%s pidx=%s" % (after, pidx))
|
# log("partition_move: after=%s pidx=%s" % (after, pidx))
|
||||||
neigh = None # partition to swap with
|
neigh = None # partition to swap with
|
||||||
if after == 0 and pidx > 0:
|
if after == 0 and pidx > 0:
|
||||||
neigh = others[pidx - 1]
|
neigh = others[pidx - 1]
|
||||||
|
@ -1058,8 +1069,20 @@ def partition_move(partition_id, after=0, REQUEST=None, redirect=1):
|
||||||
neigh = others[pidx + 1]
|
neigh = others[pidx + 1]
|
||||||
if neigh: #
|
if neigh: #
|
||||||
# swap numero between partition and its neighbor
|
# swap numero between partition and its neighbor
|
||||||
log("moving partition %s" % partition_id)
|
# log("moving partition %s" % partition_id)
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
|
# Si aucun numéro n'a été affecté, le met au minimum
|
||||||
|
min_numero = (
|
||||||
|
ndb.SimpleQuery(
|
||||||
|
"SELECT MIN(numero) FROM partition WHERE formsemestre_id=%(formsemestre_id)s",
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
).fetchone()[0]
|
||||||
|
or 0
|
||||||
|
)
|
||||||
|
if neigh["numero"] is None:
|
||||||
|
neigh["numero"] = min_numero - 1
|
||||||
|
if partition["numero"] is None:
|
||||||
|
partition["numero"] = min_numero - 1 - after
|
||||||
partition["numero"], neigh["numero"] = neigh["numero"], partition["numero"]
|
partition["numero"], neigh["numero"] = neigh["numero"], partition["numero"]
|
||||||
partitionEditor.edit(cnx, partition)
|
partitionEditor.edit(cnx, partition)
|
||||||
partitionEditor.edit(cnx, neigh)
|
partitionEditor.edit(cnx, neigh)
|
||||||
|
@ -1071,7 +1094,7 @@ def partition_move(partition_id, after=0, REQUEST=None, redirect=1):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def partition_rename(partition_id, REQUEST=None):
|
def partition_rename(partition_id):
|
||||||
"""Form to rename a partition"""
|
"""Form to rename a partition"""
|
||||||
partition = get_partition(partition_id)
|
partition = get_partition(partition_id)
|
||||||
formsemestre_id = partition["formsemestre_id"]
|
formsemestre_id = partition["formsemestre_id"]
|
||||||
|
@ -1079,8 +1102,8 @@ def partition_rename(partition_id, REQUEST=None):
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
H = ["<h2>Renommer une partition</h2>"]
|
H = ["<h2>Renommer une partition</h2>"]
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(
|
(
|
||||||
("partition_id", {"default": partition_id, "input_type": "hidden"}),
|
("partition_id", {"default": partition_id, "input_type": "hidden"}),
|
||||||
(
|
(
|
||||||
|
@ -1110,14 +1133,12 @@ def partition_rename(partition_id, REQUEST=None):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# form submission
|
# form submission
|
||||||
return partition_set_name(
|
return partition_set_name(partition_id, tf[2]["partition_name"])
|
||||||
partition_id, tf[2]["partition_name"], REQUEST=REQUEST, redirect=1
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def partition_set_name(partition_id, partition_name, REQUEST=None, redirect=1):
|
def partition_set_name(partition_id, partition_name, redirect=1):
|
||||||
"""Set partition name"""
|
"""Set partition name"""
|
||||||
partition_name = partition_name.strip()
|
partition_name = str(partition_name).strip()
|
||||||
if not partition_name:
|
if not partition_name:
|
||||||
raise ValueError("partition name must be non empty")
|
raise ValueError("partition name must be non empty")
|
||||||
partition = get_partition(partition_id)
|
partition = get_partition(partition_id)
|
||||||
|
@ -1127,13 +1148,13 @@ def partition_set_name(partition_id, partition_name, REQUEST=None, redirect=1):
|
||||||
|
|
||||||
# check unicity
|
# check unicity
|
||||||
r = ndb.SimpleDictFetch(
|
r = ndb.SimpleDictFetch(
|
||||||
"""SELECT p.* FROM partition p
|
"""SELECT p.* FROM partition p
|
||||||
WHERE p.partition_name = %(partition_name)s
|
WHERE p.partition_name = %(partition_name)s
|
||||||
AND formsemestre_id = %(formsemestre_id)s
|
AND formsemestre_id = %(formsemestre_id)s
|
||||||
""",
|
""",
|
||||||
{"partition_name": partition_name, "formsemestre_id": formsemestre_id},
|
{"partition_name": partition_name, "formsemestre_id": formsemestre_id},
|
||||||
)
|
)
|
||||||
if len(r) > 1 or (len(r) == 1 and r[0]["partition_id"] != partition_id):
|
if len(r) > 1 or (len(r) == 1 and r[0]["id"] != partition_id):
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"Partition %s déjà existante dans ce semestre !" % partition_name
|
"Partition %s déjà existante dans ce semestre !" % partition_name
|
||||||
)
|
)
|
||||||
|
@ -1153,7 +1174,7 @@ def partition_set_name(partition_id, partition_name, REQUEST=None, redirect=1):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def group_set_name(group_id, group_name, REQUEST=None, redirect=1):
|
def group_set_name(group_id, group_name, redirect=True):
|
||||||
"""Set group name"""
|
"""Set group name"""
|
||||||
if group_name:
|
if group_name:
|
||||||
group_name = group_name.strip()
|
group_name = group_name.strip()
|
||||||
|
@ -1173,14 +1194,14 @@ def group_set_name(group_id, group_name, REQUEST=None, redirect=1):
|
||||||
if redirect:
|
if redirect:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.affectGroups",
|
"scolar.affect_groups",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
partition_id=group["partition_id"],
|
partition_id=group["partition_id"],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def group_rename(group_id, REQUEST=None):
|
def group_rename(group_id):
|
||||||
"""Form to rename a group"""
|
"""Form to rename a group"""
|
||||||
group = get_group(group_id)
|
group = get_group(group_id)
|
||||||
formsemestre_id = group["formsemestre_id"]
|
formsemestre_id = group["formsemestre_id"]
|
||||||
|
@ -1188,8 +1209,8 @@ def group_rename(group_id, REQUEST=None):
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
H = ["<h2>Renommer un groupe de %s</h2>" % group["partition_name"]]
|
H = ["<h2>Renommer un groupe de %s</h2>" % group["partition_name"]]
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
(
|
(
|
||||||
("group_id", {"default": group_id, "input_type": "hidden"}),
|
("group_id", {"default": group_id, "input_type": "hidden"}),
|
||||||
(
|
(
|
||||||
|
@ -1216,19 +1237,17 @@ def group_rename(group_id, REQUEST=None):
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.affectGroups",
|
"scolar.affect_groups",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
partition_id=group["partition_id"],
|
partition_id=group["partition_id"],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# form submission
|
# form submission
|
||||||
return group_set_name(
|
return group_set_name(group_id, tf[2]["group_name"])
|
||||||
group_id, tf[2]["group_name"], REQUEST=REQUEST, redirect=1
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def groups_auto_repartition(partition_id=None, REQUEST=None):
|
def groups_auto_repartition(partition_id=None):
|
||||||
"""Reparti les etudiants dans des groupes dans une partition, en respectant le niveau
|
"""Reparti les etudiants dans des groupes dans une partition, en respectant le niveau
|
||||||
et la mixité.
|
et la mixité.
|
||||||
"""
|
"""
|
||||||
|
@ -1238,7 +1257,7 @@ def groups_auto_repartition(partition_id=None, REQUEST=None):
|
||||||
formsemestre_id = partition["formsemestre_id"]
|
formsemestre_id = partition["formsemestre_id"]
|
||||||
# renvoie sur page édition groupes
|
# renvoie sur page édition groupes
|
||||||
dest_url = url_for(
|
dest_url = url_for(
|
||||||
"scolar.affectGroups", scodoc_dept=g.scodoc_dept, partition_id=partition_id
|
"scolar.affect_groups", scodoc_dept=g.scodoc_dept, partition_id=partition_id
|
||||||
)
|
)
|
||||||
if not sco_permissions_check.can_change_groups(formsemestre_id):
|
if not sco_permissions_check.can_change_groups(formsemestre_id):
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
|
@ -1268,8 +1287,8 @@ def groups_auto_repartition(partition_id=None, REQUEST=None):
|
||||||
]
|
]
|
||||||
|
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
descr,
|
descr,
|
||||||
{},
|
{},
|
||||||
cancelbutton="Annuler",
|
cancelbutton="Annuler",
|
||||||
|
@ -1299,8 +1318,8 @@ def groups_auto_repartition(partition_id=None, REQUEST=None):
|
||||||
# checkGroupName(group_name)
|
# checkGroupName(group_name)
|
||||||
# except:
|
# except:
|
||||||
# H.append('<p class="warning">Nom de groupe invalide: %s</p>'%group_name)
|
# H.append('<p class="warning">Nom de groupe invalide: %s</p>'%group_name)
|
||||||
# return '\n'.join(H) + tf[1] + html_sco_header.sco_footer( REQUEST)
|
# return '\n'.join(H) + tf[1] + html_sco_header.sco_footer()
|
||||||
group_ids.append(createGroup(partition_id, group_name))
|
group_ids.append(create_group(partition_id, group_name))
|
||||||
#
|
#
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > identdict
|
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > identdict
|
||||||
identdict = nt.identdict
|
identdict = nt.identdict
|
||||||
|
@ -1360,6 +1379,7 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):
|
||||||
"""
|
"""
|
||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
|
|
||||||
|
partition_name = str(partition_name)
|
||||||
log("create_etapes_partition(%s)" % formsemestre_id)
|
log("create_etapes_partition(%s)" % formsemestre_id)
|
||||||
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
args={"formsemestre_id": formsemestre_id}
|
args={"formsemestre_id": formsemestre_id}
|
||||||
|
@ -1384,7 +1404,7 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):
|
||||||
groups_by_names = {g["group_name"]: g for g in groups}
|
groups_by_names = {g["group_name"]: g for g in groups}
|
||||||
for etape in etapes:
|
for etape in etapes:
|
||||||
if not (etape in groups_by_names):
|
if not (etape in groups_by_names):
|
||||||
gid = createGroup(pid, etape)
|
gid = create_group(pid, etape)
|
||||||
g = get_group(gid)
|
g = get_group(gid)
|
||||||
groups_by_names[etape] = g
|
groups_by_names[etape] = g
|
||||||
# Place les etudiants dans les groupes
|
# Place les etudiants dans les groupes
|
||||||
|
@ -1405,6 +1425,7 @@ def do_evaluation_listeetuds_groups(
|
||||||
Si include_dems, compte aussi les etudiants démissionnaires
|
Si include_dems, compte aussi les etudiants démissionnaires
|
||||||
(sinon, par défaut, seulement les 'I')
|
(sinon, par défaut, seulement les 'I')
|
||||||
"""
|
"""
|
||||||
|
# nb: pour notes_table / do_evaluation_etat, getallstudents est vrai et include_dems faux
|
||||||
fromtables = [
|
fromtables = [
|
||||||
"notes_moduleimpl_inscription Im",
|
"notes_moduleimpl_inscription Im",
|
||||||
"notes_formsemestre_inscription Isem",
|
"notes_formsemestre_inscription Isem",
|
||||||
|
@ -1416,7 +1437,7 @@ def do_evaluation_listeetuds_groups(
|
||||||
if not groups:
|
if not groups:
|
||||||
return [] # no groups, so no students
|
return [] # no groups, so no students
|
||||||
rg = ["gm.group_id = '%(group_id)s'" % g for g in groups]
|
rg = ["gm.group_id = '%(group_id)s'" % g for g in groups]
|
||||||
rq = """and Isem.etudid = gm.etudid
|
rq = """and Isem.etudid = gm.etudid
|
||||||
and gd.partition_id = p.id
|
and gd.partition_id = p.id
|
||||||
and p.formsemestre_id = Isem.formsemestre_id
|
and p.formsemestre_id = Isem.formsemestre_id
|
||||||
"""
|
"""
|
||||||
|
@ -1429,7 +1450,7 @@ def do_evaluation_listeetuds_groups(
|
||||||
req = (
|
req = (
|
||||||
"SELECT distinct Im.etudid FROM "
|
"SELECT distinct Im.etudid FROM "
|
||||||
+ ", ".join(fromtables)
|
+ ", ".join(fromtables)
|
||||||
+ """ WHERE Isem.etudid = Im.etudid
|
+ """ WHERE Isem.etudid = Im.etudid
|
||||||
and Im.moduleimpl_id = M.id
|
and Im.moduleimpl_id = M.id
|
||||||
and Isem.formsemestre_id = M.formsemestre_id
|
and Isem.formsemestre_id = M.formsemestre_id
|
||||||
and E.moduleimpl_id = M.id
|
and E.moduleimpl_id = M.id
|
||||||
|
@ -1440,10 +1461,9 @@ def do_evaluation_listeetuds_groups(
|
||||||
req += " and Isem.etat='I'"
|
req += " and Isem.etat='I'"
|
||||||
req += r
|
req += r
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor()
|
||||||
cursor.execute(req, {"evaluation_id": evaluation_id})
|
cursor.execute(req, {"evaluation_id": evaluation_id})
|
||||||
res = cursor.fetchall()
|
return [x[0] for x in cursor]
|
||||||
return [x[0] for x in res]
|
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_listegroupes(evaluation_id, include_default=False):
|
def do_evaluation_listegroupes(evaluation_id, include_default=False):
|
||||||
|
@ -1457,7 +1477,7 @@ def do_evaluation_listegroupes(evaluation_id, include_default=False):
|
||||||
else:
|
else:
|
||||||
c = " AND p.partition_name is not NULL"
|
c = " AND p.partition_name is not NULL"
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor()
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""SELECT DISTINCT gd.id AS group_id
|
"""SELECT DISTINCT gd.id AS group_id
|
||||||
FROM group_descr gd, group_membership gm, partition p,
|
FROM group_descr gd, group_membership gm, partition p,
|
||||||
|
@ -1471,8 +1491,7 @@ def do_evaluation_listegroupes(evaluation_id, include_default=False):
|
||||||
+ c,
|
+ c,
|
||||||
{"evaluation_id": evaluation_id},
|
{"evaluation_id": evaluation_id},
|
||||||
)
|
)
|
||||||
res = cursor.fetchall()
|
group_ids = [x[0] for x in cursor]
|
||||||
group_ids = [x[0] for x in res]
|
|
||||||
return listgroups(group_ids)
|
return listgroups(group_ids)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1482,9 +1501,9 @@ def listgroups(group_ids):
|
||||||
groups = []
|
groups = []
|
||||||
for group_id in group_ids:
|
for group_id in group_ids:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.*
|
"""SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.*
|
||||||
FROM group_descr gd, partition p
|
FROM group_descr gd, partition p
|
||||||
WHERE p.id = gd.partition_id
|
WHERE p.id = gd.partition_id
|
||||||
AND gd.id = %(group_id)s
|
AND gd.id = %(group_id)s
|
||||||
""",
|
""",
|
||||||
{"group_id": group_id},
|
{"group_id": group_id},
|
||||||
|
@ -1499,7 +1518,7 @@ def _sortgroups(groups):
|
||||||
# Tri: place 'all' en tête, puis groupe par partition / nom de groupe
|
# 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]
|
R = [g for g in groups if g["partition_name"] is None]
|
||||||
o = [g for g in groups if g["partition_name"] != 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
|
return R + o
|
||||||
|
|
||||||
|
|
66
app/scodoc/sco_groups_copy.py
Normal file
66
app/scodoc/sco_groups_copy.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
from app import db
|
||||||
|
|
||||||
|
from app.scodoc import sco_groups
|
||||||
|
import app.scodoc.notesdb as ndb
|
||||||
|
|
||||||
|
|
||||||
|
def clone_partitions_and_groups(
|
||||||
|
orig_formsemestre_id: int, formsemestre_id: int, inscrit_etuds=False
|
||||||
|
):
|
||||||
|
"""Crée dans le semestre formsemestre_id les mêmes partitions et groupes que ceux
|
||||||
|
de orig_formsemestre_id.
|
||||||
|
Si inscrit_etuds, inscrit les mêmes étudiants (rarement souhaité).
|
||||||
|
"""
|
||||||
|
list_groups_per_part = []
|
||||||
|
list_groups = []
|
||||||
|
groups_old2new = {} # old group_id : new_group_id
|
||||||
|
# Création des partitions:
|
||||||
|
for part in sco_groups.get_partitions_list(orig_formsemestre_id):
|
||||||
|
if part["partition_name"] is not None:
|
||||||
|
partname = part["partition_name"]
|
||||||
|
new_partition_id = sco_groups.partition_create(
|
||||||
|
formsemestre_id,
|
||||||
|
partition_name=partname,
|
||||||
|
numero=part["numero"],
|
||||||
|
redirect=False,
|
||||||
|
)
|
||||||
|
for group in sco_groups.get_partition_groups(part):
|
||||||
|
if group["group_name"] != None:
|
||||||
|
list_groups.append(group)
|
||||||
|
list_groups_per_part.append([new_partition_id, list_groups])
|
||||||
|
list_groups = []
|
||||||
|
|
||||||
|
# Création des groupes dans les nouvelles partitions:
|
||||||
|
for newpart in sco_groups.get_partitions_list(formsemestre_id):
|
||||||
|
for (new_partition_id, list_groups) in list_groups_per_part:
|
||||||
|
if newpart["partition_id"] == new_partition_id:
|
||||||
|
for group in list_groups:
|
||||||
|
new_group_id = sco_groups.create_group(
|
||||||
|
new_partition_id, group_name=group["group_name"]
|
||||||
|
)
|
||||||
|
groups_old2new[group["group_id"]] = new_group_id
|
||||||
|
#
|
||||||
|
if inscrit_etuds:
|
||||||
|
cnx = ndb.GetDBConnexion()
|
||||||
|
cursor = cnx.cursor()
|
||||||
|
for old_group_id, new_group_id in groups_old2new.items():
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
WITH etuds AS (
|
||||||
|
SELECT gm.etudid
|
||||||
|
FROM group_membership gm, notes_formsemestre_inscription ins
|
||||||
|
WHERE ins.etudid = gm.etudid
|
||||||
|
AND ins.formsemestre_id = %(orig_formsemestre_id)s
|
||||||
|
AND gm.group_id=%(old_group_id)s
|
||||||
|
)
|
||||||
|
INSERT INTO group_membership (etudid, group_id)
|
||||||
|
SELECT *, %(new_group_id)s FROM etuds
|
||||||
|
ON CONFLICT DO NOTHING
|
||||||
|
""",
|
||||||
|
{
|
||||||
|
"orig_formsemestre_id": orig_formsemestre_id,
|
||||||
|
"old_group_id": old_group_id,
|
||||||
|
"new_group_id": new_group_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
cnx.commit()
|
|
@ -27,70 +27,33 @@
|
||||||
|
|
||||||
"""Formulaires gestion des groupes
|
"""Formulaires gestion des groupes
|
||||||
"""
|
"""
|
||||||
|
from flask import render_template
|
||||||
|
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc.sco_exceptions import AccessDenied
|
from app.scodoc.sco_exceptions import AccessDenied
|
||||||
|
|
||||||
|
|
||||||
def affectGroups(partition_id, REQUEST=None):
|
def affect_groups(partition_id):
|
||||||
"""Formulaire affectation des etudiants aux groupes de la partition.
|
"""Formulaire affectation des etudiants aux groupes de la partition.
|
||||||
Permet aussi la creation et la suppression de groupes.
|
Permet aussi la creation et la suppression de groupes.
|
||||||
"""
|
"""
|
||||||
# Ported from DTML and adapted to new group management (nov 2009)
|
# réécrit pour 9.0.47 avec un template
|
||||||
partition = sco_groups.get_partition(partition_id)
|
partition = sco_groups.get_partition(partition_id)
|
||||||
formsemestre_id = partition["formsemestre_id"]
|
formsemestre_id = partition["formsemestre_id"]
|
||||||
if not sco_groups.sco_permissions_check.can_change_groups(formsemestre_id):
|
if not sco_groups.sco_permissions_check.can_change_groups(formsemestre_id):
|
||||||
raise AccessDenied("vous n'avez pas la permission d'effectuer cette opération")
|
raise AccessDenied("vous n'avez pas la permission de modifier les groupes")
|
||||||
|
return render_template(
|
||||||
H = [
|
"scolar/affect_groups.html",
|
||||||
html_sco_header.sco_header(
|
sco_header=html_sco_header.sco_header(
|
||||||
page_title="Affectation aux groupes",
|
page_title="Affectation aux groupes",
|
||||||
javascripts=["js/groupmgr.js"],
|
javascripts=["js/groupmgr.js"],
|
||||||
cssstyles=["css/groups.css"],
|
cssstyles=["css/groups.css"],
|
||||||
),
|
),
|
||||||
"""<h2 class="formsemestre">Affectation aux groupes de %s</h2><form id="sp">"""
|
sco_footer=html_sco_header.sco_footer(),
|
||||||
% partition["partition_name"],
|
partition=partition,
|
||||||
]
|
partitions_list=sco_groups.get_partitions_list(
|
||||||
|
formsemestre_id, with_default=False
|
||||||
H += [
|
),
|
||||||
"""</select></form>""",
|
formsemestre_id=formsemestre_id,
|
||||||
"""<p>Faites glisser les étudiants d'un groupe à l'autre. Les modifications ne sont enregistrées que lorsque vous cliquez sur le bouton "<em>Enregistrer ces groupes</em>". Vous pouvez créer de nouveaux groupes. Pour <em>supprimer</em> un groupe, utiliser le lien "suppr." en haut à droite de sa boite. Vous pouvez aussi <a class="stdlink" href="groups_auto_repartition?partition_id=%(partition_id)s">répartir automatiquement les groupes</a>.
|
)
|
||||||
</p>"""
|
|
||||||
% partition,
|
|
||||||
"""<div id="gmsg" class="head_message"></div>""",
|
|
||||||
"""<div id="ginfo"></div>""",
|
|
||||||
"""<div id="savedinfo"></div>""",
|
|
||||||
"""<form name="formGroup" id="formGroup" onSubmit="return false;">""",
|
|
||||||
"""<input type="hidden" name="partition_id" value="%s"/>""" % partition_id,
|
|
||||||
"""<input name="groupName" size="6"/>
|
|
||||||
<input type="button" onClick="createGroup();" value="Créer groupe"/>
|
|
||||||
|
|
||||||
<input type="button" onClick="submitGroups( target='gmsg' );" value="Enregistrer ces groupes" />
|
|
||||||
|
|
||||||
<input type="button" onClick="document.location = 'formsemestre_status?formsemestre_id=%s'" value="Annuler" />
|
|
||||||
Editer groupes de
|
|
||||||
<select name="other_partition_id" onchange="GotoAnother();">"""
|
|
||||||
% formsemestre_id,
|
|
||||||
]
|
|
||||||
for p in sco_groups.get_partitions_list(formsemestre_id, with_default=False):
|
|
||||||
H.append('<option value="%s"' % p["partition_id"])
|
|
||||||
if p["partition_id"] == partition_id:
|
|
||||||
H.append(" selected")
|
|
||||||
H.append(">%s</option>" % p["partition_name"])
|
|
||||||
H += [
|
|
||||||
"""</select>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div id="groups">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="clear: left; margin-top: 15px;">
|
|
||||||
<p class="help"></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
""",
|
|
||||||
html_sco_header.sco_footer(),
|
|
||||||
]
|
|
||||||
return "\n".join(H)
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -25,16 +25,16 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
""" Importation des etudiants à partir de fichiers CSV
|
""" Importation des étudiants à partir de fichiers CSV
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
import flask
|
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -219,21 +219,20 @@ def sco_import_generate_excel_sample(
|
||||||
|
|
||||||
def students_import_excel(
|
def students_import_excel(
|
||||||
csvfile,
|
csvfile,
|
||||||
REQUEST=None,
|
|
||||||
formsemestre_id=None,
|
formsemestre_id=None,
|
||||||
check_homonyms=True,
|
check_homonyms=True,
|
||||||
require_ine=False,
|
require_ine=False,
|
||||||
|
return_html=True,
|
||||||
):
|
):
|
||||||
"import students from Excel file"
|
"import students from Excel file"
|
||||||
diag = scolars_import_excel_file(
|
diag = scolars_import_excel_file(
|
||||||
csvfile,
|
csvfile,
|
||||||
REQUEST,
|
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
check_homonyms=check_homonyms,
|
check_homonyms=check_homonyms,
|
||||||
require_ine=require_ine,
|
require_ine=require_ine,
|
||||||
exclude_cols=["photo_filename"],
|
exclude_cols=["photo_filename"],
|
||||||
)
|
)
|
||||||
if REQUEST:
|
if return_html:
|
||||||
if formsemestre_id:
|
if formsemestre_id:
|
||||||
dest = url_for(
|
dest = url_for(
|
||||||
"notes.formsemestre_status",
|
"notes.formsemestre_status",
|
||||||
|
@ -253,8 +252,7 @@ def students_import_excel(
|
||||||
|
|
||||||
|
|
||||||
def scolars_import_excel_file(
|
def scolars_import_excel_file(
|
||||||
datafile,
|
datafile: io.BytesIO,
|
||||||
REQUEST,
|
|
||||||
formsemestre_id=None,
|
formsemestre_id=None,
|
||||||
check_homonyms=True,
|
check_homonyms=True,
|
||||||
require_ine=False,
|
require_ine=False,
|
||||||
|
@ -416,17 +414,14 @@ def scolars_import_excel_file(
|
||||||
if NbHomonyms:
|
if NbHomonyms:
|
||||||
NbImportedHomonyms += 1
|
NbImportedHomonyms += 1
|
||||||
# Insert in DB tables
|
# Insert in DB tables
|
||||||
formsemestre_to_invalidate.add(
|
formsemestre_id_etud = _import_one_student(
|
||||||
_import_one_student(
|
cnx,
|
||||||
cnx,
|
formsemestre_id,
|
||||||
REQUEST,
|
values,
|
||||||
formsemestre_id,
|
GroupIdInferers,
|
||||||
values,
|
annee_courante,
|
||||||
GroupIdInferers,
|
created_etudids,
|
||||||
annee_courante,
|
linenum,
|
||||||
created_etudids,
|
|
||||||
linenum,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verification proportion d'homonymes: si > 10%, abandonne
|
# Verification proportion d'homonymes: si > 10%, abandonne
|
||||||
|
@ -492,16 +487,15 @@ def scolars_import_excel_file(
|
||||||
|
|
||||||
|
|
||||||
def students_import_admission(
|
def students_import_admission(
|
||||||
csvfile, type_admission="", REQUEST=None, formsemestre_id=None
|
csvfile, type_admission="", formsemestre_id=None, return_html=True
|
||||||
):
|
):
|
||||||
"import donnees admission from Excel file (v2016)"
|
"import donnees admission from Excel file (v2016)"
|
||||||
diag = scolars_import_admission(
|
diag = scolars_import_admission(
|
||||||
csvfile,
|
csvfile,
|
||||||
REQUEST,
|
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
type_admission=type_admission,
|
type_admission=type_admission,
|
||||||
)
|
)
|
||||||
if REQUEST:
|
if return_html:
|
||||||
H = [html_sco_header.sco_header(page_title="Import données admissions")]
|
H = [html_sco_header.sco_header(page_title="Import données admissions")]
|
||||||
H.append("<p>Import terminé !</p>")
|
H.append("<p>Import terminé !</p>")
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -520,14 +514,13 @@ def students_import_admission(
|
||||||
|
|
||||||
def _import_one_student(
|
def _import_one_student(
|
||||||
cnx,
|
cnx,
|
||||||
REQUEST,
|
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
values,
|
values,
|
||||||
GroupIdInferers,
|
GroupIdInferers,
|
||||||
annee_courante,
|
annee_courante,
|
||||||
created_etudids,
|
created_etudids,
|
||||||
linenum,
|
linenum,
|
||||||
):
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Import d'un étudiant et inscription dans le semestre.
|
Import d'un étudiant et inscription dans le semestre.
|
||||||
Return: id du semestre dans lequel il a été inscrit.
|
Return: id du semestre dans lequel il a été inscrit.
|
||||||
|
@ -538,7 +531,7 @@ def _import_one_student(
|
||||||
)
|
)
|
||||||
# Identite
|
# Identite
|
||||||
args = values.copy()
|
args = values.copy()
|
||||||
etudid = sco_etud.identite_create(cnx, args, REQUEST=REQUEST)
|
etudid = sco_etud.identite_create(cnx, args)
|
||||||
created_etudids.append(etudid)
|
created_etudids.append(etudid)
|
||||||
# Admissions
|
# Admissions
|
||||||
args["etudid"] = etudid
|
args["etudid"] = etudid
|
||||||
|
@ -555,6 +548,12 @@ def _import_one_student(
|
||||||
else:
|
else:
|
||||||
args["formsemestre_id"] = values["codesemestre"]
|
args["formsemestre_id"] = values["codesemestre"]
|
||||||
formsemestre_id = values["codesemestre"]
|
formsemestre_id = values["codesemestre"]
|
||||||
|
try:
|
||||||
|
formsemestre_id = int(formsemestre_id)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError(
|
||||||
|
f"valeur invalide dans la colonne codesemestre, ligne {linenum+1}"
|
||||||
|
) from exc
|
||||||
# recupere liste des groupes:
|
# recupere liste des groupes:
|
||||||
if formsemestre_id not in GroupIdInferers:
|
if formsemestre_id not in GroupIdInferers:
|
||||||
GroupIdInferers[formsemestre_id] = sco_groups.GroupIdInferer(formsemestre_id)
|
GroupIdInferers[formsemestre_id] = sco_groups.GroupIdInferer(formsemestre_id)
|
||||||
|
@ -571,7 +570,7 @@ def _import_one_student(
|
||||||
)
|
)
|
||||||
|
|
||||||
do_formsemestre_inscription_with_modules(
|
do_formsemestre_inscription_with_modules(
|
||||||
args["formsemestre_id"],
|
int(args["formsemestre_id"]),
|
||||||
etudid,
|
etudid,
|
||||||
group_ids,
|
group_ids,
|
||||||
etat="I",
|
etat="I",
|
||||||
|
@ -587,9 +586,7 @@ def _is_new_ine(cnx, code_ine):
|
||||||
|
|
||||||
|
|
||||||
# ------ Fonction ré-écrite en nov 2016 pour lire des fichiers sans etudid (fichiers APB)
|
# ------ Fonction ré-écrite en nov 2016 pour lire des fichiers sans etudid (fichiers APB)
|
||||||
def scolars_import_admission(
|
def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None):
|
||||||
datafile, REQUEST, formsemestre_id=None, type_admission=None
|
|
||||||
):
|
|
||||||
"""Importe données admission depuis un fichier Excel quelconque
|
"""Importe données admission depuis un fichier Excel quelconque
|
||||||
par exemple ceux utilisés avec APB
|
par exemple ceux utilisés avec APB
|
||||||
|
|
||||||
|
@ -646,7 +643,7 @@ def scolars_import_admission(
|
||||||
raise FormatError(
|
raise FormatError(
|
||||||
"scolars_import_admission: colonnes nom et prenom requises",
|
"scolars_import_admission: colonnes nom et prenom requises",
|
||||||
dest_url=url_for(
|
dest_url=url_for(
|
||||||
"notes.form_students_import_infos_admissions",
|
"scolar.form_students_import_infos_admissions",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
),
|
),
|
||||||
|
@ -679,7 +676,7 @@ def scolars_import_admission(
|
||||||
'scolars_import_admission: valeur invalide, ligne %d colonne %s: "%s"'
|
'scolars_import_admission: valeur invalide, ligne %d colonne %s: "%s"'
|
||||||
% (nline, field_name, line[idx]),
|
% (nline, field_name, line[idx]),
|
||||||
dest_url=url_for(
|
dest_url=url_for(
|
||||||
"notes.form_students_import_infos_admissions",
|
"scolar.form_students_import_infos_admissions",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
),
|
),
|
||||||
|
@ -765,7 +762,7 @@ def adm_get_fields(titles, formsemestre_id):
|
||||||
'scolars_import_admission: titre "%s" en double (ligne 1)'
|
'scolars_import_admission: titre "%s" en double (ligne 1)'
|
||||||
% (title),
|
% (title),
|
||||||
dest_url=url_for(
|
dest_url=url_for(
|
||||||
"notes.form_students_import_infos_admissions_apb",
|
"scolar.form_students_import_infos_admissions_apb",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
),
|
),
|
||||||
|
|
|
@ -27,27 +27,23 @@
|
||||||
|
|
||||||
"""Import d'utilisateurs via fichier Excel
|
"""Import d'utilisateurs via fichier Excel
|
||||||
"""
|
"""
|
||||||
import random, time
|
import random
|
||||||
import re
|
import time
|
||||||
|
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from flask import g, url_for
|
||||||
from email.header import Header
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import db, Departement
|
from app import db
|
||||||
|
from app import email
|
||||||
|
from app.auth.models import User, UserRole
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoException
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
from app.scodoc import sco_excel
|
from app.scodoc import sco_excel
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
|
|
||||||
from flask import g
|
|
||||||
from flask_login import current_user
|
|
||||||
from app.auth.models import User, UserRole
|
|
||||||
|
|
||||||
from app import email
|
|
||||||
|
|
||||||
|
|
||||||
TITLES = ("user_name", "nom", "prenom", "email", "roles", "dept")
|
TITLES = ("user_name", "nom", "prenom", "email", "roles", "dept")
|
||||||
COMMENTS = (
|
COMMENTS = (
|
||||||
|
@ -86,11 +82,11 @@ def generate_excel_sample():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def import_excel_file(datafile):
|
def import_excel_file(datafile, force=""):
|
||||||
"""
|
"""
|
||||||
Import scodoc users from Excel file.
|
Import scodoc users from Excel file.
|
||||||
This method:
|
This method:
|
||||||
* checks that the current_user has the ability to do so (at the moment only a SuperAdmin). He may thereoff import users with any well formed role into any deprtment (or all)
|
* checks that the current_user has the ability to do so (at the moment only a SuperAdmin). He may thereoff import users with any well formed role into any department (or all)
|
||||||
* Once the check is done ans successfull, build the list of users (does not check the data)
|
* Once the check is done ans successfull, build the list of users (does not check the data)
|
||||||
* call :func:`import_users` to actually do the job
|
* call :func:`import_users` to actually do the job
|
||||||
history: scodoc7 with no SuperAdmin every Admin_XXX could import users.
|
history: scodoc7 with no SuperAdmin every Admin_XXX could import users.
|
||||||
|
@ -98,7 +94,6 @@ def import_excel_file(datafile):
|
||||||
:return: same as import users
|
:return: same as import users
|
||||||
"""
|
"""
|
||||||
# Check current user privilege
|
# Check current user privilege
|
||||||
auth_dept = current_user.dept
|
|
||||||
auth_name = str(current_user)
|
auth_name = str(current_user)
|
||||||
if not current_user.is_administrator():
|
if not current_user.is_administrator():
|
||||||
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
||||||
|
@ -109,8 +104,11 @@ def import_excel_file(datafile):
|
||||||
if not exceldata:
|
if not exceldata:
|
||||||
raise ScoValueError("Ficher excel vide ou invalide")
|
raise ScoValueError("Ficher excel vide ou invalide")
|
||||||
_, data = sco_excel.excel_bytes_to_list(exceldata)
|
_, data = sco_excel.excel_bytes_to_list(exceldata)
|
||||||
if not data: # probably a bug
|
if not data:
|
||||||
raise ScoException("import_excel_file: empty file !")
|
raise ScoValueError(
|
||||||
|
"""Le fichier xlsx attendu semble vide !
|
||||||
|
"""
|
||||||
|
)
|
||||||
# 1- --- check title line
|
# 1- --- check title line
|
||||||
fs = [scu.stripquotes(s).lower() for s in data[0]]
|
fs = [scu.stripquotes(s).lower() for s in data[0]]
|
||||||
log("excel: fs='%s'\ndata=%s" % (str(fs), str(data)))
|
log("excel: fs='%s'\ndata=%s" % (str(fs), str(data)))
|
||||||
|
@ -124,7 +122,8 @@ def import_excel_file(datafile):
|
||||||
del cols[tit]
|
del cols[tit]
|
||||||
if cols or unknown:
|
if cols or unknown:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"colonnes incorrectes (on attend %d, et non %d) <br/> (colonnes manquantes: %s, colonnes invalides: %s)"
|
"""colonnes incorrectes (on attend %d, et non %d) <br/>
|
||||||
|
(colonnes manquantes: %s, colonnes invalides: %s)"""
|
||||||
% (len(TITLES), len(fs), list(cols.keys()), unknown)
|
% (len(TITLES), len(fs), list(cols.keys()), unknown)
|
||||||
)
|
)
|
||||||
# ok, same titles... : build the list of dictionaries
|
# ok, same titles... : build the list of dictionaries
|
||||||
|
@ -135,10 +134,10 @@ def import_excel_file(datafile):
|
||||||
d[fs[i]] = line[i]
|
d[fs[i]] = line[i]
|
||||||
users.append(d)
|
users.append(d)
|
||||||
|
|
||||||
return import_users(users)
|
return import_users(users=users, force=force)
|
||||||
|
|
||||||
|
|
||||||
def import_users(users):
|
def import_users(users, force=""):
|
||||||
"""
|
"""
|
||||||
Import users from a list of users_descriptors.
|
Import users from a list of users_descriptors.
|
||||||
|
|
||||||
|
@ -179,60 +178,35 @@ def import_users(users):
|
||||||
line = line + 1
|
line = line + 1
|
||||||
user_ok, msg = sco_users.check_modif_user(
|
user_ok, msg = sco_users.check_modif_user(
|
||||||
0,
|
0,
|
||||||
|
enforce_optionals=not force,
|
||||||
user_name=u["user_name"],
|
user_name=u["user_name"],
|
||||||
nom=u["nom"],
|
nom=u["nom"],
|
||||||
prenom=u["prenom"],
|
prenom=u["prenom"],
|
||||||
email=u["email"],
|
email=u["email"],
|
||||||
roles=u["roles"].split(","),
|
roles=[r for r in u["roles"].split(",") if r],
|
||||||
|
dept=u["dept"],
|
||||||
)
|
)
|
||||||
if not user_ok:
|
if not user_ok:
|
||||||
append_msg("identifiant '%s' %s" % (u["user_name"], msg))
|
append_msg("identifiant '%s' %s" % (u["user_name"], msg))
|
||||||
# raise ScoValueError(
|
|
||||||
# "données invalides pour %s: %s" % (u["user_name"], msg)
|
|
||||||
# )
|
|
||||||
u["passwd"] = generate_password()
|
u["passwd"] = generate_password()
|
||||||
#
|
#
|
||||||
# check identifiant
|
# check identifiant
|
||||||
if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]*$", u["user_name"]):
|
|
||||||
user_ok = False
|
|
||||||
append_msg(
|
|
||||||
"identifiant '%s' invalide (pas d'accents ni de caractères spéciaux)"
|
|
||||||
% u["user_name"]
|
|
||||||
)
|
|
||||||
if len(u["user_name"]) > 64:
|
|
||||||
user_ok = False
|
|
||||||
append_msg(
|
|
||||||
"identifiant '%s' trop long (64 caractères)" % u["user_name"]
|
|
||||||
)
|
|
||||||
if len(u["nom"]) > 64:
|
|
||||||
user_ok = False
|
|
||||||
append_msg("nom '%s' trop long (64 caractères)" % u["nom"])
|
|
||||||
if len(u["prenom"]) > 64:
|
|
||||||
user_ok = False
|
|
||||||
append_msg("prenom '%s' trop long (64 caractères)" % u["prenom"])
|
|
||||||
if len(u["email"]) > 120:
|
|
||||||
user_ok = False
|
|
||||||
append_msg("email '%s' trop long (120 caractères)" % u["email"])
|
|
||||||
# check that tha same user_name has not already been described in this import
|
|
||||||
if u["user_name"] in created.keys():
|
if u["user_name"] in created.keys():
|
||||||
user_ok = False
|
user_ok = False
|
||||||
append_msg(
|
append_msg(
|
||||||
"l'utilisateur '%s' a déjà été décrit ligne %s"
|
"l'utilisateur '%s' a déjà été décrit ligne %s"
|
||||||
% (u["user_name"], created[u["user_name"]]["line"])
|
% (u["user_name"], created[u["user_name"]]["line"])
|
||||||
)
|
)
|
||||||
# check département
|
|
||||||
if u["dept"] != "":
|
|
||||||
dept = Departement.query.filter_by(acronym=u["dept"]).first()
|
|
||||||
if dept is None:
|
|
||||||
user_ok = False
|
|
||||||
append_msg("département '%s' inexistant" % u["dept"])
|
|
||||||
# check roles / ignore whitespaces around roles / build roles_string
|
# check roles / ignore whitespaces around roles / build roles_string
|
||||||
# roles_string (expected by User) appears as column 'roles' in excel file
|
# roles_string (expected by User) appears as column 'roles' in excel file
|
||||||
roles_list = []
|
roles_list = []
|
||||||
for role in u["roles"].split(","):
|
for role in u["roles"].split(","):
|
||||||
try:
|
try:
|
||||||
_, _ = UserRole.role_dept_from_string(role.strip())
|
role = role.strip()
|
||||||
roles_list.append(role.strip())
|
if role:
|
||||||
|
_, _ = UserRole.role_dept_from_string(role)
|
||||||
|
roles_list.append(role)
|
||||||
except ScoValueError as value_error:
|
except ScoValueError as value_error:
|
||||||
user_ok = False
|
user_ok = False
|
||||||
append_msg("role %s : %s" % (role, value_error))
|
append_msg("role %s : %s" % (role, value_error))
|
||||||
|
@ -244,7 +218,7 @@ def import_users(users):
|
||||||
import_ok = False
|
import_ok = False
|
||||||
except ScoValueError as value_error:
|
except ScoValueError as value_error:
|
||||||
log("import_users: exception: abort create %s" % str(created.keys()))
|
log("import_users: exception: abort create %s" % str(created.keys()))
|
||||||
raise ScoValueError(msg) # re-raise exception
|
raise ScoValueError(msg) from value_error
|
||||||
if import_ok:
|
if import_ok:
|
||||||
for u in created.values():
|
for u in created.values():
|
||||||
# Création de l'utilisateur (via SQLAlchemy)
|
# Création de l'utilisateur (via SQLAlchemy)
|
||||||
|
@ -264,7 +238,7 @@ def import_users(users):
|
||||||
|
|
||||||
|
|
||||||
ALPHABET = r"""ABCDEFGHIJKLMNPQRSTUVWXYZ123456789123456789AEIOU"""
|
ALPHABET = r"""ABCDEFGHIJKLMNPQRSTUVWXYZ123456789123456789AEIOU"""
|
||||||
PASSLEN = 6
|
PASSLEN = 8
|
||||||
RNG = random.Random(time.time())
|
RNG = random.Random(time.time())
|
||||||
|
|
||||||
|
|
||||||
|
@ -279,23 +253,18 @@ def generate_password():
|
||||||
return "".join(RNG.sample(l, PASSLEN))
|
return "".join(RNG.sample(l, PASSLEN))
|
||||||
|
|
||||||
|
|
||||||
def mail_password(u, context=None, reset=False):
|
def mail_password(user: dict, reset=False) -> None:
|
||||||
"Send password by email"
|
"Send password by email"
|
||||||
if not u["email"]:
|
if not user["email"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
u[
|
user["url"] = url_for("scodoc.index", _external=True)
|
||||||
"url"
|
|
||||||
] = (
|
|
||||||
scu.ScoURL()
|
|
||||||
) # TODO set auth page URL ? (shared by all departments) ../auth/login
|
|
||||||
|
|
||||||
txt = (
|
txt = (
|
||||||
"""
|
"""
|
||||||
Bonjour %(prenom)s %(nom)s,
|
Bonjour %(prenom)s %(nom)s,
|
||||||
|
|
||||||
"""
|
"""
|
||||||
% u
|
% user
|
||||||
)
|
)
|
||||||
if reset:
|
if reset:
|
||||||
txt += (
|
txt += (
|
||||||
|
@ -305,10 +274,10 @@ votre mot de passe ScoDoc a été ré-initialisé.
|
||||||
Le nouveau mot de passe est: %(passwd)s
|
Le nouveau mot de passe est: %(passwd)s
|
||||||
Votre nom d'utilisateur est %(user_name)s
|
Votre nom d'utilisateur est %(user_name)s
|
||||||
|
|
||||||
Vous devrez changer ce mot de passe lors de votre première connexion
|
Vous devrez changer ce mot de passe lors de votre première connexion
|
||||||
sur %(url)s
|
sur %(url)s
|
||||||
"""
|
"""
|
||||||
% u
|
% user
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
txt += (
|
txt += (
|
||||||
|
@ -320,16 +289,15 @@ Votre mot de passe est: %(passwd)s
|
||||||
|
|
||||||
Le logiciel est accessible sur: %(url)s
|
Le logiciel est accessible sur: %(url)s
|
||||||
|
|
||||||
Vous êtes invité à changer ce mot de passe au plus vite (cliquez sur
|
Vous êtes invité à changer ce mot de passe au plus vite (cliquez sur votre nom en haut à gauche de la page d'accueil).
|
||||||
votre nom en haut à gauche de la page d'accueil).
|
|
||||||
"""
|
"""
|
||||||
% u
|
% user
|
||||||
)
|
)
|
||||||
|
|
||||||
txt += (
|
txt += (
|
||||||
"""
|
"""
|
||||||
|
_______
|
||||||
ScoDoc est un logiciel libre développé à l'Université Paris 13 par Emmanuel Viennet.
|
ScoDoc est un logiciel libre développé par Emmanuel Viennet et l'association ScoDoc.
|
||||||
Pour plus d'informations sur ce logiciel, voir %s
|
Pour plus d'informations sur ce logiciel, voir %s
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -341,4 +309,4 @@ Pour plus d'informations sur ce logiciel, voir %s
|
||||||
else:
|
else:
|
||||||
subject = "Votre accès ScoDoc"
|
subject = "Votre accès ScoDoc"
|
||||||
sender = sco_preferences.get_preference("email_from_addr")
|
sender = sco_preferences.get_preference("email_from_addr")
|
||||||
email.send_email(subject, sender, [u["email"]], txt)
|
email.send_email(subject, sender, [user["email"]], txt)
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
import datetime
|
import datetime
|
||||||
from operator import itemgetter
|
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.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -81,6 +81,7 @@ def list_authorized_etuds_by_sem(sem, delai=274):
|
||||||
"title": src["titreannee"],
|
"title": src["titreannee"],
|
||||||
"title_target": "formsemestre_status?formsemestre_id=%s"
|
"title_target": "formsemestre_status?formsemestre_id=%s"
|
||||||
% src["formsemestre_id"],
|
% src["formsemestre_id"],
|
||||||
|
"filename": "etud_autorises",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
# ajoute attribut inscrit qui indique si l'étudiant est déjà inscrit dans le semestre dest.
|
# 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"],
|
% sem["formsemestre_id"],
|
||||||
"comment": " actuellement inscrits dans ce semestre",
|
"comment": " actuellement inscrits dans ce semestre",
|
||||||
"help": "Ces étudiants sont actuellement inscrits dans ce semestre. Si vous les décochez, il seront désinscrits.",
|
"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)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
|
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""SELECT I.etudid
|
"""SELECT ins.etudid
|
||||||
FROM
|
FROM
|
||||||
notes_formsemestre_inscription ins,
|
notes_formsemestre_inscription ins,
|
||||||
notes_formsemestre S,
|
notes_formsemestre S
|
||||||
identite i
|
|
||||||
WHERE ins.formsemestre_id = S.id
|
WHERE ins.formsemestre_id = S.id
|
||||||
AND S.id != %(formsemestre_id)s
|
AND S.id != %(formsemestre_id)s
|
||||||
AND S.date_debut <= %(date_debut_iso)s
|
AND S.date_debut <= %(date_debut_iso)s
|
||||||
AND S.date_fin >= %(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,
|
sem,
|
||||||
)
|
)
|
||||||
|
@ -264,7 +265,6 @@ def formsemestre_inscr_passage(
|
||||||
inscrit_groupes=False,
|
inscrit_groupes=False,
|
||||||
submitted=False,
|
submitted=False,
|
||||||
dialog_confirmed=False,
|
dialog_confirmed=False,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Form. pour inscription des etudiants d'un semestre dans un autre
|
"""Form. pour inscription des etudiants d'un semestre dans un autre
|
||||||
(donné par formsemestre_id).
|
(donné par formsemestre_id).
|
||||||
|
@ -287,8 +287,11 @@ def formsemestre_inscr_passage(
|
||||||
header = html_sco_header.sco_header(page_title="Passage des étudiants")
|
header = html_sco_header.sco_header(page_title="Passage des étudiants")
|
||||||
footer = html_sco_header.sco_footer()
|
footer = html_sco_header.sco_footer()
|
||||||
H = [header]
|
H = [header]
|
||||||
if type(etuds) == type(""):
|
if isinstance(etuds, str):
|
||||||
etuds = etuds.split(",") # vient du form de confirmation
|
# list de strings, vient du form de confirmation
|
||||||
|
etuds = [int(x) for x in etuds.split(",") if x]
|
||||||
|
elif isinstance(etuds, int):
|
||||||
|
etuds = [etuds]
|
||||||
|
|
||||||
auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem(sem)
|
auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem(sem)
|
||||||
etuds_set = set(etuds)
|
etuds_set = set(etuds)
|
||||||
|
@ -312,7 +315,6 @@ def formsemestre_inscr_passage(
|
||||||
|
|
||||||
if not submitted:
|
if not submitted:
|
||||||
H += build_page(
|
H += build_page(
|
||||||
REQUEST,
|
|
||||||
sem,
|
sem,
|
||||||
auth_etuds_by_sem,
|
auth_etuds_by_sem,
|
||||||
inscrits,
|
inscrits,
|
||||||
|
@ -342,18 +344,22 @@ def formsemestre_inscr_passage(
|
||||||
% inscrits[etudid]
|
% inscrits[etudid]
|
||||||
)
|
)
|
||||||
H.append("</ol>")
|
H.append("</ol>")
|
||||||
if not a_inscrire and not a_desinscrire:
|
todo = a_inscrire or a_desinscrire
|
||||||
|
if not todo:
|
||||||
H.append("""<h3>Il n'y a rien à modifier !</h3>""")
|
H.append("""<h3>Il n'y a rien à modifier !</h3>""")
|
||||||
H.append(
|
H.append(
|
||||||
scu.confirm_dialog(
|
scu.confirm_dialog(
|
||||||
dest_url="formsemestre_inscr_passage",
|
dest_url="formsemestre_inscr_passage"
|
||||||
|
if todo
|
||||||
|
else "formsemestre_status",
|
||||||
|
message="<p>Confirmer ?</p>" if todo else "",
|
||||||
add_headers=False,
|
add_headers=False,
|
||||||
cancel_url="formsemestre_inscr_passage?formsemestre_id="
|
cancel_url="formsemestre_inscr_passage?formsemestre_id="
|
||||||
+ str(formsemestre_id),
|
+ str(formsemestre_id),
|
||||||
OK="Effectuer l'opération",
|
OK="Effectuer l'opération" if todo else "",
|
||||||
parameters={
|
parameters={
|
||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
"etuds": ",".join(etuds),
|
"etuds": ",".join([str(x) for x in etuds]),
|
||||||
"inscrit_groupes": inscrit_groupes,
|
"inscrit_groupes": inscrit_groupes,
|
||||||
"submitted": 1,
|
"submitted": 1,
|
||||||
},
|
},
|
||||||
|
@ -385,7 +391,7 @@ def formsemestre_inscr_passage(
|
||||||
): # il y a au moins une vraie partition
|
): # il y a au moins une vraie partition
|
||||||
H.append(
|
H.append(
|
||||||
f"""<li><a class="stdlink" href="{
|
f"""<li><a class="stdlink" href="{
|
||||||
url_for("scolar.affectGroups",
|
url_for("scolar.affect_groups",
|
||||||
scodoc_dept=g.scodoc_dept, partition_id=partition["partition_id"])
|
scodoc_dept=g.scodoc_dept, partition_id=partition["partition_id"])
|
||||||
}">Répartir les groupes de {partition["partition_name"]}</a></li>
|
}">Répartir les groupes de {partition["partition_name"]}</a></li>
|
||||||
"""
|
"""
|
||||||
|
@ -397,7 +403,6 @@ def formsemestre_inscr_passage(
|
||||||
|
|
||||||
|
|
||||||
def build_page(
|
def build_page(
|
||||||
REQUEST,
|
|
||||||
sem,
|
sem,
|
||||||
auth_etuds_by_sem,
|
auth_etuds_by_sem,
|
||||||
inscrits,
|
inscrits,
|
||||||
|
@ -413,9 +418,9 @@ def build_page(
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
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="hidden" name="formsemestre_id" value="%(formsemestre_id)s"/>
|
||||||
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
||||||
<a href="#help">aide</a>
|
<a href="#help">aide</a>
|
||||||
|
@ -507,7 +512,12 @@ def etuds_select_boxes(
|
||||||
</script>
|
</script>
|
||||||
<div class="etuds_select_boxes">"""
|
<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():
|
for src_cat in auth_etuds_by_cat.keys():
|
||||||
infos = auth_etuds_by_cat[src_cat]["infos"]
|
infos = auth_etuds_by_cat[src_cat]["infos"]
|
||||||
infos["comment"] = infos.get("comment", "") # commentaire dans sous-titre boite
|
infos["comment"] = infos.get("comment", "") # commentaire dans sous-titre boite
|
||||||
|
@ -550,10 +560,8 @@ def etuds_select_boxes(
|
||||||
if with_checkbox or sel_inscrits:
|
if with_checkbox or sel_inscrits:
|
||||||
H.append(")")
|
H.append(")")
|
||||||
if base_url and etuds:
|
if base_url and etuds:
|
||||||
H.append(
|
url = scu.build_url_query(base_url, export_cat_xls=src_cat)
|
||||||
'<a href="%s&export_cat_xls=%s">%s</a> '
|
H.append(f'<a href="{url}">{scu.ICON_XLS}</a> ')
|
||||||
% (base_url, src_cat, scu.ICON_XLS)
|
|
||||||
)
|
|
||||||
H.append("</div>")
|
H.append("</div>")
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
if etud.get("inscrit", False):
|
if etud.get("inscrit", False):
|
||||||
|
@ -633,4 +641,4 @@ def etuds_select_box_xls(src_cat):
|
||||||
caption="%(title)s. %(help)s" % src_cat["infos"],
|
caption="%(title)s. %(help)s" % src_cat["infos"],
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
return tab.excel()
|
return tab.excel() # tab.make_page(filename=src_cat["infos"]["filename"])
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
@ -27,11 +27,11 @@
|
||||||
|
|
||||||
"""Liste des notes d'une évaluation
|
"""Liste des notes d'une évaluation
|
||||||
"""
|
"""
|
||||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
import urllib
|
||||||
|
|
||||||
import flask
|
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.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
@ -56,20 +56,21 @@ from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc.htmlutils import histogram_notes
|
from app.scodoc.htmlutils import histogram_notes
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_listenotes(REQUEST):
|
def do_evaluation_listenotes():
|
||||||
"""
|
"""
|
||||||
Affichage des notes d'une évaluation
|
Affichage des notes d'une évaluation
|
||||||
|
|
||||||
args: evaluation_id ou moduleimpl_id
|
args: evaluation_id ou moduleimpl_id
|
||||||
(si moduleimpl_id, affiche toutes les évaluatons du module)
|
(si moduleimpl_id, affiche toutes les évaluations du module)
|
||||||
"""
|
"""
|
||||||
mode = None
|
mode = None
|
||||||
if "evaluation_id" in REQUEST.form:
|
vals = scu.get_request_args()
|
||||||
evaluation_id = int(REQUEST.form["evaluation_id"])
|
if "evaluation_id" in vals:
|
||||||
|
evaluation_id = int(vals["evaluation_id"])
|
||||||
mode = "eval"
|
mode = "eval"
|
||||||
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||||
if "moduleimpl_id" in REQUEST.form:
|
if "moduleimpl_id" in vals and vals["moduleimpl_id"]:
|
||||||
moduleimpl_id = int(REQUEST.form["moduleimpl_id"])
|
moduleimpl_id = int(vals["moduleimpl_id"])
|
||||||
mode = "module"
|
mode = "module"
|
||||||
evals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
|
evals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
|
||||||
if not mode:
|
if not mode:
|
||||||
|
@ -77,7 +78,7 @@ def do_evaluation_listenotes(REQUEST):
|
||||||
if not evals:
|
if not evals:
|
||||||
return "<p>Aucune évaluation !</p>"
|
return "<p>Aucune évaluation !</p>"
|
||||||
|
|
||||||
format = REQUEST.form.get("format", "html")
|
format = vals.get("format", "html")
|
||||||
E = evals[0] # il y a au moins une evaluation
|
E = evals[0] # il y a au moins une evaluation
|
||||||
# description de l'evaluation
|
# description de l'evaluation
|
||||||
if mode == "eval":
|
if mode == "eval":
|
||||||
|
@ -177,8 +178,8 @@ def do_evaluation_listenotes(REQUEST):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
descr,
|
descr,
|
||||||
cancelbutton=None,
|
cancelbutton=None,
|
||||||
submitbutton=None,
|
submitbutton=None,
|
||||||
|
@ -201,7 +202,6 @@ def do_evaluation_listenotes(REQUEST):
|
||||||
hide_groups = tf[2]["hide_groups"]
|
hide_groups = tf[2]["hide_groups"]
|
||||||
with_emails = tf[2]["with_emails"]
|
with_emails = tf[2]["with_emails"]
|
||||||
return _make_table_notes(
|
return _make_table_notes(
|
||||||
REQUEST,
|
|
||||||
tf[1],
|
tf[1],
|
||||||
evals,
|
evals,
|
||||||
format=format,
|
format=format,
|
||||||
|
@ -214,7 +214,6 @@ def do_evaluation_listenotes(REQUEST):
|
||||||
|
|
||||||
|
|
||||||
def _make_table_notes(
|
def _make_table_notes(
|
||||||
REQUEST,
|
|
||||||
html_form,
|
html_form,
|
||||||
evals,
|
evals,
|
||||||
format="",
|
format="",
|
||||||
|
@ -229,8 +228,8 @@ def _make_table_notes(
|
||||||
return "<p>Aucune évaluation !</p>"
|
return "<p>Aucune évaluation !</p>"
|
||||||
E = evals[0]
|
E = evals[0]
|
||||||
moduleimpl_id = E["moduleimpl_id"]
|
moduleimpl_id = E["moduleimpl_id"]
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
Mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
|
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||||
# (debug) check that all evals are in same module:
|
# (debug) check that all evals are in same module:
|
||||||
for e in evals:
|
for e in evals:
|
||||||
|
@ -272,8 +271,8 @@ def _make_table_notes(
|
||||||
"expl_key": "Rem.",
|
"expl_key": "Rem.",
|
||||||
"email": "e-mail",
|
"email": "e-mail",
|
||||||
"emailperso": "e-mail perso",
|
"emailperso": "e-mail perso",
|
||||||
|
"signatures": "Signatures",
|
||||||
}
|
}
|
||||||
|
|
||||||
rows = []
|
rows = []
|
||||||
|
|
||||||
class keymgr(dict): # comment : key (pour regrouper les comments a la fin)
|
class keymgr(dict): # comment : key (pour regrouper les comments a la fin)
|
||||||
|
@ -316,7 +315,7 @@ def _make_table_notes(
|
||||||
|
|
||||||
rows.append(
|
rows.append(
|
||||||
{
|
{
|
||||||
"code": code,
|
"code": str(code), # INE, NIP ou etudid
|
||||||
"_code_td_attrs": 'style="padding-left: 1em; padding-right: 2em;"',
|
"_code_td_attrs": 'style="padding-left: 1em; padding-right: 2em;"',
|
||||||
"etudid": etudid,
|
"etudid": etudid,
|
||||||
"nom": etud["nom"].upper(),
|
"nom": etud["nom"].upper(),
|
||||||
|
@ -375,9 +374,11 @@ def _make_table_notes(
|
||||||
columns_ids.append(e["evaluation_id"])
|
columns_ids.append(e["evaluation_id"])
|
||||||
#
|
#
|
||||||
if anonymous_listing:
|
if anonymous_listing:
|
||||||
rows.sort(key=lambda x: x["code"])
|
rows.sort(key=lambda x: x["code"] or "")
|
||||||
else:
|
else:
|
||||||
rows.sort(key=lambda x: (x["nom"], x["prenom"])) # sort by nom, prenom
|
rows.sort(
|
||||||
|
key=lambda x: (x["nom"] or "", x["prenom"] or "")
|
||||||
|
) # sort by nom, prenom
|
||||||
|
|
||||||
# Si module, ajoute moyenne du module:
|
# Si module, ajoute moyenne du module:
|
||||||
if len(evals) > 1:
|
if len(evals) > 1:
|
||||||
|
@ -398,7 +399,7 @@ def _make_table_notes(
|
||||||
if with_emails:
|
if with_emails:
|
||||||
columns_ids += ["email", "emailperso"]
|
columns_ids += ["email", "emailperso"]
|
||||||
# Ajoute lignes en tête et moyennes
|
# Ajoute lignes en tête et moyennes
|
||||||
if len(evals) > 0:
|
if len(evals) > 0 and format != 'bordereau':
|
||||||
rows = [coefs, note_max] + rows
|
rows = [coefs, note_max] + rows
|
||||||
rows.append(moys)
|
rows.append(moys)
|
||||||
# ajout liens HTMl vers affichage une evaluation:
|
# ajout liens HTMl vers affichage une evaluation:
|
||||||
|
@ -422,6 +423,8 @@ def _make_table_notes(
|
||||||
columns_ids.append("expl_key")
|
columns_ids.append("expl_key")
|
||||||
elif format == "xls" or format == "xml":
|
elif format == "xls" or format == "xml":
|
||||||
columns_ids.append("comment")
|
columns_ids.append("comment")
|
||||||
|
elif format == "bordereau":
|
||||||
|
columns_ids.append("signatures")
|
||||||
|
|
||||||
# titres divers:
|
# titres divers:
|
||||||
gl = "".join(["&group_ids%3Alist=" + str(g) for g in group_ids])
|
gl = "".join(["&group_ids%3Alist=" + str(g) for g in group_ids])
|
||||||
|
@ -435,10 +438,33 @@ def _make_table_notes(
|
||||||
gl = "&with_emails%3Alist=yes" + gl
|
gl = "&with_emails%3Alist=yes" + gl
|
||||||
if len(evals) == 1:
|
if len(evals) == 1:
|
||||||
evalname = "%s-%s" % (Mod["code"], ndb.DateDMYtoISO(E["jour"]))
|
evalname = "%s-%s" % (Mod["code"], ndb.DateDMYtoISO(E["jour"]))
|
||||||
hh = "%s, %s (%d étudiants)" % (E["description"], gr_title, len(etudids))
|
|
||||||
filename = scu.make_filename("notes_%s_%s" % (evalname, gr_title_filename))
|
filename = scu.make_filename("notes_%s_%s" % (evalname, gr_title_filename))
|
||||||
|
|
||||||
|
|
||||||
|
if format == 'bordereau':
|
||||||
|
hh = " %d étudiants" % (len(etudids))
|
||||||
|
hh += " %d absent" % (nb_abs)
|
||||||
|
if nb_abs > 1:
|
||||||
|
hh += "s"
|
||||||
|
hh += ", %d en attente." % ( nb_att)
|
||||||
|
|
||||||
|
pdf_title = '<br/> BORDEREAU DE SIGNATURES'
|
||||||
|
pdf_title += '<br/><br/>%(titre)s' % sem
|
||||||
|
pdf_title += '<br/>(%(mois_debut)s - %(mois_fin)s)' % sem
|
||||||
|
pdf_title += ' semestre %s %s' % (sem['semestre_id'],sem.get('modalite',''))
|
||||||
|
pdf_title += '<br/>Notes du module %(code)s - %(titre)s' % Mod
|
||||||
|
pdf_title += '<br/>Evaluation : %(description)s ' % e
|
||||||
|
if len(e["jour"]) > 0 :
|
||||||
|
pdf_title += " (%(jour)s)" % e
|
||||||
|
pdf_title += '(noté sur %(note_max)s )<br/><br/>' % e
|
||||||
|
else:
|
||||||
|
hh = " %s, %s (%d étudiants)" % (E["description"], gr_title, len(etudids))
|
||||||
|
if len(e["jour"]) > 0 :
|
||||||
|
pdf_title = "%(description)s (%(jour)s)" % e
|
||||||
|
else:
|
||||||
|
pdf_title = "%(description)s " % e
|
||||||
|
|
||||||
caption = hh
|
caption = hh
|
||||||
pdf_title = "%(description)s (%(jour)s)" % e
|
|
||||||
html_title = ""
|
html_title = ""
|
||||||
base_url = "evaluation_listenotes?evaluation_id=%s" % E["evaluation_id"] + gl
|
base_url = "evaluation_listenotes?evaluation_id=%s" % E["evaluation_id"] + gl
|
||||||
html_next_section = (
|
html_next_section = (
|
||||||
|
@ -453,7 +479,7 @@ def _make_table_notes(
|
||||||
title += " %s" % gr_title
|
title += " %s" % gr_title
|
||||||
caption = title
|
caption = title
|
||||||
html_next_section = ""
|
html_next_section = ""
|
||||||
if format == "pdf":
|
if format == "pdf" or format == "bordereau":
|
||||||
caption = "" # same as pdf_title
|
caption = "" # same as pdf_title
|
||||||
pdf_title = title
|
pdf_title = title
|
||||||
html_title = (
|
html_title = (
|
||||||
|
@ -481,8 +507,9 @@ def _make_table_notes(
|
||||||
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
|
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
|
||||||
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
|
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
|
||||||
)
|
)
|
||||||
|
if format == "bordereau":
|
||||||
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
|
format = "pdf"
|
||||||
|
t = tab.make_page(format=format, with_html_headers=False)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
@ -500,12 +527,12 @@ def _make_table_notes(
|
||||||
# Une seule evaluation: ajoute histogramme
|
# Une seule evaluation: ajoute histogramme
|
||||||
histo = histogram_notes(notes)
|
histo = histogram_notes(notes)
|
||||||
# 2 colonnes: histo, comments
|
# 2 colonnes: histo, comments
|
||||||
C = [
|
C = []
|
||||||
"<table><tr><td><div><h4>Répartition des notes:</h4>"
|
C.append('<br><b><i>Bordereau de Signatures (version PDF)</i> </b><a href="%s&format=bordereau">%s</a>'%(base_url, scu.ICON_PDF))
|
||||||
+ histo
|
C.append("<br><table><tr><td><div><h4>Répartition des notes:</h4>")
|
||||||
+ "</div></td>\n",
|
C.append( histo + "</div></td>\n")
|
||||||
'<td style="padding-left: 50px; vertical-align: top;"><p>',
|
C.append('<td style="padding-left: 50px; vertical-align: top;"><p>')
|
||||||
]
|
|
||||||
commentkeys = list(K.items()) # [ (comment, key), ... ]
|
commentkeys = list(K.items()) # [ (comment, key), ... ]
|
||||||
commentkeys.sort(key=lambda x: int(x[1]))
|
commentkeys.sort(key=lambda x: int(x[1]))
|
||||||
for (comment, key) in commentkeys:
|
for (comment, key) in commentkeys:
|
||||||
|
@ -625,8 +652,11 @@ def _add_eval_columns(
|
||||||
else:
|
else:
|
||||||
moys[evaluation_id] = ""
|
moys[evaluation_id] = ""
|
||||||
|
|
||||||
titles[evaluation_id] = "%(description)s (%(jour)s)" % e
|
if len(e["jour"]) > 0:
|
||||||
|
titles[evaluation_id] = "%(description)s (%(jour)s)" % e
|
||||||
|
else:
|
||||||
|
titles[evaluation_id] = "%(description)s " % e
|
||||||
|
|
||||||
if e["eval_state"]["evalcomplete"]:
|
if e["eval_state"]["evalcomplete"]:
|
||||||
titles["_" + str(evaluation_id) + "_td_attrs"] = 'class="eval_complete"'
|
titles["_" + str(evaluation_id) + "_td_attrs"] = 'class="eval_complete"'
|
||||||
elif e["eval_state"]["evalattente"]:
|
elif e["eval_state"]["evalattente"]:
|
||||||
|
@ -665,7 +695,7 @@ def _add_moymod_column(
|
||||||
notes.append(val)
|
notes.append(val)
|
||||||
nb_notes = nb_notes + 1
|
nb_notes = nb_notes + 1
|
||||||
sum_notes += val
|
sum_notes += val
|
||||||
coefs[col_id] = ""
|
coefs[col_id] = "(avec abs)"
|
||||||
if keep_numeric:
|
if keep_numeric:
|
||||||
note_max[col_id] = 20.0
|
note_max[col_id] = 20.0
|
||||||
else:
|
else:
|
||||||
|
@ -760,9 +790,7 @@ def evaluation_check_absences(evaluation_id):
|
||||||
return ValButAbs, AbsNonSignalee, ExcNonSignalee, ExcNonJust, AbsButExc
|
return ValButAbs, AbsNonSignalee, ExcNonSignalee, ExcNonJust, AbsButExc
|
||||||
|
|
||||||
|
|
||||||
def evaluation_check_absences_html(
|
def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True):
|
||||||
evaluation_id, with_header=True, show_ok=True, REQUEST=None
|
|
||||||
):
|
|
||||||
"""Affiche etat verification absences d'une evaluation"""
|
"""Affiche etat verification absences d'une evaluation"""
|
||||||
|
|
||||||
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||||
|
@ -778,9 +806,7 @@ def evaluation_check_absences_html(
|
||||||
|
|
||||||
if with_header:
|
if with_header:
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header("Vérification absences à l'évaluation"),
|
||||||
REQUEST, "Vérification absences à l'évaluation"
|
|
||||||
),
|
|
||||||
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id),
|
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>""",
|
"""<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.</p>""",
|
||||||
]
|
]
|
||||||
|
@ -814,14 +840,16 @@ def evaluation_check_absences_html(
|
||||||
)
|
)
|
||||||
if linkabs:
|
if linkabs:
|
||||||
H.append(
|
H.append(
|
||||||
'<a class="stdlink" href="Absences/doSignaleAbsence?etudid=%s&datedebut=%s&datefin=%s&demijournee=%s&moduleimpl_id=%s">signaler cette absence</a>'
|
f"""<a class="stdlink" href="{url_for(
|
||||||
% (
|
'absences.doSignaleAbsence',
|
||||||
etud["etudid"],
|
scodoc_dept=g.scodoc_dept,
|
||||||
six.moves.urllib.parse.quote(E["jour"]),
|
etudid=etud["etudid"],
|
||||||
six.moves.urllib.parse.quote(E["jour"]),
|
datedebut=E["jour"],
|
||||||
demijournee,
|
datefin=E["jour"],
|
||||||
E["moduleimpl_id"],
|
demijournee=demijournee,
|
||||||
|
moduleimpl_id=E["moduleimpl_id"],
|
||||||
)
|
)
|
||||||
|
}">signaler cette absence</a>"""
|
||||||
)
|
)
|
||||||
H.append("</li>")
|
H.append("</li>")
|
||||||
H.append("</ul>")
|
H.append("</ul>")
|
||||||
|
@ -861,12 +889,11 @@ def evaluation_check_absences_html(
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_check_absences_html(formsemestre_id, REQUEST=None):
|
def formsemestre_check_absences_html(formsemestre_id):
|
||||||
"""Affiche etat verification absences pour toutes les evaluations du semestre !"""
|
"""Affiche etat verification absences pour toutes les evaluations du semestre !"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
REQUEST,
|
|
||||||
"Vérification absences aux évaluations de ce semestre",
|
"Vérification absences aux évaluations de ce semestre",
|
||||||
sem,
|
sem,
|
||||||
),
|
),
|
||||||
|
@ -876,9 +903,7 @@ def formsemestre_check_absences_html(formsemestre_id, REQUEST=None):
|
||||||
</p>""",
|
</p>""",
|
||||||
]
|
]
|
||||||
# Modules, dans l'ordre
|
# Modules, dans l'ordre
|
||||||
Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||||
formsemestre_id=formsemestre_id
|
|
||||||
)
|
|
||||||
for M in Mlist:
|
for M in Mlist:
|
||||||
evals = sco_evaluations.do_evaluation_list(
|
evals = sco_evaluations.do_evaluation_list(
|
||||||
{"moduleimpl_id": M["moduleimpl_id"]}
|
{"moduleimpl_id": M["moduleimpl_id"]}
|
||||||
|
@ -894,7 +919,6 @@ def formsemestre_check_absences_html(formsemestre_id, REQUEST=None):
|
||||||
E["evaluation_id"],
|
E["evaluation_id"],
|
||||||
with_header=False,
|
with_header=False,
|
||||||
show_ok=False,
|
show_ok=False,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if evals:
|
if evals:
|
||||||
|
|
|
@ -32,32 +32,242 @@ avec `ext` membre de LOGOS_IMAGES_ALLOWED_TYPES (= jpg, png)
|
||||||
|
|
||||||
SCODOC_LOGOS_DIR /opt/scodoc-data/config/logos
|
SCODOC_LOGOS_DIR /opt/scodoc-data/config/logos
|
||||||
"""
|
"""
|
||||||
|
import glob
|
||||||
import imghdr
|
import imghdr
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from flask import abort, current_app
|
from flask import abort, current_app, url_for
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
from app import Departement, ScoValueError
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
from PIL import Image as PILImage
|
||||||
|
|
||||||
|
GLOBAL = "_" # category for server level logos
|
||||||
|
|
||||||
|
|
||||||
def get_logo_filename(logo_type: str, scodoc_dept: str) -> str:
|
def find_logo(logoname, dept_id=None, strict=False, prefix=scu.LOGO_FILE_PREFIX):
|
||||||
"""return full filename for this logo, or "" if not found
|
|
||||||
an existing file with extension.
|
|
||||||
logo_type: "header" or "footer"
|
|
||||||
scodoc-dept: acronym
|
|
||||||
"""
|
"""
|
||||||
# Search logos in dept specific dir (/opt/scodoc-data/config/logos/logos_<dept>),
|
"Recherche un logo 'name' existant.
|
||||||
# then in config dir /opt/scodoc-data/config/logos/
|
Deux strategies:
|
||||||
for image_dir in (
|
si strict:
|
||||||
scu.SCODOC_LOGOS_DIR + "/logos_" + scodoc_dept,
|
reherche uniquement dans le département puis si non trouvé au niveau global
|
||||||
scu.SCODOC_LOGOS_DIR, # global logos
|
sinon
|
||||||
):
|
On recherche en local au dept d'abord puis si pas trouvé recherche globale
|
||||||
for suffix in scu.LOGOS_IMAGES_ALLOWED_TYPES:
|
quelque soit la stratégie, retourne None si pas trouvé
|
||||||
filename = os.path.join(image_dir, f"logo_{logo_type}.{suffix}")
|
:param logoname: le nom recherche
|
||||||
if os.path.isfile(filename) and os.access(filename, os.R_OK):
|
:param dept_id: l'id du département dans lequel se fait la recherche (None si global)
|
||||||
return filename
|
:param strict: stratégie de recherche (strict = False => dept ou global)
|
||||||
|
:param prefix: le prefix utilisé (parmi scu.LOGO_FILE_PREFIX / scu.BACKGROUND_FILE_PREFIX)
|
||||||
|
:return: un objet Logo désignant le fichier image trouvé (ou None)
|
||||||
|
"""
|
||||||
|
logo = Logo(logoname, dept_id, prefix).select()
|
||||||
|
if logo is None and not strict:
|
||||||
|
logo = Logo(logoname=logoname, dept_id=None, prefix=prefix).select()
|
||||||
|
return logo
|
||||||
|
|
||||||
return ""
|
|
||||||
|
def delete_logo(name, dept_id=None):
|
||||||
|
"""Delete all files matching logo (dept_id, name) (including all allowed extensions)
|
||||||
|
Args:
|
||||||
|
name: The name of the logo
|
||||||
|
dept_id: the dept_id (if local). Use None to destroy globals logos
|
||||||
|
"""
|
||||||
|
logo = find_logo(logoname=name, dept_id=dept_id)
|
||||||
|
while logo is not None:
|
||||||
|
os.unlink(logo.select().filepath)
|
||||||
|
logo = find_logo(logoname=name, dept_id=dept_id)
|
||||||
|
|
||||||
|
|
||||||
|
def write_logo(stream, name, dept_id=None):
|
||||||
|
"""Crée le fichier logo sur le serveur.
|
||||||
|
Le suffixe du fichier (parmi LOGO_IMAGES_ALLOWED_TYPES) est déduit du contenu du stream"""
|
||||||
|
Logo(logoname=name, dept_id=dept_id).create(stream)
|
||||||
|
|
||||||
|
|
||||||
|
def list_logos():
|
||||||
|
"""Crée l'inventaire de tous les logos existants.
|
||||||
|
L'inventaire se présente comme un dictionnaire de dictionnaire de Logo:
|
||||||
|
[None][name] pour les logos globaux
|
||||||
|
[dept_id][name] pour les logos propres à un département (attention id numérique du dept)
|
||||||
|
Les départements sans logos sont absents du résultat
|
||||||
|
"""
|
||||||
|
inventory = {None: _list_dept_logos()} # logos globaux (header / footer)
|
||||||
|
for dept in Departement.query.filter_by(visible=True).all():
|
||||||
|
logos_dept = _list_dept_logos(dept_id=dept.id)
|
||||||
|
if logos_dept:
|
||||||
|
inventory[dept.id] = _list_dept_logos(dept.id)
|
||||||
|
return inventory
|
||||||
|
|
||||||
|
|
||||||
|
def _list_dept_logos(dept_id=None, prefix=scu.LOGO_FILE_PREFIX):
|
||||||
|
"""nventorie toutes les images existantes pour un niveau (GLOBAL ou un département).
|
||||||
|
retourne un dictionnaire de Logo [logoname] -> Logo
|
||||||
|
les noms des fichiers concernés doivent être de la forme: <rep>/<prefix><name>.<suffixe>
|
||||||
|
<rep> : répertoire de recherche (déduit du dept_id)
|
||||||
|
<prefix>: le prefix (LOGO_FILE_PREFIX pour les logos)
|
||||||
|
<suffix>: un des suffixes autorisés
|
||||||
|
:param dept_id: l'id du departement concerné (si None -> global)
|
||||||
|
:param prefix: le préfixe utilisé
|
||||||
|
:return: le résultat de la recherche ou None si aucune image trouvée
|
||||||
|
"""
|
||||||
|
allowed_ext = "|".join(scu.LOGOS_IMAGES_ALLOWED_TYPES)
|
||||||
|
filename_parser = re.compile(f"{prefix}([^.]*).({allowed_ext})")
|
||||||
|
logos = {}
|
||||||
|
path_dir = Path(scu.SCODOC_LOGOS_DIR)
|
||||||
|
if dept_id:
|
||||||
|
path_dir = Path(
|
||||||
|
os.path.sep.join(
|
||||||
|
[scu.SCODOC_LOGOS_DIR, scu.LOGOS_DIR_PREFIX + str(dept_id)]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if path_dir.exists():
|
||||||
|
for entry in path_dir.iterdir():
|
||||||
|
if os.access(path_dir.joinpath(entry).absolute(), os.R_OK):
|
||||||
|
result = filename_parser.match(entry.name)
|
||||||
|
if result:
|
||||||
|
logoname = result.group(1)
|
||||||
|
logos[logoname] = Logo(logoname=logoname, dept_id=dept_id).select()
|
||||||
|
return logos if len(logos.keys()) > 0 else None
|
||||||
|
|
||||||
|
|
||||||
|
class Logo:
|
||||||
|
"""Responsable des opérations (select, create), du calcul des chemins et url
|
||||||
|
ainsi que de la récupération des informations sur un logp.
|
||||||
|
Usage:
|
||||||
|
logo existant: Logo(<name>, <dept_id>, ...).select() (retourne None si fichier non trouvé)
|
||||||
|
logo en création: Logo(<name>, <dept_id>, ...).create(stream)
|
||||||
|
Les attributs filename, filepath, get_url() ne devraient pas être utilisés avant les opérations
|
||||||
|
select ou save (le format n'est pas encore connu à ce moement là)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, logoname, dept_id=None, prefix=scu.LOGO_FILE_PREFIX):
|
||||||
|
"""Initialisation des noms et département des logos.
|
||||||
|
if prefix = None on recherche simplement une image 'logoname.*'
|
||||||
|
Le format est renseigné au moment de la lecture (select) ou de la création (create) de l'objet
|
||||||
|
"""
|
||||||
|
self.logoname = secure_filename(logoname)
|
||||||
|
self.scodoc_dept_id = dept_id
|
||||||
|
self.prefix = prefix or ""
|
||||||
|
if self.scodoc_dept_id:
|
||||||
|
self.dirpath = os.path.sep.join(
|
||||||
|
[
|
||||||
|
scu.SCODOC_LOGOS_DIR,
|
||||||
|
scu.LOGOS_DIR_PREFIX + secure_filename(str(dept_id)),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.dirpath = scu.SCODOC_LOGOS_DIR
|
||||||
|
self.basepath = os.path.sep.join(
|
||||||
|
[self.dirpath, self.prefix + secure_filename(self.logoname)]
|
||||||
|
)
|
||||||
|
# next attributes are computer by the select function
|
||||||
|
self.suffix = "Not inited: call the select or create function before access"
|
||||||
|
self.filepath = "Not inited: call the select or create function before access"
|
||||||
|
self.filename = "Not inited: call the select or create function before access"
|
||||||
|
self.size = "Not inited: call the select or create function before access"
|
||||||
|
self.aspect_ratio = (
|
||||||
|
"Not inited: call the select or create function before access"
|
||||||
|
)
|
||||||
|
self.density = "Not inited: call the select or create function before access"
|
||||||
|
self.mm = "Not inited: call the select or create function before access"
|
||||||
|
|
||||||
|
def _set_format(self, fmt):
|
||||||
|
self.suffix = fmt
|
||||||
|
self.filepath = self.basepath + "." + fmt
|
||||||
|
self.filename = self.logoname + "." + fmt
|
||||||
|
|
||||||
|
def _ensure_directory_exists(self):
|
||||||
|
"create enclosing directory if necessary"
|
||||||
|
if not Path(self.dirpath).exists():
|
||||||
|
current_app.logger.info(f"sco_logos creating directory %s", self.dirpath)
|
||||||
|
os.mkdir(self.dirpath)
|
||||||
|
|
||||||
|
def create(self, stream):
|
||||||
|
img_type = guess_image_type(stream)
|
||||||
|
if img_type not in scu.LOGOS_IMAGES_ALLOWED_TYPES:
|
||||||
|
abort(400, "type d'image invalide")
|
||||||
|
self._set_format(img_type)
|
||||||
|
self._ensure_directory_exists()
|
||||||
|
filename = self.basepath + "." + self.suffix
|
||||||
|
with open(filename, "wb") as f:
|
||||||
|
f.write(stream.read())
|
||||||
|
current_app.logger.info(f"sco_logos.store_image %s", self.filename)
|
||||||
|
# erase other formats if they exists
|
||||||
|
for suffix in set(scu.LOGOS_IMAGES_ALLOWED_TYPES) - set([img_type]):
|
||||||
|
try:
|
||||||
|
os.unlink(self.basepath + "." + suffix)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _read_info(self, img):
|
||||||
|
"""computes some properties from the real image
|
||||||
|
aspect_ratio assumes that x_density and y_density are equals
|
||||||
|
"""
|
||||||
|
x_size, y_size = img.size
|
||||||
|
self.density = img.info.get("dpi", None)
|
||||||
|
unit = 1
|
||||||
|
if self.density is None: # no dpi found try jfif infos
|
||||||
|
self.density = img.info.get("jfif_density", None)
|
||||||
|
unit = img.info.get("jfif_unit", 0) # 0 = no unit ; 1 = inch ; 2 = mm
|
||||||
|
if self.density is not None:
|
||||||
|
x_density, y_density = self.density
|
||||||
|
if unit != 0:
|
||||||
|
unit2mm = [0, 1 / 0.254, 0.1][unit]
|
||||||
|
x_mm = round(x_size * unit2mm / x_density, 2)
|
||||||
|
y_mm = round(y_size * unit2mm / y_density, 2)
|
||||||
|
self.mm = (x_mm, y_mm)
|
||||||
|
else:
|
||||||
|
self.mm = None
|
||||||
|
else:
|
||||||
|
self.mm = None
|
||||||
|
|
||||||
|
self.size = (x_size, y_size)
|
||||||
|
self.aspect_ratio = round(float(x_size) / y_size, 2)
|
||||||
|
|
||||||
|
def select(self):
|
||||||
|
"""
|
||||||
|
Récupération des données pour un logo existant
|
||||||
|
il doit exister un et un seul fichier image parmi de suffixe/types autorisés
|
||||||
|
(sinon on prend le premier trouvé)
|
||||||
|
cette opération permet d'affiner le format d'un logo de format inconnu
|
||||||
|
"""
|
||||||
|
for suffix in scu.LOGOS_IMAGES_ALLOWED_TYPES:
|
||||||
|
path = Path(self.basepath + "." + suffix)
|
||||||
|
if path.exists():
|
||||||
|
self._set_format(suffix)
|
||||||
|
with open(self.filepath, "rb") as f:
|
||||||
|
img = PILImage.open(f)
|
||||||
|
self._read_info(img)
|
||||||
|
return self
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_url(self):
|
||||||
|
"""Retourne l'URL permettant d'obtenir l'image du logo"""
|
||||||
|
return url_for(
|
||||||
|
"scodoc.get_logo",
|
||||||
|
dept_id=self.scodoc_dept_id,
|
||||||
|
name=self.logoname,
|
||||||
|
global_if_not_found=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_url_small(self):
|
||||||
|
"""Retourne l'URL permettant d'obtenir l'image du logo sous forme de miniature"""
|
||||||
|
return url_for(
|
||||||
|
"scodoc.get_logo_small",
|
||||||
|
dept_id=self.scodoc_dept_id,
|
||||||
|
name=self.logoname,
|
||||||
|
global_if_not_found=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_usage(self):
|
||||||
|
if self.mm is None:
|
||||||
|
return f'<logo name="{self.logoname}" width="?? mm" height="?? mm">'
|
||||||
|
else:
|
||||||
|
return f'<logo name="{self.logoname}" width="{self.mm[0]}mm"">'
|
||||||
|
|
||||||
|
|
||||||
def guess_image_type(stream) -> str:
|
def guess_image_type(stream) -> str:
|
||||||
|
@ -70,26 +280,33 @@ def guess_image_type(stream) -> str:
|
||||||
return fmt if fmt != "jpeg" else "jpg"
|
return fmt if fmt != "jpeg" else "jpg"
|
||||||
|
|
||||||
|
|
||||||
def _ensure_directory_exists(filename):
|
def make_logo_local(logoname, dept_name):
|
||||||
"create enclosing directory if necessary"
|
depts = Departement.query.filter_by(acronym=dept_name).all()
|
||||||
directory = os.path.split(filename)[0]
|
if len(depts) == 0:
|
||||||
if not os.path.exists(directory):
|
print(f"no dept {dept_name} found. aborting")
|
||||||
current_app.logger.info(f"sco_logos creating directory %s", directory)
|
return
|
||||||
os.mkdir(directory)
|
if len(depts) > 1:
|
||||||
|
print(f"several depts {dept_name} found. aborting")
|
||||||
|
return
|
||||||
def store_image(stream, basename):
|
dept = depts[0]
|
||||||
img_type = guess_image_type(stream)
|
print(f"Move logo {logoname}' from global to {dept.acronym}")
|
||||||
if img_type not in scu.LOGOS_IMAGES_ALLOWED_TYPES:
|
old_path_wild = f"/opt/scodoc-data/config/logos/logo_{logoname}.*"
|
||||||
abort(400, "type d'image invalide")
|
new_dir = f"/opt/scodoc-data/config/logos/logos_{dept.id}"
|
||||||
filename = basename + "." + img_type
|
logos = glob.glob(old_path_wild)
|
||||||
_ensure_directory_exists(filename)
|
# checks that there is non local already present
|
||||||
with open(filename, "wb") as f:
|
for logo in logos:
|
||||||
f.write(stream.read())
|
filename = os.path.split(logo)[1]
|
||||||
current_app.logger.info(f"sco_logos.store_image %s", filename)
|
new_name = os.path.sep.join([new_dir, filename])
|
||||||
# erase other formats if they exists
|
if os.path.exists(new_name):
|
||||||
for extension in set(scu.LOGOS_IMAGES_ALLOWED_TYPES) - set([img_type]):
|
print("local version of global logo already exists. aborting")
|
||||||
try:
|
return
|
||||||
os.unlink(basename + "." + extension)
|
# create new__dir if necessary
|
||||||
except IOError:
|
if not os.path.exists(new_dir):
|
||||||
pass
|
print(f"- create {new_dir} directory")
|
||||||
|
os.mkdir(new_dir)
|
||||||
|
# move global logo (all suffixes) to local dir note: pre existent file (logo_XXX.*) in local dir does not
|
||||||
|
# prevent operation if there is no conflict with moved files
|
||||||
|
# At this point everything is ok so we can do files manipulation
|
||||||
|
for logo in logos:
|
||||||
|
shutil.move(logo, new_dir)
|
||||||
|
# print(f"moved {n_moves}/{n} etuds")
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
"""
|
"""
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from flask import url_for, g
|
from flask import url_for, g, request
|
||||||
|
|
||||||
import app
|
import app
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -63,7 +63,7 @@ def formsemestre_table_etuds_lycees(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def scodoc_table_etuds_lycees(format="html", REQUEST=None):
|
def scodoc_table_etuds_lycees(format="html"):
|
||||||
"""Table avec _tous_ les étudiants des semestres non verrouillés
|
"""Table avec _tous_ les étudiants des semestres non verrouillés
|
||||||
de _tous_ les départements.
|
de _tous_ les départements.
|
||||||
"""
|
"""
|
||||||
|
@ -84,8 +84,8 @@ def scodoc_table_etuds_lycees(format="html", REQUEST=None):
|
||||||
sco_preferences.SemPreferences(),
|
sco_preferences.SemPreferences(),
|
||||||
no_links=True,
|
no_links=True,
|
||||||
)
|
)
|
||||||
tab.base_url = REQUEST.URL0
|
tab.base_url = request.base_url
|
||||||
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":
|
if format != "html":
|
||||||
return t
|
return t
|
||||||
H = [
|
H = [
|
||||||
|
@ -181,23 +181,22 @@ def formsemestre_etuds_lycees(
|
||||||
format="html",
|
format="html",
|
||||||
only_primo=False,
|
only_primo=False,
|
||||||
no_grouping=False,
|
no_grouping=False,
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Table des lycées d'origine"""
|
"""Table des lycées d'origine"""
|
||||||
tab, etuds_by_lycee = formsemestre_table_etuds_lycees(
|
tab, etuds_by_lycee = formsemestre_table_etuds_lycees(
|
||||||
formsemestre_id, only_primo=only_primo, group_lycees=not no_grouping
|
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:
|
if only_primo:
|
||||||
tab.base_url += "&only_primo=1"
|
tab.base_url += "&only_primo=1"
|
||||||
if no_grouping:
|
if no_grouping:
|
||||||
tab.base_url += "&no_grouping=1"
|
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":
|
if format != "html":
|
||||||
return t
|
return t
|
||||||
F = [
|
F = [
|
||||||
sco_report.tsp_form_primo_group(
|
sco_report.tsp_form_primo_group(
|
||||||
REQUEST, only_primo, no_grouping, formsemestre_id, format
|
only_primo, no_grouping, formsemestre_id, format
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
H = [
|
H = [
|
||||||
|
|
|
@ -88,14 +88,14 @@ def do_modalite_list(*args, **kw):
|
||||||
return _modaliteEditor.list(cnx, *args, **kw)
|
return _modaliteEditor.list(cnx, *args, **kw)
|
||||||
|
|
||||||
|
|
||||||
def do_modalite_create(args, REQUEST=None):
|
def do_modalite_create(args):
|
||||||
"create a modalite"
|
"create a modalite"
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
r = _modaliteEditor.create(cnx, args)
|
r = _modaliteEditor.create(cnx, args)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def do_modalite_delete(oid, REQUEST=None):
|
def do_modalite_delete(oid):
|
||||||
"delete a modalite"
|
"delete a modalite"
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
log("do_modalite_delete: form_modalite_id=%s" % oid)
|
log("do_modalite_delete: form_modalite_id=%s" % oid)
|
||||||
|
|
|
@ -100,9 +100,7 @@ def do_moduleimpl_delete(oid, formsemestre_id=None):
|
||||||
) # > moduleimpl_delete
|
) # > moduleimpl_delete
|
||||||
|
|
||||||
|
|
||||||
def do_moduleimpl_list(
|
def moduleimpl_list(moduleimpl_id=None, formsemestre_id=None, module_id=None):
|
||||||
moduleimpl_id=None, formsemestre_id=None, module_id=None, REQUEST=None
|
|
||||||
):
|
|
||||||
"list moduleimpls"
|
"list moduleimpls"
|
||||||
args = locals()
|
args = locals()
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
|
@ -110,7 +108,7 @@ def do_moduleimpl_list(
|
||||||
# Ajoute la liste des enseignants
|
# Ajoute la liste des enseignants
|
||||||
for mo in modimpls:
|
for mo in modimpls:
|
||||||
mo["ens"] = do_ens_list(args={"moduleimpl_id": mo["moduleimpl_id"]})
|
mo["ens"] = do_ens_list(args={"moduleimpl_id": mo["moduleimpl_id"]})
|
||||||
return scu.return_text_if_published(modimpls, REQUEST)
|
return modimpls
|
||||||
|
|
||||||
|
|
||||||
def do_moduleimpl_edit(args, formsemestre_id=None, cnx=None):
|
def do_moduleimpl_edit(args, formsemestre_id=None, cnx=None):
|
||||||
|
@ -124,10 +122,11 @@ def do_moduleimpl_edit(args, formsemestre_id=None, cnx=None):
|
||||||
) # > modif moduleimpl
|
) # > modif moduleimpl
|
||||||
|
|
||||||
|
|
||||||
def do_moduleimpl_withmodule_list(
|
def moduleimpl_withmodule_list(
|
||||||
moduleimpl_id=None, formsemestre_id=None, module_id=None, REQUEST=None
|
moduleimpl_id=None, formsemestre_id=None, module_id=None
|
||||||
):
|
):
|
||||||
"""Liste les moduleimpls et ajoute dans chacun le module correspondant
|
"""Liste les moduleimpls et ajoute dans chacun
|
||||||
|
l'UE, la matière et le module auxquels ils appartiennent.
|
||||||
Tri la liste par semestre/UE/numero_matiere/numero_module.
|
Tri la liste par semestre/UE/numero_matiere/numero_module.
|
||||||
|
|
||||||
Attention: Cette fonction fait partie de l'API ScoDoc 7 et est publiée.
|
Attention: Cette fonction fait partie de l'API ScoDoc 7 et est publiée.
|
||||||
|
@ -136,23 +135,33 @@ def do_moduleimpl_withmodule_list(
|
||||||
from app.scodoc import sco_edit_matiere
|
from app.scodoc import sco_edit_matiere
|
||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
|
|
||||||
args = locals()
|
modimpls = moduleimpl_list(
|
||||||
del args["REQUEST"]
|
|
||||||
modimpls = do_moduleimpl_list(
|
|
||||||
**{
|
**{
|
||||||
"moduleimpl_id": moduleimpl_id,
|
"moduleimpl_id": moduleimpl_id,
|
||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
"module_id": module_id,
|
"module_id": module_id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
for mo in modimpls:
|
ues = {}
|
||||||
mo["module"] = sco_edit_module.do_module_list(
|
matieres = {}
|
||||||
args={"module_id": mo["module_id"]}
|
modules = {}
|
||||||
)[0]
|
for mi in modimpls:
|
||||||
mo["ue"] = sco_edit_ue.do_ue_list(args={"ue_id": mo["module"]["ue_id"]})[0]
|
module_id = mi["module_id"]
|
||||||
mo["matiere"] = sco_edit_matiere.do_matiere_list(
|
if not mi["module_id"] in modules:
|
||||||
args={"matiere_id": mo["module"]["matiere_id"]}
|
modules[module_id] = sco_edit_module.module_list(
|
||||||
)[0]
|
args={"module_id": module_id}
|
||||||
|
)[0]
|
||||||
|
mi["module"] = modules[module_id]
|
||||||
|
ue_id = mi["module"]["ue_id"]
|
||||||
|
if not ue_id in ues:
|
||||||
|
ues[ue_id] = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
|
||||||
|
mi["ue"] = ues[ue_id]
|
||||||
|
matiere_id = mi["module"]["matiere_id"]
|
||||||
|
if not matiere_id in matieres:
|
||||||
|
matieres[matiere_id] = sco_edit_matiere.matiere_list(
|
||||||
|
args={"matiere_id": matiere_id}
|
||||||
|
)[0]
|
||||||
|
mi["matiere"] = matieres[matiere_id]
|
||||||
|
|
||||||
# tri par semestre/UE/numero_matiere/numero_module
|
# tri par semestre/UE/numero_matiere/numero_module
|
||||||
modimpls.sort(
|
modimpls.sort(
|
||||||
|
@ -166,19 +175,30 @@ def do_moduleimpl_withmodule_list(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return scu.return_text_if_published(modimpls, REQUEST)
|
return modimpls
|
||||||
|
|
||||||
|
|
||||||
def do_moduleimpl_inscription_list(moduleimpl_id=None, etudid=None, REQUEST=None):
|
def moduleimpls_in_external_ue(ue_id):
|
||||||
|
"""List of modimpls in this ue"""
|
||||||
|
cursor = ndb.SimpleQuery(
|
||||||
|
"""SELECT DISTINCT mi.*
|
||||||
|
FROM notes_ue u, notes_moduleimpl mi, notes_modules m
|
||||||
|
WHERE u.is_external is true
|
||||||
|
AND mi.module_id = m.id AND m.ue_id = %(ue_id)s
|
||||||
|
""",
|
||||||
|
{"ue_id": ue_id},
|
||||||
|
)
|
||||||
|
return cursor.dictfetchall()
|
||||||
|
|
||||||
|
|
||||||
|
def do_moduleimpl_inscription_list(moduleimpl_id=None, etudid=None):
|
||||||
"list moduleimpl_inscriptions"
|
"list moduleimpl_inscriptions"
|
||||||
args = locals()
|
args = locals()
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
return scu.return_text_if_published(
|
return _moduleimpl_inscriptionEditor.list(cnx, args)
|
||||||
_moduleimpl_inscriptionEditor.list(cnx, args), REQUEST
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def do_moduleimpl_listeetuds(moduleimpl_id):
|
def moduleimpl_listeetuds(moduleimpl_id):
|
||||||
"retourne liste des etudids inscrits a ce module"
|
"retourne liste des etudids inscrits a ce module"
|
||||||
req = """SELECT DISTINCT Im.etudid
|
req = """SELECT DISTINCT Im.etudid
|
||||||
FROM notes_moduleimpl_inscription Im,
|
FROM notes_moduleimpl_inscription Im,
|
||||||
|
@ -244,9 +264,7 @@ def do_moduleimpl_inscription_delete(oid, formsemestre_id=None):
|
||||||
) # > moduleimpl_inscription
|
) # > moduleimpl_inscription
|
||||||
|
|
||||||
|
|
||||||
def do_moduleimpl_inscrit_etuds(
|
def do_moduleimpl_inscrit_etuds(moduleimpl_id, formsemestre_id, etudids, reset=False):
|
||||||
moduleimpl_id, formsemestre_id, etudids, reset=False, REQUEST=None
|
|
||||||
):
|
|
||||||
"""Inscrit les etudiants (liste d'etudids) a ce module.
|
"""Inscrit les etudiants (liste d'etudids) a ce module.
|
||||||
Si reset, desinscrit tous les autres.
|
Si reset, desinscrit tous les autres.
|
||||||
"""
|
"""
|
||||||
|
@ -309,11 +327,11 @@ def do_ens_create(args):
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def can_change_module_resp(REQUEST, moduleimpl_id):
|
def can_change_module_resp(moduleimpl_id):
|
||||||
"""Check if current user can modify module resp. (raise exception if not).
|
"""Check if current user can modify module resp. (raise exception if not).
|
||||||
= Admin, et dir des etud. (si option l'y autorise)
|
= Admin, et dir des etud. (si option l'y autorise)
|
||||||
"""
|
"""
|
||||||
M = do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
|
M = moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
# -- check lock
|
# -- check lock
|
||||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||||
if not sem["etat"]:
|
if not sem["etat"]:
|
||||||
|
@ -329,7 +347,7 @@ def can_change_module_resp(REQUEST, moduleimpl_id):
|
||||||
|
|
||||||
def can_change_ens(moduleimpl_id, raise_exc=True):
|
def can_change_ens(moduleimpl_id, raise_exc=True):
|
||||||
"check if current user can modify ens list (raise exception if not)"
|
"check if current user can modify ens list (raise exception if not)"
|
||||||
M = do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
|
M = moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
# -- check lock
|
# -- check lock
|
||||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||||
if not sem["etat"]:
|
if not sem["etat"]:
|
||||||
|
|
|
@ -30,7 +30,8 @@
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
import flask
|
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.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
@ -50,9 +51,7 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_inscriptions_edit(
|
def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
||||||
moduleimpl_id, etuds=[], submitted=False, REQUEST=None
|
|
||||||
):
|
|
||||||
"""Formulaire inscription des etudiants a ce module
|
"""Formulaire inscription des etudiants a ce module
|
||||||
* Gestion des inscriptions
|
* Gestion des inscriptions
|
||||||
Nom TD TA TP (triable)
|
Nom TD TA TP (triable)
|
||||||
|
@ -64,9 +63,9 @@ def moduleimpl_inscriptions_edit(
|
||||||
|
|
||||||
* Si pas les droits: idem en readonly
|
* Si pas les droits: idem en readonly
|
||||||
"""
|
"""
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
formsemestre_id = M["formsemestre_id"]
|
formsemestre_id = M["formsemestre_id"]
|
||||||
mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
|
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
# -- check lock
|
# -- check lock
|
||||||
if not sem["etat"]:
|
if not sem["etat"]:
|
||||||
|
@ -137,7 +136,7 @@ def moduleimpl_inscriptions_edit(
|
||||||
|
|
||||||
</script>"""
|
</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(
|
H.append(
|
||||||
"""
|
"""
|
||||||
<input type="hidden" name="moduleimpl_id" value="%(moduleimpl_id)s"/>
|
<input type="hidden" name="moduleimpl_id" value="%(moduleimpl_id)s"/>
|
||||||
|
@ -199,7 +198,7 @@ def moduleimpl_inscriptions_edit(
|
||||||
else: # SUBMISSION
|
else: # SUBMISSION
|
||||||
# inscrit a ce module tous les etuds selectionnes
|
# inscrit a ce module tous les etuds selectionnes
|
||||||
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
|
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
|
||||||
moduleimpl_id, formsemestre_id, etuds, reset=True, REQUEST=REQUEST
|
moduleimpl_id, formsemestre_id, etuds, reset=True
|
||||||
)
|
)
|
||||||
return flask.redirect("moduleimpl_status?moduleimpl_id=%s" % (moduleimpl_id))
|
return flask.redirect("moduleimpl_status?moduleimpl_id=%s" % (moduleimpl_id))
|
||||||
#
|
#
|
||||||
|
@ -230,7 +229,7 @@ def _make_menu(partitions, title="", check="true"):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_inscriptions_stats(formsemestre_id, REQUEST=None):
|
def moduleimpl_inscriptions_stats(formsemestre_id):
|
||||||
"""Affiche quelques informations sur les inscriptions
|
"""Affiche quelques informations sur les inscriptions
|
||||||
aux modules de ce semestre.
|
aux modules de ce semestre.
|
||||||
|
|
||||||
|
@ -250,7 +249,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id, REQUEST=None):
|
||||||
tous sauf <liste d'au plus 7 noms>
|
tous sauf <liste d'au plus 7 noms>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
authuser = REQUEST.AUTHENTICATED_USER
|
authuser = current_user
|
||||||
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
|
@ -264,9 +263,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id, REQUEST=None):
|
||||||
can_change = authuser.has_permission(Permission.ScoEtudInscrit) and sem["etat"]
|
can_change = authuser.has_permission(Permission.ScoEtudInscrit) and sem["etat"]
|
||||||
|
|
||||||
# Liste des modules
|
# Liste des modules
|
||||||
Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||||
formsemestre_id=formsemestre_id
|
|
||||||
)
|
|
||||||
# Decrit les inscriptions aux modules:
|
# Decrit les inscriptions aux modules:
|
||||||
commons = [] # modules communs a tous les etuds du semestre
|
commons = [] # modules communs a tous les etuds du semestre
|
||||||
options = [] # modules ou seuls quelques etudiants sont inscrits
|
options = [] # modules ou seuls quelques etudiants sont inscrits
|
||||||
|
@ -285,9 +282,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id, REQUEST=None):
|
||||||
mod["nb_inscrits"] = nb_inscrits
|
mod["nb_inscrits"] = nb_inscrits
|
||||||
options.append(mod)
|
options.append(mod)
|
||||||
# Page HTML:
|
# Page HTML:
|
||||||
H = [
|
H = [html_sco_header.html_sem_header("Inscriptions aux modules du semestre")]
|
||||||
html_sco_header.html_sem_header(REQUEST, "Inscriptions aux modules du semestre")
|
|
||||||
]
|
|
||||||
|
|
||||||
H.append("<h3>Inscrits au semestre: %d étudiants</h3>" % len(inscrits))
|
H.append("<h3>Inscrits au semestre: %d étudiants</h3>" % len(inscrits))
|
||||||
|
|
||||||
|
@ -344,7 +339,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id, REQUEST=None):
|
||||||
UECaps = get_etuds_with_capitalized_ue(formsemestre_id)
|
UECaps = get_etuds_with_capitalized_ue(formsemestre_id)
|
||||||
if UECaps:
|
if UECaps:
|
||||||
H.append('<h3>Etudiants avec UEs capitalisées:</h3><ul class="ue_inscr_list">')
|
H.append('<h3>Etudiants avec UEs capitalisées:</h3><ul class="ue_inscr_list">')
|
||||||
ues = [sco_edit_ue.do_ue_list({"ue_id": ue_id})[0] for ue_id in UECaps.keys()]
|
ues = [sco_edit_ue.ue_list({"ue_id": ue_id})[0] for ue_id in UECaps.keys()]
|
||||||
ues.sort(key=lambda u: u["numero"])
|
ues.sort(key=lambda u: u["numero"])
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -524,40 +519,39 @@ def is_inscrit_ue(etudid, formsemestre_id, ue_id):
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id, REQUEST=None):
|
def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
|
||||||
"""Desincrit l'etudiant de tous les modules de cette UE dans ce semestre."""
|
"""Desincrit l'etudiant de tous les modules de cette UE dans ce semestre."""
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""DELETE FROM notes_moduleimpl_inscription
|
"""DELETE FROM notes_moduleimpl_inscription
|
||||||
WHERE moduleimpl_inscription_id IN (
|
WHERE id IN (
|
||||||
SELECT i.moduleimpl_inscription_id FROM
|
SELECT i.id FROM
|
||||||
notes_moduleimpl mi, notes_modules mod,
|
notes_moduleimpl mi, notes_modules mod,
|
||||||
notes_formsemestre sem, notes_moduleimpl_inscription i
|
notes_formsemestre sem, notes_moduleimpl_inscription i
|
||||||
WHERE sem.formsemestre_id = %(formsemestre_id)s
|
WHERE sem.id = %(formsemestre_id)s
|
||||||
AND mi.formsemestre_id = sem.formsemestre_id
|
AND mi.formsemestre_id = sem.id
|
||||||
AND mod.module_id = mi.module_id
|
AND mod.id = mi.module_id
|
||||||
AND mod.ue_id = %(ue_id)s
|
AND mod.ue_id = %(ue_id)s
|
||||||
AND i.moduleimpl_id = mi.moduleimpl_id
|
AND i.moduleimpl_id = mi.id
|
||||||
AND i.etudid = %(etudid)s
|
AND i.etudid = %(etudid)s
|
||||||
)
|
)
|
||||||
""",
|
""",
|
||||||
{"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id},
|
{"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id},
|
||||||
)
|
)
|
||||||
if REQUEST:
|
logdb(
|
||||||
logdb(
|
cnx,
|
||||||
cnx,
|
method="etud_desinscrit_ue",
|
||||||
method="etud_desinscrit_ue",
|
etudid=etudid,
|
||||||
etudid=etudid,
|
msg="desinscription UE %s" % ue_id,
|
||||||
msg="desinscription UE %s" % ue_id,
|
commit=False,
|
||||||
commit=False,
|
)
|
||||||
)
|
|
||||||
sco_cache.invalidate_formsemestre(
|
sco_cache.invalidate_formsemestre(
|
||||||
formsemestre_id=formsemestre_id
|
formsemestre_id=formsemestre_id
|
||||||
) # > desinscription etudiant des modules
|
) # > desinscription etudiant des modules
|
||||||
|
|
||||||
|
|
||||||
def do_etud_inscrit_ue(etudid, formsemestre_id, ue_id, REQUEST=None):
|
def do_etud_inscrit_ue(etudid, formsemestre_id, ue_id):
|
||||||
"""Incrit l'etudiant de tous les modules de cette UE dans ce semestre."""
|
"""Incrit l'etudiant de tous les modules de cette UE dans ce semestre."""
|
||||||
# Verifie qu'il est bien inscrit au semestre
|
# Verifie qu'il est bien inscrit au semestre
|
||||||
insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"""Tableau de bord module
|
"""Tableau de bord module
|
||||||
"""
|
"""
|
||||||
import time
|
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 import g, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
@ -55,16 +55,16 @@ from app.scodoc import sco_users
|
||||||
# ported from old DTML code in oct 2009
|
# ported from old DTML code in oct 2009
|
||||||
|
|
||||||
# menu evaluation dans moduleimpl
|
# menu evaluation dans moduleimpl
|
||||||
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
|
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0):
|
||||||
"Menu avec actions sur une evaluation"
|
"Menu avec actions sur une evaluation"
|
||||||
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||||
modimpl = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
|
|
||||||
group_id = sco_groups.get_default_group(modimpl["formsemestre_id"])
|
group_id = sco_groups.get_default_group(modimpl["formsemestre_id"])
|
||||||
|
|
||||||
if (
|
if (
|
||||||
sco_permissions_check.can_edit_notes(
|
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
|
and nbnotes != 0
|
||||||
):
|
):
|
||||||
|
@ -80,7 +80,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
|
||||||
"evaluation_id": evaluation_id,
|
"evaluation_id": evaluation_id,
|
||||||
},
|
},
|
||||||
"enabled": sco_permissions_check.can_edit_notes(
|
"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,
|
"evaluation_id": evaluation_id,
|
||||||
},
|
},
|
||||||
"enabled": sco_permissions_check.can_edit_notes(
|
"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
|
"enabled": nbnotes == 0
|
||||||
and sco_permissions_check.can_edit_notes(
|
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,
|
"evaluation_id": evaluation_id,
|
||||||
},
|
},
|
||||||
"enabled": sco_permissions_check.can_edit_notes(
|
"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": {
|
"args": {
|
||||||
"evaluation_id": evaluation_id,
|
"evaluation_id": evaluation_id,
|
||||||
},
|
},
|
||||||
"enabled": nbnotes == 0
|
"enabled": sco_permissions_check.can_edit_notes(
|
||||||
and sco_permissions_check.can_edit_notes(
|
current_user, E["moduleimpl_id"]
|
||||||
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"]
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Absences ce jour",
|
"title": "Absences ce jour",
|
||||||
"endpoint": "absences.EtatAbsencesDate",
|
"endpoint": "absences.EtatAbsencesDate",
|
||||||
"args": {
|
"args": {
|
||||||
"date": six.moves.urllib.parse.quote(E["jour"], safe=""),
|
"date": E["jour"],
|
||||||
"group_ids": group_id,
|
"group_ids": group_id,
|
||||||
},
|
},
|
||||||
"enabled": E["jour"],
|
"enabled": E["jour"],
|
||||||
|
@ -155,11 +154,11 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
|
||||||
return htmlutils.make_menu("actions", menuEval, alone=True)
|
return htmlutils.make_menu("actions", menuEval, alone=True)
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_status(moduleimpl_id=None, partition_id=None, REQUEST=None):
|
def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||||
"""Tableau de bord module (liste des evaluations etc)"""
|
"""Tableau de bord module (liste des evaluations etc)"""
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
formsemestre_id = M["formsemestre_id"]
|
formsemestre_id = M["formsemestre_id"]
|
||||||
Mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
|
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||||
ModInscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
|
ModInscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
|
||||||
|
@ -177,7 +176,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None, REQUEST=None):
|
||||||
current_user, moduleimpl_id, allow_ens=sem["ens_can_edit_eval"]
|
current_user, moduleimpl_id, allow_ens=sem["ens_can_edit_eval"]
|
||||||
)
|
)
|
||||||
caneditnotes = sco_permissions_check.can_edit_notes(current_user, moduleimpl_id)
|
caneditnotes = sco_permissions_check.can_edit_notes(current_user, moduleimpl_id)
|
||||||
arrow_up, arrow_down, arrow_none = sco_groups.getArrowIconsTags()
|
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
|
||||||
#
|
#
|
||||||
module_resp = User.query.get(M["responsable_id"])
|
module_resp = User.query.get(M["responsable_id"])
|
||||||
H = [
|
H = [
|
||||||
|
@ -191,7 +190,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None, REQUEST=None):
|
||||||
f"""<span class="blacktt">({module_resp.user_name})</span>""",
|
f"""<span class="blacktt">({module_resp.user_name})</span>""",
|
||||||
]
|
]
|
||||||
try:
|
try:
|
||||||
sco_moduleimpl.can_change_module_resp(REQUEST, moduleimpl_id)
|
sco_moduleimpl.can_change_module_resp(moduleimpl_id)
|
||||||
H.append(
|
H.append(
|
||||||
"""<a class="stdlink" href="edit_moduleimpl_resp?moduleimpl_id=%s">modifier</a>"""
|
"""<a class="stdlink" href="edit_moduleimpl_resp?moduleimpl_id=%s">modifier</a>"""
|
||||||
% moduleimpl_id
|
% moduleimpl_id
|
||||||
|
@ -515,7 +514,6 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None, REQUEST=None):
|
||||||
moduleimpl_evaluation_menu(
|
moduleimpl_evaluation_menu(
|
||||||
eval["evaluation_id"],
|
eval["evaluation_id"],
|
||||||
nbnotes=etat["nb_notes"],
|
nbnotes=etat["nb_notes"],
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
H.append("</td>")
|
H.append("</td>")
|
||||||
|
|
|
@ -174,7 +174,7 @@ def _get_formsemestre_infos_from_news(n):
|
||||||
elif n["type"] == NEWS_NOTE:
|
elif n["type"] == NEWS_NOTE:
|
||||||
moduleimpl_id = n["object"]
|
moduleimpl_id = n["object"]
|
||||||
if n["object"]:
|
if n["object"]:
|
||||||
mods = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
||||||
if not mods:
|
if not mods:
|
||||||
return {} # module does not exists anymore
|
return {} # module does not exists anymore
|
||||||
return {} # pas d'indication du module
|
return {} # pas d'indication du module
|
||||||
|
|
|
@ -30,7 +30,8 @@
|
||||||
Fiche description d'un étudiant et de son parcours
|
Fiche description d'un étudiant et de son parcours
|
||||||
|
|
||||||
"""
|
"""
|
||||||
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
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
@ -46,6 +47,7 @@ from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_parcours_dut
|
from app.scodoc import sco_parcours_dut
|
||||||
from app.scodoc import sco_permissions_check
|
from app.scodoc import sco_permissions_check
|
||||||
from app.scodoc import sco_photos
|
from app.scodoc import sco_photos
|
||||||
|
from app.scodoc import sco_users
|
||||||
from app.scodoc import sco_report
|
from app.scodoc import sco_report
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc.sco_bulletins import etud_descr_situation_semestre
|
from app.scodoc.sco_bulletins import etud_descr_situation_semestre
|
||||||
|
@ -142,18 +144,22 @@ def _menuScolarite(authuser, sem, etudid):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def ficheEtud(etudid=None, REQUEST=None):
|
def ficheEtud(etudid=None):
|
||||||
"fiche d'informations sur un etudiant"
|
"fiche d'informations sur un etudiant"
|
||||||
authuser = REQUEST.AUTHENTICATED_USER
|
authuser = current_user
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
if etudid and REQUEST:
|
if etudid:
|
||||||
|
try: # pour les bookmarks avec d'anciens ids...
|
||||||
|
etudid = int(etudid)
|
||||||
|
except ValueError:
|
||||||
|
raise ScoValueError("id invalide !")
|
||||||
# la sidebar est differente s'il y a ou pas un etudid
|
# la sidebar est differente s'il y a ou pas un etudid
|
||||||
# voir html_sidebar.sidebar()
|
# voir html_sidebar.sidebar()
|
||||||
REQUEST.form["etudid"] = etudid
|
g.etudid = etudid
|
||||||
args = sco_etud.make_etud_args(etudid=etudid)
|
args = sco_etud.make_etud_args(etudid=etudid)
|
||||||
etuds = sco_etud.etudident_list(cnx, args)
|
etuds = sco_etud.etudident_list(cnx, args)
|
||||||
if not etuds:
|
if not etuds:
|
||||||
log("ficheEtud: etudid=%s REQUEST.form=%s" % (etudid, REQUEST.form))
|
log("ficheEtud: etudid=%s request.args=%s" % (etudid, request.args))
|
||||||
raise ScoValueError("Etudiant inexistant !")
|
raise ScoValueError("Etudiant inexistant !")
|
||||||
etud = etuds[0]
|
etud = etuds[0]
|
||||||
etudid = etud["etudid"]
|
etudid = etud["etudid"]
|
||||||
|
@ -167,7 +173,7 @@ def ficheEtud(etudid=None, REQUEST=None):
|
||||||
info["info_naissance"] += " à " + info["lieu_naissance"]
|
info["info_naissance"] += " à " + info["lieu_naissance"]
|
||||||
if info["dept_naissance"]:
|
if info["dept_naissance"]:
|
||||||
info["info_naissance"] += " (%s)" % 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 (
|
if (
|
||||||
(not info["domicile"])
|
(not info["domicile"])
|
||||||
and (not info["codepostaldomicile"])
|
and (not info["codepostaldomicile"])
|
||||||
|
@ -254,10 +260,19 @@ def ficheEtud(etudid=None, REQUEST=None):
|
||||||
with_all_columns=False,
|
with_all_columns=False,
|
||||||
a_url="Notes/",
|
a_url="Notes/",
|
||||||
)
|
)
|
||||||
info["link_bul_pdf"] = (
|
info[
|
||||||
'<span class="link_bul_pdf"><a class="stdlink" href="Notes/etud_bulletins_pdf?etudid=%(etudid)s">tous les bulletins</a></span>'
|
"link_bul_pdf"
|
||||||
% etud
|
] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{
|
||||||
)
|
url_for("notes.etud_bulletins_pdf", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
|
}">tous les bulletins</a></span>"""
|
||||||
|
if authuser.has_permission(Permission.ScoEtudInscrit):
|
||||||
|
info[
|
||||||
|
"link_inscrire_ailleurs"
|
||||||
|
] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{
|
||||||
|
url_for("notes.formsemestre_inscription_with_modules_form", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
|
}">inscrire à un autre semestre</a></span>"""
|
||||||
|
else:
|
||||||
|
info["link_inscrire_ailleurs"] = ""
|
||||||
else:
|
else:
|
||||||
# non inscrit
|
# non inscrit
|
||||||
l = ["<p><b>Etudiant%s non inscrit%s" % (info["ne"], info["ne"])]
|
l = ["<p><b>Etudiant%s non inscrit%s" % (info["ne"], info["ne"])]
|
||||||
|
@ -269,6 +284,7 @@ def ficheEtud(etudid=None, REQUEST=None):
|
||||||
l.append("</b></b>")
|
l.append("</b></b>")
|
||||||
info["liste_inscriptions"] = "\n".join(l)
|
info["liste_inscriptions"] = "\n".join(l)
|
||||||
info["link_bul_pdf"] = ""
|
info["link_bul_pdf"] = ""
|
||||||
|
info["link_inscrire_ailleurs"] = ""
|
||||||
|
|
||||||
# Liste des annotations
|
# Liste des annotations
|
||||||
alist = []
|
alist = []
|
||||||
|
@ -289,9 +305,11 @@ def ficheEtud(etudid=None, REQUEST=None):
|
||||||
title="Supprimer cette annotation",
|
title="Supprimer cette annotation",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
author = sco_users.user_info(a["author"])
|
||||||
alist.append(
|
alist.append(
|
||||||
'<tr><td><span class="annodate">Le %(date)s par %(author)s : </span><span class="annoc">%(comment)s</span></td>%(dellink)s</tr>'
|
f"""<tr><td><span class="annodate">Le {a['date']} par {author['prenomnom']} :
|
||||||
% a
|
</span><span class="annoc">{a['comment']}</span></td>{a['dellink']}</tr>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
info["liste_annotations"] = "\n".join(alist)
|
info["liste_annotations"] = "\n".join(alist)
|
||||||
# fiche admission
|
# fiche admission
|
||||||
|
@ -345,7 +363,7 @@ def ficheEtud(etudid=None, REQUEST=None):
|
||||||
# Fichiers archivés:
|
# Fichiers archivés:
|
||||||
info["fichiers_archive_htm"] = (
|
info["fichiers_archive_htm"] = (
|
||||||
'<div class="fichetitre">Fichiers associés</div>'
|
'<div class="fichetitre">Fichiers associés</div>'
|
||||||
+ sco_archives_etud.etud_list_archives_html(REQUEST, etudid)
|
+ sco_archives_etud.etud_list_archives_html(etudid)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Devenir de l'étudiant:
|
# Devenir de l'étudiant:
|
||||||
|
@ -392,10 +410,11 @@ def ficheEtud(etudid=None, REQUEST=None):
|
||||||
"inscriptions_mkup"
|
"inscriptions_mkup"
|
||||||
] = """<div class="ficheinscriptions" id="ficheinscriptions">
|
] = """<div class="ficheinscriptions" id="ficheinscriptions">
|
||||||
<div class="fichetitre">Parcours</div>%s
|
<div class="fichetitre">Parcours</div>%s
|
||||||
%s
|
%s %s
|
||||||
</div>""" % (
|
</div>""" % (
|
||||||
info["liste_inscriptions"],
|
info["liste_inscriptions"],
|
||||||
info["link_bul_pdf"],
|
info["link_bul_pdf"],
|
||||||
|
info["link_inscrire_ailleurs"],
|
||||||
)
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -405,7 +424,7 @@ def ficheEtud(etudid=None, REQUEST=None):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
info["groupes_row"] = ""
|
info["groupes_row"] = ""
|
||||||
info["menus_etud"] = menus_etud(REQUEST)
|
info["menus_etud"] = menus_etud(etudid)
|
||||||
tmpl = """<div class="menus_etud">%(menus_etud)s</div>
|
tmpl = """<div class="menus_etud">%(menus_etud)s</div>
|
||||||
<div class="ficheEtud" id="ficheEtud"><table>
|
<div class="ficheEtud" id="ficheEtud"><table>
|
||||||
<tr><td>
|
<tr><td>
|
||||||
|
@ -487,13 +506,11 @@ def ficheEtud(etudid=None, REQUEST=None):
|
||||||
return header + tmpl % info + html_sco_header.sco_footer()
|
return header + tmpl % info + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
def menus_etud(REQUEST=None):
|
def menus_etud(etudid):
|
||||||
"""Menu etudiant (operations sur l'etudiant)"""
|
"""Menu etudiant (operations sur l'etudiant)"""
|
||||||
if "etudid" not in REQUEST.form:
|
authuser = current_user
|
||||||
return ""
|
|
||||||
authuser = REQUEST.AUTHENTICATED_USER
|
|
||||||
|
|
||||||
etud = sco_etud.get_etud_info(filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
|
|
||||||
menuEtud = [
|
menuEtud = [
|
||||||
{
|
{
|
||||||
|
@ -532,16 +549,14 @@ def menus_etud(REQUEST=None):
|
||||||
return htmlutils.make_menu("Etudiant", menuEtud, alone=True)
|
return htmlutils.make_menu("Etudiant", menuEtud, alone=True)
|
||||||
|
|
||||||
|
|
||||||
def etud_info_html(etudid, with_photo="1", REQUEST=None, debug=False):
|
def etud_info_html(etudid, with_photo="1", debug=False):
|
||||||
"""An HTML div with basic information and links about this etud.
|
"""An HTML div with basic information and links about this etud.
|
||||||
Used for popups information windows.
|
Used for popups information windows.
|
||||||
"""
|
"""
|
||||||
formsemestre_id = sco_formsemestre_status.retreive_formsemestre_from_request()
|
formsemestre_id = sco_formsemestre_status.retreive_formsemestre_from_request()
|
||||||
with_photo = int(with_photo)
|
with_photo = int(with_photo)
|
||||||
etud = sco_etud.get_etud_info(filled=True)[0]
|
etud = sco_etud.get_etud_info(filled=True)[0]
|
||||||
photo_html = sco_photos.etud_photo_html(
|
photo_html = sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"])
|
||||||
etud, title="fiche de " + etud["nom"], REQUEST=REQUEST
|
|
||||||
)
|
|
||||||
# experimental: may be too slow to be here
|
# experimental: may be too slow to be here
|
||||||
etud["codeparcours"], etud["decisions_jury"] = sco_report.get_codeparcoursetud(
|
etud["codeparcours"], etud["decisions_jury"] = sco_report.get_codeparcoursetud(
|
||||||
etud, prefix="S", separator=", "
|
etud, prefix="S", separator=", "
|
||||||
|
|
|
@ -535,7 +535,7 @@ class SituationEtudParcoursGeneric(object):
|
||||||
validated = True
|
validated = True
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def valide_decision(self, decision, REQUEST):
|
def valide_decision(self, decision):
|
||||||
"""Enregistre la decision (instance de DecisionSem)
|
"""Enregistre la decision (instance de DecisionSem)
|
||||||
Enregistre codes semestre et UE, et autorisations inscription.
|
Enregistre codes semestre et UE, et autorisations inscription.
|
||||||
"""
|
"""
|
||||||
|
@ -588,7 +588,6 @@ class SituationEtudParcoursGeneric(object):
|
||||||
self.etudid,
|
self.etudid,
|
||||||
decision.code_etat,
|
decision.code_etat,
|
||||||
decision.assiduite,
|
decision.assiduite,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
# -- modification du code du semestre precedent
|
# -- modification du code du semestre precedent
|
||||||
if self.prev and decision.new_code_prev:
|
if self.prev and decision.new_code_prev:
|
||||||
|
@ -619,7 +618,6 @@ class SituationEtudParcoursGeneric(object):
|
||||||
self.etudid,
|
self.etudid,
|
||||||
decision.new_code_prev,
|
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...
|
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(
|
sco_cache.invalidate_formsemestre(
|
||||||
|
@ -897,9 +895,7 @@ def formsemestre_update_validation_sem(
|
||||||
return to_invalidate
|
return to_invalidate
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_validate_ues(
|
def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite):
|
||||||
formsemestre_id, etudid, code_etat_sem, assiduite, REQUEST=None
|
|
||||||
):
|
|
||||||
"""Enregistre codes UE, selon état semestre.
|
"""Enregistre codes UE, selon état semestre.
|
||||||
Les codes UE sont toujours calculés ici, et non passés en paramètres
|
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.
|
car ils ne dépendent que de la note d'UE et de la validation ou non du semestre.
|
||||||
|
@ -920,7 +916,7 @@ def formsemestre_validate_ues(
|
||||||
and ue_status["moy"] >= nt.parcours.NOTES_BARRE_VALID_UE
|
and ue_status["moy"] >= nt.parcours.NOTES_BARRE_VALID_UE
|
||||||
):
|
):
|
||||||
code_ue = ADM
|
code_ue = ADM
|
||||||
elif isinstance(ue_status["moy"], float):
|
elif not isinstance(ue_status["moy"], float):
|
||||||
# aucune note (pas de moyenne) dans l'UE: ne la valide pas
|
# aucune note (pas de moyenne) dans l'UE: ne la valide pas
|
||||||
code_ue = None
|
code_ue = None
|
||||||
elif valid_semestre:
|
elif valid_semestre:
|
||||||
|
@ -933,14 +929,13 @@ def formsemestre_validate_ues(
|
||||||
cnx, nt, formsemestre_id, etudid, ue_id, code_ue
|
cnx, nt, formsemestre_id, etudid, ue_id, code_ue
|
||||||
)
|
)
|
||||||
|
|
||||||
if REQUEST:
|
logdb(
|
||||||
logdb(
|
cnx,
|
||||||
cnx,
|
method="validate_ue",
|
||||||
method="validate_ue",
|
etudid=etudid,
|
||||||
etudid=etudid,
|
msg="ue_id=%s code=%s" % (ue_id, code_ue),
|
||||||
msg="ue_id=%s code=%s" % (ue_id, code_ue),
|
commit=False,
|
||||||
commit=False,
|
)
|
||||||
)
|
|
||||||
cnx.commit()
|
cnx.commit()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -60,13 +60,10 @@ from reportlab.lib.pagesizes import letter, A4, landscape
|
||||||
from flask import g
|
from flask import g
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import (
|
from app.scodoc.sco_logos import find_logo
|
||||||
CONFIG,
|
from app.scodoc.sco_utils import CONFIG
|
||||||
SCODOC_LOGOS_DIR,
|
|
||||||
LOGOS_IMAGES_ALLOWED_TYPES,
|
|
||||||
)
|
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_exceptions import ScoGenError
|
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||||
import sco_version
|
import sco_version
|
||||||
|
|
||||||
PAGE_HEIGHT = defaultPageSize[1]
|
PAGE_HEIGHT = defaultPageSize[1]
|
||||||
|
@ -121,6 +118,7 @@ def makeParas(txt, style, suppress_empty=False):
|
||||||
"""Returns a list of Paragraph instances from a text
|
"""Returns a list of Paragraph instances from a text
|
||||||
with one or more <para> ... </para>
|
with one or more <para> ... </para>
|
||||||
"""
|
"""
|
||||||
|
result = []
|
||||||
try:
|
try:
|
||||||
paras = _splitPara(txt)
|
paras = _splitPara(txt)
|
||||||
if suppress_empty:
|
if suppress_empty:
|
||||||
|
@ -133,21 +131,30 @@ def makeParas(txt, style, suppress_empty=False):
|
||||||
if m.group(1): # non empty paragraph
|
if m.group(1): # non empty paragraph
|
||||||
r.append(para)
|
r.append(para)
|
||||||
paras = r
|
paras = r
|
||||||
return [Paragraph(SU(s), style) for s in paras]
|
result = [Paragraph(SU(s), style) for s in paras]
|
||||||
|
except OSError as e:
|
||||||
|
msg = str(e)
|
||||||
|
# If a file is missing, try to display the invalid name
|
||||||
|
m = re.match(r".*\sfilename=\'(.*?)\'.*", msg, re.DOTALL)
|
||||||
|
if m:
|
||||||
|
filename = os.path.split(m.group(1))[1]
|
||||||
|
if filename.startswith("logo_"):
|
||||||
|
filename = filename[len("logo_") :]
|
||||||
|
raise ScoValueError(
|
||||||
|
f"Erreur dans le format PDF paramétré: fichier logo <b>{filename}</b> non trouvé"
|
||||||
|
) from e
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
detail = " " + str(e)
|
|
||||||
log(traceback.format_exc())
|
log(traceback.format_exc())
|
||||||
log("Invalid pdf para format: %s" % txt)
|
log("Invalid pdf para format: %s" % txt)
|
||||||
return [
|
result = [
|
||||||
Paragraph(
|
Paragraph(
|
||||||
SU(
|
SU('<font color="red"><i>Erreur: format invalide</i></font>'),
|
||||||
'<font color="red"><i>Erreur: format invalide{}</i></font>'.format(
|
|
||||||
detail
|
|
||||||
)
|
|
||||||
),
|
|
||||||
style,
|
style,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def bold_paras(L, tag="b", close=None):
|
def bold_paras(L, tag="b", close=None):
|
||||||
|
@ -209,20 +216,16 @@ class ScolarsPageTemplate(PageTemplate):
|
||||||
)
|
)
|
||||||
PageTemplate.__init__(self, "ScolarsPageTemplate", [content])
|
PageTemplate.__init__(self, "ScolarsPageTemplate", [content])
|
||||||
self.logo = None
|
self.logo = None
|
||||||
# XXX COPIED from sco_pvpdf, to be refactored (no time now)
|
logo = find_logo(
|
||||||
# Search background in dept specific dir, then in global config dir
|
logoname="bul_pdf_background", dept_id=g.scodoc_dept_id, prefix=None
|
||||||
for image_dir in (
|
)
|
||||||
SCODOC_LOGOS_DIR + "/logos_" + g.scodoc_dept + "/",
|
if logo is None:
|
||||||
SCODOC_LOGOS_DIR + "/", # global logos
|
# Also try to use PV background
|
||||||
):
|
logo = find_logo(
|
||||||
for suffix in LOGOS_IMAGES_ALLOWED_TYPES:
|
logoname="letter_background", dept_id=g.scodoc_dept_id, prefix=None
|
||||||
fn = image_dir + "/bul_pdf_background" + "." + suffix
|
)
|
||||||
if not self.background_image_filename and os.path.exists(fn):
|
if logo is not None:
|
||||||
self.background_image_filename = fn
|
self.background_image_filename = logo.filepath
|
||||||
# Also try to use PV background
|
|
||||||
fn = image_dir + "/letter_background" + "." + suffix
|
|
||||||
if not self.background_image_filename and os.path.exists(fn):
|
|
||||||
self.background_image_filename = fn
|
|
||||||
|
|
||||||
def beforeDrawPage(self, canvas, doc):
|
def beforeDrawPage(self, canvas, doc):
|
||||||
"""Draws (optional) background, logo and contribution message on each page.
|
"""Draws (optional) background, logo and contribution message on each page.
|
||||||
|
@ -340,7 +343,6 @@ def pdf_basic_page(
|
||||||
|
|
||||||
|
|
||||||
# Gestion du lock pdf
|
# Gestion du lock pdf
|
||||||
import threading, time, six.moves.queue, six.moves._thread
|
|
||||||
|
|
||||||
|
|
||||||
class PDFLock(object):
|
class PDFLock(object):
|
||||||
|
|
|
@ -26,7 +26,7 @@ def can_edit_notes(authuser, moduleimpl_id, allow_ens=True):
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_parcours_dut
|
from app.scodoc import sco_parcours_dut
|
||||||
|
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||||
if not sem["etat"]:
|
if not sem["etat"]:
|
||||||
return False # semestre verrouillé
|
return False # semestre verrouillé
|
||||||
|
@ -64,7 +64,7 @@ def can_edit_evaluation(moduleimpl_id=None):
|
||||||
# acces pour resp. moduleimpl et resp. form semestre (dir etud)
|
# acces pour resp. moduleimpl et resp. form semestre (dir etud)
|
||||||
if moduleimpl_id is None:
|
if moduleimpl_id is None:
|
||||||
raise ValueError("no moduleimpl specified") # bug
|
raise ValueError("no moduleimpl specified") # bug
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -200,4 +200,4 @@ def can_handle_passwd(user, allow_admindepts=False):
|
||||||
if (current_user.dept == user.dept) or allow_admindepts:
|
if (current_user.dept == user.dept) or allow_admindepts:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -43,6 +43,8 @@ Les images sont servies par ScoDoc, via la méthode getphotofile?etudid=xxx
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from flask.helpers import make_response
|
||||||
|
from app.scodoc.sco_exceptions import ScoGenError
|
||||||
import datetime
|
import datetime
|
||||||
import glob
|
import glob
|
||||||
import io
|
import io
|
||||||
|
@ -52,6 +54,7 @@ import requests
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
import PIL
|
||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
|
|
||||||
from flask import request, g
|
from flask import request, g
|
||||||
|
@ -118,7 +121,7 @@ def etud_photo_url(etud, size="small", fast=False):
|
||||||
return photo_url
|
return photo_url
|
||||||
|
|
||||||
|
|
||||||
def get_photo_image(etudid=None, size="small", REQUEST=None):
|
def get_photo_image(etudid=None, size="small"):
|
||||||
"""Returns photo image (HTTP response)
|
"""Returns photo image (HTTP response)
|
||||||
If not etudid, use "unknown" image
|
If not etudid, use "unknown" image
|
||||||
"""
|
"""
|
||||||
|
@ -129,24 +132,14 @@ def get_photo_image(etudid=None, size="small", REQUEST=None):
|
||||||
filename = photo_pathname(etud, size=size)
|
filename = photo_pathname(etud, size=size)
|
||||||
if not filename:
|
if not filename:
|
||||||
filename = UNKNOWN_IMAGE_PATH
|
filename = UNKNOWN_IMAGE_PATH
|
||||||
return _http_jpeg_file(filename, REQUEST=REQUEST)
|
return _http_jpeg_file(filename)
|
||||||
|
|
||||||
|
|
||||||
def _http_jpeg_file(filename, REQUEST=None):
|
def _http_jpeg_file(filename):
|
||||||
"""returns an image.
|
"""returns an image as a Flask response"""
|
||||||
This function will be modified when we kill #zope
|
|
||||||
"""
|
|
||||||
st = os.stat(filename)
|
st = os.stat(filename)
|
||||||
last_modified = st.st_mtime # float timestamp
|
last_modified = st.st_mtime # float timestamp
|
||||||
last_modified_str = time.strftime(
|
|
||||||
"%a, %d %b %Y %H:%M:%S GMT", time.gmtime(last_modified)
|
|
||||||
)
|
|
||||||
file_size = st.st_size
|
file_size = st.st_size
|
||||||
RESPONSE = REQUEST.RESPONSE
|
|
||||||
RESPONSE.setHeader("Content-Type", "image/jpeg")
|
|
||||||
RESPONSE.setHeader("Last-Modified", last_modified_str)
|
|
||||||
RESPONSE.setHeader("Cache-Control", "max-age=3600")
|
|
||||||
RESPONSE.setHeader("Content-Length", str(file_size))
|
|
||||||
header = request.headers.get("If-Modified-Since")
|
header = request.headers.get("If-Modified-Since")
|
||||||
if header is not None:
|
if header is not None:
|
||||||
header = header.split(";")[0]
|
header = header.split(";")[0]
|
||||||
|
@ -159,20 +152,27 @@ def _http_jpeg_file(filename, REQUEST=None):
|
||||||
try:
|
try:
|
||||||
dt = datetime.datetime.strptime(header, "%a, %d %b %Y %H:%M:%S GMT")
|
dt = datetime.datetime.strptime(header, "%a, %d %b %Y %H:%M:%S GMT")
|
||||||
mod_since = dt.timestamp()
|
mod_since = dt.timestamp()
|
||||||
except:
|
except ValueError:
|
||||||
mod_since = None
|
mod_since = None
|
||||||
if (mod_since is not None) and last_modified <= mod_since:
|
if (mod_since is not None) and last_modified <= mod_since:
|
||||||
RESPONSE.setStatus(304) # not modified
|
return "", 304 # not modified
|
||||||
return ""
|
#
|
||||||
|
last_modified_str = time.strftime(
|
||||||
return open(filename, mode="rb").read()
|
"%a, %d %b %Y %H:%M:%S GMT", time.gmtime(last_modified)
|
||||||
|
)
|
||||||
|
response = make_response(open(filename, mode="rb").read())
|
||||||
|
response.headers["Content-Type"] = "image/jpeg"
|
||||||
|
response.headers["Last-Modified"] = last_modified_str
|
||||||
|
response.headers["Cache-Control"] = "max-age=3600"
|
||||||
|
response.headers["Content-Length"] = str(file_size)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def etud_photo_is_local(etud, size="small"):
|
def etud_photo_is_local(etud, size="small"):
|
||||||
return photo_pathname(etud, size=size)
|
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)
|
"""HTML img tag for the photo, either in small size (h90)
|
||||||
or original size (size=="orig")
|
or original size (size=="orig")
|
||||||
"""
|
"""
|
||||||
|
@ -204,14 +204,12 @@ def etud_photo_html(etud=None, etudid=None, title=None, size="small", REQUEST=No
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def etud_photo_orig_html(etud=None, etudid=None, title=None, REQUEST=None):
|
def etud_photo_orig_html(etud=None, etudid=None, title=None):
|
||||||
"""HTML img tag for the photo, in full size.
|
"""HTML img tag for the photo, in full size.
|
||||||
Full-size images are always stored locally in the filesystem.
|
Full-size images are always stored locally in the filesystem.
|
||||||
They are the original uploaded images, converted in jpeg.
|
They are the original uploaded images, converted in jpeg.
|
||||||
"""
|
"""
|
||||||
return etud_photo_html(
|
return etud_photo_html(etud=etud, etudid=etudid, title=title, size="orig")
|
||||||
etud=etud, etudid=etudid, title=title, size="orig", REQUEST=REQUEST
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def photo_pathname(etud, size="orig"):
|
def photo_pathname(etud, size="orig"):
|
||||||
|
@ -246,7 +244,10 @@ def store_photo(etud, data):
|
||||||
filesize = len(data)
|
filesize = len(data)
|
||||||
if filesize < 10 or filesize > MAX_FILE_SIZE:
|
if filesize < 10 or filesize > MAX_FILE_SIZE:
|
||||||
return 0, "Fichier image de taille invalide ! (%d)" % filesize
|
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:
|
# update database:
|
||||||
etud["photo_filename"] = filename
|
etud["photo_filename"] = filename
|
||||||
etud["foto"] = None
|
etud["foto"] = None
|
||||||
|
@ -260,7 +261,7 @@ def store_photo(etud, data):
|
||||||
return 1, "ok"
|
return 1, "ok"
|
||||||
|
|
||||||
|
|
||||||
def suppress_photo(etud, REQUEST=None):
|
def suppress_photo(etud):
|
||||||
"""Suppress a photo"""
|
"""Suppress a photo"""
|
||||||
log("suppress_photo etudid=%s" % etud["etudid"])
|
log("suppress_photo etudid=%s" % etud["etudid"])
|
||||||
rel_path = photo_pathname(etud)
|
rel_path = photo_pathname(etud)
|
||||||
|
@ -278,8 +279,7 @@ def suppress_photo(etud, REQUEST=None):
|
||||||
log("removing file %s" % filename)
|
log("removing file %s" % filename)
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
# 3- log
|
# 3- log
|
||||||
if REQUEST:
|
logdb(cnx, method="changePhoto", msg="suppression", etudid=etud["etudid"])
|
||||||
logdb(cnx, method="changePhoto", msg="suppression", etudid=etud["etudid"])
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
@ -298,6 +298,7 @@ def save_image(etudid, data):
|
||||||
filename = get_new_filename(etudid)
|
filename = get_new_filename(etudid)
|
||||||
path = os.path.join(PHOTO_DIR, filename)
|
path = os.path.join(PHOTO_DIR, filename)
|
||||||
log("saving %dx%d jpeg to %s" % (img.size[0], img.size[1], path))
|
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)
|
img.save(path + IMAGE_EXT, format="JPEG", quality=92)
|
||||||
# resize:
|
# resize:
|
||||||
img = scale_height(img)
|
img = scale_height(img)
|
||||||
|
@ -341,7 +342,7 @@ def find_new_dir():
|
||||||
|
|
||||||
def copy_portal_photo_to_fs(etud):
|
def copy_portal_photo_to_fs(etud):
|
||||||
"""Copy the photo from portal (distant website) to local fs.
|
"""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)
|
sco_etud.format_etud_ident(etud)
|
||||||
url = photo_portal_url(etud)
|
url = photo_portal_url(etud)
|
||||||
|
@ -353,11 +354,12 @@ def copy_portal_photo_to_fs(etud):
|
||||||
log("copy_portal_photo_to_fs: getting %s" % url)
|
log("copy_portal_photo_to_fs: getting %s" % url)
|
||||||
r = requests.get(url, timeout=portal_timeout)
|
r = requests.get(url, timeout=portal_timeout)
|
||||||
except:
|
except:
|
||||||
log("download failed: exception:\n%s" % traceback.format_exc())
|
# log("download failed: exception:\n%s" % traceback.format_exc())
|
||||||
log("called from:\n" + "".join(traceback.format_stack()))
|
# 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)
|
return None, "%s: erreur chargement de %s" % (etud["nomprenom"], url)
|
||||||
if r.status_code != 200:
|
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)
|
return None, "%s: erreur chargement de %s" % (etud["nomprenom"], url)
|
||||||
data = r.content # image bytes
|
data = r.content # image bytes
|
||||||
try:
|
try:
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -39,7 +39,6 @@ import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
import six
|
|
||||||
|
|
||||||
SCO_CACHE_ETAPE_FILENAME = os.path.join(scu.SCO_TMP_DIR, "last_etapes.xml")
|
SCO_CACHE_ETAPE_FILENAME = os.path.join(scu.SCO_TMP_DIR, "last_etapes.xml")
|
||||||
|
|
||||||
|
@ -386,7 +385,8 @@ def get_etapes_apogee():
|
||||||
# cache le resultat (utile si le portail repond de façon intermitente)
|
# cache le resultat (utile si le portail repond de façon intermitente)
|
||||||
if infos:
|
if infos:
|
||||||
log("get_etapes_apogee: caching result")
|
log("get_etapes_apogee: caching result")
|
||||||
open(SCO_CACHE_ETAPE_FILENAME, "w").write(doc)
|
with open(SCO_CACHE_ETAPE_FILENAME, "w") as f:
|
||||||
|
f.write(doc)
|
||||||
except:
|
except:
|
||||||
log("invalid XML response from getEtapes Web Service\n%s" % etapes_url)
|
log("invalid XML response from getEtapes Web Service\n%s" % etapes_url)
|
||||||
# Avons nous la copie d'une réponse récente ?
|
# Avons nous la copie d'une réponse récente ?
|
||||||
|
|
|
@ -31,7 +31,7 @@ Recapitule tous les semestres validés dans une feuille excel.
|
||||||
"""
|
"""
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
from flask import url_for, g
|
from flask import url_for, g, request
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_abs
|
||||||
|
@ -164,7 +164,7 @@ def _getEtudInfoGroupes(group_ids, etat=None):
|
||||||
return etuds
|
return etuds
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_poursuite_report(formsemestre_id, format="html", REQUEST=None):
|
def formsemestre_poursuite_report(formsemestre_id, format="html"):
|
||||||
"""Table avec informations "poursuite" """
|
"""Table avec informations "poursuite" """
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
etuds = _getEtudInfoGroupes([sco_groups.get_default_group(formsemestre_id)])
|
etuds = _getEtudInfoGroupes([sco_groups.get_default_group(formsemestre_id)])
|
||||||
|
@ -211,12 +211,11 @@ def formsemestre_poursuite_report(formsemestre_id, format="html", REQUEST=None):
|
||||||
)
|
)
|
||||||
tab.caption = "Récapitulatif %s." % sem["titreannee"]
|
tab.caption = "Récapitulatif %s." % sem["titreannee"]
|
||||||
tab.html_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(
|
return tab.make_page(
|
||||||
title="""<h2 class="formsemestre">Poursuite d'études</h2>""",
|
title="""<h2 class="formsemestre">Poursuite d'études</h2>""",
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
javascripts=["js/etud_info.js"],
|
javascripts=["js/etud_info.js"],
|
||||||
format=format,
|
format=format,
|
||||||
REQUEST=REQUEST,
|
|
||||||
with_html_headers=True,
|
with_html_headers=True,
|
||||||
)
|
)
|
||||||
|
|
|
@ -77,7 +77,7 @@ sinon, elle ne concerne que le semestre indiqué.
|
||||||
- avoir un mapping (read only) de toutes les valeurs:
|
- avoir un mapping (read only) de toutes les valeurs:
|
||||||
sco_preferences.SemPreferences(formsemestre_id)
|
sco_preferences.SemPreferences(formsemestre_id)
|
||||||
- editer les preferences globales:
|
- editer les preferences globales:
|
||||||
sco_preferences.get_base_preferences(self).edit(REQUEST=REQUEST)
|
sco_preferences.get_base_preferences(self).edit()
|
||||||
- editer les preferences d'un semestre:
|
- editer les preferences d'un semestre:
|
||||||
SemPreferences(formsemestre_id).edit()
|
SemPreferences(formsemestre_id).edit()
|
||||||
|
|
||||||
|
@ -111,7 +111,8 @@ get_base_preferences(formsemestre_id)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import flask
|
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.models import Departement
|
||||||
from app.scodoc import sco_cache
|
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):
|
def _get_pref_default_value_from_config(name, pref_spec):
|
||||||
"""get default value store in application level config.
|
"""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
|
# XXX va changer avec la nouvelle base
|
||||||
# search in scu.CONFIG
|
# search in scu.CONFIG
|
||||||
|
@ -1408,7 +1409,7 @@ class BasePreferences(object):
|
||||||
{
|
{
|
||||||
"initvalue": 1,
|
"initvalue": 1,
|
||||||
"title": "Indique si les bulletins sont publiés",
|
"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",
|
"input_type": "boolcheckbox",
|
||||||
"labels": ["non", "oui"],
|
"labels": ["non", "oui"],
|
||||||
"category": "bul",
|
"category": "bul",
|
||||||
|
@ -1891,23 +1892,11 @@ class BasePreferences(object):
|
||||||
|
|
||||||
def get(self, formsemestre_id, name):
|
def get(self, formsemestre_id, name):
|
||||||
"""Returns preference value.
|
"""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 = {
|
if formsemestre_id in self.prefs:
|
||||||
"dept_id": self.dept_id,
|
return self.prefs[formsemestre_id].get(name, self.prefs[None][name])
|
||||||
"name": name,
|
return self.prefs[None][name]
|
||||||
"formsemestre_id": formsemestre_id,
|
|
||||||
}
|
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
plist = self._editor.list(cnx, params)
|
|
||||||
if not plist:
|
|
||||||
del params["formsemestre_id"]
|
|
||||||
plist = self._editor.list(cnx, params)
|
|
||||||
if not plist:
|
|
||||||
return self.default[name]
|
|
||||||
p = plist[0]
|
|
||||||
_convert_pref_type(p, self.prefs_dict[name])
|
|
||||||
return p["value"]
|
|
||||||
|
|
||||||
def __contains__(self, item):
|
def __contains__(self, item):
|
||||||
return item in self.prefs[None]
|
return item in self.prefs[None]
|
||||||
|
@ -1948,6 +1937,15 @@ class BasePreferences(object):
|
||||||
"name": name,
|
"name": name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
if len(pdb) > 1:
|
||||||
|
# suppress buggy duplicates (may come from corrupted database for ice ages)
|
||||||
|
log(
|
||||||
|
f"**oups** detected duplicated preference !\n({self.dept_id}, {formsemestre_id}, {name}, {value})"
|
||||||
|
)
|
||||||
|
for obj in pdb[1:]:
|
||||||
|
self._editor.delete(cnx, obj["id"])
|
||||||
|
pdb = [pdb[0]]
|
||||||
|
|
||||||
if not pdb:
|
if not pdb:
|
||||||
# crée préférence
|
# crée préférence
|
||||||
log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
|
log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
|
||||||
|
@ -1961,10 +1959,8 @@ class BasePreferences(object):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
modif = True
|
modif = True
|
||||||
log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
|
|
||||||
else:
|
else:
|
||||||
# edit existing value
|
# edit existing value
|
||||||
|
|
||||||
existing_value = pdb[0]["value"] # old stored value
|
existing_value = pdb[0]["value"] # old stored value
|
||||||
if (
|
if (
|
||||||
(existing_value != value)
|
(existing_value != value)
|
||||||
|
@ -2013,7 +2009,7 @@ class BasePreferences(object):
|
||||||
self._editor.delete(cnx, pdb[0]["pref_id"])
|
self._editor.delete(cnx, pdb[0]["pref_id"])
|
||||||
sco_cache.invalidate_formsemestre() # > modif preferences
|
sco_cache.invalidate_formsemestre() # > modif preferences
|
||||||
|
|
||||||
def edit(self, REQUEST):
|
def edit(self):
|
||||||
"""HTML dialog: edit global preferences"""
|
"""HTML dialog: edit global preferences"""
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
|
|
||||||
|
@ -2021,16 +2017,18 @@ class BasePreferences(object):
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Préférences"),
|
html_sco_header.sco_header(page_title="Préférences"),
|
||||||
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
|
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
|
||||||
f"""<p><a href="{url_for("scolar.config_logos", scodoc_dept=g.scodoc_dept)
|
# 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="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>
|
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
|
||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
form = self.build_tf_form()
|
form = self.build_tf_form()
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
form,
|
form,
|
||||||
initvalues=self.prefs[None],
|
initvalues=self.prefs[None],
|
||||||
submitlabel="Enregistrer les modifications",
|
submitlabel="Enregistrer les modifications",
|
||||||
|
@ -2140,7 +2138,7 @@ class SemPreferences(object):
|
||||||
return self.base_prefs.is_global(self.formsemestre_id, name)
|
return self.base_prefs.is_global(self.formsemestre_id, name)
|
||||||
|
|
||||||
# The dialog
|
# The dialog
|
||||||
def edit(self, categories=[], REQUEST=None):
|
def edit(self, categories=[]):
|
||||||
"""Dialog to edit semestre preferences in given categories"""
|
"""Dialog to edit semestre preferences in given categories"""
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
|
@ -2151,7 +2149,7 @@ class SemPreferences(object):
|
||||||
) # a bug !
|
) # a bug !
|
||||||
sem = sco_formsemestre.get_formsemestre(self.formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(self.formsemestre_id)
|
||||||
H = [
|
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="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>
|
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
|
||||||
|
@ -2194,8 +2192,8 @@ function set_global_pref(el, pref_name) {
|
||||||
form.append(("destination", {"input_type": "hidden"}))
|
form.append(("destination", {"input_type": "hidden"}))
|
||||||
form.append(("formsemestre_id", {"input_type": "hidden"}))
|
form.append(("formsemestre_id", {"input_type": "hidden"}))
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
REQUEST.URL0,
|
request.base_url,
|
||||||
REQUEST.form,
|
scu.get_request_args(),
|
||||||
form,
|
form,
|
||||||
initvalues=self,
|
initvalues=self,
|
||||||
cssclass="sco_pref",
|
cssclass="sco_pref",
|
||||||
|
@ -2245,7 +2243,7 @@ function set_global_pref(el, pref_name) {
|
||||||
return flask.redirect(dest_url + "&head_message=Préférences modifiées")
|
return flask.redirect(dest_url + "&head_message=Préférences modifiées")
|
||||||
elif destination == "again":
|
elif destination == "again":
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
REQUEST.URL0 + "?formsemestre_id=" + str(self.formsemestre_id)
|
request.base_url + "?formsemestre_id=" + str(self.formsemestre_id)
|
||||||
)
|
)
|
||||||
elif destination == "global":
|
elif destination == "global":
|
||||||
return flask.redirect(scu.ScoURL() + "/edit_preferences")
|
return flask.redirect(scu.ScoURL() + "/edit_preferences")
|
||||||
|
@ -2253,7 +2251,7 @@ function set_global_pref(el, pref_name) {
|
||||||
|
|
||||||
#
|
#
|
||||||
def doc_preferences():
|
def doc_preferences():
|
||||||
""" Liste les preferences en MarkDown, pour la documentation"""
|
"""Liste les preferences en MarkDown, pour la documentation"""
|
||||||
L = []
|
L = []
|
||||||
for cat, cat_descr in PREF_CATEGORIES:
|
for cat, cat_descr in PREF_CATEGORIES:
|
||||||
L.append([""])
|
L.append([""])
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user