diff --git a/app/__init__.py b/app/__init__.py index e72f75d1..9d450cd7 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -303,8 +303,9 @@ def clear_scodoc_cache(): # --------- Logging def log(msg: str, silent_test=True): """log a message. - If Flask app, use configured logger, else stderr.""" - if silent_test and current_app.config["TESTING"]: + If Flask app, use configured logger, else stderr. + """ + if silent_test and current_app and current_app.config["TESTING"]: return try: dept = getattr(g, "scodoc_dept", "") diff --git a/app/models/__init__.py b/app/models/__init__.py index fc50d024..d54a8982 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -63,4 +63,4 @@ from app.models.notes import ( NotesNotes, NotesNotesLog, ) -from app.models.preferences import ScoPreference +from app.models.preferences import ScoPreference, ScoDocSiteConfig diff --git a/app/models/preferences.py b/app/models/preferences.py index 15a2ea5f..65b88508 100644 --- a/app/models/preferences.py +++ b/app/models/preferences.py @@ -2,11 +2,12 @@ """Model : preferences """ -from app import db +from app import db, log +from app.scodoc import bonus_sport class ScoPreference(db.Model): - """ScoDoc preferences""" + """ScoDoc preferences (par département)""" __tablename__ = "sco_prefs" id = db.Column(db.Integer, primary_key=True) @@ -17,3 +18,84 @@ class ScoPreference(db.Model): name = db.Column(db.String(128), nullable=False, index=True) value = db.Column(db.Text()) formsemestre_id = db.Column(db.Integer, db.ForeignKey("notes_formsemestre.id")) + + +class ScoDocSiteConfig(db.Model): + """Config. d'un site + Nouveau en ScoDoc 9: va regrouper les paramètres qui dans les versions + antérieures étaient dans scodoc_config.py + """ + + __tablename__ = "scodoc_site_config" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(128), nullable=False, index=True) + value = db.Column(db.Text()) + + BONUS_SPORT = "bonus_sport_func_name" + + def __init__(self, name, value): + self.name = name + self.value = value + + def __repr__(self): + return f"<{self.__class__.__name__}('{self.name}', '{self.value}')>" + + @classmethod + def set_bonus_sport_func(cls, func_name): + """Record bonus_sport config. + If func_name not defined, raise NameError + """ + if func_name not in cls.get_bonus_sport_func_names(): + raise NameError("invalid function name for bonus_sport") + c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first() + if c: + log("setting to " + func_name) + c.value = func_name + else: + c = ScoDocSiteConfig(cls.BONUS_SPORT, func_name) + db.session.add(c) + db.session.commit() + + @classmethod + def get_bonus_sport_func_name(cls): + """Get configured bonus function name, or None if None.""" + f = cls.get_bonus_sport_func_from_name() + if f is None: + return "" + else: + return f.__name__ + + @classmethod + def get_bonus_sport_func(cls): + """Get configured bonus function, or None if None.""" + return cls.get_bonus_sport_func_from_name() + + @classmethod + def get_bonus_sport_func_from_name(cls, func_name=None): + """returns bonus func with specified name. + If name not specified, return the configured function. + None if no bonus function configured. + Raises NameError if func_name not found in module bonus_sport. + """ + if func_name is None: + c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first() + if c is None: + return None + func_name = c.value + if func_name == "": # pas de bonus défini + return None + return getattr(bonus_sport, func_name) + + @classmethod + def get_bonus_sport_func_names(cls): + """List available functions names + (starting with empty string to represent "no bonus function"). + """ + return [""] + sorted( + [ + getattr(bonus_sport, name).__name__ + for name in dir(bonus_sport) + if name.startswith("bonus_") + ] + ) diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py index 0c5b6a65..aeb7dabf 100644 --- a/app/scodoc/notes_table.py +++ b/app/scodoc/notes_table.py @@ -35,6 +35,7 @@ from operator import itemgetter from flask import g, url_for +from app.models import ScoDocSiteConfig import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app import log @@ -922,9 +923,13 @@ class NotesTable(object): if len(coefs_bonus_gen) == 1: coefs_bonus_gen = [1.0] # irrelevant, may be zero - bonus = scu.CONFIG.compute_bonus( - notes_bonus_gen, coefs_bonus_gen, infos=infos - ) + bonus_func = ScoDocSiteConfig.get_bonus_sport_func() + if bonus_func: + bonus = bonus_func( + notes_bonus_gen, coefs_bonus_gen, infos=infos + ) + else: + bonus = 0.0 self.bonus[etudid] = bonus infos["moy"] += bonus infos["moy"] = min(infos["moy"], 20.0) # clip bogus bonus diff --git a/app/scodoc/sco_config.py b/app/scodoc/sco_config.py index df5eb922..6d06a8be 100644 --- a/app/scodoc/sco_config.py +++ b/app/scodoc/sco_config.py @@ -1,9 +1,9 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -"""Configuration de ScoDoc (version 2020) +"""Configuration de ScoDoc (version ScoDOc 9) NE PAS MODIFIER localement ce fichier ! - mais éditer /opt/scodoc/var/scodoc/config/scodoc_local.py + mais éditer /opt/scodoc-data/config/scodoc_local.py """ from app.scodoc import bonus_sport @@ -20,10 +20,6 @@ CONFIG = AttrDict() # set to 1 if you want to require INE: CONFIG.always_require_ine = 0 -# The base URL, use only if you are behind a proxy -# eg "https://scodoc.example.net/ScoDoc" -CONFIG.ABSOLUTE_URL = "" - # ----------------------------------------------------- # -------------- Documents PDF # ----------------------------------------------------- diff --git a/app/scodoc/sco_config_load.py b/app/scodoc/sco_config_load.py index 39378473..bbe52bf2 100644 --- a/app/scodoc/sco_config_load.py +++ b/app/scodoc/sco_config_load.py @@ -17,27 +17,26 @@ def load_local_configuration(scodoc_cfg_dir): """Load local configuration file (if exists) and merge it with CONFIG. """ - # this path should be synced with upgrade.sh - LOCAL_CONFIG_FILENAME = os.path.join(scodoc_cfg_dir, "scodoc_local.py") - LOCAL_CONFIG = None - if os.path.exists(LOCAL_CONFIG_FILENAME): + local_config_filename = os.path.join(scodoc_cfg_dir, "scodoc_local.py") + local_config = None + if os.path.exists(local_config_filename): if not scodoc_cfg_dir in sys.path: - sys.path.insert(1, scodoc_cfg_dir) + sys.path.insert(0, scodoc_cfg_dir) try: - from scodoc_local import CONFIG as LOCAL_CONFIG + from scodoc_local import CONFIG as local_config - log("imported %s" % LOCAL_CONFIG_FILENAME) + log("imported %s" % local_config_filename) except ImportError: - log("Error: can't import %s" % LOCAL_CONFIG_FILENAME) - del sys.path[1] - if LOCAL_CONFIG is None: + log("Error: can't import %s" % local_config_filename) + del sys.path[0] + if local_config is None: return # Now merges local config in our CONFIG - for x in [x for x in dir(LOCAL_CONFIG) if x[0] != "_"]: - v = getattr(LOCAL_CONFIG, x) - if not v in sco_config.CONFIG: - log("Warning: local config setting unused parameter %s (skipped)" % x) + for x in [x for x in local_config if x[0] != "_"]: + v = local_config.get(x) + if not x in sco_config.CONFIG: + log(f"Warning: local config setting unused parameter {x} (skipped)") else: if v != sco_config.CONFIG[x]: - log("Setting parameter %s from %s" % (x, LOCAL_CONFIG_FILENAME)) + log(f"Setting parameter {x} from {local_config_filename}") sco_config.CONFIG[x] = v diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index b526e083..b9ba65c6 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -1031,7 +1031,7 @@ def module_evaluation_renumber(moduleimpl_id, only_if_unumbered=False, redirect= # -------------- VIEWS -def evaluation_describe(evaluation_id="", edit_in_place=True, REQUEST=None): +def evaluation_describe(evaluation_id="", edit_in_place=True): """HTML description of evaluation, for page headers edit_in_place: allow in-place editing when permitted (not implemented) """ @@ -1046,7 +1046,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, REQUEST=None): resp = u["prenomnom"] nomcomplet = u["nomcomplet"] can_edit = sco_permissions_check.can_edit_notes( - REQUEST.AUTHENTICATED_USER, moduleimpl_id, allow_ens=False + current_user, moduleimpl_id, allow_ens=False ) link = ( @@ -1223,7 +1223,7 @@ def evaluation_create_form( if not readonly: H = ["

