Diverses améliorations pour faciliter la config BUT. Voir #862

This commit is contained in:
Emmanuel Viennet 2024-02-20 21:30:08 +01:00
parent 56aa5fbba3
commit fae9fbdd09
9 changed files with 239 additions and 109 deletions

View File

@ -119,6 +119,12 @@ def _build_bulletin_but_infos(
refcomp = formsemestre.formation.referentiel_competence refcomp = formsemestre.formation.referentiel_competence
if refcomp is None: if refcomp is None:
raise ScoNoReferentielCompetences(formation=formsemestre.formation) raise ScoNoReferentielCompetences(formation=formsemestre.formation)
warn_html = cursus_but.formsemestre_warning_apc_setup(
formsemestre, bulletins_sem.res
)
if warn_html:
raise ScoValueError("<b>Formation mal configurée pour le BUT</b>" + warn_html)
ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau( ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau(
refcomp, etud refcomp, etud
) )

View File

@ -23,29 +23,21 @@ from app.comp.res_but import ResultatsSemestreBUT
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models.but_refcomp import ( from app.models.but_refcomp import (
ApcAnneeParcours,
ApcCompetence, ApcCompetence,
ApcNiveau, ApcNiveau,
ApcParcours, ApcParcours,
ApcParcoursNiveauCompetence,
ApcReferentielCompetences, ApcReferentielCompetences,
) )
from app.models import Scolog, ScolarAutorisationInscription from app.models.ues import UEParcours
from app.models.but_validations import ( from app.models.but_validations import ApcValidationRCUE
ApcValidationAnnee,
ApcValidationRCUE,
)
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.formations import Formation 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.ues import UniteEns
from app.models.validations import ScolarFormSemestreValidation from app.models.validations import ScolarFormSemestreValidation
from app.scodoc import codes_cursus as sco_codes 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.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.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
from app.scodoc import sco_cursus_dut from app.scodoc import sco_cursus_dut
@ -440,11 +432,16 @@ def formsemestre_warning_apc_setup(
""" """
if not formsemestre.formation.is_apc(): if not formsemestre.formation.is_apc():
return "" 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: if formsemestre.formation.referentiel_competence is None:
return f"""<div class="formsemestre_status_warning"> return f"""<div class="formsemestre_status_warning">
La <a class="stdlink" href="{ La <a class="stdlink" href="{url_formation}">formation
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id) n'est pas associée à un référentiel de compétence.</a>
}">formation n'est pas associée à un référentiel de compétence.</a>
</div> </div>
""" """
H = [] H = []
@ -462,7 +459,9 @@ def formsemestre_warning_apc_setup(
) )
if nb_ues_sans_parcours != nb_ues_tot: if nb_ues_sans_parcours != nb_ues_tot:
H.append( H.append(
f"""Le semestre n'est associé à aucun parcours, mais les UEs de la formation ont des parcours""" """Le semestre n'est associé à aucun parcours,
mais les UEs de la formation ont des parcours
"""
) )
# Vérifie les niveaux de chaque parcours # Vérifie les niveaux de chaque parcours
for parcour in formsemestre.parcours or [None]: for parcour in formsemestre.parcours or [None]:
@ -489,7 +488,8 @@ def formsemestre_warning_apc_setup(
if not H: if not H:
return "" return ""
return f"""<div class="formsemestre_status_warning"> return f"""<div class="formsemestre_status_warning">
Problème dans la configuration de la formation: Problème dans la
<a class="stdlink" href="{url_formation}">configuration de la formation</a>:
<ul> <ul>
<li>{ '</li><li>'.join(H) }</li> <li>{ '</li><li>'.join(H) }</li>
</ul> </ul>
@ -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"""<li>Parcours {parcour_code} : {
len(niveaux)} niveaux sans UEs
<span>
{ ', '.join( f'{niveau.competence.titre} {niveau.ordre}'
for niveau in niveaux
)
}
</span>
</li>
"""
)
# 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"""<li>{nb_niveaux_tc} niveaux de compétences de tronc commun,
mais {nb_ues_tc} UEs de tronc commun !</li>"""
)
if H:
return f"""<div class="formation_semestre_niveaux_warning">
<div>Problèmes détectés à corriger :</div>
<ul>
{"".join(H)}
</ul>
</div>
"""
return "" # no problem detected
def ue_associee_au_niveau_du_parcours( def ue_associee_au_niveau_du_parcours(
ues_possibles: list[UniteEns], niveau: ApcNiveau, sem_name: str = "S" ues_possibles: list[UniteEns], niveau: ApcNiveau, sem_name: str = "S"
) -> UniteEns: ) -> UniteEns:

View File

@ -50,14 +50,11 @@ import traceback
import reportlab import reportlab
from reportlab.platypus import ( from reportlab.platypus import (
SimpleDocTemplate,
DocIf, DocIf,
Paragraph, Paragraph,
Spacer,
Frame,
PageBreak, PageBreak,
) )
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame from reportlab.platypus import Table, KeepInFrame
from flask import request from flask import request
from flask_login import current_user from flask_login import current_user
@ -213,26 +210,26 @@ class BulletinGenerator:
story.append(PageBreak()) # insert page break at end story.append(PageBreak()) # insert page break at end
return story return story
else:
# Generation du document PDF # Generation du document PDF
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
report = io.BytesIO() # in-memory document, no disk file report = io.BytesIO() # in-memory document, no disk file
document = sco_pdf.BaseDocTemplate(report) document = sco_pdf.BaseDocTemplate(report)
document.addPageTemplates( document.addPageTemplates(
sco_pdf.ScoDocPageTemplate( sco_pdf.ScoDocPageTemplate(
document, document,
author="%s %s (E. Viennet) [%s]" author=f"""{sco_version.SCONAME} {
% (sco_version.SCONAME, sco_version.SCOVERSION, self.description), sco_version.SCOVERSION} (E. Viennet) [{self.description}]""",
title=f"""Bulletin {sem["titremois"]} de {etat_civil}""", title=f"""Bulletin {sem["titremois"]} de {etat_civil}""",
subject="Bulletin de note", subject="Bulletin de note",
margins=self.margins, margins=self.margins,
server_name=self.server_name, server_name=self.server_name,
filigranne=self.filigranne, filigranne=self.filigranne,
preferences=sco_preferences.SemPreferences(formsemestre_id), preferences=sco_preferences.SemPreferences(formsemestre_id),
)
) )
document.build(story) )
data = report.getvalue() document.build(story)
data = report.getvalue()
return data return data
def buildTableObject(self, P, pdfTableStyle, colWidths): def buildTableObject(self, P, pdfTableStyle, colWidths):

View File

@ -28,7 +28,7 @@
from flask.templating import render_template from flask.templating import render_template
from app import db 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 import UniteEns, Matiere, Module, FormSemestre, ModuleImpl
from app.models.validations import ScolarFormSemestreValidation from app.models.validations import ScolarFormSemestreValidation
from app.scodoc import codes_cursus 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 = [ H = [
render_template( render_template(
"pn/form_ues.j2", "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, 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: for semestre_idx in semestre_ids:

View File

@ -892,7 +892,9 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
<form> <form>
<input type="checkbox" class="sco_tag_checkbox" <input type="checkbox" class="sco_tag_checkbox"
{'checked' if show_tags else ''} {'checked' if show_tags else ''}
> Montrer les tags des modules voire en ajouter <i>(ceux correspondant aux titres des compétences étant ajoutés par défaut)</i></input> > Montrer les tags des modules voire en ajouter
<i>(ceux correspondant aux titres des compétences étant ajoutés par défaut)</i>
</input>
</form> </form>
""" """
) )

View File

@ -211,8 +211,6 @@ def do_formsemestre_desinscription(
"""Désinscription d'un étudiant. """Désinscription d'un étudiant.
Si semestre extérieur et dernier inscrit, suppression de ce semestre. Si semestre extérieur et dernier inscrit, suppression de ce semestre.
""" """
from app.scodoc import sco_formsemestre_edit
formsemestre = FormSemestre.get_formsemestre(formsemestre_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
etud = Identite.get_etud(etudid) etud = Identite.get_etud(etudid)
# -- check lock # -- check lock
@ -258,17 +256,14 @@ def do_formsemestre_desinscription(
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id) sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
# --- Semestre extérieur # --- Semestre extérieur
if formsemestre.modalite == "EXT": if formsemestre.modalite == "EXT":
inscrits = do_formsemestre_inscription_list( if 0 == len(formsemestre.inscriptions):
args={"formsemestre_id": formsemestre_id}
)
nbinscrits = len(inscrits)
if nbinscrits == 0:
log( log(
f"""do_formsemestre_desinscription: f"""do_formsemestre_desinscription:
suppression du semestre extérieur {formsemestre}""" suppression du semestre extérieur {formsemestre}"""
) )
flash("Semestre exterieur supprimé") db.session.delete(formsemestre)
sco_formsemestre_edit.do_formsemestre_delete(formsemestre_id) db.session.commit()
flash(f"Semestre extérieur supprimé: {formsemestre.titre_annee()}")
logdb( logdb(
cnx, cnx,
@ -587,26 +582,29 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
ue_id = ue.id ue_id = ue.id
ue_descr = ue.acronyme ue_descr = ue.acronyme
if ue.type != UE_STANDARD: if ue.type != UE_STANDARD:
ue_descr += " <em>%s</em>" % UE_TYPE_NAME[ue.type] ue_descr += f" <em>{UE_TYPE_NAME[ue.type]}</em>"
ue_status = nt.get_etud_ue_status(etudid, ue_id) ue_status = nt.get_etud_ue_status(etudid, ue_id)
if ue_status and ue_status["is_capitalized"]: if ue_status and ue_status["is_capitalized"]:
sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"]) sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"])
ue_descr += ( ue_descr += f"""
' <a class="discretelink" href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="%s">(capitalisée le %s)' <a class="discretelink" href="{ url_for(
% ( 'notes.formsemestre_bulletinetud', scodoc_dept=g.scodoc_dept,
sem_origin["formsemestre_id"], formsemestre_id=sem_origin["formsemestre_id"],
etudid, etudid = etudid
sem_origin["titreannee"], )}" title="{sem_origin['titreannee']}">(capitalisée le {
ndb.DateISOtoDMY(ue_status["event_date"]), ndb.DateISOtoDMY(ue_status["event_date"])
) })
) """
descr.append( descr.append(
( (
"sec_%s" % ue_id, f"sec_{ue_id}",
{ {
"input_type": "separator", "input_type": "separator",
"title": """<b>%s :</b> <a href="#" onclick="chkbx_select('%s', true);">inscrire</a> | <a href="#" onclick="chkbx_select('%s', false);">désinscrire</a> à tous les modules""" "title": f"""<b>{ue_descr} :</b>
% (ue_descr, ue_id, ue_id), <a href="#" onclick="chkbx_select('{ue_id}', true);">inscrire</a> | <a
href="#" onclick="chkbx_select('{ue_id}', false);">désinscrire</a>
à tous les modules
""",
}, },
) )
) )
@ -776,9 +774,7 @@ def do_moduleimpl_incription_options(
# verifie que ce module existe bien # verifie que ce module existe bien
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id) mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
if len(mods) != 1: if len(mods) != 1:
raise ScoValueError( raise ScoValueError(f"inscription: invalid moduleimpl_id: {moduleimpl_id}")
"inscription: invalid moduleimpl_id: %s" % moduleimpl_id
)
mod = mods[0] mod = mods[0]
sco_moduleimpl.do_moduleimpl_inscription_create( sco_moduleimpl.do_moduleimpl_inscription_create(
{"moduleimpl_id": moduleimpl_id, "etudid": etudid}, {"moduleimpl_id": moduleimpl_id, "etudid": etudid},
@ -790,7 +786,7 @@ def do_moduleimpl_incription_options(
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id) mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
if len(mods) != 1: if len(mods) != 1:
raise ScoValueError( raise ScoValueError(
"desinscription: invalid moduleimpl_id: %s" % moduleimpl_id f"desinscription: invalid moduleimpl_id: {moduleimpl_id}"
) )
mod = mods[0] mod = mods[0]
inscr = sco_moduleimpl.do_moduleimpl_inscription_list( inscr = sco_moduleimpl.do_moduleimpl_inscription_list(
@ -798,8 +794,7 @@ def do_moduleimpl_incription_options(
) )
if not inscr: if not inscr:
raise ScoValueError( raise ScoValueError(
"pas inscrit a ce module ! (etudid=%s, moduleimpl_id=%s)" f"pas inscrit a ce module ! (etudid={etudid}, moduleimpl_id={moduleimpl_id})"
% (etudid, moduleimpl_id)
) )
oid = inscr[0]["moduleimpl_inscription_id"] oid = inscr[0]["moduleimpl_inscription_id"]
sco_moduleimpl.do_moduleimpl_inscription_delete( sco_moduleimpl.do_moduleimpl_inscription_delete(
@ -808,11 +803,13 @@ def do_moduleimpl_incription_options(
H = [ H = [
html_sco_header.sco_header(), html_sco_header.sco_header(),
"""<h3>Modifications effectuées</h3> f"""<h3>Modifications effectuées</h3>
<p><a class="stdlink" href="%s"> <p><a class="stdlink" href="{
Retour à la fiche étudiant</a></p> url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
""" }">
% url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid), Retour à la fiche étudiant</a>
</p>
""",
html_sco_header.sco_footer(), html_sco_header.sco_footer(),
] ]
return "\n".join(H) return "\n".join(H)
@ -856,49 +853,59 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
"""Page listant les étudiants inscrits dans un autre semestre """Page listant les étudiants inscrits dans un autre semestre
dont les dates recouvrent le semestre indiqué. dont les dates recouvrent le semestre indiqué.
""" """
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
"Inscriptions multiples parmi les étudiants du semestre ", "Inscriptions multiples parmi les étudiants du semestre ",
init_qtip=True,
javascripts=["js/etud_info.js"],
) )
] ]
insd = list_inscrits_ailleurs(formsemestre_id) insd = list_inscrits_ailleurs(formsemestre_id)
# liste ordonnée par nom # liste ordonnée par nom
etudlist = [ etudlist = [Identite.get_etud(etudid) for etudid, sems in insd.items() if sems]
sco_etud.get_etud_info(etudid=etudid, filled=True)[0] etudlist.sort(key=lambda x: x.sort_key)
for etudid in insd.keys()
if insd[etudid]
]
etudlist.sort(key=lambda x: x["nom"])
if etudlist: if etudlist:
H.append("<ul>") H.append("<ul>")
for etud in etudlist: for etud in etudlist:
H.append( H.append(
'<li><a href="%s" class="discretelink">%s</a> : ' f"""<li><a id="{etud.id}" class="discretelink etudinfo"
% ( href={
url_for( url_for(
"scolar.fiche_etud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etud["etudid"], etudid=etud.id,
), )
etud["nomprenom"], }
) >{etud.nomprenom}</a> :
"""
) )
l = [] l = []
for s in insd[etud["etudid"]]: for s in insd[etud.id]:
l.append( l.append(
'<a class="discretelink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titremois)s</a>' f"""<a class="discretelink" href="{
% s url_for('notes.formsemestre_status',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id
)}">{s['titremois']}</a>"""
) )
H.append(", ".join(l)) H.append(", ".join(l))
H.append("</li>") H.append("</li>")
H.append("</ul>")
H.append("<p>Total: %d étudiants concernés.</p>" % len(etudlist))
H.append( H.append(
"""<p class="help">Ces étudiants sont inscrits dans le semestre sélectionné et aussi dans d'autres semestres qui se déroulent en même temps ! <br>Sauf exception, cette situation est anormale:</p> f"""
</ul>
<p><b>Total: {len(etudlist)} étudiants concernés.</b></p>
<p class="help">Ces étudiants sont inscrits dans le semestre sélectionné et aussi
dans d'autres semestres qui se déroulent en même temps !
</p>
<p>
<b>Sauf exception, cette situation est anormale:</b>
</p>
<ul> <ul>
<li>vérifier que les dates des semestres se suivent sans se chevaucher</li> <li>vérifier que les dates des semestres se suivent <em>sans se chevaucher</em>
<li>ou si besoin désinscrire le(s) étudiant(s) de l'un des semestres (via leurs fiches individuelles).</li> </li>
<li>ou bien si besoin désinscrire le(s) étudiant(s) de l'un des semestres
(via leurs fiches individuelles).
</li>
</ul> </ul>
""" """
) )

View File

@ -110,12 +110,13 @@ def formsemestre_synchro_etuds(
raise ScoValueError("opération impossible: semestre verrouille") raise ScoValueError("opération impossible: semestre verrouille")
if not sem["etapes"]: if not sem["etapes"]:
raise ScoValueError( raise ScoValueError(
"""opération impossible: ce semestre n'a pas de code étape f"""opération impossible: ce semestre n'a pas de code étape
(voir "<a href="formsemestre_editwithmodules?formsemestre_id=%(formsemestre_id)s">Modifier ce semestre</a>") (voir <a class="stdlink" href="{
url_for('notes.formsemestre_editwithmodules',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
}">Modifier ce semestre</a>)
""" """
% sem
) )
header = html_sco_header.sco_header(page_title="Synchronisation étudiants")
footer = html_sco_header.sco_footer() footer = html_sco_header.sco_footer()
base_url = url_for( base_url = url_for(
"notes.formsemestre_synchro_etuds", "notes.formsemestre_synchro_etuds",
@ -166,7 +167,13 @@ def formsemestre_synchro_etuds(
suffix=scu.XLSX_SUFFIX, 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: if not submitted:
H += _build_page( H += _build_page(
sem, sem,

View File

@ -2411,6 +2411,29 @@ div.formation_list_ues_titre {
color: #eee; 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_modules,
div.formation_list_ues { div.formation_list_ues {
border-radius: 18px; border-radius: 18px;
@ -2426,6 +2449,7 @@ div.formation_list_ues {
} }
div.formation_list_ues_content { div.formation_list_ues_content {
margin-top: 4px;
} }
div.formation_list_modules { div.formation_list_modules {
@ -2508,7 +2532,13 @@ div.formation_parcs > div {
opacity: 0.7; opacity: 0.7;
border-radius: 4px; border-radius: 4px;
text-align: center; 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 { div.formation_parcs > div.focus {

View File

@ -4,6 +4,7 @@
<div class="formation_list_ues_titre">Unités d'Enseignement <div class="formation_list_ues_titre">Unités d'Enseignement
semestre {{semestre_idx}} &nbsp;-&nbsp; {{ects_by_sem[semestre_idx] | safe}} ECTS semestre {{semestre_idx}} &nbsp;-&nbsp; {{ects_by_sem[semestre_idx] | safe}} ECTS
</div> </div>
{{ html_ue_warning[semestre_idx] | safe }}
<div class="formation_list_ues_content"> <div class="formation_list_ues_content">
<ul class="apc_ue_list"> <ul class="apc_ue_list">
{% for ue in ues_by_sem[semestre_idx] %} {% for ue in ues_by_sem[semestre_idx] %}
@ -62,6 +63,8 @@
<div class="formation_parcs"> <div class="formation_parcs">
{% for parc in ue.parcours %} {% for parc in ue.parcours %}
<div>{{ parc.code }}</div> <div>{{ parc.code }}</div>
{% else %}
<div class="ue_tc" title="aucun parcours">Tronc Commun</div>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}