Bul. BUT: ECTS, Absences, Appréciations.

This commit is contained in:
Emmanuel Viennet 2022-03-15 21:50:37 +01:00
parent ab87a98eda
commit 7409ccce5a
11 changed files with 124 additions and 46 deletions

View File

@ -13,6 +13,7 @@ from flask import url_for, g
from app.comp.res_but import ResultatsSemestreBUT
from app.models import FormSemestre, Identite
from app.models.ues import UniteEns
from app.scodoc import sco_bulletins, sco_utils as scu
from app.scodoc import sco_bulletins_json
from app.scodoc import sco_bulletins_pdf
@ -62,18 +63,15 @@ class BulletinBUT:
# }
return d
def etud_ue_results(self, etud, ue):
def etud_ue_results(self, etud: Identite, ue: UniteEns, decision_ue: dict) -> dict:
"dict synthèse résultats UE"
res = self.res
d = {
"id": ue.id,
"titre": ue.titre,
"numero": ue.numero,
"type": ue.type,
"ECTS": {
"acquis": 0.0, # XXX TODO voir jury #sco92
"total": ue.ects or 0.0, # float même si non renseigné
},
"color": ue.color,
"competence": None, # XXX TODO lien avec référentiel
"moyenne": None,
@ -86,6 +84,11 @@ class BulletinBUT:
"ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
"saes": self.etud_ue_mod_results(etud, ue, res.saes),
}
if self.prefs["bul_show_ects"]:
d["ECTS"] = {
"acquis": decision_ue.get("ects", 0.0),
"total": ue.ects or 0.0, # float même si non renseigné
}
if ue.type != UE_SPORT:
if self.prefs["bul_show_ue_rangs"]:
rangs, effectif = res.ue_rangs[ue.id]
@ -277,11 +280,17 @@ class BulletinBUT:
"numero": formsemestre.semestre_id,
"inscription": "", # inutilisé mais nécessaire pour le js de Seb.
"groupes": [], # XXX TODO
"absences": {
}
if self.prefs["bul_show_abs"]:
semestre_infos["absences"] = {
"injustifie": nbabs - nbabsjust,
"total": nbabs,
},
}
}
decisions_ues = self.res.get_etud_decision_ues(etud.id) or {}
if self.prefs["bul_show_ects"]:
ects_tot = sum([ue.ects or 0 for ue in res.ues]) if res.ues else 0.0
ects_acquis = sum([d.get("ects", 0) for d in decisions_ues.values()])
semestre_infos["ECTS"] = {"acquis": ects_acquis, "total": ects_tot}
semestre_infos.update(
sco_bulletins_json.dict_decision_jury(etud.id, formsemestre.id)
)
@ -307,7 +316,9 @@ class BulletinBUT:
),
"saes": self.etud_mods_results(etud, res.saes, version=version),
"ues": {
ue.acronyme: self.etud_ue_results(etud, ue)
ue.acronyme: self.etud_ue_results(
etud, ue, decision_ue=decisions_ues.get(ue.id, {})
)
for ue in res.ues
# si l'UE comporte des modules auxquels on est inscrit:
if (

View File

@ -65,6 +65,9 @@ class ResultatsSemestre(ResultatsCache):
self.moyennes_matieres = {}
"""Moyennes de matières, si calculées. { matiere_id : Series, index etudid }"""
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id}, formsemestre='{self.formsemestre}')>"
def compute(self):
"Charge les notes et inscriptions et calcule toutes les moyennes"
# voir ce qui est chargé / calculé ici et dans les sous-classes
@ -177,7 +180,6 @@ class ResultatsSemestre(ResultatsCache):
if not self.validations:
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
ue_capitalisees = self.validations.ue_capitalisees
ue_by_code = {}
for etudid in ue_capitalisees.index:
recompute_mg = False
# ue_codes = set(ue_capitalisees.loc[etudid]["ue_code"])

View File

