Check APC conformity: cas UE de parcours
This commit is contained in:
parent
5258a570a6
commit
872e741d9f
@ -437,7 +437,7 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
|||||||
|
|
||||||
|
|
||||||
def moduleimpl_is_conforme(
|
def moduleimpl_is_conforme(
|
||||||
moduleimpl, evals_poids: pd.DataFrame, modules_coefficients: pd.DataFrame
|
moduleimpl, evals_poids: pd.DataFrame, modimpl_coefs_df: pd.DataFrame
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Vérifie que les évaluations de ce moduleimpl sont bien conformes
|
"""Vérifie que les évaluations de ce moduleimpl sont bien conformes
|
||||||
au PN.
|
au PN.
|
||||||
@ -446,7 +446,7 @@ def moduleimpl_is_conforme(
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
||||||
modules_coefficients: DataFrame, cols module_id, lignes UEs
|
modimpl_coefs_df: DataFrame, cols: modimpl_id, lignes: UEs du formsemestre
|
||||||
NB: les UEs dans evals_poids sont sans le bonus sport
|
NB: les UEs dans evals_poids sont sans le bonus sport
|
||||||
"""
|
"""
|
||||||
nb_evals, nb_ues = evals_poids.shape
|
nb_evals, nb_ues = evals_poids.shape
|
||||||
@ -454,18 +454,18 @@ def moduleimpl_is_conforme(
|
|||||||
return True # modules vides conformes
|
return True # modules vides conformes
|
||||||
if nb_ues == 0:
|
if nb_ues == 0:
|
||||||
return False # situation absurde (pas d'UE)
|
return False # situation absurde (pas d'UE)
|
||||||
if len(modules_coefficients) != nb_ues:
|
if len(modimpl_coefs_df) != nb_ues:
|
||||||
# il arrive (#bug) que le cache ne soit pas à jour...
|
# il arrive (#bug) que le cache ne soit pas à jour...
|
||||||
sco_cache.invalidate_formsemestre()
|
sco_cache.invalidate_formsemestre()
|
||||||
raise ScoBugCatcher("moduleimpl_is_conforme: nb ue incoherent")
|
raise ScoBugCatcher("moduleimpl_is_conforme: nb ue incoherent")
|
||||||
|
|
||||||
if moduleimpl.module_id not in modules_coefficients:
|
if moduleimpl.id not in modimpl_coefs_df:
|
||||||
# soupçon de bug cache coef ?
|
# soupçon de bug cache coef ?
|
||||||
sco_cache.invalidate_formsemestre()
|
sco_cache.invalidate_formsemestre()
|
||||||
raise ScoBugCatcher("Erreur 454 - merci de ré-essayer")
|
raise ScoBugCatcher("Erreur 454 - merci de ré-essayer")
|
||||||
|
|
||||||
module_evals_poids = evals_poids.transpose().sum(axis=1) != 0
|
module_evals_poids = evals_poids.transpose().sum(axis=1) != 0
|
||||||
return all((modules_coefficients[moduleimpl.module_id] != 0).eq(module_evals_poids))
|
return all((modimpl_coefs_df[moduleimpl.id] != 0).eq(module_evals_poids))
|
||||||
|
|
||||||
|
|
||||||
class ModuleImplResultsClassic(ModuleImplResults):
|
class ModuleImplResultsClassic(ModuleImplResults):
|
||||||
|
@ -62,7 +62,7 @@ class ModuleImpl(db.Model):
|
|||||||
"""Invalide poids cachés"""
|
"""Invalide poids cachés"""
|
||||||
df_cache.EvaluationsPoidsCache.delete(self.id)
|
df_cache.EvaluationsPoidsCache.delete(self.id)
|
||||||
|
|
||||||
def check_apc_conformity(self) -> bool:
|
def check_apc_conformity(self, res: "ResultatsSemestreBUT") -> bool:
|
||||||
"""true si les poids des évaluations du module permettent de satisfaire
|
"""true si les poids des évaluations du module permettent de satisfaire
|
||||||
les coefficients du PN.
|
les coefficients du PN.
|
||||||
"""
|
"""
|
||||||
@ -76,7 +76,7 @@ class ModuleImpl(db.Model):
|
|||||||
return moy_mod.moduleimpl_is_conforme(
|
return moy_mod.moduleimpl_is_conforme(
|
||||||
self,
|
self,
|
||||||
self.get_evaluations_poids(),
|
self.get_evaluations_poids(),
|
||||||
self.module.formation.get_module_coefs(self.module.semestre_id),
|
res.modimpl_coefs_df,
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_dict(self, convert_objects=False, with_module=True):
|
def to_dict(self, convert_objects=False, with_module=True):
|
||||||
|
@ -40,7 +40,7 @@ from app.comp import moy_mod
|
|||||||
from app.comp.moy_mod import ModuleImplResults
|
from app.comp.moy_mod import ModuleImplResults
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.models import FormSemestre
|
from app.models import FormSemestre, Module
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.evaluations import Evaluation
|
from app.models.evaluations import Evaluation
|
||||||
from app.models.moduleimpls import ModuleImpl
|
from app.models.moduleimpls import ModuleImpl
|
||||||
@ -50,9 +50,7 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
|
|||||||
from app.scodoc.sco_etud import etud_sort_key
|
from app.scodoc.sco_etud import etud_sort_key
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_evaluation_db
|
from app.scodoc import sco_evaluation_db
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_moduleimpl
|
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
@ -198,8 +196,11 @@ def do_evaluation_listenotes(
|
|||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return (
|
return (
|
||||||
flask.redirect(
|
flask.redirect(
|
||||||
"%s/Notes/moduleimpl_status?moduleimpl_id=%s"
|
url_for(
|
||||||
% (scu.ScoURL(), E["moduleimpl_id"])
|
"notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=E["moduleimpl_id"],
|
||||||
|
)
|
||||||
),
|
),
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
@ -213,7 +214,7 @@ def do_evaluation_listenotes(
|
|||||||
_make_table_notes(
|
_make_table_notes(
|
||||||
tf[1],
|
tf[1],
|
||||||
evals,
|
evals,
|
||||||
format=format,
|
fmt=format,
|
||||||
note_sur_20=note_sur_20,
|
note_sur_20=note_sur_20,
|
||||||
anonymous_listing=anonymous_listing,
|
anonymous_listing=anonymous_listing,
|
||||||
group_ids=group_ids,
|
group_ids=group_ids,
|
||||||
@ -228,45 +229,46 @@ def do_evaluation_listenotes(
|
|||||||
def _make_table_notes(
|
def _make_table_notes(
|
||||||
html_form,
|
html_form,
|
||||||
evals,
|
evals,
|
||||||
format="",
|
fmt: str = "",
|
||||||
note_sur_20=False,
|
note_sur_20=False,
|
||||||
anonymous_listing=False,
|
anonymous_listing=False,
|
||||||
hide_groups=False,
|
hide_groups=False,
|
||||||
with_emails=False,
|
with_emails=False,
|
||||||
group_ids=[],
|
group_ids: list[int] = None,
|
||||||
mode="module", # "eval" or "module"
|
mode="module", # "eval" or "module"
|
||||||
):
|
) -> str:
|
||||||
"""Table liste notes (une seule évaluation ou toutes celles d'un module)"""
|
"""Table liste notes (une seule évaluation ou toutes celles d'un module)"""
|
||||||
# Code à ré-écrire !
|
group_ids = group_ids or []
|
||||||
if not evals:
|
if not evals:
|
||||||
return "<p>Aucune évaluation !</p>"
|
return "<p>Aucune évaluation !</p>"
|
||||||
E = evals[0]
|
E = evals[0]
|
||||||
moduleimpl_id = E["moduleimpl_id"]
|
moduleimpl_id = E["moduleimpl_id"]
|
||||||
modimpl_o = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
modimpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
||||||
module = models.Module.query.get(modimpl_o["module_id"])
|
modimpl_o = modimpl.to_dict() # TODO temporaire - à refactorer
|
||||||
|
module: Module = modimpl.module
|
||||||
|
formsemestre: FormSemestre = modimpl.formsemestre
|
||||||
is_apc = module.formation.get_parcours().APC_SAE
|
is_apc = module.formation.get_parcours().APC_SAE
|
||||||
if is_apc:
|
if is_apc:
|
||||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||||
is_conforme = modimpl.check_apc_conformity()
|
is_conforme = modimpl.check_apc_conformity(res)
|
||||||
evals_poids, ues = moy_mod.load_evaluations_poids(moduleimpl_id)
|
evals_poids, ues = moy_mod.load_evaluations_poids(moduleimpl_id)
|
||||||
if not ues:
|
if not ues:
|
||||||
is_apc = False
|
is_apc = False
|
||||||
else:
|
else:
|
||||||
evals_poids, ues = None, None
|
evals_poids, ues = None, None
|
||||||
is_conforme = True
|
is_conforme = True
|
||||||
sem = sco_formsemestre.get_formsemestre(modimpl_o["formsemestre_id"])
|
|
||||||
# (debug) check that all evals are in same module:
|
# (debug) check that all evals are in same module:
|
||||||
for e in evals:
|
for e in evals:
|
||||||
if e["moduleimpl_id"] != moduleimpl_id:
|
if e["moduleimpl_id"] != moduleimpl_id:
|
||||||
raise ValueError("invalid evaluations list")
|
raise ValueError("invalid evaluations list")
|
||||||
|
|
||||||
if format == "xls":
|
if fmt == "xls":
|
||||||
keep_numeric = True # pas de conversion des notes en strings
|
keep_numeric = True # pas de conversion des notes en strings
|
||||||
else:
|
else:
|
||||||
keep_numeric = False
|
keep_numeric = False
|
||||||
# Si pas de groupe, affiche tout
|
# Si pas de groupe, affiche tout
|
||||||
if not group_ids:
|
if not group_ids:
|
||||||
group_ids = [sco_groups.get_default_group(modimpl_o["formsemestre_id"])]
|
group_ids = [sco_groups.get_default_group(formsemestre.id)]
|
||||||
groups = sco_groups.listgroups(group_ids)
|
groups = sco_groups.listgroups(group_ids)
|
||||||
|
|
||||||
gr_title = sco_groups.listgroups_abbrev(groups)
|
gr_title = sco_groups.listgroups_abbrev(groups)
|
||||||
@ -275,7 +277,7 @@ def _make_table_notes(
|
|||||||
if anonymous_listing:
|
if anonymous_listing:
|
||||||
columns_ids = ["code"] # cols in table
|
columns_ids = ["code"] # cols in table
|
||||||
else:
|
else:
|
||||||
if format == "xls" or format == "xml":
|
if fmt == "xls" or fmt == "xml":
|
||||||
columns_ids = ["nom", "prenom"]
|
columns_ids = ["nom", "prenom"]
|
||||||
else:
|
else:
|
||||||
columns_ids = ["nomprenom"]
|
columns_ids = ["nomprenom"]
|
||||||
@ -326,7 +328,7 @@ def _make_table_notes(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if etat == scu.INSCRIT: # si inscrit, indique groupe
|
if etat == scu.INSCRIT: # si inscrit, indique groupe
|
||||||
groups = sco_groups.get_etud_groups(etudid, modimpl_o["formsemestre_id"])
|
groups = sco_groups.get_etud_groups(etudid, formsemestre.id)
|
||||||
grc = sco_groups.listgroups_abbrev(groups)
|
grc = sco_groups.listgroups_abbrev(groups)
|
||||||
else:
|
else:
|
||||||
if etat == scu.DEMISSION:
|
if etat == scu.DEMISSION:
|
||||||
@ -348,7 +350,7 @@ def _make_table_notes(
|
|||||||
"_nomprenom_target": url_for(
|
"_nomprenom_target": url_for(
|
||||||
"notes.formsemestre_bulletinetud",
|
"notes.formsemestre_bulletinetud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=modimpl_o["formsemestre_id"],
|
formsemestre_id=formsemestre.id,
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
),
|
),
|
||||||
"_nomprenom_td_attrs": f"""id="{etudid}" class="etudinfo" data-sort="{etud.sort_key}" """,
|
"_nomprenom_td_attrs": f"""id="{etudid}" class="etudinfo" data-sort="{etud.sort_key}" """,
|
||||||
@ -415,7 +417,7 @@ def _make_table_notes(
|
|||||||
key_mgr,
|
key_mgr,
|
||||||
note_sur_20,
|
note_sur_20,
|
||||||
keep_numeric,
|
keep_numeric,
|
||||||
format=format,
|
format=fmt,
|
||||||
)
|
)
|
||||||
columns_ids.append(e["evaluation_id"])
|
columns_ids.append(e["evaluation_id"])
|
||||||
#
|
#
|
||||||
@ -431,7 +433,7 @@ def _make_table_notes(
|
|||||||
# Moyenne de l'étudiant dans le module
|
# Moyenne de l'étudiant dans le module
|
||||||
# Affichée même en APC à titre indicatif
|
# Affichée même en APC à titre indicatif
|
||||||
_add_moymod_column(
|
_add_moymod_column(
|
||||||
sem["formsemestre_id"],
|
formsemestre.id,
|
||||||
moduleimpl_id,
|
moduleimpl_id,
|
||||||
rows,
|
rows,
|
||||||
columns_ids,
|
columns_ids,
|
||||||
@ -464,7 +466,7 @@ def _make_table_notes(
|
|||||||
if with_emails:
|
if with_emails:
|
||||||
columns_ids += ["email", "emailperso"]
|
columns_ids += ["email", "emailperso"]
|
||||||
# Ajoute lignes en tête et moyennes
|
# Ajoute lignes en tête et moyennes
|
||||||
if len(evals) > 0 and format != "bordereau":
|
if len(evals) > 0 and fmt != "bordereau":
|
||||||
rows_head = [row_coefs]
|
rows_head = [row_coefs]
|
||||||
if is_apc:
|
if is_apc:
|
||||||
rows_head.append(row_poids)
|
rows_head.append(row_poids)
|
||||||
@ -472,7 +474,7 @@ def _make_table_notes(
|
|||||||
rows = rows_head + rows
|
rows = rows_head + rows
|
||||||
rows.append(row_moys)
|
rows.append(row_moys)
|
||||||
# ajout liens HTMl vers affichage une evaluation:
|
# ajout liens HTMl vers affichage une evaluation:
|
||||||
if format == "html" and len(evals) > 1:
|
if fmt == "html" and len(evals) > 1:
|
||||||
rlinks = {"_table_part": "head"}
|
rlinks = {"_table_part": "head"}
|
||||||
for e in evals:
|
for e in evals:
|
||||||
rlinks[e["evaluation_id"]] = "afficher"
|
rlinks[e["evaluation_id"]] = "afficher"
|
||||||
@ -488,11 +490,11 @@ def _make_table_notes(
|
|||||||
rows.append(rlinks)
|
rows.append(rlinks)
|
||||||
|
|
||||||
if len(evals) == 1: # colonne "Rem." seulement si une eval
|
if len(evals) == 1: # colonne "Rem." seulement si une eval
|
||||||
if format == "html": # pas d'indication d'origine en pdf (pour affichage)
|
if fmt == "html": # pas d'indication d'origine en pdf (pour affichage)
|
||||||
columns_ids.append("expl_key")
|
columns_ids.append("expl_key")
|
||||||
elif format == "xls" or format == "xml":
|
elif fmt == "xls" or fmt == "xml":
|
||||||
columns_ids.append("comment")
|
columns_ids.append("comment")
|
||||||
elif format == "bordereau":
|
elif fmt == "bordereau":
|
||||||
columns_ids.append("signatures")
|
columns_ids.append("signatures")
|
||||||
|
|
||||||
# titres divers:
|
# titres divers:
|
||||||
@ -510,7 +512,7 @@ def _make_table_notes(
|
|||||||
hh = "%s, %s (%d étudiants)" % (E["description"], gr_title, len(etudid_etats))
|
hh = "%s, %s (%d étudiants)" % (E["description"], gr_title, len(etudid_etats))
|
||||||
filename = scu.make_filename("notes_%s_%s" % (evalname, gr_title_filename))
|
filename = scu.make_filename("notes_%s_%s" % (evalname, gr_title_filename))
|
||||||
|
|
||||||
if format == "bordereau":
|
if fmt == "bordereau":
|
||||||
hh = " %d étudiants" % (len(etudid_etats))
|
hh = " %d étudiants" % (len(etudid_etats))
|
||||||
hh += " %d absent" % (nb_abs)
|
hh += " %d absent" % (nb_abs)
|
||||||
if nb_abs > 1:
|
if nb_abs > 1:
|
||||||
@ -518,15 +520,13 @@ def _make_table_notes(
|
|||||||
hh += ", %d en attente." % (nb_att)
|
hh += ", %d en attente." % (nb_att)
|
||||||
|
|
||||||
# Attention: ReportLab supporte seulement '<br/>', pas '<br>' !
|
# Attention: ReportLab supporte seulement '<br/>', pas '<br>' !
|
||||||
pdf_title = "<br/> BORDEREAU DE SIGNATURES"
|
pdf_title = f"""<br/> BORDEREAU DE SIGNATURES
|
||||||
pdf_title += "<br/><br/>%(titre)s" % sem
|
<br/><br/>{formsemestre.titre or ''}
|
||||||
pdf_title += "<br/>(%(mois_debut)s - %(mois_fin)s)" % sem
|
<br/>({formsemestre.mois_debut()} - {formsemestre.mois_fin()})
|
||||||
pdf_title += " semestre %s %s" % (
|
semestre {formsemestre.semestre_id} {formsemestre.modalite or ""}
|
||||||
sem["semestre_id"],
|
<br/>Notes du module {module.code} - {module.titre}
|
||||||
sem.get("modalite", ""),
|
<br/>Évaluation : {e["description"]}
|
||||||
)
|
"""
|
||||||
pdf_title += f"<br/>Notes du module {module.code} - {module.titre}"
|
|
||||||
pdf_title += "<br/>Évaluation : %(description)s " % e
|
|
||||||
if len(e["jour"]) > 0:
|
if len(e["jour"]) > 0:
|
||||||
pdf_title += " (%(jour)s)" % e
|
pdf_title += " (%(jour)s)" % e
|
||||||
pdf_title += "(noté sur %(note_max)s )<br/><br/>" % e
|
pdf_title += "(noté sur %(note_max)s )<br/><br/>" % e
|
||||||
@ -551,12 +551,12 @@ def _make_table_notes(
|
|||||||
else:
|
else:
|
||||||
filename = scu.make_filename("notes_%s_%s" % (module.code, gr_title_filename))
|
filename = scu.make_filename("notes_%s_%s" % (module.code, gr_title_filename))
|
||||||
title = f"Notes {module.type_name()} {module.code} {module.titre}"
|
title = f"Notes {module.type_name()} {module.code} {module.titre}"
|
||||||
title += " semestre %(titremois)s" % sem
|
title += f""" semestre {formsemestre.titre_mois()}"""
|
||||||
if gr_title and gr_title != "tous":
|
if gr_title and gr_title != "tous":
|
||||||
title += " %s" % gr_title
|
title += " %s" % gr_title
|
||||||
caption = title
|
caption = title
|
||||||
html_next_section = ""
|
html_next_section = ""
|
||||||
if format == "pdf" or format == "bordereau":
|
if fmt == "pdf" or fmt == "bordereau":
|
||||||
caption = "" # same as pdf_title
|
caption = "" # same as pdf_title
|
||||||
pdf_title = title
|
pdf_title = title
|
||||||
html_title = f"""<h2 class="formsemestre">Notes {module.type_name()} <a href="{
|
html_title = f"""<h2 class="formsemestre">Notes {module.type_name()} <a href="{
|
||||||
@ -580,17 +580,17 @@ def _make_table_notes(
|
|||||||
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||||
caption=caption,
|
caption=caption,
|
||||||
html_next_section=html_next_section,
|
html_next_section=html_next_section,
|
||||||
page_title="Notes de " + sem["titremois"],
|
page_title="Notes de " + formsemestre.titre_mois(),
|
||||||
html_title=html_title,
|
html_title=html_title,
|
||||||
pdf_title=pdf_title,
|
pdf_title=pdf_title,
|
||||||
html_class="notes_evaluation",
|
html_class="notes_evaluation",
|
||||||
preferences=sco_preferences.SemPreferences(modimpl_o["formsemestre_id"]),
|
preferences=sco_preferences.SemPreferences(formsemestre.id),
|
||||||
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
|
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
|
||||||
)
|
)
|
||||||
if format == "bordereau":
|
if fmt == "bordereau":
|
||||||
format = "pdf"
|
fmt = "pdf"
|
||||||
t = tab.make_page(format=format, with_html_headers=False)
|
t = tab.make_page(format=fmt, with_html_headers=False)
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return t
|
return t
|
||||||
|
|
||||||
if len(evals) > 1:
|
if len(evals) > 1:
|
||||||
@ -599,7 +599,8 @@ def _make_table_notes(
|
|||||||
if not e["eval_state"]["evalcomplete"]:
|
if not e["eval_state"]["evalcomplete"]:
|
||||||
all_complete = False
|
all_complete = False
|
||||||
if all_complete:
|
if all_complete:
|
||||||
eval_info = '<span class="eval_info"><span class="eval_complete">Evaluations prises en compte dans les moyennes.</span>'
|
eval_info = """<span class="eval_info"><span class="eval_complete">Évaluations
|
||||||
|
prises en compte dans les moyennes.</span>"""
|
||||||
else:
|
else:
|
||||||
eval_info = """<span class="eval_info help">
|
eval_info = """<span class="eval_info help">
|
||||||
Les évaluations en vert et orange sont prises en compte dans les moyennes.
|
Les évaluations en vert et orange sont prises en compte dans les moyennes.
|
||||||
|
@ -367,7 +367,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
|
|
||||||
H.append("</td></tr></table>")
|
H.append("</td></tr></table>")
|
||||||
#
|
#
|
||||||
if not modimpl.check_apc_conformity():
|
if not modimpl.check_apc_conformity(nt):
|
||||||
H.append(
|
H.append(
|
||||||
"""<div class="warning conformite">Les poids des évaluations de ce module ne sont
|
"""<div class="warning conformite">Les poids des évaluations de ce module ne sont
|
||||||
pas encore conformes au PN.
|
pas encore conformes au PN.
|
||||||
|
Loading…
Reference in New Issue
Block a user