1
0
forked from ScoDoc/ScoDoc

Cache coefs et poids. Check conformité PN.

This commit is contained in:
Emmanuel Viennet 2021-11-29 22:18:37 +01:00
parent 8647203f43
commit 47e752c95c
7 changed files with 165 additions and 8 deletions

49
app/comp/df_cache.py Normal file
View File

@ -0,0 +1,49 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2021 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
#
##############################################################################
"""caches pour tables APC
"""
from app.scodoc import sco_cache
class ModuleCoefsCache(sco_cache.ScoDocCache):
"""Cache for module coefs
Clé: formation_id.semestre_idx
Valeur: DataFrame (df_load_module_coefs)
"""
prefix = "MCO"
class EvaluationsPoidsCache(sco_cache.ScoDocCache):
"""Cache for poids evals
Clé: moduleimpl_id
Valeur: DataFrame (df_load_evaluations_poids)
"""
prefix = "EPC"

View File

@ -38,7 +38,7 @@ from app.models.formsemestre import FormSemestre
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
def df_load_module_coefs(formation_id: int, semestre_idx: int) -> pd.DataFrame: def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.DataFrame:
"""Charge les coefs des modules de la formation pour le semestre indiqué. """Charge les coefs des modules de la formation pour le semestre indiqué.
Ces coefs lient les modules à chaque UE. Ces coefs lient les modules à chaque UE.

View File

@ -52,6 +52,7 @@ class Evaluation(db.Model):
e = dict(self.__dict__) e = dict(self.__dict__)
e.pop("_sa_instance_state", None) e.pop("_sa_instance_state", None)
# ScoDoc7 output_formators # ScoDoc7 output_formators
e["evaluation_id"] = self.id
e["jour"] = ndb.DateISOtoDMY(e["jour"]) e["jour"] = ndb.DateISOtoDMY(e["jour"])
e["numero"] = ndb.int_null_is_zero(e["numero"]) e["numero"] = ndb.int_null_is_zero(e["numero"])
return sco_evaluation_db.evaluation_enrich_dict(e) return sco_evaluation_db.evaluation_enrich_dict(e)
@ -88,6 +89,7 @@ class Evaluation(db.Model):
ue = UniteEns.query.get(ue_id) ue = UniteEns.query.get(ue_id)
L.append(EvaluationUEPoids(evaluation=self, ue=ue, poids=poids)) L.append(EvaluationUEPoids(evaluation=self, ue=ue, poids=poids))
self.ue_poids = L self.ue_poids = L
self.moduleimpl.invalidate_evaluations_poids() # inval cache
def update_ue_poids_dict(self, ue_poids_dict: dict): def update_ue_poids_dict(self, ue_poids_dict: dict):
"""update poids vers UE (ajoute aux existants)""" """update poids vers UE (ajoute aux existants)"""

View File