%svaluation en %s

" % (action, mod_descr)] else: - return evaluation_describe(evaluation_id, REQUEST=REQUEST) + return evaluation_describe(evaluation_id) heures = ["%02dh%02d" % (h, m) for h in range(8, 19) for m in (0, 30)] # diff --git a/app/scodoc/sco_liste_notes.py b/app/scodoc/sco_liste_notes.py index a8f540db..9d96c77c 100644 --- a/app/scodoc/sco_liste_notes.py +++ b/app/scodoc/sco_liste_notes.py @@ -80,11 +80,7 @@ def do_evaluation_listenotes(REQUEST): E = evals[0] # il y a au moins une evaluation # description de l'evaluation if mode == "eval": - H = [ - sco_evaluations.evaluation_describe( - evaluation_id=evaluation_id, REQUEST=REQUEST - ) - ] + H = [sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)] else: H = [] # groupes @@ -529,9 +525,7 @@ def _make_table_notes( eval_info = 'Notes incomplètes, évaluation non prise en compte dans les moyennes' return ( - sco_evaluations.evaluation_describe( - evaluation_id=E["evaluation_id"], REQUEST=REQUEST - ) + sco_evaluations.evaluation_describe(evaluation_id=E["evaluation_id"]) + eval_info + html_form + t @@ -786,9 +780,7 @@ def evaluation_check_absences_html( html_sco_header.html_sem_header( REQUEST, "Vérification absences à l'évaluation" ), - sco_evaluations.evaluation_describe( - evaluation_id=evaluation_id, REQUEST=REQUEST - ), + sco_evaluations.evaluation_describe(evaluation_id=evaluation_id), """

