"
+ traceback.format_exc()
diff --git a/app/scodoc/sco_cache.py b/app/scodoc/sco_cache.py
index 7975e3b2c..0798a37d2 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_codes_parcours.py b/app/scodoc/sco_codes_parcours.py
index dc59aeb2e..14c8d8f92 100644
--- a/app/scodoc/sco_codes_parcours.py
+++ b/app/scodoc/sco_codes_parcours.py
@@ -216,7 +216,7 @@ def code_semestre_attente(code: str) -> bool:
def code_ue_validant(code: str) -> bool:
- "Vrai si ce code entraine la validation des UEs du semestre."
+ "Vrai si ce code d'UE est validant (ie attribue les ECTS)"
return CODES_UE_VALIDES.get(code, False)
diff --git a/app/scodoc/sco_cursus.py b/app/scodoc/sco_cursus.py
new file mode 100644
index 000000000..eec02b9f1
--- /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 83%
rename from app/scodoc/sco_parcours_dut.py
rename to app/scodoc/sco_cursus_dut.py
index f34413864..f4354c6f1 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):
@@ -353,9 +341,7 @@ class SituationEtudParcoursGeneric:
)[0]["formation_code"]
# si sem peut servir à compenser le semestre courant, positionne
# can_compensate
- sem["can_compensate"] = check_compensation(
- self.etudid, self.sem, self.nt, sem, nt
- )
+ sem["can_compensate"] = self.check_compensation_dut(sem, nt)
self.ue_acros = list(ue_acros.keys())
self.ue_acros.sort()
@@ -454,8 +440,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 +618,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
@@ -672,12 +653,52 @@ class SituationEtudParcoursGeneric:
formsemestre_id=formsemestre_id
) # > modif decision jury
+ def check_compensation_dut(self, semc: dict, ntc: NotesTableCompat):
+ """Compensations DUT
+ Vérifie si le semestre sem peut se compenser en utilisant semc
+ - semc non utilisé par un autre semestre
+ - decision du jury prise ADM ou ADJ ou ATT ou ADC
+ - barres UE (moy ue > 8) dans sem et semc
+ - moyenne des moy_gen > 10
+ Return boolean
+ """
+ # -- deja utilise ?
+ decc = ntc.get_etud_decision_sem(self.etudid)
+ if (
+ decc
+ and decc["compense_formsemestre_id"]
+ and decc["compense_formsemestre_id"] != self.sem["formsemestre_id"]
+ ):
+ return False
+ # -- semestres consecutifs ?
+ if abs(self.sem["semestre_id"] - semc["semestre_id"]) != 1:
+ return False
+ # -- decision jury:
+ if decc and not decc["code"] in (ADM, ADJ, ATT, ADC):
+ return False
+ # -- barres UE et moyenne des moyennes:
+ moy_gen = self.nt.get_etud_moy_gen(self.etudid)
+ moy_genc = ntc.get_etud_moy_gen(self.etudid)
+ try:
+ moy_moy = (moy_gen + moy_genc) / 2
+ except: # un des semestres sans aucune note !
+ return False
-class SituationEtudParcoursECTS(SituationEtudParcoursGeneric):
+ if (
+ self.nt.etud_check_conditions_ues(self.etudid)[0]
+ and ntc.etud_check_conditions_ues(self.etudid)[0]
+ and moy_moy >= NOTES_BARRE_GEN_COMPENSATION
+ ):
+ return True
+ else:
+ return False
+
+
+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
@@ -718,47 +739,6 @@ class SituationEtudParcoursECTS(SituationEtudParcoursGeneric):
return choices
-#
-def check_compensation(etudid, sem, nt, semc, ntc):
- """Verifie si le semestre sem peut se compenser en utilisant semc
- - semc non utilisé par un autre semestre
- - decision du jury prise ADM ou ADJ ou ATT ou ADC
- - barres UE (moy ue > 8) dans sem et semc
- - moyenne des moy_gen > 10
- Return boolean
- """
- # -- deja utilise ?
- decc = ntc.get_etud_decision_sem(etudid)
- if (
- decc
- and decc["compense_formsemestre_id"]
- and decc["compense_formsemestre_id"] != sem["formsemestre_id"]
- ):
- return False
- # -- semestres consecutifs ?
- if abs(sem["semestre_id"] - semc["semestre_id"]) != 1:
- return False
- # -- decision jury:
- if decc and not decc["code"] in (ADM, ADJ, ATT, ADC):
- return False
- # -- barres UE et moyenne des moyennes:
- moy_gen = nt.get_etud_moy_gen(etudid)
- moy_genc = ntc.get_etud_moy_gen(etudid)
- try:
- moy_moy = (moy_gen + moy_genc) / 2
- except: # un des semestres sans aucune note !
- return False
-
- if (
- nt.etud_check_conditions_ues(etudid)[0]
- and ntc.etud_check_conditions_ues(etudid)[0]
- and moy_moy >= NOTES_BARRE_GEN_COMPENSATION
- ):
- return True
- else:
- return False
-
-
# -------------------------------------------------------------------------------------------
@@ -1020,9 +1000,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 +1012,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_apc.py b/app/scodoc/sco_edit_apc.py
index 7e71f51d1..551067d3d 100644
--- a/app/scodoc/sco_edit_apc.py
+++ b/app/scodoc/sco_edit_apc.py
@@ -31,6 +31,7 @@ from flask import g, request
from flask_login import current_user
from app import db
+from app.but import apc_edit_ue
from app.models import Formation, UniteEns, Matiere, Module, FormSemestre, ModuleImpl
from app.models.validations import ScolarFormSemestreValidation
from app.scodoc.sco_codes_parcours import UE_SPORT
@@ -109,6 +110,7 @@ def html_edit_formation_apc(
icons=icons,
ues_by_sem=ues_by_sem,
ects_by_sem=ects_by_sem,
+ form_ue_choix_niveau=apc_edit_ue.form_ue_choix_niveau,
),
]
for semestre_idx in semestre_ids:
diff --git a/app/scodoc/sco_edit_formation.py b/app/scodoc/sco_edit_formation.py
index 62fc2bbf4..d7ea5e976 100644
--- a/app/scodoc/sco_edit_formation.py
+++ b/app/scodoc/sco_edit_formation.py
@@ -292,21 +292,25 @@ def do_formation_create(args):
def do_formation_edit(args):
"edit a formation"
- # log('do_formation_edit( args=%s )'%args)
- # On autorise la modif de la formation meme si elle est verrouillee
- # car cela ne change que du cosmetique, (sauf eventuellement le code formation ?)
- # mais si verrouillée on ne peut changer le type de parcours
- if sco_formations.formation_has_locked_sems(args["formation_id"]):
- if "type_parcours" in args:
- del args["type_parcours"]
# On ne peut jamais supprimer le code formation:
if "formation_code" in args and not args["formation_code"]:
del args["formation_code"]
- cnx = ndb.GetDBConnexion()
- sco_formations._formationEditor.edit(cnx, args)
- formation: Formation = Formation.query.get(args["formation_id"])
+ formation: Formation = Formation.query.get_or_404(args["formation_id"])
+ # On autorise la modif de la formation meme si elle est verrouillee
+ # car cela ne change que du cosmetique, (sauf eventuellement le code formation ?)
+ # mais si verrouillée on ne peut changer le type de parcours
+ if formation.has_locked_sems():
+ if "type_parcours" in args:
+ del args["type_parcours"]
+
+ for field in formation.__dict__:
+ if field and field[0] != "_" and field in args:
+ setattr(formation, field, args[field])
+
+ db.session.add(formation)
+ db.session.commit()
formation.invalidate_cached_sems()
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index def072c3c..e41accd47 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:
@@ -466,7 +466,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
+ ue_div
+ html_sco_header.sco_footer()
)
- elif tf[2]:
+ elif tf[0] == 1:
if create:
if not tf[2]["ue_code"]:
del tf[2]["ue_code"]
@@ -684,6 +684,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
javascripts=[
"libjs/jinplace-1.2.1.min.js",
"js/ue_list.js",
+ "js/edit_ue.js",
"libjs/jQuery-tagEditor/jquery.tag-editor.min.js",
"libjs/jQuery-tagEditor/jquery.caret.min.js",
"js/module_tag_editor.js",
diff --git a/app/scodoc/sco_excel.py b/app/scodoc/sco_excel.py
index 2c95c7ef4..1ea4cee8a 100644
--- a/app/scodoc/sco_excel.py
+++ b/app/scodoc/sco_excel.py
@@ -45,6 +45,7 @@ from openpyxl.worksheet.worksheet import Worksheet
import app.scodoc.sco_utils as scu
from app import log
from app.scodoc.sco_exceptions import ScoValueError
+from app.scodoc import notesdb, sco_preferences
class COLORS(Enum):
@@ -793,7 +794,7 @@ def excel_feuille_listeappel(
# ligne 3
cell_2 = ws.make_cell("Enseignant :", style2)
- cell_6 = ws.make_cell(("Groupe %s" % groupname), style3)
+ cell_6 = ws.make_cell(f"Groupe {groupname}", style3)
ws.append_row([None, cell_2, None, None, None, None, cell_6])
# ligne 4: Avertissement pour ne pas confondre avec listes notes
diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py
index 32c2d9e34..ce7f2c10d 100644
--- a/app/scodoc/sco_formsemestre.py
+++ b/app/scodoc/sco_formsemestre.py
@@ -101,7 +101,7 @@ def get_formsemestre(formsemestre_id, raise_soft_exc=False):
return g.stored_get_formsemestre[formsemestre_id]
if not isinstance(formsemestre_id, int):
log(f"get_formsemestre: invalid id '{formsemestre_id}'")
- raise ScoInvalidIdType("formsemestre_id must be an integer !")
+ raise ScoInvalidIdType("get_formsemestre: formsemestre_id must be an integer !")
sems = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})
if not sems:
log("get_formsemestre: invalid formsemestre_id (%s)" % formsemestre_id)
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index c4e329462..cf24c4b0c 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 0da85f2c3..62d49c6d8 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 a9d13c016..2effc1386 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 f17e017e6..d2cd636c3 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 259256bd4..9588abd4b 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 c49ef080a..66387efa5 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 b0690ea43..298181bf8 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_pdf.py b/app/scodoc/sco_pdf.py
index 2f468ccfc..3a7d7a553 100755
--- a/app/scodoc/sco_pdf.py
+++ b/app/scodoc/sco_pdf.py
@@ -55,6 +55,7 @@ from reportlab.lib import styles
from flask import g
+from app.scodoc import sco_utils as scu
from app.scodoc.sco_utils import CONFIG
from app import log
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
@@ -67,7 +68,7 @@ PAGE_WIDTH = defaultPageSize[0]
DEFAULT_PDF_FOOTER_TEMPLATE = CONFIG.DEFAULT_PDF_FOOTER_TEMPLATE
-def SU(s):
+def SU(s: str) -> str:
"convert s from string to string suitable for ReportLab"
if not s:
return ""
@@ -145,9 +146,9 @@ def makeParas(txt, style, suppress_empty=False):
) from e
else:
raise e
- except Exception as e:
+ except Exception as exc:
log(traceback.format_exc())
- log("Invalid pdf para format: %s" % txt)
+ log(f"Invalid pdf para format: {txt}")
try:
result = [
Paragraph(
@@ -155,13 +156,14 @@ def makeParas(txt, style, suppress_empty=False):
style,
)
]
- except ValueError as e: # probleme font ? essaye sans style
+ except ValueError as exc2: # probleme font ? essaye sans style
# recupere font en cause ?
m = re.match(r".*family/bold/italic for (.*)", e.args[0], re.DOTALL)
if m:
message = f"police non disponible: {m[1]}"
else:
message = "format invalide"
+ scu.flash_once(f"problème génération PDF: {message}")
return [
Paragraph(
SU(f'Erreur: {message}'),
diff --git a/app/scodoc/sco_permissions_check.py b/app/scodoc/sco_permissions_check.py
index fe21b1672..9ad457d9f 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 678b81ab2..0afb1c415 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:
@@ -103,14 +103,14 @@ def feuille_preparation_jury(formsemestre_id):
moy[etud.id] = nt.get_etud_moy_gen(etud.id)
for ue in nt.get_ues_stat_dict(filter_sport=True):
ue_status = nt.get_etud_ue_status(etud.id, ue["ue_id"])
- ue_code_s = ue["ue_code"] + "_%s" % nt.sem["semestre_id"]
+ ue_code_s = f'{ue["ue_code"]}_{nt.sem["semestre_id"]}'
moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
if Se.prev:
try:
moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0
- except:
+ except (KeyError, TypeError):
pass
decision = nt.get_etud_decision_sem(etud.id)
@@ -119,10 +119,13 @@ 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 = ScolarAutorisationInscription.query.filter_by(
+ etudid=etud.id, origin_formsemestre_id=formsemestre_id
+ ).all()
+ autorisations[etud.id] = ", ".join(
+ [f"S{x.semestre_id}" for x in autorisations_etud]
)
- autorisations[etud.id] = ", ".join(["S%s" % x["semestre_id"] for x in aut_list])
# parcours:
parcours[etud.id] = Se.get_parcours_descr()
# groupe principal (td)
@@ -153,11 +156,11 @@ def feuille_preparation_jury(formsemestre_id):
sid = sem["semestre_id"]
sn = sp = ""
if sid >= 0:
- sn = "S%s" % sid
+ sn = f"S{sid}"
if prev_moy: # si qq chose dans precedent
- sp = "S%s" % (sid - 1)
+ sp = f"S{sid - 1}"
- ws = sco_excel.ScoExcelSheet(sheet_name="Prepa Jury %s" % sn)
+ sheet = sco_excel.ScoExcelSheet(sheet_name=f"Prepa Jury {sn}")
# génération des styles
style_bold = sco_excel.excel_make_style(size=10, bold=True)
style_center = sco_excel.excel_make_style(halign="center")
@@ -173,10 +176,10 @@ def feuille_preparation_jury(formsemestre_id):
)
# Première ligne
- ws.append_single_cell_row(
+ sheet.append_single_cell_row(
"Feuille préparation Jury %s" % scu.unescape_html(sem["titreannee"]), style_bold
)
- ws.append_blank_row()
+ sheet.append_blank_row()
# Ligne de titre
titles = ["Rang"]
@@ -198,25 +201,25 @@ def feuille_preparation_jury(formsemestre_id):
]
if prev_moy: # si qq chose dans precedent
titles += [prev_ue_acro[x][1] for x in ue_prev_codes] + [
- "Moy %s" % sp,
- "Décision %s" % sp,
+ f"Moy {sp}",
+ f"Décision {sp}",
]
- titles += [ue_acro[x][1] for x in ue_codes] + ["Moy %s" % sn]
+ titles += [ue_acro[x][1] for x in ue_codes] + [f"Moy {sn}"]
if moy_inter:
- titles += ["Moy %s-%s" % (sp, sn)]
+ titles += [f"Moy {sp}-{sn}"]
titles += ["Abs", "Abs Injust."]
if code:
- titles.append("Proposit. %s" % sn)
+ titles.append("Proposit. {sn}")
if autorisations:
titles.append("Autorisations")
# titles.append('Assidu')
- ws.append_row(ws.make_row(titles, style_boldcenter))
- if prev_moy:
- tit_prev_moy = "Moy " + sp
- col_prev_moy = titles.index(tit_prev_moy)
- tit_moy = "Moy " + sn
- col_moy = titles.index(tit_moy)
- col_abs = titles.index("Abs")
+ sheet.append_row(sheet.make_row(titles, style_boldcenter))
+ # if prev_moy:
+ # tit_prev_moy = "Moy " + sp
+ # # col_prev_moy = titles.index(tit_prev_moy)
+ # tit_moy = "Moy " + sn
+ # col_moy = titles.index(tit_moy)
+ # col_abs = titles.index("Abs")
def fmt(x):
"reduit les notes a deux chiffres"
@@ -229,13 +232,13 @@ def feuille_preparation_jury(formsemestre_id):
i = 1 # numero etudiant
for etud in etuds:
cells = []
- cells.append(ws.make_cell(str(i)))
+ cells.append(sheet.make_cell(str(i)))
if sco_preferences.get_preference("prepa_jury_nip"):
- cells.append(ws.make_cell(etud.code_nip))
+ cells.append(sheet.make_cell(etud.code_nip))
if sco_preferences.get_preference("prepa_jury_ine"):
- cells.append(ws.make_cell(etud.code_ine))
+ cells.append(sheet.make_cell(etud.code_ine))
admission = etud.admission.first()
- cells += ws.make_row(
+ cells += sheet.make_row(
[
etud.id,
etud.civilite_str,
@@ -253,50 +256,52 @@ def feuille_preparation_jury(formsemestre_id):
if prev_moy:
for ue_acro in ue_prev_codes:
cells.append(
- ws.make_cell(
+ sheet.make_cell(
fmt(prev_moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note
)
)
co += 1
cells.append(
- ws.make_cell(fmt(prev_moy.get(etud.id, "")), style_bold)
+ sheet.make_cell(fmt(prev_moy.get(etud.id, "")), style_bold)
) # moy gen prev
cells.append(
- ws.make_cell(fmt(prev_code.get(etud.id, "")), style_moy)
+ sheet.make_cell(fmt(prev_code.get(etud.id, "")), style_moy)
) # decision prev
co += 2
for ue_acro in ue_codes:
cells.append(
- ws.make_cell(fmt(moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note)
+ sheet.make_cell(
+ fmt(moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note
+ )
)
co += 1
cells.append(
- ws.make_cell(fmt(moy.get(etud.id, "")), style_note_bold)
+ sheet.make_cell(fmt(moy.get(etud.id, "")), style_note_bold)
) # moy gen
co += 1
if moy_inter:
- cells.append(ws.make_cell(fmt(moy_inter.get(etud.id, "")), style_note))
- cells.append(ws.make_cell(str(nbabs.get(etud.id, "")), style_center))
- cells.append(ws.make_cell(str(nbabsjust.get(etud.id, "")), style_center))
+ cells.append(sheet.make_cell(fmt(moy_inter.get(etud.id, "")), style_note))
+ cells.append(sheet.make_cell(str(nbabs.get(etud.id, "")), style_center))
+ cells.append(sheet.make_cell(str(nbabsjust.get(etud.id, "")), style_center))
if code:
- cells.append(ws.make_cell(code.get(etud.id, ""), style_moy))
- cells.append(ws.make_cell(autorisations.get(etud.id, ""), style_moy))
+ cells.append(sheet.make_cell(code.get(etud.id, ""), style_moy))
+ cells.append(sheet.make_cell(autorisations.get(etud.id, ""), style_moy))
# l.append(assidu.get(etud.id, ''))
- ws.append_row(cells)
+ sheet.append_row(cells)
i += 1
#
- ws.append_blank_row()
+ sheet.append_blank_row()
# Explications des codes
codes = list(sco_codes_parcours.CODES_EXPL.keys())
codes.sort()
- ws.append_single_cell_row("Explication des codes")
+ sheet.append_single_cell_row("Explication des codes")
for code in codes:
- ws.append_row(
- ws.make_row(["", "", "", code, sco_codes_parcours.CODES_EXPL[code]])
+ sheet.append_row(
+ sheet.make_row(["", "", "", code, sco_codes_parcours.CODES_EXPL[code]])
)
- ws.append_row(
- ws.make_row(
+ sheet.append_row(
+ sheet.make_row(
[
"",
"",
@@ -307,16 +312,16 @@ def feuille_preparation_jury(formsemestre_id):
)
)
# UE : Correspondances acronyme et titre complet
- ws.append_blank_row()
- ws.append_single_cell_row("Titre des UE")
+ sheet.append_blank_row()
+ sheet.append_single_cell_row("Titre des UE")
if prev_moy:
for ue in ntp.get_ues_stat_dict(filter_sport=True):
- ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
+ sheet.append_row(sheet.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
for ue in nt.get_ues_stat_dict(filter_sport=True):
- ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
+ sheet.append_row(sheet.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
#
- ws.append_blank_row()
- ws.append_single_cell_row(
+ sheet.append_blank_row()
+ sheet.append_single_cell_row(
"Préparé par %s le %s sur %s pour %s"
% (
sco_version.SCONAME,
@@ -325,7 +330,7 @@ def feuille_preparation_jury(formsemestre_id):
current_user,
)
)
- xls = ws.generate()
+ xls = sheet.generate()
flash("Feuille préparation jury générée")
return scu.send_file(
xls,
diff --git a/app/scodoc/sco_pvjury.py b/app/scodoc/sco_pvjury.py
index 9b374b8e3..395f6107d 100644
--- a/app/scodoc/sco_pvjury.py
+++ b/app/scodoc/sco_pvjury.py
@@ -57,32 +57,38 @@ 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,
+ but_validations,
+)
+from app.models.etudiants import Identite
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
from app.scodoc.TrivialFormulator import TrivialFormulator
-def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem):
- """Liste des UE validées dans ce semestre"""
+def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem) -> list[dict]:
+ """Liste des UE validées dans ce semestre (incluant les UE capitalisées)"""
if not decisions_ue:
return []
uelist = []
@@ -90,17 +96,20 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem):
for ue_id in decisions_ue.keys():
try:
if decisions_ue[ue_id] and (
- decisions_ue[ue_id]["code"] == sco_codes_parcours.ADM
+ sco_codes_parcours.code_ue_validant(decisions_ue[ue_id]["code"])
or (
# XXX ceci devrait dépendre du parcours et non pas être une option ! #sco8
- scu.CONFIG.CAPITALIZE_ALL_UES
+ decision_sem
+ and scu.CONFIG.CAPITALIZE_ALL_UES
and sco_codes_parcours.code_semestre_validant(decision_sem["code"])
)
):
ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
uelist.append(ue)
except:
- log("descr_decisions_ues: ue_id=%s decisions_ue=%s" % (ue_id, decisions_ue))
+ log(
+ f"Exception in descr_decisions_ues: ue_id={ue_id} decisions_ue={decisions_ue}"
+ )
# Les UE capitalisées dans d'autres semestres:
if etudid in nt.validations.ue_capitalisees.index:
for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]:
@@ -138,12 +147,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):
@@ -233,8 +239,11 @@ def dict_pvjury(
L = []
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)
+ # etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
+ etud: Identite = Identite.query.get(etudid)
+ Se = sco_cursus.get_situation_etud_cursus(
+ etud.to_dict_scodoc7(), formsemestre_id
+ )
semestre_non_terminal = semestre_non_terminal or Se.semestre_non_terminal
d = {}
d["identite"] = nt.identdict[etudid]
@@ -243,6 +252,8 @@ def dict_pvjury(
) # I|D|DEF (inscription ou démission ou défaillant)
d["decision_sem"] = nt.get_etud_decision_sem(etudid)
d["decisions_ue"] = nt.get_etud_decision_ues(etudid)
+ if formsemestre.formation.is_apc():
+ d.update(but_validations.dict_decision_jury(etud, formsemestre))
d["last_formsemestre_id"] = Se.get_semestres()[
-1
] # id du dernier semestre (chronologiquement) dans lequel il a été inscrit
@@ -280,17 +291,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 = []
@@ -307,12 +319,7 @@ def dict_pvjury(
d["decision_sem"]["compense_formsemestre_id"]
)
obs.append(
- "%s compense %s (%s)"
- % (
- sem["sem_id_txt"],
- compensed["sem_id_txt"],
- compensed["anneescolaire"],
- )
+ f"""{sem["sem_id_txt"]} compense {compensed["sem_id_txt"]} ({compensed["anneescolaire"]})"""
)
d["observation"] = ", ".join(obs)
diff --git a/app/scodoc/sco_pvpdf.py b/app/scodoc/sco_pvpdf.py
index 8ef1c12cc..ac067339a 100644
--- a/app/scodoc/sco_pvpdf.py
+++ b/app/scodoc/sco_pvpdf.py
@@ -30,9 +30,11 @@
import io
import re
+from PIL import Image as PILImage
+
import reportlab
from reportlab.lib.units import cm, mm
-from reportlab.lib.enums import TA_RIGHT, TA_JUSTIFY
+from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_JUSTIFY
from reportlab.platypus import Paragraph, Spacer, Frame, PageBreak
from reportlab.platypus import Table, TableStyle, Image
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
@@ -41,16 +43,16 @@ from reportlab.lib import styles
from reportlab.lib.colors import Color
from flask import g
+from app.models import FormSemestre, Identite
import app.scodoc.sco_utils as scu
from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_etud
-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
@@ -125,6 +127,7 @@ def page_footer(canvas, doc, logo, preferences, with_page_numbers=True):
def page_header(canvas, doc, logo, preferences, only_on_first_page=False):
+ "Ajoute au canvas le frame avec le logo"
if only_on_first_page and int(doc.page) > 1:
return
height = doc.pagesize[1]
@@ -147,12 +150,12 @@ def page_header(canvas, doc, logo, preferences, only_on_first_page=False):
class CourrierIndividuelTemplate(PageTemplate):
- """Template pour courrier avisant des decisions de jury (1 page /etudiant)"""
+ """Template pour courrier avisant des decisions de jury (1 page par étudiant)"""
def __init__(
self,
document,
- pagesbookmarks={},
+ pagesbookmarks=None,
author=None,
title=None,
subject=None,
@@ -163,7 +166,7 @@ class CourrierIndividuelTemplate(PageTemplate):
template_name="CourrierJuryTemplate",
):
"""Initialise our page template."""
- self.pagesbookmarks = pagesbookmarks
+ self.pagesbookmarks = pagesbookmarks or {}
self.pdfmeta_author = author
self.pdfmeta_title = title
self.pdfmeta_subject = subject
@@ -237,32 +240,32 @@ class CourrierIndividuelTemplate(PageTemplate):
width=LOGO_HEADER_WIDTH,
)
- def beforeDrawPage(self, canvas, doc):
+ def beforeDrawPage(self, canv, doc):
"""Draws a logo and an contribution message on each page."""
# ---- Add some meta data and bookmarks
if self.pdfmeta_author:
- canvas.setAuthor(SU(self.pdfmeta_author))
+ canv.setAuthor(SU(self.pdfmeta_author))
if self.pdfmeta_title:
- canvas.setTitle(SU(self.pdfmeta_title))
+ canv.setTitle(SU(self.pdfmeta_title))
if self.pdfmeta_subject:
- canvas.setSubject(SU(self.pdfmeta_subject))
+ canv.setSubject(SU(self.pdfmeta_subject))
bm = self.pagesbookmarks.get(doc.page, None)
if bm != None:
key = bm
txt = SU(bm)
- canvas.bookmarkPage(key)
- canvas.addOutlineEntry(txt, bm)
+ canv.bookmarkPage(key)
+ canv.addOutlineEntry(txt, bm)
# ---- Background image
if self.background_image_filename and self.with_page_background:
- canvas.drawImage(
+ canv.drawImage(
self.background_image_filename, 0, 0, doc.pagesize[0], doc.pagesize[1]
)
# ---- Header/Footer
if self.with_header:
page_header(
- canvas,
+ canv,
doc,
self.logo_header,
self.preferences,
@@ -270,7 +273,7 @@ class CourrierIndividuelTemplate(PageTemplate):
)
if self.with_footer:
page_footer(
- canvas,
+ canv,
doc,
self.logo_footer,
self.preferences,
@@ -332,6 +335,42 @@ class PVTemplate(CourrierIndividuelTemplate):
# self.__pageNum += 1
+def _simulate_br(paragraph_txt: str, para="") -> str:
+ """Reportlab bug turnaround (could be removed in a future version).
+ p is a string with Reportlab intra-paragraph XML tags.
+ Replaces
(currently ignored by Reportlab) by
+ Also replaces
by
+ """
+ return ("" + para).join(
+ re.split(r"<.*?br.*?/>", paragraph_txt.replace("
", "
"))
+ )
+
+
+def _make_signature_image(signature, leftindent, formsemestre_id) -> Table:
+ "crée un paragraphe avec l'image signature"
+ # cree une image PIL pour avoir la taille (W,H)
+
+ f = io.BytesIO(signature)
+ img = PILImage.open(f)
+ width, height = img.size
+ pdfheight = (
+ 1.0
+ * sco_preferences.get_preference("pv_sig_image_height", formsemestre_id)
+ * mm
+ )
+ f.seek(0, 0)
+
+ style = styles.ParagraphStyle({})
+ style.leading = 1.0 * sco_preferences.get_preference(
+ "SCOLAR_FONT_SIZE", formsemestre_id
+ ) # vertical space
+ style.leftIndent = leftindent
+ return Table(
+ [("", Image(f, width=width * pdfheight / float(height), height=pdfheight))],
+ colWidths=(9 * cm, 7 * cm),
+ )
+
+
def pdf_lettres_individuelles(
formsemestre_id,
etudids=None,
@@ -352,7 +391,7 @@ def pdf_lettres_individuelles(
etuds = [x["identite"] for x in dpv["decisions"]]
sco_etud.fill_etuds_info(etuds)
#
- sem = sco_formsemestre.get_formsemestre(formsemestre_id)
+ formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
prefs = sco_preferences.SemPreferences(formsemestre_id)
params = {
"date_jury": date_jury,
@@ -363,18 +402,22 @@ def pdf_lettres_individuelles(
}
# copie preferences
for name in sco_preferences.get_base_preferences().prefs_name:
- params[name] = sco_preferences.get_preference(name, sem["formsemestre_id"])
+ params[name] = sco_preferences.get_preference(name, formsemestre_id)
bookmarks = {}
objects = [] # list of PLATYPUS objects
npages = 0
- for e in dpv["decisions"]:
- if e["decision_sem"]: # decision prise
- etud = sco_etud.get_etud_info(e["identite"]["etudid"], filled=True)[0]
- params["nomEtud"] = etud["nomprenom"]
- bookmarks[npages + 1] = scu.suppress_accents(etud["nomprenom"])
+ for decision in dpv["decisions"]:
+ if (
+ decision["decision_sem"]
+ or decision.get("decision_annee")
+ or decision.get("decision_rcue")
+ ): # decision prise
+ etud: Identite = Identite.query.get(decision["identite"]["etudid"])
+ params["nomEtud"] = etud.nomprenom
+ bookmarks[npages + 1] = scu.suppress_accents(etud.nomprenom)
objects += pdf_lettre_individuelle(
- dpv["formsemestre"], e, etud, params, signature
+ dpv["formsemestre"], decision, etud, params, signature
)
objects.append(PageBreak())
npages += 1
@@ -394,8 +437,8 @@ def pdf_lettres_individuelles(
document.addPageTemplates(
CourrierIndividuelTemplate(
document,
- author="%s %s (E. Viennet)" % (sco_version.SCONAME, sco_version.SCOVERSION),
- title="Lettres décision %s" % sem["titreannee"],
+ author=f"{sco_version.SCONAME} {sco_version.SCOVERSION} (E. Viennet)",
+ title=f"Lettres décision {formsemestre.titre_annee()}",
subject="Décision jury",
margins=margins,
pagesbookmarks=bookmarks,
@@ -408,36 +451,41 @@ def pdf_lettres_individuelles(
return data
-def _descr_jury(sem, diplome):
+def _descr_jury(formsemestre: FormSemestre, diplome):
+
if not diplome:
- t = "passage de Semestre %d en Semestre %d" % (
- sem["semestre_id"],
- sem["semestre_id"] + 1,
- )
- s = "passage de semestre"
+ if formsemestre.formation.is_apc():
+ t = f"""BUT{(formsemestre.semestre_id+1)//2}"""
+ s = t
+ else:
+ t = f"""passage de Semestre {formsemestre.semestre_id} en Semestre {formsemestre.semestre_id + 1}"""
+ s = "passage de semestre"
else:
t = "délivrance du diplôme"
s = t
return t, s # titre long, titre court
-def pdf_lettre_individuelle(sem, decision, etud, params, signature=None):
+def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=None):
"""
Renvoie une liste d'objets PLATYPUS pour intégration
dans un autre document.
"""
#
formsemestre_id = sem["formsemestre_id"]
- Se: SituationEtudParcours = decision["Se"]
- t, s = _descr_jury(sem, Se.parcours_validated() or not Se.semestre_non_terminal)
+ formsemestre = FormSemestre.query.get(formsemestre_id)
+ Se: SituationEtudCursus = decision["Se"]
+ t, s = _descr_jury(
+ formsemestre, Se.parcours_validated() or not Se.semestre_non_terminal
+ )
objects = []
style = reportlab.lib.styles.ParagraphStyle({})
style.fontSize = 14
style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre_id)
style.leading = 18
- style.alignment = TA_JUSTIFY
+ style.alignment = TA_LEFT
- params["semestre_id"] = sem["semestre_id"]
+ params["semestre_id"] = formsemestre.semestre_id
params["decision_sem_descr"] = decision["decision_sem_descr"]
params["type_jury"] = t # type de jury (passage ou delivrance)
params["type_jury_abbrv"] = s # idem, abbrégé
@@ -450,28 +498,18 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None):
params["INSTITUTION_CITY"] = (
sco_preferences.get_preference("INSTITUTION_CITY", formsemestre_id) or ""
)
+
if decision["prev_decision_sem"]:
params["prev_semestre_id"] = decision["prev"]["semestre_id"]
- params["prev_code_descr"] = decision["prev_code_descr"]
+
+ params["prev_decision_sem_txt"] = ""
+ params["decision_orig"] = ""
params.update(decision["identite"])
# fix domicile
if params["domicile"]:
params["domicile"] = params["domicile"].replace("\\n", "
")
- # Décision semestre courant:
- if sem["semestre_id"] >= 0:
- params["decision_orig"] = "du semestre S%s" % sem["semestre_id"]
- else:
- params["decision_orig"] = ""
-
- if decision["prev_decision_sem"]:
- params["prev_decision_sem_txt"] = (
- """Décision du semestre antérieur S%(prev_semestre_id)s : %(prev_code_descr)s"""
- % params
- )
- else:
- params["prev_decision_sem_txt"] = ""
# UE capitalisées:
if decision["decisions_ue"] and decision["decisions_ue_descr"]:
params["decision_ue_txt"] = (
@@ -498,7 +536,7 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None):
params[
"autorisations_txt"
] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : %s""" % (
- etud["ne"],
+ etud.e,
s,
s,
decision["autorisations_descr"],
@@ -513,6 +551,14 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None):
else:
params["diplome_txt"] = ""
+ # Les fonctions ci-dessous ajoutent ou modifient des champs:
+ if formsemestre.formation.is_apc():
+ # ajout champs spécifiques PV BUT
+ add_apc_infos(formsemestre, params, decision)
+ else:
+ # ajout champs spécifiques PV DUT
+ add_classic_infos(formsemestre, params, decision)
+
# Corps de la lettre:
objects += sco_bulletins_pdf.process_field(
sco_preferences.get_preference("PV_LETTER_TEMPLATE", sem["formsemestre_id"]),
@@ -567,39 +613,30 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None):
return objects
-def _simulate_br(p, para=""):
- """Reportlab bug turnaround (could be removed in a future version).
- p is a string with Reportlab intra-paragraph XML tags.
- Replaces
(currently ignored by Reportlab) by
+def add_classic_infos(formsemestre: FormSemestre, params: dict, decision: dict):
+ """Ajoute les champs pour les formations classiques, donc avec codes semestres"""
+ if decision["prev_decision_sem"]:
+ params["prev_code_descr"] = decision["prev_code_descr"]
+ params[
+ "prev_decision_sem_txt"
+ ] = f"""Décision du semestre antérieur S{params['prev_semestre_id']} : {params['prev_code_descr']}"""
+ # Décision semestre courant:
+ if formsemestre.semestre_id >= 0:
+ params["decision_orig"] = f"du semestre S{formsemestre.semestre_id}"
+ else:
+ params["decision_orig"] = ""
+
+
+def add_apc_infos(formsemestre: FormSemestre, params: dict, decision: dict):
+ """Ajoute les champs pour les formations APC (BUT), donc avec codes RCUE et année"""
+ annee_but = (formsemestre.semestre_id + 1) // 2
+ params["decision_orig"] = f"année BUT{annee_but}"
+ params["decision_sem_descr"] = decision.get("decision_annee", {}).get("code", "")
+ params[
+ "decision_ue_txt"
+ ] = f"""{params["decision_ue_txt"]}
+ Niveaux de compétences:
{decision.get("descr_decisions_rcue", "")}
"""
- l = re.split(r"<.*?br.*?/>", p)
- return ("" + para).join(l)
-
-
-def _make_signature_image(signature, leftindent, formsemestre_id):
- "cree un paragraphe avec l'image signature"
- # cree une image PIL pour avoir la taille (W,H)
- from PIL import Image as PILImage
-
- f = io.BytesIO(signature)
- im = PILImage.open(f)
- width, height = im.size
- pdfheight = (
- 1.0
- * sco_preferences.get_preference("pv_sig_image_height", formsemestre_id)
- * mm
- )
- f.seek(0, 0)
-
- style = styles.ParagraphStyle({})
- style.leading = 1.0 * sco_preferences.get_preference(
- "SCOLAR_FONT_SIZE", formsemestre_id
- ) # vertical space
- style.leftIndent = leftindent
- return Table(
- [("", Image(f, width=width * pdfheight / float(height), height=pdfheight))],
- colWidths=(9 * cm, 7 * cm),
- )
# ----------------------------------------------
@@ -699,7 +736,8 @@ def _pvjury_pdf_type(
sem = dpv["formsemestre"]
formsemestre_id = sem["formsemestre_id"]
- titre_jury, _ = _descr_jury(sem, diplome)
+ formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
+ titre_jury, _ = _descr_jury(formsemestre, diplome)
titre_diplome = pv_title or dpv["formation"]["titre_officiel"]
objects = []
diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py
index 214532a55..97f01f756 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/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index 31de4bb22..2087a141e 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -664,6 +664,15 @@ def flash_errors(form):
# see https://getbootstrap.com/docs/4.0/components/alerts/
+def flash_once(message: str):
+ """Flash the message, but only once per request"""
+ if not hasattr(g, "sco_flashed_once"):
+ g.sco_flashed_once = set()
+ if not message in g.sco_flashed_once:
+ flash(message)
+ g.sco_flashed_once.add(message)
+
+
def sendCSVFile(data, filename): # DEPRECATED utiliser send_file
"""publication fichier CSV."""
return send_file(data, filename=filename, mime=CSV_MIMETYPE, attached=True)
diff --git a/app/static/css/releve-but.css b/app/static/css/releve-but.css
index a38e11122..745b6a970 100644
--- a/app/static/css/releve-but.css
+++ b/app/static/css/releve-but.css
@@ -169,7 +169,7 @@ section>div:nth-child(1){
border: none;
margin-left: auto;
}
-.rang{
+.rang, .competence{
font-weight: bold;
}
.ue .rang{
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index c4db0a261..6a2dcd99b 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -2210,6 +2210,7 @@ ul.notes_module_list {
list-style-type: none;
}
+/*Choix niveau dans form edit UE */
div.ue_choix_niveau {
background-color: rgb(191, 242, 255);
border: 1px solid blue;
@@ -2219,6 +2220,19 @@ div.ue_choix_niveau {
margin-right: 15px;
}
+/* Choix niveau dans edition programme (ue_table) */
+div.formation_list_ues div.ue_choix_niveau {
+ margin-left: 64px;
+ margin-right: 64px;
+ margin-top: 2px;
+ padding: 4px;
+ font-size: 14px;
+}
+
+div.formation_list_ues div.ue_choix_niveau b {
+ font-weight: normal;
+}
+
div#ue_list_modules {
background-color: rgb(251, 225, 165);
border: 1px solid blue;
diff --git a/app/static/js/edit_ue.js b/app/static/js/edit_ue.js
index 18bae83fb..056d4cbae 100644
--- a/app/static/js/edit_ue.js
+++ b/app/static/js/edit_ue.js
@@ -1,13 +1,16 @@
// Affiche et met a jour la liste des UE partageant le meme code
$().ready(function () {
- update_ue_list();
- $("#tf_ue_code").bind("keyup", update_ue_list);
+ if (document.querySelector("#tf_ue_id")) {
+ /* fonctions spécifiques pour edition UE */
+ update_ue_list();
+ $("#tf_ue_code").bind("keyup", update_ue_list);
- $("select#tf_type").change(function () {
+ $("select#tf_type").change(function () {
+ update_bonus_description();
+ });
update_bonus_description();
- });
- update_bonus_description();
+ }
});
function update_bonus_description() {
@@ -33,11 +36,10 @@ function update_ue_list() {
});
}
-function set_ue_niveau_competence() {
- let ue_id = document.querySelector("#tf_ue_id").value;
- let select = document.querySelector("#form_ue_choix_niveau select");
- let niveau_id = select.value;
- let set_ue_niveau_competence_url = select.dataset.setter;
+function set_ue_niveau_competence(elem) {
+ let ue_id = elem.dataset.ue_id;
+ let niveau_id = elem.value;
+ let set_ue_niveau_competence_url = elem.dataset.setter;
$.post(set_ue_niveau_competence_url,
{
ue_id: ue_id,
diff --git a/app/static/js/releve-but.js b/app/static/js/releve-but.js
index 7d3bc985e..473bc797d 100644
--- a/app/static/js/releve-but.js
+++ b/app/static/js/releve-but.js
@@ -232,7 +232,7 @@ class releveBUT extends HTMLElement {
${(()=>{
let output = "";
data.semestre.decision_rcue.forEach(competence=>{
- output += `${competence.niveau.competence.titre}
${competence.code}
`;
+ output += `${competence.niveau.competence.titre}
${competence.code}
`;
})
return output;
})()}
diff --git a/app/templates/pn/form_ues.html b/app/templates/pn/form_ues.html
index fed118333..aa6b32359 100644
--- a/app/templates/pn/form_ues.html
+++ b/app/templates/pn/form_ues.html
@@ -30,7 +30,7 @@
- {{ue.acronyme}} {{ue.titre}}
+
{% set virg = joiner(", ") %}
(
{%- if ue.ue_code -%}{{ virg() }}code {{ue.ue_code}} {%- endif -%}
@@ -53,16 +54,16 @@
)
- {% if (ue.niveau_competence is none) and ue.type == 0 %}
- pas de compétence associée
- {% endif %}
-
+
{% if editable and not ue.is_locked() %}
modifier
{% endif %}
+ {{ form_ue_choix_niveau(formation, ue)|safe }}
+
+
{% if ue.type == 1 and ue.modules.count() == 0 %}
aucun module rattaché !
{% endif %}
diff --git a/app/views/notes.py b/app/views/notes.py
index c1715c927..7bb9b027c 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -296,7 +296,9 @@ def formsemestre_bulletinetud(
format = format or "html"
if not isinstance(formsemestre_id, int):
- raise ScoInvalidIdType("formsemestre_id must be an integer !")
+ raise ScoInvalidIdType(
+ "formsemestre_bulletinetud: formsemestre_id must be an integer !"
+ )
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
if etudid:
etud = models.Identite.query.get_or_404(etudid)
@@ -826,7 +828,9 @@ def XMLgetFormsemestres(etape_apo=None, formsemestre_id=None):
if not formsemestre_id:
return flask.abort(404, "argument manquant: formsemestre_id")
if not isinstance(formsemestre_id, int):
- return flask.abort(404, "formsemestre_id must be an integer !")
+ return flask.abort(
+ 404, "XMLgetFormsemestres: formsemestre_id must be an integer !"
+ )
args = {}
if etape_apo:
args["etape_apo"] = etape_apo
@@ -2548,9 +2552,8 @@ def formsemestre_validation_suppress_etud(
)
if not dialog_confirmed:
d = sco_bulletins_json.dict_decision_jury(
- etudid, formsemestre_id, with_decisions=True
+ etud, formsemestre, with_decisions=True
)
- d.update(but_validations.dict_decision_jury(etud, formsemestre))
descr_ues = [f"{u['acronyme']}: {u['code']}" for u in d.get("decision_ue", [])]
dec_annee = d.get("decision_annee")
diff --git a/sco_version.py b/sco_version.py
index 256794b73..83068bb61 100755
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.3.16"
+SCOVERSION = "9.3.19"
SCONAME = "ScoDoc"
diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py
index a00ab66e5..52ed79f0d 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"