From 9209ae05f7675c1a56f18af7395333af2bcc1dec Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:34:17 -0500 Subject: [PATCH 01/31] Add placeholder 404.html to app/templates --- app/templates/404.html | 1 + 1 file changed, 1 insertion(+) create mode 100644 app/templates/404.html diff --git a/app/templates/404.html b/app/templates/404.html new file mode 100644 index 0000000..21eb15e --- /dev/null +++ b/app/templates/404.html @@ -0,0 +1 @@ +404 Error - Page Not Found \ No newline at end of file From 7ddbf2ba2f2da2788ce7822bab09c8966b1d8d06 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:45:22 -0500 Subject: [PATCH 02/31] Add 404 error handler to app/views.py --- app/views.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/views.py b/app/views.py index 1137641..f379e36 100644 --- a/app/views.py +++ b/app/views.py @@ -779,3 +779,10 @@ def invite(): flash(gettext("Your form submission was invalid."), "error") return render_template('invite.html', form=form) + +@views.app_errorhandler(404) +def page_not_found(e): + ''' + render custom page for 404 errors + ''' + return render_template('404.html'), 404 \ No newline at end of file From b2de4b45dab424d7122a313a361d6ca310e7ef40 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:49:49 -0500 Subject: [PATCH 03/31] Add temporary styling to 404 page --- app/templates/404.html | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/templates/404.html b/app/templates/404.html index 21eb15e..6754c2b 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1 +1,13 @@ -404 Error - Page Not Found \ No newline at end of file +{% extends '__base_ui__.html' %} + +{% block title %}{{ gettext('Network of Innovators') }}{% endblock %} +{% block body_class %}b-landing-page{% endblock %} + +{% block content %} +
+ +

Page Not Found

+

Sorry, but the page you are trying to view does not exist.

