Merge branch 'refactor_nt' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises

This commit is contained in:
Arthur ZHU 2022-02-02 19:14:17 +01:00
commit 2cc5e1f164
12 changed files with 198 additions and 117 deletions

View File

@ -41,7 +41,6 @@ from app import db
from app.models import ModuleImpl, Evaluation, EvaluationUEPoids from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_codes_parcours import UE_SPORT from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
@ -92,6 +91,10 @@ class ModuleImplResults:
ne donnent pas de coef vers cette UE. ne donnent pas de coef vers cette UE.
""" """
self.load_notes() self.load_notes()
self.etuds_use_session2 = pd.Series(False, index=self.evals_notes.index)
"""1 bool par etud, indique si sa moyenne de module vient de la session2"""
self.etuds_use_rattrapage = pd.Series(False, index=self.evals_notes.index)
"""1 bool par etud, indique si sa moyenne de module utilise la note de rattrapage"""
def load_notes(self): # ré-écriture de df_load_modimpl_notes def load_notes(self): # ré-écriture de df_load_modimpl_notes
"""Charge toutes les notes de toutes les évaluations du module. """Charge toutes les notes de toutes les évaluations du module.
@ -135,8 +138,11 @@ class ModuleImplResults:
eval_df = self._load_evaluation_notes(evaluation) eval_df = self._load_evaluation_notes(evaluation)
# is_complete ssi tous les inscrits (non dem) au semestre ont une note # is_complete ssi tous les inscrits (non dem) au semestre ont une note
# ou évaluation déclarée "à prise en compte immédiate" # ou évaluation déclarée "à prise en compte immédiate"
is_complete = evaluation.publish_incomplete or ( # Les évaluations de rattrapage et 2eme session sont toujours incomplètes
not (inscrits_module - set(eval_df.index)) # car on calcule leur moyenne à part.
is_complete = (evaluation.evaluation_type == scu.EVALUATION_NORMALE) and (
evaluation.publish_incomplete
or (not (inscrits_module - set(eval_df.index)))
) )
self.evaluations_completes.append(is_complete) self.evaluations_completes.append(is_complete)
self.evaluations_completes_dict[evaluation.id] = is_complete self.evaluations_completes_dict[evaluation.id] = is_complete
@ -212,6 +218,33 @@ class ModuleImplResults:
self.evals_notes.values > scu.NOTES_ABSENCE, self.evals_notes.values, 0.0 self.evals_notes.values > scu.NOTES_ABSENCE, self.evals_notes.values, 0.0
) / [e.note_max / 20.0 for e in moduleimpl.evaluations] ) / [e.note_max / 20.0 for e in moduleimpl.evaluations]
def get_evaluation_rattrapage(self, moduleimpl: ModuleImpl):
"""L'évaluation de rattrapage de ce module, ou None s'il n'en a pas.
Rattrapage: la moyenne du module est la meilleure note entre moyenne
des autres évals et la note eval rattrapage.
"""
eval_list = [
e
for e in moduleimpl.evaluations
if e.evaluation_type == scu.EVALUATION_RATTRAPAGE
]
if eval_list:
return eval_list[0]
return None
def get_evaluation_session2(self, moduleimpl: ModuleImpl):
"""L'évaluation de deuxième session de ce module, ou None s'il n'en a pas.
Session 2: remplace la note de moyenne des autres évals.
"""
eval_list = [
e
for e in moduleimpl.evaluations
if e.evaluation_type == scu.EVALUATION_SESSION2
]
if eval_list:
return eval_list[0]
return None
class ModuleImplResultsAPC(ModuleImplResults): class ModuleImplResultsAPC(ModuleImplResults):
"Calcul des moyennes de modules à la mode BUT" "Calcul des moyennes de modules à la mode BUT"
@ -229,7 +262,7 @@ class ModuleImplResultsAPC(ModuleImplResults):
ou NaN si les évaluations (dans lesquelles l'étudiant a des notes) ou NaN si les évaluations (dans lesquelles l'étudiant a des notes)
ne donnent pas de coef vers cette UE. ne donnent pas de coef vers cette UE.
""" """
moduleimpl = ModuleImpl.query.get(self.moduleimpl_id) modimpl = ModuleImpl.query.get(self.moduleimpl_id)
nb_etuds, nb_evals = self.evals_notes.shape nb_etuds, nb_evals = self.evals_notes.shape
nb_ues = evals_poids_df.shape[1] nb_ues = evals_poids_df.shape[1]
assert evals_poids_df.shape[0] == nb_evals # compat notes/poids assert evals_poids_df.shape[0] == nb_evals # compat notes/poids
@ -237,11 +270,11 @@ class ModuleImplResultsAPC(ModuleImplResults):
return pd.DataFrame(index=[], columns=evals_poids_df.columns) return pd.DataFrame(index=[], columns=evals_poids_df.columns)
if nb_ues == 0: if nb_ues == 0:
return pd.DataFrame(index=self.evals_notes.index, columns=[]) return pd.DataFrame(index=self.evals_notes.index, columns=[])
evals_coefs = self.get_evaluations_coefs(moduleimpl) evals_coefs = self.get_evaluations_coefs(modimpl)
evals_poids = evals_poids_df.values * evals_coefs evals_poids = evals_poids_df.values * evals_coefs
# -> evals_poids shape : (nb_evals, nb_ues) # -> evals_poids shape : (nb_evals, nb_ues)
assert evals_poids.shape == (nb_evals, nb_ues) assert evals_poids.shape == (nb_evals, nb_ues)
evals_notes_20 = self.get_eval_notes_sur_20(moduleimpl) evals_notes_20 = self.get_eval_notes_sur_20(modimpl)
# Les poids des évals pour chaque étudiant: là où il a des notes # Les poids des évals pour chaque étudiant: là où il a des notes
# non neutralisées # non neutralisées
@ -262,6 +295,45 @@ class ModuleImplResultsAPC(ModuleImplResults):
etuds_moy_module = np.sum( etuds_moy_module = np.sum(
evals_poids_etuds * evals_notes_stacked, axis=1 evals_poids_etuds * evals_notes_stacked, axis=1
) / np.sum(evals_poids_etuds, axis=1) ) / np.sum(evals_poids_etuds, axis=1)
# Session2 : quand elle existe, remplace la note de module
eval_session2 = self.get_evaluation_session2(modimpl)
if eval_session2:
notes_session2 = self.evals_notes[eval_session2.id].values
# n'utilise que les notes valides (pas ATT, EXC, ABS, NaN)
etuds_use_session2 = notes_session2 > scu.NOTES_ABSENCE
etuds_moy_module = np.where(
etuds_use_session2[:, np.newaxis],
np.tile(
(notes_session2 / (eval_session2.note_max / 20.0))[:, np.newaxis],
nb_ues,
),
etuds_moy_module,
)
self.etuds_use_session2 = pd.Series(
etuds_use_session2, index=self.evals_notes.index
)
else:
# Rattrapage: remplace la note de module ssi elle est supérieure
eval_rat = self.get_evaluation_rattrapage(modimpl)
if eval_rat:
notes_rat = self.evals_notes[eval_rat.id].values
# remplace les notes invalides (ATT, EXC...) par des NaN
notes_rat = np.where(
notes_rat > scu.NOTES_ABSENCE,
notes_rat / (eval_rat.note_max / 20.0),
np.nan,
)
# prend le max
etuds_use_rattrapage = notes_rat > etuds_moy_module
etuds_moy_module = np.where(
etuds_use_rattrapage[:, np.newaxis],
np.tile(notes_rat[:, np.newaxis], nb_ues),
etuds_moy_module,
)
self.etuds_use_rattrapage = pd.Series(
etuds_use_rattrapage, index=self.evals_notes.index
)
self.etuds_moy_module = pd.DataFrame( self.etuds_moy_module = pd.DataFrame(
etuds_moy_module, etuds_moy_module,
index=self.evals_notes.index, index=self.evals_notes.index,
@ -371,8 +443,42 @@ class ModuleImplResultsClassic(ModuleImplResults):
evals_coefs_etuds * evals_notes_20, axis=1 evals_coefs_etuds * evals_notes_20, axis=1
) / np.sum(evals_coefs_etuds, axis=1) ) / np.sum(evals_coefs_etuds, axis=1)
# Session2 : quand elle existe, remplace la note de module
eval_session2 = self.get_evaluation_session2(modimpl)
if eval_session2:
notes_session2 = self.evals_notes[eval_session2.id].values
# n'utilise que les notes valides (pas ATT, EXC, ABS, NaN)
etuds_use_session2 = notes_session2 > scu.NOTES_ABSENCE
etuds_moy_module = np.where(
etuds_use_session2,
notes_session2 / (eval_session2.note_max / 20.0),
etuds_moy_module,
)
self.etuds_use_session2 = pd.Series(
etuds_use_session2, index=self.evals_notes.index
)
else:
# Rattrapage: remplace la note de module ssi elle est supérieure
eval_rat = self.get_evaluation_rattrapage(modimpl)
if eval_rat:
notes_rat = self.evals_notes[eval_rat.id].values
# remplace les notes invalides (ATT, EXC...) par des NaN
notes_rat = np.where(
notes_rat > scu.NOTES_ABSENCE,
notes_rat / (eval_rat.note_max / 20.0),
np.nan,
)
# prend le max
etuds_use_rattrapage = notes_rat > etuds_moy_module
etuds_moy_module = np.where(
etuds_use_rattrapage, notes_rat, etuds_moy_module
)
self.etuds_use_rattrapage = pd.Series(
etuds_use_rattrapage, index=self.evals_notes.index
)
self.etuds_moy_module = pd.Series( self.etuds_moy_module = pd.Series(
etuds_moy_module, etuds_moy_module,
index=self.evals_notes.index, index=self.evals_notes.index,
) )
return self.etuds_moy_module return self.etuds_moy_module

