debut form editition utilisateur

This commit is contained in:
Emmanuel Viennet 2021-06-28 10:45:00 +02:00
parent d40a2b43cd
commit 46cef02b39
7 changed files with 372 additions and 12 deletions

View File

@ -20,6 +20,8 @@ from app import db, login
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
import app.scodoc.sco_utils as scu
from app.scodoc import sco_etud # a deplacer dans scu
class User(UserMixin, db.Model):
@ -207,6 +209,21 @@ class User(UserMixin, db.Model):
def is_administrator(self):
return self.active and self.has_permission(Permission.ScoSuperAdmin, None)
# Some useful strings:
def get_nomplogin(self):
"""nomplogin est le nom en majuscules suivi du prénom et du login
e.g. Dupont Pierre (dupont)
"""
if self.nom:
n = sco_etud.format_nom(self.nom)
else:
n = scu.strupper(self.user_name)
return "%s %s (%s)" % (
n,
sco_etud.format_prenom(self.prenom),
self.user_name,
)
class AnonymousUser(AnonymousUserMixin):
def has_permission(self, perm, dept=None):

View File

@ -18,7 +18,6 @@ from flask_login import login_required
from flask import current_app
import app
from app.auth.models import Permission
class ZUser(object):
@ -111,6 +110,8 @@ def permission_required(permission):
def admin_required(f):
from app.auth.models import Permission
return permission_required(Permission.ScoSuperAdmin)(f)

View File

@ -46,7 +46,7 @@ def sidebar_common(context, REQUEST=None):
"authuser": str(authuser),
}
H = [
'<a class="scodoc_title" href="about">ScoDoc</a>',
'<a class="scodoc_title" href="about">ScoDoc 8</a>',
'<div id="authuser"><a id="authuserlink" href="%(ScoURL)s/Users/user_info_page">%(authuser)s</a><br/><a id="deconnectlink" href="%(ScoURL)s/acl_users/logout">déconnexion</a></div>'
% params,
sidebar_dept(context, REQUEST),

View File

@ -140,10 +140,10 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
)
# Liste des enseignants avec forme pour affichage / saisie avec suggestion
userlist = [sco_users.user_info(u) for u in sco_users.get_user_list()]
userlist = sco_users.get_user_list()
login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)"
for u in userlist:
login2display[u["user_name"]] = u["nomplogin"]
login2display[u.user_name] = u.get_nomplogin()
allowed_user_names = login2display.values() + [""]
#
formation_id = REQUEST.form["formation_id"]

View File

