diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index b8c4eafae..f46153133 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 cd5bddde3..c2df6738f 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 83a6650b5..e8ca47f60 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"]