View File

@ -162,6 +162,7 @@ class NotesTableCompat(ResultatsSemestre):
_cached_attrs = ResultatsSemestre._cached_attrs + ( _cached_attrs = ResultatsSemestre._cached_attrs + (
"bonus", "bonus",
"bonus_ues", "bonus_ues",
"malus",
) )
def __init__(self, formsemestre: FormSemestre): def __init__(self, formsemestre: FormSemestre):

View File

@ -22,6 +22,7 @@ from app.models.etudiants import Identite
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.sco_vdi import ApoEtapeVDI from app.scodoc.sco_vdi import ApoEtapeVDI
from app.scodoc.sco_permissions import Permission
class FormSemestre(db.Model): class FormSemestre(db.Model):
@ -169,14 +170,24 @@ class FormSemestre(db.Model):
else: else:
modimpls.sort( modimpls.sort(
key=lambda m: ( key=lambda m: (
m.module.ue.numero, m.module.ue.numero or 0,
m.module.matiere.numero, m.module.matiere.numero or 0,
m.module.numero, m.module.numero or 0,
m.module.code, m.module.code or "",
) )
) )
return modimpls return modimpls
def can_be_edited_by(self, user):
"""Vrai si user peut modifier ce semestre"""
if not user.has_permission(Permission.ScoImplement): # pas chef
if not self.resp_can_edit or user.id not in [
resp.id for resp in self.responsables
]:
return False
return True
def est_courant(self) -> bool: def est_courant(self) -> bool:
"""Vrai si la date actuelle (now) est dans le semestre """Vrai si la date actuelle (now) est dans le semestre
(les dates de début et fin sont incluses) (les dates de début et fin sont incluses)
@ -425,7 +436,7 @@ class FormSemestreUECoef(db.Model):
class FormSemestreUEComputationExpr(db.Model): class FormSemestreUEComputationExpr(db.Model):
"""Formules utilisateurs pour calcul moyenne UE""" """Formules utilisateurs pour calcul moyenne UE (désactivées en 9.2+)."""
__tablename__ = "notes_formsemestre_ue_computation_expr" __tablename__ = "notes_formsemestre_ue_computation_expr"
__table_args__ = (db.UniqueConstraint("formsemestre_id", "ue_id"),) __table_args__ = (db.UniqueConstraint("formsemestre_id", "ue_id"),)

