diff --git a/app/but/bulletin_but_court.py b/app/but/bulletin_but_court.py
index 7fb389a5..a4fc6ec1 100644
--- a/app/but/bulletin_but_court.py
+++ b/app/but/bulletin_but_court.py
@@ -119,6 +119,12 @@ def _build_bulletin_but_infos(
refcomp = formsemestre.formation.referentiel_competence
if refcomp is None:
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
+
+ warn_html = cursus_but.formsemestre_warning_apc_setup(
+ formsemestre, bulletins_sem.res
+ )
+ if warn_html:
+ raise ScoValueError("Formation mal configurée pour le BUT" + warn_html)
ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau(
refcomp, etud
)
diff --git a/app/but/cursus_but.py b/app/but/cursus_but.py
index 1f453882..4a16bf05 100644
--- a/app/but/cursus_but.py
+++ b/app/but/cursus_but.py
@@ -23,29 +23,21 @@ from app.comp.res_but import ResultatsSemestreBUT
from app.comp.res_compat import NotesTableCompat
from app.models.but_refcomp import (
- ApcAnneeParcours,
ApcCompetence,
ApcNiveau,
ApcParcours,
- ApcParcoursNiveauCompetence,
ApcReferentielCompetences,
)
-from app.models import Scolog, ScolarAutorisationInscription
-from app.models.but_validations import (
- ApcValidationAnnee,
- ApcValidationRCUE,
-)
+from app.models.ues import UEParcours
+from app.models.but_validations import ApcValidationRCUE
from app.models.etudiants import Identite
from app.models.formations import Formation
-from app.models.formsemestre import FormSemestre, FormSemestreInscription
+from app.models.formsemestre import FormSemestre
from app.models.ues import UniteEns
from app.models.validations import ScolarFormSemestreValidation
from app.scodoc import codes_cursus as sco_codes
from app.scodoc.codes_cursus import code_ue_validant, CODES_UE_VALIDES, UE_STANDARD
-
-from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
-
from app.scodoc import sco_cursus_dut
@@ -440,11 +432,16 @@ def formsemestre_warning_apc_setup(
"""
if not formsemestre.formation.is_apc():
return ""
+ url_formation = url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=formsemestre.formation.id,
+ semestre_idx=formsemestre.semestre_id,
+ )
if formsemestre.formation.referentiel_competence is None:
return f"""
- Problème dans la configuration de la formation:
+ Problème dans la
+
configuration de la formation:
@@ -502,6 +502,76 @@ def formsemestre_warning_apc_setup(
"""
+def formation_semestre_niveaux_warning(formation: Formation, semestre_idx: int) -> str:
+ """Vérifie que tous les niveaux de compétences de cette année de formation
+ ont bien des UEs.
+ Afin de ne pas générer trop de messages, on ne considère que les parcours
+ du référentiel de compétences pour lesquels au moins une UE a été associée.
+
+ Renvoie fragment de html
+ """
+ annee = (semestre_idx - 1) // 2 + 1 # année BUT
+ ref_comp: ApcReferentielCompetences = formation.referentiel_competence
+ niveaux_sans_ue_by_parcour = {} # { parcour.code : [ ue ... ] }
+ parcours_ids = {
+ uep.parcours_id
+ for uep in UEParcours.query.join(UniteEns).filter_by(
+ formation_id=formation.id, type=UE_STANDARD
+ )
+ }
+ for parcour in ref_comp.parcours:
+ if parcour.id not in parcours_ids:
+ continue # saute parcours associés à aucune UE (tous semestres)
+ niveaux_sans_ue = []
+ niveaux = ApcNiveau.niveaux_annee_de_parcours(parcour, annee, ref_comp)
+ # print(f"\n# Parcours {parcour.code} : {len(niveaux)} niveaux")
+ for niveau in niveaux:
+ ues = [ue for ue in formation.ues if ue.niveau_competence_id == niveau.id]
+ if not ues:
+ niveaux_sans_ue.append(niveau)
+ # print( niveau.competence.titre + " " + str(niveau.ordre) + "\t" + str(ue) )
+ if niveaux_sans_ue:
+ niveaux_sans_ue_by_parcour[parcour.code] = niveaux_sans_ue
+ #
+ H = []
+ for parcour_code, niveaux in niveaux_sans_ue_by_parcour.items():
+ H.append(
+ f"""
Parcours {parcour_code} : {
+ len(niveaux)} niveaux sans UEs
+
+ { ', '.join( f'{niveau.competence.titre} {niveau.ordre}'
+ for niveau in niveaux
+ )
+ }
+
+
+ """
+ )
+ # Combien de compétences de tronc commun ?
+ _, niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(annee)
+ nb_niveaux_tc = len(niveaux_by_parcours["TC"])
+ nb_ues_tc = len(
+ formation.query_ues_parcour(None)
+ .filter(UniteEns.semestre_idx == semestre_idx)
+ .all()
+ )
+ if nb_niveaux_tc != nb_ues_tc:
+ H.append(
+ f"""
{nb_niveaux_tc} niveaux de compétences de tronc commun,
+ mais {nb_ues_tc} UEs de tronc commun !"""
+ )
+
+ if H:
+ return f"""
+ """
+ return "" # no problem detected
+
+
def ue_associee_au_niveau_du_parcours(
ues_possibles: list[UniteEns], niveau: ApcNiveau, sem_name: str = "S"
) -> UniteEns:
diff --git a/app/scodoc/sco_bulletins_generator.py b/app/scodoc/sco_bulletins_generator.py
index bda2567e..b8a00f9b 100644
--- a/app/scodoc/sco_bulletins_generator.py
+++ b/app/scodoc/sco_bulletins_generator.py
@@ -50,14 +50,11 @@ import traceback
import reportlab
from reportlab.platypus import (
- SimpleDocTemplate,
DocIf,
Paragraph,
- Spacer,
- Frame,
PageBreak,
)
-from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
+from reportlab.platypus import Table, KeepInFrame
from flask import request
from flask_login import current_user
@@ -213,26 +210,26 @@ class BulletinGenerator:
story.append(PageBreak()) # insert page break at end
return story
- else:
- # Generation du document PDF
- sem = sco_formsemestre.get_formsemestre(formsemestre_id)
- report = io.BytesIO() # in-memory document, no disk file
- document = sco_pdf.BaseDocTemplate(report)
- document.addPageTemplates(
- sco_pdf.ScoDocPageTemplate(
- document,
- author="%s %s (E. Viennet) [%s]"
- % (sco_version.SCONAME, sco_version.SCOVERSION, self.description),
- title=f"""Bulletin {sem["titremois"]} de {etat_civil}""",
- subject="Bulletin de note",
- margins=self.margins,
- server_name=self.server_name,
- filigranne=self.filigranne,
- preferences=sco_preferences.SemPreferences(formsemestre_id),
- )
+
+ # Generation du document PDF
+ sem = sco_formsemestre.get_formsemestre(formsemestre_id)
+ report = io.BytesIO() # in-memory document, no disk file
+ document = sco_pdf.BaseDocTemplate(report)
+ document.addPageTemplates(
+ sco_pdf.ScoDocPageTemplate(
+ document,
+ author=f"""{sco_version.SCONAME} {
+ sco_version.SCOVERSION} (E. Viennet) [{self.description}]""",
+ title=f"""Bulletin {sem["titremois"]} de {etat_civil}""",
+ subject="Bulletin de note",
+ margins=self.margins,
+ server_name=self.server_name,
+ filigranne=self.filigranne,
+ preferences=sco_preferences.SemPreferences(formsemestre_id),
)
- document.build(story)
- data = report.getvalue()
+ )
+ document.build(story)
+ data = report.getvalue()
return data
def buildTableObject(self, P, pdfTableStyle, colWidths):
diff --git a/app/scodoc/sco_edit_apc.py b/app/scodoc/sco_edit_apc.py
index a90d37e5..b56a9393 100644
--- a/app/scodoc/sco_edit_apc.py
+++ b/app/scodoc/sco_edit_apc.py
@@ -28,7 +28,7 @@
from flask.templating import render_template
from app import db
-from app.but import apc_edit_ue
+from app.but import cursus_but
from app.models import UniteEns, Matiere, Module, FormSemestre, ModuleImpl
from app.models.validations import ScolarFormSemestreValidation
from app.scodoc import codes_cursus
@@ -101,18 +101,26 @@ def html_edit_formation_apc(
),
}
+ html_ue_warning = {
+ semestre_idx: cursus_but.formation_semestre_niveaux_warning(
+ formation, semestre_idx
+ )
+ for semestre_idx in semestre_ids
+ }
+
H = [
render_template(
"pn/form_ues.j2",
- formation=formation,
- semestre_ids=semestre_ids,
- editable=editable,
- tag_editable=tag_editable,
- icons=icons,
- ues_by_sem=ues_by_sem,
- ects_by_sem=ects_by_sem,
- scu=scu,
codes_cursus=codes_cursus,
+ ects_by_sem=ects_by_sem,
+ editable=editable,
+ formation=formation,
+ html_ue_warning=html_ue_warning,
+ icons=icons,
+ scu=scu,
+ semestre_ids=semestre_ids,
+ tag_editable=tag_editable,
+ ues_by_sem=ues_by_sem,
),
]
for semestre_idx in semestre_ids:
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index fa40ad55..7e9543ce 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -892,7 +892,9 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
"""
)
diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py
index 7ee6439a..f8ddc30d 100644
--- a/app/scodoc/sco_formsemestre_inscriptions.py
+++ b/app/scodoc/sco_formsemestre_inscriptions.py
@@ -211,8 +211,6 @@ def do_formsemestre_desinscription(
"""Désinscription d'un étudiant.
Si semestre extérieur et dernier inscrit, suppression de ce semestre.
"""
- from app.scodoc import sco_formsemestre_edit
-
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
etud = Identite.get_etud(etudid)
# -- check lock
@@ -258,17 +256,14 @@ def do_formsemestre_desinscription(
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
# --- Semestre extérieur
if formsemestre.modalite == "EXT":
- inscrits = do_formsemestre_inscription_list(
- args={"formsemestre_id": formsemestre_id}
- )
- nbinscrits = len(inscrits)
- if nbinscrits == 0:
+ if 0 == len(formsemestre.inscriptions):
log(
f"""do_formsemestre_desinscription:
suppression du semestre extérieur {formsemestre}"""
)
- flash("Semestre exterieur supprimé")
- sco_formsemestre_edit.do_formsemestre_delete(formsemestre_id)
+ db.session.delete(formsemestre)
+ db.session.commit()
+ flash(f"Semestre extérieur supprimé: {formsemestre.titre_annee()}")
logdb(
cnx,
@@ -587,26 +582,29 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
ue_id = ue.id
ue_descr = ue.acronyme
if ue.type != UE_STANDARD:
- ue_descr += "
%s" % UE_TYPE_NAME[ue.type]
+ ue_descr += f"
{UE_TYPE_NAME[ue.type]}"
ue_status = nt.get_etud_ue_status(etudid, ue_id)
if ue_status and ue_status["is_capitalized"]:
sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"])
- ue_descr += (
- '
(capitalisée le %s)'
- % (
- sem_origin["formsemestre_id"],
- etudid,
- sem_origin["titreannee"],
- ndb.DateISOtoDMY(ue_status["event_date"]),
- )
- )
+ ue_descr += f"""
+ (capitalisée le {
+ ndb.DateISOtoDMY(ue_status["event_date"])
+ })
+ """
descr.append(
(
- "sec_%s" % ue_id,
+ f"sec_{ue_id}",
{
"input_type": "separator",
- "title": """%s : inscrire |
désinscrire à tous les modules"""
- % (ue_descr, ue_id, ue_id),
+ "title": f"""
{ue_descr} :
+
inscrire |
désinscrire
+ à tous les modules
+ """,
},
)
)
@@ -776,9 +774,7 @@ def do_moduleimpl_incription_options(
# verifie que ce module existe bien
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
if len(mods) != 1:
- raise ScoValueError(
- "inscription: invalid moduleimpl_id: %s" % moduleimpl_id
- )
+ raise ScoValueError(f"inscription: invalid moduleimpl_id: {moduleimpl_id}")
mod = mods[0]
sco_moduleimpl.do_moduleimpl_inscription_create(
{"moduleimpl_id": moduleimpl_id, "etudid": etudid},
@@ -790,7 +786,7 @@ def do_moduleimpl_incription_options(
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
if len(mods) != 1:
raise ScoValueError(
- "desinscription: invalid moduleimpl_id: %s" % moduleimpl_id
+ f"desinscription: invalid moduleimpl_id: {moduleimpl_id}"
)
mod = mods[0]
inscr = sco_moduleimpl.do_moduleimpl_inscription_list(
@@ -798,8 +794,7 @@ def do_moduleimpl_incription_options(
)
if not inscr:
raise ScoValueError(
- "pas inscrit a ce module ! (etudid=%s, moduleimpl_id=%s)"
- % (etudid, moduleimpl_id)
+ f"pas inscrit a ce module ! (etudid={etudid}, moduleimpl_id={moduleimpl_id})"
)
oid = inscr[0]["moduleimpl_inscription_id"]
sco_moduleimpl.do_moduleimpl_inscription_delete(
@@ -808,11 +803,13 @@ def do_moduleimpl_incription_options(
H = [
html_sco_header.sco_header(),
- """
Modifications effectuées
-
- Retour à la fiche étudiant
- """
- % url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
+ f"""
Modifications effectuées
+
+ Retour à la fiche étudiant
+
+ """,
html_sco_header.sco_footer(),
]
return "\n".join(H)
@@ -856,49 +853,59 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
"""Page listant les étudiants inscrits dans un autre semestre
dont les dates recouvrent le semestre indiqué.
"""
- sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
html_sco_header.html_sem_header(
"Inscriptions multiples parmi les étudiants du semestre ",
+ init_qtip=True,
+ javascripts=["js/etud_info.js"],
)
]
insd = list_inscrits_ailleurs(formsemestre_id)
# liste ordonnée par nom
- etudlist = [
- sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
- for etudid in insd.keys()
- if insd[etudid]
- ]
- etudlist.sort(key=lambda x: x["nom"])
+ etudlist = [Identite.get_etud(etudid) for etudid, sems in insd.items() if sems]
+ etudlist.sort(key=lambda x: x.sort_key)
if etudlist:
H.append("
")
for etud in etudlist:
H.append(
- '- %s : '
- % (
+ f"""
- {etud.nomprenom} :
+ """
)
l = []
- for s in insd[etud["etudid"]]:
+ for s in insd[etud.id]:
l.append(
- '%(titremois)s'
- % s
+ f"""{s['titremois']}"""
)
H.append(", ".join(l))
H.append("
")
- H.append("
")
- H.append("
Total: %d étudiants concernés.
" % len(etudlist))
H.append(
- """
Ces étudiants sont inscrits dans le semestre sélectionné et aussi dans d'autres semestres qui se déroulent en même temps !
Sauf exception, cette situation est anormale:
+ f"""
+
+
Total: {len(etudlist)} étudiants concernés.
+
+
Ces étudiants sont inscrits dans le semestre sélectionné et aussi
+ dans d'autres semestres qui se déroulent en même temps !
+
+
+ Sauf exception, cette situation est anormale:
+
- - vérifier que les dates des semestres se suivent sans se chevaucher
- - ou si besoin désinscrire le(s) étudiant(s) de l'un des semestres (via leurs fiches individuelles).
+ - vérifier que les dates des semestres se suivent sans se chevaucher
+
+ - ou bien si besoin désinscrire le(s) étudiant(s) de l'un des semestres
+ (via leurs fiches individuelles).
+
"""
)
diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py
index a4a9fd2c..3f5008e3 100644
--- a/app/scodoc/sco_synchro_etuds.py
+++ b/app/scodoc/sco_synchro_etuds.py
@@ -110,12 +110,13 @@ def formsemestre_synchro_etuds(
raise ScoValueError("opération impossible: semestre verrouille")
if not sem["etapes"]:
raise ScoValueError(
- """opération impossible: ce semestre n'a pas de code étape
- (voir "
Modifier ce semestre")
+ f"""opération impossible: ce semestre n'a pas de code étape
+ (voir
Modifier ce semestre)
"""
- % sem
)
- header = html_sco_header.sco_header(page_title="Synchronisation étudiants")
footer = html_sco_header.sco_footer()
base_url = url_for(
"notes.formsemestre_synchro_etuds",
@@ -166,7 +167,13 @@ def formsemestre_synchro_etuds(
suffix=scu.XLSX_SUFFIX,
)
- H = [header]
+ H = [
+ html_sco_header.sco_header(
+ page_title="Synchronisation étudiants",
+ init_qtip=True,
+ javascripts=["js/etud_info.js"],
+ )
+ ]
if not submitted:
H += _build_page(
sem,
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index 5d141fd5..295be36c 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -2411,6 +2411,29 @@ div.formation_list_ues_titre {
color: #eee;
}
+div.formation_semestre_niveaux_warning {
+ font-weight: bold;
+ color: red;
+ padding: 4px;
+ margin-top: 8px;
+ margin-left: 24px;
+ margin-right: 24px;
+ background-color: yellow;
+ border-radius: 8px;
+}
+div.formation_semestre_niveaux_warning div {
+ color: black;
+ font-size: 110%;
+}
+div.formation_semestre_niveaux_warning ul {
+ list-style-type: none;
+ padding-left: 0;
+}
+div.formation_semestre_niveaux_warning ul li:before {
+ content: '⚠️';
+ margin-right: 10px; /* Adjust space between emoji and text */
+}
+
div.formation_list_modules,
div.formation_list_ues {
border-radius: 18px;
@@ -2426,6 +2449,7 @@ div.formation_list_ues {
}
div.formation_list_ues_content {
+ margin-top: 4px;
}
div.formation_list_modules {
@@ -2508,7 +2532,13 @@ div.formation_parcs > div {
opacity: 0.7;
border-radius: 4px;
text-align: center;
- padding: 4px 8px;
+ padding: 2px 6px;
+ margin-top: 8px;
+ margin-bottom: 2px;
+}
+div.formation_parcs > div.ue_tc {
+ color: black;
+ font-style: italic;
}
div.formation_parcs > div.focus {
diff --git a/app/templates/pn/form_ues.j2 b/app/templates/pn/form_ues.j2
index 133ccb66..437d4da2 100644
--- a/app/templates/pn/form_ues.j2
+++ b/app/templates/pn/form_ues.j2
@@ -4,6 +4,7 @@
Unités d'Enseignement
semestre {{semestre_idx}} - {{ects_by_sem[semestre_idx] | safe}} ECTS
+ {{ html_ue_warning[semestre_idx] | safe }}