@ -9,6 +9,7 @@ from app.scodoc import notesdb as ndb
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.comp import df_cache
class Formation(db.Model): class Formation(db.Model):
@ -46,6 +47,34 @@ class Formation(db.Model):
"""get l'instance de TypeParcours de cette formation""" """get l'instance de TypeParcours de cette formation"""
return sco_codes_parcours.get_parcours_from_code(self.type_parcours) return sco_codes_parcours.get_parcours_from_code(self.type_parcours)
def get_module_coefs(self, semestre_idx: int = None):
"""Les coefs des modules vers les UE (accès via cache)"""
from app.comp import moy_ue
if semestre_idx is None:
key = f"{self.id}"
else:
key = f"{self.id}.{semestre_idx}"
modules_coefficients = df_cache.ModuleCoefsCache.get(key)
if modules_coefficients is None:
modules_coefficients, _, _ = moy_ue.df_load_module_coefs(
self.id, semestre_idx
)
df_cache.ModuleCoefsCache.set(key, modules_coefficients)
return modules_coefficients
def invalidate_module_coefs(self, semestre_idx: int = None):
"""Invalide les coefficients de modules cachés.
Si semestre_idx est None, invalide tous les semestres,
sinon invalide le semestre indiqué et le cache de la formation.
"""
if semestre_idx is None:
keys = {f"{self.id}.{m.semestre_id}" for m in self.modules}
else:
keys = f"{self.id}.{semestre_idx}"
df_cache.ModuleCoefsCache.delete_many(keys | {f"{self.id}"})
class UniteEns(db.Model): class UniteEns(db.Model):
"""Unité d'Enseignement (UE)""" """Unité d'Enseignement (UE)"""
@ -186,6 +215,13 @@ class Module(db.Model):
f"<Module{ModuleType(self.module_type).name} id={self.id} code={self.code}>" f"<Module{ModuleType(self.module_type).name} id={self.id} code={self.code}>"
) )
def is_apc(self):
"True si module SAÉ ou Ressource"
return scu.ModuleType(self.module_type) in {
scu.ModuleType.RESSOURCE,
scu.ModuleType.SAE,
}
def type_name(self): def type_name(self):
return scu.MODULE_TYPE_NAMES[self.module_type] return scu.MODULE_TYPE_NAMES[self.module_type]
@ -206,6 +242,7 @@ class Module(db.Model):
else: else:
ue_coefs.append(ModuleUECoef(module=self, ue=ue, coef=coef)) ue_coefs.append(ModuleUECoef(module=self, ue=ue, coef=coef))
self.ue_coefs = ue_coefs self.ue_coefs = ue_coefs
self.formation.invalidate_module_coefs()
def update_ue_coef_dict(self, ue_coef_dict: dict): def update_ue_coef_dict(self, ue_coef_dict: dict):
"""update coefs vers UE (ajoute aux existants)""" """update coefs vers UE (ajoute aux existants)"""
@ -222,6 +259,7 @@ class Module(db.Model):
ue_coef = ModuleUECoef.query.get((self.id, ue.id)) ue_coef = ModuleUECoef.query.get((self.id, ue.id))
if ue_coef: if ue_coef:
db.session.delete(ue_coef) db.session.delete(ue_coef)
self.formation.invalidate_module_coefs()
def ue_coefs_descr(self): def ue_coefs_descr(self):
"""List of tuples [ (ue_acronyme, coef) ]""" """List of tuples [ (ue_acronyme, coef) ]"""

View File

@ -4,7 +4,10 @@
""" """
from typing import Any from typing import Any
import pandas as pd
from app import db from app import db
from app.comp import df_cache
from app.models import APO_CODE_STR_LEN from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN from app.models import CODE_STR_LEN
@ -36,6 +39,55 @@ class ModuleImpl(db.Model):
computation_expr = db.Column(db.Text()) computation_expr = db.Column(db.Text())
evaluations = db.relationship("Evaluation", lazy="dynamic", backref="moduleimpl") evaluations = db.relationship("Evaluation", lazy="dynamic", backref="moduleimpl")
enseignants = db.relationship(
"User",
secondary="notes_modules_enseignants",
lazy="dynamic",
backref="moduleimpl",
viewonly=True,
)
def __init__(self, **kwargs):
super(ModuleImpl, self).__init__(**kwargs)
def get_evaluations_poids(self) -> pd.DataFrame:
"""Les poids des évaluations vers les UE (accès via cache)"""
evaluations_poids = df_cache.EvaluationsPoidsCache.get(self.id)
if evaluations_poids is None:
from app.comp import moy_mod
evaluations_poids, _ = moy_mod.df_load_evaluations_poids(self.id)
df_cache.EvaluationsPoidsCache.set(self.id, evaluations_poids)
return evaluations_poids
def invalidate_evaluations_poids(self):
"""Invalide poids cachés"""
df_cache.EvaluationsPoidsCache.delete(self.id)
def check_apc_conformity(self) -> bool:
"""true si les poids des évaluations du module permettent de satisfaire
les coefficients du PN.
"""
if not self.module.formation.get_parcours().APC_SAE:
return True
from app.comp import moy_mod
return moy_mod.check_moduleimpl_conformity(
self,
self.get_evaluations_poids(),
self.module.formation.get_module_coefs(self.module.semestre_id),
)
def to_dict(self):
"""as a dict, with the same conversions as in ScoDoc7"""
e = dict(self.__dict__)
e.pop("_sa_instance_state", None)
# ScoDoc7 output_formators: (backward compat)
e["moduleimpl_id"] = self.id
e["ens"] = [
{"moduleimpl_id": self.id, "ens_id": e.id} for e in self.enseignants
]
return e
# Enseignants (chargés de TD ou TP) d'un moduleimpl # Enseignants (chargés de TD ou TP) d'un moduleimpl
@ -70,7 +122,7 @@ class ModuleImplInscription(db.Model):
Identite, Identite,
backref=db.backref("moduleimpl_inscriptions", cascade="all, delete-orphan"), backref=db.backref("moduleimpl_inscriptions", cascade="all, delete-orphan"),
) )
moduleimpl = db.relationship( modimpl = db.relationship(
ModuleImpl, ModuleImpl,
backref=db.backref("inscriptions", cascade="all, delete-orphan"), backref=db.backref("inscriptions", cascade="all, delete-orphan"),
) )

View File

@ -29,7 +29,7 @@
-écrite pour ScoDoc8, utilise flask_caching et REDIS -écrite pour ScoDoc8, utilise flask_caching et REDIS
ScoDoc est maintenant multiprocessus / mono-thread, avec un cache en mémoire partagé. ScoDoc est maintenant multiprocessus / mono-thread, avec un cache partagé.
""" """

View File

@ -33,6 +33,7 @@ from flask import g, url_for
from flask_login import current_user from flask_login import current_user
from app.auth.models import User from app.auth.models import User
from app.models import ModuleImpl
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
@ -156,7 +157,8 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0):
def moduleimpl_status(moduleimpl_id=None, partition_id=None): def moduleimpl_status(moduleimpl_id=None, partition_id=None):
"""Tableau de bord module (liste des evaluations etc)""" """Tableau de bord module (liste des evaluations etc)"""
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] modimpl = ModuleImpl.query.get_or_404(moduleimpl_id)
M = modimpl.to_dict()
formsemestre_id = M["formsemestre_id"] formsemestre_id = M["formsemestre_id"]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0] Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
@ -224,10 +226,17 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
H.append("""</td><td>""") H.append("""</td><td>""")
if not sem["etat"]: if not sem["etat"]:
H.append(scu.icontag("lock32_img", title="verrouillé")) H.append(scu.icontag("lock32_img", title="verrouillé"))
H.append( H.append("""</td><td class="fichetitre2">Coef dans le semestre: """)
"""</td><td class="fichetitre2">Coef dans le semestre: %(coefficient)s</td><td></td></tr>""" if modimpl.module.is_apc():
% Mod coefs_descr = modimpl.module.ue_coefs_descr()
) if coefs_descr:
coefs_descr_txt = ", ".join(["%s: %s" % x for x in coefs_descr])
else:
coefs_descr_txt = """<span class="missing_value">non définis</span>"""
H.append(coefs_descr_txt)
else:
H.append(f"{modimpl.module.coefficient}")
H.append("""</td><td></td></tr>""")
# 3ieme ligne: Formation # 3ieme ligne: Formation
H.append( H.append(
"""<tr><td class="fichetitre2">Formation: </td><td>%(titre)s</td></tr>""" % F """<tr><td class="fichetitre2">Formation: </td><td>%(titre)s</td></tr>""" % F
@ -289,6 +298,13 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
H.append("</td></tr></table>") H.append("</td></tr></table>")
# #
if not modimpl.check_apc_conformity():
H.append(
"""<ul class="tf-msg"><li class="tf-msg warning conformite">Les poids des évaluations de ce module ne sont pas encore conformes au PN.
Ses notes ne peuvent pas être prises en compte dans les moyennes d'UE.
</li></ul>"""
)
#
if has_expression and nt.expr_diagnostics: if has_expression and nt.expr_diagnostics:
H.append(sco_formsemestre_status.html_expr_diagnostic(nt.expr_diagnostics)) H.append(sco_formsemestre_status.html_expr_diagnostic(nt.expr_diagnostics))
# #