diff --git a/app/models/formations.py b/app/models/formations.py index c0f375ddc..f76607d3c 100644 --- a/app/models/formations.py +++ b/app/models/formations.py @@ -146,7 +146,8 @@ class Formation(db.Model): db.session.add(ue) db.session.commit() - app.clear_scodoc_cache() + if change: + app.clear_scodoc_cache() class Matiere(db.Model): diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index edf5fa68d..0d126fc7f 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -286,7 +286,7 @@ class FormSemestre(db.Model): """ if not self.etapes: return "" - return ", ".join([str(x.etape_apo) for x in self.etapes]) + return ", ".join(sorted([str(x.etape_apo) for x in self.etapes])) def responsables_str(self, abbrev_prenom=True) -> str: """chaîne "J. Dupond, X. Martin" @@ -433,7 +433,7 @@ notes_formsemestre_responsables = db.Table( class FormSemestreEtape(db.Model): - """Étape Apogée associées au semestre""" + """Étape Apogée associée au semestre""" __tablename__ = "notes_formsemestre_etapes" id = db.Column(db.Integer, primary_key=True) diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py index 2136ee841..01b94c064 100644 --- a/app/scodoc/gen_tables.py +++ b/app/scodoc/gen_tables.py @@ -121,6 +121,7 @@ class GenTable(object): html_with_td_classes=False, # put class=column_id in each html_before_table="", # html snippet to put before the in the page html_empty_element="", # replace table when empty + html_table_attrs="", # for html base_url=None, origin=None, # string added to excel and xml versions filename="table", # filename, without extension @@ -146,6 +147,7 @@ class GenTable(object): self.html_header = html_header self.html_before_table = html_before_table self.html_empty_element = html_empty_element + self.html_table_attrs = html_table_attrs self.page_title = page_title self.pdf_link = pdf_link self.xls_link = xls_link @@ -413,8 +415,7 @@ class GenTable(object): cls = ' class="%s"' % " ".join(tablclasses) else: cls = "" - - H = [self.html_before_table, "" % (hid, cls)] + H = [self.html_before_table, f""] line_num = 0 # thead diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py index c19f93608..6f0a75bdd 100644 --- a/app/scodoc/sco_dept.py +++ b/app/scodoc/sco_dept.py @@ -29,6 +29,7 @@ """ from flask import g, request +from flask import url_for from flask_login import current_user import app @@ -79,7 +80,7 @@ def index_html(showcodes=0, showsemtable=0): sco_formsemestre.sem_set_responsable_name(sem) if showcodes: - sem["tmpcode"] = "" % sem["formsemestre_id"] + sem["tmpcode"] = f"" else: sem["tmpcode"] = "" # Nombre d'inscrits: @@ -121,26 +122,27 @@ def index_html(showcodes=0, showsemtable=0): if showsemtable: H.append( - """
-

Semestres de %s

+ f"""
+

Semestres de {sco_preferences.get_preference("DeptName")}

""" - % sco_preferences.get_preference("DeptName") ) H.append(_sem_table_gt(sems, showcodes=showcodes).html()) H.append("
%s{sem['formsemestre_id']}
") if not showsemtable: H.append( - '

Voir tous les semestres

' - % request.base_url + f"""
+

Voir tous les semestres ({len(othersems)} verrouillés) +

""" ) H.append( - """

-Chercher étape courante: -
- """ - % scu.NotesURL() + f"""

+

+ Chercher étape courante: + +
+

""" ) # if current_user.has_permission(Permission.ScoEtudInscrit): @@ -148,23 +150,26 @@ Chercher étape courante:

Gestion des étudiants

""" ) # if current_user.has_permission(Permission.ScoEditApo): H.append( - """
+ f"""

Exports Apogée

""" - % scu.NotesURL() ) # H.append( @@ -176,7 +181,13 @@ Chercher étape courante: ' + tags.join('') + ''); } } + +/* Editeur pour champs + * Usage: créer un élément avec data-oid (object id) + * La méthode d'URL save sera appelée en POST avec deux arguments: oid et value, + * value contenant la valeur du champs. + * Inspiré par les codes et conseils de Seb. L. + */ +class ScoFieldEditor { + constructor(selector, save_url, read_only) { + this.save_url = save_url; + this.read_only = read_only; + this.selector = selector; + this.installListeners(); + } + // Enregistre l'élément obj + save(obj) { + var value = obj.innerText.trim(); + if (value.length == 0) { + value = ""; + } + if (value == obj.dataset.value) { + return true; // Aucune modification, pas d'enregistrement mais on continue normalement + } + obj.classList.add("sco_wait"); + // DEBUG + // console.log(` + // data : ${value}, + // id: ${obj.dataset.oid} + // `); + + $.post(this.save_url, + { + oid: obj.dataset.oid, + value: value, + }, + function (result) { + obj.classList.remove("sco_wait"); + obj.classList.add("sco_modified"); + } + ); + return true; + } + /*****************************/ + /* Gestion des évènements */ + /*****************************/ + installListeners() { + if (this.read_only) { + return; + } + document.body.addEventListener("keydown", this.key); + let editor = this; + this.handleSelectCell = (event) => { editor.selectCell(event) }; + this.handleModifCell = (event) => { editor.modifCell(event) }; + this.handleBlur = (event) => { editor.blurCell(event) }; + this.handleKeyCell = (event) => { editor.keyCell(event) }; + document.querySelectorAll(this.selector).forEach(cellule => { + cellule.addEventListener("click", this.handleSelectCell); + cellule.addEventListener("dblclick", this.handleModifCell); + cellule.addEventListener("blur", this.handleBlur); + }); + } + /*********************************/ + /* Interaction avec les cellules */ + /*********************************/ + blurCell(event) { + let currentModif = document.querySelector(".sco_modifying"); + if (currentModif) { + if (!this.save(currentModif)) { + return; + } + } + } + selectCell(event) { + let obj = event.currentTarget; + if (obj) { + if (obj.classList.contains("sco_modifying")) { + return; // Cellule en cours de modification, ne pas sélectionner. + } + let currentModif = document.querySelector(".sco_modifying"); + if (currentModif) { + if (!this.save(currentModif)) { + return; + } + } + + this.unselectCell(); + obj.classList.add("sco_selected"); + } + } + unselectCell() { + document.querySelectorAll(".sco_selected, .sco_modifying").forEach(cellule => { + cellule.classList.remove("sco_selected", "sco_modifying"); + cellule.removeAttribute("contentEditable"); + cellule.removeEventListener("keydown", this.handleKeyCell); + }); + } + modifCell(event) { + let obj = event.currentTarget; + if (obj) { + obj.classList.add("sco_modifying"); + obj.contentEditable = true; + obj.addEventListener("keydown", this.handleKeyCell); + obj.focus(); + } + } + key(event) { + switch (event.key) { + case "Enter": + this.modifCell(document.querySelector(".sco_selected")); + event.preventDefault(); + break; + } + } + keyCell(event) { + let obj = event.currentTarget; + if (obj) { + if (event.key == "Enter") { + event.preventDefault(); + event.stopPropagation(); + if (!this.save(obj)) { + return + } + obj.classList.remove("sco_modifying"); + // ArrowMove(0, 1); + // modifCell(document.querySelector(".sco_selected")); + this.unselectCell(); + } + } + } +} + diff --git a/app/static/js/scolar_index.js b/app/static/js/scolar_index.js new file mode 100644 index 000000000..befd846d6 --- /dev/null +++ b/app/static/js/scolar_index.js @@ -0,0 +1,22 @@ +/* Page accueil département */ +var apo_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.semlist').DataTable(table_options); + let apo_save_url = document.querySelector("table#semlist").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 688811c09..ba0ca78e5 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -2410,6 +2410,33 @@ sco_publish( Permission.ScoEditApo, ) + +@bp.route("/formsemestre_set_apo_etapes", methods=["POST"]) +@scodoc +@permission_required(Permission.ScoEditApo) +def formsemestre_set_apo_etapes(): + """Change les codes étapes du semestre indiqué. + Args: oid=formsemestre_id, value=chaine "V1RT, V1RT2", codes séparés par des virgules + """ + formsemestre_id = int(request.form.get("oid")) + etapes_apo_str = request.form.get("value") + formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) + current_etapes = {e.etape_apo for e in formsemestre.etapes} + new_etapes = {s.strip() for s in etapes_apo_str.split(",")} + + if new_etapes != current_etapes: + formsemestre.etapes = [] + for etape_apo in new_etapes: + etape = models.FormSemestreEtape( + formsemestre_id=formsemestre_id, etape_apo=etape_apo + ) + formsemestre.etapes.append(etape) + db.session.add(formsemestre) + db.session.commit() + + return ("", 204) + + # sco_semset sco_publish("/semset_page", sco_semset.semset_page, Permission.ScoEditApo) sco_publish( diff --git a/app/views/scolar.py b/app/views/scolar.py index de987cb17..23abde80b 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -327,6 +327,7 @@ def showEtudLog(etudid, format="html"): @bp.route("/") @bp.route("/index_html") +@bp.route("/index") @scodoc @permission_required(Permission.ScoView) @scodoc7func diff --git a/sco_version.py b/sco_version.py index 4380a1fa5..e3fced749 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.2" +SCOVERSION = "9.2.3" SCONAME = "ScoDoc"