@ -261,7 +261,7 @@ def _user_list(user_name):
def user_info(user_name=None, user=None):
"""Donne infos sur l'utilisateur (qui peut ne pas etre dans notre base).
"""Dict avec infos sur l'utilisateur (qui peut ne pas etre dans notre base).
Si user_name est specifie, interroge la BD. Sinon, user doit etre un dict.
"""
if user_name:
@ -294,10 +294,7 @@ def user_info(user_name=None, user=None):
if "password_hash" in info:
del info["password_hash"]
#
if info["prenom"]:
p = format_prenom(info["prenom"])
else:
p = ""
if info["nom"]:
n = format_nom(
info["nom"], uppercase=False

View File

@ -663,6 +663,7 @@ sco_publish(
"/formsemestre_edit_options",
sco_formsemestre_edit.formsemestre_edit_options,
Permission.ScoView,
methods=["GET", "POST"],
)
sco_publish(
"/formsemestre_change_lock",

View File

@ -34,8 +34,11 @@ Vues s'appuyant sur auth et sco_users
Emmanuel Viennet, 2021
"""
import jaxml
from flask import g
from flask import current_app, request
from flask_login import current_user
from app.auth.models import Permission
from app.auth.models import User
@ -48,8 +51,10 @@ from app.decorators import (
ZRequest,
)
from app.scodoc import html_sco_header
from app.scodoc import sco_users
from app.scodoc import sco_utils as scu
from app.scodoc.notes_log import log
from app.views import users_bp as bp
@ -79,9 +84,320 @@ def user_info(user_name, format="json", REQUEST=None):
return scu.sendResult(REQUEST, info, name="user", format=format)
@bp.route("/create_user_form")
def create_user_form():
raise NotImplementedError()
@bp.route("/create_user_form", methods=["GET", "POST"])
@permission_required(Permission.ScoUsersAdmin)
@scodoc7func(context)
def create_user_form(context, REQUEST, user_name=None, edit=0):
"form. creation ou edit utilisateur"
auth_dept = current_user.dept
initvalues = {}
edit = int(edit)
H = [html_sco_header.sco_header(context, REQUEST, bodyOnLoad="init_tf_form('')")]
F = html_sco_header.sco_footer(context, REQUEST)
if edit:
if not user_name:
raise ValueError("missing argument: user_name")
initvalues = sco_users._user_list(user_name)
H.append("<h2>Modification de l'utilisateur %s</h2>" % user_name)
else:
H.append("<h2>Création d'un utilisateur</h2>")
is_super_admin = False
if current_user.has_permission(Permission.ScoSuperAdmin, g.scodoc_dept):
H.append("""<p class="warning">Vous êtes super administrateur !</p>""")
is_super_admin = True
# Noms de roles pouvant etre attribues aux utilisateurs via ce dialogue
# si pas SuperAdmin, restreint aux rôles EnsX, SecrX, AdminX
#
editable_roles = Role.query.all()
if is_super_admin:
log("create_user_form called by %s (super admin)" % (current_user.user_name,))
else:
editable_roles = [
r for r in editable_roles if r.name in {u"Ens", u"Secr", u"Admin"}
]
#
if not edit:
submitlabel = "Créer utilisateur"
orig_roles = set()
else:
submitlabel = "Modifier utilisateur"
initvalues["roles"] = initvalues["roles"].split(",") or []
orig_roles = set(initvalues["roles"])
if initvalues["status"] == "old":
editable_roles = set() # can't change roles of a disabled user
# add existing user roles
displayed_roles = list(editable_roles.union(orig_roles))
displayed_roles.sort()
disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer
for i in range(len(displayed_roles)):
if displayed_roles[i] not in editable_roles:
disabled_roles[i] = True
# log('create_user_form: displayed_roles=%s' % displayed_roles)
descr = [
("edit", {"input_type": "hidden", "default": edit}),
("nom", {"title": "Nom", "size": 20, "allow_null": False}),
("prenom", {"title": "Prénom", "size": 20, "allow_null": False}),
]
if auth_name != user_name: # no one can't change its own status
descr.append(
(
"status",
{
"title": "Statut",
"input_type": "radio",
"labels": ("actif", "ancien"),
"allowed_values": ("", "old"),
},
)
)
if not edit:
descr += [
(
"user_name",
{
"title": "Pseudo (login)",
"size": 20,
"allow_null": False,
"explanation": "nom utilisé pour la connexion. Doit être unique parmi tous les utilisateurs.",
},
),
(
"passwd",
{
"title": "Mot de passe",
"input_type": "password",
"size": 14,
"allow_null": False,
},
),
(
"passwd2",
{
"title": "Confirmer mot de passe",
"input_type": "password",
"size": 14,
"allow_null": False,
},
),
]
else:
descr += [
(
"user_name",
{"input_type": "hidden", "default": initvalues["user_name"]},
),
("user_id", {"input_type": "hidden", "default": initvalues["user_id"]}),
]
descr += [
(
"email",
{
"title": "e-mail",
"input_type": "text",
"explanation": "vivement recommandé: utilisé pour contacter l'utilisateur",
"size": 20,
"allow_null": True,
},
)
]
if not auth_dept:
# si auth n'a pas de departement (admin global)
# propose de choisir le dept du nouvel utilisateur
# sinon, il sera créé dans le même département que auth
descr.append(
(
"dept",
{
"title": "Département",
"input_type": "text",
"size": 12,
"allow_null": True,
"explanation": """département d\'appartenance de l\'utilisateur (s'il s'agit d'un administrateur, laisser vide si vous voulez qu'il puisse créer des utilisateurs dans d'autres départements)""",
},
)
)
can_choose_dept = True
else:
can_choose_dept = False
if edit:
descr.append(
(
"d",
{
"input_type": "separator",
"title": "L'utilisateur appartient au département %s"
% auth_dept,
},
)
)
else:
descr.append(
(
"d",
{
"input_type": "separator",
"title": "L'utilisateur sera crée dans le département %s"
% auth_dept,
},
)
)
descr += [
(
"date_expiration",
{
"title": "Date d'expiration", # j/m/a
"input_type": "date",
"explanation": "j/m/a, laisser vide si pas de limite",
"size": 9,
"allow_null": True,
},
),
(
"roles",
{
"title": "Rôles",
"input_type": "checkbox",
"vertical": True,
"allowed_values": displayed_roles,
"disabled_items": disabled_roles,
},
),
(
"force",
{
"title": "Ignorer les avertissements",
"input_type": "checkbox",
"explanation": "passer outre les avertissements (homonymes, etc)",
"labels": ("",),
"allowed_values": ("1",),
},
),
]
if "tf-submitted" in REQUEST.form and not "roles" in REQUEST.form:
REQUEST.form["roles"] = []
if "tf-submitted" in REQUEST.form:
# Ajoute roles existants mais non modifiables (disabled dans le form)
# orig_roles - editable_roles
REQUEST.form["roles"] = list(
set(REQUEST.form["roles"]).union(orig_roles - editable_roles)
)
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
descr,
initvalues=initvalues,
submitlabel=submitlabel,
cancelbutton="Annuler",
)
if tf[0] == 0:
return "\n".join(H) + "\n" + tf[1] + F
elif tf[0] == -1:
return REQUEST.RESPONSE.redirect(context.UsersURL())
else:
vals = tf[2]
roles = set(vals["roles"]).intersection(editable_roles)
if REQUEST.form.has_key("edit"):
edit = int(REQUEST.form["edit"])
else:
edit = 0
try:
force = int(vals["force"][0])
except:
force = 0
if edit:
user_name = initvalues["user_name"]
else:
user_name = vals["user_name"]
# ce login existe ?
err = None
users = _user_list(user_name)
if edit and not users: # safety net, le user_name ne devrait pas changer
err = "identifiant %s inexistant" % user_name
if not edit and users:
err = "identifiant %s déjà utilisé" % user_name
if err:
H.append(tf_error_message("""Erreur: %s""" % err))
return "\n".join(H) + "\n" + tf[1] + F
if not force:
ok, msg = context._check_modif_user(
edit,
user_name=user_name,
nom=vals["nom"],
prenom=vals["prenom"],
email=vals["email"],
roles=vals["roles"],
)
if not ok:
H.append(
tf_error_message(
"""Attention: %s (vous pouvez forcer l'opération en cochant "<em>Ignorer les avertissements</em>" en bas de page)"""
% msg
)
)
return "\n".join(H) + "\n" + tf[1] + F
if edit: # modif utilisateur (mais pas passwd)
if (not can_choose_dept) and vals.has_key("dept"):
del vals["dept"]
if vals.has_key("passwd"):
del vals["passwd"]
if vals.has_key("date_modif_passwd"):
del vals["date_modif_passwd"]
if vals.has_key("user_name"):
del vals["user_name"]
if (auth_name == user_name) and vals.has_key("status"):
del vals["status"] # no one can't change its own status
# traitement des roles: ne doit pas affecter les roles
# que l'on en controle pas:
for role in orig_roles:
if role and not role in editable_roles:
roles.add(role)
vals["roles"] = ",".join(roles)
# ok, edit
log("sco_users: editing %s by %s" % (user_name, auth_name))
# log('sco_users: previous_values=%s' % initvalues)
# log('sco_users: new_values=%s' % vals)
context._user_edit(user_name, vals)
return REQUEST.RESPONSE.redirect(
"userinfo?user_name=%s&head_message=Utilisateur %s modifié"
% (user_name, user_name)
)
else: # creation utilisateur
vals["roles"] = ",".join(vals["roles"])
# check identifiant
if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]+$", vals["user_name"]):
msg = tf_error_message(
"identifiant invalide (pas d'accents ni de caractères spéciaux)"
)
return "\n".join(H) + msg + "\n" + tf[1] + F
# check passwords
if vals["passwd"] != vals["passwd2"]:
msg = tf_error_message(
"""Les deux mots de passes ne correspondent pas !"""
)
return "\n".join(H) + msg + "\n" + tf[1] + F
if not sco_users.is_valid_password(vals["passwd"]):
msg = tf_error_message("""Mot de passe trop simple, recommencez !""")
return "\n".join(H) + msg + "\n" + tf[1] + F
if not can_choose_dept:
vals["dept"] = auth_dept
# ok, go
log("sco_users: new_user %s by %s" % (vals["user_name"], auth_name))
context.create_user(vals, REQUEST=REQUEST)
@bp.route("/import_users_form")
@ -90,6 +406,34 @@ def import_users_form():
@bp.route("/user_info_page")
@permission_required(Permission.ScoUsersView)
@scodoc7func(context)
def user_info_page(user_name, REQUEST=None):
return sco_users.user_info_page(context, user_name=user_name, REQUEST=REQUEST)
@bp.route("/get_user_list_xml")
@permission_required(Permission.ScoView)
@scodoc7func(context)
def get_user_list_xml(context, dept=None, start="", limit=25, REQUEST=None):
"""Returns XML list of users with name (nomplogin) starting with start.
Used for forms auto-completion."""
userlist = sco_users.get_user_list(dept=dept)
start = scu.suppression_diacritics(unicode(start, "utf-8"))
start = scu.strlower(str(start))
# TODO : à refaire avec une requete SQL #py3
# (et en json)
userlist = [
user
for user in userlist
if scu.suppress_accents(scu.strlower(user.nom)).startswith(start)
]
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
doc = jaxml.XML_document(encoding=scu.SCO_ENCODING)
doc.results()
for user in userlist[:limit]:
doc._push()
doc.rs(user["nomplogin"], id=user["user_id"], info="")
doc._pop()
return repr(doc)