diff --git a/app/config/config.yml b/app/config/config.yml
index acdf50a..6c77c23 100644
--- a/app/config/config.yml
+++ b/app/config/config.yml
@@ -19,10 +19,16 @@ SECURITY_CHANGEABLE: True
SECURITY_PASSWORD_HASH: bcrypt
SECURITY_PASSWORD_SALT: 'bb83d6fa8d0a4adc'
SECURITY_SEND_REGISTER_EMAIL: False
+SECURITY_CONFIRMABLE: True
+SECURITY_LOGIN_WITHOUT_CONFIRMATION: True
+
+# This is a workaround for
+# https://github.com/mattupstate/flask-security/pull/489.
+SECURITY_MSG_CONFIRM_REGISTRATION: ['DO_NOT_DISPLAY_ME', 'success']
SECURITY_EMAIL_SENDER: noreply@networkofinnovators.org
SECURITY_POST_REGISTER_VIEW: 'views.register_step_2'
-SECURITY_POST_CONFIRM_VIEW: 'views.register_step_2'
+SECURITY_POST_CONFIRM_VIEW: 'views.confirmation_success'
SECURITY_POST_LOGIN_VIEW: 'views.activity'
SECURITY_EMAIL_SUBJECT_REGISTER: 'Welcome to the Network of Innovators!'
SECURITY_EMAIL_SUBJECT_PASSWORD_NOTICE: 'Your Network of Innovators password has been reset'
diff --git a/app/error_handlers.py b/app/error_handlers.py
new file mode 100644
index 0000000..65b1515
--- /dev/null
+++ b/app/error_handlers.py
@@ -0,0 +1,8 @@
+import flask
+from flask import Blueprint, render_template
+
+blueprint = flask.Blueprint('error_handlers', __name__)
+
+@blueprint.app_errorhandler(404)
+def page_not_found(e):
+ return render_template('404.html'), 404
diff --git a/app/factory.py b/app/factory.py
index cd4949a..893ed68 100644
--- a/app/factory.py
+++ b/app/factory.py
@@ -17,7 +17,7 @@
QUESTIONS_BY_ID, LEVELS_BY_SCORE, QUESTIONNAIRES_BY_ID)
from app.forms import (NOIForgotPasswordForm, NOILoginForm,
NOIResetPasswordForm, NOIChangePasswordForm,
- NOIRegisterForm)
+ NOIConfirmRegisterForm, NOISendConfirmationForm)
from app.models import db, User, Role
from app.views import views
from app.utils import get_nopic_avatar
@@ -29,6 +29,7 @@
import yaml
import json
import os
+import error_handlers
# App config vars that are exposed to client-side JavaScript code.
@@ -68,6 +69,11 @@ def create_app(config=None): #pylint: disable=too-many-statements
'''
app = Flask(__name__)
+ '''
+ add error page handling
+ '''
+ app.register_blueprint(error_handlers.blueprint)
+
with open('/noi/app/config/config.yml', 'r') as config_file:
app.config.update(yaml.load(config_file))
@@ -81,9 +87,6 @@ def create_app(config=None): #pylint: disable=too-many-statements
else:
app.config.update(config)
- # Confirming email is currently unsupported.
- app.config['SECURITY_CONFIRMABLE'] = False
-
with open('/noi/app/data/deployments.yaml') as deployments_yaml:
deployments = yaml.load(deployments_yaml)
@@ -118,10 +121,11 @@ def create_app(config=None): #pylint: disable=too-many-statements
user_datastore = DeploySQLAlchemyUserDatastore(db, User, Role)
security.init_app(app, datastore=user_datastore,
login_form=NOILoginForm,
- register_form=NOIRegisterForm,
+ confirm_register_form=NOIConfirmRegisterForm,
forgot_password_form=NOIForgotPasswordForm,
reset_password_form=NOIResetPasswordForm,
- change_password_form=NOIChangePasswordForm)
+ change_password_form=NOIChangePasswordForm,
+ send_confirmation_form=NOISendConfirmationForm)
db.init_app(app)
alchemydumps.init_app(app, db)
diff --git a/app/forms.py b/app/forms.py
index 2c4c0cd..44681a9 100644
--- a/app/forms.py
+++ b/app/forms.py
@@ -9,9 +9,10 @@
from flask.ext.babel import get_locale
from flask_wtf import Form
from flask_wtf.file import FileField, FileAllowed
-from flask_security.forms import (LoginForm, RegisterForm,
+from flask_security.forms import (LoginForm, ConfirmRegisterForm,
ForgotPasswordForm, ChangePasswordForm,
ResetPasswordForm,
+ SendConfirmationForm,
email_required,
email_validator, unique_user_email,
valid_user_email,
@@ -213,6 +214,13 @@ class ChangeLocaleForm(Form):
)
+class NOISendConfirmationForm(SendConfirmationForm):
+ '''
+ Localizeable version of Flask-Security's SendConfirmationForm
+ '''
+ submit = SubmitField(lazy_gettext('Resend Confirmation Instructions'))
+
+
class NOIForgotPasswordForm(ForgotPasswordForm):
'''
Localizeable version of Flask-Security's ForgotPasswordForm
@@ -233,9 +241,9 @@ class NOILoginForm(LoginForm):
submit = SubmitField(lazy_gettext('Log in'))
-class NOIRegisterForm(RegisterForm):
+class NOIConfirmRegisterForm(ConfirmRegisterForm):
'''
- Localizeable version of Flask-Security's RegisterForm
+ Localizeable version of Flask-Security's ConfirmRegisterForm
'''
# Note that extra fields in this registration form are passed
@@ -254,7 +262,6 @@ class NOIRegisterForm(RegisterForm):
password = PasswordField(
lazy_gettext('Password'), validators=[password_required,
password_length])
- password_confirm = None
submit = SubmitField(lazy_gettext('Sign up'))
diff --git a/app/l10n.py b/app/l10n.py
index f8aef37..d709785 100644
--- a/app/l10n.py
+++ b/app/l10n.py
@@ -30,6 +30,12 @@ def gettext_no_interpolate(string):
app.config['SECURITY_MSG_UNAUTHORIZED'] = (
lazy_gettext('You do not have permission to view this resource.'), 'error')
+ app.config['SECURITY_MSG_EMAIL_CONFIRMED'] = (
+ lazy_gettext('Thank you. Your email has been confirmed.'), 'success')
+ app.config['SECURITY_MSG_ALREADY_CONFIRMED'] = (
+ lazy_gettext('Your email has already been confirmed.'), 'info')
+ app.config['SECURITY_MSG_INVALID_CONFIRMATION_TOKEN'] = (
+ lazy_gettext('Invalid confirmation token.'), 'error')
app.config['SECURITY_MSG_EMAIL_ALREADY_ASSOCIATED'] = (
lazy_gettext('%(email)s is already associated with an account.'), 'error')
app.config['SECURITY_MSG_PASSWORD_MISMATCH'] = (
@@ -45,6 +51,14 @@ def gettext_no_interpolate(string):
'instructions have been sent to %(email)s.'), 'error')
app.config['SECURITY_MSG_INVALID_RESET_PASSWORD_TOKEN'] = (
lazy_gettext('Invalid reset password token.'), 'error')
+ app.config['SECURITY_MSG_CONFIRMATION_REQUIRED'] = (
+ lazy_gettext('Email requires confirmation.'), 'error')
+ app.config['SECURITY_MSG_CONFIRMATION_REQUEST'] = (
+ lazy_gettext('Confirmation instructions have been sent to %(email)s.'), 'info')
+ app.config['SECURITY_MSG_CONFIRMATION_EXPIRED'] = (
+ lazy_gettext('You did not confirm your email within %(within)s. New '
+ 'instructions to confirm your email have been sent to '
+ '%(email)s.'), 'error')
app.config['SECURITY_MSG_LOGIN_EXPIRED'] = (
lazy_gettext('You did not login within %(within)s. New instructions to '
'login have been sent to %(email)s.'), 'error')
diff --git a/app/static/sass/_error-page.scss b/app/static/sass/_error-page.scss
new file mode 100644
index 0000000..d3f2179
--- /dev/null
+++ b/app/static/sass/_error-page.scss
@@ -0,0 +1,20 @@
+.e-error-display {
+ background-color: $blue;
+ text-align: center;
+ text-transform: uppercase;
+ padding-top: 4em;
+ h2,h3,h4 {
+ color: $white-80;;
+ padding: .5em;
+ }
+}
+
+.error-page-icon {
+ color: $white-80;;
+ font-size: 4em;
+ }
+
+.e-error-display a {
+ color: $white-90;
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/app/static/sass/styles.scss b/app/static/sass/styles.scss
index a37f45d..bef6bbb 100644
--- a/app/static/sass/styles.scss
+++ b/app/static/sass/styles.scss
@@ -96,6 +96,8 @@ body {
@import 'desktop-hack';
+@import 'error-page';
+
/* Useful for making blocks of content that we don't have time to style
* not look utterly unstyled and horrible. */
.b-temporary-styling {
diff --git a/app/style_guide.py b/app/style_guide.py
index 6946e8b..ac44d4d 100644
--- a/app/style_guide.py
+++ b/app/style_guide.py
@@ -48,3 +48,4 @@ def send_static_asset(path):
return send_from_directory('/noi/app/templates/style-guide/static',
path)
+
diff --git a/app/templates/404.html b/app/templates/404.html
new file mode 100644
index 0000000..4654c70
--- /dev/null
+++ b/app/templates/404.html
@@ -0,0 +1,15 @@
+{% extends '__base_ui__.html' %}
+
+{% block title %}Page Not Found | Network of Innovators{% endblock %}
+{% block body_class %}b-landing-page{% endblock %}
+
+{% block content %}
+ Error 404
+ We couldn't find what you were looking for
+ Try using the navigation above or click here to go back to the homepage
+