diff --git a/app/models/events.py b/app/models/events.py index 9725f3c8a7..b94549e764 100644 --- a/app/models/events.py +++ b/app/models/events.py @@ -36,18 +36,21 @@ class Scolog(db.Model): class ScolarNews(db.Model): """Nouvelles pour page d'accueil""" - NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id) - NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id) - NEWS_FORM = "FORM" # modification formation (object=formation_id) - NEWS_SEM = "SEM" # creation semestre (object=None) NEWS_ABS = "ABS" # saisie absence + NEWS_APO = "APO" # changements de codes APO + NEWS_FORM = "FORM" # modification formation (object=formation_id) + NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id) NEWS_MISC = "MISC" # unused + NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id) + NEWS_SEM = "SEM" # creation semestre (object=None) NEWS_MAP = { - NEWS_INSCR: "inscription d'étudiants", - NEWS_NOTE: "saisie note", + NEWS_ABS: "saisie absence", + NEWS_APO: "modif. code Apogée", NEWS_FORM: "modification formation", - NEWS_SEM: "création semestre", + NEWS_INSCR: "inscription d'étudiants", NEWS_MISC: "opération", # unused + NEWS_NOTE: "saisie note", + NEWS_SEM: "création semestre", } NEWS_TYPES = list(NEWS_MAP.keys()) diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 0d126fc7fb..fda7238394 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -375,7 +375,7 @@ class FormSemestre(db.Model): return f"{self.titre} {self.formation.get_parcours().SESSION_NAME} {self.semestre_id}" def sem_modalite(self) -> str: - """Le semestre et la modialité, ex "S2 FI" ou "S3 APP" """ + """Le semestre et la modalité, ex "S2 FI" ou "S3 APP" """ if self.semestre_id > 0: descr_sem = f"S{self.semestre_id}" else: diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py index 01b94c064c..1c708ca5c6 100644 --- a/app/scodoc/gen_tables.py +++ b/app/scodoc/gen_tables.py @@ -385,12 +385,16 @@ class GenTable(object): colspan_count = colspan else: colspan_txt = "" + attrs = row.get("_%s_td_attrs" % cid, "") + order = row.get(f"_{cid}_order") + if order: + attrs += f' data-order="{order}"' r.append( "<%s%s %s%s%s>%s" % ( elem, std, - row.get("_%s_td_attrs" % cid, ""), + attrs, klass, colspan_txt, content, diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index c6811d4547..fd25e5da3f 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -43,10 +43,8 @@ import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import ModuleType from app.scodoc.TrivialFormulator import TrivialFormulator -from app.scodoc.gen_tables import GenTable from app.scodoc.sco_permissions import Permission from app.scodoc.sco_exceptions import ( - ScoGenError, ScoValueError, ScoLockedFormError, ScoNonEmptyFormationObject, @@ -61,7 +59,6 @@ from app.scodoc import sco_edit_module from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl -from app.scodoc import sco_preferences from app.scodoc import sco_tag_module _ueEditor = ndb.EditableTable( @@ -1355,93 +1352,6 @@ def ue_is_locked(ue_id): return len(r) > 0 -# ---- Table recap formation -def formation_table_recap(formation_id, format="html"): - """Table recapitulant formation.""" - from app.scodoc import sco_formations - - F = sco_formations.formation_list(args={"formation_id": formation_id}) - if not F: - raise ScoValueError("invalid formation_id") - F = F[0] - T = [] - ues = ue_list(args={"formation_id": formation_id}) - for ue in ues: - Matlist = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]}) - for Mat in Matlist: - Modlist = sco_edit_module.module_list( - args={"matiere_id": Mat["matiere_id"]} - ) - for Mod in Modlist: - Mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls( - Mod["module_id"] - ) - # - T.append( - { - "UE_acro": ue["acronyme"], - "Mat_tit": Mat["titre"], - "Mod_tit": Mod["abbrev"] or Mod["titre"], - "Mod_code": Mod["code"], - "Mod_coef": Mod["coefficient"], - "Mod_sem": Mod["semestre_id"], - "nb_moduleimpls": Mod["nb_moduleimpls"], - "heures_cours": Mod["heures_cours"], - "heures_td": Mod["heures_td"], - "heures_tp": Mod["heures_tp"], - "ects": Mod["ects"], - } - ) - columns_ids = [ - "UE_acro", - "Mat_tit", - "Mod_tit", - "Mod_code", - "Mod_coef", - "Mod_sem", - "nb_moduleimpls", - "heures_cours", - "heures_td", - "heures_tp", - "ects", - ] - titles = { - "UE_acro": "UE", - "Mat_tit": "Matière", - "Mod_tit": "Module", - "Mod_code": "Code", - "Mod_coef": "Coef.", - "Mod_sem": "Sem.", - "nb_moduleimpls": "Nb utilisé", - "heures_cours": "Cours (h)", - "heures_td": "TD (h)", - "heures_tp": "TP (h)", - "ects": "ECTS", - } - - title = ( - """Formation %(titre)s (%(acronyme)s) [version %(version)s] code %(formation_code)s""" - % F - ) - tab = GenTable( - columns_ids=columns_ids, - rows=T, - titles=titles, - origin="Généré par %s le " % scu.sco_version.SCONAME - + scu.timedate_human_repr() - + "", - caption=title, - html_caption=title, - html_class="table_leftalign", - base_url="%s?formation_id=%s" % (request.base_url, formation_id), - page_title=title, - html_title="

