From 81e7914620b82c05fe859ed0f803067e0e2acc60 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 7 Jul 2022 16:24:52 +0200 Subject: [PATCH] Refactoring: cursus Classic/ECTS/BUT --- app/but/cursus_but.py | 56 ++++++ app/models/validations.py | 15 ++ app/scodoc/notes_table.py | 10 +- app/scodoc/sco_apogee_csv.py | 4 +- app/scodoc/sco_cache.py | 4 +- app/scodoc/sco_cursus.py | 134 ++++++++++++++ ...{sco_parcours_dut.py => sco_cursus_dut.py} | 168 +++--------------- app/scodoc/sco_edit_ue.py | 4 +- app/scodoc/sco_formsemestre_edit.py | 6 +- app/scodoc/sco_formsemestre_exterieurs.py | 4 +- app/scodoc/sco_formsemestre_validation.py | 44 +++-- app/scodoc/sco_groups.py | 7 +- app/scodoc/sco_groups_view.py | 4 +- app/scodoc/sco_moduleimpl_status.py | 2 +- app/scodoc/sco_page_etud.py | 4 +- app/scodoc/sco_permissions_check.py | 4 +- app/scodoc/sco_prepajury.py | 15 +- app/scodoc/sco_pvjury.py | 28 ++- app/scodoc/sco_pvpdf.py | 4 +- app/scodoc/sco_report.py | 11 +- tests/unit/test_sco_basic.py | 12 +- 21 files changed, 312 insertions(+), 228 deletions(-) create mode 100644 app/but/cursus_but.py create mode 100644 app/scodoc/sco_cursus.py rename app/scodoc/{sco_parcours_dut.py => sco_cursus_dut.py} (86%) diff --git a/app/but/cursus_but.py b/app/but/cursus_but.py new file mode 100644 index 00000000..6626d20e --- /dev/null +++ b/app/but/cursus_but.py @@ -0,0 +1,56 @@ +############################################################################## +# ScoDoc +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# See LICENSE +############################################################################## + +"""Cursus en BUT + +Classe raccordant avec ScoDoc 7: + ScoDoc 7 utilisait sco_cursus_dut.SituationEtudCursus + + Ce module définit une classe SituationEtudCursusBUT + avec la même interface. + +""" + +from typing import Union + +from flask import g, url_for + +from app import db +from app import log +from app.comp.res_but import ResultatsSemestreBUT +from app.comp import res_sem +from app.models import formsemestre + +from app.models.but_refcomp import ( + ApcAnneeParcours, + ApcCompetence, + ApcNiveau, + ApcParcours, + ApcParcoursNiveauCompetence, +) +from app.models import Scolog, ScolarAutorisationInscription +from app.models.but_validations import ( + ApcValidationAnnee, + ApcValidationRCUE, + RegroupementCoherentUE, +) +from app.models.etudiants import Identite +from app.models.formations import Formation +from app.models.formsemestre import FormSemestre, FormSemestreInscription +from app.models.ues import UniteEns +from app.models.validations import ScolarFormSemestreValidation +from app.scodoc import sco_codes_parcours as sco_codes +from app.scodoc.sco_codes_parcours import RED, UE_STANDARD +from app.scodoc import sco_utils as scu +from app.scodoc.sco_exceptions import ScoException, ScoValueError + +from app.scodoc import sco_cursus_dut + + +class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursus): + def __init__(self, etud: dict, formsemestre_id: int, res: ResultatsSemestreBUT): + self.semestre_non_terminal = bool + self.formation diff --git a/app/models/validations.py b/app/models/validations.py index 42d7ba0d..b24685a8 100644 --- a/app/models/validations.py +++ b/app/models/validations.py @@ -57,6 +57,11 @@ class ScolarFormSemestreValidation(db.Model): def __repr__(self): return f"{self.__class__.__name__}({self.formsemestre_id}, {self.etudid}, code={self.code}, ue={self.ue}, moy_ue={self.moy_ue})" + def to_dict(self) -> dict: + d = dict(self.__dict__) + d.pop("_sa_instance_state", None) + return d + class ScolarAutorisationInscription(db.Model): """Autorisation d'inscription dans un semestre""" @@ -78,6 +83,11 @@ class ScolarAutorisationInscription(db.Model): db.ForeignKey("notes_formsemestre.id"), ) + def to_dict(self) -> dict: + d = dict(self.__dict__) + d.pop("_sa_instance_state", None) + return d + @classmethod def autorise_etud( cls, @@ -146,3 +156,8 @@ class ScolarEvent(db.Model): db.Integer, db.ForeignKey("notes_formsemestre.id"), ) + + def to_dict(self) -> dict: + d = dict(self.__dict__) + d.pop("_sa_instance_state", None) + return d diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py index a8cd0eb7..56f2b93e 100644 --- a/app/scodoc/notes_table.py +++ b/app/scodoc/notes_table.py @@ -54,22 +54,22 @@ from app.scodoc.sco_codes_parcours import ( ue_is_fondamentale, ue_is_professionnelle, ) -from app.scodoc.sco_parcours_dut import formsemestre_get_etud_capitalisation +from app.scodoc import sco_cache from app.scodoc import sco_codes_parcours from app.scodoc import sco_compute_moy -from app.scodoc import sco_cache +from app.scodoc.sco_cursus import formsemestre_get_etud_capitalisation +from app.scodoc import sco_cursus_dut from app.scodoc import sco_edit_matiere from app.scodoc import sco_edit_module from app.scodoc import sco_edit_ue +from app.scodoc import sco_etud from app.scodoc import sco_evaluations from app.scodoc import sco_formations from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl -from app.scodoc import sco_parcours_dut from app.scodoc import sco_preferences -from app.scodoc import sco_etud def comp_ranks(T): @@ -1175,7 +1175,7 @@ class NotesTable: ): if not cnx: cnx = ndb.GetDBConnexion() - sco_parcours_dut.do_formsemestre_validate_ue( + sco_cursus_dut.do_formsemestre_validate_ue( cnx, nt_cap, ue_cap["formsemestre_id"], diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py index e309beda..da1aed8d 100644 --- a/app/scodoc/sco_apogee_csv.py +++ b/app/scodoc/sco_apogee_csv.py @@ -112,8 +112,8 @@ from app.scodoc.sco_codes_parcours import ( NAR, RAT, ) +from app.scodoc import sco_cursus from app.scodoc import sco_formsemestre -from app.scodoc import sco_parcours_dut from app.scodoc import sco_etud APO_PORTAL_ENCODING = ( @@ -413,7 +413,7 @@ class ApoEtud(dict): export_res_etape = self.export_res_etape if (not export_res_etape) and cur_sem: # exporte toujours le résultat de l'étape si l'étudiant est diplômé - Se = sco_parcours_dut.SituationEtudParcours( + Se = sco_cursus.get_situation_etud_cursus( self.etud, cur_sem["formsemestre_id"] ) export_res_etape = Se.all_other_validated() diff --git a/app/scodoc/sco_cache.py b/app/scodoc/sco_cache.py index 7975e3b2..0798a37d 100644 --- a/app/scodoc/sco_cache.py +++ b/app/scodoc/sco_cache.py @@ -231,7 +231,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa """expire cache pour un semestre (ou tous si formsemestre_id non spécifié). Si pdfonly, n'expire que les bulletins pdf cachés. """ - from app.scodoc import sco_parcours_dut + from app.scodoc import sco_cursus if getattr(g, "defer_cache_invalidation", False): g.sem_to_invalidate.add(formsemestre_id) @@ -252,7 +252,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa else: formsemestre_ids = [ formsemestre_id - ] + sco_parcours_dut.list_formsemestre_utilisateurs_uecap(formsemestre_id) + ] + sco_cursus.list_formsemestre_utilisateurs_uecap(formsemestre_id) log(f"----- invalidate_formsemestre: clearing {formsemestre_ids} -----") if not pdfonly: diff --git a/app/scodoc/sco_cursus.py b/app/scodoc/sco_cursus.py new file mode 100644 index 00000000..eec02b9f --- /dev/null +++ b/app/scodoc/sco_cursus.py @@ -0,0 +1,134 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# 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 +# +############################################################################## + +"""Gestion des cursus (jurys suivant la formation) +""" + +from app.but import cursus_but +from app.scodoc import sco_cursus_dut + +from app.comp.res_compat import NotesTableCompat +from app.comp import res_sem +from app.models import FormSemestre +from app.scodoc import sco_formsemestre +from app.scodoc import sco_formations +import app.scodoc.notesdb as ndb + +# SituationEtudParcours -> get_situation_etud_cursus +def get_situation_etud_cursus( + etud: dict, formsemestre_id: int +) -> sco_cursus_dut.SituationEtudCursus: + """renvoie une instance de SituationEtudCursus (ou sous-classe spécialisée)""" + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) + + if formsemestre.formation.is_apc(): + return cursus_but.SituationEtudCursusBUT(etud, formsemestre_id, nt) + + parcours = nt.parcours + if parcours.ECTS_ONLY: + return sco_cursus_dut.SituationEtudCursusECTS(etud, formsemestre_id, nt) + return sco_cursus_dut.SituationEtudCursusClassic(etud, formsemestre_id, nt) + + +def formsemestre_get_etud_capitalisation( + formation_id: int, semestre_idx: int, date_debut, etudid: int +) -> list[dict]: + """Liste des UE capitalisées (ADM) correspondant au semestre sem et à l'étudiant. + + Recherche dans les semestres de la même formation (code) avec le même + semestre_id et une date de début antérieure à celle du semestre mentionné. + Et aussi les UE externes validées. + + Resultat: [ { 'formsemestre_id' : + 'ue_id' : ue_id dans le semestre origine + 'ue_code' : + 'moy_ue' : + 'event_date' : + 'is_external' + } ] + """ + cnx = ndb.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) + cursor.execute( + """ + SELECT DISTINCT SFV.*, ue.ue_code + FROM notes_ue ue, notes_formations nf, + notes_formations nf2, scolar_formsemestre_validation SFV, notes_formsemestre sem + + WHERE ue.formation_id = nf.id + and nf.formation_code = nf2.formation_code + and nf2.id=%(formation_id)s + + and SFV.ue_id = ue.id + and SFV.code = 'ADM' + and SFV.etudid = %(etudid)s + + and ( (sem.id = SFV.formsemestre_id + and sem.date_debut < %(date_debut)s + and sem.semestre_id = %(semestre_id)s ) + or ( + ((SFV.formsemestre_id is NULL) OR (SFV.is_external)) -- les UE externes ou "anterieures" + AND (SFV.semestre_id is NULL OR SFV.semestre_id=%(semestre_id)s) + ) ) + """, + { + "etudid": etudid, + "formation_id": formation_id, + "semestre_id": semestre_idx, + "date_debut": date_debut, + }, + ) + + return cursor.dictfetchall() + + +def list_formsemestre_utilisateurs_uecap(formsemestre_id): + """Liste des formsemestres pouvant utiliser une UE capitalisee de ce semestre + (et qui doivent donc etre sortis du cache si l'on modifie ce + semestre): meme code formation, meme semestre_id, date posterieure""" + cnx = ndb.GetDBConnexion() + sem = sco_formsemestre.get_formsemestre(formsemestre_id) + F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0] + cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) + cursor.execute( + """SELECT sem.id + FROM notes_formsemestre sem, notes_formations F + WHERE sem.formation_id = F.id + and F.formation_code = %(formation_code)s + and sem.semestre_id = %(semestre_id)s + and sem.date_debut >= %(date_debut)s + and sem.id != %(formsemestre_id)s; + """, + { + "formation_code": F["formation_code"], + "semestre_id": sem["semestre_id"], + "formsemestre_id": formsemestre_id, + "date_debut": ndb.DateDMYtoISO(sem["date_debut"]), + }, + ) + return [x[0] for x in cursor.fetchall()] diff --git a/app/scodoc/sco_parcours_dut.py b/app/scodoc/sco_cursus_dut.py similarity index 86% rename from app/scodoc/sco_parcours_dut.py rename to app/scodoc/sco_cursus_dut.py index f3441386..5d2f31bf 100644 --- a/app/scodoc/sco_parcours_dut.py +++ b/app/scodoc/sco_cursus_dut.py @@ -28,9 +28,10 @@ """Semestres: gestion parcours DUT (Arreté du 13 août 2005) """ +from app import db from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre, UniteEns +from app.models import FormSemestre, UniteEns, ScolarAutorisationInscription import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb @@ -105,27 +106,14 @@ class DecisionSem(object): ) ) ) - # xxx debug - # log('%s: %s %s %s %s %s' % (self.codechoice,code_etat,new_code_prev,formsemestre_id_utilise_pour_compenser,devenir,assiduite) ) -def SituationEtudParcours(etud: dict, formsemestre_id: int): - """renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)""" - formsemestre = FormSemestre.query.get_or_404(formsemestre_id) - nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - - # if formsemestre.formation.is_apc(): - # return SituationEtudParcoursBUT(etud, formsemestre_id, nt) - - parcours = nt.parcours - # - if parcours.ECTS_ONLY: - return SituationEtudParcoursECTS(etud, formsemestre_id, nt) - else: - return SituationEtudParcoursGeneric(etud, formsemestre_id, nt) +class SituationEtudCursus: + "Semestre dans un cursus" + pass -class SituationEtudParcoursGeneric: +class SituationEtudCursusClassic(SituationEtudCursus): "Semestre dans un parcours" def __init__(self, etud: dict, formsemestre_id: int, nt: NotesTableCompat): @@ -454,8 +442,7 @@ class SituationEtudParcoursGeneric: break if not cur or cur["formsemestre_id"] != self.formsemestre_id: log( - "*** SituationEtudParcours: search_prev: cur not found (formsemestre_id=%s, etudid=%s)" - % (self.formsemestre_id, self.etudid) + f"*** SituationEtudCursus: search_prev: cur not found (formsemestre_id={self.formsemestre_id}, etudid={self.etudid})" ) return None # pas de semestre courant !!! # Cherche semestre antérieur de même formation (code) et semestre_id precedent @@ -633,31 +620,27 @@ class SituationEtudParcoursGeneric: formsemestre_id=self.prev["formsemestre_id"] ) # > modif decisions jury (sem, UE) - # -- supprime autorisations venant de ce formsemestre - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) try: - cursor.execute( - """delete from scolar_autorisation_inscription - where etudid = %(etudid)s and origin_formsemestre_id=%(origin_formsemestre_id)s - """, - {"etudid": self.etudid, "origin_formsemestre_id": self.formsemestre_id}, + # -- Supprime autorisations venant de ce formsemestre + autorisations = ScolarAutorisationInscription.query.filter_by( + etudid=self.etudid, origin_formsemestre_id=self.formsemestre_id ) - - # -- enregistre autorisations inscription + for autorisation in autorisations: + db.session.delete(autorisation) + db.session.flush() + # -- Enregistre autorisations inscription next_semestre_ids = self.get_next_semestre_ids(decision.devenir) for next_semestre_id in next_semestre_ids: - _scolar_autorisation_inscription_editor.create( - cnx, - { - "etudid": self.etudid, - "formation_code": self.formation.formation_code, - "semestre_id": next_semestre_id, - "origin_formsemestre_id": self.formsemestre_id, - }, + autorisation = ScolarAutorisationInscription( + etudid=self.etudid, + formation_code=self.formation.formation_code, + semestre_id=next_semestre_id, + origin_formsemestre_id=self.formsemestre_id, ) - cnx.commit() + db.session.add(autorisation) + db.session.commit() except: - cnx.rollback() + cnx.session.rollback() raise sco_cache.invalidate_formsemestre( formsemestre_id=self.formsemestre_id @@ -673,11 +656,11 @@ class SituationEtudParcoursGeneric: ) # > modif decision jury -class SituationEtudParcoursECTS(SituationEtudParcoursGeneric): +class SituationEtudCursusECTS(SituationEtudCursusClassic): """Gestion parcours basés sur ECTS""" def __init__(self, etud, formsemestre_id, nt): - SituationEtudParcoursGeneric.__init__(self, etud, formsemestre_id, nt) + SituationEtudCursusClassic.__init__(self, etud, formsemestre_id, nt) def could_be_compensated(self): return False # jamais de compensations dans ce parcours @@ -1020,9 +1003,9 @@ def etud_est_inscrit_ue(cnx, etudid, formsemestre_id, ue_id): """ cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor.execute( - """SELECT mi.* + """SELECT mi.* FROM notes_moduleimpl mi, notes_modules mo, notes_ue ue, notes_moduleimpl_inscription i - WHERE i.etudid = %(etudid)s + WHERE i.etudid = %(etudid)s and i.moduleimpl_id=mi.id and mi.formsemestre_id = %(formsemestre_id)s and mi.module_id = mo.id @@ -1032,102 +1015,3 @@ def etud_est_inscrit_ue(cnx, etudid, formsemestre_id, ue_id): ) return len(cursor.fetchall()) - - -_scolar_autorisation_inscription_editor = ndb.EditableTable( - "scolar_autorisation_inscription", - "autorisation_inscription_id", - ("etudid", "formation_code", "semestre_id", "date", "origin_formsemestre_id"), - output_formators={"date": ndb.DateISOtoDMY}, - input_formators={"date": ndb.DateDMYtoISO}, -) -scolar_autorisation_inscription_list = _scolar_autorisation_inscription_editor.list - - -def formsemestre_get_autorisation_inscription(etudid, origin_formsemestre_id): - """Liste des autorisations d'inscription pour cet étudiant - émanant du semestre indiqué. - """ - cnx = ndb.GetDBConnexion() - return scolar_autorisation_inscription_list( - cnx, {"origin_formsemestre_id": origin_formsemestre_id, "etudid": etudid} - ) - - -def formsemestre_get_etud_capitalisation( - formation_id: int, semestre_idx: int, date_debut, etudid: int -) -> list[dict]: - """Liste des UE capitalisées (ADM) correspondant au semestre sem et à l'étudiant. - - Recherche dans les semestres de la même formation (code) avec le même - semestre_id et une date de début antérieure à celle du semestre mentionné. - Et aussi les UE externes validées. - - Resultat: [ { 'formsemestre_id' : - 'ue_id' : ue_id dans le semestre origine - 'ue_code' : - 'moy_ue' : - 'event_date' : - 'is_external' - } ] - """ - cnx = ndb.GetDBConnexion() - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - cursor.execute( - """ - SELECT DISTINCT SFV.*, ue.ue_code - FROM notes_ue ue, notes_formations nf, - notes_formations nf2, scolar_formsemestre_validation SFV, notes_formsemestre sem - - WHERE ue.formation_id = nf.id - and nf.formation_code = nf2.formation_code - and nf2.id=%(formation_id)s - - and SFV.ue_id = ue.id - and SFV.code = 'ADM' - and SFV.etudid = %(etudid)s - - and ( (sem.id = SFV.formsemestre_id - and sem.date_debut < %(date_debut)s - and sem.semestre_id = %(semestre_id)s ) - or ( - ((SFV.formsemestre_id is NULL) OR (SFV.is_external)) -- les UE externes ou "anterieures" - AND (SFV.semestre_id is NULL OR SFV.semestre_id=%(semestre_id)s) - ) ) - """, - { - "etudid": etudid, - "formation_id": formation_id, - "semestre_id": semestre_idx, - "date_debut": date_debut, - }, - ) - - return cursor.dictfetchall() - - -def list_formsemestre_utilisateurs_uecap(formsemestre_id): - """Liste des formsemestres pouvant utiliser une UE capitalisee de ce semestre - (et qui doivent donc etre sortis du cache si l'on modifie ce - semestre): meme code formation, meme semestre_id, date posterieure""" - cnx = ndb.GetDBConnexion() - sem = sco_formsemestre.get_formsemestre(formsemestre_id) - F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0] - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - cursor.execute( - """SELECT sem.id - FROM notes_formsemestre sem, notes_formations F - WHERE sem.formation_id = F.id - and F.formation_code = %(formation_code)s - and sem.semestre_id = %(semestre_id)s - and sem.date_debut >= %(date_debut)s - and sem.id != %(formsemestre_id)s; - """, - { - "formation_code": F["formation_code"], - "semestre_id": sem["semestre_id"], - "formsemestre_id": formsemestre_id, - "date_debut": ndb.DateDMYtoISO(sem["date_debut"]), - }, - ) - return [x[0] for x in cursor.fetchall()] diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index def072c3..0c052805 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -140,7 +140,7 @@ def do_ue_create(args): def do_ue_delete(ue_id, delete_validations=False, force=False): "delete UE and attached matieres (but not modules)" - from app.scodoc import sco_parcours_dut + from app.scodoc import sco_cursus_dut ue = UniteEns.query.get_or_404(ue_id) formation = ue.formation @@ -164,7 +164,7 @@ def do_ue_delete(ue_id, delete_validations=False, force=False): # raise ScoLockedFormError() # Il y a-t-il des etudiants ayant validé cette UE ? # si oui, propose de supprimer les validations - validations = sco_parcours_dut.scolar_formsemestre_validation_list( + validations = sco_cursus_dut.scolar_formsemestre_validation_list( cnx, args={"ue_id": ue.id} ) if validations and not delete_validations and not force: diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index c4e32946..cf24c4b0 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -60,7 +60,7 @@ from app.scodoc import sco_formsemestre from app.scodoc import sco_groups_copy from app.scodoc import sco_modalites from app.scodoc import sco_moduleimpl -from app.scodoc import sco_parcours_dut +from app.scodoc import sco_cursus_dut from app.scodoc import sco_permissions_check from app.scodoc import sco_portal_apogee from app.scodoc import sco_preferences @@ -1362,14 +1362,14 @@ def _reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new) if e["ue_id"]: e["ue_id"] = ues_old2new[e["ue_id"]] sco_etud.scolar_events_edit(cnx, e) - validations = sco_parcours_dut.scolar_formsemestre_validation_list( + validations = sco_cursus_dut.scolar_formsemestre_validation_list( cnx, args={"formsemestre_id": formsemestre_id} ) for e in validations: if e["ue_id"]: e["ue_id"] = ues_old2new[e["ue_id"]] # log('e=%s' % e ) - sco_parcours_dut.scolar_formsemestre_validation_edit(cnx, e) + sco_cursus_dut.scolar_formsemestre_validation_edit(cnx, e) def formsemestre_delete(formsemestre_id): diff --git a/app/scodoc/sco_formsemestre_exterieurs.py b/app/scodoc/sco_formsemestre_exterieurs.py index 0da85f2c..62d49c6d 100644 --- a/app/scodoc/sco_formsemestre_exterieurs.py +++ b/app/scodoc/sco_formsemestre_exterieurs.py @@ -51,7 +51,7 @@ from app.scodoc import sco_formations from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_formsemestre_validation -from app.scodoc import sco_parcours_dut +from app.scodoc import sco_cursus_dut from app.scodoc import sco_etud @@ -450,7 +450,7 @@ def _list_ue_with_coef_and_validations(sem, etudid): else: ue["uecoef"] = {} # add validation - validation = sco_parcours_dut.scolar_formsemestre_validation_list( + validation = sco_cursus_dut.scolar_formsemestre_validation_list( cnx, args={ "formsemestre_id": formsemestre_id, diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index a9d13c01..2effc138 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -59,8 +59,9 @@ from app.scodoc import sco_edit_ue from app.scodoc import sco_etud from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions -from app.scodoc import sco_parcours_dut -from app.scodoc.sco_parcours_dut import etud_est_inscrit_ue +from app.scodoc import sco_cursus +from app.scodoc import sco_cursus_dut +from app.scodoc.sco_cursus_dut import etud_est_inscrit_ue from app.scodoc import sco_photos from app.scodoc import sco_preferences from app.scodoc import sco_pvjury @@ -108,7 +109,7 @@ def formsemestre_validation_etud_form( check = True etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id) + Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) if not Se.sem["etat"]: raise ScoValueError("validation: semestre verrouille") @@ -274,15 +275,12 @@ def formsemestre_validation_etud_form( ass = "non assidu" H.append("

