forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into table
This commit is contained in:
commit
47ed37e90e
@ -46,5 +46,6 @@ from app.api import (
|
|||||||
jury,
|
jury,
|
||||||
logos,
|
logos,
|
||||||
partitions,
|
partitions,
|
||||||
|
semset,
|
||||||
users,
|
users,
|
||||||
)
|
)
|
||||||
|
39
app/api/semset.py
Normal file
39
app/api/semset.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
ScoDoc 9 API : accès aux formsemestres
|
||||||
|
"""
|
||||||
|
from flask import g, jsonify, request
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
|
import app
|
||||||
|
from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR
|
||||||
|
from app.decorators import scodoc, permission_required
|
||||||
|
from app.scodoc.sco_utils import json_error
|
||||||
|
from app.models.formsemestre import NotesSemSet
|
||||||
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/semset/set_periode/<int:semset_id>", methods=["POST"])
|
||||||
|
@api_web_bp.route("/semset/set_periode/<int:semset_id>", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoEditApo)
|
||||||
|
# TODO à modifier pour utiliser @as_json
|
||||||
|
def semset_set_periode(semset_id: int):
|
||||||
|
"Change la période d'un semset"
|
||||||
|
query = NotesSemSet.query.filter_by(semset_id=semset_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
semset: NotesSemSet = query.first_or_404()
|
||||||
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
|
try:
|
||||||
|
periode = int(data)
|
||||||
|
semset.set_periode(periode)
|
||||||
|
except ValueError:
|
||||||
|
return json_error(API_CLIENT_ERROR, "invalid periode value")
|
||||||
|
return jsonify({"OK": True})
|
@ -1094,6 +1094,15 @@ class NotesSemSet(db.Model):
|
|||||||
sem_id = db.Column(db.Integer, nullable=False, default=0)
|
sem_id = db.Column(db.Integer, nullable=False, default=0)
|
||||||
"période: 0 (année), 1 (Simpair), 2 (Spair)"
|
"période: 0 (année), 1 (Simpair), 2 (Spair)"
|
||||||
|
|
||||||
|
def set_periode(self, periode: int):
|
||||||
|
"""Modifie la période 0 (année), 1 (Simpair), 2 (Spair)"""
|
||||||
|
if periode not in {0, 1, 2}:
|
||||||
|
raise ValueError("periode invalide")
|
||||||
|
self.sem_id = periode
|
||||||
|
log(f"semset.set_periode({self.id}, {periode})")
|
||||||
|
db.session.add(self)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
# Association: many to many
|
# Association: many to many
|
||||||
notes_semset_formsemestre = db.Table(
|
notes_semset_formsemestre = db.Table(
|
||||||
|
@ -764,24 +764,24 @@ class SeqGenTable(object):
|
|||||||
|
|
||||||
# ----- Exemple d'utilisation minimal.
|
# ----- Exemple d'utilisation minimal.
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
T = GenTable(
|
table = GenTable(
|
||||||
rows=[{"nom": "Hélène", "age": 26}, {"nom": "Titi&çà§", "age": 21}],
|
rows=[{"nom": "Hélène", "age": 26}, {"nom": "Titi&çà§", "age": 21}],
|
||||||
columns_ids=("nom", "age"),
|
columns_ids=("nom", "age"),
|
||||||
)
|
)
|
||||||
print("--- HTML:")
|
print("--- HTML:")
|
||||||
print(T.gen(format="html"))
|
print(table.gen(format="html"))
|
||||||
print("\n--- XML:")
|
print("\n--- XML:")
|
||||||
print(T.gen(format="xml"))
|
print(table.gen(format="xml"))
|
||||||
print("\n--- JSON:")
|
print("\n--- JSON:")
|
||||||
print(T.gen(format="json"))
|
print(table.gen(format="json"))
|
||||||
# Test pdf:
|
# Test pdf:
|
||||||
import io
|
import io
|
||||||
from reportlab.platypus import KeepInFrame
|
from reportlab.platypus import KeepInFrame
|
||||||
from app.scodoc import sco_preferences, sco_pdf
|
from app.scodoc import sco_preferences, sco_pdf
|
||||||
|
|
||||||
preferences = sco_preferences.SemPreferences()
|
preferences = sco_preferences.SemPreferences()
|
||||||
T.preferences = preferences
|
table.preferences = preferences
|
||||||
objects = T.gen(format="pdf")
|
objects = table.gen(format="pdf")
|
||||||
objects = [KeepInFrame(0, 0, objects, mode="shrink")]
|
objects = [KeepInFrame(0, 0, objects, mode="shrink")]
|
||||||
doc = io.BytesIO()
|
doc = io.BytesIO()
|
||||||
document = sco_pdf.BaseDocTemplate(doc)
|
document = sco_pdf.BaseDocTemplate(doc)
|
||||||
@ -794,6 +794,6 @@ if __name__ == "__main__":
|
|||||||
data = doc.getvalue()
|
data = doc.getvalue()
|
||||||
with open("/tmp/gen_table.pdf", "wb") as f:
|
with open("/tmp/gen_table.pdf", "wb") as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
p = T.make_page(format="pdf")
|
p = table.make_page(format="pdf")
|
||||||
with open("toto.pdf", "wb") as f:
|
with open("toto.pdf", "wb") as f:
|
||||||
f.write(p)
|
f.write(p)
|
||||||
|
@ -140,7 +140,7 @@ def sco_header(
|
|||||||
init_google_maps=False, # Google maps
|
init_google_maps=False, # Google maps
|
||||||
init_datatables=True,
|
init_datatables=True,
|
||||||
titrebandeau="", # titre dans bandeau superieur
|
titrebandeau="", # titre dans bandeau superieur
|
||||||
head_message="", # message action (petit cadre jaune en haut)
|
head_message="", # message action (petit cadre jaune en haut) DEPRECATED
|
||||||
user_check=True, # verifie passwords temporaires
|
user_check=True, # verifie passwords temporaires
|
||||||
etudid=None,
|
etudid=None,
|
||||||
formsemestre_id=None,
|
formsemestre_id=None,
|
||||||
|
@ -100,7 +100,8 @@ from chardet import detect as chardet_detect
|
|||||||
from app import log
|
from app import log
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import FormSemestre, Identite
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
|
from app.models import FormSemestre, Identite, ApcValidationAnnee
|
||||||
from app.models.config import ScoDocSiteConfig
|
from app.models.config import ScoDocSiteConfig
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
|
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
|
||||||
@ -220,10 +221,10 @@ class ApoElt(object):
|
|||||||
self.cols.append(col)
|
self.cols.append(col)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "ApoElt(code='%s', cols=%s)" % (self.code, pprint.pformat(self.cols))
|
return f"ApoElt(code='{self.code}', cols={pprint.pformat(self.cols)})"
|
||||||
|
|
||||||
|
|
||||||
class EtuCol(object):
|
class EtuCol:
|
||||||
"""Valeurs colonnes d'un element pour un etudiant"""
|
"""Valeurs colonnes d'un element pour un etudiant"""
|
||||||
|
|
||||||
def __init__(self, nip, apo_elt, init_vals):
|
def __init__(self, nip, apo_elt, init_vals):
|
||||||
@ -276,7 +277,8 @@ class ApoEtud(dict):
|
|||||||
self.export_res_sem = export_res_sem # elt_sem_apo
|
self.export_res_sem = export_res_sem # elt_sem_apo
|
||||||
self.export_res_ues = export_res_ues
|
self.export_res_ues = export_res_ues
|
||||||
self.export_res_modules = export_res_modules
|
self.export_res_modules = export_res_modules
|
||||||
self.export_res_sdj = export_res_sdj # export meme si pas de decision de jury
|
self.export_res_sdj = export_res_sdj
|
||||||
|
"export meme si pas de decision de jury"
|
||||||
self.export_res_rat = export_res_rat
|
self.export_res_rat = export_res_rat
|
||||||
self.fmt_note = functools.partial(
|
self.fmt_note = functools.partial(
|
||||||
_apo_fmt_note, fmt=ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
|
_apo_fmt_note, fmt=ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
|
||||||
@ -354,7 +356,8 @@ class ApoEtud(dict):
|
|||||||
]
|
]
|
||||||
except KeyError as exc:
|
except KeyError as exc:
|
||||||
log(
|
log(
|
||||||
f"associate_sco: missing key, etud={self}\ncode='{code}'\netape='{apo_data.etape_apogee}'"
|
f"""associate_sco: missing key, etud={self}\ncode='{
|
||||||
|
code}'\netape='{apo_data.etape_apogee}'"""
|
||||||
)
|
)
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
f"""L'élément {code} n'a pas de résultat: peut-être une erreur
|
f"""L'élément {code} n'a pas de résultat: peut-être une erreur
|
||||||
@ -372,7 +375,7 @@ class ApoEtud(dict):
|
|||||||
|
|
||||||
def search_elt_in_sem(self, code, sem, cur_sem, autre_sem) -> dict:
|
def search_elt_in_sem(self, code, sem, cur_sem, autre_sem) -> dict:
|
||||||
"""
|
"""
|
||||||
VET code jury etape
|
VET code jury etape (en BUT, le code annuel)
|
||||||
ELP élément pédagogique: UE, module
|
ELP élément pédagogique: UE, module
|
||||||
Autres éléments: résultats du semestre ou de l'année scolaire:
|
Autres éléments: résultats du semestre ou de l'année scolaire:
|
||||||
=> VRTW1: code additionnel au semestre ("code élement semestre", elt_sem_apo)
|
=> VRTW1: code additionnel au semestre ("code élement semestre", elt_sem_apo)
|
||||||
@ -401,7 +404,7 @@ class ApoEtud(dict):
|
|||||||
# pas de decision de jury, on n'enregistre rien
|
# pas de decision de jury, on n'enregistre rien
|
||||||
# (meme si démissionnaire)
|
# (meme si démissionnaire)
|
||||||
if not self.has_logged_no_decision:
|
if not self.has_logged_no_decision:
|
||||||
self.log.append("Pas de decision")
|
self.log.append("Pas de décision (export désactivé)")
|
||||||
self.has_logged_no_decision = True
|
self.has_logged_no_decision = True
|
||||||
return VOID_APO_RES
|
return VOID_APO_RES
|
||||||
|
|
||||||
@ -423,6 +426,7 @@ class ApoEtud(dict):
|
|||||||
if export_res_etape:
|
if export_res_etape:
|
||||||
return self.comp_elt_annuel(etudid, cur_sem, autre_sem)
|
return self.comp_elt_annuel(etudid, cur_sem, autre_sem)
|
||||||
else:
|
else:
|
||||||
|
self.log.append("export étape désactivé")
|
||||||
return VOID_APO_RES
|
return VOID_APO_RES
|
||||||
|
|
||||||
# Element semestre:
|
# Element semestre:
|
||||||
@ -495,43 +499,53 @@ class ApoEtud(dict):
|
|||||||
def comp_elt_annuel(self, etudid, cur_sem, autre_sem):
|
def comp_elt_annuel(self, etudid, cur_sem, autre_sem):
|
||||||
"""Calcul resultat annuel (VET) à partir du semestre courant
|
"""Calcul resultat annuel (VET) à partir du semestre courant
|
||||||
et de l'autre (le suivant ou le précédent complétant l'année scolaire)
|
et de l'autre (le suivant ou le précédent complétant l'année scolaire)
|
||||||
|
En BUT, c'est la décision de jury annuelle (ApcValidationAnnee).
|
||||||
"""
|
"""
|
||||||
# Code annuel:
|
# Code annuel:
|
||||||
# - Note: moyenne des moyennes générales des deux semestres (pas vraiment de sens, mais faute de mieux)
|
# - Note: moyenne des moyennes générales des deux semestres
|
||||||
# on pourrait aussi bien prendre seulement la note du dernier semestre (S2 ou S4). Paramétrable ?
|
# (pas vraiment de sens, mais faute de mieux)
|
||||||
|
# on pourrait aussi bien prendre seulement la note du dernier semestre (S2 ou S4).
|
||||||
|
# XXX APOBUT: à modifier pour prendre moyenne indicative annuelle
|
||||||
|
#
|
||||||
# - Résultat jury:
|
# - Résultat jury:
|
||||||
# si l'autre est validé, code du semestre courant (ex: S1 (ADM), S2 (AJ) => année AJ)
|
# si l'autre est validé, code du semestre courant (ex: S1 (ADM), S2 (AJ) => année AJ)
|
||||||
# si l'autre n'est pas validé ou est DEF ou DEM, code de l'autre
|
# si l'autre n'est pas validé ou est DEF ou DEM, code de l'autre
|
||||||
#
|
#
|
||||||
# XXX cette règle est discutable, à valider
|
# XXX cette règle est discutable, à valider
|
||||||
|
|
||||||
# print 'comp_elt_annuel cur_sem=%s autre_sem=%s' % (cur_sem['formsemestre_id'], autre_sem['formsemestre_id'])
|
# log('comp_elt_annuel cur_sem=%s autre_sem=%s' % (cur_sem['formsemestre_id'], autre_sem['formsemestre_id']))
|
||||||
if not cur_sem:
|
if not cur_sem:
|
||||||
# l'étudiant n'a pas de semestre courant ?!
|
# l'étudiant n'a pas de semestre courant ?!
|
||||||
|
self.log.append("pas de semestre courant")
|
||||||
log(f"comp_elt_annuel: etudid {etudid} has no cur_sem")
|
log(f"comp_elt_annuel: etudid {etudid} has no cur_sem")
|
||||||
return VOID_APO_RES
|
return VOID_APO_RES
|
||||||
cur_formsemestre = FormSemestre.query.get_or_404(cur_sem["formsemestre_id"])
|
cur_formsemestre = FormSemestre.query.get_or_404(cur_sem["formsemestre_id"])
|
||||||
cur_nt: NotesTableCompat = res_sem.load_formsemestre_results(cur_formsemestre)
|
cur_nt: NotesTableCompat = res_sem.load_formsemestre_results(cur_formsemestre)
|
||||||
cur_decision = cur_nt.get_etud_decision_sem(etudid)
|
|
||||||
if not cur_decision:
|
|
||||||
# pas de decision => pas de résultat annuel
|
|
||||||
return VOID_APO_RES
|
|
||||||
|
|
||||||
if (cur_decision["code"] == RAT) and not self.export_res_rat:
|
if not self.is_apc:
|
||||||
# ne touche pas aux RATs
|
cur_decision = cur_nt.get_etud_decision_sem(etudid)
|
||||||
return VOID_APO_RES
|
if not cur_decision:
|
||||||
|
# pas de decision => pas de résultat annuel
|
||||||
|
return VOID_APO_RES
|
||||||
|
|
||||||
|
if (cur_decision["code"] == RAT) and not self.export_res_rat:
|
||||||
|
# ne touche pas aux RATs
|
||||||
|
return VOID_APO_RES
|
||||||
|
|
||||||
if not autre_sem:
|
if not autre_sem:
|
||||||
# formations monosemestre, ou code VET semestriel,
|
# formations monosemestre, ou code VET semestriel,
|
||||||
# ou jury intermediaire et etudiant non redoublant...
|
# ou jury intermediaire et etudiant non redoublant...
|
||||||
return self.comp_elt_semestre(cur_nt, cur_decision, etudid)
|
return self.comp_elt_semestre(cur_nt, cur_decision, etudid)
|
||||||
|
|
||||||
decision_apo = ScoDocSiteConfig.get_code_apo(cur_decision["code"])
|
|
||||||
|
|
||||||
autre_formsemestre = FormSemestre.query.get_or_404(autre_sem["formsemestre_id"])
|
autre_formsemestre = FormSemestre.query.get_or_404(autre_sem["formsemestre_id"])
|
||||||
autre_nt: NotesTableCompat = res_sem.load_formsemestre_results(
|
autre_nt: NotesTableCompat = res_sem.load_formsemestre_results(
|
||||||
autre_formsemestre
|
autre_formsemestre
|
||||||
)
|
)
|
||||||
|
# --- Traite le BUT à part:
|
||||||
|
if self.is_apc:
|
||||||
|
return self.comp_elt_annuel_apc(cur_nt, autre_nt, etudid)
|
||||||
|
# --- Formations classiques
|
||||||
|
decision_apo = ScoDocSiteConfig.get_code_apo(cur_decision["code"])
|
||||||
autre_decision = autre_nt.get_etud_decision_sem(etudid)
|
autre_decision = autre_nt.get_etud_decision_sem(etudid)
|
||||||
if not autre_decision:
|
if not autre_decision:
|
||||||
# pas de decision dans l'autre => pas de résultat annuel
|
# pas de decision dans l'autre => pas de résultat annuel
|
||||||
@ -564,6 +578,38 @@ class ApoEtud(dict):
|
|||||||
|
|
||||||
return dict(N=note_str, B=20, J="", R=decision_apo_annuelle, M="")
|
return dict(N=note_str, B=20, J="", R=decision_apo_annuelle, M="")
|
||||||
|
|
||||||
|
def comp_elt_annuel_apc(
|
||||||
|
self,
|
||||||
|
cur_res: ResultatsSemestreBUT,
|
||||||
|
autre_res: ResultatsSemestreBUT,
|
||||||
|
etudid: int,
|
||||||
|
):
|
||||||
|
"""L'élément Apo pour un résultat annuel BUT.
|
||||||
|
cur_res : les résultats du semestre sur lequel a été appelé l'export.
|
||||||
|
"""
|
||||||
|
# le semestre impair de l'année scolaire
|
||||||
|
if cur_res.formsemestre.semestre_id % 2:
|
||||||
|
formsemestre = cur_res.formsemestre
|
||||||
|
elif (
|
||||||
|
autre_res
|
||||||
|
and autre_res.formsemestre.annee_scolaire()
|
||||||
|
== cur_res.formsemestre.annee_scolaire()
|
||||||
|
):
|
||||||
|
formsemestre = autre_res.formsemestre
|
||||||
|
assert formsemestre.semestre_id % 2
|
||||||
|
else:
|
||||||
|
# ne trouve pas de semestre impair
|
||||||
|
return VOID_APO_RES
|
||||||
|
|
||||||
|
validation: ApcValidationAnnee = ApcValidationAnnee.query.filter_by(
|
||||||
|
formsemestre_id=formsemestre.id, etudid=etudid
|
||||||
|
).first()
|
||||||
|
if validation is None:
|
||||||
|
return VOID_APO_RES
|
||||||
|
return dict(
|
||||||
|
N="", B=20, J="", R=ScoDocSiteConfig.get_code_apo(validation.code), M=""
|
||||||
|
)
|
||||||
|
|
||||||
def etud_semestres_de_etape(self, apo_data):
|
def etud_semestres_de_etape(self, apo_data):
|
||||||
"""
|
"""
|
||||||
Lorsqu'on a une formation semestrialisée mais avec un code étape annuel,
|
Lorsqu'on a une formation semestrialisée mais avec un code étape annuel,
|
||||||
@ -599,12 +645,12 @@ class ApoEtud(dict):
|
|||||||
for sem in cur_sems:
|
for sem in cur_sems:
|
||||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
decision = nt.get_etud_decision_sem(self.etud["etudid"])
|
has_decision = nt.etud_has_decision(self.etud["etudid"])
|
||||||
if decision:
|
if has_decision:
|
||||||
cur_sem = sem
|
cur_sem = sem
|
||||||
break
|
break
|
||||||
if cur_sem is None:
|
if cur_sem is None:
|
||||||
cur_sem = cur_sems[0] # aucun avec decison, prend le plus recent
|
cur_sem = cur_sems[0] # aucun avec décision, prend le plus recent
|
||||||
|
|
||||||
if apo_data.cur_semestre_id <= 0:
|
if apo_data.cur_semestre_id <= 0:
|
||||||
return (
|
return (
|
||||||
@ -670,7 +716,7 @@ class ApoEtud(dict):
|
|||||||
return cur_sem, autre_sem
|
return cur_sem, autre_sem
|
||||||
|
|
||||||
|
|
||||||
class ApoData(object):
|
class ApoData:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
data: str,
|
data: str,
|
||||||
@ -697,8 +743,13 @@ class ApoData(object):
|
|||||||
self.export_res_rat = export_res_rat
|
self.export_res_rat = export_res_rat
|
||||||
self.orig_filename = orig_filename
|
self.orig_filename = orig_filename
|
||||||
self.periode = periode #
|
self.periode = periode #
|
||||||
|
"1 sem. sept-jan, 2 sem. fev-jul. 0 si étape en 1 seul semestre."
|
||||||
self.is_apc = None
|
self.is_apc = None
|
||||||
"Vrai si BUT"
|
"Vrai si BUT"
|
||||||
|
self.header: str = ""
|
||||||
|
"début du fichier Apogée (sera ré-écrit non modifié)"
|
||||||
|
self.titles: dict[str, str] = {}
|
||||||
|
"titres Apogée (section XX-APO_TITRES-XX)"
|
||||||
try:
|
try:
|
||||||
self.read_csv(data)
|
self.read_csv(data)
|
||||||
except ScoFormatError as e:
|
except ScoFormatError as e:
|
||||||
@ -723,13 +774,7 @@ class ApoData(object):
|
|||||||
False # True si jury à mi-étape, eg jury de S1 dans l'étape (S1, S2)
|
False # True si jury à mi-étape, eg jury de S1 dans l'étape (S1, S2)
|
||||||
)
|
)
|
||||||
|
|
||||||
log(
|
log(f"ApoData( periode={self.periode}, annee_scolaire={self.annee_scolaire} )")
|
||||||
"ApoData( periode=%s, annee_scolaire=%s )"
|
|
||||||
% (self.periode, self.annee_scolaire)
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_periode(self, periode): # currently unused
|
|
||||||
self.periode = periode
|
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""Recherche semestres ScoDoc concernés"""
|
"""Recherche semestres ScoDoc concernés"""
|
||||||
@ -871,16 +916,16 @@ class ApoData(object):
|
|||||||
if not line.strip():
|
if not line.strip():
|
||||||
continue # silently ignore blank lines
|
continue # silently ignore blank lines
|
||||||
line = line.strip(APO_NEWLINE)
|
line = line.strip(APO_NEWLINE)
|
||||||
fs = line.split(APO_SEP)
|
fields = line.split(APO_SEP)
|
||||||
cols = {} # { col_id : value }
|
cols = {} # { col_id : value }
|
||||||
for i in range(len(fs)):
|
for i, field in enumerate(fields):
|
||||||
cols[self.col_ids[i]] = fs[i]
|
cols[self.col_ids[i]] = field
|
||||||
L.append(
|
L.append(
|
||||||
ApoEtud(
|
ApoEtud(
|
||||||
nip=fs[0], # id etudiant
|
nip=fields[0], # id etudiant
|
||||||
nom=fs[1],
|
nom=fields[1],
|
||||||
prenom=fs[2],
|
prenom=fields[2],
|
||||||
naissance=fs[3],
|
naissance=fields[3],
|
||||||
cols=cols,
|
cols=cols,
|
||||||
export_res_etape=self.export_res_etape,
|
export_res_etape=self.export_res_etape,
|
||||||
export_res_sem=self.export_res_sem,
|
export_res_sem=self.export_res_sem,
|
||||||
@ -902,7 +947,8 @@ class ApoData(object):
|
|||||||
|
|
||||||
def get_vdi_apogee(self):
|
def get_vdi_apogee(self):
|
||||||
"""le VDI (version de diplôme), stocké dans l'élément VET
|
"""le VDI (version de diplôme), stocké dans l'élément VET
|
||||||
(note: on pourrait peut-être aussi bien le récupérer dans l'en-tête XX-APO_TITRES-XX apoC_cod_vdi)
|
(note: on pourrait peut-être aussi bien le récupérer dans
|
||||||
|
l'en-tête XX-APO_TITRES-XX apoC_cod_vdi)
|
||||||
"""
|
"""
|
||||||
for elt in self.apo_elts.values():
|
for elt in self.apo_elts.values():
|
||||||
if elt.type_objet == "VET":
|
if elt.type_objet == "VET":
|
||||||
@ -923,7 +969,7 @@ class ApoData(object):
|
|||||||
m = re.match("[12][0-9]{3}", self.titles["apoC_annee"])
|
m = re.match("[12][0-9]{3}", self.titles["apoC_annee"])
|
||||||
if not m:
|
if not m:
|
||||||
raise ScoFormatError(
|
raise ScoFormatError(
|
||||||
'Annee scolaire (apoC_annee) invalide: "%s"' % self.titles["apoC_annee"]
|
f"""Annee scolaire (apoC_annee) invalide: "{self.titles["apoC_annee"]}" """
|
||||||
)
|
)
|
||||||
return int(m.group(0))
|
return int(m.group(0))
|
||||||
|
|
||||||
@ -939,10 +985,10 @@ class ApoData(object):
|
|||||||
def write_etuds(self, f):
|
def write_etuds(self, f):
|
||||||
"""write apo CSV etuds on f"""
|
"""write apo CSV etuds on f"""
|
||||||
for e in self.etuds:
|
for e in self.etuds:
|
||||||
fs = [] # e['nip'], e['nom'], e['prenom'], e['naissance'] ]
|
fields = [] # e['nip'], e['nom'], e['prenom'], e['naissance'] ]
|
||||||
for col_id in self.col_ids:
|
for col_id in self.col_ids:
|
||||||
try:
|
try:
|
||||||
fs.append(str(e.new_cols[col_id]))
|
fields.append(str(e.new_cols[col_id]))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
log(
|
log(
|
||||||
"Error: %s %s missing column key %s"
|
"Error: %s %s missing column key %s"
|
||||||
@ -952,19 +998,18 @@ class ApoData(object):
|
|||||||
log("col_ids=%s" % pprint.pformat(self.col_ids))
|
log("col_ids=%s" % pprint.pformat(self.col_ids))
|
||||||
log("etudiant ignore.\n")
|
log("etudiant ignore.\n")
|
||||||
|
|
||||||
f.write(APO_SEP.join(fs) + APO_NEWLINE)
|
f.write(APO_SEP.join(fields) + APO_NEWLINE)
|
||||||
|
|
||||||
def list_unknown_elements(self):
|
def list_unknown_elements(self) -> list[str]:
|
||||||
"""Liste des codes des elements Apogee non trouvés dans ScoDoc
|
"""Liste des codes des elements Apogee non trouvés dans ScoDoc
|
||||||
(après traitement de tous les étudiants)
|
(après traitement de tous les étudiants)
|
||||||
"""
|
"""
|
||||||
s = set()
|
codes = set()
|
||||||
for e in self.etuds:
|
for e in self.etuds:
|
||||||
ul = [code for code in e.col_elts if e.col_elts[code] is None]
|
codes.update({code for code in e.col_elts if e.col_elts[code] is None})
|
||||||
s.update(ul)
|
codes_list = list(codes)
|
||||||
L = list(s)
|
codes_list.sort()
|
||||||
L.sort()
|
return codes_list
|
||||||
return L
|
|
||||||
|
|
||||||
def list_elements(self):
|
def list_elements(self):
|
||||||
"""Liste les codes des elements Apogée de la maquette
|
"""Liste les codes des elements Apogée de la maquette
|
||||||
@ -978,19 +1023,18 @@ class ApoData(object):
|
|||||||
declared = self.col_ids[4:] # id des colones dans l'en-tête
|
declared = self.col_ids[4:] # id des colones dans l'en-tête
|
||||||
present = sorted(self.cols.keys()) # colones presentes
|
present = sorted(self.cols.keys()) # colones presentes
|
||||||
log("Fichier Apogee invalide:")
|
log("Fichier Apogee invalide:")
|
||||||
log("Colonnes declarees: %s" % declared)
|
log(f"Colonnes declarees: {declared}")
|
||||||
log("Colonnes presentes: %s" % present)
|
log(f"Colonnes presentes: {present}")
|
||||||
raise ScoFormatError(
|
raise ScoFormatError(
|
||||||
"""Fichier Apogee invalide<br>Colonnes declarees: <tt>%s</tt>
|
f"""Fichier Apogee invalide<br>Colonnes declarees: <tt>{declared}</tt>
|
||||||
<br>Colonnes presentes: <tt>%s</tt>"""
|
<br>Colonnes presentes: <tt>{present}</tt>"""
|
||||||
% (declared, present)
|
|
||||||
)
|
)
|
||||||
# l'ensemble de tous les codes des elements apo des semestres:
|
# l'ensemble de tous les codes des elements apo des semestres:
|
||||||
sem_elems = reduce(set.union, list(self.get_codes_by_sem().values()), set())
|
sem_elems = reduce(set.union, list(self.get_codes_by_sem().values()), set())
|
||||||
|
|
||||||
return maq_elems, sem_elems
|
return maq_elems, sem_elems
|
||||||
|
|
||||||
def get_codes_by_sem(self):
|
def get_codes_by_sem(self) -> dict[int, set[str]]:
|
||||||
"""Pour chaque semestre associé, donne l'ensemble des codes de cette maquette Apogée
|
"""Pour chaque semestre associé, donne l'ensemble des codes de cette maquette Apogée
|
||||||
qui s'y trouvent (dans le semestre, les UE ou les modules).
|
qui s'y trouvent (dans le semestre, les UE ou les modules).
|
||||||
Return: { formsemestre_id : { 'code1', 'code2', ... }}
|
Return: { formsemestre_id : { 'code1', 'code2', ... }}
|
||||||
@ -1040,7 +1084,7 @@ class ApoData(object):
|
|||||||
"est_NAR": e.is_NAR,
|
"est_NAR": e.is_NAR,
|
||||||
"commentaire": "; ".join(e.log),
|
"commentaire": "; ".join(e.log),
|
||||||
}
|
}
|
||||||
if e.col_elts and e.col_elts[self.etape_apogee] != None:
|
if e.col_elts and e.col_elts[self.etape_apogee] is not None:
|
||||||
cr["etape"] = e.col_elts[self.etape_apogee].get("R", "")
|
cr["etape"] = e.col_elts[self.etape_apogee].get("R", "")
|
||||||
cr["etape_note"] = e.col_elts[self.etape_apogee].get("N", "")
|
cr["etape_note"] = e.col_elts[self.etape_apogee].get("N", "")
|
||||||
else:
|
else:
|
||||||
@ -1073,10 +1117,10 @@ def _apo_read_cols(f):
|
|||||||
Example: { 'apoL_c0001' : { 'Type Objet' : 'VET', 'Code' : 'V1IN', ... }, ... }
|
Example: { 'apoL_c0001' : { 'Type Objet' : 'VET', 'Code' : 'V1IN', ... }, ... }
|
||||||
"""
|
"""
|
||||||
line = f.readline().strip(" " + APO_NEWLINE)
|
line = f.readline().strip(" " + APO_NEWLINE)
|
||||||
fs = line.split(APO_SEP)
|
fields = line.split(APO_SEP)
|
||||||
if fs[0] != "apoL_a01_code":
|
if fields[0] != "apoL_a01_code":
|
||||||
raise ScoFormatError("invalid line: %s (expecting apoL_a01_code)" % line)
|
raise ScoFormatError(f"invalid line: {line} (expecting apoL_a01_code)")
|
||||||
col_keys = fs
|
col_keys = fields
|
||||||
|
|
||||||
while True: # skip premiere partie (apoL_a02_nom, ...)
|
while True: # skip premiere partie (apoL_a02_nom, ...)
|
||||||
line = f.readline().strip(" " + APO_NEWLINE)
|
line = f.readline().strip(" " + APO_NEWLINE)
|
||||||
@ -1090,10 +1134,9 @@ def _apo_read_cols(f):
|
|||||||
if line == "APO_COL_VAL_FIN":
|
if line == "APO_COL_VAL_FIN":
|
||||||
break
|
break
|
||||||
i += 1
|
i += 1
|
||||||
fs = line.split(APO_SEP)
|
fields = line.split(APO_SEP)
|
||||||
# print fs[0], len(fs)
|
|
||||||
# sanity check
|
# sanity check
|
||||||
col_id = fs[0] # apoL_c0001, ...
|
col_id = fields[0] # apoL_c0001, ...
|
||||||
if col_id in cols:
|
if col_id in cols:
|
||||||
raise ScoFormatError(f"duplicate column definition: {col_id}")
|
raise ScoFormatError(f"duplicate column definition: {col_id}")
|
||||||
m = re.match(r"^apoL_c([0-9]{4})$", col_id)
|
m = re.match(r"^apoL_c([0-9]{4})$", col_id)
|
||||||
@ -1104,13 +1147,13 @@ def _apo_read_cols(f):
|
|||||||
if int(m.group(1)) != i:
|
if int(m.group(1)) != i:
|
||||||
raise ScoFormatError(f"invalid column id: {col_id} for index {i}")
|
raise ScoFormatError(f"invalid column id: {col_id} for index {i}")
|
||||||
|
|
||||||
cols[col_id] = DictCol(list(zip(col_keys, fs)))
|
cols[col_id] = DictCol(list(zip(col_keys, fields)))
|
||||||
cols[col_id].lineno = f.lineno # for debuging purpose
|
cols[col_id].lineno = f.lineno # for debuging purpose
|
||||||
|
|
||||||
return cols
|
return cols
|
||||||
|
|
||||||
|
|
||||||
def _apo_read_TITRES(f):
|
def _apo_read_TITRES(f) -> dict:
|
||||||
"Lecture section TITRES du fichier Apogée, renvoie dict"
|
"Lecture section TITRES du fichier Apogée, renvoie dict"
|
||||||
d = {}
|
d = {}
|
||||||
while True:
|
while True:
|
||||||
@ -1166,10 +1209,10 @@ def nar_etuds_table(apo_data, NAR_Etuds):
|
|||||||
"""Liste les NAR -> excel table"""
|
"""Liste les NAR -> excel table"""
|
||||||
code_etape = apo_data.etape_apogee
|
code_etape = apo_data.etape_apogee
|
||||||
today = datetime.datetime.today().strftime("%d/%m/%y")
|
today = datetime.datetime.today().strftime("%d/%m/%y")
|
||||||
L = []
|
rows = []
|
||||||
NAR_Etuds.sort(key=lambda k: k["nom"])
|
NAR_Etuds.sort(key=lambda k: k["nom"])
|
||||||
for e in NAR_Etuds:
|
for e in NAR_Etuds:
|
||||||
L.append(
|
rows.append(
|
||||||
{
|
{
|
||||||
"nom": e["nom"],
|
"nom": e["nom"],
|
||||||
"prenom": e["prenom"],
|
"prenom": e["prenom"],
|
||||||
@ -1210,13 +1253,13 @@ def nar_etuds_table(apo_data, NAR_Etuds):
|
|||||||
"c13",
|
"c13",
|
||||||
"date",
|
"date",
|
||||||
)
|
)
|
||||||
T = GenTable(
|
table = GenTable(
|
||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
titles=dict(zip(columns_ids, columns_ids)),
|
titles=dict(zip(columns_ids, columns_ids)),
|
||||||
rows=L,
|
rows=rows,
|
||||||
xls_sheet_name="NAR ScoDoc",
|
xls_sheet_name="NAR ScoDoc",
|
||||||
)
|
)
|
||||||
return T.excel()
|
return table.excel()
|
||||||
|
|
||||||
|
|
||||||
def export_csv_to_apogee(
|
def export_csv_to_apogee(
|
||||||
@ -1301,24 +1344,28 @@ def export_csv_to_apogee(
|
|||||||
cr_filename = basename + "-decisions" + scu.XLSX_SUFFIX
|
cr_filename = basename + "-decisions" + scu.XLSX_SUFFIX
|
||||||
|
|
||||||
logf = io.StringIO()
|
logf = io.StringIO()
|
||||||
logf.write("export_to_apogee du %s\n\n" % time.ctime())
|
logf.write(f"export_to_apogee du {time.ctime()}\n\n")
|
||||||
logf.write("Semestres ScoDoc sources:\n")
|
logf.write("Semestres ScoDoc sources:\n")
|
||||||
for sem in apo_data.sems_etape:
|
for sem in apo_data.sems_etape:
|
||||||
logf.write("\t%(titremois)s\n" % sem)
|
logf.write("\t%(titremois)s\n" % sem)
|
||||||
logf.write("Periode: %s\n" % periode)
|
|
||||||
logf.write("export_res_etape: %s\n" % int(export_res_etape))
|
def vrai(val):
|
||||||
logf.write("export_res_sem: %s\n" % int(export_res_sem))
|
return "vrai" if int(val) else "faux"
|
||||||
logf.write("export_res_ues: %s\n" % int(export_res_ues))
|
|
||||||
logf.write("export_res_modules: %s\n" % int(export_res_modules))
|
logf.write(f"Période: {periode}\n")
|
||||||
logf.write("export_res_sdj: %s\n" % int(export_res_sdj))
|
logf.write(f"exporte résultat à l'étape: {vrai(export_res_etape)}\n")
|
||||||
|
logf.write(f"exporte résultat à l'année: {vrai(export_res_sem)}\n")
|
||||||
|
logf.write(f"exporte résultats des UEs: {vrai(export_res_ues)}\n")
|
||||||
|
logf.write(f"exporte résultats des modules: {vrai(export_res_modules)}\n")
|
||||||
|
logf.write(f"exporte résultats sans décision de jury: {vrai(export_res_sdj)}\n")
|
||||||
logf.write(
|
logf.write(
|
||||||
"\nEtudiants Apogee non trouves dans ScoDoc:\n"
|
"\nÉtudiants Apogée non trouvés dans ScoDoc:\n"
|
||||||
+ "\n".join(
|
+ "\n".join(
|
||||||
["%s\t%s\t%s" % (e["nip"], e["nom"], e["prenom"]) for e in Apo_Non_ScoDoc]
|
["%s\t%s\t%s" % (e["nip"], e["nom"], e["prenom"]) for e in Apo_Non_ScoDoc]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
logf.write(
|
logf.write(
|
||||||
"\nEtudiants Apogee non inscrits sur ScoDoc dans cette étape:\n"
|
"\nÉtudiants Apogée non inscrits sur ScoDoc dans cette étape:\n"
|
||||||
+ "\n".join(
|
+ "\n".join(
|
||||||
[
|
[
|
||||||
"%s\t%s\t%s" % (e["nip"], e["nom"], e["prenom"])
|
"%s\t%s\t%s" % (e["nip"], e["nom"], e["prenom"])
|
||||||
|
@ -342,11 +342,11 @@ def do_formsemestre_archive(
|
|||||||
formsemestre, res, include_evaluations=True
|
formsemestre, res, include_evaluations=True
|
||||||
)
|
)
|
||||||
if table_html:
|
if table_html:
|
||||||
|
flash(f"Moyennes archivées le {date}", category="info")
|
||||||
data = "\n".join(
|
data = "\n".join(
|
||||||
[
|
[
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title=f"Moyennes archivées le {date}",
|
page_title=f"Moyennes archivées le {date}",
|
||||||
head_message=f"Moyennes archivées le {date}",
|
|
||||||
no_side_bar=True,
|
no_side_bar=True,
|
||||||
),
|
),
|
||||||
f'<h2 class="fontorange">Valeurs archivées le {date}</h2>',
|
f'<h2 class="fontorange">Valeurs archivées le {date}</h2>',
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
les dossiers d'admission et autres pièces utiles.
|
les dossiers d'admission et autres pièces utiles.
|
||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, render_template
|
from flask import flash, render_template, url_for
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
@ -38,7 +38,6 @@ import app.scodoc.sco_utils as scu
|
|||||||
from app.scodoc import sco_import_etuds
|
from app.scodoc import sco_import_etuds
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_trombino
|
from app.scodoc import sco_trombino
|
||||||
from app.scodoc import sco_excel
|
|
||||||
from app.scodoc import sco_archives
|
from app.scodoc import sco_archives
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
@ -233,13 +232,9 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
EtudsArchive.delete_archive(archive_id)
|
EtudsArchive.delete_archive(archive_id)
|
||||||
|
flash("Archive supprimée")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
"scolar.ficheEtud",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
etudid=etudid,
|
|
||||||
head_message="Archive%20supprimée",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ import io
|
|||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g, send_file, request
|
from flask import flash, g, request, send_file, url_for
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
@ -692,8 +692,10 @@ def view_apo_csv_delete(etape_apo="", semset_id="", dialog_confirmed=False):
|
|||||||
)
|
)
|
||||||
if info:
|
if info:
|
||||||
sco_etape_apogee.apo_csv_delete(info["archive_id"])
|
sco_etape_apogee.apo_csv_delete(info["archive_id"])
|
||||||
return flask.redirect(dest_url + "&head_message=Archive%20supprimée")
|
flash("Archive supprimée")
|
||||||
return flask.redirect(dest_url + "&head_message=Archive%20inexistante")
|
return flask.redirect(dest_url)
|
||||||
|
flash("Archive inexistante", category="error")
|
||||||
|
return flask.redirect(dest_url)
|
||||||
|
|
||||||
|
|
||||||
def view_apo_csv(etape_apo="", semset_id="", format="html"):
|
def view_apo_csv(etape_apo="", semset_id="", format="html"):
|
||||||
|
@ -218,16 +218,14 @@ def help():
|
|||||||
</div> """
|
</div> """
|
||||||
|
|
||||||
|
|
||||||
def entete_liste_etudiant():
|
def entete_liste_etudiant() -> str:
|
||||||
return """
|
return """
|
||||||
<h4 id='effectifs'>Liste des étudiants <span id='compte'></span>
|
<ul>
|
||||||
<ul>
|
<li id="sans_filtre">Pas de filtrage: Cliquez sur un des nombres du tableau ci-dessus pour
|
||||||
<li id='sans_filtre'>Pas de filtrage: Cliquez sur un des nombres du tableau ci-dessus pour
|
|
||||||
n'afficher que les étudiants correspondants</li>
|
n'afficher que les étudiants correspondants</li>
|
||||||
<li id='filtre_row' style='display:none'></li>
|
<li id="filtre_row" style="display:none"></li>
|
||||||
<li id='filtre_col' style='display:none'></li>
|
<li id="filtre_col" style="display:none"></li>
|
||||||
</ul>
|
</ul>
|
||||||
</h4>
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -488,11 +486,21 @@ class EtapeBilan:
|
|||||||
self.all_cols_str = "'" + ",".join(["." + c for c in self.all_cols_ind]) + "'"
|
self.all_cols_str = "'" + ",".join(["." + c for c in self.all_cols_ind]) + "'"
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
'<div id="synthese" class=u"semset_description"><h4>Tableau des effectifs</h4>',
|
"""<div id="synthese" class="semset_description">
|
||||||
|
<details open="true">
|
||||||
|
<summary><b>Tableau des effectifs</b>
|
||||||
|
</summary>
|
||||||
|
""",
|
||||||
self._diagtable(),
|
self._diagtable(),
|
||||||
|
"""</details>""",
|
||||||
self.display_tags(),
|
self.display_tags(),
|
||||||
|
"""<details open="true">
|
||||||
|
<summary><b id="effectifs">Liste des étudiants <span id="compte"></span></b>
|
||||||
|
</summary>
|
||||||
|
""",
|
||||||
entete_liste_etudiant(),
|
entete_liste_etudiant(),
|
||||||
self.table_effectifs(),
|
self.table_effectifs(),
|
||||||
|
"""</details>""",
|
||||||
help(),
|
help(),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -533,44 +541,38 @@ class EtapeBilan:
|
|||||||
# filtre_row: explicitation du filtre ligne éventuelle
|
# filtre_row: explicitation du filtre ligne éventuelle
|
||||||
# filtre_col: explicitation du filtre colonne évnetuelle
|
# filtre_col: explicitation du filtre colonne évnetuelle
|
||||||
if ind_row == ROW_CUMUL and ind_col == COL_CUMUL:
|
if ind_row == ROW_CUMUL and ind_col == COL_CUMUL:
|
||||||
javascript = "doFiltrage(%s, %s, '*', '*', '%s', '%s', '%s');" % (
|
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
|
||||||
self.all_rows_str,
|
'*', '*',
|
||||||
self.all_cols_str,
|
'{comptage}',
|
||||||
comptage,
|
'', ''
|
||||||
"",
|
);"""
|
||||||
"",
|
|
||||||
)
|
|
||||||
elif ind_row == ROW_CUMUL:
|
|
||||||
javascript = "doFiltrage(%s, %s, '*', '.%s', '%s', '%s', '%s');" % (
|
|
||||||
self.all_rows_str,
|
|
||||||
self.all_cols_str,
|
|
||||||
ind_col,
|
|
||||||
comptage,
|
|
||||||
"",
|
|
||||||
json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1],
|
|
||||||
)
|
|
||||||
elif ind_col == COL_CUMUL:
|
|
||||||
javascript = "doFiltrage(%s, %s, '.%s', '*', '%s', '%s', '%s');" % (
|
|
||||||
self.all_rows_str,
|
|
||||||
self.all_cols_str,
|
|
||||||
ind_row,
|
|
||||||
" (%d étudiants)" % count,
|
|
||||||
json.dumps(self.titres[ind_row])[1:-1],
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
javascript = "doFiltrage(%s, %s, '.%s', '.%s', '%s', '%s', '%s');" % (
|
|
||||||
self.all_rows_str,
|
|
||||||
self.all_cols_str,
|
|
||||||
ind_row,
|
|
||||||
ind_col,
|
|
||||||
comptage,
|
|
||||||
json.dumps(self.titres[ind_row])[1:-1],
|
|
||||||
json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1],
|
|
||||||
)
|
|
||||||
return '<a href="#synthese" onclick="%s">%d</a>' % (javascript, count)
|
|
||||||
|
|
||||||
def _diagtable(self):
|
elif ind_row == ROW_CUMUL:
|
||||||
|
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
|
||||||
|
'*', '.{ind_col}',
|
||||||
|
'{comptage}', '',
|
||||||
|
'{json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1]}'
|
||||||
|
);"""
|
||||||
|
|
||||||
|
elif ind_col == COL_CUMUL:
|
||||||
|
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
|
||||||
|
'.{ind_row}', '*',
|
||||||
|
' ({count} étudiants)',
|
||||||
|
'{json.dumps(self.titres[ind_row])[1:-1]}', ''
|
||||||
|
);"""
|
||||||
|
|
||||||
|
else:
|
||||||
|
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
|
||||||
|
'.{ind_row}', '.{ind_col}',
|
||||||
|
'{comptage}',
|
||||||
|
'{json.dumps(self.titres[ind_row])[1:-1]}',
|
||||||
|
'{json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1]}'
|
||||||
|
);"""
|
||||||
|
|
||||||
|
return f"""<a href="#synthese" class="stdlink" onclick="{javascript}">{count}</a>"""
|
||||||
|
|
||||||
|
def _diagtable(self) -> str:
|
||||||
|
"""Table avec les semestres et les effectifs"""
|
||||||
H = []
|
H = []
|
||||||
|
|
||||||
liste_semestres = sorted(self.semestres.keys())
|
liste_semestres = sorted(self.semestres.keys())
|
||||||
@ -588,7 +590,7 @@ class EtapeBilan:
|
|||||||
col_ids.append(PLUSIEURS_ETAPES)
|
col_ids.append(PLUSIEURS_ETAPES)
|
||||||
self.titres["row_title"] = "Semestre"
|
self.titres["row_title"] = "Semestre"
|
||||||
self.titres[PAS_DE_NIP] = "Hors Apogée (" + FLAG[PAS_DE_NIP] + ")"
|
self.titres[PAS_DE_NIP] = "Hors Apogée (" + FLAG[PAS_DE_NIP] + ")"
|
||||||
self.titres[PAS_D_ETAPE] = "Pas d'étape (" + FLAG[PAS_D_ETAPE] + ")"
|
self.titres[PAS_D_ETAPE] = "Sans étape (" + FLAG[PAS_D_ETAPE] + ")"
|
||||||
self.titres[PLUSIEURS_ETAPES] = (
|
self.titres[PLUSIEURS_ETAPES] = (
|
||||||
"Plusieurs etapes (" + FLAG[PLUSIEURS_ETAPES] + ")"
|
"Plusieurs etapes (" + FLAG[PLUSIEURS_ETAPES] + ")"
|
||||||
)
|
)
|
||||||
@ -680,8 +682,11 @@ class EtapeBilan:
|
|||||||
NIP_NON_UNIQUE,
|
NIP_NON_UNIQUE,
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
'Code(s) nip) partagé(s) par <a href="#synthèse" onclick="%s">%d</a> étudiants<br>'
|
f"""Code(s) nip) partagé(s) par
|
||||||
% (javascript, self.tag_count[NIP_NON_UNIQUE])
|
<a href="#synthèse" class="stdlink"
|
||||||
|
onclick="{javascript}">{self.tag_count[NIP_NON_UNIQUE]}</a>
|
||||||
|
étudiants<br>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
@ -1179,9 +1179,13 @@ def formsemestre_clone(formsemestre_id):
|
|||||||
clone_evaluations=tf[2]["clone_evaluations"],
|
clone_evaluations=tf[2]["clone_evaluations"],
|
||||||
clone_partitions=tf[2]["clone_partitions"],
|
clone_partitions=tf[2]["clone_partitions"],
|
||||||
)
|
)
|
||||||
|
flash("Nouveau semestre créé")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé"
|
url_for(
|
||||||
% new_formsemestre_id
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=new_formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1364,7 +1368,8 @@ def formsemestre_delete2(formsemestre_id, dialog_confirmed=False):
|
|||||||
)
|
)
|
||||||
# Bon, s'il le faut...
|
# Bon, s'il le faut...
|
||||||
do_formsemestre_delete(formsemestre_id)
|
do_formsemestre_delete(formsemestre_id)
|
||||||
return flask.redirect(scu.ScoURL() + "?head_message=Semestre%20supprimé")
|
flash("Semestre supprimé !")
|
||||||
|
return flask.redirect(scu.ScoURL())
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_has_decisions_or_compensations(formsemestre: FormSemestre):
|
def formsemestre_has_decisions_or_compensations(formsemestre: FormSemestre):
|
||||||
|
@ -111,7 +111,7 @@ get_base_preferences(formsemestre_id)
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import current_app, g, request, url_for
|
from flask import flash, g, request
|
||||||
|
|
||||||
# from flask_login import current_user
|
# from flask_login import current_user
|
||||||
|
|
||||||
@ -2127,7 +2127,8 @@ class BasePreferences(object):
|
|||||||
for pref in self.prefs_definition:
|
for pref in self.prefs_definition:
|
||||||
self.prefs[None][pref[0]] = tf[2][pref[0]]
|
self.prefs[None][pref[0]] = tf[2][pref[0]]
|
||||||
self.save()
|
self.save()
|
||||||
return flask.redirect(scu.ScoURL() + "?head_message=Préférences modifiées")
|
flash("Préférences modifiées")
|
||||||
|
return flask.redirect(scu.ScoURL())
|
||||||
|
|
||||||
def build_tf_form(self, categories: list[str] = None, formsemestre_id: int = None):
|
def build_tf_form(self, categories: list[str] = None, formsemestre_id: int = None):
|
||||||
"""Build list of elements for TrivialFormulator.
|
"""Build list of elements for TrivialFormulator.
|
||||||
@ -2299,7 +2300,8 @@ function set_global_pref(el, pref_name) {
|
|||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return flask.redirect(dest_url + "&head_message=Annulé") # cancel
|
flash("Annulé")
|
||||||
|
return flask.redirect(dest_url)
|
||||||
else:
|
else:
|
||||||
# Supprime pref locale du semestre (retour à la valeur globale)
|
# Supprime pref locale du semestre (retour à la valeur globale)
|
||||||
if tf[2]["suppress"]:
|
if tf[2]["suppress"]:
|
||||||
@ -2333,7 +2335,8 @@ function set_global_pref(el, pref_name) {
|
|||||||
# done: change prefs and redirect to semestre status
|
# done: change prefs and redirect to semestre status
|
||||||
destination = tf[2]["destination"]
|
destination = tf[2]["destination"]
|
||||||
if destination == "done" or destination == "":
|
if destination == "done" or destination == "":
|
||||||
return flask.redirect(dest_url + "&head_message=Préférences modifiées")
|
flash("Préférences modifiées")
|
||||||
|
return flask.redirect(dest_url)
|
||||||
elif destination == "again":
|
elif destination == "again":
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
request.base_url + "?formsemestre_id=" + str(self.formsemestre_id)
|
request.base_url + "?formsemestre_id=" + str(self.formsemestre_id)
|
||||||
|
@ -40,6 +40,7 @@ sem_set_list()
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
from flask import g, url_for
|
||||||
|
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
@ -270,19 +271,41 @@ class SemSet(dict):
|
|||||||
' <span class="redboldtext">(attention, plusieurs années !)</span>'
|
' <span class="redboldtext">(attention, plusieurs années !)</span>'
|
||||||
)
|
)
|
||||||
H.append("</p>")
|
H.append("</p>")
|
||||||
if self["sem_id"]:
|
|
||||||
H.append(
|
|
||||||
"<p>Période: %(sem_id)s (<em>1: septembre, 2: janvier</em>)</p>" % self
|
|
||||||
)
|
|
||||||
H.append(
|
H.append(
|
||||||
"<p>Etapes: <tt>%s</tt></p>"
|
f"""<p>Période: <select name="periode" onchange="set_periode(this);">
|
||||||
% sco_formsemestre.etapes_apo_str(self.list_etapes())
|
<option value="1" {"selected" if self["sem_id"] == 1 else ""}>1re période (S1, S3)</option>
|
||||||
|
<option value="2" {"selected" if self["sem_id"] == 2 else ""}>2de période (S2, S4)</option>
|
||||||
|
<option value="0" {"selected" if self["sem_id"] == 0 else ""}>non semestrialisée (LP, ...)</option>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
<script>
|
||||||
|
function set_periode(elt) {{
|
||||||
|
fetch(
|
||||||
|
"{ url_for("apiweb.semset_set_periode", scodoc_dept=g.scodoc_dept,
|
||||||
|
semset_id=self.semset_id )
|
||||||
|
}",
|
||||||
|
{{
|
||||||
|
method: "POST",
|
||||||
|
headers: {{
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}},
|
||||||
|
body: JSON.stringify( elt.value )
|
||||||
|
}},
|
||||||
|
).then(sco_message("période modifiée"));
|
||||||
|
}};
|
||||||
|
</script>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
H.append(
|
||||||
|
f"<p>Etapes: <tt>{sco_formsemestre.etapes_apo_str(self.list_etapes())}</tt></p>"
|
||||||
)
|
)
|
||||||
H.append("""<h4>Semestres de l'ensemble:</h4><ul class="semset_listsems">""")
|
H.append("""<h4>Semestres de l'ensemble:</h4><ul class="semset_listsems">""")
|
||||||
|
|
||||||
for sem in self.sems:
|
for sem in self.sems:
|
||||||
H.append(
|
H.append(
|
||||||
'<li><a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a> %(mois_debut)s - %(mois_fin)s'
|
"""<li><a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a> %(mois_debut)s - %(mois_fin)s"""
|
||||||
% sem
|
% sem
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -1177,12 +1177,12 @@ def edit_moduleimpl_expr(moduleimpl_id):
|
|||||||
sco_cache.invalidate_formsemestre(
|
sco_cache.invalidate_formsemestre(
|
||||||
formsemestre_id=sem["formsemestre_id"]
|
formsemestre_id=sem["formsemestre_id"]
|
||||||
) # > modif regle calcul
|
) # > modif regle calcul
|
||||||
|
flash("règle de calcul modifiée")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.moduleimpl_status",
|
"notes.moduleimpl_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
head_message="règle%20de%20calcul%20modifiée",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1305,7 +1305,6 @@ def delete_ue_expr(formsemestre_id: int, ue_id: int):
|
|||||||
"notes.formsemestre_status",
|
"notes.formsemestre_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
head_message="formule supprimée",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1993,7 +1992,7 @@ def formsemestre_bulletins_choice(
|
|||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
H.append("""<select name="version" class="noprint">""")
|
H.append("""<select name="version" class="noprint">""")
|
||||||
for (version, description) in (
|
for version, description in (
|
||||||
("short", "Version courte"),
|
("short", "Version courte"),
|
||||||
("selectedevals", "Version intermédiaire"),
|
("selectedevals", "Version intermédiaire"),
|
||||||
("long", "Version complète"),
|
("long", "Version complète"),
|
||||||
|
@ -732,12 +732,12 @@ def doSuppressAnnotation(etudid, annotation_id):
|
|||||||
logdb(cnx, method="SuppressAnnotation", etudid=etudid)
|
logdb(cnx, method="SuppressAnnotation", etudid=etudid)
|
||||||
sco_etud.etud_annotations_delete(cnx, annotation_id)
|
sco_etud.etud_annotations_delete(cnx, annotation_id)
|
||||||
|
|
||||||
|
flash("Annotation supprimée")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.ficheEtud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
head_message="Annotation%%20supprimée",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1804,7 +1804,8 @@ def etudident_delete(etudid, dialog_confirmed=False):
|
|||||||
to_inval = [s["formsemestre_id"] for s in etud["sems"]]
|
to_inval = [s["formsemestre_id"] for s in etud["sems"]]
|
||||||
for formsemestre_id in to_inval:
|
for formsemestre_id in to_inval:
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id) # >
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id) # >
|
||||||
return flask.redirect(scu.ScoURL() + r"?head_message=Etudiant%20supprimé")
|
flash("Étudiant supprimé !")
|
||||||
|
return flask.redirect(scu.ScoURL())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/check_group_apogee")
|
@bp.route("/check_group_apogee")
|
||||||
|
Loading…
Reference in New Issue
Block a user