Amélioration evaluation_listenotes

This commit is contained in:
Emmanuel Viennet 2024-08-26 12:03:37 +02:00
parent 0c3216e432
commit 6c6d1103e6
5 changed files with 130 additions and 124 deletions

View File

@ -47,6 +47,7 @@ def TrivialFormulator(
title="", title="",
after_table="", after_table="",
before_table="{title}", before_table="{title}",
hidden_args: list[tuple] | None = None,
): ):
""" """
form_url : URL for this form form_url : URL for this form
@ -124,6 +125,7 @@ def TrivialFormulator(
title=title, title=title,
after_table=after_table, after_table=after_table,
before_table=before_table, before_table=before_table,
hidden_args=hidden_args,
) )
form = t.getform() form = t.getform()
if t.canceled(): if t.canceled():
@ -161,6 +163,7 @@ class TF(object):
title="", title="",
after_table="", after_table="",
before_table="{title}", before_table="{title}",
hidden_args: list[tuple] | None = None,
): ):
self.form_url = form_url self.form_url = form_url
self.values = values.copy() self.values = values.copy()
@ -186,6 +189,7 @@ class TF(object):
self.title = title self.title = title
self.after_table = after_table self.after_table = after_table
self.before_table = before_table self.before_table = before_table
self.hidden_args = hidden_args or []
self.readonly = readonly self.readonly = readonly
self.result = None self.result = None
self.is_submitted = is_submitted self.is_submitted = is_submitted
@ -470,6 +474,8 @@ class TF(object):
}"/>""" }"/>"""
) )
R.append(f"""<input type="hidden" name="{self.formid}_submitted" value="1"/>""") R.append(f"""<input type="hidden" name="{self.formid}_submitted" value="1"/>""")
for k, v in self.hidden_args:
R.append(f"""<input type="hidden" name="{k}" value="{v}"/>""")
if self.top_buttons: if self.top_buttons:
R.append(buttons_markup + "<p></p>") R.append(buttons_markup + "<p></p>")
R.append(self.before_table.format(title=self.title)) R.append(self.before_table.format(title=self.title))

View File

