forked from ScoDoc/ScoDoc
Edition en ligne des codes étapes Apogée des semestre
This commit is contained in:
parent
2dbaacf460
commit
2e6e7675bf
@ -146,6 +146,7 @@ class Formation(db.Model):
|
||||
db.session.add(ue)
|
||||
|
||||
db.session.commit()
|
||||
if change:
|
||||
app.clear_scodoc_cache()
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -121,6 +121,7 @@ class GenTable(object):
|
||||
html_with_td_classes=False, # put class=column_id in each <td>
|
||||
html_before_table="", # html snippet to put before the <table> 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, "<table%s%s>" % (hid, cls)]
|
||||
H = [self.html_before_table, f"<table{hid}{cls} {self.html_table_attrs}>"]
|
||||
|
||||
line_num = 0
|
||||
# thead
|
||||
|
@ -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"] = "<td><tt>%s</tt></td>" % sem["formsemestre_id"]
|
||||
sem["tmpcode"] = f"<td><tt>{sem['formsemestre_id']}</tt></td>"
|
||||
else:
|
||||
sem["tmpcode"] = ""
|
||||
# Nombre d'inscrits:
|
||||
@ -121,26 +122,27 @@ def index_html(showcodes=0, showsemtable=0):
|
||||
|
||||
if showsemtable:
|
||||
H.append(
|
||||
"""<hr/>
|
||||
<h2>Semestres de %s</h2>
|
||||
f"""<hr>
|
||||
<h2>Semestres de {sco_preferences.get_preference("DeptName")}</h2>
|
||||
"""
|
||||
% sco_preferences.get_preference("DeptName")
|
||||
)
|
||||
H.append(_sem_table_gt(sems, showcodes=showcodes).html())
|
||||
H.append("</table>")
|
||||
if not showsemtable:
|
||||
H.append(
|
||||
'<hr/><p><a href="%s?showsemtable=1">Voir tous les semestres</a></p>'
|
||||
% request.base_url
|
||||
f"""<hr>
|
||||
<p><a class="stdlink" href="{url_for('scolar.index_html', scodoc_dept=g.scodoc_dept, showsemtable=1)
|
||||
}">Voir tous les semestres ({len(othersems)} verrouillés)</a>
|
||||
</p>"""
|
||||
)
|
||||
|
||||
H.append(
|
||||
"""<p><form action="%s/view_formsemestre_by_etape">
|
||||
Chercher étape courante: <input name="etape_apo" type="text" size="8" spellcheck="false"></input>
|
||||
</form
|
||||
</p>
|
||||
"""
|
||||
% scu.NotesURL()
|
||||
f"""<p>
|
||||
<form action="{url_for('notes.view_formsemestre_by_etape', scodoc_dept=g.scodoc_dept)}">
|
||||
Chercher étape courante:
|
||||
<input name="etape_apo" type="text" size="8" spellcheck="false"></input>
|
||||
</form>
|
||||
</p>"""
|
||||
)
|
||||
#
|
||||
if current_user.has_permission(Permission.ScoEtudInscrit):
|
||||
@ -148,23 +150,26 @@ Chercher étape courante: <input name="etape_apo" type="text" size="8" spellchec
|
||||
"""<hr>
|
||||
<h3>Gestion des étudiants</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="etudident_create_form">créer <em>un</em> nouvel étudiant</a></li>
|
||||
<li><a class="stdlink" href="form_students_import_excel">importer de nouveaux étudiants</a> (ne pas utiliser sauf cas particulier, utilisez plutôt le lien dans
|
||||
<li><a class="stdlink" href="etudident_create_form">créer <em>un</em> nouvel étudiant</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="form_students_import_excel">importer de nouveaux étudiants</a>
|
||||
(ne pas utiliser sauf cas particulier, utilisez plutôt le lien dans
|
||||
le tableau de bord semestre si vous souhaitez inscrire les
|
||||
étudiants importés à un semestre)</li>
|
||||
étudiants importés à un semestre)
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
#
|
||||
if current_user.has_permission(Permission.ScoEditApo):
|
||||
H.append(
|
||||
"""<hr>
|
||||
f"""<hr>
|
||||
<h3>Exports Apogée</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="%s/semset_page">Années scolaires / exports Apogée</a></li>
|
||||
<li><a class="stdlink" href="{url_for('notes.semset_page', scodoc_dept=g.scodoc_dept)
|
||||
}">Années scolaires / exports Apogée</a></li>
|
||||
</ul>
|
||||
"""
|
||||
% scu.NotesURL()
|
||||
)
|
||||
#
|
||||
H.append(
|
||||
@ -176,7 +181,13 @@ Chercher étape courante: <input name="etape_apo" type="text" size="8" spellchec
|
||||
"""
|
||||
)
|
||||
#
|
||||
return html_sco_header.sco_header() + "\n".join(H) + html_sco_header.sco_footer()
|
||||
return (
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"ScoDoc {g.scodoc_dept}", javascripts=["js/scolar_index.js"]
|
||||
)
|
||||
+ "\n".join(H)
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
|
||||
|
||||
def _sem_table(sems):
|
||||
@ -213,7 +224,9 @@ def _sem_table(sems):
|
||||
|
||||
|
||||
def _sem_table_gt(sems, showcodes=False):
|
||||
"""Nouvelle version de la table des semestres"""
|
||||
"""Nouvelle version de la table des semestres
|
||||
Utilise une datatables.
|
||||
"""
|
||||
_style_sems(sems)
|
||||
columns_ids = (
|
||||
"lockimg",
|
||||
@ -236,14 +249,16 @@ def _sem_table_gt(sems, showcodes=False):
|
||||
"mois_debut": "Début",
|
||||
"dash_mois_fin": "Année",
|
||||
"titre_resp": "Semestre",
|
||||
"nb_inscrits": "N", # groupicon,
|
||||
"nb_inscrits": "N",
|
||||
},
|
||||
columns_ids=columns_ids,
|
||||
rows=sems,
|
||||
html_class="table_leftalign semlist",
|
||||
table_id="semlist",
|
||||
html_class_ignore_default=True,
|
||||
html_class="stripe cell-border compact hover order-column table_leftalign semlist",
|
||||
html_sortable=True,
|
||||
# base_url = '%s?formsemestre_id=%s' % (request.base_url, formsemestre_id),
|
||||
# caption='Maquettes enregistrées',
|
||||
html_table_attrs=f"""data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}" """,
|
||||
html_with_td_classes=True,
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
|
||||
@ -276,6 +291,10 @@ def _style_sems(sems):
|
||||
sem["semestre_id_n"] = ""
|
||||
else:
|
||||
sem["semestre_id_n"] = sem["semestre_id"]
|
||||
# pour édition codes Apogée:
|
||||
sem[
|
||||
"_etapes_apo_str_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['etapes_apo_str']}" """
|
||||
|
||||
|
||||
def delete_dept(dept_id: int):
|
||||
|
@ -34,8 +34,6 @@ from zipfile import ZipFile
|
||||
import flask
|
||||
from flask import url_for, g, send_file, request
|
||||
|
||||
# from werkzeug.utils import send_file
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc import html_sco_header
|
||||
|
@ -141,7 +141,6 @@ def _formsemestre_enrich(sem):
|
||||
"""Ajoute champs souvent utiles: titre + annee et dateord (pour tris)"""
|
||||
# imports ici pour eviter refs circulaires
|
||||
from app.scodoc import sco_formsemestre_edit
|
||||
from app.scodoc import sco_etud
|
||||
|
||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
||||
@ -350,6 +349,7 @@ def read_formsemestre_etapes(formsemestre_id): # OBSOLETE
|
||||
"""SELECT etape_apo
|
||||
FROM notes_formsemestre_etapes
|
||||
WHERE formsemestre_id = %(formsemestre_id)s
|
||||
ORDER BY etape_apo
|
||||
""",
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
|
@ -728,15 +728,13 @@ def sendResult(
|
||||
|
||||
def send_file(data, filename="", suffix="", mime=None, attached=None):
|
||||
"""Build Flask Response for file download of given type
|
||||
By default (attached is None), json and xml are inlined and otrher types are attached.
|
||||
By default (attached is None), json and xml are inlined and other types are attached.
|
||||
"""
|
||||
if attached is None:
|
||||
if mime == XML_MIMETYPE or mime == JSON_MIMETYPE:
|
||||
attached = False
|
||||
else:
|
||||
attached = True
|
||||
# if attached and not filename:
|
||||
# raise ValueError("send_file: missing attachement filename")
|
||||
if filename:
|
||||
if suffix:
|
||||
filename += suffix
|
||||
@ -755,7 +753,7 @@ def send_docx(document, filename):
|
||||
buffer.seek(0)
|
||||
return flask.send_file(
|
||||
buffer,
|
||||
attachment_filename=sanitize_filename(filename),
|
||||
download_name=sanitize_filename(filename),
|
||||
mimetype=DOCX_MIMETYPE,
|
||||
)
|
||||
|
||||
|
@ -427,8 +427,8 @@ table.semlist tr td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
table.semlist tr a.stdlink,
|
||||
table.semlist tr a.stdlink:visited {
|
||||
table.semlist tbody tr a.stdlink,
|
||||
table.semlist tbody tr a.stdlink:visited {
|
||||
color: navy;
|
||||
text-decoration: none;
|
||||
}
|
||||
@ -442,32 +442,86 @@ table.semlist tr td.semestre_id {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table.semlist tr td.modalite {
|
||||
table.semlist tbody tr td.modalite {
|
||||
text-align: left;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_S-1 {
|
||||
/***************************/
|
||||
/* Statut des cellules */
|
||||
/***************************/
|
||||
.sco_selected {
|
||||
outline: 1px solid #c09;
|
||||
}
|
||||
|
||||
.sco_modifying {
|
||||
outline: 2px dashed #c09;
|
||||
background-color: white !important;
|
||||
}
|
||||
|
||||
.sco_wait {
|
||||
outline: 2px solid #c90;
|
||||
}
|
||||
|
||||
.sco_good {
|
||||
outline: 2px solid #9c0;
|
||||
}
|
||||
|
||||
.sco_modified {
|
||||
font-weight: bold;
|
||||
color: indigo
|
||||
}
|
||||
|
||||
/***************************/
|
||||
/* Message */
|
||||
/***************************/
|
||||
.message {
|
||||
position: fixed;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
z-index: 10;
|
||||
padding: 20px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
background: #ec7068;
|
||||
background: #90c;
|
||||
color: #FFF;
|
||||
font-size: 24px;
|
||||
animation: message 3s;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
@keyframes message {
|
||||
20% {
|
||||
transform: translate(-50%, 100%)
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: translate(-50%, 100%)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S-1 td {
|
||||
background-color: rgb(251, 250, 216);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_S1 {
|
||||
div#gtrcontent table.semlist tbody tr.css_S1 td {
|
||||
background-color: rgb(92%, 95%, 94%);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_S2 {
|
||||
div#gtrcontent table.semlist tbody tr.css_S2 td {
|
||||
background-color: rgb(214, 223, 236);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_S3 {
|
||||
div#gtrcontent table.semlist tbody tr.css_S3 td {
|
||||
background-color: rgb(167, 216, 201);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_S4 {
|
||||
div#gtrcontent table.semlist tbody tr.css_S4 td {
|
||||
background-color: rgb(131, 225, 140);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_MEXT {
|
||||
div#gtrcontent table.semlist tbody tr.css_MEXT td {
|
||||
color: #0b6e08;
|
||||
}
|
||||
|
||||
|
@ -133,3 +133,134 @@ function readOnlyTags(nodes) {
|
||||
node.after('<span class="ro_tags"><span class="ro_tag">' + tags.join('</span><span class="ro_tag">') + '</span></span>');
|
||||
}
|
||||
}
|
||||
|
||||
/* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
22
app/static/js/scolar_index.js
Normal file
22
app/static/js/scolar_index.js
Normal file
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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(
|
||||
|
@ -327,6 +327,7 @@ def showEtudLog(etudid, format="html"):
|
||||
|
||||
@bp.route("/")
|
||||
@bp.route("/index_html")
|
||||
@bp.route("/index")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.2.2"
|
||||
SCOVERSION = "9.2.3"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user