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.absences import Absence, AbsenceNotification, BilletAbsence
from app.models.config import ScoDocSiteConfig
from app.models.departements import Departement
from app.models.entreprises import (
@ -63,7 +63,7 @@ from app.models.notes import (
NotesNotes,
NotesNotesLog,
)
from app.models.preferences import ScoPreference, ScoDocSiteConfig
from app.models.preferences import ScoPreference
from app.models.but_refcomp import (
ApcReferentielCompetences,

View File

@ -2,9 +2,8 @@
"""Model : preferences
"""
from app import db, log
from app.scodoc import bonus_sport
from app.scodoc.sco_exceptions import ScoValueError
from app import db
class ScoPreference(db.Model):
@ -19,108 +18,3 @@ 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"
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:
from chardet import detect as chardet_detect
from app.models.config import ScoDocSiteConfig
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_vdi import ApoEtapeVDI
from app.scodoc.sco_codes_parcours import code_semestre_validant
from app.scodoc.sco_codes_parcours import (
ADC,
ADJ,
ADM,
AJ,
ATB,
ATJ,
ATT,
CMP,
DEF,
DEM,
NAR,
RAT,
)
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_status
from app.scodoc import sco_parcours_dut
from app.scodoc import sco_etud
@ -132,24 +123,6 @@ APO_SEP = "\t"
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):
"Formatte une note pour Apogée (séparateur décimal: ',')"
if not note and isinstance(note, float):
@ -449,7 +422,7 @@ class ApoEtud(dict):
N=_apo_fmt_note(ue_status["moy"]),
B=20,
J="",
R=code_scodoc_to_apo(code_decision_ue),
R=ScoDocSiteConfig.get_code_apo(code_decision_ue),
M="",
)
else:
@ -475,13 +448,9 @@ class ApoEtud(dict):
def comp_elt_semestre(self, nt, decision, etudid):
"""Calcul résultat apo 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)
if (
decision_apo == "DEF"
or decision["code"] == "DEM"
or decision["code"] == DEF
):
if decision_apo == "DEF" or decision["code"] == DEM or decision["code"] == DEF:
note_str = "0,01" # note non nulle pour les démissionnaires
else:
note_str = _apo_fmt_note(note)
@ -520,21 +489,21 @@ class ApoEtud(dict):
# ou jury intermediaire et etudiant non redoublant...
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_decision = autre_nt.get_etud_decision_sem(etudid)
if not autre_decision:
# pas de decision dans l'autre => pas de résultat annuel
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 (
autre_decision_apo == "DEF"
or autre_decision["code"] == "DEM"
or autre_decision["code"] == DEM
or autre_decision["code"] == DEF
) or (
decision_apo == "DEF"
or cur_decision["code"] == "DEM"
or cur_decision["code"] == DEM
or cur_decision["code"] == DEF
):
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"
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)
DEM = "DEM"
# codes actions
REDOANNEE = "REDOANNEE" # redouble annee (va en Sn-1)

View File

@ -87,8 +87,10 @@ groupEditor = ndb.EditableTable(
group_list = groupEditor.list
def get_group(group_id):
def get_group(group_id: int):
"""Returns group object, with partition"""
if not isinstance(group_id, int):
raise ValueError("invalid group_id (%s)" % group_id)
r = ndb.SimpleDictFetch(
"""SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.*
FROM group_descr gd, partition p
@ -687,6 +689,10 @@ def setGroups(
group_id = fs[0].strip()
if not group_id:
continue
try:
group_id = int(group_id)
except ValueError as exc:
raise ValueError("invalid group_id: not an integer")
group = get_group(group_id)
# Anciens membres du groupe:
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:
break
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))
# 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>
{{ 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>
{% for dept_entry in form.depts.entries %}
{% set dept_form = dept_entry.form %}

View File

@ -33,49 +33,38 @@ Emmanuel Viennet, 2021
import datetime
import io
import wtforms.validators
from app.auth.models import User
import os
import flask
from flask import abort, flash, url_for, redirect, render_template, send_file
from flask import request
from flask.app import Flask
import flask_login
from flask_login.utils import login_required, current_user
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
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
from PIL import Image as PILImage
from werkzeug.exceptions import BadRequest, NotFound
import app
from app import db
from app.auth.models import User
from app.forms.main import config_forms
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 departements
from app.models import FormSemestre, FormSemestreInscription
import sco_version
from app.scodoc import sco_logos
from app.models import ScoDocSiteConfig
from app.scodoc import sco_codes_parcours, sco_logos
from app.scodoc import sco_find_etud
from app.scodoc import sco_utils as scu
from app.decorators import (
admin_required,
scodoc7func,
scodoc,
permission_required_compat_scodoc7,
permission_required,
)
from app.scodoc.sco_exceptions import AccessDenied
from app.scodoc.sco_logos import find_logo
from app.scodoc.sco_permissions import Permission
from app.views import scodoc_bp as bp
from PIL import Image as PILImage
import sco_version
@bp.route("/")
@ -132,6 +121,28 @@ def toggle_dept_vis(dept_id):
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"])
@login_required
def table_etud_in_accessible_depts():

View File

@ -81,7 +81,7 @@ _l = _
class ChangePasswordForm(FlaskForm):
user_name = HiddenField()
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(
_l("Répéter"),
validators=[

View File

@ -300,14 +300,6 @@ def delete_dept(dept): # delete-dept
from app.scodoc import notesdb as ndb
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()
ndb.open_db_connection()
d = models.Departement.query.filter_by(acronym=dept).first()