Vérification de la cohérence entre les notes saisies et les absences signalées.

""", ] else: diff --git a/app/scodoc/sco_placement.py b/app/scodoc/sco_placement.py index 4f9e9a0b..bf474883 100644 --- a/app/scodoc/sco_placement.py +++ b/app/scodoc/sco_placement.py @@ -76,9 +76,7 @@ def do_placement_selectetuds(REQUEST): # description de l'evaluation H = [ - sco_evaluations.evaluation_describe( - evaluation_id=evaluation_id, REQUEST=REQUEST - ), + sco_evaluations.evaluation_describe(evaluation_id=evaluation_id), "

Placement et émargement des étudiants

", ] # diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index ea7aac90..da452b3a 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -630,9 +630,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None): cssstyles=sco_groups_view.CSSSTYLES, init_qtip=True, ), - sco_evaluations.evaluation_describe( - evaluation_id=evaluation_id, REQUEST=REQUEST - ), + sco_evaluations.evaluation_describe(evaluation_id=evaluation_id), """Saisie des notes par fichier""", ] @@ -909,9 +907,7 @@ def saisie_notes(evaluation_id, group_ids=[], REQUEST=None): cssstyles=sco_groups_view.CSSSTYLES, init_qtip=True, ), - sco_evaluations.evaluation_describe( - evaluation_id=evaluation_id, REQUEST=REQUEST - ), + sco_evaluations.evaluation_describe(evaluation_id=evaluation_id), '
Saisie des notes', ] H.append("""
""") diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index 1bf6622e..01213d54 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -1062,6 +1062,14 @@ h2.formsemestre, .gtrcontent h2 { #formnotes td.tf-fieldlabel { border-bottom: 1px dotted #fdcaca; } + +/* Formulaires ScoDoc 9 */ +form.sco-form { + margin-top: 1em; +} +div.sco-submit { + margin-top: 2em; +} /* .formsemestre_menubar { border-top: 3px solid #67A7E3; diff --git a/app/templates/base.html b/app/templates/base.html index 1b48464b..05068400 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -23,9 +23,11 @@ ScoDoc