Amélioration page bilan ECTS: plus de détails, messages d'avertissement. Closes #992

This commit is contained in:
Emmanuel Viennet 2024-09-12 14:34:34 +02:00
parent e12dca7aef
commit 8c3e7b4ff6
10 changed files with 155 additions and 30 deletions

View File

@ -86,6 +86,12 @@ def _news_delete_jury_etud(etud: Identite, detail: str = ""):
text=f"""Suppression décision jury {detail} pour <a href="{url}">{etud.nomprenom}</a>""", text=f"""Suppression décision jury {detail} pour <a href="{url}">{etud.nomprenom}</a>""",
url=url, url=url,
) )
Scolog.logdb(
"jury_delete_manual",
etudid=etud.id,
msg=f"Validation {detail} effacée",
commit=True,
)
@bp.route( @bp.route(

View File

@ -501,7 +501,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
semestre_idx=formsemestre.semestre_id, semestre_idx=formsemestre.semestre_id,
formation_id=formsemestre.formation.id)}"> formation_id=formsemestre.formation.id)}">
{formsemestre.formation.html()} ({ {formsemestre.formation.html()|safe} ({
formsemestre.formation.id})</a> formsemestre.formation.id})</a>
</li> </li>
</ul> </ul>

View File

@ -64,7 +64,7 @@ class Formation(ScoDocModel):
def html(self) -> str: def html(self) -> str:
"titre complet pour affichage" "titre complet pour affichage"
return f"""Formation {self.titre} ({self.acronyme}) [version {self.version}] code {self.formation_code}""" return f"""Formation {self.titre} ({self.acronyme}) version {self.version} code <tt>{self.formation_code}</tt>"""
@classmethod @classmethod
def get_formation(cls, formation_id: int | str, dept_id: int = None) -> "Formation": def get_formation(cls, formation_id: int | str, dept_id: int = None) -> "Formation":

View File

@ -91,7 +91,7 @@ class ScolarFormSemestreValidation(db.Model):
d.pop("_sa_instance_state", None) d.pop("_sa_instance_state", None)
return d return d
def html(self, detail=False) -> str: def html(self, detail=True) -> str:
"Affichage html" "Affichage html"
if self.ue_id is not None: if self.ue_id is not None:
moyenne = ( moyenne = (
@ -114,11 +114,12 @@ class ScolarFormSemestreValidation(db.Model):
+ ", ".join([p.code for p in self.ue.parcours])) + ", ".join([p.code for p in self.ue.parcours]))
+ "</span>" + "</span>"
if self.ue.parcours else ""} if self.ue.parcours else ""}
{("émise par " + link)}
: <b>{self.code}</b>{moyenne} : <b>{self.code}</b>{moyenne}
<b>{(self.ue.ects or 0):g} ECTS</b> <b>{(self.ue.ects or 0):g} ECTS</b>
le {self.event_date.strftime(scu.DATEATIME_FMT)} {("émise par " + link)}
""" """ + (
f"le {self.event_date.strftime(scu.DATEATIME_FMT)}" if detail else ""
)
else: else:
return f"""Validation du semestre S{ return f"""Validation du semestre S{
self.formsemestre.semestre_id if self.formsemestre else "?"} self.formsemestre.semestre_id if self.formsemestre else "?"}

View File

@ -765,7 +765,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
"delete_small_dis_img", title="Suppression impossible (module utilisé)" "delete_small_dis_img", title="Suppression impossible (module utilisé)"
) )
H = [ H = [
f"""<h2>{formation.html()} {lockicon} f"""<h2>{formation.html()|safe} {lockicon}
</h2> </h2>
""", """,
] ]

View File

@ -8,7 +8,12 @@ span.parcours {
color: blueviolet; color: blueviolet;
} }
div.ue_list_etud_validations ul.liste_validations li { div.liste_validations {
margin-top: 16px;
margin-bottom: 16px;
}
div.ue_list_etud_validations div.liste_validations details {
margin-bottom: 8px; margin-bottom: 8px;
} }
@ -28,6 +33,7 @@ details {
} }
div.validation-details { div.validation-details {
margin-left: 32px; margin-top: 12px;
margin-bottom: 16px; margin-bottom: 16px;
margin-left: 32px;
} }

View File