View File

@ -619,7 +619,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
prefs=prefs, prefs=prefs,
) )
if nbeval: # boite autour des evaluations (en pdf) if nbeval: # boite autour des évaluations (en pdf)
P[-1]["_pdf_style"].append( P[-1]["_pdf_style"].append(
("BOX", (1, 1 - nbeval), (-1, 0), 0.2, self.PDF_LIGHT_GRAY) ("BOX", (1, 1 - nbeval), (-1, 0), 0.2, self.PDF_LIGHT_GRAY)
) )

View File

@ -289,7 +289,10 @@ def module_create(
"type": "int", "type": "int",
"title": "UE de rattachement", "title": "UE de rattachement",
"explanation": "utilisée notamment pour les malus", "explanation": "utilisée notamment pour les malus",
"labels": [f"{u.acronyme} {u.titre}" for u in ues], "labels": [
f"S{u.semestre_idx if u.semestre_idx is not None else '.'} / {u.acronyme} {u.titre}"
for u in ues
],
"allowed_values": [u.id for u in ues], "allowed_values": [u.id for u in ues],
}, },
), ),

View File

@ -231,13 +231,17 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
return None return None
def ue_create(formation_id=None): def ue_create(formation_id=None, default_semestre_idx=None):
"""Creation d'une UE""" """Formulaire création d'une UE"""
return ue_edit(create=True, formation_id=formation_id) return ue_edit(
create=True,
formation_id=formation_id,
default_semestre_idx=default_semestre_idx,
)
def ue_edit(ue_id=None, create=False, formation_id=None): def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=None):
"""Modification ou création d'une UE""" """Formulaire modification ou création d'une UE"""
create = int(create) create = int(create)
if not create: if not create:
U = ue_list(args={"ue_id": ue_id}) U = ue_list(args={"ue_id": ue_id})
@ -250,7 +254,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
submitlabel = "Modifier les valeurs" submitlabel = "Modifier les valeurs"
else: else:
title = "Création d'une UE" title = "Création d'une UE"
initvalues = {} initvalues = {"semestre_idx": default_semestre_idx}
submitlabel = "Créer cette UE" submitlabel = "Créer cette UE"
formation = Formation.query.get(formation_id) formation = Formation.query.get(formation_id)
if not formation: if not formation:

