Simplify routing a tiny bit

This commit is contained in:
Matt Wright
2012-07-12 11:29:42 -04:00
parent 0befa34dc8
commit da031b8d15
9 changed files with 121 additions and 64 deletions
+4 -4
View File
@@ -31,14 +31,14 @@ def send_confirmation_instructions(user, token):
:param user: The user to send the instructions to
"""
url = url_for('flask_security.confirm',
token=token)
url = url_for('flask_security.confirm_account', token=token)
confirmation_link = request.url_root[:-1] + url
ctx = dict(user=user, confirmation_link=confirmation_link)
send_mail('Please confirm your email', user.email,
'confirmation_instructions',
dict(user=user, confirmation_link=confirmation_link))
'confirmation_instructions', ctx)
confirm_instructions_sent.send(user, app=app._get_current_object())
+15 -11
View File
@@ -32,10 +32,10 @@ _default_config = {
'AUTH_URL': '/auth',
'LOGOUT_URL': '/logout',
'REGISTER_URL': '/register',
'FORGOT_URL': '/forgot',
'RESET_URL': '/reset/<token>',
'CONFIRM_URL': '/confirm/<token>',
'RESET_URL': '/reset',
'CONFIRM_URL': '/confirm',
'LOGIN_VIEW': '/login',
'CONFIRM_ERROR_VIEW': '/confirm',
'POST_LOGIN_VIEW': '/',
'POST_LOGOUT_VIEW': '/',
'POST_FORGOT_VIEW': '/',
@@ -141,20 +141,24 @@ def _create_blueprint(app):
if cv('REGISTERABLE', app=app):
bp.route(cv('REGISTER_URL', app=app),
methods=['POST'],
endpoint='register')(views.register)
methods=['GET', 'POST'],
endpoint='register')(views.register_user)
if cv('RECOVERABLE', app=app):
bp.route(cv('FORGOT_URL', app=app),
methods=['POST'],
endpoint='forgot')(views.forgot)
bp.route(cv('RESET_URL', app=app),
methods=['POST'],
endpoint='reset')(views.reset)
methods=['GET', 'POST'],
endpoint='forgot_password')(views.forgot_password)
bp.route(cv('RESET_URL', app=app) + '/<token>',
methods=['GET', 'POST'],
endpoint='reset_password')(views.reset_password)
if cv('CONFIRMABLE', app=app):
bp.route(cv('CONFIRM_URL', app=app),
endpoint='confirm')(views.confirm)
methods=['GET', 'POST'],
endpoint='send_confirmation')(views.send_confirmation)
bp.route(cv('CONFIRM_URL', app=app) + '/<token>',
methods=['GET', 'POST'],
endpoint='confirm_account')(views.confirm_account)
return bp
+33 -3
View File
@@ -9,9 +9,23 @@
:license: MIT, see LICENSE for more details.
"""
from flask import request
from flask import request, current_app as app
from flask.ext.wtf import Form, TextField, PasswordField, SubmitField, \
HiddenField, Required, BooleanField, EqualTo, Email
HiddenField, Required, BooleanField, EqualTo, Email, ValidationError
from werkzeug.local import LocalProxy
from .exceptions import UserNotFoundError
# Convenient reference
_datastore = LocalProxy(lambda: app.security.datastore)
def valid_user_email(form, field):
try:
_datastore.find_user(email=field.data)
except UserNotFoundError:
raise ValidationError('Invalid email address')
class EmailFormMixin():
@@ -20,6 +34,13 @@ class EmailFormMixin():
Email(message="Invalid email address")])
class UserEmailFormMixin():
email = TextField("Email Address",
validators=[Required(message="Email not provided"),
Email(message="Invalid email address"),
valid_user_email])
class PasswordFormMixin():
password = PasswordField("Password",
validators=[Required(message="Password not provided")])
@@ -30,7 +51,16 @@ class PasswordConfirmFormMixin():
validators=[EqualTo('password', message="Passwords do not match")])
class ForgotPasswordForm(Form, EmailFormMixin):
class ResendConfirmationForm(Form, UserEmailFormMixin):
"""The default forgot password form"""
submit = SubmitField("Resend Confirmation Instructions")
def to_dict(self):
return dict(email=self.email.data)
class ForgotPasswordForm(Form, UserEmailFormMixin):
"""The default forgot password form"""
submit = SubmitField("Recover Password")
+1 -1
View File
@@ -31,7 +31,7 @@ def send_reset_password_instructions(user, reset_token):
:param user: The user to send the instructions to
"""
url = url_for('flask_security.reset',
url = url_for('flask_security.reset_password',
token=reset_token)
reset_link = request.url_root[:-1] + url
@@ -1,6 +1,6 @@
{% include "../messages.html" %}
{% include "security/messages.html" %}
<h1>Resend confirmation instructions</h1>
<form action="{{ url_for('flask_security.confirm') }}" method="POST" name="reset_confirmation_form">
<form action="{{ url_for('flask_security.send_confirmation') }}" method="POST" name="reset_confirmation_form">
{{ reset_confirmation_form.hidden_tag() }}
{{ reset_confirmation_form.email.label }} {{ reset_confirmation_form.email }}
{{ reset_confirmation_form.submit }}
@@ -1,6 +1,6 @@
{% include "../messages.html" %}
<h1>Change password</h1>
<form action="{{ url_for('flask_security.reset') }}" method="POST" name="reset_password_form">
<form action="{{ url_for('flask_security.reset_password') }}" method="POST" name="reset_password_form">
{{ reset_password_form.hidden_tag() }}
{{ reset_password_form.password.label }} {{ reset_password_form.password }}<br/>
{{ reset_password_form.password_confirm.label }} {{ reset_password_form.password_confirm }}<br/>
@@ -1,6 +1,6 @@
{% include "../messages.html" %}
{% include "security/messages.html" %}
<h1>Send reset password instructions</h1>
<form action="{{ url_for('flask_security.forgot') }}" method="POST" name="forgot_password_form">
<form action="{{ url_for('flask_security.forgot_password') }}" method="POST" name="forgot_password_form">
{{ forgot_password_form.hidden_tag() }}
{{ forgot_password_form.email.label }} {{ forgot_password_form.email }}
{{ forgot_password_form.submit }}
+57 -34
View File
@@ -19,15 +19,16 @@ from werkzeug.datastructures import MultiDict
from werkzeug.local import LocalProxy
from .confirmable import confirm_by_token, reset_confirmation_token
from .exceptions import TokenExpiredError, UserNotFoundError, \
ConfirmationError, BadCredentialsError, ResetPasswordError
from .exceptions import TokenExpiredError, ConfirmationError, \
BadCredentialsError, ResetPasswordError
from .forms import LoginForm, RegisterForm, ForgotPasswordForm, \
ResetPasswordForm
ResetPasswordForm, ResendConfirmationForm
from .recoverable import reset_by_token, \
reset_password_reset_token
from .signals import user_registered
from .tokens import generate_authentication_token
from .utils import get_post_login_redirect, do_flash, get_remember_token
from .utils import get_url, get_post_login_redirect, do_flash, \
get_remember_token
# Convenient references
@@ -142,50 +143,71 @@ def logout():
_security.post_logout_view)
def register():
def register_user():
"""View function which handles a registration request."""
form = RegisterForm(csrf_enabled=not app.testing)
# Exit early if the form doesn't validate
if form.validate_on_submit():
# Create user and send signal
user = _datastore.create_user(**form.to_dict())
confirm_token = None
# Create user
u = _datastore.create_user(**form.to_dict())
# Send confirmation instructions if necessary
if _security.confirmable:
confirm_token = reset_confirmation_token(user)
t = reset_confirmation_token(u) if _security.confirmable else None
user_registered.send(dict(user=user, confirm_token=confirm_token),
app=app._get_current_object())
data = dict(user=u, confirm_token=t)
user_registered.send(data, app=app._get_current_object())
_logger.debug('User %s registered' % user)
_logger.debug('User %s registered' % u)
# Login the user if allowed
if not _security.confirmable or _security.login_without_confirmation:
_do_login(user)
_do_login(u)
return redirect(_security.post_register_view or
_security.post_login_view)
return redirect(request.referrer or _security.register_url)
return render_template('security/registrations/new.html',
register_user_form=form)
def confirm(token):
def send_confirmation():
form = ResendConfirmationForm()
if form.validate_on_submit():
user = _datastore.find_user(email=form.email.data)
reset_confirmation_token(user)
_logger.debug('%s request confirmation instructions' % user)
msg = 'A new confirmation code has been sent to ' + user.email
do_flash(msg, 'info')
else:
for key, value in form.errors.items():
do_flash(value[0], 'error')
return render_template('security/confirmations/new.html',
reset_confirmation_form=form)
def confirm_account(token):
"""View function which handles a account confirmation request."""
error = False
try:
user = confirm_by_token(token)
except ConfirmationError, e:
error = True
_logger.debug('Confirmation error: ' + str(e))
do_flash(str(e), 'error')
return redirect('/') # TODO: Don't just redirect to root
except TokenExpiredError, e:
error = True
reset_confirmation_token(e.user)
@@ -197,7 +219,8 @@ def confirm(token):
do_flash(msg, 'error')
return redirect('/') # TODO: Don't redirect to root
if error:
return redirect(get_url(_security.confirm_error_view))
_logger.debug('User %s confirmed' % user)
@@ -207,35 +230,35 @@ def confirm(token):
_security.post_login_view)
def forgot():
def forgot_password():
"""View function that handles a forgotten password request."""
form = ForgotPasswordForm(csrf_enabled=not app.testing)
if form.validate_on_submit():
try:
user = _datastore.find_user(**form.to_dict())
user = _datastore.find_user(**form.to_dict())
reset_password_reset_token(user)
reset_password_reset_token(user)
_logger.debug('%s requested to reset their password' % user)
_logger.debug('%s requested to reset their password' % user)
do_flash('Instructions to reset your password have been '
'sent to %s' % user.email, 'success')
except UserNotFoundError:
_logger.debug('A reset password request was made for %s but '
'that email does not exist.' % form.email.data)
do_flash('The email you provided could not be found', 'error')
do_flash('Instructions to reset your password have been '
'sent to %s' % user.email, 'success')
return redirect(_security.post_forgot_view)
else:
_logger.debug('A reset password request was made for %s but '
'that email does not exist.' % form.email.data)
for key, value in form.errors.items():
do_flash(value[0], 'error')
return render_template('security/passwords/new.html',
forgot_password_form=form)
def reset(token):
def reset_password(token):
"""View function that handles a reset password request."""
form = ResetPasswordForm(csrf_enabled=not app.testing)
+6 -6
View File
@@ -267,18 +267,18 @@ class RecoverableTests(SecurityTest):
def test_forgot_post_sends_email(self):
with capture_reset_password_requests():
with self.app.mail.record_messages() as outbox:
self.client.post('/forgot', data=dict(email='joe@lp.com'))
self.client.post('/reset', data=dict(email='joe@lp.com'))
self.assertEqual(len(outbox), 1)
def test_forgot_password_invalid_email(self):
r = self.client.post('/forgot',
r = self.client.post('/reset',
data=dict(email='larry@lp.com'),
follow_redirects=True)
self.assertIn('The email you provided could not be found', r.data)
self.assertIn('Invalid email address', r.data)
def test_reset_password_with_valid_token(self):
with capture_reset_password_requests() as requests:
r = self.client.post('/forgot',
r = self.client.post('/reset',
data=dict(email='joe@lp.com'),
follow_redirects=True)
t = requests[0]['token']
@@ -293,7 +293,7 @@ class RecoverableTests(SecurityTest):
def test_reset_password_twice_flashes_invalid_token_msg(self):
with capture_reset_password_requests() as requests:
self.client.post('/forgot', data=dict(email='joe@lp.com'))
self.client.post('/reset', data=dict(email='joe@lp.com'))
t = requests[0]['token']
data = {
@@ -316,7 +316,7 @@ class ExpiredResetPasswordTest(SecurityTest):
def test_reset_password_with_expired_token(self):
with capture_reset_password_requests() as requests:
r = self.client.post('/forgot',
r = self.client.post('/reset',
data=dict(email='joe@lp.com'),
follow_redirects=True)
t = requests[0]['token']