From 6c044dd4dd313e1b1b8bc9c347192c4be8ab968e Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet <emmanuel.viennet@gmail.com> Date: Sun, 25 Aug 2024 07:23:36 +0200 Subject: [PATCH] Adaptation multi-select groupes --- app/scodoc/TrivialFormulator.py | 5 + app/scodoc/sco_archives_formsemestre.py | 2 +- app/scodoc/sco_evaluations.py | 4 +- app/scodoc/sco_moduleimpl_status.py | 12 +-- app/scodoc/sco_pv_forms.py | 119 ++++++++++++++---------- app/scodoc/sco_report.py | 36 +++++-- app/scodoc/sco_saisie_excel.py | 54 +++++++---- app/scodoc/sco_saisie_notes.py | 49 ++++++---- app/scodoc/sco_trombino_doc.py | 7 +- app/scodoc/sco_utils.py | 9 +- app/static/css/scodoc.css | 4 + app/static/css/scodoc97.css | 9 +- app/views/notes.py | 19 +++- 13 files changed, 216 insertions(+), 113 deletions(-) diff --git a/app/scodoc/TrivialFormulator.py b/app/scodoc/TrivialFormulator.py index d9bd37d24..b15255105 100644 --- a/app/scodoc/TrivialFormulator.py +++ b/app/scodoc/TrivialFormulator.py @@ -40,6 +40,7 @@ def TrivialFormulator( submitbuttonattributes=None, top_buttons=False, # place buttons at top of form bottom_buttons=True, # buttons after form + html_head_markup="", html_foot_markup="", readonly=False, is_submitted=False, @@ -116,6 +117,7 @@ def TrivialFormulator( submitbuttonattributes=submitbuttonattributes or [], top_buttons=top_buttons, bottom_buttons=bottom_buttons, + html_head_markup=html_head_markup, html_foot_markup=html_foot_markup, readonly=readonly, is_submitted=is_submitted, @@ -152,6 +154,7 @@ class TF(object): submitbuttonattributes=None, top_buttons=False, # place buttons at top of form bottom_buttons=True, # buttons after form + html_head_markup="", # html snippet put at the beginning, just before the table html_foot_markup="", # html snippet put at the end, just after the table readonly=False, is_submitted=False, @@ -178,6 +181,7 @@ class TF(object): self.submitbuttonattributes = submitbuttonattributes or [] self.top_buttons = top_buttons self.bottom_buttons = bottom_buttons + self.html_head_markup = html_head_markup self.html_foot_markup = html_foot_markup self.title = title self.after_table = after_table @@ -469,6 +473,7 @@ class TF(object): if self.top_buttons: R.append(buttons_markup + "<p></p>") R.append(self.before_table.format(title=self.title)) + R.append(self.html_head_markup) R.append('<table class="tf">') for field, descr in self.formdescription: if descr.get("readonly", False): diff --git a/app/scodoc/sco_archives_formsemestre.py b/app/scodoc/sco_archives_formsemestre.py index 561d0b6cf..31548bcd6 100644 --- a/app/scodoc/sco_archives_formsemestre.py +++ b/app/scodoc/sco_archives_formsemestre.py @@ -289,7 +289,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement. ] menu_choix_groupe = ( """<div class="group_ids_sel_menu">Groupes d'étudiants à lister: """ - + sco_groups_view.menu_groups_choice(groups_infos) + + sco_groups_view.menu_groups_choice(groups_infos, submit_on_change=True) + """(pour les PV et lettres)</div>""" ) diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index 400729705..9c5fad6d7 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -732,7 +732,9 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True) H.append( f""" <a style="margin-left: 12px;" class="stdlink" href="{url_for( - "notes.saisie_notes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation_id) + "notes.form_saisie_notes", + scodoc_dept=g.scodoc_dept, + evaluation_id=evaluation_id) }">saisie des notes</a> """ ) diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index 139081f19..ed1e256d2 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -72,7 +72,7 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str: menu_eval = [ { "title": "Saisir les notes", - "endpoint": "notes.saisie_notes", + "endpoint": "notes.form_saisie_notes", "args": { "evaluation_id": evaluation_id, }, @@ -745,7 +745,7 @@ def _ligne_evaluation( ) if can_edit_notes: H.append( - f"""<a class="smallbutton" href="{url_for('notes.saisie_notes', + f"""<a class="smallbutton" href="{url_for('notes.form_saisie_notes', scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id) }">{scu.icontag("notes_img", alt="saisie notes", title="Saisie des notes")}</a>""" ) @@ -824,7 +824,7 @@ def _ligne_evaluation( ) else: H.append( - f"""<a class="redlink" href="{url_for('notes.saisie_notes', + f"""<a class="redlink" href="{url_for('notes.form_saisie_notes', scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id) }">saisir notes</a> """ @@ -880,7 +880,7 @@ def _ligne_evaluation( H.append("""[<font color="red">""") if can_edit_notes: H.append( - f"""<a class="redlink" href="{url_for('notes.saisie_notes', + f"""<a class="redlink" href="{url_for('notes.form_saisie_notes', scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id, **{'group_ids:list': gr_moyenne["group_id"]}) }">incomplet : terminer saisie</a></font>]""" @@ -891,9 +891,9 @@ def _ligne_evaluation( H.append("""<span class="redboldtext"> """) if can_edit_notes: H.append( - f"""<a class="redlink" href="{url_for('notes.saisie_notes', + f"""<a class="redlink" href="{url_for('notes.form_saisie_notes', scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id, - **{'group_ids:list': gr_moyenne["group_id"]}) + **{'group_ids': gr_moyenne["group_id"]}) }">""" ) H.append("pas de notes") diff --git a/app/scodoc/sco_pv_forms.py b/app/scodoc/sco_pv_forms.py index 02521cacf..ce8e97f99 100644 --- a/app/scodoc/sco_pv_forms.py +++ b/app/scodoc/sco_pv_forms.py @@ -50,6 +50,7 @@ from app.scodoc import sco_pdf from app.scodoc import sco_preferences from app.scodoc import sco_pv_pdf from app.scodoc import sco_pv_lettres_inviduelles +from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.gen_tables import GenTable from app.scodoc.codes_cursus import NO_SEMESTRE_ID from app.scodoc.sco_pdf import PDFLOCK @@ -336,12 +337,20 @@ def formsemestre_pvjury(formsemestre_id, fmt="html", publish=True): # --------------------------------------------------------------------------- -def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid=None): - """Generation PV jury en PDF: saisie des paramètres - Si etudid, PV pour un seul etudiant. Sinon, tout les inscrits au groupe indiqué. +def formsemestre_pvjury_pdf(formsemestre_id, etudid=None): + """Génération PV jury en PDF: saisie des paramètres + Si etudid, PV pour un seul etudiant. + Sinon, tout les inscrits au(x) groupe(s) indiqué(s). """ - group_ids = group_ids or [] - formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) + if request.method == "POST": + group_ids = request.form.getlist("group_ids") + else: + 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 + formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id) # Mise à jour des groupes d'étapes: sco_groups.create_etapes_partition(formsemestre_id) groups_infos = None @@ -361,7 +370,8 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid etudids = [m["etudid"] for m in groups_infos.members] H = [ - f"""<div class="help">Utiliser cette page pour éditer des versions provisoires des PV. + f"""<div class="help space-after-24"> + Utiliser cette page pour éditer des versions provisoires des PV. <span class="fontred">Il est recommandé d'archiver les versions définitives: <a class="stdlink" href="{url_for( 'notes.formsemestre_archive', @@ -381,7 +391,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid if groups_infos: menu_choix_groupe = ( """<div class="group_ids_sel_menu">Groupes d'étudiants à lister sur le PV: """ - + sco_groups_view.menu_groups_choice(groups_infos) + + sco_groups_view.menu_groups_choice(groups_infos, submit_on_change=True) + """</div>""" ) else: @@ -394,24 +404,28 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid submitlabel="Générer document", name="tf", formid="group_selector", - html_foot_markup=menu_choix_groupe, + html_head_markup=menu_choix_groupe, ) if tf[0] == 0: + info_etud = ( + f"""de <a class="discretelink" href="{ + url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid) + }">{etud.nomprenom}</a>""" + if etud + else "" + ) return render_template( "sco_page.j2", title=f"Édition du PV de jury {('de ' + etud.nom_prenom()) if etud else ''}", content=f"""<h2 class="formsemestre">Édition du PV de jury - de <a class="discretelink" href="{ - url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid) - }">{etud.nomprenom}</a></h2>""" + {info_etud}</h2>""" + "\n".join(H) + "\n" + tf[1] + "\n".join(F), - javascripts=sco_groups_view.JAVASCRIPTS, - cssstyles=sco_groups_view.CSSSTYLES, + javascripts=["js/groups_view.js"], ) - elif tf[0] == -1: + if tf[0] == -1: return flask.redirect( url_for( "notes.formsemestre_pvjury", @@ -419,34 +433,34 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid formsemestre_id=formsemestre_id, ) ) + + # submit + tf[2]["show_title"] = bool(tf[2]["show_title"]) + tf[2]["anonymous"] = bool(tf[2]["anonymous"]) + try: + PDFLOCK.acquire() + pdfdoc = sco_pv_pdf.pvjury_pdf( + formsemestre, + etudids, + numero_arrete=tf[2]["numero_arrete"], + code_vdi=tf[2]["code_vdi"], + date_commission=tf[2]["date_commission"], + date_jury=tf[2]["date_jury"], + show_title=tf[2]["show_title"], + pv_title_session=tf[2]["pv_title_session"], + pv_title=tf[2]["pv_title"], + with_paragraph_nom=tf[2]["with_paragraph_nom"], + anonymous=tf[2]["anonymous"], + ) + finally: + PDFLOCK.release() + date_iso = time.strftime("%Y-%m-%d") + if groups_infos: + groups_filename = "-" + groups_infos.groups_filename else: - # submit - tf[2]["show_title"] = bool(tf[2]["show_title"]) - tf[2]["anonymous"] = bool(tf[2]["anonymous"]) - try: - PDFLOCK.acquire() - pdfdoc = sco_pv_pdf.pvjury_pdf( - formsemestre, - etudids, - numero_arrete=tf[2]["numero_arrete"], - code_vdi=tf[2]["code_vdi"], - date_commission=tf[2]["date_commission"], - date_jury=tf[2]["date_jury"], - show_title=tf[2]["show_title"], - pv_title_session=tf[2]["pv_title_session"], - pv_title=tf[2]["pv_title"], - with_paragraph_nom=tf[2]["with_paragraph_nom"], - anonymous=tf[2]["anonymous"], - ) - finally: - PDFLOCK.release() - date_iso = time.strftime("%Y-%m-%d") - if groups_infos: - groups_filename = "-" + groups_infos.groups_filename - else: - groups_filename = "" - filename = f"""PV-{formsemestre.titre_num()}{groups_filename}-{date_iso}.pdf""" - return scu.sendPDFFile(pdfdoc, filename) + groups_filename = "" + filename = f"""PV-{formsemestre.titre_num()}{groups_filename}-{date_iso}.pdf""" + return scu.sendPDFFile(pdfdoc, filename) def descrform_pvjury(formsemestre: FormSemestre): @@ -542,9 +556,17 @@ def descrform_pvjury(formsemestre: FormSemestre): ] -def formsemestre_lettres_individuelles(formsemestre_id, group_ids=()): +def formsemestre_lettres_individuelles(formsemestre_id): "Lettres avis jury en PDF" formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) + if request.method == "POST": + group_ids = request.form.getlist("group_ids") + else: + 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 if not group_ids: # tous les inscrits du semestre group_ids = [sco_groups.get_default_group(formsemestre_id)] @@ -556,20 +578,22 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=()): H = [ f""" <h2 class="formsemestre">Édition des lettres individuelles</h2> - <p class="help">Utiliser cette page pour éditer des versions provisoires des PV. + <div class="help space-after-24"> + Utiliser cette page pour éditer des versions provisoires des PV. <span class="fontred">Il est recommandé d'archiver les versions définitives: <a href="{url_for( "notes.formsemestre_archive", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, )}" - >voir cette page</a></span></p> + >voir cette page</a></span> + </div> """, ] descr = descrform_lettres_individuelles() menu_choix_groupe = ( """<div class="group_ids_sel_menu">Groupes d'étudiants à lister: """ - + sco_groups_view.menu_groups_choice(groups_infos) + + sco_groups_view.menu_groups_choice(groups_infos, submit_on_change=True) + """</div>""" ) @@ -581,15 +605,14 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=()): submitlabel="Générer document", name="tf", formid="group_selector", - html_foot_markup=menu_choix_groupe, + html_head_markup=menu_choix_groupe, ) if tf[0] == 0: return render_template( "sco_page.j2", title="Édition des lettres individuelles", content="\n".join(H) + "\n" + tf[1], - javascripts=sco_groups_view.JAVASCRIPTS, - cssstyles=sco_groups_view.CSSSTYLES, + javascripts=["js/groups_view.js"], ) elif tf[0] == -1: return flask.redirect( diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py index 0984c8c3a..f234786ff 100644 --- a/app/scodoc/sco_report.py +++ b/app/scodoc/sco_report.py @@ -296,6 +296,14 @@ def formsemestre_report_counts( sinon liste prédéfinie (voir ci-dessous) """ formsemestre = FormSemestre.get_formsemestre(formsemestre_id) + if request.method == "POST": + group_ids = request.form.getlist("group_ids") + else: + 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 = sco_groups_view.DisplayedGroupsInfos( group_ids, formsemestre_id=formsemestre.id, @@ -420,8 +428,7 @@ def formsemestre_report_counts( ] return render_template( "sco_page.j2", - cssstyles=sco_groups_view.CSSSTYLES, - javascripts=sco_groups_view.JAVASCRIPTS, + javascripts=["js/groups_view.js"], title=title, content="\n".join(H), ) @@ -740,7 +747,6 @@ def table_suivi_cohorte( def formsemestre_suivi_cohorte( formsemestre_id, fmt="html", - group_ids: list[int] = None, # si indiqué, ne prend que ces groupes percent=1, bac="", bacspecialite="", @@ -759,6 +765,14 @@ def formsemestre_suivi_cohorte( raise ScoValueError("formsemestre_suivi_cohorte: argument invalide") from exc formsemestre = FormSemestre.get_formsemestre(formsemestre_id) + if request.method == "POST": + group_ids = request.form.getlist("group_ids") + else: + 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 = sco_groups_view.DisplayedGroupsInfos( group_ids, formsemestre_id=formsemestre.id, @@ -850,8 +864,7 @@ def formsemestre_suivi_cohorte( ] return render_template( "sco_page.j2", - cssstyles=sco_groups_view.CSSSTYLES, - javascripts=sco_groups_view.JAVASCRIPTS, + javascripts=["js/groups_view.js"], title=tab.page_title, content="\n".join(H), ) @@ -1629,7 +1642,6 @@ def graph_cursus( def formsemestre_graph_cursus( formsemestre_id, - group_ids: list[int] = None, # si indiqué, ne prend que ces groupes fmt="html", only_primo=False, bac="", # selection sur type de bac @@ -1644,6 +1656,15 @@ def formsemestre_graph_cursus( annee_bac = str(annee_bac or "") annee_admission = str(annee_admission or "") formsemestre = FormSemestre.get_formsemestre(formsemestre_id) + if request.method == "POST": + group_ids = request.form.getlist("group_ids") + else: + 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 = sco_groups_view.DisplayedGroupsInfos( group_ids, formsemestre_id=formsemestre.id, @@ -1781,8 +1802,7 @@ def formsemestre_graph_cursus( ] return render_template( "sco_page.j2", - cssstyles=sco_groups_view.CSSSTYLES, - javascripts=sco_groups_view.JAVASCRIPTS, + javascripts=["js/groups_view.js"], page_title=f"Graphe cursus de {sem['titreannee']}", no_sidebar=True, content="\n".join(H), diff --git a/app/scodoc/sco_saisie_excel.py b/app/scodoc/sco_saisie_excel.py index 6aa6a86f9..f46d4a6dc 100644 --- a/app/scodoc/sco_saisie_excel.py +++ b/app/scodoc/sco_saisie_excel.py @@ -68,7 +68,9 @@ from app.views import ScoData FONT_NAME = "Arial" -def excel_feuille_saisie(evaluation: "Evaluation", rows: list[dict]) -> AnyStr: +def excel_feuille_saisie( + evaluation: "Evaluation", rows: list[dict], groups_titles: str = "" +) -> AnyStr: """Génère feuille excel pour saisie des notes dans l'evaluation - evaluation - rows: liste de dict @@ -77,7 +79,9 @@ def excel_feuille_saisie(evaluation: "Evaluation", rows: list[dict]) -> AnyStr: """ ws = ScoExcelSheet("Saisie notes") styles = _build_styles() - nb_lines_titles = _insert_top_title(ws, styles, evaluation=evaluation) + nb_lines_titles = _insert_top_title( + ws, styles, evaluation=evaluation, groups_titles=groups_titles + ) _insert_line_titles( ws, @@ -263,6 +267,7 @@ def _insert_top_title( evaluation: Evaluation | None = None, formsemestre: FormSemestre | None = None, description="", + groups_titles: str = "", ) -> int: """Insère les lignes de titre de la feuille (suivies d'une ligne blanche). Si evaluation, indique son titre. @@ -298,7 +303,7 @@ def _insert_top_title( evaluation.moduleimpl.formsemestre.titre_annee() if evaluation else (formsemestre.titre_annee() if formsemestre else "") - ) + ) + ((" - " + groups_titles) if groups_titles else "") ws.append_single_cell_row( scu.unescape_html(titre_annee), styles["titres"], prefix=[""] ) @@ -372,15 +377,16 @@ def _insert_bottom_help(ws, styles: dict): ) -def feuille_saisie_notes( - evaluation_id, group_ids: list[int] = None -): # TODO ré-écrire et passer dans notes.py +def feuille_saisie_notes(evaluation_id: int): # TODO ré-écrire et passer dans notes.py """Vue: document Excel pour saisie notes dans l'évaluation et les groupes indiqués""" evaluation = Evaluation.get_evaluation(evaluation_id) - group_ids = group_ids or [] + group_ids = request.args.getlist("group_ids") or [] + try: + group_ids = [int(gid) for gid in group_ids] + except ValueError as exc: + raise ScoValueError("group_ids invalide") from exc modimpl = evaluation.moduleimpl formsemestre = modimpl.formsemestre - if evaluation.date_debut: indication_date = evaluation.date_debut.date().isoformat() else: @@ -430,7 +436,9 @@ def feuille_saisie_notes( eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}" filename = f"notes_{eval_name}_{gr_title_filename}" - xls = excel_feuille_saisie(evaluation, rows=rows) + xls = excel_feuille_saisie( + evaluation, rows=rows, groups_titles=groups_infos.groups_titles + ) return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE) @@ -962,9 +970,14 @@ def _get_sheet_evaluations( raise ValueError("_get_sheet_evaluations") -def saisie_notes_tableur(evaluation_id: int, group_ids=()): +def saisie_notes_tableur(evaluation_id: int): """Saisie des notes via un fichier Excel""" - evaluation = Evaluation.query.get_or_404(evaluation_id) + 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 + evaluation = Evaluation.get_evaluation(evaluation_id) moduleimpl_id = evaluation.moduleimpl.id formsemestre_id = evaluation.moduleimpl.formsemestre_id if not evaluation.moduleimpl.can_edit_notes(current_user): @@ -1004,18 +1017,20 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()): # Menu choix groupe: H.append("""<div id="group-tabs"><table><tr><td>""") - H.append(sco_groups_view.form_groups_choice(groups_infos)) + H.append(sco_groups_view.form_groups_choice(groups_infos, submit_on_change=True)) H.append("</td></tr></table></div>") H.append( f"""<div class="saisienote_etape1"> <span class="titredivsaisienote">Étape 1 : </span> <ul> - <li><a class="stdlink" href="feuille_saisie_notes?evaluation_id={evaluation_id}&{ + <li><a class="stdlink" href="{ + url_for('notes.feuille_saisie_notes', + scodoc_dept=g.scodoc_dept, evaluation_id=evaluation_id)}&{ groups_infos.groups_query_args}" id="lnk_feuille_saisie">obtenir le fichier tableur à remplir</a> </li> - <li>ou <a class="stdlink" href="{url_for("notes.saisie_notes", + <li>ou <a class="stdlink" href="{url_for("notes.form_saisie_notes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation_id) }">aller au formulaire de saisie</a></li> </ul> @@ -1085,7 +1100,7 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()): scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id) }">Charger un autre fichier de notes</a> - <a class="stdlink" href="{url_for("notes.saisie_notes", + <a class="stdlink" href="{url_for("notes.form_saisie_notes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id) }">Formulaire de saisie des notes</a> </div>""" @@ -1113,7 +1128,9 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()): <div> <ul> <li> - <form action="do_evaluation_set_missing" method="POST"> + <form action="{ + url_for("notes.do_evaluation_set_missing", scodoc_dept=g.scodoc_dept) + }" method="POST"> Mettre toutes les notes manquantes à <input type="text" size="5" name="value"/> <input type="submit" value="OK"/> <input type="hidden" name="evaluation_id" value="{evaluation_id}"/> @@ -1129,7 +1146,7 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()): scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id) }">Revenir au module</a> </li> - <li><a class="stdlink" href="{url_for("notes.saisie_notes", + <li><a class="stdlink" href="{url_for("notes.form_saisie_notes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id) }">Revenir au formulaire de saisie</a> </li> @@ -1176,8 +1193,7 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()): "sco_page.j2", content="\n".join(H), page_title=page_title, - javascripts=sco_groups_view.JAVASCRIPTS, - cssstyles=sco_groups_view.CSSSTYLES, + javascripts=["js/groups_view.js"], ) diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index 3eb0b346f..10dfed91e 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -54,7 +54,6 @@ from app.scodoc.sco_exceptions import ( AccessDenied, NoteProcessError, ScoException, - ScoInvalidParamError, ScoValueError, ) from app.scodoc import htmlutils @@ -71,6 +70,7 @@ from app.scodoc.TrivialFormulator import TF import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import json_error from app.scodoc.sco_utils import ModuleType +from app.views import ScoData def convert_note_from_string( @@ -212,7 +212,7 @@ def do_evaluation_set_missing( evaluation_id, value, dialog_confirmed=False, group_ids_str: str = "" ): """Initialisation des notes manquantes""" - evaluation = Evaluation.query.get_or_404(evaluation_id) + evaluation = Evaluation.get_evaluation(evaluation_id) modimpl = evaluation.moduleimpl # Check access # (admin, respformation, and responsable_id) @@ -222,8 +222,12 @@ def do_evaluation_set_missing( notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id) if not group_ids_str: groups = None + groups_infos = None else: group_ids = [int(x) for x in str(group_ids_str).split(",")] + groups_infos = sco_groups_view.DisplayedGroupsInfos( + group_ids, formsemestre_id=modimpl.formsemestre.id + ) groups = sco_groups.listgroups(group_ids) etudid_etats = sco_groups.do_evaluation_listeetuds_groups( @@ -240,7 +244,9 @@ def do_evaluation_set_missing( # Convert and check values valid_notes, invalids, _, _, _ = check_notes(notes, evaluation) dest_url = url_for( - "notes.saisie_notes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation_id + "notes.form_saisie_notes", + scodoc_dept=g.scodoc_dept, + evaluation_id=evaluation_id, ) diag = "" if len(invalids) > 0: @@ -260,12 +266,17 @@ def do_evaluation_set_missing( plural = len(valid_notes) > 1 return scu.confirm_dialog( f"""<h2>Mettre toutes les notes manquantes de l'évaluation - à la valeur {value} ?</h2> + à la valeur <span class="fontred">{value} / {evaluation.note_max:g}</span> ?</h2> <p>Seuls les étudiants pour lesquels aucune note (ni valeur, ni ABS, ni EXC) n'a été rentrée seront affectés.</p> - <p><b>{len(valid_notes)} étudiant{"s" if plural else ""} concerné{"s" if plural else ""} + <div> + <b>Groupes: {groups_infos.groups_titles if groups_infos else "tous"}, + dont + <span class="fontred"> + {len(valid_notes)} étudiant{"s" if plural else ""} concerné{"s" if plural else ""} + </span> par ce changement de note.</b> - </p> + </div> """, dest_url="", cancel_url=dest_url, @@ -624,14 +635,8 @@ def _record_note( # Nouveau formulaire saisie notes (2016) -def saisie_notes(evaluation_id: int, group_ids: list = None): +def saisie_notes(evaluation: Evaluation, group_ids: list[int] | tuple[int] = ()): """Formulaire saisie notes d'une évaluation pour un groupe""" - if not isinstance(evaluation_id, int): - raise ScoInvalidParamError() - group_ids = [int(group_id) for group_id in (group_ids or [])] - evaluation: Evaluation = db.session.get(Evaluation, evaluation_id) - if evaluation is None: - raise ScoValueError("évaluation inexistante") modimpl = evaluation.moduleimpl moduleimpl_status_url = url_for( "notes.moduleimpl_status", @@ -660,7 +665,6 @@ def saisie_notes(evaluation_id: int, group_ids: list = None): select_all_when_unspecified=True, etat=None, ) - page_title = ( f'Saisie "{evaluation.description}"' if evaluation.description @@ -669,12 +673,12 @@ def saisie_notes(evaluation_id: int, group_ids: list = None): # HTML page: H = [ sco_evaluations.evaluation_describe( - evaluation_id=evaluation_id, link_saisie=False + evaluation_id=evaluation.id, link_saisie=False ), '<div id="saisie_notes"><span class="eval_title">Saisie des notes</span>', ] H.append("""<div id="group-tabs"><table><tr><td>""") - H.append(sco_groups_view.form_groups_choice(groups_infos)) + H.append(sco_groups_view.form_groups_choice(groups_infos, submit_on_change=True)) H.append('</td><td style="padding-left: 35px;">') H.append( htmlutils.make_menu( @@ -755,7 +759,8 @@ def saisie_notes(evaluation_id: int, group_ids: list = None): "sco_page.j2", content="\n".join(H), title=page_title, - javascripts=["js/saisie_notes.js"], + javascripts=["js/groups_view.js", "js/saisie_notes.js"], + sco=ScoData(formsemestre=modimpl.formsemestre), ) @@ -839,11 +844,15 @@ def _form_saisie_notes( """ formsemestre_id = modimpl.formsemestre_id formsemestre: FormSemestre = evaluation.moduleimpl.formsemestre + groups = sco_groups.listgroups(groups_infos.group_ids) res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) etudids = [ x[0] for x in sco_groups.do_evaluation_listeetuds_groups( - evaluation.id, getallstudents=True, include_demdef=True + evaluation.id, + groups=groups, + getallstudents=groups is None, + include_demdef=True, ) ] if not etudids: @@ -1001,7 +1010,9 @@ def _form_saisie_notes( H.append( f""" <div> - <form id="do_evaluation_set_missing" action="do_evaluation_set_missing" method="POST"> + <form id="do_evaluation_set_missing" action="{ + url_for("notes.do_evaluation_set_missing", scodoc_dept=g.scodoc_dept) + }" method="POST"> Mettre les notes manquantes à <input type="text" size="5" name="value"/> <input type="submit" value="OK"/> diff --git a/app/scodoc/sco_trombino_doc.py b/app/scodoc/sco_trombino_doc.py index 91b573e5a..b6517ca99 100644 --- a/app/scodoc/sco_trombino_doc.py +++ b/app/scodoc/sco_trombino_doc.py @@ -12,7 +12,6 @@ from docx.shared import Mm from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.table import WD_ALIGN_VERTICAL -from app.scodoc import sco_etud from app.scodoc import sco_photos import app.scodoc.sco_utils as scu import sco_version @@ -31,9 +30,9 @@ def trombino_doc(groups_infos): ) section = document.sections[0] footer = section.footer - footer.paragraphs[ - 0 - ].text = f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}" + footer.paragraphs[0].text = ( + f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}" + ) nb_images = len(groups_infos.members) table = document.add_table(rows=2 * (nb_images // N_PER_ROW + 1), cols=N_PER_ROW) diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index f75090cd6..245b7d5b6 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -1543,15 +1543,18 @@ def confirm_dialog( H = [ f"""<form {action} method="POST"> {message} + + <div class="form-group space-before-24"> """, ] if OK or not cancel_url: - H.append(f'<input type="submit" value="{OK}"/>') + H.append(f'<input class="btn btn-default" type="submit" value="{OK}"/>') if cancel_url: H.append( - f"""<input type ="button" value="{cancel_label}" - onClick="document.location='{cancel_url}';"/>""" + f"""<input class="btn btn-default" type="submit" name="cancel" type ="button" value="{cancel_label}" + onClick="event.preventDefault(); document.location='{cancel_url}';"/>""" ) + H.append("</div>") for param in parameters.keys(): if parameters[param] is None: parameters[param] = "" diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index bbf47a87f..835140365 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -66,6 +66,10 @@ div.sco-app-content { margin-top: 24px !important; } +.space-after-24 { + margin-bottom: 24px !important; +} + div.scobox.maxwidth { max-width: none; } diff --git a/app/static/css/scodoc97.css b/app/static/css/scodoc97.css index 6e7917807..43dd369f2 100644 --- a/app/static/css/scodoc97.css +++ b/app/static/css/scodoc97.css @@ -441,8 +441,13 @@ textarea { transition: border-color 0.2s ease-in-out; } -.form-group input[type="submit"] { - max-width: var(--sco-content-max-width); +/* Media query for desktop devices + évite que les boutons submit ne deviennent trop larges + */ +@media (min-width: 769px) { + .form-group input[type="submit"] { + width: 192px; + } } .form-group input:focus, diff --git a/app/views/notes.py b/app/views/notes.py index 3ed64a434..3ba9ef0ac 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -1836,7 +1836,6 @@ sco_publish( sco_saisie_excel.feuille_saisie_notes, Permission.EnsView, ) -sco_publish("/saisie_notes", sco_saisie_notes.saisie_notes, Permission.EnsView) sco_publish( "/do_evaluation_set_missing", sco_saisie_notes.do_evaluation_set_missing, @@ -1851,6 +1850,20 @@ sco_publish( ) +@bp.route("/form_saisie_notes/<int:evaluation_id>") +@scodoc +@permission_required(Permission.EnsView) # + controle contextuel +def form_saisie_notes(evaluation_id: int): + "Formulaire de saisie des notes d'une évaluation" + evaluation = Evaluation.get_evaluation(evaluation_id) + 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 + return sco_saisie_notes.saisie_notes(evaluation, group_ids) + + @bp.route("/formsemestre_import_notes/<int:formsemestre_id>", methods=["GET", "POST"]) @scodoc @permission_required(Permission.ScoView) # controle contextuel @@ -2117,7 +2130,9 @@ def _formsemestre_bulletins_choice( explanation=explanation, choose_mail=choose_mail, formsemestre=formsemestre, - menu_groups_choice=sco_groups_view.menu_groups_choice(groups_infos), + menu_groups_choice=sco_groups_view.menu_groups_choice( + groups_infos, submit_on_change=True + ), sco=ScoData(formsemestre=formsemestre), sco_groups_view=sco_groups_view, title=title, -- GitLab