From 6c6d1103e68bae3d046e7d69b2b6f344c9dd70ae Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 26 Aug 2024 12:03:37 +0200 Subject: [PATCH] =?UTF-8?q?Am=C3=A9lioration=20evaluation=5Flistenotes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scodoc/TrivialFormulator.py | 6 + app/scodoc/sco_groups_view.py | 21 ++- app/scodoc/sco_liste_notes.py | 218 +++++++++++++++----------------- app/static/css/scodoc.css | 8 +- app/views/notes.py | 1 + 5 files changed, 130 insertions(+), 124 deletions(-) diff --git a/app/scodoc/TrivialFormulator.py b/app/scodoc/TrivialFormulator.py index b1525510..a2afc76b 100644 --- a/app/scodoc/TrivialFormulator.py +++ b/app/scodoc/TrivialFormulator.py @@ -47,6 +47,7 @@ def TrivialFormulator( title="", after_table="", before_table="{title}", + hidden_args: list[tuple] | None = None, ): """ form_url : URL for this form @@ -124,6 +125,7 @@ def TrivialFormulator( title=title, after_table=after_table, before_table=before_table, + hidden_args=hidden_args, ) form = t.getform() if t.canceled(): @@ -161,6 +163,7 @@ class TF(object): title="", after_table="", before_table="{title}", + hidden_args: list[tuple] | None = None, ): self.form_url = form_url self.values = values.copy() @@ -186,6 +189,7 @@ class TF(object): self.title = title self.after_table = after_table self.before_table = before_table + self.hidden_args = hidden_args or [] self.readonly = readonly self.result = None self.is_submitted = is_submitted @@ -470,6 +474,8 @@ class TF(object): }"/>""" ) R.append(f"""""") + for k, v in self.hidden_args: + R.append(f"""""") if self.top_buttons: R.append(buttons_markup + "

") R.append(self.before_table.format(title=self.title)) diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index 775e48ac..c2a478e2 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -42,7 +42,6 @@ from flask_login import current_user from app import db, log from app.models import FormSemestre, Identite, ScolarEvent 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_excel from app.scodoc import sco_formsemestre @@ -170,11 +169,10 @@ def form_groups_choice( with_deselect_butt=False, submit_on_change=False, default_deselect_others=True, + args: dict | None = None, + title="Groupes :", ): - """form pour selection groupes - 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) + """Formulaire complet pour choix de groupe. 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. @@ -182,7 +180,14 @@ def form_groups_choice( 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) - + args = args or {} + args_wdg = "\n".join( + [ + f"""""" + for k, v in args.items() + if k not in ("formsemestre_id", "group_ids") + ] + ) H = [ f"""
@@ -190,7 +195,8 @@ def form_groups_choice( value="{groups_infos.formsemestre_id}"/> - Groupes: + {args_wdg} + {title} { menu_groups_choice( groups_infos, @@ -480,6 +486,7 @@ class DisplayedGroupsInfos: return "\n".join(H) def get_formsemestre(self) -> FormSemestre: + "le formsemestre" return ( db.session.get(FormSemestre, self.formsemestre_id) if self.formsemestre_id diff --git a/app/scodoc/sco_liste_notes.py b/app/scodoc/sco_liste_notes.py index fda6e0d8..eb664725 100644 --- a/app/scodoc/sco_liste_notes.py +++ b/app/scodoc/sco_liste_notes.py @@ -43,17 +43,21 @@ from app.models import FormSemestre, Module from app.models.etudiants import Identite from app.models.evaluations import Evaluation 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 import sco_evaluations -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.sco_exceptions import ScoValueError from app.scodoc.gen_tables import GenTable from app.scodoc.htmlutils import histogram_notes +from app.scodoc.TrivialFormulator import TrivialFormulator +import app.scodoc.sco_utils as scu import sco_version @@ -82,18 +86,23 @@ def do_evaluation_listenotes( return "

Aucune évaluation !

", "ScoDoc" evaluation = evaluations[0] 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 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}" else: - H = [] 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: descr = [ @@ -109,37 +118,6 @@ def do_evaluation_listenotes( {"default": evaluation.id, "input_type": "hidden"}, ) ] - if len(grnams) > 1: - descr += [ - ( - "s", - { - "input_type": "separator", - "title": "Choix du ou des groupes d'étudiants:", - }, - ), - ( - "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 += [ ( "anonymous_listing", @@ -202,17 +180,16 @@ def do_evaluation_listenotes( request.base_url, scu.get_request_args(), descr, + hidden_args=[("group_ids", gid) for gid in group_ids], cancelbutton=None, submitbutton=None, bottom_buttons=False, method="GET", # consultation - cssclass="noprint", + cssclass="noprint tf-liste-notes", name="tf", is_submitted=True, # toujours "soumis" (démarre avec liste complète) ) - if tf[0] == 0: - return "\n".join(H) + "\n" + tf[1], page_title - elif tf[0] == -1: + if tf[0] == -1: return ( flask.redirect( url_for( @@ -223,44 +200,43 @@ def do_evaluation_listenotes( ), "", ) - else: - anonymous_listing = tf[2]["anonymous_listing"] - note_sur_20 = tf[2]["note_sur_20"] - hide_groups = tf[2]["hide_groups"] - split_groups = tf[2]["split_groups"] - with_emails = tf[2]["with_emails"] - group_ids = [x for x in tf[2]["group_ids"] if x != ""] - return ( - _make_table_notes( - tf[1], - evaluations, - fmt=fmt, - note_sur_20=note_sur_20, - anonymous_listing=anonymous_listing, - group_ids=group_ids, - hide_groups=hide_groups, - split_groups=split_groups, - with_emails=with_emails, - mode=mode, - ), - page_title, - ) + return ( + _make_table_notes( + tf[1], + evaluations, + fmt=fmt, + groups_infos=groups_infos, + args={ + "note_sur_20": tf[2]["note_sur_20"], + "anonymous_listing": tf[2]["anonymous_listing"], + "group_ids": group_ids, + "hide_groups": tf[2]["hide_groups"], + "split_groups": tf[2]["split_groups"], + "with_emails": tf[2]["with_emails"], + }, + mode=mode, + ), + page_title, + ) def _make_table_notes( html_form, evaluations: list[Evaluation], fmt: str = "", - note_sur_20=False, - anonymous_listing=False, - hide_groups=False, - split_groups=False, - with_emails=False, - group_ids: list[int] | None = None, + groups_infos: DisplayedGroupsInfos = None, + args: dict = None, mode="module", # "eval" or "module" ) -> str: - """Table liste notes (une seule évaluation ou toutes celles d'un module)""" - group_ids = group_ids or [] + """Table liste notes (une seule évaluation ou toutes celles d'un module) + 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: return "

Aucune évaluation !

" evaluation = evaluations[0] @@ -286,15 +262,25 @@ def _make_table_notes( keep_numeric = True # pas de conversion des notes en strings else: keep_numeric = False - # Si pas de groupe, affiche tout - if not group_ids: - group_ids = [sco_groups.get_default_group(formsemestre.id)] - groups = sco_groups.listgroups(group_ids) + # Menu groupes + group_selector = ( + sco_groups_view.form_groups_choice( + groups_infos, submit_on_change=True, args=args + ) + if groups_infos + else "" + ) - gr_title = sco_groups.listgroups_abbrev(groups) - gr_title_filename = sco_groups.listgroups_filename(groups) + # Si pas de groupe, affiche tout XXX TODO + # 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 else: if fmt in {"xls", "xml", "json"}: @@ -302,14 +288,15 @@ def _make_table_notes( else: columns_ids = ["nomprenom"] partitions = [] - if split_groups: - 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"}: - # n'indique pas les groupes en xml et json car notation "humaine" ici - columns_ids.append("group") + if not args["hide_groups"]: + if args["split_groups"]: + partitions = formsemestre.get_partitions_list( + with_default=False, only_listed=True + ) + columns_ids += [f"partition_{p.id}" for p in partitions] + elif fmt not in {"xml", "json"}: + # n'indique pas les groupes en xml et json car notation "humaine" ici + columns_ids.append("group") titles = { "code": "Code", @@ -456,13 +443,13 @@ def _make_table_notes( row_moys, is_apc, key_mgr, - note_sur_20, + args["note_sur_20"], keep_numeric, fmt=fmt, ) columns_ids.append(e.id) # - if anonymous_listing: + if args["anonymous_listing"]: rows.sort(key=lambda x: x["code"] or "") else: # sort by nom, prenom, sans accents @@ -504,7 +491,7 @@ def _make_table_notes( ) # Ajoute colonnes emails tout à droite: - if with_emails: + if args["with_emails"]: columns_ids += ["email", "emailperso"] # Ajoute lignes en tête et moyennes if len(evaluations) > 0 and fmt != "bordereau" and fmt != "json": @@ -539,17 +526,10 @@ def _make_table_notes( columns_ids.append("signatures") # titres divers: - gl = "".join(["&group_ids%3Alist=" + str(g) for g in group_ids]) - if note_sur_20: - gl = "¬e_sur_20%3Alist=yes" + gl - 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 + gl = "".join(["&group_ids=" + str(g) for g in groups_infos.group_ids]) + for k, v in args.items(): + gl += f"&{k}%3Alist=yes" if v else "" + if len(evaluations) == 1: evalname = f"""{module.code}-{ evaluation.date_debut.replace(tzinfo=None).isoformat() @@ -582,7 +562,8 @@ def _make_table_notes( len(etudid_etats), ) 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: 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 (sauf pour les moyennes des moyennes d'UE) ni les démissionnaires.""" eval_info += """""" - return html_form + eval_info + t + "

" + return group_selector + html_form + eval_info + t + "

" + # Une seule evaluation: ajoute histogramme histo = histogram_notes(notes) # 2 colonnes: histo, comments - C = [ - f"""
Bordereau de Signatures (version PDF) + section_basse_html = [ + f"""
Bordereau de Signatures (version PDF)

Répartition des notes:

@@ -689,15 +672,17 @@ def _make_table_notes( commentkeys = list(key_mgr.items()) # [ (comment, key), ... ] commentkeys.sort(key=lambda x: int(x[1])) for comment, key in commentkeys: - C.append(f"""({key}) {comment}
""") + section_basse_html.append( + f"""({key}) {comment}
""" + ) if commentkeys: - C.append( + section_basse_html.append( f"""Gérer les opérations
""" ) - eval_info = "xxx" + eval_info = "" if evals_state[evaluation.id]["evalcomplete"]: eval_info = 'Evaluation prise en compte dans les moyennes' elif evals_state[evaluation.id]["evalattente"]: @@ -708,9 +693,10 @@ def _make_table_notes( return ( sco_evaluations.evaluation_describe(evaluation_id=evaluation.id) + eval_info + + group_selector + html_form + t - + "\n".join(C) + + "\n".join(section_basse_html) ) diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index d38595a9..8c72b700 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -1536,6 +1536,10 @@ span.eval_title { padding-top: 24px; } +.tf-liste-notes td.tf-field { + max-width: var(--sco-content-max-width); +} + /* #saisie_notes span.eval_title { border-bottom: 1px solid rgb(100,100,100); } @@ -1581,8 +1585,10 @@ div.jury_footer>span { color: red; } -span.eval_info { +.eval_info { font-style: italic; + max-width: var(--sco-content-max-width); + margin: 16px 0 16px 0; } span.eval_complete { diff --git a/app/views/notes.py b/app/views/notes.py index c153e75e..5499641e 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -1787,6 +1787,7 @@ def evaluation_listenotes(): content=content, title=page_title, cssstyles=["css/verticalhisto.css"], + javascripts=["js/groups_view.js"], ) return content