Browse Source

added user registration form

master
Holger Frey 7 years ago
parent
commit
b8a0cd008b
  1. 5
      ordr2/__init__.py
  2. 8
      ordr2/templates/account/register.jinja2
  3. 77
      ordr2/views/account.py
  4. 23
      tests/__init__.py
  5. 55
      tests/_functional/registration.py
  6. 107
      tests/views/account.py

5
ordr2/__init__.py

@ -14,11 +14,14 @@ def main(global_config, **settings): @@ -14,11 +14,14 @@ def main(global_config, **settings):
config.include('.models')
config.include('.resources')
config.include('.schemas')
config.include('.security')
config.include('.session')
config.include('.schemas')
config.include('.views')
# explicit include for jinja2 to enable rendering in events module
config.include('pyramid_jinja2')
config.scan()
return config.make_wsgi_app()

8
ordr2/templates/account/register.jinja2

@ -3,12 +3,12 @@ @@ -3,12 +3,12 @@
{% block title %} Ordr | Account Registration {% endblock title %}
{% block content %}
<div class="row justify-content-center">
<div class="col-8">
<div class="row">
<div class="col-2"></div>
<div class="col-5">
<div class="jumbotron">
<h1>Account Registration</h1>
</div>
{{ form.render()|safe }}
</div>
</div>

77
ordr2/views/account.py

@ -1,28 +1,24 @@ @@ -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): @@ -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'))

23
tests/__init__.py

@ -5,6 +5,8 @@ import pytest @@ -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 = { @@ -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 = { @@ -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): @@ -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): @@ -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

55
tests/_functional/registration.py

@ -21,7 +21,6 @@ def test_account_register_unauthenticated(testapp): @@ -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): @@ -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 '<!-- user is logged in -->' 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 '<!-- user is logged in -->' in response

107
tests/views/account.py

@ -1,12 +1,26 @@ @@ -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): @@ -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)