@ -161,7 +161,6 @@ class FormSemestre(db.Model):
d["periode"] = 1 # typiquement, début en septembre: S1, S3...
else:
d["periode"] = 2 # typiquement, début en février: S2, S4...
d["titre_num"] = self.titre_num()
d["titreannee"] = self.titre_annee()
d["mois_debut"] = self.mois_debut()
d["mois_fin"] = self.mois_fin()
@ -174,7 +173,6 @@ class FormSemestre(db.Model):
d["session_id"] = self.session_id()
d["etapes"] = self.etapes_apo_vdi()
d["etapes_apo_str"] = self.etapes_apo_str()
d["responsables"] = [u.id for u in self.responsables] # liste des ids
return d
def query_ues(self, with_sport=False) -> flask_sqlalchemy.BaseQuery:
@ -302,6 +300,10 @@ class FormSemestre(db.Model):
else:
return ", ".join([u.get_nomcomplet() for u in self.responsables])
def est_responsable(self, user):
"True si l'user est l'un des responsables du semestre"
return user.id in [u.id for u in self.responsables]
def annee_scolaire_str(self):
"2021 - 2022"
return scu.annee_scolaire_repr(self.date_debut.year, self.date_debut.month)

View File

@ -51,10 +51,11 @@ Chaque semestre peut si nécessaire utiliser un type de bulletin différent.
"""
import io
import pprint
import pydoc
import re
import time
import traceback
import pydoc
from flask import g, request
@ -140,7 +141,11 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
cdict
) # note that None values are mapped to empty strings
except:
log("process_field: invalid format=%s" % field)
log(
f"""process_field: invalid format. field={field!r}
values={pprint.pformat(cdict)}
"""
)
text = (
"<para><i>format invalide !</i></para><para>"
+ traceback.format_exc()

View File

@ -1172,7 +1172,7 @@ class BasePreferences(object):
"bul_show_abs", # ex "gestion_absence"
{
"initvalue": 1,
"title": "Indiquer les absences sous les bulletins",
"title": "Indiquer les absences dans les bulletins",
"input_type": "boolcheckbox",
"category": "bul",
"labels": ["non", "oui"],

View File

@ -175,11 +175,24 @@ section>div:nth-child(1){
.ue .rang{
font-weight: 400;
}
.absencesRecap {
align-items: baseline;
}
.absencesRecap > div:nth-child(2n) {
font-weight: normal;
}
.abs {
font-weight: bold;
}
.decision{
margin: 5px 0;
font-weight: bold;
font-size: 20px;
text-decoration: underline var(--couleurIntense);
}
#ects_tot {
margin-left: 8px;
font-weight: bold;
font-size: 20px;
}
.enteteSemestre{
color: black;

View File

@ -2152,6 +2152,18 @@ div.eval_description {
padding: 3px;
}
div.bul_foot {
max-width: 1000px;
background: #FFE7D5;
border-radius: 16px;
border: 1px solid #AAA;
padding: 16px 32px;
margin: auto;
}
div.bull_appreciations {
border-left: 1px solid black;
padding-left: 5px;
}
/* Saisie des notes */
div.saisienote_etape1 {

View File

@ -83,7 +83,7 @@ class releveBUT extends HTMLElement {
<div>
<div class=infoSemestre></div>
<div>
<div class=decision></div>
<div><span class=decision></span><span class="ects" id="ects_tot"></span></div>
<div class=dateInscription>Inscrit le </div>
<em>Les moyennes ci-dessus servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.</em>
</div>
@ -204,9 +204,10 @@ class releveBUT extends HTMLElement {
<div>Min. promo. :</div><div>${data.semestre.notes.min}</div>
</div>
<div class=absencesRecap>
<div class=enteteSemestre>Absences</div>
<div class=enteteSemestre>N.J. ${data.semestre.absences?.injustifie ?? "-"}</div>
<div style="grid-column: 2">Total ${data.semestre.absences?.total ?? "-"}</div>
<div class=enteteSemestre>Absences</div><div class=enteteSemestre>1/2 jour.</div>
<div class=abs>Non justifiées</div>
<div>${data.semestre.absences?.injustifie ?? "-"}</div>
<div class=abs>Total</div><div>${data.semestre.absences?.total ?? "-"}</div>
</div>
<a class=photo href="${data.etudiant.fiche_url}"><img src="${data.etudiant.photo_url || "default_Student.svg"}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0"></a>
`;
@ -223,7 +224,8 @@ class releveBUT extends HTMLElement {
}).join("")
}*/
this.shadow.querySelector(".infoSemestre").innerHTML = output;
this.shadow.querySelector(".decision").innerHTML = data.semestre.decision?.code || "";
this.shadow.querySelector(".decision").innerHTML = "Décision jury: " + (data.semestre.decision?.code || "");
this.shadow.querySelector("#ects_tot").innerHTML = "ECTS&nbsp;:&nbsp;" + (data.semestre.ECTS?.acquis || "-") + "&nbsp;/&nbsp;" + (data.semestre.ECTS?.total || "-");
}
/*******************************/
@ -256,7 +258,7 @@ class releveBUT extends HTMLElement {
Bonus&nbsp;:&nbsp;${dataUE.bonus || 0}&nbsp;-
Malus&nbsp;:&nbsp;${dataUE.malus || 0}
<span class=ects>&nbsp;-
ECTS&nbsp;:&nbsp;${dataUE.ECTS.acquis}&nbsp;/&nbsp;${dataUE.ECTS.total}
ECTS&nbsp;:&nbsp;${dataUE.ECTS?.acquis || "-"}&nbsp;/&nbsp;${dataUE.ECTS?.total || "-"}
</span>
</div>
</div>`;
@ -377,9 +379,9 @@ class releveBUT extends HTMLElement {
setOptions(options) {
Object.entries(options).forEach(([option, value]) => {
if (value === false) {
document.body.classList.add(option.replace("show", "hide"))
this.shadow.children[0].classList.add(option.replace("show", "hide"));
}
})
});
}

View File

@ -1,28 +1,54 @@
{# -*- mode: jinja-html -*- #}
{# Pied des bulletins HTML #}
<p>Situation actuelle:
{% if inscription_courante %}
<a class="stdlink" href="{{url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=inscription_courante.formsemestre_id)
}}">{{inscription_str}}</a>
{% else %}
{{inscription_str}}
{% endif %}
</p>
{% if formsemestre.modalite == "EXT" %}
<p><a href="{{
url_for('notes.formsemestre_ext_edit_ue_validations',
<div class="bul_foot">
<div>
<p>Situation actuelle:
{% if inscription_courante %}
<a class="stdlink" href="{{url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
etudid=etud.id)}}"
class="stdlink">
Éditer les validations d'UE dans ce semestre extérieur
</a></p>
{% endif %}
formsemestre_id=inscription_courante.formsemestre_id)
}}">{{inscription_str}}</a>
{% else %}
{{inscription_str}}
{% endif %}
</p>
<div class="bull_appreciations">
<h3>Appréciations</h3>
{% for app in appreciations %}
<p><span class="bull_appreciations_date">{{app.date}}</span>{{
app.comment}}<span
class="bull_appreciations_link">{% if can_edit_appreciations %}<a
class="stdlink" href="{{url_for('notes.appreciation_add_form',
scodoc_dept=g.scodoc_dept, id=app.id)}}">modifier</a>
<a class="stdlink" href="{{url_for('notes.appreciation_add_form',
scodoc_dept=g.scodoc_dept, id=app.id, suppress=1)}}">supprimer</a>{% endif %}
</span>
</p>
{% endfor %}
{% if can_edit_appreciations %}
<p><a class="stdlink" href="{{url_for(
'notes.appreciation_add_form', scodoc_dept=g.scodoc_dept,
etudid=etud.id, formsemestre_id=formsemestre_id)
}}">Ajouter une appréciation</a>
</p>
{% endif %}
</div>
{% if formsemestre.modalite == "EXT" %}
<p><a href="{{
url_for('notes.formsemestre_ext_edit_ue_validations',
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
etudid=etud.id)}}"
class="stdlink">
Éditer les validations d'UE dans ce semestre extérieur
</a></p>
{% endif %}
</div>
</div>
{# Place du diagramme radar #}
<form id="params">

View File

@ -5,7 +5,7 @@
<div class="formsemestre_page_title">
<div class="infos">
<span class="semtitle"><a class="stdlink"
title="{{formsemestre.session_id}}"
title="{{formsemestre.session_id()}}"
href="{{url_for('notes.formsemestre_status',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)}}"
>{{formsemestre.titre}}</a>

View File

@ -323,6 +323,9 @@ def formsemestre_bulletinetud(
elif format == "html":
return render_template(
"but/bulletin.html",
appreciations=models.BulAppreciations.query.filter_by(
etudid=etudid, formsemestre_id=formsemestre.id
).order_by(models.BulAppreciations.date),
bul_url=url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
@ -332,6 +335,8 @@ def formsemestre_bulletinetud(
force_publishing=1, # pour ScoDoc lui même
version=version,
),
can_edit_appreciations=formsemestre.est_responsable(current_user)
or (current_user.has_permission(Permission.ScoEtudInscrit)),
etud=etud,
formsemestre=formsemestre,
inscription_courante=etud.inscription_courante(),