" + title + "

", - pdf_title=title, - preferences=sco_preferences.SemPreferences(), - ) - return tab.make_page(format=format) - - def ue_list_semestre_ids(ue: dict): """Liste triée des numeros de semestres des modules dans cette UE Il est recommandable que tous les modules d'une UE aient le même indice de semestre. diff --git a/app/scodoc/sco_formation_recap.py b/app/scodoc/sco_formation_recap.py new file mode 100644 index 0000000000..e23b043edf --- /dev/null +++ b/app/scodoc/sco_formation_recap.py @@ -0,0 +1,153 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +"""Table recap formation (avec champs éditables) +""" +from flask import url_for +from flask import g, request +from flask_login import current_user + +from app.models import Formation, UniteEns, Module +from app.models.formations import Matiere + +from app.scodoc.gen_tables import GenTable +from app.scodoc.sco_permissions import Permission +from app.scodoc import sco_preferences +import app.scodoc.sco_utils as scu + + +# ---- Table recap formation +def formation_table_recap(formation_id, format="html"): + """Table recapitulant formation.""" + T = [] + formation = Formation.query.get_or_404(formation_id) + ues = formation.ues.order_by(UniteEns.semestre_idx, UniteEns.numero) + can_edit = current_user.has_permission(Permission.ScoChangeFormation) + li = 0 + for ue in ues: + # L'UE + T.append( + { + "sem": f"S{ue.semestre_idx}" if ue.semestre_idx is not None else "-", + "_sem_order": f"{li:04d}", + "code": ue.acronyme, + "titre": ue.titre or "", + "apo": ue.code_apogee or "", + "_apo_td_attrs": f""" data-oid="{ue.id}" data-value="{ue.code_apogee or ''}" """, + "coef": ue.coefficient or "", + "ects": ue.ects, + "_css_row_class": f"ue ue_", + } + ) + li += 1 + matieres = ue.matieres.order_by(Matiere.numero) + for mat in matieres: + modules = mat.modules.order_by(Module.numero) + for mod in modules: + nb_moduleimpls = mod.modimpls.count() + # le module (ou ressource ou sae) + T.append( + { + "sem": f"S{mod.semestre_id}" + if mod.semestre_id is not None + else "-", + "_sem_order": f"{li:04d}", + "code": mod.code, + "titre": mod.abbrev or mod.titre, + "_titre_target": url_for( + "notes.module_edit", + scodoc_dept=g.scodoc_dept, + module_id=mod.id, + ) + if can_edit + else None, + "apo": mod.code_apogee, + "_apo_td_attrs": f""" data-oid="{mod.id}" data-value="{mod.code_apogee or ''}" """, + "coef": mod.coefficient, + "nb_moduleimpls": nb_moduleimpls, + "heures_cours": mod.heures_cours, + "heures_td": mod.heures_td, + "heures_tp": mod.heures_tp, + "_css_row_class": f"mod {mod.type_abbrv()}", + } + ) + columns_ids = [ + "sem", + "code", + "apo", + # "mat", inutile d'afficher la matière + "titre", + "coef", + "ects", + "nb_moduleimpls", + "heures_cours", + "heures_td", + "heures_tp", + ] + titles = { + "ue": "UE", + "mat": "Matière", + "titre": "Titre", + "code": "Code", + "apo": "Apo", + "coef": "Coef.", + "sem": "Sem.", + "nb_moduleimpls": "Nb utilisé", + "heures_cours": "Cours (h)", + "heures_td": "TD (h)", + "heures_tp": "TP (h)", + "ects": "ECTS", + } + + title = f"""Formation {formation.titre} ({formation.acronyme}) + [version {formation.version}] code {formation.formation_code}""" + html_class = "stripe cell-border compact hover order-column formation_table_recap" + if current_user.has_permission(Permission.ScoEditApo): + html_class += " apo_editable" + + tab = GenTable( + columns_ids=columns_ids, + rows=T, + titles=titles, + origin=f"Généré par {scu.sco_version.SCONAME} le {scu.timedate_human_repr()}", + caption=title, + html_caption=title, + html_class=html_class, + html_class_ignore_default=True, + html_table_attrs=f""" + data-apo_ue_save_url="{url_for('notes.ue_set_apo', scodoc_dept=g.scodoc_dept)}" + data-apo_mod_save_url="{url_for('notes.module_set_apo', scodoc_dept=g.scodoc_dept)}" + """, + html_with_td_classes=True, + base_url=f"{request.base_url}?formation_id={formation_id}", + page_title=title, + html_title=f"

