forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises
This commit is contained in:
commit
0d8e7f4268
@ -204,7 +204,7 @@ class ReverseProxied(object):
|
||||
def create_app(config_class=DevConfig):
|
||||
app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static")
|
||||
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||
app.logger.setLevel(logging.DEBUG)
|
||||
app.logger.setLevel(logging.INFO)
|
||||
|
||||
# Evite de logguer toutes les requetes dans notre log
|
||||
logging.getLogger("werkzeug").disabled = True
|
||||
|
@ -70,7 +70,9 @@ def form_ue_choix_niveau(formation: Formation, ue: UniteEns) -> str:
|
||||
<div class="ue_choix_niveau">
|
||||
<form id="form_ue_choix_niveau">
|
||||
<b>Niveau de compétence associé:</b>
|
||||
<select onchange="set_ue_niveau_competence();" data-setter="{
|
||||
<select onchange="set_ue_niveau_competence(this);"
|
||||
data-ue_id="{ue.id}"
|
||||
data-setter="{
|
||||
url_for( "notes.set_ue_niveau_competence", scodoc_dept=g.scodoc_dept)
|
||||
}">
|
||||
<option value="" {'selected' if ue.niveau_competence is None else ''}>aucun</option>
|
||||
@ -83,7 +85,6 @@ def form_ue_choix_niveau(formation: Formation, ue: UniteEns) -> str:
|
||||
|
||||
def set_ue_niveau_competence(ue_id: int, niveau_id: int):
|
||||
"""Associe le niveau et l'UE"""
|
||||
log(f"set_ue_niveau_competence( {ue_id}, {niveau_id} )")
|
||||
ue = UniteEns.query.get_or_404(ue_id)
|
||||
|
||||
autres_ues = ue.formation.ues.filter_by(semestre_idx=ue.semestre_idx)
|
||||
@ -96,6 +97,7 @@ def set_ue_niveau_competence(ue_id: int, niveau_id: int):
|
||||
)
|
||||
return "", 409 # conflict
|
||||
if niveau_id == "":
|
||||
niveau = ""
|
||||
# suppression de l'association
|
||||
ue.niveau_competence = None
|
||||
else:
|
||||
@ -103,4 +105,6 @@ def set_ue_niveau_competence(ue_id: int, niveau_id: int):
|
||||
ue.niveau_competence = niveau
|
||||
db.session.add(ue)
|
||||
db.session.commit()
|
||||
log(f"set_ue_niveau_competence( {ue}, {niveau} )")
|
||||
|
||||
return "", 204
|
||||
|
@ -326,10 +326,7 @@ class BulletinBUT:
|
||||
semestre_infos["ECTS"] = {"acquis": ects_acquis, "total": ects_tot}
|
||||
if sco_preferences.get_preference("bul_show_decision", formsemestre.id):
|
||||
semestre_infos.update(
|
||||
sco_bulletins_json.dict_decision_jury(etud.id, formsemestre.id)
|
||||
)
|
||||
semestre_infos.update(
|
||||
but_validations.dict_decision_jury(etud, formsemestre)
|
||||
sco_bulletins_json.dict_decision_jury(etud, formsemestre)
|
||||
)
|
||||
if etat_inscription == scu.INSCRIT:
|
||||
# moyenne des moyennes générales du semestre
|
||||
|
67
app/but/cursus_but.py
Normal file
67
app/but/cursus_but.py
Normal file
@ -0,0 +1,67 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Cursus en BUT
|
||||
|
||||
Classe raccordant avec ScoDoc 7:
|
||||
ScoDoc 7 utilisait sco_cursus_dut.SituationEtudCursus
|
||||
|
||||
Ce module définit une classe SituationEtudCursusBUT
|
||||
avec la même interface.
|
||||
|
||||
"""
|
||||
|
||||
from typing import Union
|
||||
|
||||
from flask import g, url_for
|
||||
|
||||
from app import db
|
||||
from app import log
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.models import formsemestre
|
||||
|
||||
from app.models.but_refcomp import (
|
||||
ApcAnneeParcours,
|
||||
ApcCompetence,
|
||||
ApcNiveau,
|
||||
ApcParcours,
|
||||
ApcParcoursNiveauCompetence,
|
||||
)
|
||||
from app.models import Scolog, ScolarAutorisationInscription
|
||||
from app.models.but_validations import (
|
||||
ApcValidationAnnee,
|
||||
ApcValidationRCUE,
|
||||
RegroupementCoherentUE,
|
||||
)
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formations import Formation
|
||||
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
||||
from app.models.ues import UniteEns
|
||||
from app.models.validations import ScolarFormSemestreValidation
|
||||
from app.scodoc import sco_codes_parcours as sco_codes
|
||||
from app.scodoc.sco_codes_parcours import RED, UE_STANDARD
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoException, ScoValueError
|
||||
|
||||
from app.scodoc import sco_cursus_dut
|
||||
|
||||
|
||||
class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
|
||||
def __init__(self, etud: dict, formsemestre_id: int, res: ResultatsSemestreBUT):
|
||||
super().__init__(etud, formsemestre_id, res)
|
||||
# Ajustements pour le BUT
|
||||
self.can_compensate_with_prev = False # jamais de compensation à la mode DUT
|
||||
|
||||
def check_compensation_dut(self, semc: dict, ntc: NotesTableCompat):
|
||||
"Jamais de compensation façon DUT"
|
||||
return False
|
||||
|
||||
def parcours_validated(self):
|
||||
"True si le parcours est validé"
|
||||
return False # XXX TODO
|
@ -89,6 +89,7 @@ from app.models.formations import Formation
|
||||
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
||||
from app.models.ues import UniteEns
|
||||
from app.models.validations import ScolarFormSemestreValidation
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_codes_parcours as sco_codes
|
||||
from app.scodoc.sco_codes_parcours import RED, UE_STANDARD
|
||||
from app.scodoc import sco_utils as scu
|
||||
@ -573,31 +574,33 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
Si les code_rcue et le code_annee ne sont pas fournis,
|
||||
et qu'il n'y en a pas déjà, enregistre ceux par défaut.
|
||||
"""
|
||||
for key in form:
|
||||
code = form[key]
|
||||
# Codes d'UE
|
||||
m = re.match(r"^code_ue_(\d+)$", key)
|
||||
if m:
|
||||
ue_id = int(m.group(1))
|
||||
dec_ue = self.decisions_ues.get(ue_id)
|
||||
if not dec_ue:
|
||||
raise ScoValueError(f"UE invalide ue_id={ue_id}")
|
||||
dec_ue.record(code)
|
||||
else:
|
||||
# Codes de RCUE
|
||||
m = re.match(r"^code_rcue_(\d+)$", key)
|
||||
log("jury_but.DecisionsProposeesAnnee.record_form")
|
||||
with sco_cache.DeferredSemCacheManager():
|
||||
for key in form:
|
||||
code = form[key]
|
||||
# Codes d'UE
|
||||
m = re.match(r"^code_ue_(\d+)$", key)
|
||||
if m:
|
||||
niveau_id = int(m.group(1))
|
||||
dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id)
|
||||
if not dec_rcue:
|
||||
raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}")
|
||||
dec_rcue.record(code)
|
||||
elif key == "code_annee":
|
||||
# Code annuel
|
||||
self.record(code)
|
||||
ue_id = int(m.group(1))
|
||||
dec_ue = self.decisions_ues.get(ue_id)
|
||||
if not dec_ue:
|
||||
raise ScoValueError(f"UE invalide ue_id={ue_id}")
|
||||
dec_ue.record(code)
|
||||
else:
|
||||
# Codes de RCUE
|
||||
m = re.match(r"^code_rcue_(\d+)$", key)
|
||||
if m:
|
||||
niveau_id = int(m.group(1))
|
||||
dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id)
|
||||
if not dec_rcue:
|
||||
raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}")
|
||||
dec_rcue.record(code)
|
||||
elif key == "code_annee":
|
||||
# Code annuel
|
||||
self.record(code)
|
||||
|
||||
self.record_all()
|
||||
db.session.commit()
|
||||
self.record_all()
|
||||
db.session.commit()
|
||||
|
||||
def record(self, code: str, no_overwrite=False):
|
||||
"""Enregistre le code de l'année, et au besoin l'autorisation d'inscription.
|
||||
@ -647,6 +650,16 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
)
|
||||
|
||||
self.recorded = True
|
||||
self.invalidate_formsemestre_cache()
|
||||
|
||||
def invalidate_formsemestre_cache(self):
|
||||
"invalide le résultats des deux formsemestres"
|
||||
if self.formsemestre_impair is not None:
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=self.formsemestre_impair.id
|
||||
)
|
||||
if self.formsemestre_pair is not None:
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre_pair.id)
|
||||
|
||||
def record_all(self):
|
||||
"""Enregistre les codes qui n'ont pas été spécifiés par le formulaire, et sont donc en mode "automatique" """
|
||||
@ -687,6 +700,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
for validation in validations:
|
||||
db.session.delete(validation)
|
||||
db.session.flush()
|
||||
self.invalidate_formsemestre_cache()
|
||||
|
||||
def get_autorisations_passage(self) -> list[int]:
|
||||
"""Les liste des indices de semestres auxquels on est autorisé à
|
||||
@ -805,6 +819,14 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
||||
msg=f"Validation RCUE {repr(self.rcue)}",
|
||||
)
|
||||
db.session.add(self.validation)
|
||||
if self.rcue.formsemestre_1 is not None:
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=self.rcue.formsemestre_1.id
|
||||
)
|
||||
if self.rcue.formsemestre_2 is not None:
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=self.rcue.formsemestre_2.id
|
||||
)
|
||||
self.recorded = True
|
||||
|
||||
def erase(self):
|
||||
@ -948,6 +970,8 @@ class DecisionsProposeesUE(DecisionsProposees):
|
||||
msg=f"Validation UE {self.ue.id}",
|
||||
)
|
||||
db.session.add(self.validation)
|
||||
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id)
|
||||
self.recorded = True
|
||||
|
||||
def erase(self):
|
||||
|
@ -101,10 +101,17 @@ def formsemestre_saisie_jury_but(
|
||||
f"""<h3>Décisions de jury enregistrées pour les étudiants de ce semestre</h3>
|
||||
<div class="table_jury_but_links">
|
||||
<div>
|
||||
<a href="{url_for(
|
||||
<ul>
|
||||
<li><a href="{url_for(
|
||||
"notes.pvjury_table_but",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
|
||||
}" class="stdlink">tableau PV de jury</a>
|
||||
}" class="stdlink">Tableau PV de jury</a>
|
||||
</li>
|
||||
<li><a href="{url_for(
|
||||
"notes.formsemestre_lettres_individuelles",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
|
||||
}" class="stdlink">Courriers individuels (classeur pdf)</a>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
@ -4,15 +4,14 @@
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Jury BUT: clacul des décisions de jury annuelles "automatiques"
|
||||
"""Jury BUT: calcul des décisions de jury annuelles "automatiques"
|
||||
"""
|
||||
|
||||
from flask import g, url_for
|
||||
|
||||
from app import db
|
||||
from app.but import jury_but
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
@ -23,12 +22,13 @@ def formsemestre_validation_auto_but(formsemestre: FormSemestre) -> int:
|
||||
if not formsemestre.formation.is_apc():
|
||||
raise ScoValueError("fonction réservée aux formations BUT")
|
||||
nb_admis = 0
|
||||
for etudid in formsemestre.etuds_inscriptions:
|
||||
etud: Identite = Identite.query.get(etudid)
|
||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||
if deca.admis: # année réussie
|
||||
deca.record_all()
|
||||
nb_admis += 1
|
||||
with sco_cache.DeferredSemCacheManager():
|
||||
for etudid in formsemestre.etuds_inscriptions:
|
||||
etud: Identite = Identite.query.get(etudid)
|
||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||
if deca.admis: # année réussie
|
||||
deca.record_all()
|
||||
nb_admis += 1
|
||||
|
||||
db.session.commit()
|
||||
return nb_admis
|
||||
|
@ -285,7 +285,17 @@ class ResultatsSemestre(ResultatsCache):
|
||||
else:
|
||||
return None
|
||||
# converti la Series en dict, afin que les np.int64 reviennent en int
|
||||
return ue_cap.to_dict()
|
||||
# et remplace les NaN (venant des NULL en base) par des None
|
||||
ue_cap_dict = ue_cap.to_dict()
|
||||
if ue_cap_dict["formsemestre_id"] is not None and np.isnan(
|
||||
ue_cap_dict["formsemestre_id"]
|
||||
):
|
||||
ue_cap_dict["formsemestre_id"] = None
|
||||
if ue_cap_dict["compense_formsemestre_id"] is not None and np.isnan(
|
||||
ue_cap_dict["compense_formsemestre_id"]
|
||||
):
|
||||
ue_cap_dict["compense_formsemestre_id"] = None
|
||||
return ue_cap_dict
|
||||
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict:
|
||||
"""L'état de l'UE pour cet étudiant.
|
||||
@ -651,8 +661,19 @@ class ResultatsSemestre(ResultatsCache):
|
||||
row["_ues_validables_class"] += " moy_inf"
|
||||
row["_ues_validables_order"] = nb_ues_validables # pour tri
|
||||
if mode_jury and self.validations:
|
||||
dec_sem = self.validations.decisions_jury.get(etudid)
|
||||
jury_code_sem = dec_sem["code"] if dec_sem else ""
|
||||
if self.is_apc:
|
||||
# formations BUT: pas de code semestre, concatene ceux des UE
|
||||
dec_ues = self.validations.decisions_jury_ues.get(etudid)
|
||||
if dec_ues:
|
||||
jury_code_sem = ",".join(
|
||||
[dec_ues[ue_id].get("code", "") for ue_id in dec_ues]
|
||||
)
|
||||
else:
|
||||
jury_code_sem = ""
|
||||
else:
|
||||
# formations classiqes: code semestre
|
||||
dec_sem = self.validations.decisions_jury.get(etudid)
|
||||
jury_code_sem = dec_sem["code"] if dec_sem else ""
|
||||
idx = add_cell(
|
||||
row,
|
||||
"jury_code_sem",
|
||||
@ -668,7 +689,7 @@ class ResultatsSemestre(ResultatsCache):
|
||||
f"""<a href="{url_for('notes.formsemestre_validation_etud_form',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=self.formsemestre.id, etudid=etudid
|
||||
)
|
||||
}">saisir décision</a>""",
|
||||
}">{"saisir" if not jury_code_sem else "modifier"} décision</a>""",
|
||||
"col_jury_link",
|
||||
idx,
|
||||
)
|
||||
|
@ -47,7 +47,7 @@ def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||
def load_formsemestre_validations(formsemestre: FormSemestre) -> ValidationsSemestre:
|
||||
"""Charge les résultats de jury de ce semestre.
|
||||
Search in local cache (g.formsemestre_result_cache)
|
||||
If not in cache, build it and cache it.
|
||||
If not in cache, build it and cache it (in g).
|
||||
"""
|
||||
if not hasattr(g, "formsemestre_validation_cache"):
|
||||
g.formsemestre_validations_cache = {} # pylint: disable=C0237
|
||||
|
@ -84,7 +84,7 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
||||
formations = db.relationship("Formation", backref="referentiel_competence")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ApcReferentielCompetences {self.id} {self.specialite!r}>"
|
||||
return f"<ApcReferentielCompetences {self.id} {self.specialite!r} {self.departement!r}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Représentation complète du ref. de comp.
|
||||
@ -269,7 +269,7 @@ class ApcNiveau(db.Model, XMLModel):
|
||||
ues = db.relationship("UniteEns", back_populates="niveau_competence")
|
||||
|
||||
def __repr__(self):
|
||||
return f"""<{self.__class__.__name__} ordre={self.ordre!r} annee={
|
||||
return f"""<{self.__class__.__name__} {self.id} ordre={self.ordre!r} annee={
|
||||
self.annee!r} {self.competence!r}>"""
|
||||
|
||||
def to_dict(self):
|
||||
@ -432,7 +432,7 @@ class ApcParcours(db.Model, XMLModel):
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} {self.code!r}>"
|
||||
return f"<{self.__class__.__name__} {self.id} {self.code!r} ref={self.referentiel}>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
@ -452,7 +452,7 @@ class ApcAnneeParcours(db.Model, XMLModel):
|
||||
"numéro de l'année: 1, 2, 3"
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} ordre={self.ordre!r} parcours={self.parcours.code!r}>"
|
||||
return f"<{self.__class__.__name__} {self.id} ordre={self.ordre!r} parcours={self.parcours.code!r}>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
|
@ -67,7 +67,7 @@ class ApcValidationRCUE(db.Model):
|
||||
return self.ue2.niveau_competence
|
||||
|
||||
def to_dict_bul(self) -> dict:
|
||||
"Export dict pour bulletins"
|
||||
"Export dict pour bulletins: le code et le niveau de compétence"
|
||||
return {"code": self.code, "niveau": self.niveau().to_dict_bul()}
|
||||
|
||||
|
||||
@ -309,7 +309,9 @@ class ApcValidationAnnee(db.Model):
|
||||
|
||||
def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
|
||||
"""
|
||||
Un dict avec les décisions de jury BUT enregistrées.
|
||||
Un dict avec les décisions de jury BUT enregistrées:
|
||||
- decision_rcue : list[dict]
|
||||
- decision_annee : dict
|
||||
Ne reprend pas les décisions d'UE, non spécifiques au BUT.
|
||||
"""
|
||||
decisions = {}
|
||||
@ -320,8 +322,19 @@ def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
|
||||
etudid=etud.id, formsemestre_id=formsemestre.id
|
||||
)
|
||||
decisions["decision_rcue"] = [v.to_dict_bul() for v in validations_rcues]
|
||||
decisions["descr_decisions_rcue"] = ", ".join(
|
||||
[
|
||||
f"""{dec_rcue["niveau"]["competence"]["titre"]} {dec_rcue["niveau"]["ordre"]}: {dec_rcue["code"]}"""
|
||||
for dec_rcue in decisions["decision_rcue"]
|
||||
]
|
||||
)
|
||||
decisions["descr_decisions_niveaux"] = (
|
||||
"Niveaux de compétences: " + decisions["descr_decisions_rcue"]
|
||||
)
|
||||
else:
|
||||
decisions["decision_rcue"] = []
|
||||
decisions["descr_decisions_rcue"] = ""
|
||||
decisions["descr_decisions_niveaux"] = ""
|
||||
# --- Année: prend la validation pour l'année scolaire de ce semestre
|
||||
validation = (
|
||||
ApcValidationAnnee.query.filter_by(
|
||||
|
@ -31,6 +31,9 @@ class Departement(db.Model):
|
||||
"ScoPreference", lazy="dynamic", backref="departement"
|
||||
)
|
||||
semsets = db.relationship("NotesSemSet", lazy="dynamic", backref="departement")
|
||||
referentiels = db.relationship(
|
||||
"ApcReferentielCompetences", lazy="dynamic", backref="departement"
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}(id={self.id}, acronym='{self.acronym}')>"
|
||||
|
@ -5,7 +5,7 @@
|
||||
import datetime
|
||||
from functools import cached_property
|
||||
|
||||
from flask import flash
|
||||
from flask import flash, g
|
||||
import flask_sqlalchemy
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
@ -19,11 +19,11 @@ from app.models.but_refcomp import (
|
||||
ApcNiveau,
|
||||
ApcParcours,
|
||||
ApcParcoursNiveauCompetence,
|
||||
ApcReferentielCompetences,
|
||||
)
|
||||
from app.models.groups import GroupDescr, Partition
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.models.but_refcomp import ApcParcours
|
||||
from app.models.but_refcomp import parcours_formsemestre
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.modules import Module
|
||||
@ -577,7 +577,11 @@ class FormSemestre(db.Model):
|
||||
)
|
||||
# Inscrit les étudiants des groupes de parcours:
|
||||
for group in partition.groups:
|
||||
query = ApcParcours.query.filter_by(code=group.group_name)
|
||||
query = (
|
||||
ApcParcours.query.filter_by(code=group.group_name)
|
||||
.join(ApcReferentielCompetences)
|
||||
.filter_by(dept_id=g.scodoc_dept_id)
|
||||
)
|
||||
if query.count() != 1:
|
||||
log(
|
||||
f"""update_inscriptions_parcours_from_groups: {
|
||||
|
@ -57,6 +57,11 @@ class ScolarFormSemestreValidation(db.Model):
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.formsemestre_id}, {self.etudid}, code={self.code}, ue={self.ue}, moy_ue={self.moy_ue})"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
return d
|
||||
|
||||
|
||||
class ScolarAutorisationInscription(db.Model):
|
||||
"""Autorisation d'inscription dans un semestre"""
|
||||
@ -78,6 +83,11 @@ class ScolarAutorisationInscription(db.Model):
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def autorise_etud(
|
||||
cls,
|
||||
@ -146,3 +156,8 @@ class ScolarEvent(db.Model):
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
return d
|
||||
|
@ -54,22 +54,22 @@ from app.scodoc.sco_codes_parcours import (
|
||||
ue_is_fondamentale,
|
||||
ue_is_professionnelle,
|
||||
)
|
||||
from app.scodoc.sco_parcours_dut import formsemestre_get_etud_capitalisation
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_compute_moy
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc.sco_cursus import formsemestre_get_etud_capitalisation
|
||||
from app.scodoc import sco_cursus_dut
|
||||
from app.scodoc import sco_edit_matiere
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_parcours_dut
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_etud
|
||||
|
||||
|
||||
def comp_ranks(T):
|
||||
@ -1175,7 +1175,7 @@ class NotesTable:
|
||||
):
|
||||
if not cnx:
|
||||
cnx = ndb.GetDBConnexion()
|
||||
sco_parcours_dut.do_formsemestre_validate_ue(
|
||||
sco_cursus_dut.do_formsemestre_validate_ue(
|
||||
cnx,
|
||||
nt_cap,
|
||||
ue_cap["formsemestre_id"],
|
||||
|
@ -265,9 +265,9 @@ def DBUpdateArgs(cnx, table, vals, where=None, commit=False, convert_empty_to_nu
|
||||
cursor.execute(req, vals)
|
||||
# log('req=%s\n'%req)
|
||||
# log('vals=%s\n'%vals)
|
||||
except psycopg2.errors.StringDataRightTruncation:
|
||||
except psycopg2.errors.StringDataRightTruncation as exc:
|
||||
cnx.rollback()
|
||||
raise ScoValueError("champs de texte trop long !")
|
||||
raise ScoValueError("champs de texte trop long !") from exc
|
||||
except:
|
||||
cnx.rollback() # get rid of this transaction
|
||||
log('Exception in DBUpdateArgs:\n\treq="%s"\n\tvals="%s"\n' % (req, vals))
|
||||
|
@ -112,8 +112,8 @@ from app.scodoc.sco_codes_parcours import (
|
||||
NAR,
|
||||
RAT,
|
||||
)
|
||||
from app.scodoc import sco_cursus
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_parcours_dut
|
||||
from app.scodoc import sco_etud
|
||||
|
||||
APO_PORTAL_ENCODING = (
|
||||
@ -413,7 +413,7 @@ class ApoEtud(dict):
|
||||
export_res_etape = self.export_res_etape
|
||||
if (not export_res_etape) and cur_sem:
|
||||
# exporte toujours le résultat de l'étape si l'étudiant est diplômé
|
||||
Se = sco_parcours_dut.SituationEtudParcours(
|
||||
Se = sco_cursus.get_situation_etud_cursus(
|
||||
self.etud, cur_sem["formsemestre_id"]
|
||||
)
|
||||
export_res_etape = Se.all_other_validated()
|
||||
|
@ -347,23 +347,23 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
u[
|
||||
"modules_capitalized"
|
||||
] = [] # modules de l'UE capitalisée (liste vide si pas capitalisée)
|
||||
if ue_status["is_capitalized"]:
|
||||
sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"])
|
||||
u["ue_descr_txt"] = "capitalisée le %s" % ndb.DateISOtoDMY(
|
||||
ue_status["event_date"]
|
||||
)
|
||||
if ue_status["is_capitalized"] and ue_status["formsemestre_id"] is not None:
|
||||
sem_origin = FormSemestre.query.get(ue_status["formsemestre_id"])
|
||||
u[
|
||||
"ue_descr_html"
|
||||
] = f"""<a href="{ url_for( 'notes.formsemestre_bulletinetud',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=sem_origin['formsemestre_id'], etudid=etudid)}"
|
||||
title="{sem_origin['titreannee']}" class="bull_link"
|
||||
>{u["ue_descr_txt"]} pouet</a>
|
||||
"ue_descr_txt"
|
||||
] = f'capitalisée le {ndb.DateISOtoDMY(ue_status["event_date"])}'
|
||||
u["ue_descr_html"] = (
|
||||
f"""<a href="{ url_for( 'notes.formsemestre_bulletinetud',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=sem_origin.id, etudid=etudid)}"
|
||||
title="{sem_origin.titre_annee()}" class="bull_link"
|
||||
>{u["ue_descr_txt"]}</a>
|
||||
"""
|
||||
if ue_status["moy"] != "NA" and ue_status["formsemestre_id"]:
|
||||
if sem_origin
|
||||
else ""
|
||||
)
|
||||
if ue_status["moy"] != "NA":
|
||||
# détail des modules de l'UE capitalisée
|
||||
formsemestre_cap = FormSemestre.query.get_or_404(
|
||||
ue_status["formsemestre_id"]
|
||||
)
|
||||
formsemestre_cap = FormSemestre.query.get(ue_status["formsemestre_id"])
|
||||
nt_cap: NotesTableCompat = res_sem.load_formsemestre_results(
|
||||
formsemestre_cap
|
||||
)
|
||||
@ -434,7 +434,7 @@ def _get_etud_etat_html(etat: str) -> str:
|
||||
elif etat == scu.DEF: # "DEF"
|
||||
return ' <font color="red">(DEFAILLANT)</font> '
|
||||
else:
|
||||
return ' <font color="red">(%s)</font> ' % etat
|
||||
return f' <font color="red">({etat})</font> '
|
||||
|
||||
|
||||
def _sort_mod_by_matiere(modlist, nt, etudid):
|
||||
@ -707,11 +707,11 @@ def etud_descr_situation_semestre(
|
||||
descr_decisions_ue : ' UE acquises: UE1, UE2', ou vide si pas de dec. ou si pas show_uevalid
|
||||
descr_mention : 'Mention Bien', ou vide si pas de mention ou si pas show_mention
|
||||
"""
|
||||
# Fonction utilisée par tous les bulletins (APC ou classiques)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
infos = scu.DictDefault(defaultvalue="")
|
||||
|
||||
# --- Situation et décisions jury
|
||||
|
||||
# démission/inscription ?
|
||||
events = sco_etud.scolar_events_list(
|
||||
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||
@ -728,8 +728,7 @@ def etud_descr_situation_semestre(
|
||||
# il y a eu une erreur qui a laissé un event 'inscription'
|
||||
# on l'efface:
|
||||
log(
|
||||
"etud_descr_situation_semestre: removing duplicate INSCRIPTION event for etudid=%s !"
|
||||
% etudid
|
||||
f"etud_descr_situation_semestre: removing duplicate INSCRIPTION event for etudid={etudid} !"
|
||||
)
|
||||
sco_etud.scolar_events_delete(cnx, event["event_id"])
|
||||
else:
|
||||
@ -738,8 +737,7 @@ def etud_descr_situation_semestre(
|
||||
# assert date_dem == None, 'plusieurs démissions !'
|
||||
if date_dem: # cela ne peut pas arriver sauf bug (signale a Evry 2013?)
|
||||
log(
|
||||
"etud_descr_situation_semestre: removing duplicate DEMISSION event for etudid=%s !"
|
||||
% etudid
|
||||
f"etud_descr_situation_semestre: removing duplicate DEMISSION event for etudid={etudid} !"
|
||||
)
|
||||
sco_etud.scolar_events_delete(cnx, event["event_id"])
|
||||
else:
|
||||
@ -747,8 +745,7 @@ def etud_descr_situation_semestre(
|
||||
elif event_type == "DEFAILLANCE":
|
||||
if date_def:
|
||||
log(
|
||||
"etud_descr_situation_semestre: removing duplicate DEFAILLANCE event for etudid=%s !"
|
||||
% etudid
|
||||
f"etud_descr_situation_semestre: removing duplicate DEFAILLANCE event for etudid={etudid} !"
|
||||
)
|
||||
sco_etud.scolar_events_delete(cnx, event["event_id"])
|
||||
else:
|
||||
@ -756,33 +753,37 @@ def etud_descr_situation_semestre(
|
||||
if show_date_inscr:
|
||||
if not date_inscr:
|
||||
infos["date_inscription"] = ""
|
||||
infos["descr_inscription"] = "Pas inscrit%s." % ne
|
||||
infos["descr_inscription"] = f"Pas inscrit{ne}."
|
||||
else:
|
||||
infos["date_inscription"] = date_inscr
|
||||
infos["descr_inscription"] = "Inscrit%s le %s." % (ne, date_inscr)
|
||||
infos["descr_inscription"] = f"Inscrit{ne} le {date_inscr}."
|
||||
else:
|
||||
infos["date_inscription"] = ""
|
||||
infos["descr_inscription"] = ""
|
||||
|
||||
infos["situation"] = infos["descr_inscription"]
|
||||
# Décision: valeurs par defaut vides:
|
||||
infos["decision_jury"] = infos["descr_decision_jury"] = ""
|
||||
infos["decision_sem"] = ""
|
||||
infos["decisions_ue"] = infos["descr_decisions_ue"] = ""
|
||||
infos["descr_decisions_niveaux"] = infos["descr_decisions_rcue"] = ""
|
||||
infos["descr_decision_annee"] = ""
|
||||
|
||||
if date_dem:
|
||||
infos["descr_demission"] = "Démission le %s." % date_dem
|
||||
infos["descr_demission"] = f"Démission le {date_dem}."
|
||||
infos["date_demission"] = date_dem
|
||||
infos["descr_decision_jury"] = "Démission"
|
||||
infos["decision_jury"] = infos["descr_decision_jury"] = "Démission"
|
||||
infos["situation"] += " " + infos["descr_demission"]
|
||||
return infos, None # ne donne pas les dec. de jury pour les demissionnaires
|
||||
if date_def:
|
||||
infos["descr_defaillance"] = "Défaillant%s" % ne
|
||||
infos["descr_defaillance"] = f"Défaillant{ne}"
|
||||
infos["date_defaillance"] = date_def
|
||||
infos["descr_decision_jury"] = "Défaillant%s" % ne
|
||||
infos["descr_decision_jury"] = f"Défaillant{ne}"
|
||||
infos["situation"] += " " + infos["descr_defaillance"]
|
||||
|
||||
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=[etudid])
|
||||
if dpv:
|
||||
infos["decision_sem"] = dpv["decisions"][0]["decision_sem"]
|
||||
else:
|
||||
infos["decision_sem"] = ""
|
||||
|
||||
if not show_decisions:
|
||||
return infos, dpv
|
||||
@ -798,25 +799,37 @@ def etud_descr_situation_semestre(
|
||||
dec = infos["descr_decision_jury"]
|
||||
else:
|
||||
infos["descr_decision_jury"] = ""
|
||||
infos["decision_jury"] = ""
|
||||
|
||||
if pv["decisions_ue_descr"] and show_uevalid:
|
||||
infos["decisions_ue"] = pv["decisions_ue_descr"]
|
||||
infos["descr_decisions_ue"] = " UE acquises: " + pv["decisions_ue_descr"] + ". "
|
||||
dec += infos["descr_decisions_ue"]
|
||||
else:
|
||||
# infos['decisions_ue'] = None
|
||||
infos["decisions_ue"] = ""
|
||||
infos["descr_decisions_ue"] = ""
|
||||
|
||||
infos["mention"] = pv["mention"]
|
||||
if pv["mention"] and show_mention:
|
||||
dec += "Mention " + pv["mention"] + ". "
|
||||
dec += f"Mention {pv['mention']}."
|
||||
|
||||
# Décisions APC / BUT
|
||||
if pv.get("decision_annee", {}):
|
||||
infos["descr_decision_annee"] = "Décision année: " + pv.get(
|
||||
"decision_annee", {}
|
||||
).get("code", "")
|
||||
else:
|
||||
infos["descr_decision_annee"] = ""
|
||||
|
||||
infos["descr_decisions_rcue"] = pv.get("descr_decisions_rcue", "")
|
||||
infos["descr_decisions_niveaux"] = pv.get("descr_decisions_niveaux", "")
|
||||
|
||||
infos["situation"] += " " + dec
|
||||
if not pv["validation_parcours"]: # parcours non terminé
|
||||
if pv["autorisations_descr"]:
|
||||
infos["situation"] += (
|
||||
" Autorisé à s'inscrire en %s." % pv["autorisations_descr"]
|
||||
)
|
||||
infos[
|
||||
"situation"
|
||||
] += f" Autorisé à s'inscrire en {pv['autorisations_descr']}."
|
||||
else:
|
||||
infos["situation"] += " Diplôme obtenu."
|
||||
return infos, dpv
|
||||
@ -832,9 +845,20 @@ def formsemestre_bulletinetud(
|
||||
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
||||
prefer_mail_perso=False,
|
||||
):
|
||||
"""Page bulletin de notes
|
||||
pour les formations classiques hors BUT (page HTML)
|
||||
ou le format "oldjson".
|
||||
"""Page bulletin de notes pour
|
||||
- HTML des formations classiques (non BUT)
|
||||
- le format "oldjson" (les "json" sont générés à part, voir get_formsemestre_bulletin_etud_json)
|
||||
- les formats PDF, XML et mail pdf (toutes formations)
|
||||
|
||||
Note: le format XML n'est plus maintenu et pour les BUT ne contient pas
|
||||
toutes les informations. Privilégier le format JSON.
|
||||
|
||||
Paramètres:
|
||||
- version: pour les formations classqiues, versions short/selectedevals/long
|
||||
- xml_with_decisions: inclue ou non les
|
||||
- force_publishing: renvoie le bulletin même si semestre non publie sur "portail"
|
||||
- prefer_mail_perso: pour pdfmail, utilise adresse mail perso en priorité.
|
||||
|
||||
"""
|
||||
format = format or "html"
|
||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||
@ -893,7 +917,12 @@ def do_formsemestre_bulletinetud(
|
||||
prefer_mail_perso=False, # mails envoyés sur adresse perso si non vide
|
||||
):
|
||||
"""Génère le bulletin au format demandé.
|
||||
Retourne: (bul, filigranne)
|
||||
Utilisé pour:
|
||||
- HTML des formations classiques (non BUT)
|
||||
- le format "oldjson" (les json sont générés à part, voir get_formsemestre_bulletin_etud_json)
|
||||
- les formats PDF, XML et mail pdf (toutes formations)
|
||||
|
||||
Résultat: (bul, filigranne)
|
||||
où bul est str ou bytes au format demandé (html, pdf, pdfmail, pdfpart, xml, json)
|
||||
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
|
||||
"""
|
||||
@ -909,7 +938,7 @@ def do_formsemestre_bulletinetud(
|
||||
|
||||
return bul, ""
|
||||
|
||||
elif format == "json":
|
||||
elif format == "json": # utilisé pour classic et "oldjson"
|
||||
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
|
||||
formsemestre.id,
|
||||
etudid,
|
||||
@ -922,20 +951,20 @@ def do_formsemestre_bulletinetud(
|
||||
if formsemestre.formation.is_apc():
|
||||
etudiant = Identite.query.get(etudid)
|
||||
r = bulletin_but.BulletinBUT(formsemestre)
|
||||
I = r.bulletin_etud_complet(etudiant, version=version)
|
||||
infos = r.bulletin_etud_complet(etudiant, version=version)
|
||||
else:
|
||||
I = formsemestre_bulletinetud_dict(formsemestre.id, etudid)
|
||||
etud = I["etud"]
|
||||
infos = formsemestre_bulletinetud_dict(formsemestre.id, etudid)
|
||||
etud = infos["etud"]
|
||||
|
||||
if format == "html":
|
||||
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
|
||||
I, version=version, format="html"
|
||||
infos, version=version, format="html"
|
||||
)
|
||||
return htm, I["filigranne"]
|
||||
return htm, infos["filigranne"]
|
||||
|
||||
elif format == "pdf" or format == "pdfpart":
|
||||
bul, filename = sco_bulletins_generator.make_formsemestre_bulletinetud(
|
||||
I,
|
||||
infos,
|
||||
version=version,
|
||||
format="pdf",
|
||||
stand_alone=(format != "pdfpart"),
|
||||
@ -943,10 +972,10 @@ def do_formsemestre_bulletinetud(
|
||||
if format == "pdf":
|
||||
return (
|
||||
scu.sendPDFFile(bul, filename),
|
||||
I["filigranne"],
|
||||
infos["filigranne"],
|
||||
) # unused ret. value
|
||||
else:
|
||||
return bul, I["filigranne"]
|
||||
return bul, infos["filigranne"]
|
||||
|
||||
elif format == "pdfmail":
|
||||
# format pdfmail: envoie le pdf par mail a l'etud, et affiche le html
|
||||
@ -955,7 +984,7 @@ def do_formsemestre_bulletinetud(
|
||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||
|
||||
pdfdata, filename = sco_bulletins_generator.make_formsemestre_bulletinetud(
|
||||
I, version=version, format="pdf"
|
||||
infos, version=version, format="pdf"
|
||||
)
|
||||
|
||||
if prefer_mail_perso:
|
||||
@ -965,21 +994,21 @@ def do_formsemestre_bulletinetud(
|
||||
|
||||
if not recipient_addr:
|
||||
flash(f"{etud['nomprenom']} n'a pas d'adresse e-mail !")
|
||||
return False, I["filigranne"]
|
||||
return False, infos["filigranne"]
|
||||
else:
|
||||
mail_bulletin(formsemestre.id, I, pdfdata, filename, recipient_addr)
|
||||
mail_bulletin(formsemestre.id, infos, pdfdata, filename, recipient_addr)
|
||||
flash(f"mail envoyé à {recipient_addr}")
|
||||
|
||||
return True, I["filigranne"]
|
||||
return True, infos["filigranne"]
|
||||
|
||||
raise ValueError("do_formsemestre_bulletinetud: invalid format (%s)" % format)
|
||||
|
||||
|
||||
def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
|
||||
def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
|
||||
"""Send bulletin by email to etud
|
||||
If bul_mail_list_abs pref is true, put list of absences in mail body (text).
|
||||
"""
|
||||
etud = I["etud"]
|
||||
etud = infos["etud"]
|
||||
webmaster = sco_preferences.get_preference("bul_mail_contact_addr", formsemestre_id)
|
||||
dept = scu.unescape_html(
|
||||
sco_preferences.get_preference("DeptName", formsemestre_id)
|
||||
@ -1006,7 +1035,7 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
|
||||
etud["etudid"], with_evals=False, format="text"
|
||||
)
|
||||
|
||||
subject = "Relevé de notes de %s" % etud["nomprenom"]
|
||||
subject = f"""Relevé de notes de {etud["nomprenom"]}"""
|
||||
recipients = [recipient_addr]
|
||||
sender = sco_preferences.get_preference("email_from_addr", formsemestre_id)
|
||||
if copy_addr:
|
||||
@ -1015,7 +1044,7 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
|
||||
bcc = ""
|
||||
|
||||
# Attach pdf
|
||||
log("mail bulletin a %s" % recipient_addr)
|
||||
log(f"""mail bulletin a {recipient_addr}""")
|
||||
email.send_email(
|
||||
subject,
|
||||
sender,
|
||||
|
@ -25,7 +25,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Génération du bulletin en format JSON (beta, non completement testé)
|
||||
"""Génération du bulletin en format JSON
|
||||
|
||||
"""
|
||||
import datetime
|
||||
@ -33,8 +33,9 @@ import json
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.models import but_validations
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -139,7 +140,7 @@ def formsemestre_bulletinetud_published_dict(
|
||||
|
||||
etat_inscription = etud.inscription_etat(formsemestre.id)
|
||||
if etat_inscription != scu.INSCRIT:
|
||||
d.update(dict_decision_jury(etudid, formsemestre_id, with_decisions=True))
|
||||
d.update(dict_decision_jury(etud, formsemestre, with_decisions=True))
|
||||
return d
|
||||
|
||||
# Groupes:
|
||||
@ -343,9 +344,7 @@ def formsemestre_bulletinetud_published_dict(
|
||||
d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)
|
||||
|
||||
# --- Decision Jury
|
||||
d.update(
|
||||
dict_decision_jury(etudid, formsemestre_id, with_decisions=xml_with_decisions)
|
||||
)
|
||||
d.update(dict_decision_jury(etud, formsemestre, with_decisions=xml_with_decisions))
|
||||
# --- Appreciations
|
||||
cnx = ndb.GetDBConnexion()
|
||||
apprecs = sco_etud.appreciations_list(
|
||||
@ -364,7 +363,9 @@ def formsemestre_bulletinetud_published_dict(
|
||||
return d
|
||||
|
||||
|
||||
def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
|
||||
def dict_decision_jury(
|
||||
etud: Identite, formsemestre: FormSemestre, with_decisions: bool = False
|
||||
) -> dict:
|
||||
"""dict avec decision pour bulletins json
|
||||
- decision : décision semestre
|
||||
- decision_ue : list des décisions UE
|
||||
@ -372,6 +373,8 @@ def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
|
||||
|
||||
with_decision donne les décision même si bul_show_decision est faux.
|
||||
|
||||
Si formation APC, indique aussi validations année et RCUEs
|
||||
|
||||
Exemple:
|
||||
{
|
||||
'autorisation_inscription': [{'semestre_id': 4}],
|
||||
@ -397,14 +400,14 @@ def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
|
||||
|
||||
d = {}
|
||||
if (
|
||||
sco_preferences.get_preference("bul_show_decision", formsemestre_id)
|
||||
sco_preferences.get_preference("bul_show_decision", formsemestre.id)
|
||||
or with_decisions
|
||||
):
|
||||
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
|
||||
etudid,
|
||||
formsemestre_id,
|
||||
etud.id,
|
||||
formsemestre.id,
|
||||
show_uevalid=sco_preferences.get_preference(
|
||||
"bul_show_uevalid", formsemestre_id
|
||||
"bul_show_uevalid", formsemestre.id
|
||||
),
|
||||
)
|
||||
d["situation"] = infos["situation"]
|
||||
@ -456,4 +459,7 @@ def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
|
||||
)
|
||||
else:
|
||||
d["decision"] = dict(code="", etat="DEM")
|
||||
# Ajout jury BUT:
|
||||
if formsemestre.formation.is_apc():
|
||||
d.update(but_validations.dict_decision_jury(etud, formsemestre))
|
||||
return d
|
||||
|
@ -140,12 +140,23 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
|
||||
text = (field or "") % scu.WrapDict(
|
||||
cdict
|
||||
) # note that None values are mapped to empty strings
|
||||
except:
|
||||
except KeyError as exc:
|
||||
log(
|
||||
f"""process_field: KeyError on field={field!r}
|
||||
values={pprint.pformat(cdict)}
|
||||
"""
|
||||
)
|
||||
if len(exc.args) > 0:
|
||||
missing_field = exc.args[0]
|
||||
text = f"""<para><i>format invalide: champs</i> {missing_field} <i>inexistant !</i></para>"""
|
||||
except: # pylint: disable=bare-except
|
||||
log(
|
||||
f"""process_field: invalid format. field={field!r}
|
||||
values={pprint.pformat(cdict)}
|
||||
"""
|
||||
)
|
||||
# ne sera pas visible si lien vers pdf:
|
||||
scu.flash_once(f"Attention: format PDF invalide (champs {field}")
|
||||
text = (
|
||||
"<para><i>format invalide !</i></para><para>"
|
||||
+ traceback.format_exc()
|
||||
|
@ -231,29 +231,29 @@ 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.models.formsemestre import FormSemestre
|
||||
from app.scodoc import sco_cursus
|
||||
|
||||
if getattr(g, "defer_cache_invalidation", False):
|
||||
assert isinstance(formsemestre_id, int) or formsemestre_id is None
|
||||
if getattr(g, "defer_cache_invalidation", 0) > 0:
|
||||
g.sem_to_invalidate.add(formsemestre_id)
|
||||
return
|
||||
log(f"inval_cache, formsemestre_id={formsemestre_id} pdfonly={pdfonly}")
|
||||
if formsemestre_id is None:
|
||||
# clear all caches
|
||||
log("----- invalidate_formsemestre: clearing all caches -----")
|
||||
log(
|
||||
f"----- invalidate_formsemestre: clearing all caches. pdfonly={pdfonly}-----"
|
||||
)
|
||||
formsemestre_ids = [
|
||||
x[0]
|
||||
for x in ndb.SimpleQuery(
|
||||
"""SELECT id FROM notes_formsemestre s
|
||||
WHERE s.dept_id=%(dept_id)s
|
||||
""",
|
||||
{"dept_id": g.scodoc_dept_id},
|
||||
)
|
||||
formsemestre.id
|
||||
for formsemestre in FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
]
|
||||
else:
|
||||
formsemestre_ids = [
|
||||
formsemestre_id
|
||||
] + sco_parcours_dut.list_formsemestre_utilisateurs_uecap(formsemestre_id)
|
||||
log(f"----- invalidate_formsemestre: clearing {formsemestre_ids} -----")
|
||||
] + sco_cursus.list_formsemestre_utilisateurs_uecap(formsemestre_id)
|
||||
log(
|
||||
f"----- invalidate_formsemestre: clearing {formsemestre_ids}. pdfonly={pdfonly} -----"
|
||||
)
|
||||
|
||||
if not pdfonly:
|
||||
# Delete cached notes and evaluations
|
||||
@ -289,17 +289,19 @@ class DeferredSemCacheManager:
|
||||
"""
|
||||
|
||||
def __enter__(self):
|
||||
assert not hasattr(g, "defer_cache_invalidation")
|
||||
g.defer_cache_invalidation = True
|
||||
g.sem_to_invalidate = set()
|
||||
if not hasattr(g, "defer_cache_invalidation"):
|
||||
g.defer_cache_invalidation = 0
|
||||
g.sem_to_invalidate = set()
|
||||
g.defer_cache_invalidation += 1
|
||||
return True
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||
assert g.defer_cache_invalidation
|
||||
g.defer_cache_invalidation = False
|
||||
while g.sem_to_invalidate:
|
||||
formsemestre_id = g.sem_to_invalidate.pop()
|
||||
invalidate_formsemestre(formsemestre_id)
|
||||
g.defer_cache_invalidation -= 1
|
||||
if g.defer_cache_invalidation == 0:
|
||||
while g.sem_to_invalidate:
|
||||
formsemestre_id = g.sem_to_invalidate.pop()
|
||||
invalidate_formsemestre(formsemestre_id)
|
||||
|
||||
|
||||
# ---- Nouvelles classes ScoDoc 9.2
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
134
app/scodoc/sco_cursus.py
Normal file
134
app/scodoc/sco_cursus.py
Normal file
@ -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()]
|
@ -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
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@ -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()]
|
@ -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:
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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("<p>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("</p>")
|
||||
|
||||
# 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,
|
||||
@ -873,10 +871,9 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
|
||||
# -----------
|
||||
def formsemestre_validation_auto(formsemestre_id):
|
||||
"Formulaire saisie automatisee des decisions d'un semestre"
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Saisie automatique des décisions du semestre"),
|
||||
"""
|
||||
f"""
|
||||
<ul>
|
||||
<li>Seuls les étudiants qui obtiennent le semestre seront affectés (code ADM, moyenne générale et
|
||||
toutes les barres, semestre précédent validé);</li>
|
||||
@ -888,12 +885,11 @@ def formsemestre_validation_auto(formsemestre_id):
|
||||
<p>Il est donc vivement conseillé de relire soigneusement les décisions à l'issue
|
||||
de cette procédure !</p>
|
||||
<form action="do_formsemestre_validation_auto">
|
||||
<input type="hidden" name="formsemestre_id" value="%s"/>
|
||||
<input type="hidden" name="formsemestre_id" value="{formsemestre_id}"/>
|
||||
<input type="submit" value="Calculer automatiquement ces décisions"/>
|
||||
<p><em>Le calcul prend quelques minutes, soyez patients !</em></p>
|
||||
</form>
|
||||
"""
|
||||
% formsemestre_id,
|
||||
""",
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
@ -908,55 +904,56 @@ def do_formsemestre_validation_auto(formsemestre_id):
|
||||
etudids = nt.get_etudids()
|
||||
nb_valid = 0
|
||||
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)
|
||||
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||
{"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||
)[0]
|
||||
with sco_cache.DeferredSemCacheManager():
|
||||
for etudid in etudids:
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
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]
|
||||
|
||||
# Conditions pour validation automatique:
|
||||
if ins["etat"] == "I" and (
|
||||
(
|
||||
(not Se.prev)
|
||||
or (Se.prev_decision and Se.prev_decision["code"] in (ADM, ADC, ADJ))
|
||||
)
|
||||
and Se.barre_moy_ok
|
||||
and Se.barres_ue_ok
|
||||
and not etud_has_notes_attente(etudid, formsemestre_id)
|
||||
):
|
||||
# check: s'il existe une decision ou autorisation et qu'elles sont differentes,
|
||||
# warning (et ne fait rien)
|
||||
decision_sem = nt.get_etud_decision_sem(etudid)
|
||||
ok = True
|
||||
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
|
||||
if (
|
||||
len(autorisations) != 1
|
||||
or autorisations[0]["semestre_id"] != next_semestre_id
|
||||
):
|
||||
if ok:
|
||||
conflicts.append(etud)
|
||||
ok = False
|
||||
|
||||
# ok, valide !
|
||||
if ok:
|
||||
formsemestre_validation_etud_manu(
|
||||
formsemestre_id,
|
||||
etudid,
|
||||
code_etat=ADM,
|
||||
devenir="NEXT",
|
||||
assidu=True,
|
||||
redirect=False,
|
||||
# Conditions pour validation automatique:
|
||||
if ins["etat"] == "I" and (
|
||||
(
|
||||
(not Se.prev)
|
||||
or (
|
||||
Se.prev_decision and Se.prev_decision["code"] in (ADM, ADC, ADJ)
|
||||
)
|
||||
)
|
||||
nb_valid += 1
|
||||
and Se.barre_moy_ok
|
||||
and Se.barres_ue_ok
|
||||
and not etud_has_notes_attente(etudid, formsemestre_id)
|
||||
):
|
||||
# check: s'il existe une decision ou autorisation et qu'elles sont differentes,
|
||||
# warning (et ne fait rien)
|
||||
decision_sem = nt.get_etud_decision_sem(etudid)
|
||||
ok = True
|
||||
if decision_sem and decision_sem["code"] != ADM:
|
||||
ok = False
|
||||
conflicts.append(etud)
|
||||
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
|
||||
):
|
||||
if ok:
|
||||
conflicts.append(etud)
|
||||
ok = False
|
||||
|
||||
# ok, valide !
|
||||
if ok:
|
||||
formsemestre_validation_etud_manu(
|
||||
formsemestre_id,
|
||||
etudid,
|
||||
code_etat=ADM,
|
||||
devenir="NEXT",
|
||||
assidu=True,
|
||||
redirect=False,
|
||||
)
|
||||
nb_valid += 1
|
||||
log(
|
||||
"do_formsemestre_validation_auto: %d validations, %d conflicts"
|
||||
% (nb_valid, len(conflicts))
|
||||
@ -1176,7 +1173,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)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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'<font color="red"><b>Erreur: {message}</b></font>'),
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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="<para>") -> str:
|
||||
"""Reportlab bug turnaround (could be removed in a future version).
|
||||
p is a string with Reportlab intra-paragraph XML tags.
|
||||
Replaces <br/> (currently ignored by Reportlab) by </para><para>
|
||||
Also replaces <br> by <br/>
|
||||
"""
|
||||
return ("</para>" + para).join(
|
||||
re.split(r"<.*?br.*?/>", paragraph_txt.replace("<br>", "<br/>"))
|
||||
)
|
||||
|
||||
|
||||
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", "<br/>")
|
||||
|
||||
# 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"] = (
|
||||
"""<b>Décision du semestre antérieur S%(prev_semestre_id)s :</b> %(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 : <b>%s</b>""" % (
|
||||
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="<para>"):
|
||||
"""Reportlab bug turnaround (could be removed in a future version).
|
||||
p is a string with Reportlab intra-paragraph XML tags.
|
||||
Replaces <br/> (currently ignored by Reportlab) by </para><para>
|
||||
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"""<b>Décision du semestre antérieur S{params['prev_semestre_id']} :</b> {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"]}<br/>
|
||||
<b>Niveaux de compétences:</b><br/> {decision.get("descr_decisions_rcue", "")}
|
||||
"""
|
||||
l = re.split(r"<.*?br.*?/>", p)
|
||||
return ("</para>" + 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 = []
|
||||
|
||||
|
@ -386,6 +386,7 @@ def gen_formsemestre_recapcomplet_html(
|
||||
table_html = sco_cache.TableRecapWithEvalsCache.get(formsemestre.id)
|
||||
else:
|
||||
table_html = sco_cache.TableRecapCache.get(formsemestre.id)
|
||||
# en mode jury ne cache pas la table html
|
||||
if mode_jury or (table_html is None):
|
||||
table_html = _gen_formsemestre_recapcomplet_html(
|
||||
formsemestre,
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -169,7 +169,7 @@ section>div:nth-child(1){
|
||||
border: none;
|
||||
margin-left: auto;
|
||||
}
|
||||
.rang{
|
||||
.rang, .competence{
|
||||
font-weight: bold;
|
||||
}
|
||||
.ue .rang{
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -232,7 +232,21 @@ class releveBUT extends HTMLElement {
|
||||
${(()=>{
|
||||
let output = "";
|
||||
data.semestre.decision_rcue.forEach(competence=>{
|
||||
output += `<div class=rang>${competence.niveau.competence.titre}</div><div>${competence.code}</div>`;
|
||||
output += `<div class=competence>${competence.niveau.competence.titre}</div><div>${competence.code}</div>`;
|
||||
})
|
||||
return output;
|
||||
})()}
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
if(data.semestre.decision_ue?.length){
|
||||
output += `
|
||||
<div>
|
||||
<div class=enteteSemestre>UE</div><div></div>
|
||||
${(()=>{
|
||||
let output = "";
|
||||
data.semestre.decision_ue.forEach(ue=>{
|
||||
output += `<div class=competence>${ue.acronyme}</div><div>${ue.code}</div>`;
|
||||
})
|
||||
return output;
|
||||
})()}
|
||||
|
@ -30,7 +30,7 @@
|
||||
<span class="ue_type_{{ue.type}}">
|
||||
<span class="ue_color_indicator" style="background:{{
|
||||
ue.color if ue.color is not none else 'blue'}}"></span>
|
||||
<b>{{ue.acronyme}}</b> <a class="discretelink" href="{{
|
||||
<b>{{ue.acronyme}} <a class="discretelink" href="{{
|
||||
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}"
|
||||
title="{{ue.acronyme}}: {{
|
||||
('pas de compétence associée'
|
||||
@ -40,6 +40,7 @@
|
||||
else ''
|
||||
}}"
|
||||
>{{ue.titre}}</a>
|
||||
</b>
|
||||
{% set virg = joiner(", ") %}
|
||||
<span class="ue_code">(
|
||||
{%- if ue.ue_code -%}{{ virg() }}code {{ue.ue_code}} {%- endif -%}
|
||||
@ -53,9 +54,6 @@
|
||||
)
|
||||
</span>
|
||||
</span>
|
||||
{% if (ue.niveau_competence is none) and ue.type == 0 %}
|
||||
<span class="fontred">pas de compétence associée</span>
|
||||
{% endif %}
|
||||
|
||||
{% if editable and not ue.is_locked() %}
|
||||
<a class="stdlink" href="{{ url_for('notes.ue_edit',
|
||||
@ -63,6 +61,9 @@
|
||||
}}">modifier</a>
|
||||
{% endif %}
|
||||
|
||||
{{ form_ue_choix_niveau(formation, ue)|safe }}
|
||||
|
||||
|
||||
{% if ue.type == 1 and ue.modules.count() == 0 %}
|
||||
<span class="warning" title="pas de module, donc pas de bonus calculé">aucun module rattaché !</span>
|
||||
{% endif %}
|
||||
|
@ -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")
|
||||
@ -2661,6 +2664,7 @@ def formsemestre_jury_but_erase(formsemestre_id: int, etudid: int = None):
|
||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||
deca.erase()
|
||||
db.session.commit()
|
||||
log(f"formsemestre_jury_but_erase({formsemestre_id}, {etudid})")
|
||||
flash("décisions de jury effacées")
|
||||
return redirect(dest_url)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.3.16"
|
||||
SCOVERSION = "9.3.19"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user