diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py
index d4444b944..7dfd2f643 100644
--- a/app/api/formsemestres.py
+++ b/app/api/formsemestres.py
@@ -60,6 +60,7 @@ def formsemestre(formsemestre_id: int):
# pour accéder aux préferences
dept = Departement.query.get(formsemestre.dept_id)
app.set_sco_dept(dept.acronym)
+ data["annee_scolaire"] = formsemestre.annee_scolaire_str()
data["session_id"] = formsemestre.session_id()
return jsonify(data)
diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py
index 689ea9ed9..f9dbb8706 100644
--- a/app/but/bulletin_but.py
+++ b/app/but/bulletin_but.py
@@ -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:
diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py
index 8d426b682..06a7bde76 100644
--- a/app/comp/bonus_spo.py
+++ b/app/comp/bonus_spo.py
@@ -703,6 +703,51 @@ class BonusGrenobleIUT1(BonusSportMultiplicatif):
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).
+
+
+ - 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.
+
+ - Le vingtième des points au dessus de 10 est ajouté à la moyenne des UE.
+
+ - Exemple: un étudiant ayant 16/20 bénéficiera d'un bonus de (16-10)/20 = 0,3 points
+ sur chaque UE.
+
+
+ """
+
+ 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):
"""Calcul bonus modules optionnels (sport, culture), règle IUT de La Rochelle.
diff --git a/app/comp/res_common.py b/app/comp/res_common.py
index 7bc199ead..5170875d7 100644
--- a/app/comp/res_common.py
+++ b/app/comp/res_common.py
@@ -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
diff --git a/app/comp/res_compat.py b/app/comp/res_compat.py
index 8bbed0904..5ac18ff4e 100644
--- a/app/comp/res_compat.py
+++ b/app/comp/res_compat.py
@@ -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.
diff --git a/app/forms/main/config_main.py b/app/forms/main/config_main.py
index 2c2aa3d5e..d08719375 100644
--- a/app/forms/main/config_main.py
+++ b/app/forms/main/config_main.py
@@ -82,7 +82,9 @@ def configuration():
form_bonus.data["bonus_sport_func_name"]
)
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"))
elif form_scodoc.submit_scodoc.data and form_scodoc.validate():
if ScoDocSiteConfig.enable_entreprises(
diff --git a/app/models/etudiants.py b/app/models/etudiants.py
index 2e9292c1d..0bce6d47e 100644
--- a/app/models/etudiants.py
+++ b/app/models/etudiants.py
@@ -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""
+ return f""
@classmethod
def from_request(cls, etudid=None, code_nip=None):
diff --git a/app/models/evaluations.py b/app/models/evaluations.py
index ef2a62887..5b0960ba0 100644
--- a/app/models/evaluations.py
+++ b/app/models/evaluations.py
@@ -46,16 +46,23 @@ class Evaluation(db.Model):
def __repr__(self):
return f""""""
- def to_dict(self):
+ def to_dict(self) -> dict:
+ "Représentation dict, pour json"
e = dict(self.__dict__)
e.pop("_sa_instance_state", None)
# ScoDoc7 output_formators
e["evaluation_id"] = self.id
- e["jour"] = ndb.DateISOtoDMY(e["jour"])
- e["date_debut"] = datetime.datetime.combine(
- self.jour, self.heure_debut
- ).isoformat()
- e["date_fin"] = datetime.datetime.combine(self.jour, self.heure_fin).isoformat()
+ 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(
+ 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()
e["numero"] = ndb.int_null_is_zero(e["numero"])
e["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
return evaluation_enrich_dict(e)
diff --git a/app/models/groups.py b/app/models/groups.py
index 9cf5f2364..4c64ad543 100644
--- a/app/models/groups.py
+++ b/app/models/groups.py
@@ -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"))
diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py
index efc0b5341..df790649a 100644
--- a/app/scodoc/sco_bulletins.py
+++ b/app/scodoc/sco_bulletins.py
@@ -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 = {}, {}, {}
diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py
index 7a6bbd49e..78425028d 100644
--- a/app/scodoc/sco_bulletins_json.py
+++ b/app/scodoc/sco_bulletins_json.py
@@ -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(
diff --git a/app/scodoc/sco_bulletins_xml.py b/app/scodoc/sco_bulletins_xml.py
index d6925d8c6..f173b56ba 100644
--- a/app/scodoc/sco_bulletins_xml.py
+++ b/app/scodoc/sco_bulletins_xml.py
@@ -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(
diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py
index 80d8e02c6..d316640fc 100644
--- a/app/scodoc/sco_groups.py
+++ b/app/scodoc/sco_groups.py
@@ -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
]
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index ff91c1495..c1e7dca27 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -191,7 +191,7 @@ def fmt_note(val, note_max=None, keep_numeric=False):
return "EXC" # excuse, note neutralise
if val == NOTES_ATTENTE:
return "ATT" # attente, note neutralisee
- if isinstance(val, float) or isinstance(val, int):
+ if not isinstance(val, str):
if np.isnan(val):
return "~"
if (note_max is not None) and note_max > 0:
diff --git a/sco_version.py b/sco_version.py
index 120447d32..3222274fa 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.2.21"
+SCOVERSION = "9.2.22"
SCONAME = "ScoDoc"