Décision existante du %(event_date)s: %(code)s" % decision_jury) H.append(" (%s)" % ass) - auts = sco_parcours_dut.formsemestre_get_autorisation_inscription( - etudid, formsemestre_id - ) - if auts: + autorisations = ScolarAutorisationInscription.query.filter_by( + etudid=etudid, origin_formsemestre_id=formsemestre_id + ).all() + if autorisations: H.append(". Autorisé%s à s'inscrire en " % etud["ne"]) - alist = [] - for aut in auts: - alist.append(str(aut["semestre_id"])) - H.append(", ".join(["S%s" % x for x in alist]) + ".") + H.append(", ".join([f"S{aut.semestre_id}" for aut in autorisations]) + ".") H.append("

") # Cas particulier pour ATJ: corriger precedent avant de continuer @@ -382,7 +380,7 @@ def formsemestre_validation_etud( ): """Enregistre validation""" etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id) + Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) # retrouve la decision correspondant au code: choices = Se.get_possible_choices(assiduite=True) choices += Se.get_possible_choices(assiduite=False) @@ -415,7 +413,7 @@ def formsemestre_validation_etud_manu( if assidu: assidu = True etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id) + Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) if code_etat in Se.parcours.UNUSED_CODES: raise ScoValueError("code decision invalide dans ce parcours") # Si code ADC, extrait le semestre utilisé: @@ -430,7 +428,7 @@ def formsemestre_validation_etud_manu( formsemestre_id_utilise_pour_compenser = None # Construit le choix correspondant: - choice = sco_parcours_dut.DecisionSem( + choice = sco_cursus_dut.DecisionSem( code_etat=code_etat, new_code_prev=new_code_prev, devenir=devenir, @@ -910,7 +908,7 @@ def do_formsemestre_validation_auto(formsemestre_id): conflicts = [] # liste des etudiants avec decision differente déjà saisie for etudid in etudids: etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id) + Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( {"etudid": etudid, "formsemestre_id": formsemestre_id} )[0] @@ -932,15 +930,13 @@ def do_formsemestre_validation_auto(formsemestre_id): if decision_sem and decision_sem["code"] != ADM: ok = False conflicts.append(etud) - autorisations = sco_parcours_dut.formsemestre_get_autorisation_inscription( - etudid, formsemestre_id - ) - if ( - len(autorisations) != 0 - ): # accepte le cas ou il n'y a pas d'autorisation : BUG 23/6/7, A RETIRER ENSUITE + autorisations = ScolarAutorisationInscription.query.filter_by( + etudid=etudid, origin_formsemestre_id=formsemestre_id + ).all() + if len(autorisations) != 0: if ( - len(autorisations) != 1 - or autorisations[0]["semestre_id"] != next_semestre_id + len(autorisations) > 1 + or autorisations[0].semestre_id != next_semestre_id ): if ok: conflicts.append(etud) @@ -1176,7 +1172,7 @@ def do_formsemestre_validate_previous_ue( ) else: sco_formsemestre.do_formsemestre_uecoef_delete(cnx, formsemestre_id, ue_id) - sco_parcours_dut.do_formsemestre_validate_ue( + sco_cursus_dut.do_formsemestre_validate_ue( cnx, nt, formsemestre_id, # "importe" cette UE dans le semestre (new 3/2015) diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index f17e017e..d2cd636c 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -56,8 +56,9 @@ import app.scodoc.notesdb as ndb from app import log, cache from app.scodoc.scolog import logdb 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_codes_parcours +from app.scodoc import sco_cursus from app.scodoc import sco_etud from app.scodoc import sco_permissions_check from app.scodoc import sco_xml @@ -1489,13 +1490,13 @@ def _get_prev_moy(etudid, formsemestre_id): """Donne la derniere moyenne generale calculee pour cette étudiant, ou 0 si on n'en trouve pas (nouvel inscrit,...). """ - from app.scodoc import sco_parcours_dut + from app.scodoc import sco_cursus_dut info = sco_etud.get_etud_info(etudid=etudid, filled=True) if not info: raise ScoValueError("etudiant invalide: etudid=%s" % etudid) etud = info[0] - Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id) + Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) if Se.prev: prev_sem = FormSemestre.query.get(Se.prev["formsemestre_id"]) nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_sem) diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index 259256bd..9588abd4 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -49,7 +49,7 @@ from app.scodoc import sco_excel from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl -from app.scodoc import sco_parcours_dut +from app.scodoc import sco_cursus from app.scodoc import sco_portal_apogee from app.scodoc import sco_preferences from app.scodoc import sco_etud @@ -776,7 +776,7 @@ def groups_table( m.update(etud) sco_etud.etud_add_lycee_infos(etud) # et ajoute le parcours - Se = sco_parcours_dut.SituationEtudParcours( + Se = sco_cursus.get_situation_etud_cursus( etud, groups_infos.formsemestre_id ) m["parcours"] = Se.get_parcours_descr() diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index c49ef080..66387efa 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -39,7 +39,7 @@ from app.models import ModuleImpl from app.models.evaluations import Evaluation import app.scodoc.sco_utils as scu from app.scodoc.sco_exceptions import ScoInvalidIdType -from app.scodoc.sco_parcours_dut import formsemestre_has_decisions +from app.scodoc.sco_cursus_dut import formsemestre_has_decisions from app.scodoc.sco_permissions import Permission from app.scodoc import html_sco_header diff --git a/app/scodoc/sco_page_etud.py b/app/scodoc/sco_page_etud.py index b0690ea4..298181bf 100644 --- a/app/scodoc/sco_page_etud.py +++ b/app/scodoc/sco_page_etud.py @@ -46,7 +46,7 @@ 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_groups -from app.scodoc import sco_parcours_dut +from app.scodoc import sco_cursus from app.scodoc import sco_permissions_check from app.scodoc import sco_photos from app.scodoc import sco_users @@ -269,7 +269,7 @@ def ficheEtud(etudid=None): sem_info[sem["formsemestre_id"]] = grlink if info["sems"]: - Se = sco_parcours_dut.SituationEtudParcours(etud, info["last_formsemestre_id"]) + Se = sco_cursus.get_situation_etud_cursus(etud, info["last_formsemestre_id"]) info["liste_inscriptions"] = formsemestre_recap_parcours_table( Se, etudid, diff --git a/app/scodoc/sco_permissions_check.py b/app/scodoc/sco_permissions_check.py index fe21b167..9ad457d9 100644 --- a/app/scodoc/sco_permissions_check.py +++ b/app/scodoc/sco_permissions_check.py @@ -24,14 +24,14 @@ def can_edit_notes(authuser, moduleimpl_id, allow_ens=True): seul le directeur des études peut saisir des notes (et il ne devrait pas). """ from app.scodoc import sco_formsemestre - from app.scodoc import sco_parcours_dut + from app.scodoc import sco_cursus_dut M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"]) if not sem["etat"]: return False # semestre verrouillé - if sco_parcours_dut.formsemestre_has_decisions(sem["formsemestre_id"]): + if sco_cursus_dut.formsemestre_has_decisions(sem["formsemestre_id"]): # il y a des décisions de jury dans ce semestre ! return ( authuser.has_permission(Permission.ScoEditAllNotes) diff --git a/app/scodoc/sco_prepajury.py b/app/scodoc/sco_prepajury.py index 678b81ab..81093af7 100644 --- a/app/scodoc/sco_prepajury.py +++ b/app/scodoc/sco_prepajury.py @@ -37,14 +37,14 @@ from flask_login import current_user from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre, Identite +from app.models import FormSemestre, Identite, ScolarAutorisationInscription from app.scodoc import sco_abs from app.scodoc import sco_codes_parcours from app.scodoc import sco_groups from app.scodoc import sco_etud from app.scodoc import sco_excel from app.scodoc import sco_formsemestre -from app.scodoc import sco_parcours_dut +from app.scodoc import sco_cursus from app.scodoc import sco_preferences import app.scodoc.sco_utils as scu import sco_version @@ -78,7 +78,7 @@ def feuille_preparation_jury(formsemestre_id): nbabs = {} nbabsjust = {} for etud in etuds: - Se = sco_parcours_dut.SituationEtudParcours( + Se = sco_cursus.get_situation_etud_cursus( etud.to_dict_scodoc7(), formsemestre_id ) if Se.prev: @@ -119,10 +119,11 @@ def feuille_preparation_jury(formsemestre_id): if decision["compense_formsemestre_id"]: code[etud.id] += "+" # indique qu'il a servi a compenser assidu[etud.id] = {False: "Non", True: "Oui"}.get(decision["assidu"], "") - aut_list = sco_parcours_dut.formsemestre_get_autorisation_inscription( - etud.id, formsemestre_id - ) - autorisations[etud.id] = ", ".join(["S%s" % x["semestre_id"] for x in aut_list]) + + autorisations = ScolarAutorisationInscription.query.filter_by( + etudid=etud.id, origin_formsemestre_id=formsemestre_id + ).all() + autorisations[etud.id] = ", ".join(["S{x.semestre_id}" for x in autorisations]) # parcours: parcours[etud.id] = Se.get_parcours_descr() # groupe principal (td) diff --git a/app/scodoc/sco_pvjury.py b/app/scodoc/sco_pvjury.py index 9b374b8e..9975e7c6 100644 --- a/app/scodoc/sco_pvjury.py +++ b/app/scodoc/sco_pvjury.py @@ -57,24 +57,24 @@ from flask import g, request from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre, UniteEns +from app.models import FormSemestre, UniteEns, ScolarAutorisationInscription import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app import log 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_cursus +from app.scodoc import sco_cursus_dut from app.scodoc import sco_edit_ue +from app.scodoc import sco_etud from app.scodoc import sco_formations from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_groups_view -from app.scodoc import sco_parcours_dut from app.scodoc import sco_pdf from app.scodoc import sco_preferences from app.scodoc import sco_pvpdf -from app.scodoc import sco_etud from app.scodoc.gen_tables import GenTable from app.scodoc.sco_codes_parcours import NO_SEMESTRE_ID from app.scodoc.sco_pdf import PDFLOCK @@ -138,12 +138,9 @@ def _descr_decision_sem_abbrev(etat, decision_sem): return decision -def descr_autorisations(autorisations): +def descr_autorisations(autorisations: list[ScolarAutorisationInscription]) -> str: "résumé textuel des autorisations d'inscription (-> 'S1, S3' )" - alist = [] - for aut in autorisations: - alist.append("S" + str(aut["semestre_id"])) - return ", ".join(alist) + return ", ".join([f"S{a.semestre_id}" for a in autorisations]) def _comp_ects_by_ue_code(nt, decision_ues): @@ -234,7 +231,7 @@ def dict_pvjury( D = {} # même chose que L, mais { etudid : dec } for etudid in etudids: etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - Se = sco_parcours_dut.SituationEtudParcours(etud, formsemestre_id) + Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) semestre_non_terminal = semestre_non_terminal or Se.semestre_non_terminal d = {} d["identite"] = nt.identdict[etudid] @@ -280,17 +277,18 @@ def dict_pvjury( else: d["decision_sem_descr"] = _descr_decision_sem(d["etat"], d["decision_sem"]) - d["autorisations"] = sco_parcours_dut.formsemestre_get_autorisation_inscription( - etudid, formsemestre_id - ) - d["autorisations_descr"] = descr_autorisations(d["autorisations"]) + autorisations = ScolarAutorisationInscription.query.filter_by( + etudid=etudid, origin_formsemestre_id=formsemestre_id + ).all() + d["autorisations"] = [a.to_dict() for a in autorisations] + d["autorisations_descr"] = descr_autorisations(autorisations) d["validation_parcours"] = Se.parcours_validated() d["parcours"] = Se.get_parcours_descr(filter_futur=True) if with_parcours_decisions: d["parcours_decisions"] = Se.get_parcours_decisions() # Observations sur les compensations: - compensators = sco_parcours_dut.scolar_formsemestre_validation_list( + compensators = sco_cursus_dut.scolar_formsemestre_validation_list( cnx, args={"compense_formsemestre_id": formsemestre_id, "etudid": etudid} ) obs = [] diff --git a/app/scodoc/sco_pvpdf.py b/app/scodoc/sco_pvpdf.py index 8ef1c12c..d889277e 100644 --- a/app/scodoc/sco_pvpdf.py +++ b/app/scodoc/sco_pvpdf.py @@ -50,7 +50,7 @@ from app.scodoc import sco_formsemestre from app.scodoc import sco_pdf from app.scodoc import sco_preferences from app.scodoc.sco_logos import find_logo -from app.scodoc.sco_parcours_dut import SituationEtudParcours +from app.scodoc.sco_cursus_dut import SituationEtudCursus from app.scodoc.sco_pdf import SU import sco_version @@ -428,7 +428,7 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None): """ # formsemestre_id = sem["formsemestre_id"] - Se: SituationEtudParcours = decision["Se"] + Se: SituationEtudCursus = decision["Se"] t, s = _descr_jury(sem, Se.parcours_validated() or not Se.semestre_non_terminal) objects = [] style = reportlab.lib.styles.ParagraphStyle({}) diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py index 214532a5..97f01f75 100644 --- a/app/scodoc/sco_report.py +++ b/app/scodoc/sco_report.py @@ -41,7 +41,7 @@ import pydot from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre +from app.models import FormSemestre, ScolarAutorisationInscription import app.scodoc.sco_utils as scu from app.models import FormationModalite @@ -51,7 +51,6 @@ from app.scodoc import sco_codes_parcours from app.scodoc import sco_etud from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions -from app.scodoc import sco_parcours_dut from app.scodoc import sco_preferences import sco_version from app.scodoc.gen_tables import GenTable @@ -81,10 +80,10 @@ def formsemestre_etuds_stats(sem, only_primo=False): if "codedecision" not in etud: etud["codedecision"] = "(nd)" # pas de decision jury # Ajout devenir (autorisations inscriptions), utile pour stats passage - aut_list = sco_parcours_dut.formsemestre_get_autorisation_inscription( - etudid, sem["formsemestre_id"] - ) - autorisations = ["S%s" % x["semestre_id"] for x in aut_list] + aut_list = ScolarAutorisationInscription.query.filter_by( + etudid=etudid, origin_formsemestre_id=sem["formsemestre_id"] + ).all() + autorisations = [f"S{a.semestre_id}" for a in aut_list] autorisations.sort() autorisations_str = ", ".join(autorisations) etud["devenir"] = autorisations_str diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py index a00ab66e..52ed79f0 100644 --- a/tests/unit/test_sco_basic.py +++ b/tests/unit/test_sco_basic.py @@ -20,6 +20,7 @@ from config import TestConfig from tests.unit import sco_fake_gen import app +from app import db from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import FormSemestre @@ -31,8 +32,7 @@ from app.scodoc import sco_codes_parcours from app.scodoc import sco_evaluations from app.scodoc import sco_evaluation_db from app.scodoc import sco_formsemestre_validation -from app.scodoc import sco_parcours_dut -from app.scodoc import sco_cache +from app.scodoc import sco_cursus_dut from app.scodoc import sco_saisie_notes from app.scodoc import sco_utils as scu @@ -194,20 +194,20 @@ def run_sco_basic(verbose=False): # --- Permission saisie notes et décisions de jury, avec ou sans démission ou défaillance # on n'a pas encore saisi de décisions - assert not sco_parcours_dut.formsemestre_has_decisions(formsemestre_id) + assert not sco_cursus_dut.formsemestre_has_decisions(formsemestre_id) # Saisie d'un décision AJ, non assidu etudid = etuds[-1]["etudid"] - sco_parcours_dut.formsemestre_validate_ues( + sco_cursus_dut.formsemestre_validate_ues( formsemestre_id, etudid, sco_codes_parcours.AJ, False ) - assert sco_parcours_dut.formsemestre_has_decisions( + assert sco_cursus_dut.formsemestre_has_decisions( formsemestre_id ), "décisions manquantes" # Suppression de la décision sco_formsemestre_validation.formsemestre_validation_suppress_etud( formsemestre_id, etudid ) - assert not sco_parcours_dut.formsemestre_has_decisions( + assert not sco_cursus_dut.formsemestre_has_decisions( formsemestre_id ), "décisions non effacées"