@ -42,7 +42,6 @@ from flask_login import current_user
from app import db, log from app import db, log
from app.models import FormSemestre, Identite, ScolarEvent from app.models import FormSemestre, Identite, ScolarEvent
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import html_sco_header
from app.scodoc import sco_assiduites as scass from app.scodoc import sco_assiduites as scass
from app.scodoc import sco_excel from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
@ -170,11 +169,10 @@ def form_groups_choice(
with_deselect_butt=False, with_deselect_butt=False,
submit_on_change=False, submit_on_change=False,
default_deselect_others=True, default_deselect_others=True,
args: dict | None = None,
title="Groupes&nbsp;:",
): ):
"""form pour selection groupes """Formulaire complet pour choix de groupe.
group_ids est la liste des groupes actuellement sélectionnés
et doit comporter au moins un élément, sauf si formsemestre_id est spécifié.
(utilisé pour retrouver le semestre et proposer la liste des autres groupes)
Si submit_on_change, soumet (recharge la page) à chaque modif. Si submit_on_change, soumet (recharge la page) à chaque modif.
Si default_deselect_others, désélectionne le groupe "Tous" quand on sélectionne un autre groupe. Si default_deselect_others, désélectionne le groupe "Tous" quand on sélectionne un autre groupe.
@ -182,7 +180,14 @@ def form_groups_choice(
Ces deux options ajoutent des classes utilisées en JS pour la gestion du formulaire. Ces deux options ajoutent des classes utilisées en JS pour la gestion du formulaire.
""" """
default_group_id = sco_groups.get_default_group(groups_infos.formsemestre_id) default_group_id = sco_groups.get_default_group(groups_infos.formsemestre_id)
args = args or {}
args_wdg = "\n".join(
[
f"""<input type="hidden" name="{k}" value="{v}"/>"""
for k, v in args.items()
if k not in ("formsemestre_id", "group_ids")
]
)
H = [ H = [
f""" f"""
<form id="group_selector" method="get"> <form id="group_selector" method="get">
@ -190,7 +195,8 @@ def form_groups_choice(
value="{groups_infos.formsemestre_id}"/> value="{groups_infos.formsemestre_id}"/>
<input type="hidden" name="default_group_id" id="default_group_id" <input type="hidden" name="default_group_id" id="default_group_id"
value="{default_group_id}"/> value="{default_group_id}"/>
Groupes: {args_wdg}
{title}
{ {
menu_groups_choice( menu_groups_choice(
groups_infos, groups_infos,
@ -480,6 +486,7 @@ class DisplayedGroupsInfos:
return "\n".join(H) return "\n".join(H)
def get_formsemestre(self) -> FormSemestre: def get_formsemestre(self) -> FormSemestre:
"le formsemestre"
return ( return (
db.session.get(FormSemestre, self.formsemestre_id) db.session.get(FormSemestre, self.formsemestre_id)
if self.formsemestre_id if self.formsemestre_id

View File

@ -43,17 +43,21 @@ 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
from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc import (
sco_evaluations,
sco_evaluation_db,
sco_groups,
sco_groups_view,
sco_preferences,
sco_users,
)
from app.scodoc.sco_groups_view import DisplayedGroupsInfos
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.sco_exceptions import ScoValueError
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_groups
from app.scodoc import sco_preferences
from app.scodoc import sco_users
import app.scodoc.sco_utils as scu
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.htmlutils import histogram_notes from app.scodoc.htmlutils import histogram_notes
from app.scodoc.TrivialFormulator import TrivialFormulator
import app.scodoc.sco_utils as scu
import sco_version import sco_version
@ -82,18 +86,23 @@ def do_evaluation_listenotes(
return "<p>Aucune évaluation !</p>", "ScoDoc" return "<p>Aucune évaluation !</p>", "ScoDoc"
evaluation = evaluations[0] evaluation = evaluations[0]
modimpl = evaluation.moduleimpl # il y a au moins une evaluation modimpl = evaluation.moduleimpl # il y a au moins une evaluation
# Argument groupes:
group_ids = request.args.getlist("group_ids")
try:
group_ids = [int(gid) for gid in group_ids]
except ValueError as exc:
raise ScoValueError("group_ids invalide") from exc
groups_infos = DisplayedGroupsInfos(
group_ids,
formsemestre_id=modimpl.formsemestre.id,
select_all_when_unspecified=True,
)
# description de l'évaluation # description de l'évaluation
if evaluation_id is not None: if evaluation_id is not None:
H = [sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)]
page_title = f"Notes {evaluation.description or modimpl.module.code}" page_title = f"Notes {evaluation.description or modimpl.module.code}"
else: else:
H = []
page_title = f"Notes {modimpl.module.code}" page_title = f"Notes {modimpl.module.code}"
# groupes
groups = sco_groups.do_evaluation_listegroupes(evaluation.id, include_default=True)
grlabs = [g["group_name"] or "tous" for g in groups] # legendes des boutons
grnams = [str(g["group_id"]) for g in groups] # noms des checkbox
if len(evaluations) > 1: if len(evaluations) > 1:
descr = [ descr = [
@ -109,37 +118,6 @@ def do_evaluation_listenotes(
{"default": evaluation.id, "input_type": "hidden"}, {"default": evaluation.id, "input_type": "hidden"},
) )
] ]
if len(grnams) > 1:
descr += [
(
"s",
{
"input_type": "separator",
"title": "<b>Choix du ou des groupes d'étudiants:</b>",
},
),
(
"group_ids",
{
"input_type": "checkbox",
"title": "",
"allowed_values": grnams,
"labels": grlabs,
"attributes": ('onclick="document.tf.submit();"',),
},
),
]
else:
if grnams:
def_nam = grnams[0]
else:
def_nam = ""
descr += [
(
"group_ids",
{"input_type": "hidden", "type": "list", "default": [def_nam]},
)
]
descr += [ descr += [
( (
"anonymous_listing", "anonymous_listing",
@ -202,17 +180,16 @@ def do_evaluation_listenotes(
request.base_url, request.base_url,
scu.get_request_args(), scu.get_request_args(),
descr, descr,
hidden_args=[("group_ids", gid) for gid in group_ids],
cancelbutton=None, cancelbutton=None,
submitbutton=None, submitbutton=None,
bottom_buttons=False, bottom_buttons=False,
method="GET", # consultation method="GET", # consultation
cssclass="noprint", cssclass="noprint tf-liste-notes",
name="tf", name="tf",
is_submitted=True, # toujours "soumis" (démarre avec liste complète) is_submitted=True, # toujours "soumis" (démarre avec liste complète)
) )
if tf[0] == 0: if tf[0] == -1:
return "\n".join(H) + "\n" + tf[1], page_title
elif tf[0] == -1:
return ( return (
flask.redirect( flask.redirect(
url_for( url_for(
@ -223,44 +200,43 @@ def do_evaluation_listenotes(
), ),
"", "",
) )
else: return (
anonymous_listing = tf[2]["anonymous_listing"] _make_table_notes(
note_sur_20 = tf[2]["note_sur_20"] tf[1],
hide_groups = tf[2]["hide_groups"] evaluations,
split_groups = tf[2]["split_groups"] fmt=fmt,
with_emails = tf[2]["with_emails"] groups_infos=groups_infos,
group_ids = [x for x in tf[2]["group_ids"] if x != ""] args={
return ( "note_sur_20": tf[2]["note_sur_20"],
_make_table_notes( "anonymous_listing": tf[2]["anonymous_listing"],
tf[1], "group_ids": group_ids,
evaluations, "hide_groups": tf[2]["hide_groups"],
fmt=fmt, "split_groups": tf[2]["split_groups"],
note_sur_20=note_sur_20, "with_emails": tf[2]["with_emails"],
anonymous_listing=anonymous_listing, },
group_ids=group_ids, mode=mode,
hide_groups=hide_groups, ),
split_groups=split_groups, page_title,
with_emails=with_emails, )
mode=mode,
),
page_title,
)
def _make_table_notes( def _make_table_notes(
html_form, html_form,
evaluations: list[Evaluation], evaluations: list[Evaluation],
fmt: str = "", fmt: str = "",
note_sur_20=False, groups_infos: DisplayedGroupsInfos = None,
anonymous_listing=False, args: dict = None,
hide_groups=False,
split_groups=False,
with_emails=False,
group_ids: list[int] | None = None,
mode="module", # "eval" or "module" mode="module", # "eval" or "module"
) -> str: ) -> 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)
group_ids = group_ids or [] args doit contenir:
note_sur_20=False,
anonymous_listing=False,
hide_groups=False,
split_groups=False,
with_emails=False
"""
args = args or {}
if not evaluations: if not evaluations:
return "<p>Aucune évaluation !</p>" return "<p>Aucune évaluation !</p>"
evaluation = evaluations[0] evaluation = evaluations[0]
@ -286,15 +262,25 @@ def _make_table_notes(
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 # Menu groupes
if not group_ids: group_selector = (
group_ids = [sco_groups.get_default_group(formsemestre.id)] sco_groups_view.form_groups_choice(
groups = sco_groups.listgroups(group_ids) groups_infos, submit_on_change=True, args=args
)
if groups_infos
else ""
)
gr_title = sco_groups.listgroups_abbrev(groups) # Si pas de groupe, affiche tout XXX TODO
gr_title_filename = sco_groups.listgroups_filename(groups) # if not groups_infos.group_ids:
# groups_infos.group_ids = [sco_groups.get_default_group(formsemestre.id)]
if anonymous_listing: groups = sco_groups.listgroups(groups_infos.group_ids)
gr_title = groups_infos.groups_titles
gr_title_filename = groups_infos.groups_filename
if args["anonymous_listing"]:
columns_ids = ["code"] # cols in table columns_ids = ["code"] # cols in table
else: else:
if fmt in {"xls", "xml", "json"}: if fmt in {"xls", "xml", "json"}:
@ -302,14 +288,15 @@ def _make_table_notes(
else: else:
columns_ids = ["nomprenom"] columns_ids = ["nomprenom"]
partitions = [] partitions = []
if split_groups: if not args["hide_groups"]:
partitions = formsemestre.get_partitions_list( if args["split_groups"]:
with_default=False, only_listed=True partitions = formsemestre.get_partitions_list(
) with_default=False, only_listed=True
columns_ids += [f"partition_{p.id}" for p in partitions] )
elif not hide_groups and fmt not in {"xml", "json"}: columns_ids += [f"partition_{p.id}" for p in partitions]
# n'indique pas les groupes en xml et json car notation "humaine" ici elif fmt not in {"xml", "json"}:
columns_ids.append("group") # n'indique pas les groupes en xml et json car notation "humaine" ici
columns_ids.append("group")
titles = { titles = {
"code": "Code", "code": "Code",
@ -456,13 +443,13 @@ def _make_table_notes(
row_moys, row_moys,
is_apc, is_apc,
key_mgr, key_mgr,
note_sur_20, args["note_sur_20"],
keep_numeric, keep_numeric,
fmt=fmt, fmt=fmt,
) )
columns_ids.append(e.id) columns_ids.append(e.id)
# #
if anonymous_listing: if args["anonymous_listing"]:
rows.sort(key=lambda x: x["code"] or "") rows.sort(key=lambda x: x["code"] or "")
else: else:
# sort by nom, prenom, sans accents # sort by nom, prenom, sans accents
@ -504,7 +491,7 @@ def _make_table_notes(
) )
# Ajoute colonnes emails tout à droite: # Ajoute colonnes emails tout à droite:
if with_emails: if args["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(evaluations) > 0 and fmt != "bordereau" and fmt != "json": if len(evaluations) > 0 and fmt != "bordereau" and fmt != "json":
@ -539,17 +526,10 @@ def _make_table_notes(
columns_ids.append("signatures") columns_ids.append("signatures")
# titres divers: # titres divers:
gl = "".join(["&group_ids%3Alist=" + str(g) for g in group_ids]) gl = "".join(["&group_ids=" + str(g) for g in groups_infos.group_ids])
if note_sur_20: for k, v in args.items():
gl = "&note_sur_20%3Alist=yes" + gl gl += f"&{k}%3Alist=yes" if v else ""
if anonymous_listing:
gl = "&anonymous_listing%3Alist=yes" + gl
if hide_groups:
gl = "&hide_groups%3Alist=yes" + gl
if split_groups:
gl = "&split_groups%3Alist=yes" + gl
if with_emails:
gl = "&with_emails%3Alist=yes" + gl
if len(evaluations) == 1: if len(evaluations) == 1:
evalname = f"""{module.code}-{ evalname = f"""{module.code}-{
evaluation.date_debut.replace(tzinfo=None).isoformat() evaluation.date_debut.replace(tzinfo=None).isoformat()
@ -582,7 +562,8 @@ def _make_table_notes(
len(etudid_etats), len(etudid_etats),
) )
if evaluation.date_debut: if evaluation.date_debut:
pdf_title = f"{evaluation.description} ({evaluation.date_debut.strftime('%d/%m/%Y')})" pdf_title = f"""{evaluation.description} ({
evaluation.date_debut.strftime('%d/%m/%Y')})"""
else: else:
pdf_title = evaluation.description or f"évaluation dans {module.code}" pdf_title = evaluation.description or f"évaluation dans {module.code}"
@ -671,12 +652,14 @@ def _make_table_notes(
Les moyennes sur le groupe sont estimées sans les absents Les moyennes sur le groupe sont estimées sans les absents
(sauf pour les moyennes des moyennes d'UE) ni les démissionnaires.</span>""" (sauf pour les moyennes des moyennes d'UE) ni les démissionnaires.</span>"""
eval_info += """</span>""" eval_info += """</span>"""
return html_form + eval_info + t + "<p></p>" return group_selector + html_form + eval_info + t + "<p></p>"
# Une seule evaluation: ajoute histogramme # Une seule evaluation: ajoute histogramme
histo = histogram_notes(notes) histo = histogram_notes(notes)
# 2 colonnes: histo, comments # 2 colonnes: histo, comments
C = [ section_basse_html = [
f"""<br><a class="stdlink" href="{base_url}&fmt=bordereau">Bordereau de Signatures (version PDF)</a> f"""<br><a class="stdlink" href="{base_url}&fmt=bordereau"
>Bordereau de Signatures (version PDF)</a>
<table> <table>
<tr><td> <tr><td>
<div><h4>Répartition des notes:</h4> <div><h4>Répartition des notes:</h4>
@ -689,15 +672,17 @@ def _make_table_notes(
commentkeys = list(key_mgr.items()) # [ (comment, key), ... ] commentkeys = list(key_mgr.items()) # [ (comment, key), ... ]
commentkeys.sort(key=lambda x: int(x[1])) commentkeys.sort(key=lambda x: int(x[1]))
for comment, key in commentkeys: for comment, key in commentkeys:
C.append(f"""<span class="colcomment">({key})</span> <em>{comment}</em><br>""") section_basse_html.append(
f"""<span class="colcomment">({key})</span> <em>{comment}</em><br>"""
)
if commentkeys: if commentkeys:
C.append( section_basse_html.append(
f"""<span><a class=stdlink" href="{ url_for( f"""<span><a class=stdlink" href="{ url_for(
'notes.evaluation_list_operations', scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id ) 'notes.evaluation_list_operations', scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id )
}">Gérer les opérations</a></span><br> }">Gérer les opérations</a></span><br>
""" """
) )
eval_info = "xxx" eval_info = ""
if evals_state[evaluation.id]["evalcomplete"]: if evals_state[evaluation.id]["evalcomplete"]:
eval_info = '<span class="eval_info eval_complete">Evaluation prise en compte dans les moyennes</span>' eval_info = '<span class="eval_info eval_complete">Evaluation prise en compte dans les moyennes</span>'
elif evals_state[evaluation.id]["evalattente"]: elif evals_state[evaluation.id]["evalattente"]:
@ -708,9 +693,10 @@ def _make_table_notes(
return ( return (
sco_evaluations.evaluation_describe(evaluation_id=evaluation.id) sco_evaluations.evaluation_describe(evaluation_id=evaluation.id)
+ eval_info + eval_info
+ group_selector
+ html_form + html_form
+ t + t
+ "\n".join(C) + "\n".join(section_basse_html)
) )

View File

@ -1536,6 +1536,10 @@ span.eval_title {
padding-top: 24px; padding-top: 24px;
} }
.tf-liste-notes td.tf-field {
max-width: var(--sco-content-max-width);
}
/* #saisie_notes span.eval_title { /* #saisie_notes span.eval_title {
border-bottom: 1px solid rgb(100,100,100); border-bottom: 1px solid rgb(100,100,100);
} }
@ -1581,8 +1585,10 @@ div.jury_footer>span {
color: red; color: red;
} }
span.eval_info { .eval_info {
font-style: italic; font-style: italic;
max-width: var(--sco-content-max-width);
margin: 16px 0 16px 0;
} }
span.eval_complete { span.eval_complete {

View File

@ -1787,6 +1787,7 @@ def evaluation_listenotes():
content=content, content=content,
title=page_title, title=page_title,
cssstyles=["css/verticalhisto.css"], cssstyles=["css/verticalhisto.css"],
javascripts=["js/groups_view.js"],
) )
return content return content