Skip to content

Commit 9caa635

Browse files
authored
Merge pull request #1711 from RogerHaase/1707-settings
User Settings > Personal allows creation of invalid names fixes #1707
2 parents f01baa3 + 53fb91e commit 9caa635

File tree

8 files changed

+72
-32
lines changed

8 files changed

+72
-32
lines changed

docs/user/accounts.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ wiki experience, each of these sub-pages are listed below:
5454
Personal Settings
5555
-----------------
5656

57-
Personal settings include wiki language and locale, username and alias.
57+
Personal settings include wiki language and locale, name, alias and display-name.
5858

5959
Name
6060
Your username, as it will appear on the login form, the history pages of wiki items
@@ -65,8 +65,10 @@ Name
6565
Alias names are only useful at login.
6666

6767
Display-Name
68-
The display name can be used to override your username, so you will still log in using your username
69-
but your display name will be displayed to other users and in your history page.
68+
If your wiki has a custom auth method that creates cryptic user names, then
69+
the display-name can be created as an alternative. You will still login using your username
70+
or alias. The display-name will appear as links in history pages and the footer of items you have edited.
71+
Use your display-name to create your home page in the users namespace.
7072

7173
Timezone
7274
Setting this value will display edit times converted to your local time zone. For

src/moin/apps/admin/templates/admin/userbrowser.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{#
22
Display a table of user data collected from userprofiles metadata.
3-
The report includes users name(s), email address, group memberships,
3+
The report includes users name(s), display_name, email address, group memberships,
44
and subscriptions. Action buttons include links to disable/enable a user's account,
55
email a password reset, and display a User ACL Report.
66
#}
@@ -12,6 +12,7 @@ <h1>{{ _("Users") }}</h1>
1212
<thead>
1313
<tr>
1414
<th>{{ _("User name") }}</th>
15+
<th>{{ _("Display name") }}</th>
1516
<th>{{ _("Email address") }}</th>
1617
<th class="center" colspan="3" data-sorter="false">{{ _("Actions") }}</th>
1718
<th>{{ _("Groups") }}</th>
@@ -22,6 +23,7 @@ <h1>{{ _("Users") }}</h1>
2223
{% for u in user_accounts %}
2324
<tr>
2425
<td><a href="{{ url_for('frontend.show_item', item_name=u.fqname) }}">{{ u.name|join(', ') }}</a>{{ u.disabled and " (%s)" % _("disabled") or "" }}</td>
26+
<td>{{ u.display_name }}</td>
2527
<td>
2628
{%- if u.email -%}
2729
<a href="mailto:{{ u.email|e }}" class="mailto">{{ u.email|e }}</a>

src/moin/apps/admin/views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from moin import user
3232
from moin.constants.keys import (
3333
NAME,
34+
DISPLAY_NAME,
3435
ITEMID,
3536
SIZE,
3637
EMAIL,
@@ -180,6 +181,7 @@ def userbrowser():
180181
user_accounts = []
181182
for rev in revs:
182183
user_names = rev.meta[NAME]
184+
display_name = rev.meta.get(DISPLAY_NAME, "")
183185
user_groups = member_groups.get(user_names[0], [])
184186
for name in user_names[1:]:
185187
user_groups = user_groups + member_groups.get(name, [])
@@ -188,6 +190,7 @@ def userbrowser():
188190
dict(
189191
uid=rev.meta[ITEMID],
190192
name=user_names,
193+
display_name=display_name,
191194
fqname=CompositeName(NAMESPACE_USERS, NAME_EXACT, rev.name),
192195
email=rev.meta[EMAIL] if EMAIL in rev.meta else rev.meta[EMAIL_UNVALIDATED],
193196
disabled=rev.meta[DISABLED],

src/moin/apps/frontend/views.py

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
from werkzeug.utils import secure_filename
3636

37-
from flask import request, url_for, flash, Response, make_response, redirect, abort, jsonify
37+
from flask import request, url_for, flash, Response, make_response, redirect, abort, jsonify, session
3838
from flask import current_app as app
3939
from flask import g as flaskg
4040
from flask_babel import format_datetime
@@ -95,6 +95,7 @@
9595
from moin.constants.itemtypes import ITEMTYPE_DEFAULT, ITEMTYPE_TICKET
9696
from moin.constants.contenttypes import * # noqa
9797
from moin.constants.rights import SUPERUSER
98+
from moin.constants.misc import FLASH_REPEAT
9899
from moin.utils import crypto, rev_navigation, close_file, show_time, utcfromtimestamp
99100
from moin.utils.crypto import make_uuid, hash_hexdigest
100101
from moin.utils.interwiki import url_for_item, split_fqname, CompositeName
@@ -2366,12 +2367,36 @@ def usersettings():
23662367
# TODO: maybe "is_xhr = request.method == 'POST'" would work
23672368
is_xhr = request.accept_mimetypes.best in ("application/json", "text/javascript")
23682369

2370+
class ValidUserSettingsPersonal(Validator):
2371+
"""Validator for settings personal change, name, display-name"""
2372+
2373+
def validate(self, element, state):
2374+
invalid_id_in_use_msg = L_("This name is already in use: ")
2375+
invalid_character_msg = L_("The Display-Name contains invalid characters: ")
2376+
invalid_character_message = L_("The Username contains invalid characters: ")
2377+
errors = []
2378+
if set(form["name"].value) != set(flaskg.user.name):
2379+
new_names = set(form["name"].value) - set(flaskg.user.name)
2380+
for name in new_names:
2381+
if user.search_users(**{NAME_EXACT: name}):
2382+
# duplicate name
2383+
errors.append(invalid_id_in_use_msg + name)
2384+
if not user.normalizeName(name) == name:
2385+
errors.append(invalid_character_message + name)
2386+
display_name = form[DISPLAY_NAME].value
2387+
if display_name:
2388+
if not user.normalizeName(display_name) == display_name:
2389+
errors.append(invalid_character_msg + display_name)
2390+
if errors:
2391+
return self.note_error(element, state, message=", ".join(errors))
2392+
return True
2393+
23692394
# these forms can't be global because we need app object, which is only available within a request:
23702395
class UserSettingsPersonalForm(Form):
23712396
form_name = "usersettings_personal"
23722397
name = Names.using(label=L_("Usernames")).with_properties(placeholder=L_("The login usernames you want to use"))
23732398
display_name = OptionalText.using(label=L_("Display-Name")).with_properties(
2374-
placeholder=L_("Your display name (informational)")
2399+
placeholder=L_("Your display name (optional, rarely used)")
23752400
)
23762401
# _timezones_keys = sorted(Locale('en').time_zones.keys())
23772402
_timezones_keys = [str(tz) for tz in pytz.common_timezones]
@@ -2382,6 +2407,8 @@ class UserSettingsPersonalForm(Form):
23822407
)
23832408
submit_label = L_("Save")
23842409

2410+
validators = [ValidUserSettingsPersonal()]
2411+
23852412
class UserSettingsUIForm(Form):
23862413
form_name = "usersettings_ui"
23872414
theme_name = RadioChoice.using(label=L_("Theme name")).with_properties(
@@ -2437,24 +2464,6 @@ class UserSettingsUIForm(Form):
24372464
flaskg.user.save()
24382465
response["flash"].append((_("Your password has been changed."), "info"))
24392466
else:
2440-
if part == "personal":
2441-
if set(form["name"].value) != set(flaskg.user.name):
2442-
new_names = set(form["name"].value) - set(flaskg.user.name)
2443-
for name in new_names:
2444-
if user.search_users(**{NAME_EXACT: name}):
2445-
# duplicate name
2446-
response["flash"].append(
2447-
(_("The username '{name}' is already in use.").format(name=name), "error")
2448-
)
2449-
success = False
2450-
if not user.normalizeName(name) == name:
2451-
response["flash"].append(
2452-
(
2453-
_("The username '{name}' contains invalid characters").format(name=name),
2454-
"error",
2455-
)
2456-
)
2457-
success = False
24582467
if part == "notification":
24592468
if (
24602469
form["email"].value != flaskg.user.email
@@ -2513,9 +2522,12 @@ class UserSettingsUIForm(Form):
25132522
else:
25142523
# validation failed
25152524
response["flash"].append((_("Nothing saved."), "error"))
2525+
25162526
if not response["flash"]:
25172527
# if no flash message was added until here, we add a generic success message
2518-
response["flash"].append((_("Your changes have been saved."), "info"))
2528+
msg = _("Your changes have been saved.")
2529+
response["flash"].append((msg, "info"))
2530+
repeat_flash_msg(msg, "info")
25192531

25202532
if response["redirect"] is not None or not is_xhr:
25212533
# if we redirect or it is no XHR request, we just flash() the messages normally
@@ -2544,6 +2556,15 @@ class UserSettingsUIForm(Form):
25442556
return render_template("usersettings.html", title_name=title_name, form_objs=forms)
25452557

25462558

2559+
def repeat_flash_msg(msg, level):
2560+
"""
2561+
Add a flash message to flask session. The message will be re-flashed by the next transaction.
2562+
"""
2563+
if FLASH_REPEAT not in session:
2564+
session[FLASH_REPEAT] = []
2565+
session[FLASH_REPEAT].append((msg, level))
2566+
2567+
25472568
@frontend.route("/+bookmark")
25482569
def bookmark():
25492570
"""set bookmark (in time) for recent changes (or delete them)"""

src/moin/config/default.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,14 @@ def _default_password_checker(cfg, username, password, min_length=8, min_differe
292292
"""
293293
# in any case, do a very simple built-in check to avoid the worst passwords
294294
if len(password) < min_length:
295-
return _("For a password a minimum length of {min_length:d} characters is required.", min_length=min_length)
295+
return _(
296+
"For a password a minimum length of {min_length} characters is required.".format(min_length=min_length)
297+
)
296298
if len(set(password)) < min_different:
297299
return _(
298-
"For a password a minimum of {min_different:d} different characters is required.",
299-
min_different=min_different,
300+
"For a password a minimum of {min_different:d} different characters is required.".format(
301+
min_different=min_different
302+
)
300303
)
301304

302305
username_lower = username.lower()

src/moin/constants/misc.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,8 @@
7373

7474
# Valid views allowed for itemlinks
7575
VALID_ITEMLINK_VIEWS = ["+meta", "+history", "+download", "+highlight"]
76+
77+
# Transient attribute added/removed to/from flask session. Used when a User Settings
78+
# form creates a flash message but then redirects the page making the flash message a
79+
# very short flash message.
80+
FLASH_REPEAT = "flash_repeat"

src/moin/templates/snippets.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
{% if cfg.show_interwiki %}
8585
{{ cfg.interwikiname }}:
8686
{% endif %}
87-
{{ item_name }} (rev {{ rev.revid | shorten_id }}),
87+
{{ item_name }} (rev {{ rev.meta['rev_number'] }}),
8888
{{ _("modified") }} {{ rev.meta['mtime']|time_datetime }}
8989
{{ _("by") }} {{ utils.editor_info(rev.meta) }}
9090
{% if rev.meta['tags'] %}

src/moin/themes/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from flask import current_app as app
1919
from flask import g as flaskg
20-
from flask import url_for, request
20+
from flask import url_for, request, session, flash
2121
from flask_theme import get_theme, render_theme_template
2222

2323
from babel import Locale
@@ -26,7 +26,7 @@
2626
from moin import wikiutil, user
2727
from moin.constants.keys import USERID, ADDRESS, HOSTNAME, REVID, ITEMID, NAME_EXACT, ASSIGNED_TO, NAME, NAMESPACE
2828
from moin.constants.contenttypes import CONTENTTYPES_MAP, CONTENTTYPE_MARKUP, CONTENTTYPE_TEXT, CONTENTTYPE_MOIN_19
29-
from moin.constants.misc import VALID_ITEMLINK_VIEWS
29+
from moin.constants.misc import VALID_ITEMLINK_VIEWS, FLASH_REPEAT
3030
from moin.constants.namespaces import NAMESPACE_DEFAULT, NAMESPACE_USERS, NAMESPACE_ALL
3131
from moin.constants.rights import SUPERUSER
3232
from moin.search import SearchForm
@@ -102,6 +102,10 @@ def __init__(self, cfg):
102102
self.wiki_root = "/" + request.url_root[len(request.host_url) : -1]
103103
else:
104104
self.wiki_root = ""
105+
if FLASH_REPEAT in session:
106+
for msg, level in session[FLASH_REPEAT]:
107+
flash(msg, level)
108+
del session[FLASH_REPEAT]
105109

106110
def get_fullname(self, meta):
107111
"""
@@ -655,7 +659,7 @@ def get_editor_info(meta, external=False):
655659
if userid:
656660
u = user.User(userid)
657661
name = u.name0
658-
text = name
662+
text = u.display_name or name
659663
display_name = u.display_name or name
660664
if title:
661665
# we already have some address info

0 commit comments

Comments
 (0)