feat: add view to change current user password
This commit is contained in:
parent
70d13ee1e7
commit
a2ba88ca8f
13 changed files with 259 additions and 7 deletions
|
@ -122,6 +122,7 @@ def make_pyramid_config(settings):
|
|||
pyramid_config.include('pyramid_beaker')
|
||||
pyramid_config.include('pyramid_deform')
|
||||
pyramid_config.include('pyramid_mako')
|
||||
pyramid_config.include('pyramid_tm')
|
||||
|
||||
return pyramid_config
|
||||
|
||||
|
|
|
@ -337,6 +337,16 @@ class Form:
|
|||
with label and containing a widget.
|
||||
|
||||
Actual output will depend on the field attributes etc.
|
||||
Typical output might look like:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<b-field label="Foo"
|
||||
horizontal
|
||||
type="is-danger"
|
||||
message="something went wrong!">
|
||||
<!-- widget element(s) -->
|
||||
</b-field>
|
||||
"""
|
||||
dform = self.get_deform()
|
||||
field = dform[fieldname]
|
||||
|
@ -354,8 +364,50 @@ class Form:
|
|||
'label': label,
|
||||
}
|
||||
|
||||
# next we will build array of messages to display..some
|
||||
# fields always show a "helptext" msg, and some may have
|
||||
# validation errors..
|
||||
field_type = None
|
||||
messages = []
|
||||
|
||||
# show errors if present
|
||||
errors = self.get_field_errors(fieldname)
|
||||
if errors:
|
||||
field_type = 'is-danger'
|
||||
messages.extend(errors)
|
||||
|
||||
# ..okay now we can declare the field messages and type
|
||||
if field_type:
|
||||
attrs['type'] = field_type
|
||||
if messages:
|
||||
if len(messages) == 1:
|
||||
msg = messages[0]
|
||||
if msg.startswith('`') and msg.endswith('`'):
|
||||
attrs[':message'] = msg
|
||||
else:
|
||||
attrs['message'] = msg
|
||||
# TODO
|
||||
# else:
|
||||
# # nb. must pass an array as JSON string
|
||||
# attrs[':message'] = '[{}]'.format(', '.join([
|
||||
# "'{}'".format(msg.replace("'", r"\'"))
|
||||
# for msg in messages]))
|
||||
|
||||
return HTML.tag('b-field', c=[html], **attrs)
|
||||
|
||||
def get_field_errors(self, field):
|
||||
"""
|
||||
Return a list of error messages for the given field.
|
||||
|
||||
Not useful unless a call to :meth:`validate()` failed.
|
||||
"""
|
||||
dform = self.get_deform()
|
||||
if field in dform:
|
||||
error = dform[field].errormsg
|
||||
if error:
|
||||
return [error]
|
||||
return []
|
||||
|
||||
def get_vue_field_value(self, field):
|
||||
"""
|
||||
This method returns a JSON string which will be assigned as
|
||||
|
@ -400,7 +452,10 @@ class Form:
|
|||
the :attr:`validated` attribute.
|
||||
|
||||
However if the data is not valid, ``False`` is returned, and
|
||||
there will be no :attr:`validated` attribute.
|
||||
there will be no :attr:`validated` attribute. In that case
|
||||
you should inspect the form errors to learn/display what went
|
||||
wrong for the user's sake. See also
|
||||
:meth:`get_field_errors()`.
|
||||
|
||||
:returns: Data dict, or ``False``.
|
||||
"""
|
||||
|
|
7
src/wuttaweb/templates/auth/change_password.mako
Normal file
7
src/wuttaweb/templates/auth/change_password.mako
Normal file
|
@ -0,0 +1,7 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="/form.mako" />
|
||||
|
||||
<%def name="title()">Change Password</%def>
|
||||
|
||||
|
||||
${parent.body()}
|
|
@ -316,6 +316,7 @@
|
|||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">${request.user}</a>
|
||||
<div class="navbar-dropdown">
|
||||
${h.link_to("Change Password", url('change_password'), class_='navbar-item')}
|
||||
${h.link_to("Logout", url('logout'), class_='navbar-item')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
13
src/wuttaweb/templates/deform/checked_password.pt
Normal file
13
src/wuttaweb/templates/deform/checked_password.pt
Normal file
|
@ -0,0 +1,13 @@
|
|||
<div tal:define="name name|field.name;
|
||||
vmodel vmodel|'model_'+name;">
|
||||
${field.start_mapping()}
|
||||
<b-input name="${name}"
|
||||
value="${field.widget.redisplay and cstruct or ''}"
|
||||
type="password"
|
||||
placeholder="Password" />
|
||||
<b-input name="${name}-confirm"
|
||||
value="${field.widget.redisplay and confirm or ''}"
|
||||
type="password"
|
||||
placeholder="Confirm Password" />
|
||||
${field.end_mapping()}
|
||||
</div>
|
|
@ -1,6 +1,12 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="/page.mako" />
|
||||
|
||||
<%def name="page_content()">
|
||||
<div style="margin-top: 2rem; width: 50%;">
|
||||
${form.render_vue_tag()}
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="render_this_page_template()">
|
||||
${parent.render_this_page_template()}
|
||||
${form.render_vue_template()}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
% endfor
|
||||
</section>
|
||||
|
||||
<div style="margin-top: 1.5rem; display: flex; gap: 0.5rem; justify-content: end; width: 100%;">
|
||||
<div style="margin-top: 1.5rem; display: flex; gap: 0.5rem; justify-content: ${'end' if form.align_buttons_right else 'start'}; width: 100%; padding-left: 10rem;">
|
||||
|
||||
% if form.show_button_reset:
|
||||
<b-button native-type="reset">
|
||||
|
|
|
@ -43,7 +43,7 @@ def get_form_data(request):
|
|||
# https://docs.pylonsproject.org/projects/pyramid/en/latest/api/request.html#pyramid.request.Request.is_xhr
|
||||
if not request.POST and (
|
||||
getattr(request, 'is_xhr', False)
|
||||
or request.content_type == 'application/json'):
|
||||
or getattr(request, 'content_type', None) == 'application/json'):
|
||||
return request.json_body
|
||||
return request.POST
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ Auth Views
|
|||
"""
|
||||
|
||||
import colander
|
||||
from deform.widget import TextInputWidget, PasswordWidget
|
||||
from deform.widget import TextInputWidget, PasswordWidget, CheckedPasswordWidget
|
||||
|
||||
from wuttaweb.views import View
|
||||
from wuttaweb.db import Session
|
||||
|
@ -45,7 +45,7 @@ class AuthView(View):
|
|||
Upon successful login, user is redirected to home page.
|
||||
|
||||
* route: ``login``
|
||||
* template: ``/login.mako``
|
||||
* template: ``/auth/login.mako``
|
||||
"""
|
||||
auth = self.app.get_auth_handler()
|
||||
|
||||
|
@ -138,6 +138,66 @@ class AuthView(View):
|
|||
referrer = self.request.route_url('login')
|
||||
return self.redirect(referrer, headers=headers)
|
||||
|
||||
def change_password(self):
|
||||
"""
|
||||
View allowing a user to change their own password.
|
||||
|
||||
This view shows a change-password form, and handles its
|
||||
submission. If successful, user is redirected to home page.
|
||||
|
||||
If current user is not authenticated, no form is shown and
|
||||
user is redirected to home page.
|
||||
|
||||
* route: ``change_password``
|
||||
* template: ``/auth/change_password.mako``
|
||||
"""
|
||||
if not self.request.user:
|
||||
return self.redirect(self.request.route_url('home'))
|
||||
|
||||
form = self.make_form(schema=self.change_password_make_schema(),
|
||||
show_button_reset=True)
|
||||
|
||||
data = form.validate()
|
||||
if data:
|
||||
auth = self.app.get_auth_handler()
|
||||
auth.set_user_password(self.request.user, data['new_password'])
|
||||
self.request.session.flash("Your password has been changed.")
|
||||
# TODO: should use request.get_referrer() instead
|
||||
referrer = self.request.route_url('home')
|
||||
return self.redirect(referrer)
|
||||
|
||||
return {'index_title': str(self.request.user),
|
||||
'form': form}
|
||||
|
||||
def change_password_make_schema(self):
|
||||
schema = colander.Schema()
|
||||
|
||||
schema.add(colander.SchemaNode(
|
||||
colander.String(),
|
||||
name='current_password',
|
||||
widget=PasswordWidget(),
|
||||
validator=self.change_password_validate_current_password))
|
||||
|
||||
schema.add(colander.SchemaNode(
|
||||
colander.String(),
|
||||
name='new_password',
|
||||
widget=CheckedPasswordWidget(),
|
||||
validator=self.change_password_validate_new_password))
|
||||
|
||||
return schema
|
||||
|
||||
def change_password_validate_current_password(self, node, value):
|
||||
auth = self.app.get_auth_handler()
|
||||
user = self.request.user
|
||||
if not auth.check_user_password(user, value):
|
||||
node.raise_invalid("Current password is incorrect.")
|
||||
|
||||
def change_password_validate_new_password(self, node, value):
|
||||
auth = self.app.get_auth_handler()
|
||||
user = self.request.user
|
||||
if auth.check_user_password(user, value):
|
||||
node.raise_invalid("New password must be different from old password.")
|
||||
|
||||
@classmethod
|
||||
def defaults(cls, config):
|
||||
cls._auth_defaults(config)
|
||||
|
@ -149,13 +209,19 @@ class AuthView(View):
|
|||
config.add_route('login', '/login')
|
||||
config.add_view(cls, attr='login',
|
||||
route_name='login',
|
||||
renderer='/login.mako')
|
||||
renderer='/auth/login.mako')
|
||||
|
||||
# logout
|
||||
config.add_route('logout', '/logout')
|
||||
config.add_view(cls, attr='logout',
|
||||
route_name='logout')
|
||||
|
||||
# change password
|
||||
config.add_route('change_password', '/change-password')
|
||||
config.add_view(cls, attr='change_password',
|
||||
route_name='change_password',
|
||||
renderer='/auth/change_password.mako')
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue