From 4e2fab29d5faf9bee558030dc411c7b9371aa734 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Sun, 5 Sep 2021 18:14:16 +0200
Subject: [PATCH 01/25] fix typo role RespPe
---
tools/import_scodoc7_user_db.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/import_scodoc7_user_db.py b/tools/import_scodoc7_user_db.py
index 1cd7774f..199f23e2 100644
--- a/tools/import_scodoc7_user_db.py
+++ b/tools/import_scodoc7_user_db.py
@@ -59,7 +59,7 @@ def import_scodoc7_user_db(scodoc7_db="dbname=SCOUSERS"):
roles7 = []
for role_dept in roles7:
# Migre les rôles RespPeX, EnsX, AdminX, SecrX et ignore les autres
- m = re.match(r"^(-?Ens|-?Secr|-?ResPe|-?Admin)(.*)$", role_dept)
+ m = re.match(r"^(-?Ens|-?Secr|-?RespPe|-?Admin)(.*)$", role_dept)
if not m:
msg = f"User {user_name}: role inconnu '{role_dept}' (ignoré)"
current_app.logger.warning(msg)
@@ -75,7 +75,7 @@ def import_scodoc7_user_db(scodoc7_db="dbname=SCOUSERS"):
dept = m.group(2)
role = Role.query.filter_by(name=role_name).first()
if not role:
- msg = f"User {user_name}: ignoring role '{role_dept}'"
+ msg = f"Role '{role_name}' introuvable. User {user_name}: ignoring role '{role_dept}'"
current_app.logger.warning(msg)
messages.append(msg)
else:
From 0bc8138c72d0e7ca1ac3b0e33c7ecd1865214e96 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Mon, 6 Sep 2021 08:25:06 +0200
Subject: [PATCH 02/25] version bump
---
sco_version.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sco_version.py b/sco_version.py
index 5fa98d28..d2735ed1 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.0.8"
+SCOVERSION = "9.0.9"
SCONAME = "ScoDoc"
From ee0961d247c2d8a061ca2f92e74ac0dd0a272503 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Mon, 6 Sep 2021 21:32:56 +0200
Subject: [PATCH 03/25] Debian desinstall (purge) script
---
tools/debian/postrm | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100755 tools/debian/postrm
diff --git a/tools/debian/postrm b/tools/debian/postrm
new file mode 100755
index 00000000..a01b1a38
--- /dev/null
+++ b/tools/debian/postrm
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# Déinstallation de scodoc
+# Ne touche pas aux données (/opt/scodoc-data)
+# N'enlève complètement /opt/scodoc qui si --purge
+
+systemctl stop scodoc9
+systemctl disable scodoc9
+
+if [ "$#" == 1 ] && [ "$1" == "purge" ]
+then
+ /bin/rm -rf /opt/scodoc
+ /bin/rm -f scodoc9.service
+ /bin/rm -f /etc/systemd/system/scodoc-updater.service
+ /bin/rm -f /etc/systemd/system/scodoc-updater.timer
+ /bin/rm -f /etc/nginx/sites-enabled/scodoc9.nginx
+fi
+
+systemctl reload nginx
From 04d5dd2ad72202e468719e10387707eaa9808c72 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Mon, 6 Sep 2021 21:35:17 +0200
Subject: [PATCH 04/25] better help messages
---
tools/configure-scodoc9.sh | 9 +++++----
tools/debian/postinst | 2 +-
tools/debian/preinst | 0
3 files changed, 6 insertions(+), 5 deletions(-)
mode change 100644 => 100755 tools/debian/postinst
mode change 100644 => 100755 tools/debian/preinst
diff --git a/tools/configure-scodoc9.sh b/tools/configure-scodoc9.sh
index 1c914830..b1c18ec5 100755
--- a/tools/configure-scodoc9.sh
+++ b/tools/configure-scodoc9.sh
@@ -108,8 +108,7 @@ change_scodoc_file_ownership
# ------------ CREATION BASE DE DONNEES
echo
echo "Voulez-vous créer la base SQL SCODOC ?"
-echo "répondre oui sauf si vous avez déjà une base existante"
-echo "que vous souhaitez conserver (mais pour les migrations, répondre oui)."
+echo "(répondre oui sauf si vous savez vraiment ce que vous faites)"
echo -n 'Créer la base de données SCODOC ? (y/n) [y] '
read -r ans
if [ "$(norm_ans "$ans")" != 'N' ]
@@ -121,9 +120,10 @@ then
echo
echo "Création des tables et du compte admin"
echo
- su -c "(cd /opt/scodoc; source venv/bin/activate; flask db upgrade; flask sco-db-init; flask user-password admin)" "$SCODOC_USER" || die "Erreur: sco-db-init"
+ msg="Saisir le mot de passe de l\'administrateur \(admin\):"
+ su -c "(cd /opt/scodoc; source venv/bin/activate; flask db upgrade; flask sco-db-init; echo; echo $msg; flask user-password admin)" "$SCODOC_USER" || die "Erreur: sco-db-init"
echo
- echo "base initialisée et admin créé."
+ echo "Base initialisée et admin créé."
echo
fi
@@ -134,6 +134,7 @@ systemctl start scodoc9
echo
echo "Service configuré et démarré."
+echo "Vous pouvez vous connecter en web et vous identifier comme \"admin\"."
echo
diff --git a/tools/debian/postinst b/tools/debian/postinst
old mode 100644
new mode 100755
index 73167c38..412c298a
--- a/tools/debian/postinst
+++ b/tools/debian/postinst
@@ -29,7 +29,7 @@ do
/usr/sbin/locale-gen --keep-existing
fi
done
-echo "debian postinst: scodoc9 is $(systemctl is-active scodoc9)"
+echo "debian postinst: scodoc9 systemd service is $(systemctl is-active scodoc9)"
# On a besoin d'un postgresql lancé pour la mise à jour
systemctl restart postgresql
diff --git a/tools/debian/preinst b/tools/debian/preinst
old mode 100644
new mode 100755
From af77a2a389e3d459489ada56cbbc597cc39c9b7d Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Mon, 6 Sep 2021 21:38:40 +0200
Subject: [PATCH 05/25] removed obsolete test-interactive command
---
scodoc.py | 18 ------------------
1 file changed, 18 deletions(-)
diff --git a/scodoc.py b/scodoc.py
index fcefe749..3a085227 100755
--- a/scodoc.py
+++ b/scodoc.py
@@ -188,24 +188,6 @@ def create_dept(dept): # create-dept
return 0
-@app.cli.command()
-@click.argument("filename")
-@with_appcontext
-def test_interactive(filename=None):
- "Run interactive test"
- import flask_login
- from app import decorators
-
- click.echo("Executing {}".format(filename))
- with app.test_request_context(""):
- u = User.query.first()
- flask_login.login_user(u)
- REQUEST = decorators.ZRequest()
- exec(open(filename).read())
-
- click.echo("Done.")
-
-
@app.cli.command()
@with_appcontext
def import_scodoc7_users(): # import-scodoc7-users
From 998f28d4a4771f26749255d8766a2cded2058277 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Tue, 7 Sep 2021 22:03:33 +0200
Subject: [PATCH 06/25] =?UTF-8?q?Modifie=20l'impl=C3=A9mentation=20des=20p?=
=?UTF-8?q?r=C3=A9f=C3=A9rences=20pour=20ScoDoc=209?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/scodoc/sco_preferences.py | 444 +++++++++++++++++---------------
tests/unit/sco_fake_gen.py | 10 +-
tests/unit/test_departements.py | 3 +
3 files changed, 243 insertions(+), 214 deletions(-)
diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index b8c4eafa..f4615313 100644
--- a/app/scodoc/sco_preferences.py
+++ b/app/scodoc/sco_preferences.py
@@ -147,6 +147,52 @@ def get_preference(name, formsemestre_id=None):
return get_base_preferences().get(formsemestre_id, name)
+def _convert_pref_type(p, pref_spec):
+ """p est une ligne de la bd
+ {'id': , 'dept_id': , 'name': '', 'value': '', 'formsemestre_id': }
+ converti la valeur chane en le type désiré spécifié par pref_spec
+ """
+ if "type" in pref_spec:
+ typ = pref_spec["type"]
+ if typ == "float":
+ # special case for float values (where NULL means 0)
+ if p["value"]:
+ p["value"] = float(p["value"])
+ else:
+ p["value"] = 0.0
+ else:
+ func = eval(typ)
+ p["value"] = func(p["value"])
+ if pref_spec.get("input_type", None) == "boolcheckbox":
+ # boolcheckbox: la valeur stockée en base est une chaine "0" ou "1"
+ # que l'on ressort en True|False
+ if p["value"]:
+ try:
+ p["value"] = bool(int(p["value"]))
+ except ValueError:
+ log(
+ f"""Warning: invalid value for boolean pref in db: '{p["value"]}'"""
+ )
+ p["value"] = False
+ else:
+ p["value"] = False # NULL (backward compat)
+
+
+def _get_pref_default_value_from_config(name, pref_spec):
+ """get default value store in application level config.
+ If not found, use defalut value hardcoded in pref_spec.
+ """
+ # XXX va changer avec la nouvelle base
+ # search in scu.CONFIG
+ if hasattr(scu.CONFIG, name):
+ value = getattr(scu.CONFIG, name)
+ log("sco_preferences: found default value in config for %s=%s" % (name, value))
+ else:
+ # uses hardcoded default
+ value = pref_spec["initvalue"]
+ return value
+
+
PREF_CATEGORIES = (
# sur page "Paramètres"
("general", {}),
@@ -469,21 +515,27 @@ class BasePreferences(object):
"abs_notification_mail_tmpl",
{
"initvalue": """
- --- Ceci est un message de notification automatique issu de ScoDoc ---
+ --- Ceci est un message de notification automatique issu de ScoDoc ---
+ L'étudiant %(nomprenom)s
L'étudiant %(nomprenom)s
+ L'étudiant %(nomprenom)s
+ inscrit en %(inscription)s)
inscrit en %(inscription)s)
+ inscrit en %(inscription)s)
+ a cumulé %(nbabsjust)s absences justifiées
a cumulé %(nbabsjust)s absences justifiées
- et %(nbabsnonjust)s absences NON justifiées.
+ a cumulé %(nbabsjust)s absences justifiées
+ et %(nbabsnonjust)s absences NON justifiées.
- Le compte a pu changer depuis cet envoi, voir la fiche sur %(url_ficheetud)s.
+ Le compte a pu changer depuis cet envoi, voir la fiche sur %(url_ficheetud)s.
- Votre dévoué serveur ScoDoc.
+ Votre dévoué serveur ScoDoc.
- PS: Au dela de %(abs_notify_abs_threshold)s, un email automatique est adressé toutes les %(abs_notify_abs_increment)s absences. Ces valeurs sont modifiables dans les préférences de ScoDoc.
- """,
+ PS: Au dela de %(abs_notify_abs_threshold)s, un email automatique est adressé toutes les %(abs_notify_abs_increment)s absences. Ces valeurs sont modifiables dans les préférences de ScoDoc.
+ """,
"title": """Message notification e-mail""",
"explanation": """Balises remplacées, voir la documentation""",
"input_type": "textarea",
@@ -826,14 +878,18 @@ class BasePreferences(object):
"PV_INTRO",
{
"initvalue": """-
- Vu l'arrêté du 3 août 2005 relatif au diplôme universitaire de technologie et notamment son article 4 et 6;
-
+ Vu l'arrêté du 3 août 2005 relatif au diplôme universitaire de technologie et notamment son article 4 et 6;
+
+ -
-
- vu l'arrêté n° %(Decnum)s du Président de l'%(UnivName)s;
-
+ -
+ vu l'arrêté n° %(Decnum)s du Président de l'%(UnivName)s;
+
+ -
-
- vu la délibération de la commission %(Type)s en date du %(Date)s présidée par le Chef du département;
- """,
+ -
+ vu la délibération de la commission %(Type)s en date du %(Date)s présidée par le Chef du département;
+ """,
"title": """Paragraphe d'introduction sur le PV""",
"explanation": """Balises remplacées: %(Univname)s = nom de l'université, %(DecNum)s = numéro de l'arrêté, %(Date)s = date de la commission, %(Type)s = type de commission (passage ou délivrance), %(VDICode)s = code diplôme""",
"input_type": "textarea",
@@ -940,8 +996,8 @@ class BasePreferences(object):
"PV_LETTER_PASSAGE_SIGNATURE",
{
"initvalue": """Pour le Directeur de l'IUT
- et par délégation
- Le Chef du département""",
+ et par délégation
+ Le Chef du département""",
"title": """Signature des lettres individuelles de passage d'un semestre à l'autre""",
"explanation": """%(DirectorName)s et %(DirectorTitle)s remplacés""",
"input_type": "textarea",
@@ -965,43 +1021,45 @@ class BasePreferences(object):
"PV_LETTER_TEMPLATE",
{
"initvalue": """
- %(INSTITUTION_CITY)s, le %(date_jury)s
-
+ %(INSTITUTION_CITY)s, le %(date_jury)s
+
-
- à %(nomprenom)s
-
- %(domicile)s
- %(codepostaldomicile)s %(villedomicile)s
+
+ à %(nomprenom)s
+
+ %(domicile)s
+ %(codepostaldomicile)s %(villedomicile)s
-
- Jury de %(type_jury)s
%(titre_formation)s
-
+
+ Jury de %(type_jury)s
%(titre_formation)s
+
-
- Le jury de %(type_jury_abbrv)s du département %(DeptName)s
+
+ Le jury de %(type_jury_abbrv)s du département %(DeptName)s
+ s'est réuni le %(date_jury)s.
s'est réuni le %(date_jury)s.
-
- Les décisions vous concernant sont :
-
+ s'est réuni le %(date_jury)s.
+
+ Les décisions vous concernant sont :
+
- %(prev_decision_sem_txt)s
-
- Décision %(decision_orig)s : %(decision_sem_descr)s
-
+ %(prev_decision_sem_txt)s
+
+ Décision %(decision_orig)s : %(decision_sem_descr)s
+
-
- %(decision_ue_txt)s
-
+
+ %(decision_ue_txt)s
+
-
- %(observation_txt)s
-
+
+ %(observation_txt)s
+
- %(autorisations_txt)s
+ %(autorisations_txt)s
- %(diplome_txt)s
- """,
+ %(diplome_txt)s
+ """,
"title": """Lettre individuelle""",
"explanation": """Balises remplacées et balisage XML, voir la documentation""",
"input_type": "textarea",
@@ -1362,24 +1420,24 @@ class BasePreferences(object):
"bul_pdf_title",
{
"initvalue": """
- %(UnivName)s
-
-
- %(InstituteName)s
-
-
- RELEVÉ DE NOTES
-
+ %(UnivName)s
+
+
+ %(InstituteName)s
+
+
+ RELEVÉ DE NOTES
+
-
- %(nomprenom)s %(demission)s
-
+
+ %(nomprenom)s %(demission)s
+
-
- Formation: %(titre_num)s
-
- Année scolaire: %(anneescolaire)s
- """,
+
+ Formation: %(titre_num)s
+
+ Année scolaire: %(anneescolaire)s
+ """,
"title": "Bulletins PDF: paragraphe de titre",
"explanation": "(balises interprétées, voir documentation)",
"input_type": "textarea",
@@ -1404,10 +1462,10 @@ class BasePreferences(object):
"bul_pdf_sig_left",
{
"initvalue": """La direction des études
-
- %(responsable)s
-
- """,
+
+ %(responsable)s
+
+ """,
"title": "Bulletins PDF: signature gauche",
"explanation": "(balises interprétées, voir documentation)",
"input_type": "textarea",
@@ -1420,10 +1478,10 @@ class BasePreferences(object):
"bul_pdf_sig_right",
{
"initvalue": """Le chef de département
-
- %(ChiefDeptName)s
-
- """,
+
+ %(ChiefDeptName)s
+
+ """,
"title": "Bulletins PDF: signature droite",
"explanation": "(balises interprétées, voir documentation)",
"input_type": "textarea",
@@ -1799,88 +1857,57 @@ class BasePreferences(object):
def load(self):
"""Load all preferences from db"""
log(f"loading preferences for dept_id={self.dept_id}")
- try:
- scu.GSL.acquire()
- cnx = ndb.GetDBConnexion()
- preflist = self._editor.list(cnx, {"dept_id": self.dept_id})
- self.prefs = {None: {}} # { formsemestre_id (or None) : { name : value } }
- self.default = {} # { name : default_value }
- for p in preflist:
- if not p["formsemestre_id"] in self.prefs:
- self.prefs[p["formsemestre_id"]] = {}
- # Ignore les noms de préférences non utilisés dans le code:
- if p["name"] not in self.prefs_dict:
- continue
- # Convert types:
- if (
- p["name"] in self.prefs_dict
- and "type" in self.prefs_dict[p["name"]]
- ):
- typ = self.prefs_dict[p["name"]]["type"]
- if typ == "float":
- # special case for float values (where NULL means 0)
- if p["value"]:
- p["value"] = float(p["value"])
- else:
- p["value"] = 0.0
- else:
- func = eval(typ)
- p["value"] = func(p["value"])
- if (
- p["name"] in self.prefs_dict
- and self.prefs_dict[p["name"]].get("input_type", None)
- == "boolcheckbox"
- ):
- # boolcheckbox: la valeur stockée en base est une chaine "0" ou "1"
- # que l'on ressort en True|False
- if p["value"]:
- try:
- p["value"] = bool(int(p["value"]))
- except ValueError:
- log(
- f"""Warning: invalid value for boolean pref in db: '{p["value"]}'"""
- )
- p["value"] = False
- else:
- p["value"] = False # NULL (backward compat)
- self.prefs[p["formsemestre_id"]][p["name"]] = p["value"]
- # add defaults for missing prefs
- for pref in self.prefs_definition:
- name = pref[0]
- # search preferences in configuration file
- if name and name[0] != "_" and name not in self.prefs[None]:
- # search in scu.CONFIG
- if hasattr(scu.CONFIG, name):
- value = getattr(scu.CONFIG, name)
- log(
- "sco_preferences: found default value in config for %s=%s"
- % (name, value)
- )
- else:
- # uses hardcoded default
- value = pref[1]["initvalue"]
+ cnx = ndb.GetDBConnexion()
+ preflist = self._editor.list(cnx, {"dept_id": self.dept_id})
+ self.prefs = {None: {}} # { formsemestre_id (or None) : { name : value } }
+ self.default = {} # { name : default_value }
+ for p in preflist:
+ if not p["formsemestre_id"] in self.prefs:
+ self.prefs[p["formsemestre_id"]] = {}
+ # Ignore les noms de préférences non utilisés dans le code:
+ if p["name"] not in self.prefs_dict:
+ continue
- self.default[name] = value
- self.prefs[None][name] = value
- log("creating missing preference for %s=%s" % (name, value))
- # add to db table
- self._editor.create(
- cnx, {"dept_id": self.dept_id, "name": name, "value": value}
- )
- finally:
- scu.GSL.release()
+ # Convert types:
+ if p["name"] in self.prefs_dict:
+ _convert_pref_type(p, self.prefs_dict[p["name"]])
+
+ self.prefs[p["formsemestre_id"]][p["name"]] = p["value"]
+
+ # add defaults for missing prefs
+ for pref in self.prefs_definition:
+ name = pref[0]
+ # search preferences in configuration file
+ if name and name[0] != "_" and name not in self.prefs[None]:
+ value = _get_pref_default_value_from_config(name, pref[1])
+ self.default[name] = value
+ self.prefs[None][name] = value
+ log("creating missing preference for %s=%s" % (name, value))
+ # add to db table
+ self._editor.create(
+ cnx, {"dept_id": self.dept_id, "name": name, "value": value}
+ )
def get(self, formsemestre_id, name):
"""Returns preference value.
- If no value defined for this semestre, returns global value.
+ If global_lookup, when no value defined for this semestre, returns global value.
"""
- if formsemestre_id in self.prefs and name in self.prefs[formsemestre_id]:
- return self.prefs[formsemestre_id][name]
- elif name in self.prefs[None]:
- return self.prefs[None][name]
- else:
- return self.default[name]
+ params = {
+ "dept_id": self.dept_id,
+ "name": 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):
return item in self.prefs[None]
@@ -1890,74 +1917,75 @@ class BasePreferences(object):
def is_global(self, formsemestre_id, name):
"True if name if not defined for semestre"
- if (
- not (formsemestre_id in self.prefs)
- or not name in self.prefs[formsemestre_id]
- ):
- return True
- else:
- return False
+ params = {
+ "dept_id": self.dept_id,
+ "name": name,
+ "formsemestre_id": formsemestre_id,
+ }
+ cnx = ndb.GetDBConnexion()
+ plist = self._editor.list(cnx, params)
+ return len(plist) == 0
def save(self, formsemestre_id=None, name=None):
"""Write one or all (if name is None) values to db"""
- try:
- scu.GSL.acquire()
- modif = False
- cnx = ndb.GetDBConnexion()
- if name is None:
- names = list(self.prefs[formsemestre_id].keys())
- else:
- names = [name]
- for name in names:
- value = self.get(formsemestre_id, name)
- if self.prefs_dict[name].get("input_type", None) == "boolcheckbox":
- # repasse les booleens en chaines "0":"1"
- value = "1" if value else "0"
- # existe deja ?
- pdb = self._editor.list(
+ modif = False
+ cnx = ndb.GetDBConnexion()
+ if name is None:
+ names = list(self.prefs[formsemestre_id].keys())
+ else:
+ names = [name]
+ for name in names:
+ value = self.prefs[formsemestre_id][name]
+ if self.prefs_dict[name].get("input_type", None) == "boolcheckbox":
+ # repasse les booleens en chaines "0":"1"
+ value = "1" if value else "0"
+ # existe deja ?
+ pdb = self._editor.list(
+ cnx,
+ args={
+ "dept_id": self.dept_id,
+ "formsemestre_id": formsemestre_id,
+ "name": name,
+ },
+ )
+ if not pdb:
+ # crée préférence
+ log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
+ self._editor.create(
cnx,
- args={
+ {
"dept_id": self.dept_id,
- "formsemestre_id": formsemestre_id,
"name": name,
+ "value": value,
+ "formsemestre_id": formsemestre_id,
},
)
- if not pdb:
- # crée préférence
- log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
- self._editor.create(
+ modif = True
+ log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
+ else:
+ # edit existing value
+
+ existing_value = pdb[0]["value"] # old stored value
+ if (
+ (existing_value != value)
+ and (existing_value != str(value))
+ and (existing_value or str(value))
+ ):
+ self._editor.edit(
cnx,
{
- "dept_id": self.dept_id,
+ "pref_id": pdb[0]["pref_id"],
+ "formsemestre_id": formsemestre_id,
"name": name,
"value": value,
- "formsemestre_id": formsemestre_id,
},
)
modif = True
- log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
- else:
- # edit existing value
- if pdb[0]["value"] != str(value) and (
- pdb[0]["value"] or str(value)
- ):
- self._editor.edit(
- cnx,
- {
- "pref_id": pdb[0]["pref_id"],
- "formsemestre_id": formsemestre_id,
- "name": name,
- "value": value,
- },
- )
- modif = True
- log("save pref sem=%s %s=%s" % (formsemestre_id, name, value))
+ log("save pref sem=%s %s=%s" % (formsemestre_id, name, value))
- # les preferences peuvent affecter les PDF cachés et les notes calculées:
- if modif:
- sco_cache.invalidate_formsemestre()
- finally:
- scu.GSL.release()
+ # les preferences peuvent affecter les PDF cachés et les notes calculées:
+ if modif:
+ sco_cache.invalidate_formsemestre()
def set(self, formsemestre_id, name, value):
if not name or name[0] == "_" or name not in self.prefs_name:
@@ -1972,26 +2000,24 @@ class BasePreferences(object):
def delete(self, formsemestre_id, name):
if not formsemestre_id:
raise ScoException()
- try:
- scu.GSL.acquire()
- if formsemestre_id in self.prefs and name in self.prefs[formsemestre_id]:
- del self.prefs[formsemestre_id][name]
- cnx = ndb.GetDBConnexion()
- pdb = self._editor.list(
- cnx, args={"formsemestre_id": formsemestre_id, "name": name}
- )
- if pdb:
- log("deleting pref sem=%s %s" % (formsemestre_id, name))
- assert pdb[0]["dept_id"] == self.dept_id
- self._editor.delete(cnx, pdb[0]["pref_id"])
- sco_cache.invalidate_formsemestre() # > modif preferences
- finally:
- scu.GSL.release()
+
+ if formsemestre_id in self.prefs and name in self.prefs[formsemestre_id]:
+ del self.prefs[formsemestre_id][name]
+ cnx = ndb.GetDBConnexion()
+ pdb = self._editor.list(
+ cnx, args={"formsemestre_id": formsemestre_id, "name": name}
+ )
+ if pdb:
+ log("deleting pref sem=%s %s" % (formsemestre_id, name))
+ assert pdb[0]["dept_id"] == self.dept_id
+ self._editor.delete(cnx, pdb[0]["pref_id"])
+ sco_cache.invalidate_formsemestre() # > modif preferences
def edit(self, REQUEST):
"""HTML dialog: edit global preferences"""
from app.scodoc import html_sco_header
+ self.load()
H = [
html_sco_header.sco_header(page_title="Préférences"),
"Préférences globales pour %s
" % scu.ScoURL(),
diff --git a/tests/unit/sco_fake_gen.py b/tests/unit/sco_fake_gen.py
index cd5bddde..c2df6738 100644
--- a/tests/unit/sco_fake_gen.py
+++ b/tests/unit/sco_fake_gen.py
@@ -40,11 +40,11 @@ from app.scodoc.sco_exceptions import ScoValueError
random.seed(12345) # tests reproductibles
-DEMO_DIR = Config.SCODOC_DIR + "/tools/demo/"
-NOMS = [x.strip() for x in open(DEMO_DIR + "/noms.txt").readlines()]
-PRENOMS_H = [x.strip() for x in open(DEMO_DIR + "/prenoms-h.txt").readlines()]
-PRENOMS_F = [x.strip() for x in open(DEMO_DIR + "/prenoms-f.txt").readlines()]
-PRENOMS_X = [x.strip() for x in open(DEMO_DIR + "/prenoms-x.txt").readlines()]
+NOMS_DIR = Config.SCODOC_DIR + "/tools/fakeportal/nomsprenoms"
+NOMS = [x.strip() for x in open(NOMS_DIR + "/noms.txt").readlines()]
+PRENOMS_H = [x.strip() for x in open(NOMS_DIR + "/prenoms-h.txt").readlines()]
+PRENOMS_F = [x.strip() for x in open(NOMS_DIR + "/prenoms-f.txt").readlines()]
+PRENOMS_X = [x.strip() for x in open(NOMS_DIR + "/prenoms-x.txt").readlines()]
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
diff --git a/tests/unit/test_departements.py b/tests/unit/test_departements.py
index 83a6650b..e8ca47f6 100644
--- a/tests/unit/test_departements.py
+++ b/tests/unit/test_departements.py
@@ -73,6 +73,8 @@ def test_preferences(test_client):
assert len(prefs2) == len(prefs)
prefs2.set(None, "abs_notification_mail_tmpl", "toto")
assert prefs2.get(None, "abs_notification_mail_tmpl") == "toto"
+ # Vérifie que les prefs sont bien sur un seul département:
+ app.set_sco_dept(current_dept.acronym)
assert prefs.get(None, "abs_notification_mail_tmpl") != "toto"
orm_val = (
ScoPreference.query.filter_by(dept_id=d.id, name="abs_notification_mail_tmpl")
@@ -82,6 +84,7 @@ def test_preferences(test_client):
assert orm_val == "toto"
# --- Preferences d'un semestre
# rejoue ce test pour avoir un semestre créé
+ app.set_sco_dept("D2")
test_sco_basic.run_sco_basic()
sem = sco_formsemestre.do_formsemestre_list()[0]
formsemestre_id = sem["formsemestre_id"]
From 97fe4cc73f5d912db92fea3bb7eee33ea9718f03 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Tue, 7 Sep 2021 23:54:33 +0200
Subject: [PATCH 07/25] =?UTF-8?q?Ne=20quote=20plus=20par=20d=C3=A9faut=20l?=
=?UTF-8?q?e=20HTML=20des=20chaines=20entrants=20en=20base?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/scodoc/notesdb.py | 8 +++++---
app/scodoc/sco_moduleimpl.py | 1 -
app/scodoc/sco_pdf.py | 7 ++++++-
tools/fakeportal/fakeportal.py | 2 +-
4 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/app/scodoc/notesdb.py b/app/scodoc/notesdb.py
index 135675cc..99c4ca9c 100644
--- a/app/scodoc/notesdb.py
+++ b/app/scodoc/notesdb.py
@@ -287,7 +287,7 @@ class EditableTable(object):
input_formators={},
aux_tables=[],
convert_null_outputs_to_empty=True,
- html_quote=True,
+ html_quote=False, # changed in 9.0.10
fields_creators={}, # { field : [ sql_command_to_create_it ] }
filter_nulls=True, # dont allow to set fields to null
filter_dept=False, # ajoute selection sur g.scodoc_dept_id
@@ -321,8 +321,10 @@ class EditableTable(object):
del vals["id"]
if self.filter_dept:
vals["dept_id"] = g.scodoc_dept_id
- if self.html_quote:
- quote_dict(vals) # quote all HTML markup
+ if (
+ self.html_quote
+ ): # quote all HTML markup (une bien mauvaise idée venue des ages obscurs)
+ quote_dict(vals)
# format value
for title in vals:
if title in self.input_formators:
diff --git a/app/scodoc/sco_moduleimpl.py b/app/scodoc/sco_moduleimpl.py
index fbb4b350..a5db24a2 100644
--- a/app/scodoc/sco_moduleimpl.py
+++ b/app/scodoc/sco_moduleimpl.py
@@ -221,7 +221,6 @@ _moduleimpl_inscriptionEditor = ndb.EditableTable(
def do_moduleimpl_inscription_create(args, formsemestre_id=None):
"create a moduleimpl_inscription"
cnx = ndb.GetDBConnexion()
- log("do_moduleimpl_inscription_create: " + str(args))
r = _moduleimpl_inscriptionEditor.create(cnx, args)
sco_cache.invalidate_formsemestre(
formsemestre_id=formsemestre_id
diff --git a/app/scodoc/sco_pdf.py b/app/scodoc/sco_pdf.py
index 1f496463..2a97e716 100755
--- a/app/scodoc/sco_pdf.py
+++ b/app/scodoc/sco_pdf.py
@@ -33,6 +33,7 @@
En ScoDoc 9, ce n'est pas nécessaire car on est multiptocessus / monothread.
"""
+import html
import io
import os
import queue
@@ -85,7 +86,11 @@ def SU(s):
# car les "combining accents" ne sont pas traités par ReportLab mais peuvent
# nous être envoyés par certains navigateurs ou imports
# (on en a dans les bases de données)
- return unicodedata.normalize("NFC", s)
+ s = unicodedata.normalize("NFC", s)
+ # Remplace les entité XML/HTML
+ # reportlab ne les supporte pas non plus.
+ s = html.unescape(s)
+ return s
def _splitPara(txt):
diff --git a/tools/fakeportal/fakeportal.py b/tools/fakeportal/fakeportal.py
index 9fdbcd01..a732d3d7 100755
--- a/tools/fakeportal/fakeportal.py
+++ b/tools/fakeportal/fakeportal.py
@@ -100,7 +100,7 @@ class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
if "etapes" in self.path.lower():
self.path = str(Path(script_dir / "etapes.xml").relative_to(Path.cwd()))
- elif "scodocEtudiant" in self.path:
+ elif "scodocEtudiant" in self.path: # API v2
# 2 forms: nip=xxx or etape=eee&annee=aaa
if "nip" in query_components:
nip = query_components["nip"][0]
From 2f78f7f6fc81e260ed75607b981a5e2c8bfc0847 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Wed, 8 Sep 2021 00:10:36 +0200
Subject: [PATCH 08/25] Message erreur si import Excel d'une date invalide
---
app/scodoc/sco_excel.py | 47 +++-------------------------------
app/scodoc/sco_import_etuds.py | 8 ++++--
2 files changed, 9 insertions(+), 46 deletions(-)
diff --git a/app/scodoc/sco_excel.py b/app/scodoc/sco_excel.py
index 32cf27c0..c9551b69 100644
--- a/app/scodoc/sco_excel.py
+++ b/app/scodoc/sco_excel.py
@@ -79,52 +79,11 @@ def send_excel_file(request, data, filename, mime=scu.XLSX_MIMETYPE):
# font, border, number_format, fill, .. (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
-# (stolen from xlrd)
-# Convert an Excel number (presumed to represent a date, a datetime or a time) into
-# a Python datetime.datetime
-# @param xldate The Excel number
-# @param datemode 0: 1900-based, 1: 1904-based.
-# @return a datetime.datetime object, to the nearest_second.
-#
Special case: if 0.0 <= xldate < 1.0, it is assumed to represent a time;
-# a datetime.time object will be returned.
-#
Note: 1904-01-01 is not regarded as a valid date in the datemode 1 system; its "serial number"
-# is zero.
-#
-# _XLDAYS_TOO_LARGE = (2958466, 2958466 - 1462) # This is equivalent to 10000-01-01
-#
-
-
def xldate_as_datetime(xldate, datemode=0):
+ """Conversion d'une date Excel en date
+ Peut lever une ValueError
+ """
return openpyxl.utils.datetime.from_ISO8601(xldate)
- # if datemode not in (0, 1):
- # raise ValueError("invalid mode %s" % datemode)
- # if xldate == 0.00:
- # return datetime.time(0, 0, 0)
- # if xldate < 0.00:
- # raise ValueError("invalid date code %s" % xldate)
- # xldays = int(xldate)
- # frac = xldate - xldays
- # seconds = int(round(frac * 86400.0))
- # assert 0 <= seconds <= 86400
- # if seconds == 86400:
- # seconds = 0
- # xldays += 1
- # if xldays >= _XLDAYS_TOO_LARGE[datemode]:
- # raise ValueError("date too large %s" % xldate)
- #
- # if xldays == 0:
- # # second = seconds % 60; minutes = seconds // 60
- # minutes, second = divmod(seconds, 60)
- # # minute = minutes % 60; hour = minutes // 60
- # hour, minute = divmod(minutes, 60)
- # return datetime.time(hour, minute, second)
- #
- # if xldays < 61 and datemode == 0:
- # raise ValueError("ambiguous date %s" % xldate)
- #
- # return datetime.datetime.fromordinal(
- # xldays + 693594 + 1462 * datemode
- # ) + datetime.timedelta(seconds=seconds)
class ScoExcelBook:
diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py
index 5e5016bc..b9b8b5de 100644
--- a/app/scodoc/sco_import_etuds.py
+++ b/app/scodoc/sco_import_etuds.py
@@ -378,8 +378,12 @@ def scolars_import_excel_file(
# Excel date conversion:
if titleslist[i].lower() == "date_naissance":
if val:
- # if re.match(r"^[0-9]*\.?[0-9]*$", str(val)):
- val = sco_excel.xldate_as_datetime(val)
+ try:
+ val = sco_excel.xldate_as_datetime(val)
+ except ValueError:
+ raise ScoValueError(
+ f"date invalide ({val}) sur ligne {linenum}, colonne {titleslist[i]}"
+ )
# INE
if (
titleslist[i].lower() == "code_ine"
From 19f6053dda39b08ea1113fe7c05e1360ca8418fe Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Wed, 8 Sep 2021 00:11:11 +0200
Subject: [PATCH 09/25] =?UTF-8?q?Fix:=20tri=20de=20liste=20h=C3=A9t=C3=A9r?=
=?UTF-8?q?og=C3=A8ne?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/scodoc/pe_tagtable.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/app/scodoc/pe_tagtable.py b/app/scodoc/pe_tagtable.py
index dbc6bf03..e32a1173 100644
--- a/app/scodoc/pe_tagtable.py
+++ b/app/scodoc/pe_tagtable.py
@@ -163,7 +163,7 @@ class TableTag(object):
# *****************************************************************************************************************
# -----------------------------------------------------------------------------------------------------------
- def add_moyennesTag(self, tag, listMoyEtCoeff):
+ def add_moyennesTag(self, tag, listMoyEtCoeff) -> bool:
"""
Mémorise les moyennes, les coeffs de pondération et les etudid dans resultats
avec calcul du rang
@@ -181,7 +181,9 @@ class TableTag(object):
lesMoyennesTriees = sorted(
listMoyEtCoeff,
reverse=True,
- key=lambda col: col[0] or 0, # remplace les None par des zéros
+ key=lambda col: col[0]
+ if isinstance(col[0], float)
+ else 0, # remplace les None et autres chaines par des zéros
) # triées
self.rangs[tag] = notes_table.comp_ranks(lesMoyennesTriees) # les rangs
From 5ab0dec6af0a509d01b670f39ea1dc879b1c7894 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Wed, 8 Sep 2021 00:13:41 +0200
Subject: [PATCH 10/25] =?UTF-8?q?Timeout=20gunicorn=20pass=C3=A9=20=C3=A0?=
=?UTF-8?q?=20600s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
tools/etc/scodoc9.service | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/etc/scodoc9.service b/tools/etc/scodoc9.service
index 6d0f223e..8028442c 100644
--- a/tools/etc/scodoc9.service
+++ b/tools/etc/scodoc9.service
@@ -20,7 +20,7 @@ User=scodoc
Group=scodoc
WorkingDirectory=/opt/scodoc
#Environment=FLASK_ENV=production
-ExecStart=/opt/scodoc/venv/bin/gunicorn -b localhost:8000 -w 4 scodoc:app
+ExecStart=/opt/scodoc/venv/bin/gunicorn -b localhost:8000 -w 4 --timeout 600 scodoc:app
Restart=always
[Install]
From 7f92a21b53c4daf17e4a19570714a3504feae0a4 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Wed, 8 Sep 2021 00:34:45 +0200
Subject: [PATCH 11/25] =?UTF-8?q?Fixes:=20trombino=5Fcopy=5Fphotos,=20impo?=
=?UTF-8?q?rt=20fichiers=20associ=C3=A9s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/scodoc/sco_archives.py | 4 ++--
app/scodoc/sco_groups_view.py | 10 ++++------
app/scodoc/sco_import_etuds.py | 3 +--
app/scodoc/sco_pvjury.py | 4 ++--
app/scodoc/sco_saisie_notes.py | 3 ---
app/scodoc/sco_trombino.py | 9 ++++-----
app/scodoc/sco_trombino_tours.py | 4 ++--
app/views/absences.py | 12 +++++-------
app/views/scolar.py | 5 ++++-
9 files changed, 24 insertions(+), 30 deletions(-)
diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py
index 108bb055..9266724f 100644
--- a/app/scodoc/sco_archives.py
+++ b/app/scodoc/sco_archives.py
@@ -313,7 +313,7 @@ def do_formsemestre_archive(
# tous les inscrits du semestre
group_ids = [sco_groups.get_default_group(formsemestre_id)]
groups_infos = sco_groups_view.DisplayedGroupsInfos(
- group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
+ group_ids, formsemestre_id=formsemestre_id
)
groups_filename = "-" + groups_infos.groups_filename
etudids = [m["etudid"] for m in groups_infos.members]
@@ -403,7 +403,7 @@ def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]):
# tous les inscrits du semestre
group_ids = [sco_groups.get_default_group(formsemestre_id)]
groups_infos = sco_groups_view.DisplayedGroupsInfos(
- group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
+ group_ids, formsemestre_id=formsemestre_id
)
H = [
diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py
index 9852899c..7ca1788f 100644
--- a/app/scodoc/sco_groups_view.py
+++ b/app/scodoc/sco_groups_view.py
@@ -34,12 +34,12 @@
import collections
import datetime
import operator
-import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
+import urllib
from urllib.parse import parse_qs
import time
-from flask import url_for, g
+from flask import url_for, g, request
import app.scodoc.sco_utils as scu
from app.scodoc import html_sco_header
@@ -86,7 +86,6 @@ def groups_view(
group_ids,
formsemestre_id=formsemestre_id,
etat=etat,
- REQUEST=REQUEST,
select_all_when_unspecified=True,
)
# Formats spéciaux: download direct
@@ -301,7 +300,6 @@ class DisplayedGroupsInfos(object):
etat=None,
select_all_when_unspecified=False,
moduleimpl_id=None, # used to find formsemestre when unspecified
- REQUEST=None,
):
if isinstance(group_ids, int):
if group_ids:
@@ -334,7 +332,7 @@ class DisplayedGroupsInfos(object):
for group_id in group_ids:
gq.append("group_ids=" + str(group_id))
self.groups_query_args = "&".join(gq)
- self.base_url = REQUEST.URL0 + "?" + self.groups_query_args
+ self.base_url = request.base_url + "?" + self.groups_query_args
self.group_ids = group_ids
self.groups = []
groups_titles = []
@@ -918,7 +916,7 @@ def form_choix_saisie_semaine(groups_infos, REQUEST=None):
del query_args["head_message"]
destination = "%s?%s" % (
REQUEST.URL,
- six.moves.urllib.parse.urlencode(query_args, True),
+ urllib.parse.urlencode(query_args, True),
)
destination = destination.replace(
"%", "%%"
diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py
index b9b8b5de..5b4d0cad 100644
--- a/app/scodoc/sco_import_etuds.py
+++ b/app/scodoc/sco_import_etuds.py
@@ -157,7 +157,6 @@ def sco_import_generate_excel_sample(
exclude_cols=[],
extra_cols=[],
group_ids=[],
- REQUEST=None,
):
"""Generates an excel document based on format fmt
(format is the result of sco_import_format())
@@ -188,7 +187,7 @@ def sco_import_generate_excel_sample(
titles += extra_cols
titlesStyles += [style] * len(extra_cols)
if group_ids:
- groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
+ groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
members = groups_infos.members
log(
"sco_import_generate_excel_sample: group_ids=%s %d members"
diff --git a/app/scodoc/sco_pvjury.py b/app/scodoc/sco_pvjury.py
index 3fa5196a..f06180f4 100644
--- a/app/scodoc/sco_pvjury.py
+++ b/app/scodoc/sco_pvjury.py
@@ -622,7 +622,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=
group_ids = [sco_groups.get_default_group(formsemestre_id)]
groups_infos = sco_groups_view.DisplayedGroupsInfos(
- group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
+ group_ids, formsemestre_id=formsemestre_id
)
etudids = [m["etudid"] for m in groups_infos.members]
@@ -800,7 +800,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
# tous les inscrits du semestre
group_ids = [sco_groups.get_default_group(formsemestre_id)]
groups_infos = sco_groups_view.DisplayedGroupsInfos(
- group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
+ group_ids, formsemestre_id=formsemestre_id
)
etudids = [m["etudid"] for m in groups_infos.members]
diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py
index da452b3a..faba5e2c 100644
--- a/app/scodoc/sco_saisie_notes.py
+++ b/app/scodoc/sco_saisie_notes.py
@@ -620,7 +620,6 @@ def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
formsemestre_id=formsemestre_id,
select_all_when_unspecified=True,
etat=None,
- REQUEST=REQUEST,
)
H = [
@@ -793,7 +792,6 @@ def feuille_saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
formsemestre_id=formsemestre_id,
select_all_when_unspecified=True,
etat=None,
- REQUEST=REQUEST,
)
groups = sco_groups.listgroups(groups_infos.group_ids)
gr_title_filename = sco_groups.listgroups_filename(groups)
@@ -891,7 +889,6 @@ def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
formsemestre_id=formsemestre_id,
select_all_when_unspecified=True,
etat=None,
- REQUEST=REQUEST,
)
if E["description"]:
diff --git a/app/scodoc/sco_trombino.py b/app/scodoc/sco_trombino.py
index 47769baf..a0f858b0 100644
--- a/app/scodoc/sco_trombino.py
+++ b/app/scodoc/sco_trombino.py
@@ -78,7 +78,7 @@ def trombino(
etat = None # may be passed as ''
# Informations sur les groupes à afficher:
groups_infos = sco_groups_view.DisplayedGroupsInfos(
- group_ids, formsemestre_id=formsemestre_id, etat=etat, REQUEST=REQUEST
+ group_ids, formsemestre_id=formsemestre_id, etat=etat
)
#
@@ -247,7 +247,7 @@ def _trombino_zip(groups_infos):
# Copy photos from portal to ScoDoc
def trombino_copy_photos(group_ids=[], REQUEST=None, dialog_confirmed=False):
"Copy photos from portal to ScoDoc (overwriting local copy)"
- groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
+ groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args
portal_url = sco_portal_apogee.get_portal_url()
@@ -485,14 +485,13 @@ def photos_generate_excel_sample(group_ids=[], REQUEST=None):
"photo_filename",
],
extra_cols=["fichier_photo"],
- REQUEST=REQUEST,
)
return sco_excel.send_excel_file(REQUEST, data, "ImportPhotos" + scu.XLSX_SUFFIX)
def photos_import_files_form(group_ids=[], REQUEST=None):
"""Formulaire pour importation photos"""
- groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
+ groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args
H = [
@@ -541,7 +540,7 @@ def photos_import_files_form(group_ids=[], REQUEST=None):
def photos_import_files(group_ids=[], xlsfile=None, zipfile=None, REQUEST=None):
"""Importation des photos"""
- groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
+ groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args
filename_title = "fichier_photo"
page_title = "Téléchargement des photos des étudiants"
diff --git a/app/scodoc/sco_trombino_tours.py b/app/scodoc/sco_trombino_tours.py
index b77bf61d..7a2ec1f0 100644
--- a/app/scodoc/sco_trombino_tours.py
+++ b/app/scodoc/sco_trombino_tours.py
@@ -61,7 +61,7 @@ def pdf_trombino_tours(
"""Generation du trombinoscope en fichier PDF"""
# Informations sur les groupes à afficher:
groups_infos = sco_groups_view.DisplayedGroupsInfos(
- group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
+ group_ids, formsemestre_id=formsemestre_id
)
DeptName = sco_preferences.get_preference("DeptName")
@@ -296,7 +296,7 @@ def pdf_feuille_releve_absences(
# Informations sur les groupes à afficher:
groups_infos = sco_groups_view.DisplayedGroupsInfos(
- group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
+ group_ids, formsemestre_id=formsemestre_id
)
DeptName = sco_preferences.get_preference("DeptName")
diff --git a/app/views/absences.py b/app/views/absences.py
index f0dee786..6216dc7e 100644
--- a/app/views/absences.py
+++ b/app/views/absences.py
@@ -312,7 +312,7 @@ def SignaleAbsenceGrHebdo(
moduleimpl_id = None
groups_infos = sco_groups_view.DisplayedGroupsInfos(
- group_ids, moduleimpl_id=moduleimpl_id, REQUEST=REQUEST
+ group_ids, moduleimpl_id=moduleimpl_id
)
if not groups_infos.members:
return (
@@ -474,7 +474,7 @@ def SignaleAbsenceGrSemestre(
REQUEST=None,
):
"""Saisie des absences sur une journée sur un semestre (ou intervalle de dates) entier"""
- groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
+ groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
if not groups_infos.members:
return (
html_sco_header.sco_header(page_title="Saisie des absences")
@@ -847,7 +847,7 @@ def EtatAbsencesGr(
datedebut = ndb.DateDMYtoISO(debut)
datefin = ndb.DateDMYtoISO(fin)
# Informations sur les groupes à afficher:
- groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
+ groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
formsemestre_id = groups_infos.formsemestre_id
sem = groups_infos.formsemestre
@@ -971,13 +971,11 @@ ou entrez une date pour visualiser les absents un jour donné :
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
-def EtatAbsencesDate(
- group_ids=[], date=None, REQUEST=None # list of groups to display
-):
+def EtatAbsencesDate(group_ids=[], date=None): # list of groups to display
# ported from dtml
"""Etat des absences pour un groupe à une date donnée"""
# Informations sur les groupes à afficher:
- groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
+ groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
H = [html_sco_header.sco_header(page_title="Etat des absences")]
if date:
dateiso = ndb.DateDMYtoISO(date)
diff --git a/app/views/scolar.py b/app/views/scolar.py
index 54640bea..41632ff7 100644
--- a/app/views/scolar.py
+++ b/app/views/scolar.py
@@ -283,7 +283,10 @@ sco_publish(
)
sco_publish(
- "/trombino_copy_photos", sco_trombino.trombino_copy_photos, Permission.ScoView
+ "/trombino_copy_photos",
+ sco_trombino.trombino_copy_photos,
+ Permission.ScoView,
+ methods=["GET", "POST"],
)
sco_publish("/groups_view", sco_groups_view.groups_view, Permission.ScoView)
From 381e0818446245a46ec870db115f7684ce2928a3 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Wed, 8 Sep 2021 00:35:40 +0200
Subject: [PATCH 12/25] version bump
---
sco_version.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sco_version.py b/sco_version.py
index d2735ed1..7669307f 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.0.9"
+SCOVERSION = "9.0.10"
SCONAME = "ScoDoc"
From 72dfc4f49b22be563d192beab2e1f7fbf4c42601 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Wed, 8 Sep 2021 23:00:01 +0200
Subject: [PATCH 13/25] Configuration des logos via formulaires
---
app/models/preferences.py | 18 ++++
app/scodoc/sco_logos.py | 95 ++++++++++++++++++++
app/scodoc/sco_preferences.py | 4 +-
app/scodoc/sco_utils.py | 4 +-
app/static/css/scodoc.css | 11 +++
app/templates/configuration.html | 52 +++++++----
app/views/scodoc.py | 145 +++++++++++++++++++++++++++++--
app/views/scolar.py | 70 ++++++++++++++-
config.py | 6 +-
tools/scodoc_config.py | 7 +-
10 files changed, 374 insertions(+), 38 deletions(-)
create mode 100644 app/scodoc/sco_logos.py
diff --git a/app/models/preferences.py b/app/models/preferences.py
index 65b88508..b04ad0da 100644
--- a/app/models/preferences.py
+++ b/app/models/preferences.py
@@ -33,6 +33,17 @@ class ScoDocSiteConfig(db.Model):
value = db.Column(db.Text())
BONUS_SPORT = "bonus_sport_func_name"
+ NAMES = {
+ BONUS_SPORT: str,
+ "always_require_ine": bool,
+ "SCOLAR_FONT": str,
+ "SCOLAR_FONT_SIZE": str,
+ "SCOLAR_FONT_SIZE_FOOT": str,
+ "INSTITUTION_NAME": str,
+ "INSTITUTION_ADDRESS": str,
+ "INSTITUTION_CITY": str,
+ "DEFAULT_PDF_FOOTER_TEMPLATE": str,
+ }
def __init__(self, name, value):
self.name = name
@@ -41,6 +52,13 @@ class ScoDocSiteConfig(db.Model):
def __repr__(self):
return f"<{self.__class__.__name__}('{self.name}', '{self.value}')>"
+ def get_dict(self) -> dict:
+ "Returns all data as a dict name = value"
+ return {
+ c.name: self.NAMES.get(c.name, lambda x: x)(c.value)
+ for c in ScoDocSiteConfig.query.all()
+ }
+
@classmethod
def set_bonus_sport_func(cls, func_name):
"""Record bonus_sport config.
diff --git a/app/scodoc/sco_logos.py b/app/scodoc/sco_logos.py
new file mode 100644
index 00000000..e29b5183
--- /dev/null
+++ b/app/scodoc/sco_logos.py
@@ -0,0 +1,95 @@
+# -*- mode: python -*-
+# -*- coding: utf-8 -*-
+
+##############################################################################
+#
+# Gestion scolarite IUT
+#
+# 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
+#
+##############################################################################
+
+"""Gestion des images logos (nouveau ScoDoc 9)
+
+Les logos sont `logo_header.` et `logo_footer.`
+avec `ext` membre de LOGOS_IMAGES_ALLOWED_TYPES (= jpg, png)
+
+SCODOC_LOGOS_DIR /opt/scodoc-data/config/logos
+"""
+import imghdr
+import os
+
+from flask import abort, current_app
+
+from app.scodoc import sco_utils as scu
+
+
+def get_logo_filename(logo_type: str, scodoc_dept: str) -> str:
+ """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_),
+ # then in config dir /opt/scodoc-data/config/logos/
+ for image_dir in (
+ scu.SCODOC_LOGOS_DIR + "/logos_" + scodoc_dept,
+ scu.SCODOC_LOGOS_DIR, # global logos
+ ):
+ for suffix in scu.LOGOS_IMAGES_ALLOWED_TYPES:
+ filename = os.path.join(image_dir, f"logo_{logo_type}.{suffix}")
+ if os.path.isfile(filename) and os.access(filename, os.R_OK):
+ return filename
+
+ return ""
+
+
+def guess_image_type(stream) -> str:
+ "guess image type from header in stream"
+ header = stream.read(512)
+ stream.seek(0)
+ fmt = imghdr.what(None, header)
+ if not fmt:
+ return None
+ return fmt if fmt != "jpeg" else "jpg"
+
+
+def _ensure_directory_exists(filename):
+ "create enclosing directory if necessary"
+ directory = os.path.split(filename)[0]
+ if not os.path.exists(directory):
+ current_app.logger.info(f"sco_logos creating directory %s", directory)
+ os.mkdir(directory)
+
+
+def store_image(stream, basename):
+ img_type = guess_image_type(stream)
+ if img_type not in scu.LOGOS_IMAGES_ALLOWED_TYPES:
+ abort(400, "type d'image invalide")
+ filename = basename + "." + img_type
+ _ensure_directory_exists(filename)
+ with open(filename, "wb") as f:
+ f.write(stream.read())
+ current_app.logger.info(f"sco_logos.store_image %s", filename)
+ # erase other formats if they exists
+ for extension in set(scu.LOGOS_IMAGES_ALLOWED_TYPES) - set([img_type]):
+ try:
+ os.unlink(basename + "." + extension)
+ except IOError:
+ pass
diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index f4615313..26ff4ec2 100644
--- a/app/scodoc/sco_preferences.py
+++ b/app/scodoc/sco_preferences.py
@@ -111,7 +111,7 @@ get_base_preferences(formsemestre_id)
"""
import flask
-from flask import g
+from flask import g, url_for
from app.models import Departement
from app.scodoc import sco_cache
@@ -2021,6 +2021,8 @@ class BasePreferences(object):
H = [
html_sco_header.sco_header(page_title="Préférences"),
"Préférences globales pour %s
" % scu.ScoURL(),
+ f"""modification des logos du département (pour documents pdf)
""",
"""Ces paramètres s'appliquent par défaut à tous les semestres, sauf si ceux-ci définissent des valeurs spécifiques.
Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !
""",
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index bf6a332a..2447c4e8 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -232,6 +232,8 @@ if not os.path.exists(SCO_TMP_DIR):
os.mkdir(SCO_TMP_DIR, 0o755)
# ----- Les logos: /opt/scodoc-data/config/logos
SCODOC_LOGOS_DIR = os.path.join(SCODOC_CFG_DIR, "logos")
+LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "jpeg", "png") # remind that PIL does not read pdf
+
# ----- Les outils distribués
SCO_TOOLS_DIR = os.path.join(Config.SCODOC_DIR, "tools")
@@ -305,8 +307,6 @@ PDF_MIMETYPE = "application/pdf"
XML_MIMETYPE = "text/xml"
JSON_MIMETYPE = "application/json"
-LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "png") # remind that PIL does not read pdf
-
# Admissions des étudiants
# Différents types de voies d'admission:
# (stocké en texte libre dans la base, mais saisie par menus pour harmoniser)
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index 01213d54..95b332bd 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -816,11 +816,22 @@ a.discretelink:hover {
div.sco_help {
margin-top: 12px;
+ margin-bottom: 3px;
font-style: italic;
color: navy;
background-color: rgb(200,200,220);
}
+span.wtf-field ul.errors li {
+ color: red;
+}
+.configuration_logo div.img-container {
+ width: 256px;
+}
+.configuration_logo div.img-container img {
+ max-width: 100%;
+}
+
p.indent {
padding-left: 2em;
}
diff --git a/app/templates/configuration.html b/app/templates/configuration.html
index 1580979c..6dcf1c51 100644
--- a/app/templates/configuration.html
+++ b/app/templates/configuration.html
@@ -2,32 +2,52 @@
{% import 'bootstrap/wtf.html' as wtf %}
{% macro render_field(field) %}
-
-
{{ field.label }} :
-
{{ field()|safe }}
+
+
{{ field.label }} :
+
{{ field()|safe }}
{% if field.errors %}
-
+
{% for error in field.errors %}
- - {{ error }}
+ - {{ error }}
{% endfor %}
-
+
{% endif %}
-
-
+
+
{% endmacro %}
{% block app_content %}
-Configuration générale
-Les paramètres donnés ici s'appliquent à tout ScoDoc (tous les départements).
+{% if scodoc_dept %}
+Logos du département {{ scodoc_dept }}
+{% else %}
+Configuration générale {{ scodoc_dept }}
+{% endif %}
-
-{% endblock %}
+{% endblock %}
\ No newline at end of file
diff --git a/app/views/scodoc.py b/app/views/scodoc.py
index 1d4b7799..d2fd9f9d 100644
--- a/app/views/scodoc.py
+++ b/app/views/scodoc.py
@@ -30,19 +30,29 @@ Module main: page d'accueil, avec liste des départements
Emmanuel Viennet, 2021
"""
+import os
+
import flask
-from flask import flash, url_for, redirect, render_template
+from flask import abort, flash, url_for, redirect, render_template, send_file
from flask import request
from flask.app import Flask
from flask_login.utils import login_required
from flask_wtf import FlaskForm
+from flask_wtf.file import FileField, FileAllowed
+from werkzeug.exceptions import BadRequest, NotFound
from wtforms import SelectField, SubmitField
+from wtforms.fields import IntegerField
+from wtforms.fields.simple import BooleanField, StringField, TextAreaField
+from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
-# from wtforms.validators import DataRequired
-
-from app.models import Departement, ScoDocSiteConfig
+import app
+from app.models import Departement, Identite
+from app.models import FormSemestre, NotesFormsemestreInscription
+from app.models import ScoDocSiteConfig
import sco_version
+from app.scodoc import sco_logos
from app.scodoc import sco_find_etud
+from app.scodoc import sco_utils as scu
from app.decorators import admin_required
from app.scodoc.sco_permissions import Permission
from app.views import scodoc_bp as bp
@@ -72,13 +82,56 @@ def table_etud_in_accessible_depts():
return sco_find_etud.table_etud_in_accessible_depts(expnom=request.form["expnom"])
+@bp.route("/ScoDoc/get_etud_dept")
+@login_required
+def get_etud_dept():
+ """Returns the dept acronym (eg "GEII") of an etud (identified by etudid,
+ code_nip ou code_ine in the request).
+ API: ramène la chaine brute, sans JSON ou XML.
+ """
+ if "etudid" in request.args:
+ # zero ou une réponse:
+ etuds = [Identite.query.get(request.args["etudid"])]
+ elif "code_nip" in request.args:
+ # il peut y avoir plusieurs réponses si l'étudiant est passé par plusieurs départements
+ etuds = Identite.query.filter_by(code_nip=request.args["code_nip"]).all()
+ elif "code_ine" in request.args:
+ etuds = Identite.query.filter_by(code_nip=request.args["code_ine"]).all()
+ else:
+ raise BadRequest(
+ "missing argument (expected one among: etudid, code_nip or code_ine)"
+ )
+ if not etuds:
+ raise NotFound("student not found")
+ elif len(etuds) == 1:
+ last_etud = etuds[0]
+ else:
+ # inscriptions dans plusieurs departements: cherche la plus recente
+ last_etud = None
+ last_date = None
+ for etud in etuds:
+ inscriptions = NotesFormsemestreInscription.query.filter_by(
+ etudid=etud.id
+ ).all()
+ for ins in inscriptions:
+ date_fin = FormSemestre.query.get(ins.formsemestre_id).date_fin
+ if (last_date is None) or date_fin > last_date:
+ last_date = date_fin
+ last_etud = etud
+ if not last_etud:
+ # est présent dans plusieurs semestres mais inscrit dans aucun !
+ # le choix a peu d'importance...
+ last_etud = etuds[-1]
+
+ return Departement.query.get(last_etud.dept_id).acronym
+
+
# ---- CONFIGURATION
class ScoDocConfigurationForm(FlaskForm):
"Panneau de configuration général"
- # très préliminaire ;-)
- # On veut y mettre la fonction bonus et ensuite les logos
+
bonus_sport_func_name = SelectField(
label="Fonction de calcul des bonus sport&culture",
choices=[
@@ -86,28 +139,104 @@ class ScoDocConfigurationForm(FlaskForm):
for x in ScoDocSiteConfig.get_bonus_sport_func_names()
],
)
+
+ logo_header = FileField(
+ label="Modifier l'image:",
+ description="logo placé en haut des documents PDF",
+ validators=[
+ FileAllowed(
+ scu.LOGOS_IMAGES_ALLOWED_TYPES,
+ f"n'accepte que les fichiers image {','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}",
+ )
+ ],
+ )
+
+ logo_footer = FileField(
+ label="Modifier l'image:",
+ description="logo placé en pied des documents PDF",
+ validators=[
+ FileAllowed(
+ scu.LOGOS_IMAGES_ALLOWED_TYPES,
+ f"n'accepte que les fichiers image {','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}",
+ )
+ ],
+ )
+
submit = SubmitField("Enregistrer")
+# Notes pour variables config: (valeurs par défaut des paramètres de département)
+# Chaines simples
+# SCOLAR_FONT = "Helvetica"
+# SCOLAR_FONT_SIZE = 10
+# SCOLAR_FONT_SIZE_FOOT = 6
+# INSTITUTION_NAME = "Institut Universitaire de Technologie - Université Georges Perec"
+# INSTITUTION_ADDRESS = "Web www.sor.bonne.top - 11, rue Simon Crubelier - 75017 Paris"
+# INSTITUTION_CITY = "Paris"
+# Textareas:
+# DEFAULT_PDF_FOOTER_TEMPLATE = "Edité par %(scodoc_name)s le %(day)s/%(month)s/%(year)s à %(hour)sh%(minute)s sur %(server_url)s"
+
+# Booléens
+# always_require_ine
+
+# Logos:
+# LOGO_FOOTER*, LOGO_HEADER*
+
+
@bp.route("/ScoDoc/configuration", methods=["GET", "POST"])
@admin_required
def configuration():
"Panneau de configuration général"
form = ScoDocConfigurationForm(
- bonus_sport_func_name=ScoDocSiteConfig.get_bonus_sport_func_name()
+ bonus_sport_func_name=ScoDocSiteConfig.get_bonus_sport_func_name(),
)
if form.validate_on_submit():
ScoDocSiteConfig.set_bonus_sport_func(form.bonus_sport_func_name.data)
+ if form.logo_header.data:
+ sco_logos.store_image(
+ form.logo_header.data, os.path.join(scu.SCODOC_LOGOS_DIR, "logo_header")
+ )
+ if form.logo_footer.data:
+ sco_logos.store_image(
+ form.logo_footer.data, os.path.join(scu.SCODOC_LOGOS_DIR, "logo_footer")
+ )
+ app.clear_scodoc_cache()
flash(f"Configuration enregistrée")
return redirect(url_for("scodoc.index"))
+
return render_template(
"configuration.html",
title="Configuration ScoDoc",
form=form,
- # bonus_sport_func_name=ScoDocSiteConfig.get_bonus_sport_func(),
+ scodoc_dept=None,
)
+def _return_logo(logo_type="header", scodoc_dept=""):
+ # stockée dans /opt/scodoc-data/config/logos donc servie manuellement ici
+ filename = sco_logos.get_logo_filename(logo_type, scodoc_dept)
+ if filename:
+ extension = os.path.splitext(filename)[1]
+ return send_file(filename, mimetype=f"image/{extension}")
+ else:
+ return ""
+
+
+@bp.route("/ScoDoc/logo_header")
+@bp.route("/ScoDoc//logo_header")
+def logo_header(scodoc_dept=""):
+ "Image logo header"
+ # "/opt/scodoc-data/config/logos/logo_header")
+ return _return_logo(logo_type="header", scodoc_dept=scodoc_dept)
+
+
+@bp.route("/ScoDoc/logo_footer")
+@bp.route("/ScoDoc//logo_footer")
+def logo_footer(scodoc_dept=""):
+ "Image logo footer"
+ return _return_logo(logo_type="footer", scodoc_dept=scodoc_dept)
+
+
# essais
# @bp.route("/testlog")
# def testlog():
diff --git a/app/views/scolar.py b/app/views/scolar.py
index 41632ff7..73371619 100644
--- a/app/views/scolar.py
+++ b/app/views/scolar.py
@@ -30,7 +30,7 @@ issu de ScoDoc7 / ZScolar.py
Emmanuel Viennet, 2021
"""
-
+import os
import sys
import time
@@ -40,9 +40,12 @@ from zipfile import ZipFile
import psycopg2
import flask
-from flask import jsonify, url_for
+from flask import jsonify, url_for, flash, redirect, render_template
from flask import current_app, g, request
from flask_login import current_user
+from flask_wtf import FlaskForm
+from flask_wtf.file import FileField, FileAllowed
+from wtforms import SubmitField
from config import Config
from app.decorators import (
@@ -71,8 +74,8 @@ from app.scodoc.sco_exceptions import (
)
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
import sco_version
+import app
from app.scodoc.gen_tables import GenTable
-
from app.scodoc import html_sco_header
from app.scodoc import html_sidebar
from app.scodoc import imageresize
@@ -94,6 +97,7 @@ from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups
from app.scodoc import sco_groups_edit
from app.scodoc import sco_groups_view
+from app.scodoc import sco_logos
from app.scodoc import sco_news
from app.scodoc import sco_page_etud
from app.scodoc import sco_parcours_dut
@@ -201,6 +205,66 @@ def doc_preferences(REQUEST):
return sco_preferences.doc_preferences()
+class DeptLogosConfigurationForm(FlaskForm):
+ "Panneau de configuration logos dept"
+
+ logo_header = FileField(
+ label="Modifier l'image:",
+ description="logo placé en haut des documents PDF",
+ validators=[
+ FileAllowed(
+ scu.LOGOS_IMAGES_ALLOWED_TYPES,
+ f"n'accepte que les fichiers image {','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}",
+ )
+ ],
+ )
+
+ logo_footer = FileField(
+ label="Modifier l'image:",
+ description="logo placé en pied des documents PDF",
+ validators=[
+ FileAllowed(
+ scu.LOGOS_IMAGES_ALLOWED_TYPES,
+ f"n'accepte que les fichiers image {','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}",
+ )
+ ],
+ )
+
+ submit = SubmitField("Enregistrer")
+
+
+@bp.route("/config_logos", methods=["GET", "POST"])
+@permission_required(Permission.ScoChangePreferences)
+def config_logos(scodoc_dept):
+ "Panneau de configuration général"
+ form = DeptLogosConfigurationForm()
+ if form.validate_on_submit():
+ if form.logo_header.data:
+ sco_logos.store_image(
+ form.logo_header.data,
+ os.path.join(
+ scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_header"
+ ),
+ )
+ if form.logo_footer.data:
+ sco_logos.store_image(
+ form.logo_footer.data,
+ os.path.join(
+ scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_footer"
+ ),
+ )
+ app.clear_scodoc_cache()
+ flash(f"Logos enregistrés")
+ return redirect(url_for("scolar.index_html", scodoc_dept=scodoc_dept))
+
+ return render_template(
+ "configuration.html",
+ title="Configuration Logos du département",
+ form=form,
+ scodoc_dept=scodoc_dept,
+ )
+
+
# --------------------------------------------------------------------
#
# ETUDIANTS
diff --git a/config.py b/config.py
index aed798d6..2e424a58 100755
--- a/config.py
+++ b/config.py
@@ -30,9 +30,9 @@ class Config:
SCODOC_DIR = os.environ.get("SCODOC_DIR", "/opt/scodoc")
SCODOC_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "/opt/scodoc-data")
SCODOC_LOG_FILE = os.path.join(SCODOC_VAR_DIR, "log", "scodoc.log")
- # For legacy ScoDoc7 installs: postgresql user
- SCODOC7_SQL_USER = os.environ.get("SCODOC7_SQL_USER", "www-data")
- DEFAULT_SQL_PORT = os.environ.get("DEFAULT_SQL_PORT", "5432")
+ #
+ MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # Flask uploads
+
# STATIC_URL_PATH = "/ScoDoc/static"
# static_folder = "stat"
# SERVER_NAME = os.environ.get("SERVER_NAME")
diff --git a/tools/scodoc_config.py b/tools/scodoc_config.py
index 9c33c9f0..2937dfd4 100644
--- a/tools/scodoc_config.py
+++ b/tools/scodoc_config.py
@@ -23,10 +23,6 @@ CONFIG = CFG()
CONFIG.always_require_ine = 0 # set to 1 if you want to require INE
-# The base URL, use only if you are behind a proxy
-# eg "https://scodoc.example.net/ScoDoc"
-CONFIG.ABSOLUTE_URL = ""
-
#
# ------------- Documents PDF -------------
#
@@ -78,6 +74,7 @@ CONFIG.DEFAULT_PDF_FOOTER_TEMPLATE = "Edité par %(scodoc_name)s le %(day)s/%(mo
#
# - règle "LMD": capitalisation uniquement des UE avec moy. > 10
+# XXX à revoir pour le BUT: variable à intégrer aux parcours
CONFIG.CAPITALIZE_ALL_UES = (
True # si vrai, capitalise toutes les UE des semestres validés (règle "LMD").
)
@@ -86,7 +83,7 @@ CONFIG.CAPITALIZE_ALL_UES = (
#
# -----------------------------------------------------
#
-# -------------- Personnalisation des pages
+# -------------- Personnalisation des pages (DEPRECATED)
#
# -----------------------------------------------------
# Nom (chemin complet) d'un fichier .html à inclure juste après le
From 829d5d8b2e590cb01e27f07c91028aeab1efaaf5 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Wed, 8 Sep 2021 23:10:28 +0200
Subject: [PATCH 14/25] =?UTF-8?q?fix:=20ajout=20semestre=20=C3=A0=20semset?=
=?UTF-8?q?=20(exports=20apog=C3=A9e)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/scodoc/sco_semset.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/app/scodoc/sco_semset.py b/app/scodoc/sco_semset.py
index b6d3abc2..1f35afae 100644
--- a/app/scodoc/sco_semset.py
+++ b/app/scodoc/sco_semset.py
@@ -158,11 +158,10 @@ class SemSet(dict):
ndb.SimpleQuery(
"""INSERT INTO notes_semset_formsemestre
- (dept_id, id, semset_id)
- VALUES (%(dept_id)s, %(formsemestre_id)s, %(semset_id)s)
+ (id, semset_id)
+ VALUES (%(formsemestre_id)s, %(semset_id)s)
""",
{
- "dept_id": g.scodoc_dept_id,
"formsemestre_id": formsemestre_id,
"semset_id": self.semset_id,
},
From c6f6f45e0d12f4a75e1cf80654c9481ec7260dd7 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Thu, 9 Sep 2021 07:59:28 +0200
Subject: [PATCH 15/25] removed up_to_date checking
---
app/scodoc/sco_up_to_date.py | 93 ++----------------------------------
sco_version.py | 2 +-
2 files changed, 5 insertions(+), 90 deletions(-)
diff --git a/app/scodoc/sco_up_to_date.py b/app/scodoc/sco_up_to_date.py
index cab38a63..cd473f97 100644
--- a/app/scodoc/sco_up_to_date.py
+++ b/app/scodoc/sco_up_to_date.py
@@ -28,104 +28,19 @@
""" Verification version logiciel vs version "stable" sur serveur
N'effectue pas la mise à jour automatiquement, mais permet un affichage d'avertissement.
+
+ Désactivé temporairement pour ScoDoc 9.
"""
-import datetime
-import app.scodoc.sco_utils as scu
-from app import log
-
-# Appel renvoyant la subversion "stable"
-# La notion de "stable" est juste là pour éviter d'afficher trop frequemment
-# des avertissements de mise à jour: on veut pouvoir inciter à mettre à jour lors de
-# correctifs majeurs.
-
-GET_VER_URL = "http://scodoc.iutv.univ-paris13.fr/scodoc-installmgr/last_stable_version"
-
-
-def get_last_stable_version():
- """request last stable version number from server
- (returns string as given by server, empty if failure)
- (do not wait server answer more than 3 seconds)
- """
- global _LAST_UP_TO_DATE_REQUEST
- ans = scu.query_portal(
- GET_VER_URL, msg="ScoDoc version server", timeout=3
- ) # sco_utils
- if ans:
- ans = ans.strip()
- _LAST_UP_TO_DATE_REQUEST = datetime.datetime.now()
- log(
- 'get_last_stable_version: updated at %s, answer="%s"'
- % (_LAST_UP_TO_DATE_REQUEST, ans)
- )
- return ans
-
-
-_LAST_UP_TO_DATE_REQUEST = None # datetime of last request to server
-_UP_TO_DATE = True # cached result (limit requests to 1 per day)
-_UP_TO_DATE_MSG = ""
+from flask import current_app
def is_up_to_date():
"""True if up_to_date
Returns status, message
"""
- log("Warning: is_up_to_date not implemented for ScoDoc8")
+ current_app.logger.debug("Warning: is_up_to_date not implemented for ScoDoc9")
return True, "unimplemented"
- # global _LAST_UP_TO_DATE_REQUEST, _UP_TO_DATE, _UP_TO_DATE_MSG
- # if _LAST_UP_TO_DATE_REQUEST and (
- # datetime.datetime.now() - _LAST_UP_TO_DATE_REQUEST
- # ) < datetime.timedelta(1):
- # # requete deja effectuee aujourd'hui:
- # return _UP_TO_DATE, _UP_TO_DATE_MSG
-
- # last_stable_ver = get_last_stable_version()
- # cur_ver = scu.get_svn_version(scu.SCO_SRC_DIR) # in sco_utils
- # cur_ver2 = cur_ver
- # cur_ver_num = -1
- # # Convert versions to integers:
- # try:
- # # cur_ver can be "1234" or "1234M' or '1234:1245M'...
- # fs = cur_ver.split(":", 1)
- # if len(fs) > 1:
- # cur_ver2 = fs[-1]
- # m = re.match(r"([0-9]*)", cur_ver2)
- # if not m:
- # raise ValueError(
- # "invalid svn version"
- # ) # should never occur, regexp always (maybe empty) match
- # cur_ver_num = int(m.group(1))
- # except:
- # log('Warning: no numeric subversion ! (cur_ver="%s")' % cur_ver)
- # return _UP_TO_DATE, _UP_TO_DATE_MSG # silently ignore misconfiguration ?
- # try:
- # last_stable_ver_num = int(last_stable_ver)
- # except:
- # log("Warning: last_stable_version returned by server is invalid !")
- # return (
- # _UP_TO_DATE,
- # _UP_TO_DATE_MSG,
- # ) # should ignore this error (maybe server is unreachable)
- # #
- # if cur_ver_num < last_stable_ver_num:
- # _UP_TO_DATE = False
- # _UP_TO_DATE_MSG = "Version %s disponible (version %s installée)" % (
- # last_stable_ver,
- # cur_ver_num,
- # )
- # log(
- # "Warning: ScoDoc installation is not up-to-date, should upgrade\n%s"
- # % _UP_TO_DATE_MSG
- # )
- # else:
- # _UP_TO_DATE = True
- # _UP_TO_DATE_MSG = ""
- # log(
- # "ScoDoc is up-to-date (cur_ver: %s, using %s=%s)"
- # % (cur_ver, cur_ver2, cur_ver_num)
- # )
-
- # return _UP_TO_DATE, _UP_TO_DATE_MSG
def html_up_to_date_box():
diff --git a/sco_version.py b/sco_version.py
index 7669307f..2e77dbe6 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.0.10"
+SCOVERSION = "9.0.11"
SCONAME = "ScoDoc"
From 4ac076ec6c2efc15aa42936c62057f7d8079c1fb Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Thu, 9 Sep 2021 08:03:43 +0200
Subject: [PATCH 16/25] =?UTF-8?q?Fix:=20recherche=20=C3=A9tudiant?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/__init__.py | 4 +---
app/scodoc/sco_find_etud.py | 23 ++++++++++++++---------
app/templates/scodoc.html | 2 +-
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/app/__init__.py b/app/__init__.py
index 9d450cd7..c0cbdad5 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -190,9 +190,7 @@ def create_app(config_class=DevConfig):
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
- app.logger.info(
- f"registered bulletin classes {[ k for k in sco_bulletins_generator.BULLETIN_CLASSES ]}"
- )
+
return app
diff --git a/app/scodoc/sco_find_etud.py b/app/scodoc/sco_find_etud.py
index 2d828ee6..13426bea 100644
--- a/app/scodoc/sco_find_etud.py
+++ b/app/scodoc/sco_find_etud.py
@@ -120,10 +120,11 @@ def search_etud_in_dept(expnom=""):
if etudid is not None:
etuds = sco_etud.get_etud_info(filled=True, etudid=expnom)
if (etudid is None) or len(etuds) != 1:
- if scu.is_valid_code_nip(expnom):
- etuds = search_etuds_infos(code_nip=expnom)
+ expnom_str = str(expnom)
+ if scu.is_valid_code_nip(expnom_str):
+ etuds = search_etuds_infos(code_nip=expnom_str)
else:
- etuds = search_etuds_infos(expnom=expnom)
+ etuds = search_etuds_infos(expnom=expnom_str)
else:
etuds = [] # si expnom est trop court, n'affiche rien
@@ -151,7 +152,7 @@ def search_etud_in_dept(expnom=""):
H = [
html_sco_header.sco_header(
page_title="Recherche d'un étudiant",
- no_side_bar=True,
+ no_side_bar=False,
init_qtip=True,
javascripts=["js/etud_info.js"],
)
@@ -250,10 +251,12 @@ def search_etud_by_name(term: str) -> list:
r = ndb.SimpleDictFetch(
"""SELECT nom, prenom, code_nip
FROM identite
- WHERE code_nip
- LIKE %(beginning)s ORDER BY nom
+ WHERE
+ dept_id = %(dept_id)s
+ AND code_nip LIKE %(beginning)s
+ ORDER BY nom
""",
- {"beginning": term + "%"},
+ {"beginning": term + "%", "dept_id": g.scodoc_dept_id},
)
data = [
{
@@ -267,10 +270,12 @@ def search_etud_by_name(term: str) -> list:
r = ndb.SimpleDictFetch(
"""SELECT id AS etudid, nom, prenom
FROM identite
- WHERE nom LIKE %(beginning)s
+ WHERE
+ dept_id = %(dept_id)s
+ AND nom LIKE %(beginning)s
ORDER BY nom
""",
- {"beginning": term + "%"},
+ {"beginning": term + "%", "dept_id": g.scodoc_dept_id},
)
data = [
diff --git a/app/templates/scodoc.html b/app/templates/scodoc.html
index 240389bb..23826e3c 100644
--- a/app/templates/scodoc.html
+++ b/app/templates/scodoc.html
@@ -30,7 +30,7 @@
{% if current_user.is_authenticated %}
-