diff --git a/ordr2/resources/base.py b/ordr2/resources/base.py index dc7c32e..fb67aa2 100644 --- a/ordr2/resources/base.py +++ b/ordr2/resources/base.py @@ -6,14 +6,15 @@ class BaseResource(object): __parent__ = None __name__ = None - _request = None + request = None + model = None nav_highlight = None def __init__(self, name, parent): self.__name__ = name self.__parent__ = parent - self._request = parent._request + self.request = parent.request def __acl__(self): return [ DENY_ALL ] diff --git a/ordr2/schemas/account.py b/ordr2/schemas/account.py index 04f66d7..1531ca7 100644 --- a/ordr2/schemas/account.py +++ b/ordr2/schemas/account.py @@ -1,13 +1,18 @@ import colander import deform +from ordr2.models import Role + from . import CSRFSchema from .helpers import ( deferred_unique_email_validator, - deferred_unique_username_validator + deferred_unique_username_validator, + deferred_password_validator ) +ROLES = [(role.name, role.value.capitalize()) for role in Role] + # schema for user registration class RegistrationSchema(CSRFSchema): @@ -45,3 +50,89 @@ class RegistrationSchema(CSRFSchema): settings.update(override) return super().as_form(request, **settings) + +# schema for user settings + +class UserSchema(CSRFSchema): + ''' user settings schema ''' + + user_name = colander.SchemaNode( + colander.String(), + widget=deform.widget.TextInputWidget( + template='textinput_disabled.pt' + ), + ) + first_name = colander.SchemaNode( + colander.String() + ) + last_name = colander.SchemaNode( + colander.String() + ) + email = colander.SchemaNode( + colander.String(), + validator=deferred_unique_email_validator + ) + role = colander.SchemaNode( + colander.String(), + widget=deform.widget.SelectWidget(values=ROLES) + ) + + @classmethod + def as_form(cls, request, **override): + settings = { + 'buttons': ('Save changes', 'Cancel'), + 'css_class': 'form-horizontal', + } + settings.update(override) + return super().as_form(request, **settings) + + +class ChangePasswordSchema(CSRFSchema): + ''' change password of an account ''' + + new_password = colander.SchemaNode( + colander.String(), + widget=deform.widget.CheckedPasswordWidget(), + missing='' + ) + + @classmethod + def as_form(cls, request, **override): + settings = { + 'buttons': ('Change Password', 'Cancel'), + 'css_class': 'form-horizontal' + } + settings.update(override) + return super().as_form(request, **settings) + + +class ConfirmSettingsSchema(CSRFSchema): + ''' confirm changes with current password ''' + + current_password = colander.SchemaNode( + colander.String(), + widget=deform.widget.PasswordWidget(), + description='Enter your current password to confirm changes', + validator=deferred_password_validator + ) + + +class SettingsSchema(CSRFSchema): + general = UserSchema() + change_password = ChangePasswordSchema() + confirm_changes = ConfirmSettingsSchema() + + @classmethod + def as_form(cls, request, **override): + settings = { + 'buttons': ('Save Settings', 'Cancel'), + 'css_class': 'form-horizontal user-settings' + } + settings.update(override) + form = super().as_form(request, **settings) + # disable the role field for user settings + form['general']['role'].widget.template='select_disabled.pt' + form['general']['role'].widget=deform.widget.TextInputWidget( + template='textinput_disabled.pt' + ) + return form diff --git a/ordr2/schemas/helpers.py b/ordr2/schemas/helpers.py index af76bb0..bed69a2 100644 --- a/ordr2/schemas/helpers.py +++ b/ordr2/schemas/helpers.py @@ -44,8 +44,20 @@ def deferred_unique_email_validator(node, kw): email_validator(node, value) # raises exception on invalid address request = kw.get('request') user = request.dbsession.query(User).filter_by(email=value).first() - if user is not None: + if user not in (request.user, request.context.model): + # allow existing email addresses if + # it belongs to the current logged in user or + # an administrator edits a user raise colander.Invalid(node, 'Email address in use') return validate_unique_email +@colander.deferred +def deferred_password_validator(node, kw): + ''' checks password confirmation for settings ''' + + def validate_password_confirmation(node, value): + request = kw.get('request') + if request.user is None or not request.user.check_password(value): + raise colander.Invalid(node, 'Wrong password') + return validate_password_confirmation diff --git a/ordr2/static/css/style.css b/ordr2/static/css/style.css index 40904e2..ea79ca6 100755 --- a/ordr2/static/css/style.css +++ b/ordr2/static/css/style.css @@ -722,3 +722,12 @@ hgroup .info { /*================ STYLES FOR php2python BRANCH ================*/ input[value="password:mapping"] + div { margin-bottom:10px; } +input[value="new_password:mapping"] + div { margin-bottom:10px; } + +.form-horizontal.user-settings fieldset > .controls { margin-left:0; } +.user-settings .panel-heading { + font-size:150%; + padding-top: 20px; + padding-bottom: 20px; + margin-bottom: 20px; + border-bottom: 1px solid #aaa;} diff --git a/ordr2/templates/account/settings.jinja2 b/ordr2/templates/account/settings.jinja2 new file mode 100644 index 0000000..aaa7cf6 --- /dev/null +++ b/ordr2/templates/account/settings.jinja2 @@ -0,0 +1,24 @@ +{% extends "ordr2:templates/layout.jinja2" %} +{% import 'ordr2:templates/macros.jinja2' as macros with context %} + +{% block subtitle %} Account | Settings {% endblock subtitle %} + +{% block content %} +