Config; des codes Apogée. Closes #111.

This commit is contained in:
Emmanuel Viennet 2022-01-21 00:46:45 +01:00
parent 687d5d6569
commit 90bff9ded6
10 changed files with 59 additions and 182 deletions

View File

@ -12,7 +12,7 @@ GROUPNAME_STR_LEN = 64
from app.models.raw_sql_init import create_database_functions from app.models.raw_sql_init import create_database_functions
from app.models.absences import Absence, AbsenceNotification, BilletAbsence from app.models.absences import Absence, AbsenceNotification, BilletAbsence
from app.models.config import ScoDocSiteConfig
from app.models.departements import Departement from app.models.departements import Departement
from app.models.entreprises import ( from app.models.entreprises import (
@ -63,7 +63,7 @@ from app.models.notes import (
NotesNotes, NotesNotes,
NotesNotesLog, NotesNotesLog,
) )
from app.models.preferences import ScoPreference, ScoDocSiteConfig from app.models.preferences import ScoPreference
from app.models.but_refcomp import ( from app.models.but_refcomp import (
ApcReferentielCompetences, ApcReferentielCompetences,

View File

@ -2,9 +2,8 @@
"""Model : preferences """Model : preferences
""" """
from app import db, log
from app.scodoc import bonus_sport from app import db
from app.scodoc.sco_exceptions import ScoValueError
class ScoPreference(db.Model): class ScoPreference(db.Model):
@ -19,108 +18,3 @@ class ScoPreference(db.Model):
name = db.Column(db.String(128), nullable=False, index=True) name = db.Column(db.String(128), nullable=False, index=True)
value = db.Column(db.Text()) value = db.Column(db.Text())
formsemestre_id = db.Column(db.Integer, db.ForeignKey("notes_formsemestre.id")) 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"
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
self.value = value
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.
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 ScoValueError 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
try:
return getattr(bonus_sport, func_name)
except AttributeError:
raise ScoValueError(
f"""Fonction de calcul maison inexistante: {func_name}.
(contacter votre administrateur local)."""
)
@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_")
]
)

View File

@ -95,30 +95,21 @@ from flask import send_file
# Pour la détection auto de l'encodage des fichiers Apogée: # Pour la détection auto de l'encodage des fichiers Apogée:
from chardet import detect as chardet_detect from chardet import detect as chardet_detect
from app.models.config import ScoDocSiteConfig
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log from app import log
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_vdi import ApoEtapeVDI from app.scodoc.sco_vdi import ApoEtapeVDI
from app.scodoc.sco_codes_parcours import code_semestre_validant from app.scodoc.sco_codes_parcours import code_semestre_validant
from app.scodoc.sco_codes_parcours import ( from app.scodoc.sco_codes_parcours import (
ADC,
ADJ,
ADM,
AJ,
ATB,
ATJ,
ATT,
CMP,
DEF, DEF,
DEM,
NAR, NAR,
RAT, RAT,
) )
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_status
from app.scodoc import sco_parcours_dut from app.scodoc import sco_parcours_dut
from app.scodoc import sco_etud from app.scodoc import sco_etud
@ -132,24 +123,6 @@ APO_SEP = "\t"
APO_NEWLINE = "\r\n" APO_NEWLINE = "\r\n"
def code_scodoc_to_apo(code):
"""Conversion code jury ScoDoc en code Apogée"""
return {
ATT: "AJAC",
ATB: "AJAC",
ATJ: "AJAC",
ADM: "ADM",
ADJ: "ADM",
ADC: "ADMC",
AJ: "AJ",
CMP: "COMP",
"DEM": "NAR",
DEF: "NAR",
NAR: "NAR",
RAT: "ATT",
}.get(code, "DEF")
def _apo_fmt_note(note): def _apo_fmt_note(note):
"Formatte une note pour Apogée (séparateur décimal: ',')" "Formatte une note pour Apogée (séparateur décimal: ',')"
if not note and isinstance(note, float): if not note and isinstance(note, float):
@ -449,7 +422,7 @@ class ApoEtud(dict):
N=_apo_fmt_note(ue_status["moy"]), N=_apo_fmt_note(ue_status["moy"]),
B=20, B=20,
J="", J="",
R=code_scodoc_to_apo(code_decision_ue), R=ScoDocSiteConfig.get_code_apo(code_decision_ue),
M="", M="",
) )
else: else:
@ -475,13 +448,9 @@ class ApoEtud(dict):
def comp_elt_semestre(self, nt, decision, etudid): def comp_elt_semestre(self, nt, decision, etudid):
"""Calcul résultat apo semestre""" """Calcul résultat apo semestre"""
# resultat du semestre # resultat du semestre
decision_apo = code_scodoc_to_apo(decision["code"]) decision_apo = ScoDocSiteConfig.get_code_apo(decision["code"])
note = nt.get_etud_moy_gen(etudid) note = nt.get_etud_moy_gen(etudid)
if ( if decision_apo == "DEF" or decision["code"] == DEM or decision["code"] == DEF:
decision_apo == "DEF"
or decision["code"] == "DEM"
or decision["code"] == DEF
):
note_str = "0,01" # note non nulle pour les démissionnaires note_str = "0,01" # note non nulle pour les démissionnaires
else: else:
note_str = _apo_fmt_note(note) note_str = _apo_fmt_note(note)
@ -520,21 +489,21 @@ class ApoEtud(dict):
# ou jury intermediaire et etudiant non redoublant... # ou jury intermediaire et etudiant non redoublant...
return self.comp_elt_semestre(cur_nt, cur_decision, etudid) return self.comp_elt_semestre(cur_nt, cur_decision, etudid)
decision_apo = code_scodoc_to_apo(cur_decision["code"]) decision_apo = ScoDocSiteConfig.get_code_apo(cur_decision["code"])
autre_nt = sco_cache.NotesTableCache.get(autre_sem["formsemestre_id"]) autre_nt = sco_cache.NotesTableCache.get(autre_sem["formsemestre_id"])
autre_decision = autre_nt.get_etud_decision_sem(etudid) autre_decision = autre_nt.get_etud_decision_sem(etudid)
if not autre_decision: if not autre_decision:
# pas de decision dans l'autre => pas de résultat annuel # pas de decision dans l'autre => pas de résultat annuel
return VOID_APO_RES return VOID_APO_RES
autre_decision_apo = code_scodoc_to_apo(autre_decision["code"]) autre_decision_apo = ScoDocSiteConfig.get_code_apo(autre_decision["code"])
if ( if (
autre_decision_apo == "DEF" autre_decision_apo == "DEF"
or autre_decision["code"] == "DEM" or autre_decision["code"] == DEM
or autre_decision["code"] == DEF or autre_decision["code"] == DEF
) or ( ) or (
decision_apo == "DEF" decision_apo == "DEF"
or cur_decision["code"] == "DEM" or cur_decision["code"] == DEM
or cur_decision["code"] == DEF or cur_decision["code"] == DEF
): ):
note_str = "0,01" # note non nulle pour les démissionnaires note_str = "0,01" # note non nulle pour les démissionnaires