@ -44,7 +44,7 @@
{%- endmacro %} {%- endmacro %}
{% block app_content %} {% block app_content %}
<h2>{{formation.html()}}</h2> <h2>{{formation.html()|safe}}</h2>
{# Liens vers les différents parcours #} {# Liens vers les différents parcours #}
<div class="les_parcours"> <div class="les_parcours">
@ -133,7 +133,7 @@ Choisissez un parcours...
d'associer à chaque semestre d'un niveau de compétence une UE de la formation d'associer à chaque semestre d'un niveau de compétence une UE de la formation
<a class="stdlink" <a class="stdlink"
href="{{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept, formation_id=formation.id ) href="{{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept, formation_id=formation.id )
}}">{{formation.html()}} }}">{{formation.html()|safe}}
</a>.</p> </a>.</p>
<p>Le symbole <span class="parc">TC</span> désigne un niveau du tronc commun <p>Le symbole <span class="parc">TC</span> désigne un niveau du tronc commun

View File

@ -1,4 +1,4 @@
{% extends "sco_page.j2" %} {% extends "sco_page_dept.j2" %}
{% block styles %} {% block styles %}
{{super()}} {{super()}}
@ -10,8 +10,13 @@
<h1>Bilan des ECTS de {{etud.html_link_fiche()|safe}}</h1> <h1>Bilan des ECTS de {{etud.html_link_fiche()|safe}}</h1>
<div class="help"> <div class="help">
Cette page donne toutes les UEs acquises par l'étudiant (codes <tt>ADM, ADJ, ADJR, ADSUP, CMP...</tt>) <p>
dans chaque formation qu'il a suivi. Cette page donne toutes les UEs acquises par l'étudiant (codes <tt>ADM, ADJ, ADJR, ADSUP, CMP...</tt>).
</p>
<p>
La somme des crédits ECTS en bas de page peut compter des UEs
suivies plusieurs fois (redoublements) n'a pas de signification pour l'octroit des diplômes.
</p>
</div> </div>
{% for diplome in formsemestre_by_diplome %} {% for diplome in formsemestre_by_diplome %}
@ -21,4 +26,29 @@
{% include "jury/ue_list_etud_validations.j2" %} {% include "jury/ue_list_etud_validations.j2" %}
{% endfor %} {% endfor %}
{% if ue_warnings %}
<div class="scobox">
<div class="scobox-title warning">Attention</div>
<ul>
{% for warning in ue_warnings %}
<li>{{warning|safe}}</li>
{% endfor %}
</ul>
<div class="help fontred">Ces problème peuvent dans certains cas affecter
le comptage des crédits ECTS et la délivrance des diplômes.</div>
</div>
{% endif %}
{% endblock app_content %} {% endblock app_content %}
{% block scripts %}
{{super()}}
<script>
function open_all_details() {
var details = document.querySelectorAll('.liste_validations details');
details.forEach(function(detail) {
detail.open = true;
});
}
</script>
{% endblock %}

View File

