Merge pull request 'master' (#1) from ScoDoc/ScoDoc:master into master
Reviewed-on: pascal.bouron/ScoDoc_Lyon#1
This commit is contained in:
commit
f843e3132a
|
@ -0,0 +1,78 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# ScoDoc
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
Formulaires configuration Exports Apogée (codes)
|
||||
"""
|
||||
import re
|
||||
|
||||
from flask import flash, url_for, redirect, render_template
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import SubmitField, validators
|
||||
from wtforms.fields.simple import StringField
|
||||
|
||||
from app import models
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models import SHORT_STR_LEN
|
||||
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
def _build_code_field(code):
|
||||
return StringField(
|
||||
label=code,
|
||||
description=sco_codes_parcours.CODES_EXPL[code],
|
||||
validators=[
|
||||
validators.regexp(
|
||||
r"^[A-Z0-9_]*$",
|
||||
message="Ne doit comporter que majuscules et des chiffres",
|
||||
),
|
||||
validators.Length(
|
||||
max=SHORT_STR_LEN,
|
||||
message=f"L'acronyme ne doit pas dépasser {SHORT_STR_LEN} caractères",
|
||||
),
|
||||
validators.DataRequired("code requis"),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class CodesDecisionsForm(FlaskForm):
|
||||
ADC = _build_code_field("ADC")
|
||||
ADJ = _build_code_field("ADJ")
|
||||
ADM = _build_code_field("ADM")
|
||||
AJ = _build_code_field("AJ")
|
||||
ATB = _build_code_field("ATB")
|
||||
ATJ = _build_code_field("ATJ")
|
||||
ATT = _build_code_field("ATT")
|
||||
CMP = _build_code_field("CMP")
|
||||
DEF = _build_code_field("DEF")
|
||||
DEM = _build_code_field("DEF")
|
||||
NAR = _build_code_field("NAR")
|
||||
RAT = _build_code_field("RAT")
|
||||
submit = SubmitField("Valider")
|
||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
# -*- coding: UTF-8 -*
|
||||
|
||||
"""Model : site config WORK IN PROGRESS #WIP
|
||||
"""
|
||||
|
||||
from app import db, log
|
||||
from app.scodoc import bonus_sport
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
import functools
|
||||
|
||||
from app.scodoc.sco_codes_parcours import (
|
||||
ADC,
|
||||
ADJ,
|
||||
ADM,
|
||||
AJ,
|
||||
ATB,
|
||||
ATJ,
|
||||
ATT,
|
||||
CMP,
|
||||
DEF,
|
||||
DEM,
|
||||
NAR,
|
||||
RAT,
|
||||
)
|
||||
|
||||
CODES_SCODOC_TO_APO = {
|
||||
ADC: "ADMC",
|
||||
ADJ: "ADM",
|
||||
ADM: "ADM",
|
||||
AJ: "AJ",
|
||||
ATB: "AJAC",
|
||||
ATJ: "AJAC",
|
||||
ATT: "AJAC",
|
||||
CMP: "COMP",
|
||||
DEF: "NAR",
|
||||
DEM: "NAR",
|
||||
NAR: "NAR",
|
||||
RAT: "ATT",
|
||||
}
|
||||
|
||||
|
||||
def code_scodoc_to_apo_default(code):
|
||||
"""Conversion code jury ScoDoc en code Apogée
|
||||
(codes par défaut, c'est configurable via ScoDocSiteConfig.get_code_apo)
|
||||
"""
|
||||
return CODES_SCODOC_TO_APO.get(code, "DEF")
|
||||
|
||||
|
||||
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}')>"
|
||||
|
||||
@classmethod
|
||||
def get_dict(cls) -> dict:
|
||||
"Returns all data as a dict name = value"
|
||||
return {
|
||||
c.name: cls.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_")
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_code_apo(cls, code: str) -> str:
|
||||
"""La représentation d'un code pour les exports Apogée.
|
||||
Par exemple, à l'iUT du H., le code ADM est réprésenté par VAL
|
||||
Les codes par défaut sont donnés dans sco_apogee_csv.
|
||||
|
||||
"""
|
||||
cfg = ScoDocSiteConfig.query.filter_by(name=code).first()
|
||||
if not cfg:
|
||||
code_apo = code_scodoc_to_apo_default(code)
|
||||
else:
|
||||
code_apo = cfg.value
|
||||
return code_apo
|
||||
|
||||
@classmethod
|
||||
def set_code_apo(cls, code: str, code_apo: str):
|
||||
"""Enregistre nouvelle représentation du code"""
|
||||
if code_apo != cls.get_code_apo(code):
|
||||
cfg = ScoDocSiteConfig.query.filter_by(name=code).first()
|
||||
if cfg is None:
|
||||
cfg = ScoDocSiteConfig(code, code_apo)
|
||||
else:
|
||||
cfg.value = code_apo
|
||||
db.session.add(cfg)
|
||||
db.session.commit()
|
|
@ -1,6 +1,7 @@
|
|||
"""ScoDoc 9 models : Formations
|
||||
"""
|
||||
|
||||
import app
|
||||
from app import db
|
||||
from app.comp import df_cache
|
||||
from app.models import SHORT_STR_LEN
|
||||
|
@ -141,8 +142,7 @@ class Formation(db.Model):
|
|||
db.session.add(ue)
|
||||
|
||||
db.session.commit()
|
||||
if change:
|
||||
self.invalidate_module_coefs()
|
||||
app.clear_scodoc_cache()
|
||||
|
||||
|
||||
class Matiere(db.Model):
|
||||
|
|
|
@ -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_")
|
||||
]
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
@ -140,22 +141,26 @@ BUG = "BUG"
|
|||
|
||||
ALL = "ALL"
|
||||
|
||||
# Explication des codes (de demestre ou d'UE)
|
||||
CODES_EXPL = {
|
||||
ADM: "Validé",
|
||||
ADC: "Validé par compensation",
|
||||
ADJ: "Validé par le Jury",
|
||||
ATT: "Décision en attente d'un autre semestre (faute d'atteindre la moyenne)",
|
||||
ADM: "Validé",
|
||||
AJ: "Ajourné",
|
||||
ATB: "Décision en attente d'un autre semestre (au moins une UE sous la barre)",
|
||||
ATJ: "Décision en attente d'un autre semestre (assiduité insuffisante)",
|
||||
AJ: "Ajourné",
|
||||
NAR: "Echec, non autorisé à redoubler",
|
||||
RAT: "En attente d'un rattrapage",
|
||||
ATT: "Décision en attente d'un autre semestre (faute d'atteindre la moyenne)",
|
||||
CMP: "Code UE acquise car semestre acquis",
|
||||
DEF: "Défaillant",
|
||||
NAR: "Échec, non autorisé à redoubler",
|
||||
RAT: "En attente d'un rattrapage",
|
||||
}
|
||||
# Nota: ces explications sont personnalisables via le fichier
|
||||
# de config locale /opt/scodoc/var/scodoc/config/scodoc_local.py
|
||||
# variable: CONFIG.CODES_EXP
|
||||
|
||||
# Les codes de semestres:
|
||||
CODES_JURY_SEM = {ADC, ADJ, ADM, AJ, ATB, ATJ, ATT, DEF, NAR, RAT}
|
||||
CODES_SEM_VALIDES = {ADM: True, ADC: True, ADJ: True} # semestre validé
|
||||
CODES_SEM_ATTENTES = {ATT: True, ATB: True, ATJ: True} # semestre en attente
|
||||
|
||||
|
|
|
@ -738,7 +738,7 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
|
|||
)
|
||||
|
||||
# Choix code semestre:
|
||||
codes = list(sco_codes_parcours.CODES_EXPL.keys())
|
||||
codes = list(sco_codes_parcours.CODES_JURY_SEM)
|
||||
codes.sort() # fortuitement, cet ordre convient bien !
|
||||
|
||||
H.append(
|
||||
|
|
|
@ -87,7 +87,7 @@ groupEditor = ndb.EditableTable(
|
|||
group_list = groupEditor.list
|
||||
|
||||
|
||||
def get_group(group_id):
|
||||
def get_group(group_id: int):
|
||||
"""Returns group object, with partition"""
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.*
|
||||
|
@ -687,6 +687,11 @@ def setGroups(
|
|||
group_id = fs[0].strip()
|
||||
if not group_id:
|
||||
continue
|
||||
try:
|
||||
group_id = int(group_id)
|
||||
except ValueError as exc:
|
||||
log("setGroups: ignoring invalid group_id={group_id}")
|
||||
continue
|
||||
group = get_group(group_id)
|
||||
# Anciens membres du groupe:
|
||||
old_members = get_group_members(group_id)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -567,7 +567,7 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
|||
if "prev_decision" in row and row["prev_decision"]:
|
||||
counts[row["prev_decision"]] += 0
|
||||
# Légende des codes
|
||||
codes = list(counts.keys()) # sco_codes_parcours.CODES_EXPL.keys()
|
||||
codes = list(counts.keys())
|
||||
codes.sort()
|
||||
H.append("<h3>Explication des codes</h3>")
|
||||
lines = []
|
||||
|
|
|
@ -153,7 +153,10 @@ def _check_notes(notes, evaluation, mod):
|
|||
|
||||
for (etudid, note) in notes:
|
||||
note = str(note).strip().upper()
|
||||
try:
|
||||
etudid = int(etudid) #
|
||||
except ValueError as exc:
|
||||
raise ScoValueError(f"Code étudiant ({etudid}) invalide")
|
||||
if note[:3] == "DEM":
|
||||
continue # skip !
|
||||
if note:
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{% extends "base.html" %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Configuration des codes de décision exportés vers Apogée</h1>
|
||||
|
||||
|
||||
<div class="help">
|
||||
<p>Ces codes (ADM, AJ, ...) sont utilisés pour représenter les décisions de jury
|
||||
et les validations de semestres ou d'UE. les valeurs indiquées ici sont utilisées
|
||||
dans les exports Apogée.
|
||||
<p>
|
||||
<p>Ne les modifier que si vous savez ce que vous faites !
|
||||
</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{{ wtf.quick_form(form) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -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 %}
|
||||
|
|
|
@ -290,13 +290,17 @@ def formsemestre_bulletinetud(
|
|||
if etudid:
|
||||
etud = models.Identite.query.get_or_404(etudid)
|
||||
elif code_nip:
|
||||
etud = models.Identite.query.filter_by(
|
||||
code_nip=str(code_nip)
|
||||
).first_or_404()
|
||||
etud = (
|
||||
models.Identite.query.filter_by(code_nip=str(code_nip))
|
||||
.filter_by(dept_id=formsemestre.dept_id)
|
||||
.first_or_404()
|
||||
)
|
||||
elif code_ine:
|
||||
etud = models.Identite.query.filter_by(
|
||||
code_ine=str(code_ine)
|
||||
).first_or_404()
|
||||
etud = (
|
||||
models.Identite.query.filter_by(code_ine=str(code_ine))
|
||||
.filter_by(dept_id=formsemestre.dept_id)
|
||||
.first_or_404()
|
||||
)
|
||||
else:
|
||||
raise ScoValueError(
|
||||
"Paramètre manquant: spécifier code_nip ou etudid ou code_ine"
|
||||
|
|
|
@ -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():
|
||||
|
@ -255,14 +266,16 @@ def _return_logo(name="header", dept_id="", small=False, strict: bool = True):
|
|||
suffix = logo.suffix
|
||||
if small:
|
||||
with PILImage.open(logo.filepath) as im:
|
||||
im.thumbnail(SMALL_SIZE)
|
||||
stream = io.BytesIO()
|
||||
# on garde le même format (on pourrait plus simplement générer systématiquement du JPEG)
|
||||
fmt = { # adapt suffix to be compliant with PIL save format
|
||||
"PNG": "PNG",
|
||||
"JPG": "JPEG",
|
||||
"JPEG": "JPEG",
|
||||
}[suffix.upper()]
|
||||
if fmt == "JPEG":
|
||||
im = im.convert("RGB")
|
||||
im.thumbnail(SMALL_SIZE)
|
||||
stream = io.BytesIO()
|
||||
im.save(stream, fmt)
|
||||
stream.seek(0)
|
||||
return send_file(stream, mimetype=f"image/{fmt}")
|
||||
|
|
|
@ -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=[
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.1.29"
|
||||
SCOVERSION = "9.1.32"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue