From 9f8bfd3e2162729dbbd815b807c8b5fc8ab1f496 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 10 Feb 2022 21:55:06 +0100 Subject: [PATCH 01/56] refactoring / sco_groups sans nt --- app/models/evaluations.py | 98 +++++++++++++++++++++++++++++++-- app/scodoc/sco_evaluation_db.py | 98 ++------------------------------- app/scodoc/sco_groups.py | 14 ++--- sco_version.py | 3 +- 4 files changed, 108 insertions(+), 105 deletions(-) diff --git a/app/models/evaluations.py b/app/models/evaluations.py index b4e5f4e2..46244a9a 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -2,12 +2,16 @@ """ScoDoc models: evaluations """ +import datetime from app import db -from app.models import UniteEns +from app.models import formsemestre +from app.models.formsemestre import FormSemestre +from app.models.moduleimpls import ModuleImpl +from app.models.ues import UniteEns +from app.scodoc.sco_exceptions import ScoValueError import app.scodoc.notesdb as ndb -from app.scodoc import sco_evaluation_db class Evaluation(db.Model): @@ -51,11 +55,11 @@ class Evaluation(db.Model): e["evaluation_id"] = self.id e["jour"] = ndb.DateISOtoDMY(e["jour"]) e["numero"] = ndb.int_null_is_zero(e["numero"]) - return sco_evaluation_db.evaluation_enrich_dict(e) + return evaluation_enrich_dict(e) def from_dict(self, data): """Set evaluation attributes from given dict values.""" - sco_evaluation_db._check_evaluation_args(data) + check_evaluation_args(data) for k in self.__dict__.keys(): if k != "_sa_instance_state" and k != "id" and k in data: setattr(self, k, data[k]) @@ -145,3 +149,89 @@ class EvaluationUEPoids(db.Model): def __repr__(self): return f"" + + +# Fonction héritée de ScoDoc7 à refactorer +def evaluation_enrich_dict(e): + """add or convert some fileds in an evaluation dict""" + # For ScoDoc7 compat + heure_debut_dt = e["heure_debut"] or datetime.time( + 8, 00 + ) # au cas ou pas d'heure (note externe?) + heure_fin_dt = e["heure_fin"] or datetime.time(8, 00) + e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"]) + e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"]) + e["jouriso"] = ndb.DateDMYtoISO(e["jour"]) + heure_debut, heure_fin = e["heure_debut"], e["heure_fin"] + d = ndb.TimeDuration(heure_debut, heure_fin) + if d is not None: + m = d % 60 + e["duree"] = "%dh" % (d / 60) + if m != 0: + e["duree"] += "%02d" % m + else: + e["duree"] = "" + if heure_debut and (not heure_fin or heure_fin == heure_debut): + e["descrheure"] = " à " + heure_debut + elif heure_debut and heure_fin: + e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin) + else: + e["descrheure"] = "" + # matin, apresmidi: utile pour se referer aux absences: + if heure_debut_dt < datetime.time(12, 00): + e["matin"] = 1 + else: + e["matin"] = 0 + if heure_fin_dt > datetime.time(12, 00): + e["apresmidi"] = 1 + else: + e["apresmidi"] = 0 + return e + + +def check_evaluation_args(args): + "Check coefficient, dates and duration, raises exception if invalid" + moduleimpl_id = args["moduleimpl_id"] + # check bareme + note_max = args.get("note_max", None) + if note_max is None: + raise ScoValueError("missing note_max") + try: + note_max = float(note_max) + except ValueError: + raise ScoValueError("Invalid note_max value") + if note_max < 0: + raise ScoValueError("Invalid note_max value (must be positive or null)") + # check coefficient + coef = args.get("coefficient", None) + if coef is None: + raise ScoValueError("missing coefficient") + try: + coef = float(coef) + except ValueError: + raise ScoValueError("Invalid coefficient value") + if coef < 0: + raise ScoValueError("Invalid coefficient value (must be positive or null)") + # check date + jour = args.get("jour", None) + args["jour"] = jour + if jour: + modimpl = ModuleImpl.query.get(moduleimpl_id) + formsemestre = modimpl.formsemestre + y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")] + jour = datetime.date(y, m, d) + if (jour > formsemestre.date_fin) or (jour < formsemestre.date_debut): + raise ScoValueError( + "La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !" + % (d, m, y), + dest_url="javascript:history.back();", + ) + heure_debut = args.get("heure_debut", None) + args["heure_debut"] = heure_debut + heure_fin = args.get("heure_fin", None) + args["heure_fin"] = heure_fin + if jour and ((not heure_debut) or (not heure_fin)): + raise ScoValueError("Les heures doivent être précisées") + d = ndb.TimeDuration(heure_debut, heure_fin) + if d and ((d < 0) or (d > 60 * 12)): + raise ScoValueError("Heures de l'évaluation incohérentes !") diff --git a/app/scodoc/sco_evaluation_db.py b/app/scodoc/sco_evaluation_db.py index 6586ddd0..23bc4579 100644 --- a/app/scodoc/sco_evaluation_db.py +++ b/app/scodoc/sco_evaluation_db.py @@ -36,6 +36,8 @@ from flask import url_for, g from flask_login import current_user from app import log + +from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc.sco_exceptions import AccessDenied, ScoValueError @@ -81,43 +83,6 @@ _evaluationEditor = ndb.EditableTable( ) -def evaluation_enrich_dict(e): - """add or convert some fileds in an evaluation dict""" - # For ScoDoc7 compat - heure_debut_dt = e["heure_debut"] or datetime.time( - 8, 00 - ) # au cas ou pas d'heure (note externe?) - heure_fin_dt = e["heure_fin"] or datetime.time(8, 00) - e["heure_debut"] = ndb.TimefromISO8601(e["heure_debut"]) - e["heure_fin"] = ndb.TimefromISO8601(e["heure_fin"]) - e["jouriso"] = ndb.DateDMYtoISO(e["jour"]) - heure_debut, heure_fin = e["heure_debut"], e["heure_fin"] - d = ndb.TimeDuration(heure_debut, heure_fin) - if d is not None: - m = d % 60 - e["duree"] = "%dh" % (d / 60) - if m != 0: - e["duree"] += "%02d" % m - else: - e["duree"] = "" - if heure_debut and (not heure_fin or heure_fin == heure_debut): - e["descrheure"] = " à " + heure_debut - elif heure_debut and heure_fin: - e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin) - else: - e["descrheure"] = "" - # matin, apresmidi: utile pour se referer aux absences: - if heure_debut_dt < datetime.time(12, 00): - e["matin"] = 1 - else: - e["matin"] = 0 - if heure_fin_dt > datetime.time(12, 00): - e["apresmidi"] = 1 - else: - e["apresmidi"] = 0 - return e - - def do_evaluation_list(args, sortkey=None): """List evaluations, sorted by numero (or most recent date first). @@ -127,7 +92,7 @@ def do_evaluation_list(args, sortkey=None): 'apresmidi' : 1 (termine après 12:00) ou 0 'descrheure' : ' de 15h00 à 16h30' """ - # Attention: transformation fonction ScoDc7 en SQLAlchemy + # Attention: transformation fonction ScoDoc7 en SQLAlchemy cnx = ndb.GetDBConnexion() evals = _evaluationEditor.list(cnx, args, sortkey=sortkey) # calcule duree (chaine de car.) de chaque evaluation et ajoute jouriso, matin, apresmidi @@ -146,59 +111,6 @@ def do_evaluation_list_in_formsemestre(formsemestre_id): return evals -def _check_evaluation_args(args): - "Check coefficient, dates and duration, raises exception if invalid" - moduleimpl_id = args["moduleimpl_id"] - # check bareme - note_max = args.get("note_max", None) - if note_max is None: - raise ScoValueError("missing note_max") - try: - note_max = float(note_max) - except ValueError: - raise ScoValueError("Invalid note_max value") - if note_max < 0: - raise ScoValueError("Invalid note_max value (must be positive or null)") - # check coefficient - coef = args.get("coefficient", None) - if coef is None: - raise ScoValueError("missing coefficient") - try: - coef = float(coef) - except ValueError: - raise ScoValueError("Invalid coefficient value") - if coef < 0: - raise ScoValueError("Invalid coefficient value (must be positive or null)") - # check date - jour = args.get("jour", None) - args["jour"] = jour - if jour: - M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] - sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"]) - d, m, y = [int(x) for x in sem["date_debut"].split("/")] - date_debut = datetime.date(y, m, d) - d, m, y = [int(x) for x in sem["date_fin"].split("/")] - date_fin = datetime.date(y, m, d) - # passe par ndb.DateDMYtoISO pour avoir date pivot - y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")] - jour = datetime.date(y, m, d) - if (jour > date_fin) or (jour < date_debut): - raise ScoValueError( - "La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !" - % (d, m, y), - dest_url="javascript:history.back();", - ) - heure_debut = args.get("heure_debut", None) - args["heure_debut"] = heure_debut - heure_fin = args.get("heure_fin", None) - args["heure_fin"] = heure_fin - if jour and ((not heure_debut) or (not heure_fin)): - raise ScoValueError("Les heures doivent être précisées") - d = ndb.TimeDuration(heure_debut, heure_fin) - if d and ((d < 0) or (d > 60 * 12)): - raise ScoValueError("Heures de l'évaluation incohérentes !") - - def do_evaluation_create( moduleimpl_id=None, jour=None, @@ -220,7 +132,7 @@ def do_evaluation_create( ) args = locals() log("do_evaluation_create: args=" + str(args)) - _check_evaluation_args(args) + check_evaluation_args(args) # Check numeros module_evaluation_renumber(moduleimpl_id, only_if_unumbered=True) if not "numero" in args or args["numero"] is None: @@ -288,7 +200,7 @@ def do_evaluation_edit(args): "Modification évaluation impossible pour %s" % current_user.get_nomplogin() ) args["moduleimpl_id"] = moduleimpl_id - _check_evaluation_args(args) + check_evaluation_args(args) cnx = ndb.GetDBConnexion() _evaluationEditor.edit(cnx, args) diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index d1cfb47d..d5d55ea7 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -45,6 +45,9 @@ from flask import g, request from flask import url_for, make_response from app import db +from app.comp import res_sem +from app.comp.res_common import NotesTableCompat +from app.models import FormSemestre from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN from app.models.groups import Partition import app.scodoc.sco_utils as scu @@ -488,17 +491,14 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD ... """ - from app.scodoc import sco_formsemestre - - cnx = ndb.GetDBConnexion() - t0 = time.time() partition = get_partition(partition_id) formsemestre_id = partition["formsemestre_id"] - sem = sco_formsemestre.get_formsemestre(formsemestre_id) + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + etuds_set = {ins.etudid for ins in formsemestre.inscriptions} + + sem = formsemestre.get_infos_dict() # transition TODO groups = get_partition_groups(partition) - nt = sco_cache.NotesTableCache.get(formsemestre_id) # > inscrdict - etuds_set = set(nt.inscrdict) # Build XML: t1 = time.time() doc = Element("ajax-response") diff --git a/sco_version.py b/sco_version.py index a75f9a94..bda50cef 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,13 +1,14 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.1.50a" +SCOVERSION = "9.1.50" SCONAME = "ScoDoc" SCONEWS = """

Année 2021

") H.append("") - H.append( - """

Cette page décrit les inscriptions actuelles. - Vous pouvez changer (si vous en avez le droit) les inscrits dans chaque module en - cliquant sur la ligne du module.

-

Note: la déinscription d'un module ne perd pas les notes. Ainsi, si - l'étudiant est ensuite réinscrit au même module, il retrouvera ses notes.

- """ - ) + H.append( + """

Cette page décrit les inscriptions actuelles. + Vous pouvez changer (si vous en avez le droit) les inscrits dans chaque module en + cliquant sur la ligne du module.

+

Note: la déinscription d'un module ne perd pas les notes. Ainsi, si + l'étudiant est ensuite réinscrit au même module, il retrouvera ses notes.

+ """ + ) H.append(html_sco_header.sco_footer()) return "\n".join(H) diff --git a/app/scodoc/sco_prepajury.py b/app/scodoc/sco_prepajury.py index 1a683737..12a1f252 100644 --- a/app/scodoc/sco_prepajury.py +++ b/app/scodoc/sco_prepajury.py @@ -45,7 +45,6 @@ from app.scodoc import sco_codes_parcours import sco_version from app.scodoc import sco_etud from app.scodoc import sco_preferences -from app.scodoc.sco_excel import ScoExcelSheet def feuille_preparation_jury(formsemestre_id): From 3cafbf5988662d218fae0bbe03427acfc93e0f43 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 13 Feb 2022 15:19:39 +0100 Subject: [PATCH 37/56] Fixes #293 : suppression utilisateur --- app/auth/models.py | 6 ++++-- scodoc.py | 30 ++++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/app/auth/models.py b/app/auth/models.py index 32768df0..329bc386 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -76,7 +76,9 @@ class User(UserMixin, db.Model): "Departement", foreign_keys=[Departement.acronym], primaryjoin=(dept == Departement.acronym), - lazy="dynamic", + lazy="select", + passive_deletes="all", + uselist=False, ) def __init__(self, **kwargs): @@ -236,7 +238,7 @@ class User(UserMixin, db.Model): def get_dept_id(self) -> int: "returns user's department id, or None" if self.dept: - return self._departement.first().id + return self._departement.id return None # Permissions management: diff --git a/scodoc.py b/scodoc.py index 28dfe7cf..5b76ccb0 100755 --- a/scodoc.py +++ b/scodoc.py @@ -14,6 +14,8 @@ import click import flask from flask.cli import with_appcontext from flask.templating import render_template +import psycopg2 +import sqlalchemy from app import create_app, cli, db from app import initialize_scodoc_database @@ -133,11 +135,11 @@ def user_create(username, role, dept, nom=None, prenom=None): # user-create "Create a new user" r = Role.get_named_role(role) if not r: - sys.stderr.write("user_create: role {r} does not exist\n".format(r=role)) + sys.stderr.write(f"user_create: role {role} does not exist\n") return 1 u = User.query.filter_by(user_name=username).first() if u: - sys.stderr.write("user_create: user {u} already exists\n".format(u=u)) + sys.stderr.write(f"user_create: user {u} already exists\n") return 2 if dept == "@all": dept = None @@ -145,11 +147,26 @@ def user_create(username, role, dept, nom=None, prenom=None): # user-create u.add_role(r, dept) db.session.add(u) db.session.commit() - click.echo( - "created user, login: {u.user_name}, with role {r} in dept. {dept}".format( - u=u, r=r, dept=dept + click.echo(f"created user, login: {u.user_name}, with role {r} in dept. {dept}") + + +@app.cli.command() +@click.argument("username") +def user_delete(username): # user-delete + "Try to delete this user. Fails if it's associated to some scodoc objects." + u = User.query.filter_by(user_name=username).first() + if not u: + sys.stderr.write(f"user_delete: user {username} not found\n") + return 2 + db.session.delete(u) + try: + db.session.commit() + except (sqlalchemy.exc.IntegrityError, psycopg2.errors.ForeignKeyViolation): + sys.stderr.write( + f"""\nuser_delete: ne peux pas supprimer l'utilisateur {username}\ncar il est associé à des objets dans ScoDoc (modules, notes, ...).\n""" ) - ) + return 1 + click.echo(f"deleted user, login: {username}") @app.cli.command() @@ -485,6 +502,7 @@ def recursive_help(cmd, parent=None): @app.cli.command() def dumphelp(): + """Génère la page d'aide complète pour la doc.""" recursive_help(app.cli) From 45c845d23bb92c1b3311a5b21feb63ba06b61fe8 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 13 Feb 2022 15:50:16 +0100 Subject: [PATCH 38/56] was_capitalized / optim. -nt --- app/comp/res_common.py | 12 +++++- app/scodoc/sco_formsemestre_inscriptions.py | 17 ++++++--- app/scodoc/sco_moduleimpl_inscriptions.py | 8 +++- app/scodoc/sco_report.py | 42 ++++++++++----------- scodoc.py | 1 + 5 files changed, 52 insertions(+), 28 deletions(-) diff --git a/app/comp/res_common.py b/app/comp/res_common.py index 006cbe6b..5b9ef625 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -219,6 +219,7 @@ class ResultatsSemestre(ResultatsCache): if ue.type == UE_SPORT: return { "is_capitalized": False, + "was_capitalized": False, "is_external": False, "coef_ue": 0.0, "cur_moy_ue": 0.0, @@ -235,7 +236,10 @@ class ResultatsSemestre(ResultatsCache): self.validations = res_sem.load_formsemestre_validations(self.formsemestre) cur_moy_ue = self.etud_moy_ue[ue_id][etudid] moy_ue = cur_moy_ue - is_capitalized = False + is_capitalized = False # si l'UE prise en compte est une UE capitalisée + was_capitalized = ( + False # s'il y a precedemment une UE capitalisée (pas forcement meilleure) + ) if etudid in self.validations.ue_capitalisees.index: ue_cap = self._get_etud_ue_cap(etudid, ue) if ( @@ -243,6 +247,7 @@ class ResultatsSemestre(ResultatsCache): and not ue_cap.empty and not np.isnan(ue_cap["moy_ue"]) ): + was_capitalized = True if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue): moy_ue = ue_cap["moy_ue"] is_capitalized = True @@ -251,6 +256,7 @@ class ResultatsSemestre(ResultatsCache): return { "is_capitalized": is_capitalized, + "was_capitalized": was_capitalized, "is_external": ue_cap["is_external"] if is_capitalized else ue.is_external, "coef_ue": coef_ue, "ects_pot": ue.ects or 0.0, @@ -425,6 +431,10 @@ class NotesTableCompat(ResultatsSemestre): ue_status_list.append(ue_status) return self.parcours.check_barre_ues(ue_status_list) + def etud_has_decision(self, etudid): + """True s'il y a une décision de jury pour cet étudiant""" + return self.get_etud_decision_ues(etudid) or self.get_etud_decision_sem(etudid) + def get_etud_decision_ues(self, etudid: int) -> dict: """Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu. Ne tient pas compte des UE capitalisées. diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py index 6ecfa32f..3e398c83 100644 --- a/app/scodoc/sco_formsemestre_inscriptions.py +++ b/app/scodoc/sco_formsemestre_inscriptions.py @@ -32,14 +32,16 @@ import time import flask from flask import url_for, g, request +from app.comp import res_sem +from app.comp.res_common import NotesTableCompat +from app.models import FormSemestre import app.scodoc.sco_utils as scu from app import log from app.scodoc.scolog import logdb from app.scodoc.sco_exceptions import ScoException, ScoValueError -from app.scodoc.sco_permissions import Permission from app.scodoc.sco_codes_parcours import UE_STANDARD, UE_SPORT, UE_TYPE_NAME import app.scodoc.notesdb as ndb -from app.scodoc.TrivialFormulator import TrivialFormulator, TF +from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc import sco_find_etud from app.scodoc import sco_formsemestre from app.scodoc import sco_moduleimpl @@ -186,7 +188,9 @@ def do_formsemestre_desinscription(etudid, formsemestre_id): raise ScoValueError("desinscription impossible: semestre verrouille") # -- Si decisions de jury, desinscription interdite - nt = sco_cache.NotesTableCache.get(formsemestre_id) + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) + if nt.etud_has_decision(etudid): raise ScoValueError( "desinscription impossible: l'étudiant a une décision de jury (la supprimer avant si nécessaire)" @@ -475,7 +479,8 @@ def formsemestre_inscription_option(etudid, formsemestre_id): raise ScoValueError("Modification impossible: semestre verrouille") etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - nt = sco_cache.NotesTableCache.get(formsemestre_id) + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) F = html_sco_header.sco_footer() H = [ @@ -785,7 +790,9 @@ def list_inscrits_ailleurs(formsemestre_id): Pour chacun, donne la liste des semestres. { etudid : [ liste de sems ] } """ - nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) + etudids = nt.get_etudids() d = {} for etudid in etudids: diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py index cbd9a3b0..a0266219 100644 --- a/app/scodoc/sco_moduleimpl_inscriptions.py +++ b/app/scodoc/sco_moduleimpl_inscriptions.py @@ -33,6 +33,10 @@ import flask from flask import url_for, g, request from flask_login import current_user +from app.comp import res_sem +from app.comp.res_common import NotesTableCompat +from app.models import FormSemestre + import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu from app import log @@ -479,7 +483,9 @@ def get_etuds_with_capitalized_ue(formsemestre_id): returns { ue_id : [ { infos } ] } """ UECaps = scu.DictDefault(defaultvalue=[]) - nt = sco_cache.NotesTableCache.get(formsemestre_id) + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) + inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( args={"formsemestre_id": formsemestre_id} ) diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py index 6c2582ef..fff02647 100644 --- a/app/scodoc/sco_report.py +++ b/app/scodoc/sco_report.py @@ -39,14 +39,16 @@ from operator import itemgetter from flask import url_for, g, request import pydot +from app.comp import res_sem +from app.comp.res_common import NotesTableCompat +from app.models import FormSemestre + import app.scodoc.sco_utils as scu from app.models import FormationModalite from app.scodoc import notesdb as ndb from app.scodoc import html_sco_header from app.scodoc import sco_codes_parcours -from app.scodoc import sco_cache from app.scodoc import sco_etud -from app.scodoc import sco_excel from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_parcours_dut @@ -61,9 +63,9 @@ MAX_ETUD_IN_DESCR = 20 def formsemestre_etuds_stats(sem, only_primo=False): """Récupère liste d'etudiants avec etat et decision.""" - nt = sco_cache.NotesTableCache.get( - sem["formsemestre_id"] - ) # > get_table_moyennes_triees, identdict, get_etud_decision_sem, get_etud_etat, + formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"]) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) + T = nt.get_table_moyennes_triees() # Construit liste d'étudiants du semestre avec leur decision etuds = [] @@ -400,9 +402,8 @@ def table_suivi_cohorte( logt("table_suivi_cohorte: start") # 1-- Liste des semestres posterieurs dans lesquels ont été les etudiants de sem - nt = sco_cache.NotesTableCache.get( - formsemestre_id - ) # > get_etudids, get_etud_decision_sem + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) etudids = nt.get_etudids() logt("A: orig etuds set") @@ -456,9 +457,8 @@ def table_suivi_cohorte( s["members"] = orig_set.intersection(inset) nb_dipl = 0 # combien de diplomes dans ce semestre ? if s["semestre_id"] == nt.parcours.NB_SEM: - nt = sco_cache.NotesTableCache.get( - s["formsemestre_id"] - ) # > get_etud_decision_sem + s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"]) + nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre) for etudid in s["members"]: dec = nt.get_etud_decision_sem(etudid) if dec and code_semestre_validant(dec["code"]): @@ -905,9 +905,9 @@ def _descr_etud_set(etudids): def _count_dem_reo(formsemestre_id, etudids): "count nb of demissions and reorientation in this etud set" - nt = sco_cache.NotesTableCache.get( - formsemestre_id - ) # > get_etud_etat, get_etud_decision_sem + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) + dems = set() reos = set() for etudid in etudids: @@ -971,9 +971,9 @@ def get_codeparcoursetud(etud, prefix="", separator=""): i = len(sems) - 1 while i >= 0: s = sems[i] # 'sems' est a l'envers, du plus recent au plus ancien - nt = sco_cache.NotesTableCache.get( - s["formsemestre_id"] - ) # > get_etud_etat, get_etud_decision_sem + s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"]) + nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre) + p.append(_codesem(s, prefix=prefix)) # code decisions jury de chaque semestre: if nt.get_etud_etat(etud["etudid"]) == "D": @@ -1017,7 +1017,8 @@ def tsp_etud_list( """ # log('tsp_etud_list(%s, bac="%s")' % (formsemestre_id,bac)) sem = sco_formsemestre.get_formsemestre(formsemestre_id) - nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids, + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) etudids = nt.get_etudids() etuds = [] bacs = set() @@ -1260,9 +1261,8 @@ def graph_parcours( nxt = {} etudid = etud["etudid"] for s in etud["sems"]: # du plus recent au plus ancien - nt = sco_cache.NotesTableCache.get( - s["formsemestre_id"] - ) # > get_etud_decision_sem, get_etud_etat + s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"]) + nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre) dec = nt.get_etud_decision_sem(etudid) if nxt: if ( diff --git a/scodoc.py b/scodoc.py index 5b76ccb0..4dc5165f 100755 --- a/scodoc.py +++ b/scodoc.py @@ -434,6 +434,7 @@ def localize_logo(logo: str = None, dept: str = None): # migrate-scodoc7-dept-l @click.argument("xlsfile", type=click.File("rb")) @click.argument("zipfile", type=click.File("rb")) def photos_import_files(formsemestre_id: int, xlsfile: str, zipfile: str): + """Import des photos d'étudiants à partir d'une liste excel et d'un zip avec les images.""" import app as mapp from app.scodoc import sco_trombino, sco_photos from app.scodoc import notesdb as ndb From e7a97919b4aa299ec9832d1c92dcf9e4c845d794 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 13 Feb 2022 22:01:39 +0100 Subject: [PATCH 39/56] Fix: bac None --- app/scodoc/sco_bac.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/app/scodoc/sco_bac.py b/app/scodoc/sco_bac.py index ce00d96c..6f316a10 100644 --- a/app/scodoc/sco_bac.py +++ b/app/scodoc/sco_bac.py @@ -132,19 +132,29 @@ BACS_S = {t[0]: t[2:] for t in _BACS} class Baccalaureat: def __init__(self, bac, specialite=""): - self.bac = bac - self.specialite = specialite - self._abbrev, self._type = BACS_SSP.get((bac, specialite), (None, None)) + self.bac = bac or "" + self.specialite = specialite or "" + self._abbrev, self._type = BACS_SSP.get( + (self.bac, self.specialite), (None, None) + ) # Parfois, la specialite commence par la serie: essaye - if self._type is None and specialite and specialite.startswith(bac): - specialite = specialite[len(bac) :].strip(" -") - self._abbrev, self._type = BACS_SSP.get((bac, specialite), (None, None)) + if ( + self._type is None + and self.specialite + and self.specialite.startswith(self.bac) + ): + specialite = self.specialite[len(self.bac) :].strip(" -") + self._abbrev, self._type = BACS_SSP.get( + (self.bac, specialite), (None, None) + ) # Cherche la forme serie specialite if self._type is None and specialite: - self._abbrev, self._type = BACS_S.get(bac + " " + specialite, (None, None)) + self._abbrev, self._type = BACS_S.get( + self.bac + " " + specialite, (None, None) + ) # Cherche avec juste le bac, sans specialite if self._type is None: - self._abbrev, self._type = BACS_S.get(bac, (None, None)) + self._abbrev, self._type = BACS_S.get(self.bac, (None, None)) def abbrev(self): "abbreviation for this bac" From b28d22b3ed95aab5ce46efb17b2621fc6d8a7dfe Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 13 Feb 2022 22:03:46 +0100 Subject: [PATCH 40/56] Modif css ue color var name --- app/views/scodoc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/scodoc.py b/app/views/scodoc.py index e5cdc771..e65c4e66 100644 --- a/app/views/scodoc.py +++ b/app/views/scodoc.py @@ -374,7 +374,7 @@ def ue_colors_css(formation_id: int, semestre_idx: int): ":root{\n" + "\n".join( [ - f"--color-UE{semestre_idx}.{ue_idx+1}: {ue.color};" + f"--color-UE{semestre_idx}-{ue_idx+1}: {ue.color};" for ue_idx, ue in enumerate(ues) if ue.color ] From f3d2420117ddf212bc5520b0cef2dbfef4c51629 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 13 Feb 2022 22:08:16 +0100 Subject: [PATCH 41/56] Fix: bug St Malo --- app/comp/moy_ue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/comp/moy_ue.py b/app/comp/moy_ue.py index bb341baa..b0a534e5 100644 --- a/app/comp/moy_ue.py +++ b/app/comp/moy_ue.py @@ -234,12 +234,12 @@ def compute_ue_moys_apc( modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl) modimpl_coefs_df: matrice coefficients (UE x modimpl), sans UEs bonus sport - Résultat: DataFrame columns UE (sans sport), rows etudid + Résultat: DataFrame columns UE (sans bonus), rows etudid """ nb_etuds, nb_modules, nb_ues_no_bonus = sem_cube.shape nb_ues_tot = len(ues) assert len(modimpls) == nb_modules - if nb_modules == 0 or nb_etuds == 0: + if nb_modules == 0 or nb_etuds == 0 or nb_ues_no_bonus == 0: return pd.DataFrame( index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index ) From 129a39f7f0d0f67ffefa1c06482c3439c4db4e06 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 13 Feb 2022 22:25:51 +0100 Subject: [PATCH 42/56] Fix: robustifie get_evals_in_mod --- app/comp/res_common.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/app/comp/res_common.py b/app/comp/res_common.py index 0adf82da..09a06e79 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -9,6 +9,7 @@ from functools import cached_property import numpy as np import pandas as pd +from app import log from app.comp.aux_stats import StatsMoyenne from app.comp import moy_sem from app.comp.res_cache import ResultatsCache @@ -19,8 +20,7 @@ from app.models import FormSemestreUECoef from app.models.ues import UniteEns from app.scodoc import sco_utils as scu from app.scodoc.sco_cache import ResultatsSemestreCache -from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF -from app.scodoc.sco_exceptions import ScoValueError +from app.scodoc.sco_codes_parcours import UE_SPORT, DEF # Il faut bien distinguer # - ce qui est caché de façon persistente (via redis): @@ -554,25 +554,32 @@ class NotesTableCompat(ResultatsSemestre): Évaluation "complete" ssi toutes notes saisies ou en attente. """ modimpl = ModuleImpl.query.get(moduleimpl_id) + modimpl_results = self.modimpls_results.get(moduleimpl_id) + if not modimpl_results: + return [] # safeguard evals_results = [] for e in modimpl.evaluations: - if self.modimpls_results[moduleimpl_id].evaluations_completes_dict[e.id]: + if modimpl_results.evaluations_completes_dict.get(e.id, False): d = e.to_dict() - moduleimpl_results = self.modimpls_results[e.moduleimpl_id] d["heure_debut"] = e.heure_debut # datetime.time d["heure_fin"] = e.heure_fin d["jour"] = e.jour # datetime d["notes"] = { etud.id: { "etudid": etud.id, - "value": moduleimpl_results.evals_notes[e.id][etud.id], + "value": modimpl_results.evals_notes[e.id][etud.id], } for etud in self.etuds } d["etat"] = { - "evalattente": moduleimpl_results.evaluations_etat[e.id].nb_attente, + "evalattente": modimpl_results.evaluations_etat[e.id].nb_attente, } evals_results.append(d) + elif e.id not in modimpl_results.evaluations_completes_dict: + # ne devrait pas arriver ? XXX + log( + f"Warning: 220213 get_evals_in_mod {e.id} not in mod {moduleimpl_id} ?" + ) return evals_results def get_evaluations_etats(self): From 67fc12053ed2519d5cb3c7fa6001b9482ba5a21e Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 13 Feb 2022 23:53:11 +0100 Subject: [PATCH 43/56] Elimination des derniers NotesTableCache --- app/comp/res_common.py | 6 ++--- app/models/formsemestre.py | 1 + app/pe/pe_jurype.py | 4 +-- app/scodoc/notes_table.py | 2 +- app/scodoc/sco_abs_views.py | 12 ++++++--- app/scodoc/sco_apogee_csv.py | 26 +++++++++++++----- app/scodoc/sco_bulletins.py | 6 +---- app/scodoc/sco_debouche.py | 11 +++++--- app/scodoc/sco_etape_apogee.py | 4 +-- app/scodoc/sco_evaluations.py | 10 ++++--- app/scodoc/sco_export_results.py | 6 ++++- app/scodoc/sco_formsemestre_exterieurs.py | 6 ++++- app/scodoc/sco_formsemestre_status.py | 7 +++-- app/scodoc/sco_liste_notes.py | 15 ++++++----- app/scodoc/sco_moduleimpl_status.py | 1 - app/scodoc/sco_parcours_dut.py | 9 ------- app/scodoc/sco_poursuite_dut.py | 6 ++++- app/scodoc/sco_pvjury.py | 3 --- app/scodoc/sco_saisie_notes.py | 11 ++++---- app/scodoc/sco_semset.py | 6 ++++- app/scodoc/sco_tag_module.py | 6 ++++- app/views/absences.py | 33 ++++++++++++----------- app/views/notes.py | 20 +++++++++----- tests/unit/test_caches.py | 8 ++++-- tests/unit/test_notes_modules.py | 15 ++++++++--- tests/unit/test_sco_basic.py | 8 ++++-- 26 files changed, 145 insertions(+), 97 deletions(-) diff --git a/app/comp/res_common.py b/app/comp/res_common.py index 09a06e79..948c0e95 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -4,7 +4,7 @@ # See LICENSE ############################################################################## -from collections import defaultdict, Counter +from collections import Counter from functools import cached_property import numpy as np import pandas as pd @@ -367,7 +367,7 @@ class NotesTableCompat(ResultatsSemestre): sous forme de dict etud, classée dans l'ordre alphabétique de noms. """ - etuds = self.formsemestre.get_inscrits(include_demdef=True, sorted=True) + etuds = self.formsemestre.get_inscrits(include_demdef=True, order=True) return [e.to_dict_scodoc7() for e in etuds] @cached_property @@ -401,7 +401,7 @@ class NotesTableCompat(ResultatsSemestre): d = modimpl.to_dict() # compat ScoDoc < 9.2: ajoute matières d["mat"] = modimpl.module.matiere.to_dict() - modimpls_dict.append(d) + modimpls_dict.append(d) return modimpls_dict def compute_rangs(self): diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index c2850c2d..24514248 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -121,6 +121,7 @@ class FormSemestre(db.Model): d.pop("_sa_instance_state", None) # ScoDoc7 output_formators: (backward compat) d["formsemestre_id"] = self.id + d["titre_num"] = self.titre_num() if self.date_debut: d["date_debut"] = self.date_debut.strftime("%d/%m/%Y") d["date_debut_iso"] = self.date_debut.isoformat() diff --git a/app/pe/pe_jurype.py b/app/pe/pe_jurype.py index d29d040f..2720ad43 100644 --- a/app/pe/pe_jurype.py +++ b/app/pe/pe_jurype.py @@ -322,12 +322,10 @@ class JuryPE(object): etudiants = [] for sem in semsListe: # pour chacun des semestres de la liste - # nt = self.get_notes_d_un_semestre( sem['formsemestre_id'] ) nt = self.get_cache_notes_d_un_semestre(sem["formsemestre_id"]) - # sco_cache.NotesTableCache.get( sem['formsemestre_id']) etudiantsDuSemestre = ( nt.get_etudids() - ) # nt.identdict.keys() # identification des etudiants du semestre + ) # identification des etudiants du semestre if pe_tools.PE_DEBUG: pe_tools.pe_print( diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py index 7fc03ce8..b1ac97b8 100644 --- a/app/scodoc/notes_table.py +++ b/app/scodoc/notes_table.py @@ -171,7 +171,7 @@ class NotesTable: def __init__(self, formsemestre_id): # log(f"NotesTable( formsemestre_id={formsemestre_id} )") - # raise NotImplementedError() # XXX + raise NotImplementedError() # XXX if not formsemestre_id: raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id) self.formsemestre_id = formsemestre_id diff --git a/app/scodoc/sco_abs_views.py b/app/scodoc/sco_abs_views.py index 2f108c24..d92ee855 100644 --- a/app/scodoc/sco_abs_views.py +++ b/app/scodoc/sco_abs_views.py @@ -33,7 +33,9 @@ import datetime from flask import url_for, g, request, abort from app import log -from app.models import Identite +from app.comp import res_sem +from app.comp.res_common import NotesTableCompat +from app.models import Identite, FormSemestre import app.scodoc.sco_utils as scu from app.scodoc import notesdb as ndb from app.scodoc.scolog import logdb @@ -118,7 +120,8 @@ def doSignaleAbsence( if moduleimpl_id and moduleimpl_id != "NULL": mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] formsemestre_id = mod["formsemestre_id"] - nt = sco_cache.NotesTableCache.get(formsemestre_id) + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) ues = nt.get_ues_stat_dict() for ue in ues: modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"]) @@ -179,11 +182,12 @@ def SignaleAbsenceEtud(): # etudid implied menu_module = "" else: formsemestre_id = etud["cursem"]["formsemestre_id"] + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) + ues = nt.get_ues_stat_dict() require_module = sco_preferences.get_preference( "abs_require_module", formsemestre_id ) - nt = sco_cache.NotesTableCache.get(formsemestre_id) - ues = nt.get_ues_stat_dict() if require_module: menu_module = """