+
+ +{% endblock %} \ No newline at end of file From d5915404f76b85284c4607aae847f650cf446b1e Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:52:44 -0500 Subject: [PATCH 04/31] Fix title in head to 'Page Not Found...' --- app/templates/404.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/404.html b/app/templates/404.html index 6754c2b..53426f4 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1,6 +1,6 @@ {% extends '__base_ui__.html' %} -{% block title %}{{ gettext('Network of Innovators') }}{% endblock %} +{% block title %}Page Not Found | Network of Innovators{% endblock %} {% block body_class %}b-landing-page{% endblock %} {% block content %} From f307e2cae7a3cea22fdd0c71d088268cf0200f2e Mon Sep 17 00:00:00 2001 From: Atul Varma Date: Mon, 22 Feb 2016 10:44:19 -0500 Subject: [PATCH 05/31] Show tracebacks for 500 errors in tests. --- app/tests/test_views.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/tests/test_views.py b/app/tests/test_views.py index caf7915..5463ab1 100644 --- a/app/tests/test_views.py +++ b/app/tests/test_views.py @@ -1,3 +1,4 @@ +import logging from StringIO import StringIO from urllib import urlencode from moto import mock_s3 @@ -38,7 +39,13 @@ class ViewTestCase(DbTestCase): ) def create_app(self): - return create_app(config=self.BASE_APP_CONFIG.copy()) + app = create_app(config=self.BASE_APP_CONFIG.copy()) + + handler = logging.StreamHandler() + handler.setLevel(logging.DEBUG) + app.logger.addHandler(handler) + + return app def register_and_login(self, username, password): res = self.client.post('/register', data=dict( From d9f8633e91d78524383e84fba819d25da19d9415 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:34:17 -0500 Subject: [PATCH 06/31] Add placeholder 404.html to app/templates --- app/templates/404.html | 1 + 1 file changed, 1 insertion(+) create mode 100644 app/templates/404.html diff --git a/app/templates/404.html b/app/templates/404.html new file mode 100644 index 0000000..21eb15e --- /dev/null +++ b/app/templates/404.html @@ -0,0 +1 @@ +404 Error - Page Not Found \ No newline at end of file From 1ca2a02a4b270f3f4ff482c27ae99fa5648fbda2 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:45:22 -0500 Subject: [PATCH 07/31] Add 404 error handler to app/views.py --- app/views.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/views.py b/app/views.py index 1137641..f379e36 100644 --- a/app/views.py +++ b/app/views.py @@ -779,3 +779,10 @@ def invite(): flash(gettext("Your form submission was invalid."), "error") return render_template('invite.html', form=form) + +@views.app_errorhandler(404) +def page_not_found(e): + ''' + render custom page for 404 errors + ''' + return render_template('404.html'), 404 \ No newline at end of file From 88627c3c888421e1b3c17c9c95512eee55982eef Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:49:49 -0500 Subject: [PATCH 08/31] Add temporary styling to 404 page --- app/templates/404.html | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/templates/404.html b/app/templates/404.html index 21eb15e..6754c2b 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1 +1,13 @@ -404 Error - Page Not Found \ No newline at end of file +{% extends '__base_ui__.html' %} + +{% block title %}{{ gettext('Network of Innovators') }}{% endblock %} +{% block body_class %}b-landing-page{% endblock %} + +{% block content %} +
+ +

Page Not Found

+

Sorry, but the page you are trying to view does not exist.

+
+ +{% endblock %} \ No newline at end of file From c3c38552f73058141e49b036e52214053a147b3d Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:52:44 -0500 Subject: [PATCH 09/31] Fix title in head to 'Page Not Found...' --- app/templates/404.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/404.html b/app/templates/404.html index 6754c2b..53426f4 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1,6 +1,6 @@ {% extends '__base_ui__.html' %} -{% block title %}{{ gettext('Network of Innovators') }}{% endblock %} +{% block title %}Page Not Found | Network of Innovators{% endblock %} {% block body_class %}b-landing-page{% endblock %} {% block content %} From b57e92cd0fdd1c067fbbeffa59241785dc2e1277 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Thu, 25 Feb 2016 21:25:45 -0500 Subject: [PATCH 10/31] Add styling and icon to 404 page --- app/static/sass/_error-page.scss | 14 ++++++++++++++ app/static/sass/styles.scss | 2 ++ app/style_guide.py | 1 + app/templates/404.html | 11 ++++++----- 4 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 app/static/sass/_error-page.scss diff --git a/app/static/sass/_error-page.scss b/app/static/sass/_error-page.scss new file mode 100644 index 0000000..040b13a --- /dev/null +++ b/app/static/sass/_error-page.scss @@ -0,0 +1,14 @@ +.e-error-display { + background-color: $blue; + text-align: center; + text-transform: uppercase; + padding: 4em; + h2,h4 { + color: rgba(255, 255, 255, .8); + } +} + +.error-page-icon { + color: rgba(255, 255, 255, .8); + font-size: 4em; + } \ 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 index 53426f4..80249ca 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -4,10 +4,11 @@ {% block body_class %}b-landing-page{% endblock %} {% block content %} -
- -

Page Not Found

-

Sorry, but the page you are trying to view does not exist.

+
+
+ error_outline +

Error 404

+

We couldn't find what you were looking for

+
- {% endblock %} \ No newline at end of file From dbd34c55d623cbcfe55374871168eb92e7437b62 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Fri, 26 Feb 2016 09:41:27 -0500 Subject: [PATCH 11/31] Change text to error page and adjust styling --- app/static/sass/_error-page.scss | 16 +++++++++++----- app/templates/404.html | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/static/sass/_error-page.scss b/app/static/sass/_error-page.scss index 040b13a..d3f2179 100644 --- a/app/static/sass/_error-page.scss +++ b/app/static/sass/_error-page.scss @@ -2,13 +2,19 @@ background-color: $blue; text-align: center; text-transform: uppercase; - padding: 4em; - h2,h4 { - color: rgba(255, 255, 255, .8); + padding-top: 4em; + h2,h3,h4 { + color: $white-80;; + padding: .5em; } } .error-page-icon { - color: rgba(255, 255, 255, .8); + color: $white-80;; font-size: 4em; - } \ No newline at end of file + } + +.e-error-display a { + color: $white-90; + font-weight: bold; +} \ No newline at end of file diff --git a/app/templates/404.html b/app/templates/404.html index 80249ca..4654c70 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -8,7 +8,8 @@
error_outline

Error 404

-

We couldn't find what you were looking for

+

We couldn't find what you were looking for

+

Try using the navigation above or click here to go back to the homepage

{% endblock %} \ No newline at end of file From 81b730f936ec665493bac2d7c06bd638aa54bde4 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Fri, 26 Feb 2016 18:35:47 -0500 Subject: [PATCH 12/31] Remove error handler in views --- app/views.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/views.py b/app/views.py index f379e36..1137641 100644 --- a/app/views.py +++ b/app/views.py @@ -779,10 +779,3 @@ def invite(): flash(gettext("Your form submission was invalid."), "error") return render_template('invite.html', form=form) - -@views.app_errorhandler(404) -def page_not_found(e): - ''' - render custom page for 404 errors - ''' - return render_template('404.html'), 404 \ No newline at end of file From 8e6c559165f167dc2dcd5d3fce471ec955a653d9 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Fri, 26 Feb 2016 18:36:37 -0500 Subject: [PATCH 13/31] Add error_handlers module --- app/error_handlers.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 app/error_handlers.py diff --git a/app/error_handlers.py b/app/error_handlers.py new file mode 100644 index 0000000..590d113 --- /dev/null +++ b/app/error_handlers.py @@ -0,0 +1,9 @@ +import flask +from flask import Blueprint, render_template + + +blueprint = flask.Blueprint('error_handlers', __name__) + +@blueprint.app_errorhandler(404) +def handleerror404(e): + return render_template('404.html'), 404 From 876109a29c424cf480832a81744bc551f972c016 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Fri, 26 Feb 2016 18:37:13 -0500 Subject: [PATCH 14/31] Add register_blueprint for error handling --- app/factory.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/factory.py b/app/factory.py index cd4949a..09977f3 100644 --- a/app/factory.py +++ b/app/factory.py @@ -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)) From 30d93cff6fadb44312f989ca52735eaec5012bf8 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Fri, 26 Feb 2016 18:38:55 -0500 Subject: [PATCH 15/31] Change to 4 spaces --- app/error_handlers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/error_handlers.py b/app/error_handlers.py index 590d113..1356024 100644 --- a/app/error_handlers.py +++ b/app/error_handlers.py @@ -1,7 +1,6 @@ import flask from flask import Blueprint, render_template - blueprint = flask.Blueprint('error_handlers', __name__) @blueprint.app_errorhandler(404) From c66065a5a842cd578b13356e82195cd51e35ab08 Mon Sep 17 00:00:00 2001 From: Atul Varma Date: Fri, 26 Feb 2016 07:35:26 -0500 Subject: [PATCH 16/31] Add support for optional email confirmation. --- app/config/config.yml | 8 +++- app/factory.py | 10 ++-- app/forms.py | 15 ++++-- app/l10n.py | 14 ++++++ app/templates/__base_new__.html | 6 +++ app/templates/security/_menu_new.html | 3 -- app/templates/security/send_confirmation.html | 24 ++++++++++ app/tests/test_views.py | 48 ++++++++++++++++++- app/views.py | 8 ++++ 9 files changed, 121 insertions(+), 15 deletions(-) create mode 100644 app/templates/security/send_confirmation.html 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/factory.py b/app/factory.py index cd4949a..54eb3e6 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 @@ -81,9 +81,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 +115,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/templates/__base_new__.html b/app/templates/__base_new__.html index f4e0293..e49fb7e 100644 --- a/app/templates/__base_new__.html +++ b/app/templates/__base_new__.html @@ -27,7 +27,13 @@ {% from "_macros.html" import render_alert %} {% for category, message in get_flashed_messages(with_categories=true) %} + {# Some flashed messages come from third-party code and we don't want + them to be displayed. The only way we can disable them from being + displayed, unfortunately, is to set their message text to something + we detect for here. #} + {%- if message != 'DO_NOT_DISPLAY_ME' -%} {{ render_alert(message=message, category=category) }} + {%- endif -%} {% endfor %} {% block header %}{% endblock %} diff --git a/app/templates/security/_menu_new.html b/app/templates/security/_menu_new.html index 5490e5c..5f6e29a 100644 --- a/app/templates/security/_menu_new.html +++ b/app/templates/security/_menu_new.html @@ -5,8 +5,5 @@

{{ gettext('Menu') }}

{% if security.recoverable %}
  • {{ gettext('Forgot password') }}
  • {% endif %} - {% if security.confirmable %} -
  • {{ gettext('Confirm account') }}
  • - {% endif %} {% endif %} diff --git a/app/templates/security/send_confirmation.html b/app/templates/security/send_confirmation.html new file mode 100644 index 0000000..46aa55c --- /dev/null +++ b/app/templates/security/send_confirmation.html @@ -0,0 +1,24 @@ +{% extends '__base_new__.html' %} + +{% block title %}{{ gettext('Send confirmation instructions') }}{% endblock %} + +{% from "security/_macros_new.html" import render_field_with_errors, render_field %} + +{% block content %} +
    +
    + +
    + +
    +

    {{ gettext('Send confirmation instructions') }}

    +
    + {{ send_confirmation_form.hidden_tag() }} + {{ render_field_with_errors(send_confirmation_form.email) }} + {{ send_confirmation_form.submit(class="b-button") }} +
    +
    + {% include "security/_menu_new.html" %} +
    + +{% endblock %} diff --git a/app/tests/test_views.py b/app/tests/test_views.py index 5463ab1..d7d1ee6 100644 --- a/app/tests/test_views.py +++ b/app/tests/test_views.py @@ -1,8 +1,10 @@ +import re import logging from StringIO import StringIO from urllib import urlencode from moto import mock_s3 import boto +from flask import url_for from flask_login import current_user from app import (QUESTIONS_BY_ID, MIN_QUESTIONS_TO_JOIN, LEVELS, @@ -103,7 +105,8 @@ def assertRequiresLogin(self, path, method='get'): self.assertRedirects(method(path), '/login?%s' % urlencode({'next': path})) -class InviteTests(ViewTestCase): + +class EmailTestCase(ViewTestCase): BASE_APP_CONFIG = ViewTestCase.BASE_APP_CONFIG.copy() BASE_APP_CONFIG.update( @@ -112,6 +115,8 @@ class InviteTests(ViewTestCase): MAIL_SUPPRESS_SEND=True, ) + +class InviteTests(EmailTestCase): def setUp(self): super(ViewTestCase, self).setUp() self.login() @@ -637,7 +642,48 @@ def test_valid_form_sets_picture(self): self.assertEqual(key.content_type, 'image/png') +class EmailConfirmationTests(EmailTestCase): + def test_confirmation_works(self): + self.register_and_login('foo@example.org', 'test123') + + with mail.record_messages() as outbox: + res = self.client.post('/confirm', data=dict( + email=u'foo@example.org' + )) + self.assertEqual(len(outbox), 1) + msg = outbox[0] + self.assertEqual(msg.sender, 'noreply@networkofinnovators.org') + self.assertEqual(msg.recipients, ['foo@example.org']) + assert 'http://localhost/confirm/' in msg.body + assert ('Confirmation instructions have been ' + 'sent to foo@example.org') in res.data + + token = re.search( + r'http:\/\/localhost\/confirm\/(.+)', + msg.body, + flags=re.MULTILINE + ).group(1) + + res = self.client.get('/confirm/%s' % token) + self.assertRedirects(res, '/confirm/success') + + res = self.client.get('/confirm/success') + self.assertRedirects(res, '/activity') + + res = self.client.get('/activity') + self.assert200(res) + assert 'Your email has been confirmed' in res.data + class ViewTests(ViewTestCase): + def test_security_post_views_exist(self): + view_names = [ + self.app.config['SECURITY_POST_REGISTER_VIEW'], + self.app.config['SECURITY_POST_CONFIRM_VIEW'], + self.app.config['SECURITY_POST_LOGIN_VIEW'], + ] + for view_name in view_names: + url_for(view_name) + def test_main_page_is_ok(self): self.assert200(self.client.get('/')) diff --git a/app/views.py b/app/views.py index 1137641..07eb6e9 100644 --- a/app/views.py +++ b/app/views.py @@ -295,6 +295,14 @@ def register_step_2(): return render_template('register-step-2.html', form=form) +@views.route('/confirm/success') +@login_required +def confirmation_success(): + # In the future, we can make this view redirect the user to + # whatever they wanted to do that required confirmation. + return redirect(url_for('views.activity')) + + def render_user_profile(userid=None, **kwargs): if userid is None: user = current_user From 9ecbabd502a1277d2ba9ab540b430e8c275edcc7 Mon Sep 17 00:00:00 2001 From: Atul Varma Date: Fri, 26 Feb 2016 18:25:21 -0500 Subject: [PATCH 17/31] better 500 tracebacks for *all* tests, not just test_views. --- app/tests/test_admin.py | 4 ++-- app/tests/test_l10n.py | 5 ++--- app/tests/test_models.py | 7 +++---- app/tests/test_style_guide.py | 5 +++-- app/tests/test_views.py | 11 ++--------- app/tests/util.py | 20 ++++++++++++++++++++ 6 files changed, 32 insertions(+), 20 deletions(-) diff --git a/app/tests/test_admin.py b/app/tests/test_admin.py index 0de97e3..ad47232 100644 --- a/app/tests/test_admin.py +++ b/app/tests/test_admin.py @@ -1,8 +1,8 @@ from base64 import b64encode -from flask import Flask from flask_testing import TestCase +from .util import create_empty_flask_app from .test_views import ViewTestCase from ..admin import _init_basic_auth as init_basic_auth @@ -47,7 +47,7 @@ def test_admin_users_are_not_redirected_to_login(self): class BasicAuthTests(TestCase): def create_app(self): - app = Flask('test') + app = create_empty_flask_app() app.config.update({'ADMIN_UI_BASIC_AUTH': 'foo:bar'}) @app.route('/foo/bar') diff --git a/app/tests/test_l10n.py b/app/tests/test_l10n.py index 35cff5d..ca0f4c7 100644 --- a/app/tests/test_l10n.py +++ b/app/tests/test_l10n.py @@ -2,14 +2,13 @@ from babel.messages.pofile import read_po from babel.messages.catalog import TranslationError, Message from babel.messages import checkers -from flask import Flask from .test_views import ViewTestCase -from .util import eq_ +from .util import eq_, create_empty_flask_app from app import l10n def test_flask_security_strings_do_not_interpolate(): - app = Flask('test') + app = create_empty_flask_app() l10n.configure_app(app) eq_(unicode(app.config['SECURITY_MSG_EMAIL_ALREADY_ASSOCIATED'][0]), '%(email)s is already associated with an account.') diff --git a/app/tests/test_models.py b/app/tests/test_models.py index b5f2088..31caf76 100644 --- a/app/tests/test_models.py +++ b/app/tests/test_models.py @@ -1,7 +1,6 @@ import time import StringIO import contextlib -from flask import Flask from flask_testing import TestCase from sqlalchemy import create_engine from sqlalchemy.exc import OperationalError, IntegrityError @@ -9,7 +8,7 @@ from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT import psycopg2 -from .util import eq_ +from .util import eq_, create_empty_flask_app from .factories import UserFactory, UserSkillFactory from .. import models, LEVELS, babel @@ -60,7 +59,7 @@ def create_postgres_database(): con.close() def db_test_request_context(): - app = Flask('minimal_db_app') + app = create_empty_flask_app() app.config['SQLALCHEMY_DATABASE_URI'] = TEST_DB_URL db.init_app(app) return app.test_request_context() @@ -96,7 +95,7 @@ class DbTestCase(TestCase): ) def create_app(self): - app = Flask('test') + app = create_empty_flask_app() app.config.update(self.BASE_APP_CONFIG) db.init_app(app) return app diff --git a/app/tests/test_style_guide.py b/app/tests/test_style_guide.py index df26d07..82b3e32 100644 --- a/app/tests/test_style_guide.py +++ b/app/tests/test_style_guide.py @@ -2,12 +2,13 @@ from app import assets, csrf from app.views import views -from flask import Flask from flask.ext.testing import TestCase +from .util import create_empty_flask_app + class StyleGuideTests(TestCase): def create_app(self): - app = Flask('app') + app = create_empty_flask_app() app.config['SECRET_KEY'] = 'test' app.register_blueprint(views) app.register_blueprint(style_guide.views) diff --git a/app/tests/test_views.py b/app/tests/test_views.py index d7d1ee6..94d02a9 100644 --- a/app/tests/test_views.py +++ b/app/tests/test_views.py @@ -1,5 +1,4 @@ import re -import logging from StringIO import StringIO from urllib import urlencode from moto import mock_s3 @@ -9,10 +8,10 @@ from app import (QUESTIONS_BY_ID, MIN_QUESTIONS_TO_JOIN, LEVELS, QUESTIONNAIRES_BY_ID, mail) -from app.factory import create_app from app.views import views, get_best_registration_step_url from app.models import User, SharedMessageEvent, ConnectionEvent, db +from .util import create_app from .test_models import DbTestCase from .factories import UserFactory, UserSkillFactory @@ -41,13 +40,7 @@ class ViewTestCase(DbTestCase): ) def create_app(self): - app = create_app(config=self.BASE_APP_CONFIG.copy()) - - handler = logging.StreamHandler() - handler.setLevel(logging.DEBUG) - app.logger.addHandler(handler) - - return app + return create_app(config=self.BASE_APP_CONFIG.copy()) def register_and_login(self, username, password): res = self.client.post('/register', data=dict( diff --git a/app/tests/util.py b/app/tests/util.py index 7b7940d..7c95a8d 100644 --- a/app/tests/util.py +++ b/app/tests/util.py @@ -1,3 +1,22 @@ +import logging +from flask import Flask + +from app import factory + +def add_logging_to_app(app): + handler = logging.StreamHandler() + handler.setLevel(logging.DEBUG) + app.logger.addHandler(handler) + + return app + +def create_app(*args, **kwargs): + app = factory.create_app(*args, **kwargs) + return add_logging_to_app(app) + +def create_empty_flask_app(name='app'): + return add_logging_to_app(Flask(name)) + # Taken from nose: # # https://github.com/nose-devs/nose/blob/master/nose/tools/trivial.py @@ -7,3 +26,4 @@ def eq_(a, b, msg=None): """ if not a == b: raise AssertionError(msg or "%r != %r" % (a, b)) + From 02cf7595131b1e865c9232f59cca5d9ec848ead8 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:34:17 -0500 Subject: [PATCH 18/31] Add placeholder 404.html to app/templates --- app/templates/404.html | 1 + 1 file changed, 1 insertion(+) create mode 100644 app/templates/404.html diff --git a/app/templates/404.html b/app/templates/404.html new file mode 100644 index 0000000..21eb15e --- /dev/null +++ b/app/templates/404.html @@ -0,0 +1 @@ +404 Error - Page Not Found \ No newline at end of file From 1d0f587eff9ac0204010134b7ed3ad344cfcdf3f Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:45:22 -0500 Subject: [PATCH 19/31] Add 404 error handler to app/views.py --- app/views.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/views.py b/app/views.py index 07eb6e9..81d6eac 100644 --- a/app/views.py +++ b/app/views.py @@ -787,3 +787,10 @@ def invite(): flash(gettext("Your form submission was invalid."), "error") return render_template('invite.html', form=form) + +@views.app_errorhandler(404) +def page_not_found(e): + ''' + render custom page for 404 errors + ''' + return render_template('404.html'), 404 \ No newline at end of file From c6db4b22054ea39ce2f80abcc0bd7c41376ede47 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:49:49 -0500 Subject: [PATCH 20/31] Add temporary styling to 404 page --- app/templates/404.html | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/templates/404.html b/app/templates/404.html index 21eb15e..6754c2b 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1 +1,13 @@ -404 Error - Page Not Found \ No newline at end of file +{% extends '__base_ui__.html' %} + +{% block title %}{{ gettext('Network of Innovators') }}{% endblock %} +{% block body_class %}b-landing-page{% endblock %} + +{% block content %} +
    + +

    Page Not Found

    +

    Sorry, but the page you are trying to view does not exist.

    +
    + +{% endblock %} \ No newline at end of file From b64a488ec94da4e8b3fe49b62f6e9f23f6e10a62 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:52:44 -0500 Subject: [PATCH 21/31] Fix title in head to 'Page Not Found...' --- app/templates/404.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/404.html b/app/templates/404.html index 6754c2b..53426f4 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1,6 +1,6 @@ {% extends '__base_ui__.html' %} -{% block title %}{{ gettext('Network of Innovators') }}{% endblock %} +{% block title %}Page Not Found | Network of Innovators{% endblock %} {% block body_class %}b-landing-page{% endblock %} {% block content %} From 553c8e42bd19c4470bdfb9d37e294130f10d9f5a Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:34:17 -0500 Subject: [PATCH 22/31] Add placeholder 404.html to app/templates --- app/templates/404.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/templates/404.html b/app/templates/404.html index 53426f4..b5efccc 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1,3 +1,4 @@ + {% extends '__base_ui__.html' %} {% block title %}Page Not Found | Network of Innovators{% endblock %} @@ -10,4 +11,4 @@

    Page Not Found

    Sorry, but the page you are trying to view does not exist.

    -{% endblock %} \ No newline at end of file +{% endblock %} From 34af48d5a96d842a1b7dfb62460107102fe84de6 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:49:49 -0500 Subject: [PATCH 23/31] Add temporary styling to 404 page --- app/templates/404.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/templates/404.html b/app/templates/404.html index b5efccc..02d0503 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1,7 +1,13 @@ +<<<<<<< 553c8e42bd19c4470bdfb9d37e294130f10d9f5a {% extends '__base_ui__.html' %} {% block title %}Page Not Found | Network of Innovators{% endblock %} +======= +{% extends '__base_ui__.html' %} + +{% block title %}{{ gettext('Network of Innovators') }}{% endblock %} +>>>>>>> Add temporary styling to 404 page {% block body_class %}b-landing-page{% endblock %} {% block content %} @@ -11,4 +17,8 @@

    Page Not Found

    Sorry, but the page you are trying to view does not exist.

    +<<<<<<< 553c8e42bd19c4470bdfb9d37e294130f10d9f5a +{% endblock %} +======= {% endblock %} +>>>>>>> Add temporary styling to 404 page From e20be7e0f179ba1ff1f6c5e12b5c6dd6b0794e99 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Mon, 22 Feb 2016 13:52:44 -0500 Subject: [PATCH 24/31] Fix title in head to 'Page Not Found...' --- app/templates/404.html | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/templates/404.html b/app/templates/404.html index 02d0503..8894852 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1,13 +1,8 @@ -<<<<<<< 553c8e42bd19c4470bdfb9d37e294130f10d9f5a {% extends '__base_ui__.html' %} {% block title %}Page Not Found | Network of Innovators{% endblock %} -======= -{% extends '__base_ui__.html' %} -{% block title %}{{ gettext('Network of Innovators') }}{% endblock %} ->>>>>>> Add temporary styling to 404 page {% block body_class %}b-landing-page{% endblock %} {% block content %} @@ -17,8 +12,4 @@

    Page Not Found

    Sorry, but the page you are trying to view does not exist.

    -<<<<<<< 553c8e42bd19c4470bdfb9d37e294130f10d9f5a -{% endblock %} -======= {% endblock %} ->>>>>>> Add temporary styling to 404 page From ebd81b3f24b6e9483e623abbb4ba8cbf8832c6b0 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Thu, 25 Feb 2016 21:25:45 -0500 Subject: [PATCH 25/31] Add styling and icon to 404 page --- app/static/sass/_error-page.scss | 14 ++++++++++++++ app/static/sass/styles.scss | 2 ++ app/style_guide.py | 1 + app/templates/404.html | 13 +++++++------ 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 app/static/sass/_error-page.scss diff --git a/app/static/sass/_error-page.scss b/app/static/sass/_error-page.scss new file mode 100644 index 0000000..040b13a --- /dev/null +++ b/app/static/sass/_error-page.scss @@ -0,0 +1,14 @@ +.e-error-display { + background-color: $blue; + text-align: center; + text-transform: uppercase; + padding: 4em; + h2,h4 { + color: rgba(255, 255, 255, .8); + } +} + +.error-page-icon { + color: rgba(255, 255, 255, .8); + font-size: 4em; + } \ 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 index 8894852..4706529 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -6,10 +6,11 @@ {% block body_class %}b-landing-page{% endblock %} {% block content %} -
    - -

    Page Not Found

    -

    Sorry, but the page you are trying to view does not exist.

    +
    +
    + error_outline +

    Error 404

    +

    We couldn't find what you were looking for

    +
    - -{% endblock %} +{% endblock %} \ No newline at end of file From 5f0bd23722a20a45ec8e70a60e89b58c035ce13d Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Fri, 26 Feb 2016 09:41:27 -0500 Subject: [PATCH 26/31] Change text to error page and adjust styling --- app/static/sass/_error-page.scss | 16 +++++++++++----- app/templates/404.html | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/static/sass/_error-page.scss b/app/static/sass/_error-page.scss index 040b13a..d3f2179 100644 --- a/app/static/sass/_error-page.scss +++ b/app/static/sass/_error-page.scss @@ -2,13 +2,19 @@ background-color: $blue; text-align: center; text-transform: uppercase; - padding: 4em; - h2,h4 { - color: rgba(255, 255, 255, .8); + padding-top: 4em; + h2,h3,h4 { + color: $white-80;; + padding: .5em; } } .error-page-icon { - color: rgba(255, 255, 255, .8); + color: $white-80;; font-size: 4em; - } \ No newline at end of file + } + +.e-error-display a { + color: $white-90; + font-weight: bold; +} \ No newline at end of file diff --git a/app/templates/404.html b/app/templates/404.html index 4706529..b1e6cb9 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -10,7 +10,8 @@
    error_outline

    Error 404

    -

    We couldn't find what you were looking for

    +

    We couldn't find what you were looking for

    +

    Try using the navigation above or click here to go back to the homepage

    {% endblock %} \ No newline at end of file From c382ab266361eb6fdc0dd69784226fb989b15c8e Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Fri, 26 Feb 2016 18:35:47 -0500 Subject: [PATCH 27/31] Remove error handler in views --- app/views.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/views.py b/app/views.py index 81d6eac..07eb6e9 100644 --- a/app/views.py +++ b/app/views.py @@ -787,10 +787,3 @@ def invite(): flash(gettext("Your form submission was invalid."), "error") return render_template('invite.html', form=form) - -@views.app_errorhandler(404) -def page_not_found(e): - ''' - render custom page for 404 errors - ''' - return render_template('404.html'), 404 \ No newline at end of file From 3167074b7fb88ff5fb2af9ba7e4dd06c37fae6b0 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Fri, 26 Feb 2016 18:36:37 -0500 Subject: [PATCH 28/31] Add error_handlers module --- app/error_handlers.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 app/error_handlers.py diff --git a/app/error_handlers.py b/app/error_handlers.py new file mode 100644 index 0000000..590d113 --- /dev/null +++ b/app/error_handlers.py @@ -0,0 +1,9 @@ +import flask +from flask import Blueprint, render_template + + +blueprint = flask.Blueprint('error_handlers', __name__) + +@blueprint.app_errorhandler(404) +def handleerror404(e): + return render_template('404.html'), 404 From 7da40ac8e37c8c9a0ecde15eb8247ddef51f7f0c Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Fri, 26 Feb 2016 18:37:13 -0500 Subject: [PATCH 29/31] Add register_blueprint for error handling --- app/factory.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/factory.py b/app/factory.py index 54eb3e6..893ed68 100644 --- a/app/factory.py +++ b/app/factory.py @@ -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)) From 664f83949694b2b44ef74f478a604c7f8d8022f3 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Fri, 26 Feb 2016 18:38:55 -0500 Subject: [PATCH 30/31] Change to 4 spaces --- app/error_handlers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/error_handlers.py b/app/error_handlers.py index 590d113..1356024 100644 --- a/app/error_handlers.py +++ b/app/error_handlers.py @@ -1,7 +1,6 @@ import flask from flask import Blueprint, render_template - blueprint = flask.Blueprint('error_handlers', __name__) @blueprint.app_errorhandler(404) From 94c12dd98f8ef4596f31e601fea1fa2fa5e81150 Mon Sep 17 00:00:00 2001 From: Denny Tek Date: Fri, 26 Feb 2016 20:34:25 -0500 Subject: [PATCH 31/31] Add more descriptive name for method --- app/error_handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/error_handlers.py b/app/error_handlers.py index 1356024..65b1515 100644 --- a/app/error_handlers.py +++ b/app/error_handlers.py @@ -4,5 +4,5 @@ blueprint = flask.Blueprint('error_handlers', __name__) @blueprint.app_errorhandler(404) -def handleerror404(e): +def page_not_found(e): return render_template('404.html'), 404