Compare commits

..

12 Commits

15 changed files with 243 additions and 43 deletions

View File

@ -60,6 +60,7 @@ def formsemestre(formsemestre_id: int):
# pour accéder aux préferences # pour accéder aux préferences
dept = Departement.query.get(formsemestre.dept_id) dept = Departement.query.get(formsemestre.dept_id)
app.set_sco_dept(dept.acronym) app.set_sco_dept(dept.acronym)
data["annee_scolaire"] = formsemestre.annee_scolaire_str()
data["session_id"] = formsemestre.session_id() data["session_id"] = formsemestre.session_id()
return jsonify(data) return jsonify(data)

View File

@ -14,10 +14,12 @@ from flask import url_for, g
from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_but import ResultatsSemestreBUT
from app.models import FormSemestre, Identite from app.models import FormSemestre, Identite
from app.models.groups import GroupDescr
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.scodoc import sco_bulletins, sco_utils as scu from app.scodoc import sco_bulletins, sco_utils as scu
from app.scodoc import sco_bulletins_json from app.scodoc import sco_bulletins_json
from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_groups
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
from app.scodoc.sco_utils import fmt_note from app.scodoc.sco_utils import fmt_note
@ -64,8 +66,16 @@ class BulletinBUT:
# } # }
return d return d
def etud_ue_results(self, etud: Identite, ue: UniteEns, decision_ue: dict) -> dict: def etud_ue_results(
"dict synthèse résultats UE" 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 res = self.res
d = { d = {
@ -81,7 +91,7 @@ class BulletinBUT:
if res.bonus_ues is not None and ue.id in res.bonus_ues if res.bonus_ues is not None and ue.id in res.bonus_ues
else fmt_note(0.0), else fmt_note(0.0),
"malus": fmt_note(res.malus[ue.id][etud.id]), "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), "ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
"saes": self.etud_ue_mod_results(etud, ue, res.saes), "saes": self.etud_ue_mod_results(etud, ue, res.saes),
} }
@ -103,6 +113,17 @@ class BulletinBUT:
"moy": fmt_note(res.etud_moy_ue[ue.id].mean()), "moy": fmt_note(res.etud_moy_ue[ue.id].mean()),
"rang": rang, "rang": rang,
"total": effectif, # nb etud avec note dans cette UE "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: else:
# ceci suppose que l'on a une seule UE bonus, # ceci suppose que l'on a une seule UE bonus,
@ -275,6 +296,9 @@ class BulletinBUT:
return d return d
nbabs, nbabsjust = formsemestre.get_abs_count(etud.id) nbabs, nbabsjust = formsemestre.get_abs_count(etud.id)
etud_groups = sco_groups.get_etud_formsemestre_groups(
etud, formsemestre, only_to_show=True
)
semestre_infos = { semestre_infos = {
"etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo], "etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo],
"date_debut": formsemestre.date_debut.isoformat(), "date_debut": formsemestre.date_debut.isoformat(),
@ -282,7 +306,7 @@ class BulletinBUT:
"annee_universitaire": formsemestre.annee_scolaire_str(), "annee_universitaire": formsemestre.annee_scolaire_str(),
"numero": formsemestre.semestre_id, "numero": formsemestre.semestre_id,
"inscription": "", # inutilisé mais nécessaire pour le js de Seb. "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"]: if self.prefs["bul_show_abs"]:
semestre_infos["absences"] = { semestre_infos["absences"] = {
@ -306,15 +330,25 @@ class BulletinBUT:
"max": fmt_note(res.etud_moy_gen.max()), "max": fmt_note(res.etud_moy_gen.max()),
} }
if self.prefs["bul_show_rangs"] and not np.isnan(res.etud_moy_gen[etud.id]): 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"] = { semestre_infos["rang"] = {
"value": res.etud_moy_gen_ranks[etud.id], "value": res.etud_moy_gen_ranks[etud.id],
"total": nb_inscrits, "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: else:
semestre_infos["rang"] = { semestre_infos["rang"] = {
"value": "-", "value": "-",
"total": nb_inscrits, "total": nb_inscrits,
"groupes": {},
} }
d.update( d.update(
{ {
@ -324,7 +358,10 @@ class BulletinBUT:
"saes": self.etud_mods_results(etud, res.saes, version=version), "saes": self.etud_mods_results(etud, res.saes, version=version),
"ues": { "ues": {
ue.acronyme: self.etud_ue_results( 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 for ue in res.ues
# si l'UE comporte des modules auxquels on est inscrit: # si l'UE comporte des modules auxquels on est inscrit:

View File

@ -703,6 +703,51 @@ class BonusGrenobleIUT1(BonusSportMultiplicatif):
super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan) super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
class BonusIUTRennes1(BonusSportAdditif):
"""Calcul bonus optionnels (sport, langue vivante, engagement étudiant),
règle IUT de l'Université de Rennes 1 (Lannion, St Malo).
<ul>
<li>Les étudiants peuvent suivre un ou plusieurs activités optionnelles notées.
La meilleure des notes obtenue est prise en compte, si elle est supérieure à 10/20.
</li>
<li>Le vingtième des points au dessus de 10 est ajouté à la moyenne des UE.
</li>
<li> Exemple: un étudiant ayant 16/20 bénéficiera d'un bonus de (16-10)/20 = 0,3 points
sur chaque UE.
</li>
</ul>
"""
name = "bonus_iut_rennes1"
displayed_name = "IUTs de Rennes 1 (Lannion, St Malo)"
seuil_moy_gen = 10.0
proportion_point = 1 / 20.0
classic_use_bonus_ues = True
# Adapté de BonusTarbes, mais s'applique aussi en classic
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus"""
# Prend la note de chaque modimpl, sans considération d'UE
if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
# ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
nb_ues = self.formsemestre.query_ues(with_sport=False).count()
bonus_moy_arr = np.where(
note_bonus_max > self.seuil_moy_gen,
(note_bonus_max - self.seuil_moy_gen) * self.proportion_point,
0.0,
)
# Seuil: bonus dans [min, max] (défaut [0,20])
bonus_max = self.bonus_max or 20.0
np.clip(bonus_moy_arr, self.bonus_min, bonus_max, out=bonus_moy_arr)
if self.formsemestre.formation.is_apc():
bonus_moy_arr = np.stack([bonus_moy_arr] * nb_ues).T
self.bonus_additif(bonus_moy_arr)
class BonusLaRochelle(BonusSportAdditif): class BonusLaRochelle(BonusSportAdditif):
"""Calcul bonus modules optionnels (sport, culture), règle IUT de La Rochelle. """Calcul bonus modules optionnels (sport, culture), règle IUT de La Rochelle.

View File

@ -18,7 +18,7 @@ from app.auth.models import User
from app.comp.res_cache import ResultatsCache from app.comp.res_cache import ResultatsCache
from app.comp import res_sem from app.comp import res_sem
from app.comp.moy_mod import ModuleImplResults 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 Identite
from app.models import ModuleImpl, ModuleImplInscription from app.models import ModuleImpl, ModuleImplInscription
from app.models.ues import UniteEns from app.models.ues import UniteEns
@ -151,6 +151,7 @@ class ResultatsSemestre(ResultatsCache):
if m.module.module_type == scu.ModuleType.SAE if m.module.module_type == scu.ModuleType.SAE
] ]
# --- JURY...
def get_etud_ue_validables(self, etudid: int) -> list[UniteEns]: def get_etud_ue_validables(self, etudid: int) -> list[UniteEns]:
"""Liste des UEs du semestre qui doivent être validées """Liste des UEs du semestre qui doivent être validées

View File

@ -35,7 +35,9 @@ class NotesTableCompat(ResultatsSemestre):
"malus", "malus",
"etud_moy_gen_ranks", "etud_moy_gen_ranks",
"etud_moy_gen_ranks_int", "etud_moy_gen_ranks_int",
"moy_gen_rangs_by_group",
"ue_rangs", "ue_rangs",
"ue_rangs_by_group",
) )
def __init__(self, formsemestre: FormSemestre): def __init__(self, formsemestre: FormSemestre):
@ -48,6 +50,8 @@ class NotesTableCompat(ResultatsSemestre):
self.moy_min = "NA" self.moy_min = "NA"
self.moy_max = "NA" self.moy_max = "NA"
self.moy_moy = "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.expr_diagnostics = ""
self.parcours = self.formsemestre.formation.get_parcours() self.parcours = self.formsemestre.formation.get_parcours()
@ -153,31 +157,83 @@ class NotesTableCompat(ResultatsSemestre):
def compute_rangs(self): def compute_rangs(self):
"""Calcule les classements """Calcule les classements
Moyenne générale: etud_moy_gen_ranks 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,
self.etud_moy_gen_ranks_int, self.etud_moy_gen_ranks_int,
) = moy_sem.comp_ranks_series(self.etud_moy_gen) ) = 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] moy_ue = self.etud_moy_ue[ue.id]
self.ue_rangs[ue.id] = ( self.ue_rangs[ue.id] = (
moy_sem.comp_ranks_series(moy_ue)[0], # juste en chaine moy_sem.comp_ranks_series(moy_ue)[0], # juste en chaine
int(moy_ue.count()), int(moy_ue.count()),
) )
# .count() -> nb of non NaN values # .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 """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 Result: rang:str, effectif:str
""" """
if group_id is None:
rangs, effectif = self.ue_rangs[ue_id] rangs, effectif = self.ue_rangs[ue_id]
if rangs is not None: if rangs is not None:
rang = rangs[etudid] rang = rangs[etudid]
else: else:
return "", "" return "", ""
else:
rangs = self.ue_rangs_by_group[ue_id][group_id][0]
rang = rangs[etudid]
effectif = len(rangs)
return rang, effectif 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): def etud_check_conditions_ues(self, etudid):
"""Vrai si les conditions sur les UE sont remplies. """Vrai si les conditions sur les UE sont remplies.
Ne considère que les UE ayant des notes (moyenne calculée). 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é) "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]: def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
"""Liste d'informations (compat NotesTable) sur évaluations completes """Liste d'informations (compat NotesTable) sur évaluations completes
de ce module. de ce module.

View File

@ -82,7 +82,9 @@ def configuration():
form_bonus.data["bonus_sport_func_name"] form_bonus.data["bonus_sport_func_name"]
) )
app.clear_scodoc_cache() app.clear_scodoc_cache()
flash(f"Fonction bonus sport&culture configurée.") flash("""Fonction bonus sport&culture configurée.""")
else:
flash("Fonction bonus inchangée.")
return redirect(url_for("scodoc.index")) return redirect(url_for("scodoc.index"))
elif form_scodoc.submit_scodoc.data and form_scodoc.validate(): elif form_scodoc.submit_scodoc.data and form_scodoc.validate():
if ScoDocSiteConfig.enable_entreprises( if ScoDocSiteConfig.enable_entreprises(

View File

@ -56,11 +56,11 @@ class Identite(db.Model):
# #
adresses = db.relationship("Adresse", lazy="dynamic", backref="etud") adresses = db.relationship("Adresse", lazy="dynamic", backref="etud")
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic") billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
# one-to-one relation: #
admission = db.relationship("Admission", backref="identite", lazy="dynamic") admission = db.relationship("Admission", backref="identite", lazy="dynamic")
def __repr__(self): 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 @classmethod
def from_request(cls, etudid=None, code_nip=None): def from_request(cls, etudid=None, code_nip=None):

View File

@ -46,16 +46,23 @@ class Evaluation(db.Model):
def __repr__(self): def __repr__(self):
return f"""<Evaluation {self.id} {self.jour.isoformat() if self.jour else ''} "{self.description[:16] if self.description else ''}">""" return f"""<Evaluation {self.id} {self.jour.isoformat() if self.jour else ''} "{self.description[:16] if self.description else ''}">"""
def to_dict(self): def to_dict(self) -> dict:
"Représentation dict, pour json"
e = dict(self.__dict__) e = dict(self.__dict__)
e.pop("_sa_instance_state", None) e.pop("_sa_instance_state", None)
# ScoDoc7 output_formators # ScoDoc7 output_formators
e["evaluation_id"] = self.id e["evaluation_id"] = self.id
e["jour"] = ndb.DateISOtoDMY(e["jour"]) e["jour"] = e["jour"].strftime("%d/%m/%Y") if e["jour"] else ""
if self.jour is None:
e["date_debut"] = None
e["date_fin"] = None
else:
e["date_debut"] = datetime.datetime.combine( e["date_debut"] = datetime.datetime.combine(
self.jour, self.heure_debut self.jour, self.heure_debut or datetime.time(0, 0)
).isoformat()
e["date_fin"] = datetime.datetime.combine(
self.jour, self.heure_fin or datetime.time(0, 0)
).isoformat() ).isoformat()
e["date_fin"] = datetime.datetime.combine(self.jour, self.heure_fin).isoformat()
e["numero"] = ndb.int_null_is_zero(e["numero"]) e["numero"] = ndb.int_null_is_zero(e["numero"])
e["poids"] = self.get_ue_poids_dict() # { ue_id : poids } e["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
return evaluation_enrich_dict(e) return evaluation_enrich_dict(e)

View File

@ -25,9 +25,11 @@ class Partition(db.Model):
partition_name = db.Column(db.String(SHORT_STR_LEN)) partition_name = db.Column(db.String(SHORT_STR_LEN))
# numero = ordre de presentation) # numero = ordre de presentation)
numero = db.Column(db.Integer) numero = db.Column(db.Integer)
# Calculer le rang ?
bul_show_rank = db.Column( bul_show_rank = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false" db.Boolean(), nullable=False, default=False, server_default="false"
) )
# Montrer quand on indique les groupes de l'étudiant ?
show_in_lists = db.Column( show_in_lists = db.Column(
db.Boolean(), nullable=False, default=True, server_default="true" db.Boolean(), nullable=False, default=True, server_default="true"
) )
@ -50,6 +52,18 @@ class Partition(db.Model):
def __repr__(self): def __repr__(self):
return f"""<{self.__class__.__name__} {self.id} "{self.partition_name or '(default)'}">""" 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): class GroupDescr(db.Model):
"""Description d'un groupe d'une partition""" """Description d'un groupe d'une partition"""
@ -78,6 +92,17 @@ class GroupDescr(db.Model):
"Nom avec partition: 'TD A'" "Nom avec partition: 'TD A'"
return f"{self.partition.partition_name or ''} {self.group_name or '-'}" 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 = db.Table(
"group_membership", "group_membership",
@ -85,3 +110,11 @@ group_membership = db.Table(
db.Column("group_id", db.Integer, db.ForeignKey("group_descr.id")), db.Column("group_id", db.Integer, db.ForeignKey("group_descr.id")),
db.UniqueConstraint("etudid", "group_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"))

View File

@ -251,7 +251,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
rang = "" rang = ""
rang_gr, ninscrits_gr, gr_name = get_etud_rangs_groups( 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(): if nt.get_moduleimpls_attente():
@ -651,7 +651,7 @@ def _ue_mod_bulletin(
def get_etud_rangs_groups( 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""" """Ramene rang et nb inscrits dans chaque partition"""
rang_gr, ninscrits_gr, gr_name = {}, {}, {} rang_gr, ninscrits_gr, gr_name = {}, {}, {}

View File

@ -165,7 +165,7 @@ def formsemestre_bulletinetud_published_dict(
else: else:
rang = str(nt.get_etud_rang(etudid)) rang = str(nt.get_etud_rang(etudid))
rang_gr, ninscrits_gr, gr_name = sco_bulletins.get_etud_rangs_groups( 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( d["note"] = dict(

View File

@ -172,7 +172,7 @@ def make_xml_formsemestre_bulletinetud(
else: else:
rang = str(nt.get_etud_rang(etudid)) rang = str(nt.get_etud_rang(etudid))
rang_gr, ninscrits_gr, gr_name = sco_bulletins.get_etud_rangs_groups( 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( doc.append(

View File

@ -43,13 +43,14 @@ from xml.etree.ElementTree import Element
import flask import flask
from flask import g, request from flask import g, request
from flask import url_for, make_response from flask import url_for, make_response
from sqlalchemy.sql import text
from app import db from app import db
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat 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 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.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app import log, cache 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_permissions_check
from app.scodoc import sco_xml from app.scodoc import sco_xml
from app.scodoc.sco_exceptions import ScoException, AccessDenied, ScoValueError from app.scodoc.sco_exceptions import ScoException, AccessDenied, ScoValueError
from app.scodoc.sco_permissions import Permission
from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.TrivialFormulator import TrivialFormulator
@ -413,6 +413,34 @@ def formsemestre_get_etud_groupnames(formsemestre_id, attr="group_name"):
return R 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): def etud_add_group_infos(etud, formsemestre_id, sep=" ", only_to_show=False):
"""Add informations on partitions and group memberships to etud """Add informations on partitions and group memberships to etud
(a dict with an etudid) (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( etud["partitionsgroupes"] = sep.join(
[ [
gr["partition_name"] + ":" + gr["group_name"] (gr["partition_name"] or "") + ":" + gr["group_name"]
for gr in infos for gr in infos
if gr["group_name"] is not None if gr["group_name"] is not None
] ]

View File

@ -191,7 +191,7 @@ def fmt_note(val, note_max=None, keep_numeric=False):
return "EXC" # excuse, note neutralise return "EXC" # excuse, note neutralise
if val == NOTES_ATTENTE: if val == NOTES_ATTENTE:
return "ATT" # attente, note neutralisee return "ATT" # attente, note neutralisee
if isinstance(val, float) or isinstance(val, int): if not isinstance(val, str):
if np.isnan(val): if np.isnan(val):
return "~" return "~"
if (note_max is not None) and note_max > 0: if (note_max is not None) and note_max > 0:

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.2.21" SCOVERSION = "9.2.22"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"