diff --git a/.gitignore b/.gitignore
index 7292bf9..4da0b88 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
# ignore sqlite database
ordr2.sqlite
+# ignor pyramid_mailer.debug folder
+mail/
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/ordr2/events.py b/ordr2/events.py
index 1f7635f..3218f93 100644
--- a/ordr2/events.py
+++ b/ordr2/events.py
@@ -19,12 +19,12 @@ class UserNotification(object):
class AccountActivation(UserNotification):
- subject='[ordr] Your account was activated',
+ subject='[ordr] Your account was activated'
template = 'ordr2:templates/emails/activation.jinja2'
class PasswordReset(UserNotification):
- subject='[ordr] Password Reset',
+ subject='[ordr] Password Reset'
template = 'ordr2:templates/emails/password_reset.jinja2'
@@ -50,7 +50,7 @@ def notify_user(event):
event.request
)
message = Message(
- subject=event.subject
+ subject=event.subject,
sender=event.request.registry.settings['mail.default_sender'],
recipients=[event.user.email],
html=body
diff --git a/ordr2/models/user.py b/ordr2/models/user.py
index 400e675..8c25394 100644
--- a/ordr2/models/user.py
+++ b/ordr2/models/user.py
@@ -1,5 +1,6 @@
import bcrypt
import enum
+import uuid
from collections import namedtuple
from datetime import datetime
@@ -40,6 +41,7 @@ class User(Base):
email = Column(Text, nullable=False, unique=True)
password_hash = Column(Text, nullable=False)
role = Column(Enum(Role), nullable=False)
+ password_reset = Column(Text, nullable=False, default='')
date_created = Column(Date, nullable=False, default=datetime.utcnow)
@property
@@ -75,6 +77,11 @@ class User(Base):
return bcrypt.checkpw(password.encode('utf8'), expected_hash)
return False
+ def generate_password_token(self):
+ token = uuid.uuid4()
+ self.password_reset = token.hex
+ return token.hex
+
def __str__(self):
''' string representation '''
return '{!s}'.format(self.user_name)
diff --git a/ordr2/schemas/account.py b/ordr2/schemas/account.py
index d013933..0f06534 100644
--- a/ordr2/schemas/account.py
+++ b/ordr2/schemas/account.py
@@ -81,9 +81,14 @@ class UserSchema(CSRFSchema):
def as_form(cls, request, **override):
settings = {
'buttons': (
- deform.Button('Save changes'),
- deform.Button('Reset password', css_class='btn-danger'),
- deform.Button('Cancel')
+ deform.Button(name='save', title='Save changes'),
+ deform.Button(
+ name='delete',
+ title='Delete user',
+ css_class='btn-danger'
+ ),
+ deform.Button(name='reset', title='Reset password'),
+ deform.Button(name='cancel', title='Cancel')
),
'css_class': 'form-horizontal',
}
diff --git a/ordr2/views/admin.py b/ordr2/views/admin.py
index 4234e6c..7f2ce53 100644
--- a/ordr2/views/admin.py
+++ b/ordr2/views/admin.py
@@ -5,6 +5,7 @@ from pyramid.renderers import render
from pyramid.security import remember, forget
from pyramid.view import view_config
+from ordr2.events import AccountActivation, PasswordReset
from ordr2.models import User, Role
from ordr2.schemas.account import UserSchema
@@ -66,6 +67,7 @@ def change_column_view(context, request):
renderer='ordr2:templates/admin/user_edit.jinja2'
)
def user_account_form(context, request):
+ ''' display the user edit form '''
form = UserSchema.as_form(request)
form_data = {
'user_name': context.model.user_name,
@@ -79,34 +81,55 @@ def user_account_form(context, request):
@view_config(
- context='ordr2:resources.Account',
- name='settingsx',
- permission='settings',
+ context='ordr2:resources.UserAccount',
+ permission='edit',
request_method='POST',
- renderer='ordr2:templates/account/settings.jinja2'
+ renderer='ordr2:templates/admin/user_edit.jinja2'
)
-def settingsx_form_processing(context, request):
- ''' display the user settings form '''
+def user_account_form_processing(context, request):
+ ''' process the user edit form '''
- form = SettingsSchema.as_form(request)
+ form = UserSchema.as_form(request)
data = request.POST.items()
- try:
- appstruct = form.validate(data)
- except deform.ValidationFailure as e:
- return {'form': form}
-
- # form validation sucessful, change settings
- request.user.first_name = appstruct['general']['first_name']
- request.user.last_name = appstruct['general']['last_name']
- request.user.email = appstruct['general']['email']
- if appstruct['change_password']['new_password']:
- request.user.set_password(appstruct['change_password']['new_password'])
- if len(appstruct['change_password']['new_password']) < 8:
- request.flash(
- 'warning',
- 'You should really consider using a longer password.'
+ if 'delete' in request.POST:
+ return HTTPFound(request.resource_url(context, 'delete'))
+
+ elif 'save' in request.POST:
+ try:
+ appstruct = form.validate(data)
+ except deform.ValidationFailure as e:
+ return {'form': form}
+
+ # form validation sucessful, change settings
+ was_active = context.model.is_active
+ context.model.first_name = appstruct['first_name']
+ context.model.last_name = appstruct['last_name']
+ context.model.email = appstruct['email']
+ context.model.role = Role[appstruct['role']]
+
+ if not was_active and context.model.is_active:
+ # user account was activated, notify user
+ event = AccountActivation(request, context.model)
+ request.registry.notify(event)
+ text = 'An activation email was sent to {}'.format(
+ appstruct['email']
)
+ else:
+ text = ''
- request.flash('success', 'Your account information has been updated.')
+ msg = 'User account {} updated.'.format(
+ context.model.user_name
+ )
+ request.flash('success', msg, text)
- return {'form': form}
+ elif 'reset' in request.POST:
+ token = context.model.generate_password_token()
+ event = PasswordReset(request, context.model, token)
+ request.registry.notify(event)
+ msg = 'Password reset mail sent to {}.'.format(context.model.email)
+ request.flash('success', msg)
+
+ elif 'delete' in request.POST:
+ return HTTPFound(context, 'delete')
+
+ return HTTPFound(context.__parent__.url())