{title}

", + pdf_title=title, + preferences=sco_preferences.SemPreferences(), + table_id="formation_table_recap", + ) + return tab.make_page(format=format, javascripts=["js/formation_recap.js"]) diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index 24548b28c9..6a0d9e73d6 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -1056,7 +1056,7 @@ span.wtf-field ul.errors li { } .configuration_logo entete_dept { - display: inline-block; + display: inline-block; } .configuration_logo .effectifs { @@ -3971,4 +3971,18 @@ table.evaluations_recap td.nb_abs, table.evaluations_recap td.nb_att, table.evaluations_recap td.nb_exc { text-align: center; +} + +/* ------------- Tableau récap formation ------------ */ +table.formation_table_recap tr.ue td { + font-weight: bold; +} + +table.formation_table_recap td.coef, +table.formation_table_recap td.ects, +table.formation_table_recap td.nb_moduleimpls, +table.formation_table_recap td.heures_cours, +table.formation_table_recap td.heures_td, +table.formation_table_recap td.heures_tp { + text-align: right; } \ No newline at end of file diff --git a/app/static/js/formation_recap.js b/app/static/js/formation_recap.js new file mode 100644 index 0000000000..0bcb009f47 --- /dev/null +++ b/app/static/js/formation_recap.js @@ -0,0 +1,28 @@ +/* Page accueil département */ +var apo_ue_editor = null; +var apo_mod_editor = null; + +$(document).ready(function () { + var table_options = { + "paging": false, + "searching": false, + "info": false, + /* "autoWidth" : false, */ + "fixedHeader": { + "header": true, + "footer": true + }, + "orderCellsTop": true, // cellules ligne 1 pour tri + "aaSorting": [], // Prevent initial sorting + }; + $('table#formation_table_recap').DataTable(table_options); + let table_editable = document.querySelector("table#formation_table_recap.apo_editable"); + if (table_editable) { + let apo_ue_save_url = document.querySelector("table#formation_table_recap.apo_editable").dataset.apo_ue_save_url; + apo_ue_editor = new ScoFieldEditor("table#formation_table_recap tr.ue td.apo", apo_ue_save_url, false); + let apo_mod_save_url = document.querySelector("table#formation_table_recap.apo_editable").dataset.apo_mod_save_url; + apo_mod_editor = new ScoFieldEditor("table#formation_table_recap tr.mod td.apo", apo_mod_save_url, false); + } +}); + + diff --git a/app/static/js/scolar_index.js b/app/static/js/scolar_index.js index 4718e467e7..600a7cf5a4 100644 --- a/app/static/js/scolar_index.js +++ b/app/static/js/scolar_index.js @@ -15,8 +15,11 @@ $(document).ready(function () { "aaSorting": [], // Prevent initial sorting }; $('table.semlist').DataTable(table_options); - let apo_save_url = document.querySelector("table#semlist.apo_editable").dataset.apo_save_url; - apo_editor = new ScoFieldEditor(".etapes_apo_str", apo_save_url, false); + let table_editable = document.querySelector("table#semlist.apo_editable"); + if (table_editable) { + let apo_save_url = document.querySelector("table#semlist.apo_editable").dataset.apo_save_url; + apo_editor = new ScoFieldEditor(".etapes_apo_str", apo_save_url, false); + } }); diff --git a/app/views/notes.py b/app/views/notes.py index ba0ca78e55..a4d8774192 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -45,11 +45,13 @@ from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestreUEComputationExpr +from app.models.modules import Module from app.models.ues import UniteEns from app import api from app import db from app import models +from app.models import ScolarNews from app.auth.models import User from app.but import bulletin_but from app.decorators import ( @@ -86,7 +88,6 @@ from app.scodoc import sco_archives from app.scodoc import sco_bulletins from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_cache -from app.scodoc import sco_compute_moy from app.scodoc import sco_cost_formation from app.scodoc import sco_debouche from app.scodoc import sco_edit_apc @@ -103,6 +104,7 @@ from app.scodoc import sco_evaluation_edit from app.scodoc import sco_evaluation_recap from app.scodoc import sco_export_results from app.scodoc import sco_formations +from app.scodoc import sco_formation_recap from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_custommenu from app.scodoc import sco_formsemestre_edit @@ -480,7 +482,9 @@ sco_publish( methods=["GET", "POST"], ) sco_publish( - "/formation_table_recap", sco_edit_ue.formation_table_recap, Permission.ScoView + "/formation_table_recap", + sco_formation_recap.formation_table_recap, + Permission.ScoView, ) sco_publish( "/formation_add_malus_modules", @@ -2433,7 +2437,52 @@ def formsemestre_set_apo_etapes(): formsemestre.etapes.append(etape) db.session.add(formsemestre) db.session.commit() + ScolarNews.add( + typ=ScolarNews.NEWS_APO, + text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})", + ) + return ("", 204) + +@bp.route("/ue_set_apo", methods=["POST"]) +@scodoc +@permission_required(Permission.ScoEditApo) +def ue_set_apo(): + """Change le code APO de l'UE + Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE) + """ + ue_id = int(request.form.get("oid")) + code_apo = (request.form.get("value") or "").strip() + ue = UniteEns.query.get_or_404(ue_id) + if code_apo != ue.code_apogee: + ue.code_apogee = code_apo + db.session.add(ue) + db.session.commit() + ScolarNews.add( + typ=ScolarNews.NEWS_FORM, + text=f"Modification code Apogée d'UE dans la formation {ue.formation.titre} ({ue.formation.acronyme})", + ) + return ("", 204) + + +@bp.route("/module_set_apo", methods=["POST"]) +@scodoc +@permission_required(Permission.ScoEditApo) +def module_set_apo(): + """Change le code APO du module + Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE) + """ + oid = int(request.form.get("oid")) + code_apo = (request.form.get("value") or "").strip() + mod = Module.query.get_or_404(oid) + if code_apo != mod.code_apogee: + mod.code_apogee = code_apo + db.session.add(mod) + db.session.commit() + ScolarNews.add( + typ=ScolarNews.NEWS_FORM, + text=f"Modification code Apogée d'UE dans la formation {mod.formation.titre} ({mod.formation.acronyme})", + ) return ("", 204) diff --git a/sco_version.py b/sco_version.py index 9136c351bf..a8d3aae110 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.5" +SCOVERSION = "9.2.6" SCONAME = "ScoDoc"