Ajout groupes et rangs/groupes aux bulletins BUT
This commit is contained in:
parent
abffc00570
commit
878ea41933
@ -14,10 +14,12 @@ from flask import url_for, g
|
||||
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.models import FormSemestre, Identite
|
||||
from app.models.groups import GroupDescr
|
||||
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
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
|
||||
from app.scodoc.sco_utils import fmt_note
|
||||
@ -64,8 +66,16 @@ class BulletinBUT:
|
||||
# }
|
||||
return d
|
||||
|
||||
def etud_ue_results(self, etud: Identite, ue: UniteEns, decision_ue: dict) -> dict:
|
||||
"dict synthèse résultats UE"
|
||||
def etud_ue_results(
|
||||
self,
|
||||
etud: Identite,
|
||||
ue: UniteEns,
|
||||
decision_ue: dict,
|
||||
etud_groups: list[GroupDescr] = None,
|
||||
) -> dict:
|
||||
"""dict synthèse résultats UE
|
||||
etud_groups : liste des groupes, pour affichage du rang.
|
||||
"""
|
||||
res = self.res
|
||||
|
||||
d = {
|
||||
@ -81,7 +91,7 @@ class BulletinBUT:
|
||||
if res.bonus_ues is not None and ue.id in res.bonus_ues
|
||||
else fmt_note(0.0),
|
||||
"malus": fmt_note(res.malus[ue.id][etud.id]),
|
||||
"capitalise": None, # "AAAA-MM-JJ" TODO #sco92
|
||||
"capitalise": None, # "AAAA-MM-JJ" TODO #sco93
|
||||
"ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
|
||||
"saes": self.etud_ue_mod_results(etud, ue, res.saes),
|
||||
}
|
||||
@ -103,7 +113,18 @@ class BulletinBUT:
|
||||
"moy": fmt_note(res.etud_moy_ue[ue.id].mean()),
|
||||
"rang": rang,
|
||||
"total": effectif, # nb etud avec note dans cette UE
|
||||
"groupes": {},
|
||||
}
|
||||
if self.prefs["bul_show_ue_rangs"]:
|
||||
for group in etud_groups:
|
||||
if group.partition.bul_show_rank:
|
||||
rang, effectif = self.res.get_etud_ue_rang(
|
||||
ue.id, etud.id, group.id
|
||||
)
|
||||
d["moyenne"]["groupes"][group.id] = {
|
||||
"value": rang,
|
||||
"total": effectif,
|
||||
}
|
||||
else:
|
||||
# ceci suppose que l'on a une seule UE bonus,
|
||||
# en tous cas elles auront la même description
|
||||
@ -275,6 +296,9 @@ class BulletinBUT:
|
||||
return d
|
||||
|
||||
nbabs, nbabsjust = formsemestre.get_abs_count(etud.id)
|
||||
etud_groups = sco_groups.get_etud_formsemestre_groups(
|
||||
etud, formsemestre, only_to_show=True
|
||||
)
|
||||
semestre_infos = {
|
||||
"etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo],
|
||||
"date_debut": formsemestre.date_debut.isoformat(),
|
||||
@ -282,7 +306,7 @@ class BulletinBUT:
|
||||
"annee_universitaire": formsemestre.annee_scolaire_str(),
|
||||
"numero": formsemestre.semestre_id,
|
||||
"inscription": "", # inutilisé mais nécessaire pour le js de Seb.
|
||||
"groupes": [], # XXX TODO
|
||||
"groupes": [group.to_dict() for group in etud_groups],
|
||||
}
|
||||
if self.prefs["bul_show_abs"]:
|
||||
semestre_infos["absences"] = {
|
||||
@ -306,15 +330,25 @@ class BulletinBUT:
|
||||
"max": fmt_note(res.etud_moy_gen.max()),
|
||||
}
|
||||
if self.prefs["bul_show_rangs"] and not np.isnan(res.etud_moy_gen[etud.id]):
|
||||
# classement wrt moyenne général, indicatif
|
||||
# classement wrt moyenne générale, indicatif
|
||||
semestre_infos["rang"] = {
|
||||
"value": res.etud_moy_gen_ranks[etud.id],
|
||||
"total": nb_inscrits,
|
||||
"groupes": {},
|
||||
}
|
||||
# Rangs par groupes
|
||||
for group in etud_groups:
|
||||
if group.partition.bul_show_rank:
|
||||
rang, effectif = self.res.get_etud_rang_group(etud.id, group.id)
|
||||
semestre_infos["rang"]["groupes"][group.id] = {
|
||||
"value": rang,
|
||||
"total": effectif,
|
||||
}
|
||||
else:
|
||||
semestre_infos["rang"] = {
|
||||
"value": "-",
|
||||
"total": nb_inscrits,
|
||||
"groupes": {},
|
||||
}
|
||||
d.update(
|
||||
{
|
||||
@ -324,7 +358,10 @@ class BulletinBUT:
|
||||
"saes": self.etud_mods_results(etud, res.saes, version=version),
|
||||
"ues": {
|
||||
ue.acronyme: self.etud_ue_results(
|
||||
etud, ue, decision_ue=decisions_ues.get(ue.id, {})
|
||||
etud,
|
||||
ue,
|
||||
decision_ue=decisions_ues.get(ue.id, {}),
|
||||
etud_groups=etud_groups,
|
||||
)
|
||||
for ue in res.ues
|
||||
# si l'UE comporte des modules auxquels on est inscrit:
|
||||
|
@ -18,7 +18,7 @@ from app.auth.models import User
|
||||
from app.comp.res_cache import ResultatsCache
|
||||
from app.comp import res_sem
|
||||
from app.comp.moy_mod import ModuleImplResults
|
||||
from app.models import FormSemestre, FormSemestreUECoef, formsemestre
|
||||
from app.models import FormSemestre, FormSemestreUECoef
|
||||
from app.models import Identite
|
||||
from app.models import ModuleImpl, ModuleImplInscription
|
||||
from app.models.ues import UniteEns
|
||||
@ -151,6 +151,7 @@ class ResultatsSemestre(ResultatsCache):
|
||||
if m.module.module_type == scu.ModuleType.SAE
|
||||
]
|
||||
|
||||
# --- JURY...
|
||||
def get_etud_ue_validables(self, etudid: int) -> list[UniteEns]:
|
||||
"""Liste des UEs du semestre qui doivent être validées
|
||||
|
||||
|
@ -35,7 +35,9 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
"malus",
|
||||
"etud_moy_gen_ranks",
|
||||
"etud_moy_gen_ranks_int",
|
||||
"moy_gen_rangs_by_group",
|
||||
"ue_rangs",
|
||||
"ue_rangs_by_group",
|
||||
)
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
@ -48,6 +50,8 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
self.moy_min = "NA"
|
||||
self.moy_max = "NA"
|
||||
self.moy_moy = "NA"
|
||||
self.moy_gen_rangs_by_group = {} # { group_id : (Series, Series) }
|
||||
self.ue_rangs_by_group = {} # { ue_id : {group_id : (Series, Series)}}
|
||||
self.expr_diagnostics = ""
|
||||
self.parcours = self.formsemestre.formation.get_parcours()
|
||||
|
||||
@ -153,31 +157,83 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
def compute_rangs(self):
|
||||
"""Calcule les classements
|
||||
Moyenne générale: etud_moy_gen_ranks
|
||||
Par UE (sauf ue bonus)
|
||||
Par UE (sauf ue bonus): ue_rangs[ue.id]
|
||||
Par groupe: classements selon moy_gen et UE:
|
||||
moy_gen_rangs_by_group[group_id]
|
||||
ue_rangs_by_group[group_id]
|
||||
"""
|
||||
(
|
||||
self.etud_moy_gen_ranks,
|
||||
self.etud_moy_gen_ranks_int,
|
||||
) = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
||||
for ue in self.formsemestre.query_ues():
|
||||
ues = self.formsemestre.query_ues()
|
||||
for ue in ues:
|
||||
moy_ue = self.etud_moy_ue[ue.id]
|
||||
self.ue_rangs[ue.id] = (
|
||||
moy_sem.comp_ranks_series(moy_ue)[0], # juste en chaine
|
||||
int(moy_ue.count()),
|
||||
)
|
||||
# .count() -> nb of non NaN values
|
||||
# Rangs dans les groupes (moy. gen et par UE)
|
||||
self.moy_gen_rangs_by_group = {} # { group_id : (Series, Series) }
|
||||
self.ue_rangs_by_group = {}
|
||||
partitions_avec_rang = self.formsemestre.partitions.filter_by(
|
||||
bul_show_rank=True
|
||||
)
|
||||
for partition in partitions_avec_rang:
|
||||
for group in partition.groups:
|
||||
# on prend l'intersection car les groupes peuvent inclure des étudiants désinscrits
|
||||
group_members = list(
|
||||
{etud.id for etud in group.etuds}.intersection(
|
||||
self.etud_moy_gen.index
|
||||
)
|
||||
)
|
||||
# list() car pandas veut une sequence pour take()
|
||||
# Rangs / moyenne générale:
|
||||
group_moys_gen = self.etud_moy_gen[group_members]
|
||||
self.moy_gen_rangs_by_group[group.id] = moy_sem.comp_ranks_series(
|
||||
group_moys_gen
|
||||
)
|
||||
# Rangs / UEs:
|
||||
for ue in ues:
|
||||
group_moys_ue = self.etud_moy_ue[ue.id][group_members]
|
||||
self.ue_rangs_by_group.setdefault(ue.id, {})[
|
||||
group.id
|
||||
] = moy_sem.comp_ranks_series(group_moys_ue)
|
||||
|
||||
def get_etud_ue_rang(self, ue_id, etudid) -> tuple[str, int]:
|
||||
def get_etud_rang(self, etudid: int) -> str:
|
||||
"""Le rang (classement) de l'étudiant dans le semestre.
|
||||
Result: "13" ou "12 ex"
|
||||
"""
|
||||
return self.etud_moy_gen_ranks.get(etudid, 99999)
|
||||
|
||||
def get_etud_ue_rang(self, ue_id, etudid, group_id=None) -> tuple[str, int]:
|
||||
"""Le rang de l'étudiant dans cette ue
|
||||
Si le group_id est spécifié, rang au sein de ce groupe, sinon global.
|
||||
Result: rang:str, effectif:str
|
||||
"""
|
||||
rangs, effectif = self.ue_rangs[ue_id]
|
||||
if rangs is not None:
|
||||
rang = rangs[etudid]
|
||||
if group_id is None:
|
||||
rangs, effectif = self.ue_rangs[ue_id]
|
||||
if rangs is not None:
|
||||
rang = rangs[etudid]
|
||||
else:
|
||||
return "", ""
|
||||
else:
|
||||
return "", ""
|
||||
rangs = self.ue_rangs_by_group[ue_id][group_id][0]
|
||||
rang = rangs[etudid]
|
||||
effectif = len(rangs)
|
||||
return rang, effectif
|
||||
|
||||
def get_etud_rang_group(self, etudid: int, group_id: int) -> tuple[str, int]:
|
||||
"""Rang de l'étudiant (selon moy gen) et effectif dans ce groupe.
|
||||
Si le groupe n'a pas de rang (partition avec bul_show_rank faux), ramène "", 0
|
||||
"""
|
||||
if group_id in self.moy_gen_rangs_by_group:
|
||||
r = self.moy_gen_rangs_by_group[group_id][0] # version en str
|
||||
return (r[etudid], len(r))
|
||||
else:
|
||||
return "", 0
|
||||
|
||||
def etud_check_conditions_ues(self, etudid):
|
||||
"""Vrai si les conditions sur les UE sont remplies.
|
||||
Ne considère que les UE ayant des notes (moyenne calculée).
|
||||
@ -298,16 +354,6 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
"ects_pot_fond": 0.0, # not implemented (anciennemment pour école ingé)
|
||||
}
|
||||
|
||||
def get_etud_rang(self, etudid: int) -> str:
|
||||
"""Le rang (classement) de l'étudiant dans le semestre.
|
||||
Result: "13" ou "12 ex"
|
||||
"""
|
||||
return self.etud_moy_gen_ranks.get(etudid, 99999)
|
||||
|
||||
def get_etud_rang_group(self, etudid: int, group_id: int):
|
||||
"Le rang de l'étudiant dans ce groupe (NON IMPLEMENTE)"
|
||||
return (None, 0) # XXX unimplemented TODO
|
||||
|
||||
def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
|
||||
"""Liste d'informations (compat NotesTable) sur évaluations completes
|
||||
de ce module.
|
||||
|
@ -56,11 +56,11 @@ class Identite(db.Model):
|
||||
#
|
||||
adresses = db.relationship("Adresse", lazy="dynamic", backref="etud")
|
||||
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
|
||||
# one-to-one relation:
|
||||
#
|
||||
admission = db.relationship("Admission", backref="identite", lazy="dynamic")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Etud {self.id} {self.nom} {self.prenom}>"
|
||||
return f"<Etud {self.id}/{self.departement.acronym} {self.nom} {self.prenom}>"
|
||||
|
||||
@classmethod
|
||||
def from_request(cls, etudid=None, code_nip=None):
|
||||
|
@ -25,9 +25,11 @@ class Partition(db.Model):
|
||||
partition_name = db.Column(db.String(SHORT_STR_LEN))
|
||||
# numero = ordre de presentation)
|
||||
numero = db.Column(db.Integer)
|
||||
# Calculer le rang ?
|
||||
bul_show_rank = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
# Montrer quand on indique les groupes de l'étudiant ?
|
||||
show_in_lists = db.Column(
|
||||
db.Boolean(), nullable=False, default=True, server_default="true"
|
||||
)
|
||||
@ -50,6 +52,18 @@ class Partition(db.Model):
|
||||
def __repr__(self):
|
||||
return f"""<{self.__class__.__name__} {self.id} "{self.partition_name or '(default)'}">"""
|
||||
|
||||
def to_dict(self, with_groups=False) -> dict:
|
||||
"""as a dict, with or without groups"""
|
||||
d = {
|
||||
"id": self.id,
|
||||
"formsemestre_id": self.partition_id,
|
||||
"name": self.partition_name,
|
||||
"numero": self.numero,
|
||||
}
|
||||
if with_groups:
|
||||
d["groups"] = [group.to_dict(with_partition=False) for group in self.groups]
|
||||
return d
|
||||
|
||||
|
||||
class GroupDescr(db.Model):
|
||||
"""Description d'un groupe d'une partition"""
|
||||
@ -78,6 +92,17 @@ class GroupDescr(db.Model):
|
||||
"Nom avec partition: 'TD A'"
|
||||
return f"{self.partition.partition_name or ''} {self.group_name or '-'}"
|
||||
|
||||
def to_dict(self, with_partition=True) -> dict:
|
||||
"""as a dict, with or without partition"""
|
||||
d = {
|
||||
"id": self.id,
|
||||
"partition_id": self.partition_id,
|
||||
"name": self.group_name,
|
||||
}
|
||||
if with_partition:
|
||||
d["partition"] = self.partition.to_dict(with_groups=False)
|
||||
return d
|
||||
|
||||
|
||||
group_membership = db.Table(
|
||||
"group_membership",
|
||||
@ -85,3 +110,11 @@ group_membership = db.Table(
|
||||
db.Column("group_id", db.Integer, db.ForeignKey("group_descr.id")),
|
||||
db.UniqueConstraint("etudid", "group_id"),
|
||||
)
|
||||
# class GroupMembership(db.Model):
|
||||
# """Association groupe / étudiant"""
|
||||
|
||||
# __tablename__ = "group_membership"
|
||||
# __table_args__ = (db.UniqueConstraint("etudid", "group_id"),)
|
||||
# id = db.Column(db.Integer, primary_key=True)
|
||||
# etudid = db.Column(db.Integer, db.ForeignKey("identite.id"))
|
||||
# group_id = db.Column(db.Integer, db.ForeignKey("group_descr.id"))
|
||||
|
@ -251,7 +251,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
rang = ""
|
||||
|
||||
rang_gr, ninscrits_gr, gr_name = get_etud_rangs_groups(
|
||||
etudid, formsemestre_id, partitions, partitions_etud_groups, nt
|
||||
etudid, partitions, partitions_etud_groups, nt
|
||||
)
|
||||
|
||||
if nt.get_moduleimpls_attente():
|
||||
@ -651,7 +651,7 @@ def _ue_mod_bulletin(
|
||||
|
||||
|
||||
def get_etud_rangs_groups(
|
||||
etudid, formsemestre_id, partitions, partitions_etud_groups, nt
|
||||
etudid: int, partitions, partitions_etud_groups, nt: NotesTableCompat
|
||||
):
|
||||
"""Ramene rang et nb inscrits dans chaque partition"""
|
||||
rang_gr, ninscrits_gr, gr_name = {}, {}, {}
|
||||
|
@ -165,7 +165,7 @@ def formsemestre_bulletinetud_published_dict(
|
||||
else:
|
||||
rang = str(nt.get_etud_rang(etudid))
|
||||
rang_gr, ninscrits_gr, gr_name = sco_bulletins.get_etud_rangs_groups(
|
||||
etudid, formsemestre_id, partitions, partitions_etud_groups, nt
|
||||
etudid, partitions, partitions_etud_groups, nt
|
||||
)
|
||||
|
||||
d["note"] = dict(
|
||||
|
@ -172,7 +172,7 @@ def make_xml_formsemestre_bulletinetud(
|
||||
else:
|
||||
rang = str(nt.get_etud_rang(etudid))
|
||||
rang_gr, ninscrits_gr, gr_name = sco_bulletins.get_etud_rangs_groups(
|
||||
etudid, formsemestre_id, partitions, partitions_etud_groups, nt
|
||||
etudid, partitions, partitions_etud_groups, nt
|
||||
)
|
||||
|
||||
doc.append(
|
||||
|
@ -43,13 +43,14 @@ from xml.etree.ElementTree import Element
|
||||
import flask
|
||||
from flask import g, request
|
||||
from flask import url_for, make_response
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
from app import db
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import FormSemestre, formsemestre
|
||||
from app.models import FormSemestre, Identite
|
||||
from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN
|
||||
from app.models.groups import Partition
|
||||
from app.models.groups import GroupDescr, Partition
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log, cache
|
||||
@ -61,7 +62,6 @@ from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_permissions_check
|
||||
from app.scodoc import sco_xml
|
||||
from app.scodoc.sco_exceptions import ScoException, AccessDenied, ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
|
||||
|
||||
@ -413,6 +413,34 @@ def formsemestre_get_etud_groupnames(formsemestre_id, attr="group_name"):
|
||||
return R
|
||||
|
||||
|
||||
def get_etud_formsemestre_groups(
|
||||
etud: Identite, formsemestre: FormSemestre, only_to_show=True
|
||||
) -> list[GroupDescr]:
|
||||
"""Liste les groupes auxquels est inscrit"""
|
||||
# Note: je n'ai pas réussi à cosntruire une requete SQLAlechemy avec
|
||||
# la Table d'association group_membership
|
||||
cursor = db.session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT g.id
|
||||
FROM group_descr g, group_membership gm, partition p
|
||||
WHERE gm.etudid = :etudid
|
||||
AND gm.group_id = g.id
|
||||
AND g.partition_id = p.id
|
||||
AND p.formsemestre_id = :formsemestre_id
|
||||
AND p.partition_name is not NULL
|
||||
"""
|
||||
+ (" and (p.show_in_lists is True) " if only_to_show else "")
|
||||
+ """
|
||||
ORDER BY p.numero
|
||||
"""
|
||||
),
|
||||
{"etudid": etud.id, "formsemestre_id": formsemestre.id},
|
||||
)
|
||||
return [GroupDescr.query.get(group_id) for group_id in cursor]
|
||||
|
||||
|
||||
# Ancienne fonction:
|
||||
def etud_add_group_infos(etud, formsemestre_id, sep=" ", only_to_show=False):
|
||||
"""Add informations on partitions and group memberships to etud
|
||||
(a dict with an etudid)
|
||||
@ -453,7 +481,7 @@ def etud_add_group_infos(etud, formsemestre_id, sep=" ", only_to_show=False):
|
||||
)
|
||||
etud["partitionsgroupes"] = sep.join(
|
||||
[
|
||||
gr["partition_name"] + ":" + gr["group_name"]
|
||||
(gr["partition_name"] or "") + ":" + gr["group_name"]
|
||||
for gr in infos
|
||||
if gr["group_name"] is not None
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user