diff --git a/app/models/formations.py b/app/models/formations.py
index c0f375ddc6..f76607d3c9 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 edf5fa68d2..0d126fc7fb 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 2136ee8418..01b94c064c 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 c19f936084..6f0a75bdd3 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"] = "%s " % sem["formsemestre_id"]
+ sem["tmpcode"] = f"{sem['formsemestre_id']} "
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("
")
if not showsemtable:
H.append(
- 'Voir tous les semestres
'
- % request.base_url
+ f"""
+ Voir tous les semestres ({len(othersems)} verrouillés)
+
"""
)
H.append(
- """
- """
- % scu.NotesURL()
+ f"""
+
+ """
)
#
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 0000000000..befd846d60
--- /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 688811c09e..ba0ca78e55 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 de987cb17b..23abde80bb 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 4380a1fa58..e3fced749a 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"