Compare commits

...

6 Commits

18 changed files with 460 additions and 75 deletions

View File

@ -100,8 +100,9 @@ def comp_ranks_series(notes: pd.Series) -> (pd.Series, pd.Series):
if (notes is None) or (len(notes) == 0):
return (pd.Series([], dtype=object), pd.Series([], dtype=int))
notes = notes.sort_values(ascending=False) # Serie, tri par ordre décroissant
rangs_str = pd.Series(index=notes.index, dtype=str) # le rang est une chaîne
rangs_int = pd.Series(index=notes.index, dtype=int) # le rang numérique pour tris
rangs_str = pd.Series("", index=notes.index, dtype=str) # le rang est une chaîne
# le rang numérique pour tris:
rangs_int = pd.Series(0, index=notes.index, dtype=int)
N = len(notes)
nb_ex = 0 # nb d'ex-aequo consécutifs en cours
notes_i = notes.iat
@ -128,4 +129,5 @@ def comp_ranks_series(notes: pd.Series) -> (pd.Series, pd.Series):
rangs_int[etudid] = i + 1
srang = "%d" % (i + 1)
rangs_str[etudid] = srang
assert rangs_int.dtype == int
return rangs_str, rangs_int

View File

@ -41,11 +41,8 @@ from wtforms.fields.simple import StringField, HiddenField
from app.models import Departement
from app.scodoc import sco_logos, html_sco_header
from app.scodoc import sco_utils as scu
from app.scodoc.sco_config_actions import (
LogoDelete,
LogoUpdate,
LogoInsert,
)
from app.scodoc.sco_config_actions import LogoInsert
from app.scodoc.sco_logos import find_logo
@ -120,6 +117,8 @@ def logo_name_validator(message=None):
class AddLogoForm(FlaskForm):
"""Formulaire permettant l'ajout d'un logo (dans un département)"""
from app.scodoc.sco_config_actions import LogoInsert
dept_key = HiddenField()
name = StringField(
label="Nom",
@ -151,7 +150,7 @@ class AddLogoForm(FlaskForm):
dept_id = dept_key_to_id(self.dept_key.data)
if dept_id == GLOBAL:
dept_id = None
if find_logo(logoname=name.data, dept_id=dept_id) is not None:
if find_logo(logoname=name.data, dept_id=dept_id, strict=True) is not None:
raise validators.ValidationError("Un logo de même nom existe déjà")
def select_action(self):
@ -160,6 +159,14 @@ class AddLogoForm(FlaskForm):
return LogoInsert.build_action(self.data)
return None
def opened(self):
if self.do_insert.data:
if self.name.errors:
return "open"
if self.upload.errors:
return "open"
return ""
class LogoForm(FlaskForm):
"""Embed both presentation of a logo (cf. template file configuration.html)
@ -176,7 +183,18 @@ class LogoForm(FlaskForm):
)
],
)
do_delete = SubmitField("Supprimer l'image")
do_delete = SubmitField("Supprimer")
do_rename = SubmitField("Renommer")
new_name = StringField(
label="Nom",
validators=[
logo_name_validator("Nom de logo invalide (alphanumérique, _)"),
validators.Length(
max=20, message="Un nom ne doit pas dépasser 20 caractères"
),
validators.DataRequired("Nom de logo requis (alphanumériques ou '-')"),
],
)
def __init__(self, *args, **kwargs):
kwargs["meta"] = {"csrf": False}
@ -205,12 +223,25 @@ class LogoForm(FlaskForm):
self.titre = "Logo pied de page"
def select_action(self):
from app.scodoc.sco_config_actions import LogoRename
from app.scodoc.sco_config_actions import LogoUpdate
from app.scodoc.sco_config_actions import LogoDelete
if self.do_delete.data and self.can_delete:
return LogoDelete.build_action(self.data)
if self.upload.data and self.validate():
return LogoUpdate.build_action(self.data)
if self.do_rename.data and self.validate():
return LogoRename.build_action(self.data)
return None
def opened(self):
if self.upload.data and self.upload.errors:
return "open"
if self.new_name.data and self.new_name.errors:
return "open"
return ""
class DeptForm(FlaskForm):
dept_key = HiddenField()
@ -244,6 +275,23 @@ class DeptForm(FlaskForm):
return self
return self.index.get(logoname, None)
def opened(self):
if self.add_logo.opened():
return "open"
for logo_form in self.logos:
if logo_form.opened():
return "open"
return ""
def count(self):
compte = len(self.logos.entries)
if compte == 0:
return "vide"
elif compte == 1:
return "1 élément"
else:
return f"{compte} éléments"
def _make_dept_id_name():
"""Cette section assure que tous les départements sont traités (y compris ceux qu'ont pas de logo au départ)

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -28,11 +28,10 @@
"""
"""
from app.models import ScoDocSiteConfig
from app.scodoc.sco_logos import write_logo, find_logo, delete_logo
import app
from flask import current_app
from app.scodoc.sco_logos import find_logo
class Action:
"""Base class for all classes describing an action from from config form."""
@ -42,9 +41,9 @@ class Action:
self.parameters = parameters
@staticmethod
def build_action(parameters, stream=None):
def build_action(parameters):
"""Check (from parameters) if some action has to be done and
then return list of action (or else return empty list)."""
then return list of action (or else return None)."""
raise NotImplementedError
def display(self):
@ -59,6 +58,45 @@ class Action:
GLOBAL = "_"
class LogoRename(Action):
"""Action: rename a logo
dept_id: dept_id or '-'
logo_id: logo_id (old name)
new_name: new_name
"""
def __init__(self, parameters):
super().__init__(
f"Renommage du logo {parameters['logo_id']} en {parameters['new_name']}",
parameters,
)
@staticmethod
def build_action(parameters):
dept_id = parameters["dept_key"]
if dept_id == GLOBAL:
dept_id = None
parameters["dept_id"] = dept_id
if parameters["new_name"]:
logo = find_logo(
logoname=parameters["new_name"],
dept_id=parameters["dept_key"],
strict=True,
)
if logo is None:
return LogoRename(parameters)
def execute(self):
from app.scodoc.sco_logos import rename_logo
current_app.logger.info(self.message)
rename_logo(
old_name=self.parameters["logo_id"],
new_name=self.parameters["new_name"],
dept_id=self.parameters["dept_id"],
)
class LogoUpdate(Action):
"""Action: change a logo
dept_id: dept_id or '_',
@ -83,6 +121,8 @@ class LogoUpdate(Action):
return None
def execute(self):
from app.scodoc.sco_logos import write_logo
current_app.logger.info(self.message)
write_logo(
stream=self.parameters["upload"],
@ -113,6 +153,8 @@ class LogoDelete(Action):
return None
def execute(self):
from app.scodoc.sco_logos import delete_logo
current_app.logger.info(self.message)
delete_logo(name=self.parameters["logo_id"], dept_id=self.parameters["dept_id"])
@ -136,13 +178,15 @@ class LogoInsert(Action):
parameters["dept_id"] = None
if parameters["upload"] and parameters["name"]:
logo = find_logo(
logoname=parameters["name"], dept_id=parameters["dept_key"]
logoname=parameters["name"], dept_id=parameters["dept_key"], strict=True
)
if logo is None:
return LogoInsert(parameters)
return None
def execute(self):
from app.scodoc.sco_logos import write_logo
dept_id = self.parameters["dept_key"]
if dept_id == GLOBAL:
dept_id = None

View File

@ -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
le tableau de bord semestre si vous souhaitez inscrire les
étudiants importés à un semestre)</li>
<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>
</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",
@ -228,6 +241,9 @@ def _sem_table_gt(sems, showcodes=False):
if showcodes:
columns_ids = ("formsemestre_id",) + columns_ids
html_class = "stripe cell-border compact hover order-column table_leftalign semlist"
if current_user.has_permission(Permission.ScoEditApo):
html_class += " apo_editable"
tab = GenTable(
titles={
"formsemestre_id": "id",
@ -236,14 +252,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=html_class,
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 +294,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):

View File

@ -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

View File

@ -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},
)

View File

@ -89,6 +89,11 @@ def write_logo(stream, name, dept_id=None):
Logo(logoname=name, dept_id=dept_id).create(stream)
def rename_logo(old_name, new_name, dept_id):
logo = find_logo(old_name, dept_id, True)
logo.rename(new_name)
def list_logos():
"""Crée l'inventaire de tous les logos existants.
L'inventaire se présente comme un dictionnaire de dictionnaire de Logo:
@ -285,6 +290,20 @@ class Logo:
dt = path.stat().st_mtime
return path.stat().st_mtime
def rename(self, new_name):
"""Change le nom (pas le département)
Les éléments non utiles ne sont pas recalculés (car rechargés lors des accès ultérieurs)
"""
old_path = Path(self.filepath)
self.logoname = secure_filename(new_name)
if not self.logoname:
self.logoname = "*** *** nom de logo invalide *** à changer ! *** ***"
else:
new_path = os.path.sep.join(
[self.dirpath, self.prefix + self.logoname + "." + self.suffix]
)
old_path.rename(new_path)
def guess_image_type(stream) -> str:
"guess image type from header in stream"

View File

@ -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,
)

View File

@ -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;
}
@ -1001,6 +1055,14 @@ span.wtf-field ul.errors li {
display: list-item !important;
}
.configuration_logo entete_dept {
display: inline-block;
}
.configuration_logo .effectifs {
float: right;
}
.configuration_logo h1 {
display: inline-block;
}

View File

@ -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();
}
}
}
}

View 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.apo_editable").dataset.apo_save_url;
apo_editor = new ScoFieldEditor(".etapes_apo_str", apo_save_url, false);
});

View File

@ -20,7 +20,7 @@
{% endmacro %}
{% macro render_add_logo(add_logo_form) %}
<details>
<details {{ add_logo_form.opened() }}>
<summary>
<h3>Ajouter un logo</h3>
</summary>
@ -33,7 +33,7 @@
{% endmacro %}
{% macro render_logo(dept_form, logo_form) %}
<details>
<details {{ logo_form.opened() }}>
{{ logo_form.hidden_tag() }}
<summary>
{% if logo_form.titre %}
@ -65,6 +65,11 @@
<span class="wtf-field">{{ render_field(logo_form.upload, False, onchange="submit_form()") }}</span>
</div>
{% if logo_form.can_delete %}
<div class="action_label">Renommer</div>
<div class="action_button">
{{ render_field(logo_form.new_name, False) }}
{{ render_field(logo_form.do_rename, False, onSubmit="submit_form()") }}
</div>
<div class="action_label">Supprimer l'image</div>
<div class="action_button">
{{ render_field(logo_form.do_delete, False, onSubmit="submit_form()") }}
@ -97,20 +102,24 @@
<div class="configuration_logo">
<h1>Bibliothèque de logos</h1>
{% for dept_entry in form.depts.entries %}
<details>
{% set dept_form = dept_entry.form %}
{{ dept_entry.form.hidden_tag() }}
<details {{ dept_form.opened() }}>
<summary>
{% if dept_entry.form.is_local() %}
<h2>Département {{ dept_form.dept_name.data }}</h2>
<div class="sco_help">Les paramètres donnés sont spécifiques à ce département.<br />
Les logos du département se substituent aux logos de même nom définis globalement:</div>
{% else %}
<h2>Logos généraux</h2>
<div class="sco_help">Les images de cette section sont utilisé pour tous les départements,
mais peuvent être redéfinies localement au niveau de chaque département
(il suffit de définir un logo local de même nom)</div>
{% endif %}
<span class="entete_dept">
{% if dept_entry.form.is_local() %}
<h2>Département {{ dept_form.dept_name.data }}</h2>
<h3 class="effectifs">{{ dept_form.count() }}</h3>
<div class="sco_help">Les paramètres donnés sont spécifiques à ce département.<br />
Les logos du département se substituent aux logos de même nom définis globalement:</div>
{% else %}
<h2>Logos généraux</h2>
<h3 class="effectifs">{{ dept_form.count() }}</h3>
<div class="sco_help">Les images de cette section sont utilisé pour tous les départements,
mais peuvent être redéfinies localement au niveau de chaque département
(il suffit de définir un logo local de même nom)</div>
{% endif %}
</span>
</summary>
<div>
{{ render_logos(dept_form) }}

View File

@ -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(

View File

@ -327,6 +327,7 @@ def showEtudLog(etudid, format="html"):
@bp.route("/")
@bp.route("/index_html")
@bp.route("/index")
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func

View File

@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.2.2"
SCOVERSION = "9.2.3"
SCONAME = "ScoDoc"