@ -5,14 +5,18 @@
<div class="help">Liste de toutes les UEs validées par {{etud.html_link_fiche()|safe}}, <div class="help">Liste de toutes les UEs validées par {{etud.html_link_fiche()|safe}},
sur des semestres ou déclarées comme "antérieures" (externes). sur des semestres ou déclarées comme "antérieures" (externes).
</div> </div>
<ul class="liste_validations"> <div class="liste_validations">
{% for validation in validations %} {% for validation in validations %}
<li <div
{% if loop.index0 > 0 and validation.formsemestre and loop.previtem.formsemestre.semestre_id != validation.formsemestre.semestre_id %} {% if loop.index0 > 0 and validation.formsemestre and loop.previtem.formsemestre.semestre_id != validation.formsemestre.semestre_id %}
class="new_semestre" class="new_semestre"
data-ue_id="{{validation.ue.id}}"
{% endif %} {% endif %}
>{{ validation.html() | safe }} >
<details>
<summary>
{{ validation.html(detail=False) | safe }}
{% if edit_mode %} {% if edit_mode %}
{% if validation.formsemestre and validation.formsemestre.can_edit_jury() %} {% if validation.formsemestre and validation.formsemestre.can_edit_jury() %}
<form class="inline-form"> <form class="inline-form">
@ -24,12 +28,29 @@
{{ scu.icontag("lock_img", border="0", title="Semestre verrouillé")|safe }} {{ scu.icontag("lock_img", border="0", title="Semestre verrouillé")|safe }}
{% endif %} {% endif %}
{% endif %} {% endif %}
</li> </summary>
{% endfor %} <div class="validation-details">
</ul> <b>UE {{validation.ue.acronyme}}</b> <tt>[{{validation.ue.ue_code}}]</tt> en
{% if total_ects %} <a class="discretelink" href="{{
<div class="total_ects"> url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
Total ECTS: {{ "%g" % total_ects }} formation_id=validation.ue.formation.id, semestre_idx=validation.ue.semestre_idx)
}}">{{validation.ue.formation.html()|safe}}</a>
{% if validation.ue.formation.is_apc() %}
<div>Compétence: {{validation.ue.niveau_competence}}</div>
<div>Référentiel :
{{ validation.ue.formation.referentiel_competence.get_title()
if validation.ue.formation.referentiel_competence else '<em>pas de référentiel</em>' }}
</div>
{% endif %}
</div>
</details>
</div>
{% endfor %}
</div>
<div><a class="stdlink" href="#" onclick="open_all_details()">ouvrir tous les détails</a></div>
{% if total_ects %}
<div class="total_ects">
Total ECTS (toutes UEs, y compris redoublées): {{ "%g" % total_ects }}
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@ -944,6 +944,8 @@ def etud_bilan_ects(etudid: int):
ects_by_diplome = {} ects_by_diplome = {}
titre_by_diplome = {} # { diplome : titre } titre_by_diplome = {} # { diplome : titre }
validations_by_diplome = {} # { diplome : query validations UEs } validations_by_diplome = {} # { diplome : query validations UEs }
validations_by_ue_code = defaultdict(list) # { ue_code : [validation] }
validations_by_niveau_sem = defaultdict(list) # { niveau_sem : [validation] }
for diplome, formsemestres in formsemestre_by_diplome.items(): for diplome, formsemestres in formsemestre_by_diplome.items():
formsemestre = formsemestres[0] formsemestre = formsemestres[0]
titre_by_diplome[diplome] = formsemestre.formation.get_titre_version() titre_by_diplome[diplome] = formsemestre.formation.get_titre_version()
@ -962,6 +964,62 @@ def etud_bilan_ects(etudid: int):
(validation.ue.ects or 0.0) (validation.ue.ects or 0.0)
for validation in validations_by_diplome[diplome] for validation in validations_by_diplome[diplome]
) )
for validation in validations:
validations_by_ue_code[validation.ue.ue_code].append(validation)
validations_by_niveau_sem[
(
(
validation.ue.niveau_competence.id
if validation.ue.niveau_competence
else None
),
validation.ue.semestre_idx,
)
].append(validation)
ref_comp_ids = {
v.ue.formation.referentiel_competence_id
for validations in validations_by_ue_code.values()
for v in validations
if v.ue.formation.referentiel_competence_id is not None
}
ue_warnings = []
if len(ref_comp_ids) > 1:
ue_warnings.append(
"""plusieurs référentiels de compétences utilisés&nbsp;!
(ok si plusieurs diplôme différents suivis)"""
)
for ue_code, validations in validations_by_ue_code.items():
ectss = {v.ue.ects for v in validations}
if len(ectss) > 1:
ects_str = ", ".join(
f"{v.ue.acronyme}: {v.ue.ects} ects" for v in validations
)
ue_acros = ", ".join({v.ue.acronyme for v in validations})
ue_warnings.append(
f"""Les UEs {ue_acros} ont le même code ({ue_code
}) mais des ECTS différents: {ects_str}"""
)
for (niveau_id, semestre_idx), validations in validations_by_niveau_sem.items():
if not validations:
continue # safeguard
formation = validations[0].ue.formation
ue_acros = ", ".join({v.ue.acronyme for v in validations})
if niveau_id is None and formation.is_apc():
ue_warnings.append(
f"""Les UEs {ue_acros} du S{semestre_idx
} n'ont pas de niveau de compétence associé !"""
)
ectss = {v.ue.ects for v in validations}
if len(ectss) > 1:
ects_str = ", ".join(
f"{v.ue.acronyme}: {v.ue.ects} ects" for v in validations
)
ue_warnings.append(
f"""Les UEs {ue_acros} du même code niveau de compétence
({validations[0].ue.niveau_competence}) ont des ECTS différents: {ects_str}"""
)
return render_template( return render_template(
"jury/etud_bilan_ects.j2", "jury/etud_bilan_ects.j2",
@ -969,5 +1027,8 @@ def etud_bilan_ects(etudid: int):
ects_by_diplome=ects_by_diplome, ects_by_diplome=ects_by_diplome,
formsemestre_by_diplome=formsemestre_by_diplome, formsemestre_by_diplome=formsemestre_by_diplome,
titre_by_diplome=titre_by_diplome, titre_by_diplome=titre_by_diplome,
title=f"Bilan ECTS {etud.nomprenom}",
ue_warnings=ue_warnings,
validations_by_diplome=validations_by_diplome, validations_by_diplome=validations_by_diplome,
sco=ScoData(etud=etud),
) )