View File

@ -125,6 +125,7 @@ CMP = "CMP" # utile pour UE seulement (indique UE acquise car semestre acquis)
NAR = "NAR" NAR = "NAR"
RAT = "RAT" # en attente rattrapage, sera ATT dans Apogée RAT = "RAT" # en attente rattrapage, sera ATT dans Apogée
DEF = "DEF" # défaillance (n'est pas un code jury dans scodoc mais un état, comme inscrit ou demission) DEF = "DEF" # défaillance (n'est pas un code jury dans scodoc mais un état, comme inscrit ou demission)
DEM = "DEM"
# codes actions # codes actions
REDOANNEE = "REDOANNEE" # redouble annee (va en Sn-1) REDOANNEE = "REDOANNEE" # redouble annee (va en Sn-1)

View File

@ -87,8 +87,10 @@ groupEditor = ndb.EditableTable(
group_list = groupEditor.list group_list = groupEditor.list
def get_group(group_id): def get_group(group_id: int):
"""Returns group object, with partition""" """Returns group object, with partition"""
if not isinstance(group_id, int):
raise ValueError("invalid group_id (%s)" % group_id)
r = ndb.SimpleDictFetch( r = ndb.SimpleDictFetch(
"""SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.* """SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.*
FROM group_descr gd, partition p FROM group_descr gd, partition p
@ -687,6 +689,10 @@ def setGroups(
group_id = fs[0].strip() group_id = fs[0].strip()
if not group_id: if not group_id:
continue continue
try:
group_id = int(group_id)
except ValueError as exc:
raise ValueError("invalid group_id: not an integer")
group = get_group(group_id) group = get_group(group_id)
# Anciens membres du groupe: # Anciens membres du groupe:
old_members = get_group_members(group_id) old_members = get_group_members(group_id)

View File

@ -169,7 +169,9 @@ def get_inscrits_etape(code_etape, anneeapogee=None, ntrials=2):
if doc: if doc:
break break
if not doc: if not doc:
raise ScoValueError("pas de réponse du portail ! (timeout=%s)" % portal_timeout) raise ScoValueError(
f"pas de réponse du portail ! <br>(timeout={portal_timeout}, requête: <tt>{req}</tt>)"
)
etuds = _normalize_apo_fields(xml_to_list_of_dicts(doc, req=req)) etuds = _normalize_apo_fields(xml_to_list_of_dicts(doc, req=req))
# Filtre sur annee inscription Apogee: # Filtre sur annee inscription Apogee:

View File

@ -92,6 +92,8 @@
<div class="sco_help">Les paramètres donnés ici s'appliquent à tout ScoDoc (tous les départements):</div> <div class="sco_help">Les paramètres donnés ici s'appliquent à tout ScoDoc (tous les départements):</div>
{{ render_field(form.bonus_sport_func_name, onChange="submit_form()")}} {{ render_field(form.bonus_sport_func_name, onChange="submit_form()")}}
<h1>Exports Apogée</h1>
<p><a href="{{url_for('scodoc.config_codes_decisions')}}">configuration des codes de décision</a></p>
<h1>Bibliothèque de logos</h1> <h1>Bibliothèque de logos</h1>
{% for dept_entry in form.depts.entries %} {% for dept_entry in form.depts.entries %}
{% set dept_form = dept_entry.form %} {% set dept_form = dept_entry.form %}

View File

@ -33,49 +33,38 @@ Emmanuel Viennet, 2021
import datetime import datetime
import io import io
import wtforms.validators
from app.auth.models import User
import os
import flask import flask
from flask import abort, flash, url_for, redirect, render_template, send_file from flask import abort, flash, url_for, redirect, render_template, send_file
from flask import request from flask import request
from flask.app import Flask
import flask_login import flask_login
from flask_login.utils import login_required, current_user from flask_login.utils import login_required, current_user
from flask_wtf import FlaskForm from PIL import Image as PILImage
from flask_wtf.file import FileField, FileAllowed
from werkzeug.exceptions import BadRequest, NotFound from werkzeug.exceptions import BadRequest, NotFound
from wtforms import SelectField, SubmitField, FormField, validators, Form, FieldList
from wtforms.fields import IntegerField
from wtforms.fields.simple import BooleanField, StringField, TextAreaField, HiddenField
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
import app
from app import db from app import db
from app.auth.models import User
from app.forms.main import config_forms from app.forms.main import config_forms
from app.forms.main.create_dept import CreateDeptForm from app.forms.main.create_dept import CreateDeptForm
from app.forms.main.config_apo import CodesDecisionsForm
from app import models
from app.models import Departement, Identite from app.models import Departement, Identite
from app.models import departements from app.models import departements
from app.models import FormSemestre, FormSemestreInscription from app.models import FormSemestre, FormSemestreInscription
import sco_version from app.models import ScoDocSiteConfig
from app.scodoc import sco_logos from app.scodoc import sco_codes_parcours, sco_logos
from app.scodoc import sco_find_etud from app.scodoc import sco_find_etud
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.decorators import ( from app.decorators import (
admin_required, admin_required,
scodoc7func, scodoc7func,
scodoc, scodoc,
permission_required_compat_scodoc7,
permission_required,
) )
from app.scodoc.sco_exceptions import AccessDenied from app.scodoc.sco_exceptions import AccessDenied
from app.scodoc.sco_logos import find_logo
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.views import scodoc_bp as bp from app.views import scodoc_bp as bp
import sco_version
from PIL import Image as PILImage
@bp.route("/") @bp.route("/")
@ -132,6 +121,28 @@ def toggle_dept_vis(dept_id):
return redirect(url_for("scodoc.index")) return redirect(url_for("scodoc.index"))
@bp.route("/ScoDoc/config_codes_decisions", methods=["GET", "POST"])
@admin_required
def config_codes_decisions():
"""Form config codes decisions"""
form = CodesDecisionsForm()
if request.method == "POST" and form.cancel.data: # cancel button
return redirect(url_for("scodoc.index"))
if form.validate_on_submit():
for code in models.config.CODES_SCODOC_TO_APO:
ScoDocSiteConfig.set_code_apo(code, getattr(form, code).data)
flash(f"Codes décisions enregistrés.")
return redirect(url_for("scodoc.index"))
elif request.method == "GET":
for code in models.config.CODES_SCODOC_TO_APO:
getattr(form, code).data = ScoDocSiteConfig.get_code_apo(code)
return render_template(
"config_codes_decisions.html",
form=form,
title="Configuration des codes de décisions",
)
@bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"]) @bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"])
@login_required @login_required
def table_etud_in_accessible_depts(): def table_etud_in_accessible_depts():

View File

@ -81,7 +81,7 @@ _l = _
class ChangePasswordForm(FlaskForm): class ChangePasswordForm(FlaskForm):
user_name = HiddenField() user_name = HiddenField()
old_password = PasswordField(_l("Identifiez-vous")) old_password = PasswordField(_l("Identifiez-vous"))
new_password = PasswordField(_l("Nouveau mot de passe")) new_password = PasswordField(_l("Nouveau mot de passe de l'utilisateur"))
bis_password = PasswordField( bis_password = PasswordField(
_l("Répéter"), _l("Répéter"),
validators=[ validators=[

View File

@ -300,14 +300,6 @@ def delete_dept(dept): # delete-dept
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb
from app.scodoc import sco_dept from app.scodoc import sco_dept
if False:
click.confirm(
f"""Attention: Cela va effacer toutes les données du département {dept}
(étudiants, notes, formations, etc)
Voulez-vous vraiment continuer ?
""",
abort=True,
)
db.reflect() db.reflect()
ndb.open_db_connection() ndb.open_db_connection()
d = models.Departement.query.filter_by(acronym=dept).first() d = models.Departement.query.filter_by(acronym=dept).first()