forked from ScoDoc/ScoDoc
placement fait
This commit is contained in:
parent
37484b7fc9
commit
050e54de3e
@ -1046,7 +1046,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
|
|||||||
resp = u["prenomnom"]
|
resp = u["prenomnom"]
|
||||||
nomcomplet = u["nomcomplet"]
|
nomcomplet = u["nomcomplet"]
|
||||||
can_edit = sco_permissions_check.can_edit_notes(
|
can_edit = sco_permissions_check.can_edit_notes(
|
||||||
current_user, moduleimpl_id, allow_ens=False
|
, moduleimpl_id, allow_ens=False
|
||||||
)
|
)
|
||||||
|
|
||||||
link = (
|
link = (
|
||||||
|
@ -111,7 +111,7 @@ class PlacementForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
submit = SubmitField("OK")
|
submit = SubmitField("OK")
|
||||||
|
|
||||||
def _set_evaluation_infos(self, evaluation_id):
|
def set_evaluation_infos(self, evaluation_id):
|
||||||
eval_data = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
eval_data = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||||
if not eval_data:
|
if not eval_data:
|
||||||
raise ScoValueError("invalid evaluation_id")
|
raise ScoValueError("invalid evaluation_id")
|
||||||
@ -146,7 +146,7 @@ def placement_eval_selectetuds(evaluation_id, REQUEST=None):
|
|||||||
request.form,
|
request.form,
|
||||||
data={"evaluation_id": int(evaluation_id), "groups": PlacementForm.TOUS},
|
data={"evaluation_id": int(evaluation_id), "groups": PlacementForm.TOUS},
|
||||||
)
|
)
|
||||||
form._set_evaluation_infos(evaluation_id)
|
form.set_evaluation_infos(evaluation_id)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
exec_placement(form)
|
exec_placement(form)
|
||||||
return flask.redirect(titi())
|
return flask.redirect(titi())
|
||||||
@ -154,27 +154,6 @@ def placement_eval_selectetuds(evaluation_id, REQUEST=None):
|
|||||||
H.append(sco_evaluations.evaluation_describe(evaluation_id=evaluation_id))
|
H.append(sco_evaluations.evaluation_describe(evaluation_id=evaluation_id))
|
||||||
H.append("<h3>Placement et émargement des étudiants</h3>")
|
H.append("<h3>Placement et émargement des étudiants</h3>")
|
||||||
H.append(render_template("forms/placement.html", form=form))
|
H.append(render_template("forms/placement.html", form=form))
|
||||||
H.append(
|
|
||||||
"""<h3>Explications</h3>
|
|
||||||
<ul>
|
|
||||||
<li>préciser les surveillants et la localisation (bâtiment et salle) et indiquer le nombre de colonnes;</li>
|
|
||||||
<li>deux types de placements sont possibles :
|
|
||||||
<ul>
|
|
||||||
<li>continue suppose que les tables ont toutes un numéro unique;</li>
|
|
||||||
<li>coordonnées localise chaque table via un numéro de colonne et un numéro de ligne (ou rangée).</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>Choisir le format du fichier résultat :
|
|
||||||
<ul>
|
|
||||||
<li>le format pdf consiste en un tableau précisant pour chaque étudiant la localisation de sa table;</li>
|
|
||||||
<li>le format xls produit un classeur avec deux onglets:
|
|
||||||
<ul>
|
|
||||||
<li>le premier onglet donne une vue de la salle avec la localisation des étudiants et
|
|
||||||
peut servir de feuille d'émargement;</li>
|
|
||||||
<li>le second onglet est un tableau similaire à celui du fichier pdf;</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul> </li>
|
|
||||||
</ul> """
|
|
||||||
)
|
|
||||||
F = html_sco_header.sco_footer()
|
F = html_sco_header.sco_footer()
|
||||||
return "\n".join(H) + "<p>" + F
|
return "\n".join(H) + "<p>" + F
|
||||||
|
|
||||||
@ -323,17 +302,17 @@ def do_placement_selectetuds():
|
|||||||
for x in group_ids
|
for x in group_ids
|
||||||
]
|
]
|
||||||
query = (
|
query = (
|
||||||
"evaluation_id=%s&placement_method=%s&teachers=%s&building=%s&room=%s&columns=%s&numbering=%s&"
|
"evaluation_id=%s&placement_method=%s&teachers=%s&building=%s&room=%s&columns=%s&numbering=%s&"
|
||||||
% (
|
% (
|
||||||
evaluation_id,
|
evaluation_id,
|
||||||
placement_method,
|
placement_method,
|
||||||
teachers,
|
teachers,
|
||||||
building,
|
building,
|
||||||
room,
|
room,
|
||||||
columns,
|
columns,
|
||||||
numbering,
|
numbering,
|
||||||
)
|
)
|
||||||
+ "&".join(gs)
|
+ "&".join(gs)
|
||||||
)
|
)
|
||||||
return flask.redirect(scu.NotesURL() + "/do_placement?" + query)
|
return flask.redirect(scu.NotesURL() + "/do_placement?" + query)
|
||||||
else:
|
else:
|
||||||
@ -342,45 +321,78 @@ def do_placement_selectetuds():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def do_placement(REQUEST):
|
def exec_placement(form):
|
||||||
"""
|
|
||||||
Choisi le placement
|
|
||||||
"""
|
|
||||||
authuser = REQUEST.AUTHENTICATED_USER
|
|
||||||
authusername = str(authuser)
|
|
||||||
try:
|
try:
|
||||||
evaluation_id = int(REQUEST.form["evaluation_id"])
|
evaluation_id = int(form["evaluation_id"].data)
|
||||||
except:
|
except:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"Formulaire incomplet ! Vous avez sans doute attendu trop longtemps, veuillez vous reconnecter. Si le problème persiste, contacter l'administrateur. Merci."
|
"Formulaire incomplet ! Vous avez sans doute attendu trop longtemps, veuillez vous reconnecter. Si le problème persiste, contacter l'administrateur. Merci."
|
||||||
)
|
)
|
||||||
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
eval_data = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||||||
# Check access
|
# Check access
|
||||||
# (admin, respformation, and responsable_id)
|
# (admin, respformation, and responsable_id)
|
||||||
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
|
if not sco_permissions_check.can_edit_notes(
|
||||||
|
current_user, eval_data["moduleimpl_id"]
|
||||||
|
):
|
||||||
return (
|
return (
|
||||||
"<h2>Génération du placement impossible pour %s</h2>" % authusername
|
"<h2>Génération du placement impossible pour %s</h2>" % authusername
|
||||||
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||||||
avez l'autorisation d'effectuer cette opération)</p>
|
avez l'autorisation d'effectuer cette opération)</p>
|
||||||
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
||||||
"""
|
"""
|
||||||
% E["moduleimpl_id"]
|
% E["moduleimpl_id"]
|
||||||
)
|
)
|
||||||
|
plan = repartition(form, eval_data)
|
||||||
|
breakpoint()
|
||||||
|
sem_preferences = sco_preferences.SemPreferences()
|
||||||
|
space = sem_preferences.get("feuille_placement_emargement")
|
||||||
|
maxlines = sem_preferences.get("feuille_placement_positions")
|
||||||
|
|
||||||
|
|
||||||
|
def repartition(form, eval_data):
|
||||||
|
"""
|
||||||
|
Calcule le placement. retourne une liste de couples ((nom, prenom), position)
|
||||||
|
"""
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
# Infos transmises
|
# Infos transmises
|
||||||
placement_method = REQUEST.form["placement_method"]
|
evaluation_id = form["evaluation_id"].data
|
||||||
teachers = REQUEST.form["teachers"]
|
etiquetage = form["etiquetage"].data
|
||||||
building = REQUEST.form["building"]
|
teachers = form["surveillants"].data
|
||||||
room = REQUEST.form["room"]
|
building = form["batiment"].data
|
||||||
columns = REQUEST.form["columns"]
|
room = form["salle"].data
|
||||||
numbering = REQUEST.form["numbering"]
|
nb_rangs = form["nb_rangs"].data
|
||||||
|
group_ids = form["groups"].data
|
||||||
|
|
||||||
# Construit liste des etudiants
|
# Construit liste des etudiants
|
||||||
group_ids = REQUEST.form.get("group_ids", [])
|
|
||||||
groups = sco_groups.listgroups(group_ids)
|
groups = sco_groups.listgroups(group_ids)
|
||||||
gr_title_filename = sco_groups.listgroups_filename(groups)
|
gr_title_filename = sco_groups.listgroups_filename(groups)
|
||||||
# gr_title = sco_groups.listgroups_abbrev(groups)
|
# gr_title = sco_groups.listgroups_abbrev(groups)
|
||||||
|
|
||||||
|
moduleimpl_data = sco_moduleimpl.do_moduleimpl_list(
|
||||||
|
moduleimpl_id=eval_data["moduleimpl_id"]
|
||||||
|
)[0]
|
||||||
|
Mod = sco_edit_module.do_module_list(
|
||||||
|
args={"module_id": moduleimpl_data["module_id"]}
|
||||||
|
)[0]
|
||||||
|
sem = sco_formsemestre.get_formsemestre(moduleimpl_data["formsemestre_id"])
|
||||||
|
evalname = "%s-%s" % (Mod["code"], ndb.DateDMYtoISO(eval_data["jour"]))
|
||||||
|
if eval_data["description"]:
|
||||||
|
evaltitre = eval_data["description"]
|
||||||
|
else:
|
||||||
|
evaltitre = "évaluation du %s" % eval_data["jour"]
|
||||||
|
|
||||||
|
desceval = [
|
||||||
|
["%s" % sem["titreannee"]],
|
||||||
|
["Module : %s - %s" % (Mod["code"], Mod["abbrev"])],
|
||||||
|
["Surveillants : %s" % teachers],
|
||||||
|
["Batiment : %s - Salle : %s" % (building, room)],
|
||||||
|
["Controle : %s (coef. %g)" % (evaltitre, eval_data["coefficient"])]
|
||||||
|
] # une liste de liste de chaines: description de l'evaluation
|
||||||
|
listetud = build_listetud(cnx, groups, evaluation_id, moduleimpl_data)
|
||||||
|
return affectation_places(listetud, etiquetage, nb_rangs)
|
||||||
|
|
||||||
|
|
||||||
|
def build_listetud(cnx, groups, evaluation_id, moduleimpl_data):
|
||||||
if None in [g["group_name"] for g in groups]: # tous les etudiants
|
if None in [g["group_name"] for g in groups]: # tous les etudiants
|
||||||
getallstudents = True
|
getallstudents = True
|
||||||
gr_title_filename = "tous"
|
gr_title_filename = "tous"
|
||||||
@ -389,47 +401,70 @@ def do_placement(REQUEST):
|
|||||||
etudids = sco_groups.do_evaluation_listeetuds_groups(
|
etudids = sco_groups.do_evaluation_listeetuds_groups(
|
||||||
evaluation_id, groups, getallstudents=getallstudents, include_dems=True
|
evaluation_id, groups, getallstudents=getallstudents, include_dems=True
|
||||||
)
|
)
|
||||||
if not etudids:
|
|
||||||
return "<p>Aucun groupe sélectionné !</p>"
|
|
||||||
|
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
|
||||||
Mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
|
|
||||||
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
|
|
||||||
evalname = "%s-%s" % (Mod["code"], ndb.DateDMYtoISO(E["jour"]))
|
|
||||||
if E["description"]:
|
|
||||||
evaltitre = E["description"]
|
|
||||||
else:
|
|
||||||
evaltitre = "évaluation du %s" % E["jour"]
|
|
||||||
|
|
||||||
desceval = [] # une liste de liste de chaines: description de l'evaluation
|
|
||||||
desceval.append(["%s" % sem["titreannee"]])
|
|
||||||
desceval.append(["Module : %s - %s" % (Mod["code"], Mod["abbrev"])])
|
|
||||||
desceval.append(["Surveillants : %s" % teachers])
|
|
||||||
desceval.append(["Batiment : %s - Salle : %s" % (building, room)])
|
|
||||||
desceval.append(["Controle : %s (coef. %g)" % (evaltitre, E["coefficient"])])
|
|
||||||
|
|
||||||
listetud = [] # liste de couples (nom,prenom)
|
listetud = [] # liste de couples (nom,prenom)
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
# infos identite etudiant (xxx sous-optimal: 1/select par etudiant)
|
# infos identite etudiant (xxx sous-optimal: 1/select par etudiant)
|
||||||
ident = sco_etud.etudident_list(cnx, {"etudid": etudid})[0]
|
ident = sco_etud.etudident_list(cnx, {"etudid": etudid})[0]
|
||||||
# infos inscription
|
# infos inscription
|
||||||
inscr = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
inscr = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
{"etudid": etudid, "formsemestre_id": M["formsemestre_id"]}
|
{"etudid": etudid, "formsemestre_id": moduleimpl_data["formsemestre_id"]}
|
||||||
)[0]
|
)[0]
|
||||||
if inscr["etat"] != "D":
|
if inscr["etat"] != "D":
|
||||||
nom = ident["nom"].upper()
|
nom = ident["nom"].upper()
|
||||||
prenom = ident["prenom"].lower().capitalize()
|
prenom = ident["prenom"].lower().capitalize()
|
||||||
listetud.append((nom, prenom))
|
listetud.append((nom, prenom))
|
||||||
random.shuffle(listetud)
|
random.shuffle(listetud)
|
||||||
|
return listetud
|
||||||
|
|
||||||
sem_preferences = sco_preferences.SemPreferences()
|
|
||||||
space = sem_preferences.get("feuille_placement_emargement")
|
|
||||||
maxlines = sem_preferences.get("feuille_placement_positions")
|
|
||||||
|
|
||||||
if placement_method == "xls":
|
class DistributeurContinu:
|
||||||
|
"""Distribue les places selon un ordre numérique."""
|
||||||
|
|
||||||
|
def __init(self):
|
||||||
|
self.position = 1
|
||||||
|
|
||||||
|
def suivant(self):
|
||||||
|
retour = self.position
|
||||||
|
self.position += 1
|
||||||
|
return retour
|
||||||
|
|
||||||
|
|
||||||
|
class Distributeur2D:
|
||||||
|
"""Distribue les places selon des coordonnées sur nb_rangs."""
|
||||||
|
|
||||||
|
def __init__(self, nb_rangs):
|
||||||
|
self.nb_rangs = nb_rangs
|
||||||
|
self.rang = 1
|
||||||
|
self.index = 1
|
||||||
|
|
||||||
|
def suivant(self):
|
||||||
|
retour = (self.index, self.rang)
|
||||||
|
self.rang += 1
|
||||||
|
if self.rang > self.nb_rangs:
|
||||||
|
self.rang = 1
|
||||||
|
self.index += 1
|
||||||
|
return retour
|
||||||
|
|
||||||
|
|
||||||
|
def affectation_places(listetud, etiquetage, nb_rangs=1):
|
||||||
|
affectation = []
|
||||||
|
if etiquetage == "continu":
|
||||||
|
distributeur = DistributeurContinu()
|
||||||
|
else:
|
||||||
|
distributeur = Distributeur2D(nb_rangs)
|
||||||
|
for etud in listetud:
|
||||||
|
affectation.append((etud, distributeur.suivant()))
|
||||||
|
return affectation
|
||||||
|
|
||||||
|
|
||||||
|
def production_xls(file_format, eval_dat, plan):
|
||||||
|
|
||||||
|
|
||||||
|
def production(file_format, eval_dat, plan):
|
||||||
|
if file_format == "xls":
|
||||||
filename = f"placement_{evalname}_{gr_title_filename}{scu.XLSX_SUFFIX}"
|
filename = f"placement_{evalname}_{gr_title_filename}{scu.XLSX_SUFFIX}"
|
||||||
xls = _excel_feuille_placement(
|
xls = _excel_feuille_placement(
|
||||||
E, desceval, listetud, columns, space, maxlines, building, room, numbering
|
eval_data, desceval, listetud, columns, space, maxlines, building, room, numbering
|
||||||
)
|
)
|
||||||
return sco_excel.send_excel_file(REQUEST, xls, filename)
|
return sco_excel.send_excel_file(REQUEST, xls, filename)
|
||||||
else:
|
else:
|
||||||
@ -495,8 +530,8 @@ def do_placement(REQUEST):
|
|||||||
rows=rows,
|
rows=rows,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
origin="Généré par %s le " % sco_version.SCONAME
|
origin="Généré par %s le " % sco_version.SCONAME
|
||||||
+ scu.timedate_human_repr()
|
+ scu.timedate_human_repr()
|
||||||
+ "",
|
+ "",
|
||||||
pdf_title=pdf_title,
|
pdf_title=pdf_title,
|
||||||
# pdf_shorttitle = '',
|
# pdf_shorttitle = '',
|
||||||
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
|
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
|
||||||
@ -669,16 +704,16 @@ def _titres(ws, description, evaluation, building, room, styles):
|
|||||||
|
|
||||||
|
|
||||||
def _feuille0(
|
def _feuille0(
|
||||||
ws0,
|
ws0,
|
||||||
description,
|
description,
|
||||||
evaluation,
|
evaluation,
|
||||||
styles,
|
styles,
|
||||||
numbering,
|
numbering,
|
||||||
listetud,
|
listetud,
|
||||||
nbcolumns,
|
nbcolumns,
|
||||||
building,
|
building,
|
||||||
room,
|
room,
|
||||||
space,
|
space,
|
||||||
):
|
):
|
||||||
_titres(ws0, description, evaluation, building, room, styles)
|
_titres(ws0, description, evaluation, building, room, styles)
|
||||||
# entetes colonnes - feuille0
|
# entetes colonnes - feuille0
|
||||||
@ -766,16 +801,16 @@ def _next_page(ws):
|
|||||||
|
|
||||||
|
|
||||||
def _feuille1(
|
def _feuille1(
|
||||||
ws,
|
ws,
|
||||||
description,
|
description,
|
||||||
evaluation,
|
evaluation,
|
||||||
styles,
|
styles,
|
||||||
numbering,
|
numbering,
|
||||||
maxlines,
|
maxlines,
|
||||||
nbcolumns,
|
nbcolumns,
|
||||||
building,
|
building,
|
||||||
room,
|
room,
|
||||||
listetud,
|
listetud,
|
||||||
):
|
):
|
||||||
# etudiants - feuille1
|
# etudiants - feuille1
|
||||||
# structuration:
|
# structuration:
|
||||||
@ -832,15 +867,15 @@ def _feuille1(
|
|||||||
|
|
||||||
|
|
||||||
def _excel_feuille_placement(
|
def _excel_feuille_placement(
|
||||||
evaluation,
|
evaluation,
|
||||||
description,
|
description,
|
||||||
listetud,
|
listetud,
|
||||||
columns,
|
columns,
|
||||||
space,
|
space,
|
||||||
maxlines,
|
maxlines,
|
||||||
building,
|
building,
|
||||||
room,
|
room,
|
||||||
numbering,
|
numbering,
|
||||||
):
|
):
|
||||||
"""Genere feuille excel pour placement des etudiants.
|
"""Genere feuille excel pour placement des etudiants.
|
||||||
E: evaluation (dict)
|
E: evaluation (dict)
|
||||||
@ -862,7 +897,7 @@ def _excel_feuille_placement(
|
|||||||
ws0.set_column_dimension_width("A", 750 * column_width_ratio)
|
ws0.set_column_dimension_width("A", 750 * column_width_ratio)
|
||||||
for col in range(nbcolumns):
|
for col in range(nbcolumns):
|
||||||
ws0.set_column_dimension_width(
|
ws0.set_column_dimension_width(
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"[col + 1 : col + 2], width
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"[col + 1: col + 2], width
|
||||||
)
|
)
|
||||||
|
|
||||||
SheetName1 = "Positions"
|
SheetName1 = "Positions"
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{% macro render_field(field) %}
|
{% macro render_field(field) %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="wtf-field">{{ field.label }}</td>
|
<td class="wtf-field">{{ field.label }}</td>
|
||||||
<td class="wtf-field">{{ field(**kwargs)|safe }}
|
<td class="wtf-field">{{ field()|safe }}
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
<ul class=errors>
|
<ul class=errors>
|
||||||
{% for error in field.errors %}
|
{% for error in field.errors %}
|
||||||
@ -18,6 +18,7 @@
|
|||||||
<div class="saisienote_etape1 form_placement">
|
<div class="saisienote_etape1 form_placement">
|
||||||
<form method=post>
|
<form method=post>
|
||||||
{{ form.evaluation_id }}
|
{{ form.evaluation_id }}
|
||||||
|
{{ form.csrf_token }}
|
||||||
<table class="tf">
|
<table class="tf">
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ render_field(form.surveillants) }}
|
{{ render_field(form.surveillants) }}
|
||||||
@ -51,6 +52,25 @@
|
|||||||
<input id="gr_cancel" type=submit value="Annuler">
|
<input id="gr_cancel" type=submit value="Annuler">
|
||||||
</script>
|
</script>
|
||||||
</form>
|
</form>
|
||||||
|
<h3>Explications</h3>
|
||||||
|
<ul>
|
||||||
|
<li>préciser les surveillants et la localisation (bâtiment et salle) et indiquer le nombre de colonnes;</li>
|
||||||
|
<li>deux types de placements sont possibles :
|
||||||
|
<ul>
|
||||||
|
<li>continue suppose que les tables ont toutes un numéro unique;</li>
|
||||||
|
<li>coordonnées localise chaque table via un numéro de colonne et un numéro de ligne (ou rangée).</li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Choisir le format du fichier résultat :
|
||||||
|
<ul>
|
||||||
|
<li>le format pdf consiste en un tableau précisant pour chaque étudiant la localisation de sa table;</li>
|
||||||
|
<li>le format xls produit un classeur avec deux onglets:
|
||||||
|
<ul>
|
||||||
|
<li>le premier onglet donne une vue de la salle avec la localisation des étudiants et
|
||||||
|
peut servir de feuille d'émargement;</li>
|
||||||
|
<li>le second onglet est un tableau similaire à celui du fichier pdf;</li>
|
||||||
|
</ul></li>
|
||||||
|
</ul> </li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -1643,7 +1643,6 @@ sco_publish(
|
|||||||
Permission.ScoEnsView,
|
Permission.ScoEnsView,
|
||||||
methods=["GET", "POST"],
|
methods=["GET", "POST"],
|
||||||
)
|
)
|
||||||
sco_publish("/do_placement", sco_placement.do_placement, Permission.ScoEnsView)
|
|
||||||
|
|
||||||
# --- Saisie des notes
|
# --- Saisie des notes
|
||||||
sco_publish(
|
sco_publish(
|
||||||
|
Loading…
Reference in New Issue
Block a user