Ajout configuration gloable via formualaire: bonus_sport. Modif migration fichier config. A suivre
This commit is contained in:
parent
dccae56fe7
commit
abd6d53510
@ -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", "")
|
||||
|
@ -63,4 +63,4 @@ from app.models.notes import (
|
||||
NotesNotes,
|
||||
NotesNotesLog,
|
||||
)
|
||||
from app.models.preferences import ScoPreference
|
||||
from app.models.preferences import ScoPreference, ScoDocSiteConfig
|
||||
|
@ -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_")
|
||||
]
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
# -----------------------------------------------------
|
||||
|
@ -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
|
||||
|
@ -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 = ["<h3>%svaluation en %s</h3>" % (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)]
|
||||
#
|
||||
|
@ -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 = '<span class="eval_info eval_incomplete">Notes incomplètes, évaluation non prise en compte dans les moyennes</span>'
|
||||
|
||||
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),
|
||||
"""<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.</p>""",
|
||||
]
|
||||
else:
|
||||
|
@ -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),
|
||||
"<h3>Placement et émargement des étudiants</h3>",
|
||||
]
|
||||
#
|
||||
|
@ -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),
|
||||
"""<span class="eval_title">Saisie des notes par fichier</span>""",
|
||||
]
|
||||
|
||||
@ -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),
|
||||
'<div id="saisie_notes"><span class="eval_title">Saisie des notes</span>',
|
||||
]
|
||||
H.append("""<div id="group-tabs"><table><tr><td>""")
|
||||
|
@ -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;
|
||||
|
@ -23,9 +23,11 @@
|
||||
<a class="navbar-brand" href="{{ url_for('scodoc.index') }}">ScoDoc</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
{% if current_user.is_administrator() %}
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="{{ url_for('scodoc.index') }}">Home</a></li>
|
||||
<li><a href="{{ url_for('scodoc.configuration') }}">Configuration</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% if current_user.is_anonymous %}
|
||||
<li><a href="{{ url_for('auth.login') }}">Login</a></li>
|
||||
|
33
app/templates/configuration.html
Normal file
33
app/templates/configuration.html
Normal file
@ -0,0 +1,33 @@
|
||||
{% extends 'base.html' %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% macro render_field(field) %}
|
||||
<div>
|
||||
<span class="wtf-field">{{ field.label }} :</span>
|
||||
<span class="wtf-field">{{ field()|safe }}
|
||||
{% if field.errors %}
|
||||
<ul class=errors>
|
||||
{% for error in field.errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Configuration générale</h1>
|
||||
|
||||
<p class="help">Les paramètres donnés ici s'appliquent à tout ScoDoc (tous les départements).</p>
|
||||
|
||||
<form class="sco-form" action="" method="post" novalidate>
|
||||
{{ form.hidden_tag() }}
|
||||
{{ render_field(form.bonus_sport_func_name)}}
|
||||
{# <p>
|
||||
{{ form.bonus_sport_func_name.label }}<br>
|
||||
{{ form.bonus_sport_func_name() }}
|
||||
</p> #}
|
||||
<div class="sco-submit">{{ form.submit() }}</div>
|
||||
</form>
|
||||
{% endblock %}
|
@ -15,7 +15,7 @@
|
||||
<li>
|
||||
<a class="stdlink {{'link_accessible' if current_user.has_permission(Permission.ScoView, dept=dept.acronym) else 'link_unauthorized'}}"
|
||||
href="{{url_for('scolar.index_html', scodoc_dept=dept.acronym)}}">Département
|
||||
{{dept.acronym}}</a>
|
||||
{{dept.preferences.filter_by(name="DeptName").first().value}}</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
|
@ -31,13 +31,19 @@ Module main: page d'accueil, avec liste des départements
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
import flask
|
||||
from flask import render_template
|
||||
from flask import flash, url_for, redirect, render_template
|
||||
from flask import request
|
||||
from flask.app import Flask
|
||||
from flask_login.utils import login_required
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import SelectField, SubmitField
|
||||
|
||||
from app.models import Departement
|
||||
# from wtforms.validators import DataRequired
|
||||
|
||||
from app.models import Departement, ScoDocSiteConfig
|
||||
import sco_version
|
||||
from app.scodoc import sco_find_etud
|
||||
from app.decorators import admin_required
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.views import scodoc_bp as bp
|
||||
|
||||
@ -66,6 +72,42 @@ def table_etud_in_accessible_depts():
|
||||
return sco_find_etud.table_etud_in_accessible_depts(expnom=request.form["expnom"])
|
||||
|
||||
|
||||
# ---- 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=[
|
||||
(x, x if x else "Aucune")
|
||||
for x in ScoDocSiteConfig.get_bonus_sport_func_names()
|
||||
],
|
||||
)
|
||||
submit = SubmitField("Enregistrer")
|
||||
|
||||
|
||||
@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()
|
||||
)
|
||||
if form.validate_on_submit():
|
||||
ScoDocSiteConfig.set_bonus_sport_func(form.bonus_sport_func_name.data)
|
||||
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(),
|
||||
)
|
||||
|
||||
|
||||
# essais
|
||||
# @bp.route("/testlog")
|
||||
# def testlog():
|
||||
|
35
migrations/versions/f73251d1d825_table_configuration_site.py
Normal file
35
migrations/versions/f73251d1d825_table_configuration_site.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""Table configuration site
|
||||
|
||||
Revision ID: f73251d1d825
|
||||
Revises: f6e7d2e01be1
|
||||
Create Date: 2021-09-04 23:10:39.149965
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f73251d1d825'
|
||||
down_revision = 'f6e7d2e01be1'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('scodoc_site_config',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=128), nullable=False),
|
||||
sa.Column('value', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_scodoc_site_config_name'), 'scodoc_site_config', ['name'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_scodoc_site_config_name'), table_name='scodoc_site_config')
|
||||
op.drop_table('scodoc_site_config')
|
||||
# ### end Alembic commands ###
|
@ -125,6 +125,29 @@ migrate_local_files() {
|
||||
mkdir -p "$old_logs_dest" || die "erreur creation $old_logs_dest"
|
||||
mv "${SCODOC7_HOME}"/log/* "$old_logs_dest" || die "erreur mv"
|
||||
|
||||
# Le fichier de customization local:
|
||||
# peut être dans .../var/config/scodoc_local.py
|
||||
# ou bien, sur les très anciennes installs, dans Products/ScoDoc/config/scodoc_config.py
|
||||
# (si migration, copié dans SCODOC7_HOME/config/scodoc_config.py)
|
||||
# en principe ScoDoc 9 est encore compatible avec cet ancien fichier.
|
||||
# donc:
|
||||
if [ ! -e "$SCODOC_VAR_DIR"/scodoc_local.py ]
|
||||
then
|
||||
|
||||
# if [ "$INPLACE" == 1 ]
|
||||
# then
|
||||
# scodoc_config_filename = "${SCODOC7_HOME}"/Products/ScoDoc/config/scodoc_config.py
|
||||
# else
|
||||
# scodoc_config_filename = "${SCODOC7_HOME}"/config/scodoc_config.py
|
||||
# fi
|
||||
# # Le fichier distribué avait-il été modifié ?
|
||||
# if [ $(md5sum "$scodoc_config_filename" | cut -f1 -d ' ') == "378caca5cb2e3b2753f5989c0762b8cc" ]
|
||||
# then
|
||||
# echo "copying $scodoc_config_filename to $SCODOC_VAR_DIR/scodoc_local.py"
|
||||
# cp "$scodoc_config_filename" "$SCODOC_VAR_DIR"/scodoc_local.py || die "erreur cp"
|
||||
# fi
|
||||
fi
|
||||
|
||||
# Templates locaux poursuites etudes
|
||||
if [ -e "${SCODOC7_HOME}"/config/doc_poursuites_etudes/local ]
|
||||
then
|
||||
|
Loading…
Reference in New Issue
Block a user