View File

@ -207,7 +207,7 @@ def evaluation_create_form(
{ {
"size": 6, "size": 6,
"type": "float", "type": "float",
"explanation": "coef. dans le module (choisi librement par l'enseignant)", "explanation": "coef. dans le module (choisi librement par l'enseignant, non utilisé pour rattrapage et 2ème session)",
"allow_null": False, "allow_null": False,
}, },
) )

View File

@ -118,10 +118,16 @@ def formsemestre_editwithmodules(formsemestre_id):
vals = scu.get_request_args() vals = scu.get_request_args()
if not vals.get("tf_submitted", False): if not vals.get("tf_submitted", False):
H.append( H.append(
"""<p class="help">Seuls les modules cochés font partie de ce semestre. Pour les retirer, les décocher et appuyer sur le bouton "modifier". """<p class="help">Seuls les modules cochés font partie de ce semestre.
</p> Pour les retirer, les décocher et appuyer sur le bouton "modifier".
<p class="help">Attention : s'il y a déjà des évaluations dans un module, il ne peut pas être supprimé !</p> </p>
<p class="help">Les modules ont toujours un responsable. Par défaut, c'est le directeur des études.</p>""" <p class="help">Attention : s'il y a déjà des évaluations dans un module,
il ne peut pas être supprimé !</p>
<p class="help">Les modules ont toujours un responsable.
Par défaut, c'est le directeur des études.</p>
<p class="help">Un semestre ne peut comporter qu'une seule UE "bonus
sport/culture"</p>
"""
) )
return "\n".join(H) + html_sco_header.sco_footer() return "\n".join(H) + html_sco_header.sco_footer()
@ -739,6 +745,7 @@ def do_formsemestre_createwithmodules(edit=False):
# Modules sélectionnés: # Modules sélectionnés:
# (retire le "MI" du début du nom de champs) # (retire le "MI" du début du nom de champs)
module_ids_checked = [int(x[2:]) for x in tf[2]["tf-checked"]] module_ids_checked = [int(x[2:]) for x in tf[2]["tf-checked"]]
_formsemestre_check_ue_bonus_unicity(module_ids_checked)
if not edit: if not edit:
if formation.is_apc(): if formation.is_apc():
_formsemestre_check_module_list( _formsemestre_check_module_list(
@ -882,6 +889,18 @@ def _formsemestre_check_module_list(module_ids, semestre_idx):
) )
def _formsemestre_check_ue_bonus_unicity(module_ids):
"""Vérifie qu'il n'y a qu'une seule UE bonus associée aux modules choisis"""
ues = [Module.query.get_or_404(module_id).ue for module_id in module_ids]
ues_bonus = {ue.id for ue in ues if ue.type == sco_codes_parcours.UE_SPORT}
if len(ues_bonus) > 1:
raise ScoValueError(
"""Les modules de bonus sélectionnés ne sont pas tous dans la même UE bonus.
Changez la sélection ou modifiez la structure du programme de formation.""",
dest_url="javascript:history.back();",
)
def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del): def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
"""Delete moduleimpls """Delete moduleimpls
module_ids_to_del: list of module_id (warning: not moduleimpl) module_ids_to_del: list of module_id (warning: not moduleimpl)

View File

@ -1152,30 +1152,19 @@ def formsemestre_tableau_modules(
f"""<tr class="formsemestre_status_ue"><td colspan="4"> f"""<tr class="formsemestre_status_ue"><td colspan="4">
<span class="status_ue_acro">{ue["acronyme"]}</span> <span class="status_ue_acro">{ue["acronyme"]}</span>
<span class="status_ue_title">{titre}</span> <span class="status_ue_title">{titre}</span>
</td><td>""" </td><td colspan="2">"""
) )
if can_edit:
H.append(
' <a href="edit_ue_expr?formsemestre_id=%s&ue_id=%s">'
% (formsemestre_id, ue["ue_id"])
)
H.append(
scu.icontag(
"formula",
title="Mode calcul moyenne d'UE",
style="vertical-align:middle",
)
)
if can_edit:
H.append("</a>")
expr = sco_compute_moy.get_ue_expression( expr = sco_compute_moy.get_ue_expression(
formsemestre_id, ue["ue_id"], html_quote=True formsemestre_id, ue["ue_id"], html_quote=True
) )
if expr: if expr:
H.append( H.append(
""" <span class="formula" title="mode de calcul de la moyenne d'UE">%s</span>""" f""" <span class="formula" title="mode de calcul de la moyenne d'UE">{expr}</span>
% expr <span class="warning">formule inutilisée en 9.2: <a href="{
url_for("notes.delete_ue_expr", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ue_id=ue["ue_id"] )
}
">supprimer</a></span>"""
) )
H.append("</td></tr>") H.append("</td></tr>")

