+
+
+
-
-
Account Registration
-
+
Account Registration
+ {{ form.render()|safe }}
diff --git a/ordr2/views/account.py b/ordr2/views/account.py
index d9ba83c..fcadb9f 100644
--- a/ordr2/views/account.py
+++ b/ordr2/views/account.py
@@ -1,28 +1,24 @@
''' account registration, login, logout and settings '''
+import deform
+
from pyramid.httpexceptions import HTTPFound
from pyramid.security import remember, forget
from pyramid.view import view_config
-from ordr2.models import User
+from ordr2.events import CompleteRegistration
+from ordr2.models.account import User, Role, TokenSubject
+from ordr2.schemas.account import RegistrationSchema
-@view_config(
- context='ordr2:resources.account.AccountResource',
- name='register',
- permission='register',
- renderer='ordr2:templates/account/register.jinja2'
- )
-def register(context, request):
- ''' the new user registraion page '''
- return {}
+PROPOSED_PASSWORD_LENGTH = 12
@view_config(
context='ordr2:resources.account.AccountResource',
name='login',
- permission='login',
request_method='POST',
+ permission='login',
renderer='ordr2:templates/account/login.jinja2'
)
def login(context, request):
@@ -52,3 +48,62 @@ def logout(context, request):
''' log out an user '''
headers = forget(request)
return HTTPFound(request.resource_url(request.root), headers=headers)
+
+
+@view_config(
+ context='ordr2:resources.account.AccountResource',
+ name='register',
+ request_method='GET',
+ permission='register',
+ renderer='ordr2:templates/account/register.jinja2'
+ )
+def registration_form(context, request):
+ ''' the new user registraion page '''
+ form = RegistrationSchema.as_form(request)
+ return {'form':form}
+
+
+@view_config(
+ context='ordr2:resources.account.AccountResource',
+ name='register',
+ request_method='POST',
+ permission='register',
+ renderer='ordr2:templates/account/register.jinja2'
+ )
+def registration_form_processing(context, request):
+ ''' registration form processing '''
+ if 'Cancel' in request.POST:
+ return HTTPFound(request.resource_url(request.root))
+
+ # validate the form data
+ form = RegistrationSchema.as_form(request)
+ data = request.POST.items()
+ try:
+ appstruct = form.validate(data)
+ except deform.ValidationFailure as e:
+ return {'form': form}
+
+ # form validation successfull, create user
+ account = User(
+ username=appstruct['username'],
+ first_name=appstruct['first_name'],
+ last_name=appstruct['last_name'],
+ email=appstruct['email'],
+ role=Role.UNVALIDATED
+ )
+ account.set_password(appstruct['password'])
+ request.dbsession.add(account)
+
+ # create a verify-new-account token and send email
+ hash = account.issue_token(request, TokenSubject.USER_REGISTRATION)
+ notification = CompleteRegistration(request, account, {'hash': hash})
+ request.registry.notify(notification)
+
+ # issue a warning on a short password
+ if len(appstruct['password']) < PROPOSED_PASSWORD_LENGTH:
+ request.session.flash(
+ 'warning',
+ 'You should really consider a longer password'
+ )
+
+ return HTTPFound(request.resource_url(context, 'registered'))
diff --git a/tests/__init__.py b/tests/__init__.py
index d3e20fb..48fb794 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -5,6 +5,8 @@ import pytest
import transaction
from pyramid import testing
+from pyramid.csrf import get_csrf_token
+from webob.multidict import MultiDict
# some path mangling to get the path to passlib.ini
@@ -22,7 +24,8 @@ APP_SETTINGS = {
'passlib.config': passlib_config_path,
'pyramid.includes': [
'pyramid_jinja2',
- ]
+ ],
+ 'mail.default_sender': 'ordr@example.com'
}
EXAMPLE_USER_DATA = {
@@ -41,7 +44,8 @@ EXAMPLE_USER_DATA = {
def app_config():
''' fixture for tests requiring a pyramid.testing setup '''
with testing.testConfig(settings=APP_SETTINGS) as config:
- #config.include('pyramid_mailer.testing')
+ config.include('pyramid_mailer.testing')
+ config.include('pyramid_jinja2')
from ordr2.models.account import passlib_context
passlib_context.update(schemes=['argon2'])
@@ -71,7 +75,6 @@ def dbsession(app_config):
Base.metadata.drop_all(engine)
-
# helpers
def get_user(role_name):
@@ -98,4 +101,18 @@ def create_users(db):
db.add(user)
+def set_deform_data(request, form_data, extra_data=None, **kwargs):
+ ''' augments the request to include post data as provided by deform '''
+ post_dict = MultiDict()
+ post_dict['__formid__'] = 'deform'
+ post_dict['_charset_'] = 'UTF-8'
+ post_dict['csrf_token'] = get_csrf_token(request)
+ post_dict.update(form_data)
+ if extra_data:
+ post_dict.update(extra_data)
+ post_dict.update(kwargs)
+ request.POST = post_dict
+
+
+
diff --git a/tests/_functional/registration.py b/tests/_functional/registration.py
index d2d7555..847ee28 100644
--- a/tests/_functional/registration.py
+++ b/tests/_functional/registration.py
@@ -21,7 +21,6 @@ def test_account_register_unauthenticated(testapp):
testapp.reset()
response = testapp.get('/account/register')
-
# basic content test
assert 'Ordr | Account Registration' in response
# test the main nav section links and highlighting
@@ -30,4 +29,58 @@ def test_account_register_unauthenticated(testapp):
assert li_one.find('a').text == 'FAQs'
assert 'active' in li_two['class']
assert li_two.find('a').text == 'Register'
+ # check for the registration form
+ form = response.html.find('form', class_='registration')
+ assert form is not None
+
+
+@pytest.mark.xfail
+def test_account_registeration_flow(testapp):
+ ''' test the complete registration process '''
+
+ # submit the registration form
+ form = resonse.forms[1]
+ form['username'] = 'AmyMcDonald'
+ form['first_name'] = 'Amy'
+ form['last_name'] = 'McDonald'
+ form['email'] = 'amy@example.com'
+ form['password'] = 'Amy'
+ form['password_confirm'] = 'Amy'
+ response = form.submitt()
+
+ assert response.location == '/account/verify'
+ response = response.follow()
+ assert 'email sent' in response
+
+ # click the email verification token
+ email = ''
+ token = email
+ response = testapp.get('/account/' + token)
+ assert 'consider a longer password' in response
+ assert 'activated by an administrator' in response
+
+ # logging in should not work
+ form = response.forms[0]
+ form['username'] = 'AmyMcDonald'
+ form['password'] = 'Amy'
+ response = form.submit()
+ assert '' not in response
+
+ # activate the new user
+ testapp.login('admin')
+ response = testapp.get('/admin/users?role=new')
+ response = response.click('edit user')
+ form = response.forms[1]
+ form['role'] = 'USER'
+ form.submit()
+ testapp.logout()
+
+ # login should now work
+ response = testapp.get('/')
+ form = response.forms[0]
+ form['username'] = 'AmyMcDonald'
+ form['password'] = 'Amy'
+ response = form.submit()
+ assert '' in response
+
diff --git a/tests/views/account.py b/tests/views/account.py
index 335b29a..8e5d238 100644
--- a/tests/views/account.py
+++ b/tests/views/account.py
@@ -1,12 +1,26 @@
''' Tests for ordr2.views.account '''
+import deform
import pytest
from pyramid.httpexceptions import HTTPFound
from pyramid.testing import DummyRequest, DummyResource
+from pyramid_mailer import get_mailer
+from webob.multidict import MultiDict
+from .. import app_config, dbsession, get_user, create_users, set_deform_data
-from .. import app_config, dbsession, get_user, create_users
+
+REGISTRATION_FORM_DATA = MultiDict([
+ ('username', 'AmyMcDonald'),
+ ('first_name', 'Amy'),
+ ('last_name', 'McDonald'),
+ ('email', 'mcdonald@example.com'),
+ ('__start__', 'password:mapping'),
+ ('password', 'Amy'),
+ ('password-confirm', 'Amy'),
+ ('__end__', 'password:mapping'),
+ ])
@pytest.mark.parametrize('rolename', ['user', 'purchaser', 'admin'])
@@ -83,3 +97,94 @@ def test_logout(app_config):
assert isinstance(result, HTTPFound)
assert result.location == 'http://example.com//'
+
+
+def test_registration_form(app_config):
+ ''' registration form '''
+ from ordr2.views.account import registration_form
+
+ request = DummyRequest()
+ context = DummyResource()
+ result = registration_form(context, request)
+
+ assert isinstance(result['form'], deform.Form)
+
+
+def test_registration_form_processing_ok(dbsession):
+ ''' registration form processing with valid data'''
+ from ordr2.models.account import User, Role, TokenSubject
+ from ordr2.views.account import registration_form_processing
+
+ user = get_user('user') # intentionally not added to database
+ context = DummyResource(model=user)
+ request = DummyRequest(dbsession=dbsession, context=context)
+ set_deform_data(request, REGISTRATION_FORM_DATA)
+ result = registration_form_processing(context, request)
+
+ # return value of function call
+ assert isinstance(result, HTTPFound)
+ assert result.location == 'http://example.com/registered'
+
+ # user should be added to database
+ user = dbsession.query(User).first()
+ assert user.username == REGISTRATION_FORM_DATA['username']
+ assert user.first_name == REGISTRATION_FORM_DATA['first_name']
+ assert user.last_name == REGISTRATION_FORM_DATA['last_name']
+ assert user.email == REGISTRATION_FORM_DATA['email']
+ assert user.check_password(REGISTRATION_FORM_DATA['password'])
+ assert user.role == Role.UNVALIDATED
+
+ # a token should be created
+ token = user.tokens[0]
+ assert token.subject == TokenSubject.USER_REGISTRATION
+
+ # and a verification email should be sent
+ # mailer = get_mailer(request.registry)
+ # last_mail = mailer.outbox[-1]
+ # assert 'Please verify your email address ' in last_mail.html
+ # assert 'http://example.com/' + token.hash in last_mail.html
+
+
+def test_registration_form_processing_cancel(app_config):
+ ''' canceling registration form processing '''
+ from ordr2.models.account import User, Role, TokenSubject
+ from ordr2.views.account import registration_form_processing
+
+ user = get_user('user') # intentionally not added to database
+ context = DummyResource(model=user)
+ request = DummyRequest(dbsession=dbsession, context=context)
+ set_deform_data(request, REGISTRATION_FORM_DATA, {'Cancel': 'Cancel'})
+ result = registration_form_processing(context, request)
+
+ assert isinstance(result, HTTPFound)
+ assert result.location == 'http://example.com//'
+
+
+@pytest.mark.parametrize(
+ 'key,value', [
+ ('username', ''),
+ ('username', 'TerryGilliam'),
+ ('first_name', ''),
+ ('last_name', ''),
+ ('email', ''),
+ ('email', 'no email'),
+ ('email', 'gilliam@example.com'),
+ ('password', ''),
+ ('password-confirm', ''),
+ ('password-confirm', 'no match')
+ ]
+ )
+def test_registration_form_processing_validation_error(dbsession, key, value):
+ ''' registration form processing with valid data'''
+ from ordr2.models.account import User, Role, TokenSubject
+ from ordr2.views.account import registration_form_processing
+
+ admin = get_user('user')
+ dbsession.add(admin)
+ context = DummyResource(model=get_user('admin'))
+ request = DummyRequest(dbsession=dbsession, context=context)
+ set_deform_data(request, REGISTRATION_FORM_DATA, {key: value})
+ result = registration_form_processing(context, request)
+
+ # return value of function call
+ assert isinstance(result['form'], deform.Form)