View File

@ -46,16 +46,17 @@
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endfor %}
{% if editable %} {% if editable %}
<ul> <ul>
<li class="notes_ue_list notes_ue_list_add"><a class="stdlink" href="{{ <li class="notes_ue_list notes_ue_list_add"><a class="stdlink" href="{{
url_for('notes.ue_create', url_for('notes.ue_create',
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
formation_id=formation.id, formation_id=formation.id,
default_semestre_idx=semestre_idx,
)}}" )}}"
>ajouter une UE</a> >ajouter une UE</a>
</li> </li>
</ul> </ul>
{% endif %} {% endif %}
{% endfor %}
</div> </div>

View File

@ -30,23 +30,19 @@ Module notes: issu de ScoDoc7 / ZNotes.py
Emmanuel Viennet, 2021 Emmanuel Viennet, 2021
""" """
import sys
import time
import datetime
import pprint
from operator import itemgetter from operator import itemgetter
from xml.etree import ElementTree from xml.etree import ElementTree
import flask import flask
from flask import url_for, jsonify, render_template from flask import flash, jsonify, render_template, url_for
from flask import current_app, g, request from flask import current_app, g, request
from flask_login import current_user from flask_login import current_user
from werkzeug.utils import redirect from werkzeug.utils import redirect
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
from app.models.formsemestre import FormSemestreUEComputationExpr
from app.models.ues import UniteEns from app.models.ues import UniteEns
from config import Config
from app import api from app import api
from app import db from app import db
from app import models from app import models
@ -1245,76 +1241,28 @@ def view_module_abs(moduleimpl_id, format="html"):
return "\n".join(H) + tab.html() + html_sco_header.sco_footer() return "\n".join(H) + tab.html() + html_sco_header.sco_footer()
@bp.route("/edit_ue_expr", methods=["GET", "POST"]) @bp.route("/delete_ue_expr/<int:formsemestre_id>/<int:ue_id>", methods=["GET", "POST"])
@scodoc @scodoc
@permission_required(Permission.ScoView) def delete_ue_expr(formsemestre_id: int, ue_id: int):
@scodoc7func """Efface une expression de calcul d'UE"""
def edit_ue_expr(formsemestre_id, ue_id): formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
"""Edition formule calcul moyenne UE""" if not formsemestre.can_be_edited_by(current_user):
# Check access
sem = sco_formsemestre_edit.can_edit_sem(formsemestre_id)
if not sem:
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération") raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
cnx = ndb.GetDBConnexion() expr = FormSemestreUEComputationExpr.query.filter_by(
# formsemestre_id=formsemestre_id, ue_id=ue_id
ue = sco_edit_ue.ue_list({"ue_id": ue_id})[0] ).first()
H = [ if expr is not None:
html_sco_header.html_sem_header( db.session.delete(expr)
"Modification règle de calcul de l'UE %s (%s)" db.session.commit()
% (ue["acronyme"], ue["titre"]), flash("formule supprimée")
), return flask.redirect(
_EXPR_HELP % {"target": "de l'UE", "objs": "modules", "ordre": ""}, url_for(
] "notes.formsemestre_status",
el = sco_compute_moy.formsemestre_ue_computation_expr_list( scodoc_dept=g.scodoc_dept,
cnx, {"formsemestre_id": formsemestre_id, "ue_id": ue_id} formsemestre_id=formsemestre_id,
) head_message="formule supprimée",
if el:
initvalues = el[0]
else:
initvalues = {}
form = [
("ue_id", {"input_type": "hidden"}),
("formsemestre_id", {"input_type": "hidden"}),
(
"computation_expr",
{
"title": "Formule de calcul",
"input_type": "textarea",
"rows": 4,
"cols": 60,
"explanation": "formule de calcul (expérimental)",
},
),
]
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
form,
submitlabel="Modifier formule de calcul",
cancelbutton="Annuler",
initvalues=initvalues,
)
if tf[0] == 0:
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
elif tf[0] == -1:
return flask.redirect(
"formsemestre_status?formsemestre_id=" + str(formsemestre_id)
)
else:
if el:
el[0]["computation_expr"] = tf[2]["computation_expr"]
sco_compute_moy.formsemestre_ue_computation_expr_edit(cnx, el[0])
else:
sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, tf[2])
sco_cache.invalidate_formsemestre(
formsemestre_id=formsemestre_id
) # > modif regle calcul
return flask.redirect(
"formsemestre_status?formsemestre_id="
+ str(formsemestre_id)
+ "&head_message=règle%20de%20calcul%20modifiée"
) )
)
@bp.route("/formsemestre_enseignants_list") @bp.route("/formsemestre_enseignants_list")

View File

@ -269,7 +269,6 @@ def create_user_form(user_name=None, edit=0, all_roles=False):
for i in range(len(displayed_roles_strings)): for i in range(len(displayed_roles_strings)):
if displayed_roles_strings[i] not in editable_roles_strings: if displayed_roles_strings[i] not in editable_roles_strings:
disabled_roles[i] = True disabled_roles[i] = True
breakpoint()
descr = [ descr = [
("edit", {"input_type": "hidden", "default": edit}), ("edit", {"input_type": "hidden", "default": edit}),
("nom", {"title": "Nom", "size": 20, "allow_null": False}), ("nom", {"title": "Nom", "size": 20, "allow_null": False}),