mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-04 18:32:33 -05:00
Merge with master.
This commit is contained in:
12
README.md
12
README.md
@@ -28,17 +28,17 @@ Node's package management tool.
|
||||
brew install node
|
||||
```
|
||||
|
||||
n is a tool for managing different versions of node. The following installs n and uses the latest version of node.
|
||||
|
||||
n is a tool for managing different versions of node. The following installs n
|
||||
and uses the latest version of node.
|
||||
```shell
|
||||
npm install -g n
|
||||
n latest
|
||||
npm rebuild node-sass
|
||||
```
|
||||
|
||||
Most of the frontend dependencies are managed using Git Submodules. Some are
|
||||
managed with NPM and Bower. To install or update *all the things*, run
|
||||
The frontend dependencies are managed using NPM and Bower. To install or update
|
||||
*all the things*, run
|
||||
```shell
|
||||
git submodule init
|
||||
git submodule update
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
from flask import Flask, session, Markup, render_template
|
||||
from flask import Flask, session, Markup, escape, render_template
|
||||
from flask._compat import string_types
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
from flask_login import LoginManager
|
||||
@@ -47,6 +47,7 @@ def create_app(config_name, config_overrides=None):
|
||||
|
||||
application.add_template_filter(placeholders)
|
||||
application.add_template_filter(replace_placeholders)
|
||||
application.add_template_filter(nl2br)
|
||||
|
||||
application.after_request(useful_headers_after_request)
|
||||
register_errorhandlers(application)
|
||||
@@ -108,6 +109,14 @@ def placeholders(value):
|
||||
))
|
||||
|
||||
|
||||
def nl2br(value):
|
||||
_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')
|
||||
|
||||
result = u'\n\n'.join(u'<p>%s</p>' % p.replace('\n', Markup('<br>\n'))
|
||||
for p in _paragraph_re.split(escape(value)))
|
||||
return Markup(result)
|
||||
|
||||
|
||||
def replace_placeholders(template, values):
|
||||
if not template:
|
||||
return template
|
||||
|
||||
Submodule app/assets/govuk_elements deleted from 9e44a8d74c
Submodule app/assets/govuk_frontend_toolkit deleted from 26b6a60d07
58
app/assets/javascripts/apiKey.js
Normal file
58
app/assets/javascripts/apiKey.js
Normal file
@@ -0,0 +1,58 @@
|
||||
(function(Modules) {
|
||||
"use strict";
|
||||
|
||||
Modules.ApiKey = function() {
|
||||
|
||||
const states = {
|
||||
'initial': `
|
||||
<input type='button' class='api-key-button-show' value='Show API key' />
|
||||
`,
|
||||
'keyVisibleBasic': key => `
|
||||
<span class="api-key-key">${key}</span>
|
||||
`,
|
||||
'keyVisible': key => `
|
||||
<span class="api-key-key">${key}</span>
|
||||
<input type='button' class='api-key-button-copy' value='Copy API key to clipboard' />
|
||||
`,
|
||||
'keyCopied': `
|
||||
<span class="api-key-key">Copied to clipboard</span>
|
||||
<input type='button' class='api-key-button-show' value='Show API key' />
|
||||
`
|
||||
};
|
||||
|
||||
this.copyKey = function(keyElement, callback) {
|
||||
var selection = window.getSelection ? window.getSelection() : document.selection,
|
||||
range = document.createRange();
|
||||
selection.removeAllRanges();
|
||||
range.selectNodeContents(keyElement);
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
selection.removeAllRanges();
|
||||
callback();
|
||||
};
|
||||
|
||||
this.start = function(component) {
|
||||
|
||||
const $component = $(component).html(states.initial).attr('aria-live', 'polite'),
|
||||
key = $component.data('key');
|
||||
|
||||
$component
|
||||
.on(
|
||||
'click', '.api-key-button-show', () =>
|
||||
$component.html(
|
||||
document.queryCommandSupported('copy') ?
|
||||
states.keyVisible(key) : states.keyVisibleBasic(key)
|
||||
)
|
||||
)
|
||||
.on(
|
||||
'click', '.api-key-button-copy', () =>
|
||||
this.copyKey(
|
||||
$('.api-key-key', component)[0], () =>
|
||||
$component.html(states.keyCopied)
|
||||
)
|
||||
);
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
})(window.GOVUK.Modules);
|
||||
@@ -1 +1,3 @@
|
||||
$(() => GOVUK.modules.start());
|
||||
|
||||
$(() => new GOVUK.SelectionButtons('.block-label input'));
|
||||
|
||||
@@ -5,3 +5,7 @@
|
||||
.column-three-quarters {
|
||||
@include grid-column(3/4);
|
||||
}
|
||||
|
||||
.bottom-gutter {
|
||||
margin-bottom: $gutter;
|
||||
}
|
||||
|
||||
17
app/assets/stylesheets/components/api-key.scss
Normal file
17
app/assets/stylesheets/components/api-key.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
.api-key {
|
||||
|
||||
&-key {
|
||||
font-family: monospace;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&-button-show {
|
||||
@include button($grey-3);
|
||||
}
|
||||
|
||||
&-button-copy {
|
||||
@include button($grey-3);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,3 +27,20 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.banner-dangerous {
|
||||
|
||||
@extend .banner;
|
||||
background: $white;
|
||||
color: $error-colour;
|
||||
border: 5px solid $error-colour;
|
||||
margin: 15px 0;
|
||||
@include bold-19;
|
||||
text-align: left;
|
||||
|
||||
.button {
|
||||
@include button($error-colour);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
24
app/assets/stylesheets/components/email-message.scss
Normal file
24
app/assets/stylesheets/components/email-message.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
.email-message {
|
||||
|
||||
margin-bottom: $gutter;
|
||||
border: 1px solid $border-colour;
|
||||
|
||||
&-subject {
|
||||
border-bottom: 1px solid $border-colour;;
|
||||
padding: 10px;
|
||||
@include bold-19;
|
||||
}
|
||||
|
||||
&-body {
|
||||
border-bottom: 1px solid white;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
max-height: 103px;
|
||||
}
|
||||
|
||||
&-name {
|
||||
@include bold-19;
|
||||
margin: 50px 0 10px 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,26 @@
|
||||
margin-top: $gutter;
|
||||
}
|
||||
|
||||
&-delete-link {
|
||||
|
||||
line-height: 40px;
|
||||
padding: 0 0 0 5px;
|
||||
|
||||
a:visited,
|
||||
a:link {
|
||||
color: $error-colour;
|
||||
display: inline-block;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:active {
|
||||
color: $mellow-red;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.button {}
|
||||
|
||||
.button-destructive {
|
||||
|
||||
@@ -45,4 +45,9 @@
|
||||
margin: -$gutter-half 0 $gutter 0;
|
||||
}
|
||||
|
||||
&-name {
|
||||
@include bold-19;
|
||||
margin: 50px 0 10px 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
// Dependencies from GOV.UK Frontend Toolkit
|
||||
// https://github.com/alphagov/govuk_frontend_toolkit/
|
||||
@import '../govuk_frontend_toolkit/stylesheets/conditionals';
|
||||
@import '../govuk_frontend_toolkit/stylesheets/shims';
|
||||
@import '../govuk_frontend_toolkit/stylesheets/measurements';
|
||||
@import '../govuk_frontend_toolkit/stylesheets/css3';
|
||||
@import '../govuk_frontend_toolkit/stylesheets/colours';
|
||||
@import '../govuk_frontend_toolkit/stylesheets/typography';
|
||||
@import '../govuk_frontend_toolkit/stylesheets/grid_layout';
|
||||
@import '../govuk_frontend_toolkit/stylesheets/helpers';
|
||||
@import '../govuk_frontend_toolkit/stylesheets/url-helpers';
|
||||
@import '../govuk_frontend_toolkit/stylesheets/design-patterns/buttons';
|
||||
@import '../govuk_frontend_toolkit/stylesheets/design-patterns/alpha-beta';
|
||||
@import 'conditionals';
|
||||
@import 'shims';
|
||||
@import 'measurements';
|
||||
@import 'css3';
|
||||
@import 'colours';
|
||||
@import 'typography';
|
||||
@import 'grid_layout';
|
||||
@import 'helpers';
|
||||
@import 'url-helpers';
|
||||
@import 'design-patterns/buttons';
|
||||
@import 'design-patterns/alpha-beta';
|
||||
|
||||
// Dependencies from GOV.UK Elements
|
||||
// https://github.com/alphagov/govuk_elements
|
||||
@import '../govuk_elements/public/sass/elements/helpers';
|
||||
@import '../govuk_elements/public/sass/elements/reset';
|
||||
@import '../govuk_elements/public/sass/elements/buttons';
|
||||
@import '../govuk_elements/public/sass/elements/details';
|
||||
@import '../govuk_elements/public/sass/elements/elements-typography';
|
||||
@import '../govuk_elements/public/sass/elements/forms';
|
||||
@import '../govuk_elements/public/sass/elements/forms/form-validation';
|
||||
@import '../govuk_elements/public/sass/elements/forms/form-block-labels';
|
||||
@import '../govuk_elements/public/sass/elements/forms/form-validation';
|
||||
@import '../govuk_elements/public/sass/elements/icons';
|
||||
@import '../govuk_elements/public/sass/elements/layout';
|
||||
@import '../govuk_elements/public/sass/elements/lists';
|
||||
@import '../govuk_elements/public/sass/elements/panels';
|
||||
@import '../govuk_elements/public/sass/elements/tables';
|
||||
@import 'elements/helpers';
|
||||
@import 'elements/reset';
|
||||
@import 'elements/buttons';
|
||||
@import 'elements/details';
|
||||
@import 'elements/elements-typography';
|
||||
@import 'elements/forms';
|
||||
@import 'elements/forms/form-validation';
|
||||
@import 'elements/forms/form-block-labels';
|
||||
@import 'elements/forms/form-validation';
|
||||
@import 'elements/icons';
|
||||
@import 'elements/layout';
|
||||
@import 'elements/lists';
|
||||
@import 'elements/panels';
|
||||
@import 'elements/tables';
|
||||
|
||||
|
||||
// Specific to this application
|
||||
@@ -44,6 +44,8 @@
|
||||
@import 'components/browse-list';
|
||||
@import 'components/management-navigation';
|
||||
@import 'components/dropdown';
|
||||
@import 'components/email-message';
|
||||
@import 'components/api-key';
|
||||
|
||||
@import 'views/job';
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ main = Blueprint('main', __name__)
|
||||
from app.main.views import (
|
||||
index, sign_in, sign_out, register, two_factor, verify, sms, add_service,
|
||||
code_not_received, jobs, dashboard, templates, service_settings, forgot_password,
|
||||
new_password, styleguide, user_profile, choose_service
|
||||
new_password, styleguide, user_profile, choose_service, api_keys
|
||||
)
|
||||
|
||||
@@ -166,8 +166,12 @@ class AddServiceForm(Form):
|
||||
self._names_func = names_func
|
||||
super(AddServiceForm, self).__init__(*args, **kwargs)
|
||||
|
||||
name = StringField('Service Name', validators=[
|
||||
DataRequired(message='Service name can not be empty')])
|
||||
name = StringField(
|
||||
'Service name',
|
||||
validators=[
|
||||
DataRequired(message='Service name can not be empty')
|
||||
]
|
||||
)
|
||||
|
||||
def validate_name(self, a):
|
||||
if a.data in self._names_func():
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from boto3 import resource
|
||||
|
||||
|
||||
# TODO add service name to bucket name as well
|
||||
def s3upload(filepath):
|
||||
filename = filepath.split(os.path.sep)[-1]
|
||||
def s3upload(service_id, filedata):
|
||||
upload_id = str(uuid.uuid4())
|
||||
s3 = resource('s3')
|
||||
s3.create_bucket(Bucket=upload_id)
|
||||
key = s3.Object(upload_id, filename)
|
||||
key.put(Body=open(filepath, 'rb'), ServerSideEncryption='AES256')
|
||||
bucket_name = 'service-{}-notify'.format(service_id)
|
||||
s3.create_bucket(Bucket=bucket_name)
|
||||
contents = '\n'.join(filedata['data'])
|
||||
key = s3.Object(bucket_name, upload_id)
|
||||
key.put(Body=contents, ServerSideEncryption='AES256')
|
||||
return upload_id
|
||||
|
||||
|
||||
def s3download(service_id, upload_id):
|
||||
s3 = resource('s3')
|
||||
bucket_name = 'service-{}-notify'.format(service_id)
|
||||
key = s3.Object(bucket_name, upload_id)
|
||||
contents = key.get()['Body'].read().decode('utf-8')
|
||||
return contents
|
||||
|
||||
39
app/main/views/_templates.py
Normal file
39
app/main/views/_templates.py
Normal file
@@ -0,0 +1,39 @@
|
||||
templates = [
|
||||
{
|
||||
'type': 'sms',
|
||||
'name': 'Confirmation',
|
||||
'body': 'Lasting power of attorney: We’ve received your application. Applications take between 8 and 10 weeks to process.' # noqa
|
||||
},
|
||||
{
|
||||
'type': 'sms',
|
||||
'name': 'Reminder',
|
||||
'body': 'Vehicle tax: Your vehicle tax for ((registration number)) expires on ((date)). Tax your vehicle at www.gov.uk/vehicle-tax' # noqa
|
||||
},
|
||||
{
|
||||
'type': 'sms',
|
||||
'name': 'Warning',
|
||||
'body': 'Vehicle tax: Your vehicle tax for ((registration number)) has expired. Tax your vehicle at www.gov.uk/vehicle-tax' # noqa
|
||||
},
|
||||
{
|
||||
'type': 'email',
|
||||
'name': 'Application alert 06/2016',
|
||||
'subject': 'Your lasting power of attorney application',
|
||||
'body': """Dear ((name)),
|
||||
|
||||
When you’ve made your lasting power of attorney (LPA), you need to register it \
|
||||
with the Office of the Public Guardian (OPG).
|
||||
|
||||
You can apply to register your LPA yourself if you’re able to make your own decisions.
|
||||
|
||||
Your attorney can also register it for you. You’ll be told if they do and you can \
|
||||
object to the registration.
|
||||
|
||||
It takes between 8 and 10 weeks to register an LPA if there are no mistakes in the application.
|
||||
"""
|
||||
},
|
||||
{
|
||||
'type': 'sms',
|
||||
'name': 'Air quality alert',
|
||||
'body': 'Air pollution levels will be ((level)) in ((region)) tomorrow.'
|
||||
},
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
from flask import render_template, jsonify, redirect, session, url_for
|
||||
from flask import request, render_template, jsonify, redirect, session, url_for, abort
|
||||
from flask_login import login_required
|
||||
from app.main import main
|
||||
from app.main.dao import services_dao, users_dao
|
||||
@@ -8,11 +8,19 @@ from app.main.forms import AddServiceForm
|
||||
@main.route("/add-service", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_service():
|
||||
# TODO fix up this
|
||||
form = AddServiceForm(services_dao.find_all_service_names)
|
||||
services = services_dao.get_services()
|
||||
if len(services) > 0:
|
||||
heading = 'Set up notifications for your service'
|
||||
else:
|
||||
heading = 'Add a new service'
|
||||
if form.validate_on_submit():
|
||||
user = users_dao.get_user_by_id(session['user_id'])
|
||||
service_id = services_dao.insert_new_service(form.name.data, user)
|
||||
return redirect(url_for('main.service_dashboard', service_id=service_id))
|
||||
else:
|
||||
return render_template('views/add-service.html', form=form)
|
||||
return render_template(
|
||||
'views/add-service.html',
|
||||
form=form,
|
||||
heading=heading
|
||||
)
|
||||
|
||||
9
app/main/views/api_keys.py
Normal file
9
app/main/views/api_keys.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from flask import render_template
|
||||
from flask_login import login_required
|
||||
from app.main import main
|
||||
|
||||
|
||||
@main.route("/services/<int:service_id>/api-keys")
|
||||
@login_required
|
||||
def api_keys(service_id):
|
||||
return render_template('views/api-keys.html', service_id=service_id)
|
||||
@@ -36,9 +36,3 @@ def check_email(service_id):
|
||||
@login_required
|
||||
def manage_users(service_id):
|
||||
return render_template('views/manage-users.html', service_id=service_id)
|
||||
|
||||
|
||||
@main.route("/services/<int:service_id>/api-keys")
|
||||
@login_required
|
||||
def apikeys(service_id):
|
||||
return render_template('views/api-keys.html', service_id=service_id)
|
||||
|
||||
@@ -20,24 +20,15 @@ from werkzeug import secure_filename
|
||||
|
||||
from app.main import main
|
||||
from app.main.forms import CsvUploadForm
|
||||
from app.main.uploader import s3upload
|
||||
from app.main.uploader import (
|
||||
s3upload,
|
||||
s3download
|
||||
)
|
||||
|
||||
# TODO move this to the templates directory
|
||||
message_templates = [
|
||||
{
|
||||
'name': 'Reminder',
|
||||
'body': """
|
||||
Vehicle tax: Your vehicle tax for ((registration number)) expires on ((date)).
|
||||
Tax your vehicle at www.gov.uk/vehicle-tax
|
||||
"""
|
||||
},
|
||||
{
|
||||
'name': 'Warning',
|
||||
'body': """
|
||||
Vehicle tax: Your vehicle tax for ((registration number)) has expired.
|
||||
Tax your vehicle at www.gov.uk/vehicle-tax
|
||||
"""
|
||||
},
|
||||
from ._templates import templates
|
||||
|
||||
sms_templates = [
|
||||
template for template in templates if template['type'] == 'sms'
|
||||
]
|
||||
|
||||
|
||||
@@ -48,68 +39,52 @@ def send_sms(service_id):
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
csv_file = form.file.data
|
||||
filename = _format_filename(csv_file.filename)
|
||||
filepath = os.path.join(current_app.config['UPLOAD_FOLDER'],
|
||||
filename)
|
||||
csv_file.save(filepath)
|
||||
_check_file(csv_file.filename, filepath)
|
||||
filedata = _get_filedata(csv_file)
|
||||
upload_id = s3upload(service_id, filedata)
|
||||
return redirect(url_for('.check_sms',
|
||||
service_id=service_id,
|
||||
recipients=filename))
|
||||
except (IOError, ValueError) as e:
|
||||
upload_id=upload_id))
|
||||
except ValueError as e:
|
||||
message = 'There was a problem uploading: {}'.format(
|
||||
csv_file.filename)
|
||||
flash(message)
|
||||
if isinstance(e, ValueError):
|
||||
flash(str(e))
|
||||
os.remove(filepath)
|
||||
flash(str(e))
|
||||
return redirect(url_for('.send_sms', service_id=service_id))
|
||||
|
||||
return render_template('views/send-sms.html',
|
||||
message_templates=message_templates,
|
||||
message_templates=sms_templates,
|
||||
form=form,
|
||||
service_id=service_id)
|
||||
|
||||
|
||||
@main.route("/services/<int:service_id>/sms/check", methods=['GET', 'POST'])
|
||||
@main.route("/services/<int:service_id>/sms/check/<upload_id>",
|
||||
methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def check_sms(service_id):
|
||||
def check_sms(service_id, upload_id):
|
||||
if request.method == 'GET':
|
||||
filename = request.args.get('recipients')
|
||||
if not filename:
|
||||
abort(400)
|
||||
filepath = os.path.join(current_app.config['UPLOAD_FOLDER'],
|
||||
filename)
|
||||
upload_result = _build_upload_result(filepath)
|
||||
if upload_result.get('rejects'):
|
||||
flash('There was a problem with some of the numbers')
|
||||
|
||||
contents = s3download(service_id, upload_id)
|
||||
upload_result = _get_numbers(contents)
|
||||
# TODO get original file name
|
||||
return render_template(
|
||||
'views/check-sms.html',
|
||||
upload_result=upload_result,
|
||||
filename=filename,
|
||||
message_template=message_templates[0]['body'],
|
||||
filename='someupload_file_name.csv',
|
||||
message_template=sms_templates[0]['body'],
|
||||
service_id=service_id
|
||||
)
|
||||
elif request.method == 'POST':
|
||||
filename = request.form['recipients']
|
||||
filepath = os.path.join(current_app.config['UPLOAD_FOLDER'],
|
||||
filename)
|
||||
try:
|
||||
upload_id = s3upload(filepath)
|
||||
# TODO when job is created record filename in job itself
|
||||
# so downstream pages can get the original filename that way
|
||||
session[upload_id] = filename
|
||||
return redirect(url_for('main.view_job', service_id=service_id, job_id=upload_id))
|
||||
except:
|
||||
flash('There as a problem saving the file')
|
||||
return redirect(url_for('.check_sms', recipients=filename))
|
||||
# TODO create the job with template, file location etc.
|
||||
return redirect(url_for('main.view_job',
|
||||
service_id=service_id,
|
||||
job_id=upload_id))
|
||||
|
||||
|
||||
def _check_file(filename, filepath):
|
||||
if os.stat(filepath).st_size == 0:
|
||||
message = 'The file {} contained no data'.format(filename)
|
||||
def _get_filedata(file):
|
||||
lines = file.read().decode('utf-8').splitlines()
|
||||
if len(lines) < 2: # must be at least header and one line
|
||||
message = 'The file {} contained no data'.format(file.filename)
|
||||
raise ValueError(message)
|
||||
return {'filename': file.filename, 'data': lines}
|
||||
|
||||
|
||||
def _format_filename(filename):
|
||||
@@ -119,24 +94,16 @@ def _format_filename(filename):
|
||||
return secure_filename(formatted_name)
|
||||
|
||||
|
||||
def _open(file):
|
||||
return open(file, 'r')
|
||||
|
||||
|
||||
def _build_upload_result(csv_file):
|
||||
try:
|
||||
file = _open(csv_file, 'r')
|
||||
pattern = re.compile(r'^\+44\s?\d{4}\s?\d{6}$')
|
||||
reader = csv.DictReader(
|
||||
file.read().splitlines(),
|
||||
lineterminator='\n',
|
||||
quoting=csv.QUOTE_NONE)
|
||||
valid, rejects = [], []
|
||||
for i, row in enumerate(reader):
|
||||
if pattern.match(row['phone']):
|
||||
valid.append(row)
|
||||
else:
|
||||
rejects.append({"line_number": i+2, "phone": row['phone']})
|
||||
return {"valid": valid, "rejects": rejects}
|
||||
finally:
|
||||
file.close()
|
||||
def _get_numbers(contents):
|
||||
pattern = re.compile(r'^\+44\s?\d{4}\s?\d{6}$') # need better validation
|
||||
reader = csv.DictReader(
|
||||
contents.split('\n'),
|
||||
lineterminator='\n',
|
||||
quoting=csv.QUOTE_NONE)
|
||||
valid, rejects = [], []
|
||||
for i, row in enumerate(reader):
|
||||
if pattern.match(row['phone']):
|
||||
valid.append(row)
|
||||
else:
|
||||
rejects.append({"line_number": i+2, "phone": row['phone']})
|
||||
return {"valid": valid, "rejects": rejects}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
from flask import request, render_template, redirect, url_for
|
||||
from flask import request, render_template, redirect, url_for, flash
|
||||
from flask_login import login_required
|
||||
|
||||
from app.main import main
|
||||
from app.main.forms import TemplateForm
|
||||
|
||||
from ._templates import templates
|
||||
|
||||
|
||||
@main.route("/services/<int:service_id>/templates")
|
||||
@login_required
|
||||
def manage_templates(service_id):
|
||||
return render_template(
|
||||
'views/manage-templates.html',
|
||||
service_id=service_id
|
||||
service_id=service_id,
|
||||
templates=templates,
|
||||
)
|
||||
|
||||
|
||||
@@ -31,21 +34,49 @@ def add_template(service_id):
|
||||
return redirect(url_for('.manage_templates', service_id=service_id))
|
||||
|
||||
|
||||
@main.route("/services/<int:service_id>/templates/<template_id>", methods=['GET', 'POST'])
|
||||
@main.route("/services/<int:service_id>/templates/<int:template_id>", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_template(service_id, template_id):
|
||||
|
||||
form = TemplateForm()
|
||||
|
||||
form.template_name.data = 'Reminder'
|
||||
form.template_body.data = 'Vehicle tax: Your vehicle tax for ((registration number)) expires on ((date)). Tax your vehicle at www.gov.uk/vehicle-tax' # noqa
|
||||
form.template_name.data = templates[template_id - 1]['name']
|
||||
form.template_body.data = templates[template_id - 1]['body']
|
||||
|
||||
if request.method == 'GET':
|
||||
return render_template(
|
||||
'views/edit-template.html',
|
||||
h1='Edit template',
|
||||
form=form,
|
||||
service_id=service_id
|
||||
service_id=service_id,
|
||||
template_id=template_id
|
||||
)
|
||||
elif request.method == 'POST':
|
||||
return redirect(url_for('.manage_templates', service_id=service_id))
|
||||
|
||||
|
||||
@main.route("/services/<int:service_id>/templates/<int:template_id>/delete", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def delete_template(service_id, template_id):
|
||||
|
||||
form = TemplateForm()
|
||||
|
||||
form.template_name.data = templates[template_id - 1]['name']
|
||||
form.template_body.data = templates[template_id - 1]['body']
|
||||
|
||||
if request.method == 'GET':
|
||||
|
||||
flash('Are you sure you want to delete ‘{}’?'.format(form.template_name.data), 'delete')
|
||||
|
||||
return render_template(
|
||||
'views/edit-template.html',
|
||||
h1='Edit template',
|
||||
form=form,
|
||||
service_id=service_id,
|
||||
template_id=template_id
|
||||
)
|
||||
elif request.method == 'POST':
|
||||
if request.form.get('delete'):
|
||||
return redirect(url_for('.manage_templates', service_id=service_id))
|
||||
else:
|
||||
return redirect(url_for('.manage_templates', service_id=service_id))
|
||||
|
||||
@@ -20,5 +20,5 @@ def verify():
|
||||
verify_codes_dao.use_code_for_user_and_type(user_id=user.id, code_type='sms')
|
||||
users_dao.activate_user(user.id)
|
||||
login_user(user)
|
||||
return redirect(url_for('.add_service'))
|
||||
return redirect(url_for('.add_service', first='first'))
|
||||
return render_template('views/verify.html', form=form)
|
||||
|
||||
@@ -14,7 +14,6 @@ def send_sms_code(user_id, mobile_number):
|
||||
verify_codes_dao.add_code(user_id=user_id, code=sms_code, code_type='sms')
|
||||
notifications_api_client.send_sms(mobile_number=mobile_number,
|
||||
message=sms_code)
|
||||
|
||||
return sms_code
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
Service name
|
||||
</div>
|
||||
<a href="#">Switch to A N Other service</a>
|
||||
<a href="#">Add a new service to Notify…</a>
|
||||
<a href="{{ url_for('.add_service') }}">Add a new service to GOV.UK Notify</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column-half management-navigation-account">
|
||||
@@ -68,16 +68,22 @@
|
||||
{% endif %}
|
||||
|
||||
<main id="content" role="main" class="page-container">
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div class="error-summary">
|
||||
<ul>
|
||||
{% for message in messages %}
|
||||
<li>{{ message }}</li>
|
||||
{% endfor %}
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<ul class="banner-dangerous">
|
||||
{% for category, message in messages %}
|
||||
<li class="flash-message">
|
||||
{{ message }}
|
||||
{% if 'delete' == category %}
|
||||
<form method='post'>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="submit" class="button" name="delete" value="Yes, delete this template" />
|
||||
</form>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block fullwidth_content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
5
app/templates/components/api-key.html
Normal file
5
app/templates/components/api-key.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{% macro api_key(key) %}
|
||||
<div data-module="api-key" data-key="{{ key }}">
|
||||
<span class="api-key-key">{{ key }}</span>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
19
app/templates/components/email-message.html
Normal file
19
app/templates/components/email-message.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{% macro email_message(subject, body, name=None, edit_link=None) %}
|
||||
{% if name %}
|
||||
<h3 class="email-message-name">
|
||||
{% if edit_link %}
|
||||
<a href="{{ edit_link }}">{{ name }}</a>
|
||||
{% else %}
|
||||
{{ name }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
{% endif %}
|
||||
<div class="email-message">
|
||||
<div class="email-message-subject">
|
||||
{{ subject|placeholders }}
|
||||
</div>
|
||||
<div class="email-message-body">
|
||||
{{ body|nl2br|placeholders }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,11 +1,23 @@
|
||||
{% macro page_footer(button_text=None, back_link=False, back_link_text="Back", destructive=False) %}
|
||||
{% macro page_footer(
|
||||
button_text=None,
|
||||
destructive=False,
|
||||
back_link=False,
|
||||
back_link_text="Back",
|
||||
delete_link=False,
|
||||
delete_link_text="delete"
|
||||
) %}
|
||||
<div class="page-footer">
|
||||
{% if button_text %}
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="submit" class="button{% if destructive %}-destructive{% endif %}" value="{{ button_text }}" />
|
||||
{% endif %}
|
||||
{% if delete_link %}
|
||||
<span class="page-footer-delete-link">
|
||||
or <a href="{{ delete_link }}">{{ delete_link_text }}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if back_link %}
|
||||
<a class="page-footer-back-link" role="button" href="{{ back_link }}">{{ back_link_text }}</a>
|
||||
<a class="page-footer-back-link" href="{{ back_link }}">{{ back_link_text }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
{% macro sms_message(body, recipient) %}
|
||||
{% macro sms_message(body, recipient=None, name=None, edit_link=None) %}
|
||||
{% if name %}
|
||||
<h3 class="sms-message-name">
|
||||
{% if edit_link %}
|
||||
<a href="{{ edit_link }}">{{ name }}</a>
|
||||
{% else %}
|
||||
{{ name }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
{% endif %}
|
||||
<div class="sms-message">
|
||||
<div class="sms-message-wrapper">
|
||||
{{ body|placeholders }}
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
{% block fullwidth_content %}
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
<h1>404</h1>
|
||||
<p>Sorry, that page doesn't exist.</p>
|
||||
<h1 class="heading-xlarge">
|
||||
Page could not be found
|
||||
</h1>
|
||||
<p>
|
||||
Check you've entered the correct web address.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
{% block fullwidth_content %}
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
<h1>500</h1>
|
||||
<p>Sorry, something went wrong on our system.</p>
|
||||
<h1 class="heading-xlarge">
|
||||
Sorry, we're experiencing technical difficulties
|
||||
</h1>
|
||||
<p>Try again later.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<nav class="navigation">
|
||||
<ul>
|
||||
<li><a href="{{ url_for('.service_dashboard', service_id=123) }}">Dashboard</a></li>
|
||||
<li><a href="{{ url_for('.service_dashboard', service_id=service_id) }}">Dashboard</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="{{ url_for('.send_sms', service_id=123) }}">Send text messages</a></li>
|
||||
<li><a href="{{ url_for('.send_email', service_id=123) }}">Send emails</a></li>
|
||||
<li><a href="{{ url_for('.view_jobs', service_id=123) }}">Activity</a></li>
|
||||
<li><a href="{{ url_for('.send_sms', service_id=service_id) }}">Send text messages</a></li>
|
||||
<li><a href="{{ url_for('.send_email', service_id=service_id) }}">Send emails</a></li>
|
||||
<li><a href="{{ url_for('.view_jobs', service_id=service_id) }}">Activity</a></li>
|
||||
<li><a href="{{ url_for('.manage_templates', service_id=service_id) }}">Templates</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="{{ url_for('.apikeys', service_id=123) }}">API keys and documentation</a></li>
|
||||
<li><a href="{{ url_for('.api_keys', service_id=service_id) }}">API keys and documentation</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="{{ url_for('.manage_users', service_id=123) }}">Manage users</a></li>
|
||||
<li><a href="{{ url_for('.service_settings', service_id=123) }}">Service settings</a></li>
|
||||
<li><a href="{{ url_for('.manage_users', service_id=service_id) }}">Manage users</a></li>
|
||||
<li><a href="{{ url_for('.service_settings', service_id=service_id) }}">Service settings</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "admin_template.html" %}
|
||||
{% from "components/textbox.html" import textbox %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
|
||||
{% block page_title %}
|
||||
GOV.UK Notify | Set up service
|
||||
@@ -8,24 +9,34 @@ GOV.UK Notify | Set up service
|
||||
{% block fullwidth_content %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
<h1 class="heading-xlarge">Set up notifications for your service</h1>
|
||||
<div class="column-two-thirds">
|
||||
|
||||
<p>Users will see your service name:</p>
|
||||
<ul class="list-bullet">
|
||||
<li>at the start of every text message, eg 'Vehicle tax: we received your payment, thank you'</li>
|
||||
<li>as your email sender name</li>
|
||||
</ul>
|
||||
<h1 class="heading-xlarge">
|
||||
{{ heading }}
|
||||
</h1>
|
||||
|
||||
<form autocomplete="off" method="post">
|
||||
{{ textbox(form.name) }}
|
||||
<p>
|
||||
Users will see your service name:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<button class="button" href="dashboard" role="button">Continue</button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-bullet bottom-gutter">
|
||||
<li>
|
||||
at the start of every text message, eg ‘Vehicle tax: we received your
|
||||
payment, thank you’
|
||||
</li>
|
||||
<li>
|
||||
as your email sender name
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<form autocomplete="off" method="post">
|
||||
{{ textbox(form.name) }}
|
||||
{{ page_footer(
|
||||
'Continue'
|
||||
) }}
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,15 +1,52 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
{% from "components/api-key.html" import api_key %}
|
||||
|
||||
{% block page_title %}
|
||||
GOV.UK Notify | API keys and documentation
|
||||
GOV.UK Notify | API keys and documentation
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<h1 class="heading-xlarge">API keys and documentation</h1>
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
|
||||
<p>Here's where developers can access information about the API and access keys</p>
|
||||
<h1 class="heading-xlarge">
|
||||
API keys and documentation
|
||||
</h1>
|
||||
|
||||
<h2 class="heading-medium">
|
||||
How to integrate GOV.UK Notify into your service
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
blah blah blah this is where we tell you how the API works
|
||||
</p>
|
||||
|
||||
<h2 class="heading-medium">Repositories</h2>
|
||||
|
||||
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/alphagov/notifications-api">GOV.UK Notify API</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/alphagov/notify-api-client">GOV.UK Notify Python client</a>
|
||||
</p>
|
||||
|
||||
<h2 class="heading-medium">API key for [service name]</h2>
|
||||
|
||||
{{ api_key('d30512af92e1386d63b90e5973b49a10') }}
|
||||
|
||||
<h2 class="heading-medium">API endpoint</h2>
|
||||
|
||||
<p>
|
||||
https://www.notify.works/api/endpoint
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ page_footer(
|
||||
back_link=url_for('.service_dashboard', service_id=service_id),
|
||||
|
||||
@@ -59,8 +59,6 @@
|
||||
back_link = url_for(".send_sms", service_id=service_id)
|
||||
)}}
|
||||
|
||||
<input type='hidden' name='recipients' value='{{filename}}'>
|
||||
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% extends "admin_template.html" %}
|
||||
{% from "components/browse-list.html" import browse_list %}
|
||||
|
||||
{% block page_title %}
|
||||
|
||||
@@ -14,9 +14,10 @@ GOV.UK Notify | Edit template
|
||||
{{ textbox(form.template_name) }}
|
||||
{{ textbox(form.template_body, highlight_tags=True) }}
|
||||
{{ page_footer(
|
||||
'Save and continue',
|
||||
back_link=url_for('.service_dashboard', service_id=service_id),
|
||||
back_link_text='Back to manage templates'
|
||||
'Save',
|
||||
delete_link=url_for('.delete_template', service_id=service_id, template_id=template_id) if template_id or None,
|
||||
back_link=url_for('.manage_templates', service_id=service_id),
|
||||
back_link_text='Back to templates'
|
||||
) }}
|
||||
</form>
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% from "components/sms-message.html" import sms_message %}
|
||||
{% from "components/email-message.html" import email_message %}
|
||||
{% from "components/browse-list.html" import browse_list %}
|
||||
|
||||
{% block page_title %}
|
||||
GOV.UK Notify | Manage templates
|
||||
@@ -6,18 +9,32 @@ GOV.UK Notify | Manage templates
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
|
||||
<h1 class="heading-xlarge">Manage templates</h1>
|
||||
<h1 class="heading-xlarge">Templates</h1>
|
||||
|
||||
<p>Here's where you can view templates, choose to add one, or edit/delete one.</p>
|
||||
<p>
|
||||
<a href="{{ url_for('.add_template', service_id=service_id) }}">Create new template</a>
|
||||
|
||||
<p>
|
||||
<a href="{{ url_for('.edit_template', service_id=service_id, template_id=1) }}">Here is my first template</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a class="button" href="{{ url_for('.add_template', service_id=service_id) }}" role="button">Add a new message template</a>
|
||||
</p>
|
||||
{% for template in templates %}
|
||||
{% if template.type == 'sms' %}
|
||||
{{ sms_message(
|
||||
template.body,
|
||||
name=template.name,
|
||||
edit_link=url_for('.edit_template', service_id=service_id, template_id=loop.index)
|
||||
) }}
|
||||
{% elif template.type == 'email' %}
|
||||
{{ email_message(
|
||||
template.subject,
|
||||
template.body,
|
||||
name=template.name,
|
||||
edit_link=url_for('.edit_template', service_id=service_id, template_id=loop.index)
|
||||
) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,45 +4,42 @@
|
||||
{% from "components/textbox.html" import textbox %}
|
||||
|
||||
{% block page_title %}
|
||||
GOV.UK Notify | Send text messages
|
||||
GOV.UK Notify | Send text messages
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
|
||||
<h1 class="heading-xlarge">Send text messages</h1>
|
||||
<h1 class="heading-xlarge">Send text messages</h1>
|
||||
|
||||
<h2 class="heading-medium">1. Choose text message template</h2>
|
||||
{% for template in message_templates %}
|
||||
<div class="template-picker-option">
|
||||
<div class="template-picker-option-radio">
|
||||
<label class="block-label" for="template-{{loop.index}}">
|
||||
{{ template.name }}
|
||||
<input type="radio" name="template" id="template-{{loop.index}}" value="{{ template.name }}" />
|
||||
</label>
|
||||
</div>
|
||||
{{ sms_message(template.body) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<fieldset class='form-group'>
|
||||
<legend class="heading-medium">1. Choose text message template</legend>
|
||||
{% for template in message_templates %}
|
||||
<label class="block-label" for="template-{{loop.index}}">
|
||||
{{ template.name }}
|
||||
<input type="radio" name="template" id="template-{{loop.index}}" value="{{ template.name }}" />
|
||||
</label>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
|
||||
<p>
|
||||
or <a href="{{ url_for(".add_template", service_id=service_id) }}">create a new template</a>
|
||||
</p>
|
||||
<p>
|
||||
or <a href="{{ url_for(".add_template", service_id=service_id) }}">create a new template</a>
|
||||
</p>
|
||||
|
||||
<h2 class="heading-medium">2. Add recipients</h2>
|
||||
<h2 class="heading-medium">2. Add recipients</h2>
|
||||
|
||||
<p>
|
||||
Upload a CSV file to add your recipients’ details.
|
||||
</p>
|
||||
<p>
|
||||
You can also <a href="#">download an example CSV</a>.
|
||||
</p>
|
||||
<p>
|
||||
{{textbox(form.file)}}
|
||||
</p>
|
||||
<p>
|
||||
Upload a CSV file to add your recipients’ details.
|
||||
</p>
|
||||
<p>
|
||||
You can also <a href="#">download an example CSV</a>.
|
||||
</p>
|
||||
<p>
|
||||
{{textbox(form.file)}}
|
||||
</p>
|
||||
|
||||
{{ page_footer("Continue") }}
|
||||
{{ page_footer("Continue") }}
|
||||
|
||||
</form>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
{% from "components/sms-message.html" import sms_message %}
|
||||
{% from "components/table.html" import mapping_table, list_table, row, field %}
|
||||
{% from "components/textbox.html" import textbox %}
|
||||
{% from "components/api-key.html" import api_key %}
|
||||
|
||||
{% block page_title %}
|
||||
Styleguide – GOV.UK Notify
|
||||
@@ -152,4 +153,8 @@
|
||||
{{ textbox(form.password) }}
|
||||
{{ textbox(form.message, highlight_tags=True) }}
|
||||
|
||||
<h2 class="heading-large">API key</h2>
|
||||
|
||||
{{ api_key('d30512af92e1386d63b90e5973b49a10') }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -13,7 +13,8 @@ var gulp = require('gulp'),
|
||||
paths = {
|
||||
src: 'app/assets/',
|
||||
dist: 'app/static/',
|
||||
templates: 'app/templates/'
|
||||
templates: 'app/templates/',
|
||||
npm: 'node_modules/'
|
||||
};
|
||||
|
||||
// 3. TASKS
|
||||
@@ -31,25 +32,34 @@ gulp.task('copy:govuk_template:assets', () => gulp.src('bower_components/govuk_t
|
||||
|
||||
gulp.task('javascripts', () => gulp
|
||||
.src([
|
||||
paths.src + 'govuk_frontend_toolkit/javascripts/govuk/modules.js',
|
||||
paths.src + 'javascripts/highlightTags.js',
|
||||
paths.npm + 'govuk_frontend_toolkit/javascripts/govuk/modules.js',
|
||||
paths.npm + 'govuk_frontend_toolkit/javascripts/govuk/selection-buttons.js',
|
||||
paths.src + 'javascripts/apiKey.js',
|
||||
paths.src + 'javascripts/dropdown.js',
|
||||
paths.src + 'javascripts/highlightTags.js',
|
||||
paths.src + 'javascripts/main.js'
|
||||
])
|
||||
.pipe(plugins.babel({
|
||||
presets: ['es2015']
|
||||
}))
|
||||
.pipe(plugins.uglify())
|
||||
.pipe(plugins.addSrc.prepend(
|
||||
'./node_modules/jquery/dist/jquery.min.js'
|
||||
))
|
||||
.pipe(plugins.addSrc.prepend([
|
||||
paths.npm + 'jquery/dist/jquery.min.js',
|
||||
paths.npm + 'query-command-supported/dist/queryCommandSupported.min.js'
|
||||
]))
|
||||
.pipe(plugins.concat('all.js'))
|
||||
.pipe(gulp.dest(paths.dist + 'javascripts/'))
|
||||
);
|
||||
|
||||
gulp.task('sass', () => gulp
|
||||
.src(paths.src + '/stylesheets/main*.scss')
|
||||
.pipe(plugins.sass({outputStyle: 'compressed'}))
|
||||
.pipe(plugins.sass({
|
||||
outputStyle: 'compressed',
|
||||
includePaths: [
|
||||
paths.npm + 'govuk-elements-sass/public/sass/',
|
||||
paths.npm + 'govuk_frontend_toolkit/stylesheets/'
|
||||
]
|
||||
}))
|
||||
.pipe(gulp.dest(paths.dist + '/stylesheets'))
|
||||
);
|
||||
|
||||
@@ -57,7 +67,10 @@ gulp.task('sass', () => gulp
|
||||
// Copy images
|
||||
|
||||
gulp.task('images', () => gulp
|
||||
.src(paths.src + 'images/**/*')
|
||||
.src([
|
||||
paths.src + 'images/**/*',
|
||||
paths.npm + 'govuk_frontend_toolkit/images/**/*'
|
||||
])
|
||||
.pipe(gulp.dest(paths.dist + '/images'))
|
||||
);
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
"babel-core": "6.3.26",
|
||||
"babel-preset-es2015": "6.3.13",
|
||||
"bower": "1.7.1",
|
||||
"govuk-elements-sass": "1.1.1",
|
||||
"govuk_frontend_toolkit": "4.6.0",
|
||||
"gulp": "3.9.0",
|
||||
"gulp-add-src": "0.2.0",
|
||||
"gulp-babel": "6.1.1",
|
||||
@@ -31,6 +33,7 @@
|
||||
"gulp-load-plugins": "1.1.0",
|
||||
"gulp-sass": "2.1.1",
|
||||
"gulp-uglify": "1.5.1",
|
||||
"jquery": "1.11.2"
|
||||
"jquery": "1.11.2",
|
||||
"query-command-supported": "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
-r requirements.txt
|
||||
pep8==1.5.7
|
||||
pytest==2.8.1
|
||||
pytest-mock==0.8.1
|
||||
pytest-mock==0.8.1
|
||||
moto==0.4.19
|
||||
httpretty==0.8.10
|
||||
|
||||
@@ -4,7 +4,12 @@ from tests import create_test_user
|
||||
from app.models import User
|
||||
|
||||
|
||||
def test_get_should_render_add_service_template(app_, db_, db_session, active_user, mock_get_service):
|
||||
def test_get_should_render_add_service_template(app_,
|
||||
db_,
|
||||
db_session,
|
||||
active_user,
|
||||
mock_get_service,
|
||||
mock_get_services):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(active_user)
|
||||
@@ -35,7 +40,8 @@ def test_should_return_form_errors_when_service_name_is_empty(app_,
|
||||
db_,
|
||||
db_session,
|
||||
active_user,
|
||||
mock_get_service):
|
||||
mock_get_service,
|
||||
mock_get_services):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(active_user)
|
||||
|
||||
13
tests/app/main/views/test_api_keys.py
Normal file
13
tests/app/main/views/test_api_keys.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from flask import url_for
|
||||
|
||||
|
||||
def test_should_show_api_keys_and_documentation_page(app_,
|
||||
db_,
|
||||
db_session,
|
||||
active_user):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(active_user)
|
||||
response = client.get(url_for('main.api_keys', service_id=123))
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -1,21 +1,15 @@
|
||||
from io import BytesIO
|
||||
from unittest import mock
|
||||
from unittest.mock import mock_open
|
||||
from flask import url_for
|
||||
|
||||
from tests import create_test_user
|
||||
import moto
|
||||
|
||||
|
||||
def test_upload_empty_csvfile_returns_to_upload_page(
|
||||
app_, db_, db_session,
|
||||
mocker):
|
||||
|
||||
_setup_mocker_for_empty_file(mocker)
|
||||
def test_upload_empty_csvfile_returns_to_upload_page(app_, db_, db_session, active_user):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
user = create_test_user('active')
|
||||
client.login(user)
|
||||
client.login(active_user)
|
||||
upload_data = {'file': (BytesIO(''.encode('utf-8')), 'emtpy.csv')}
|
||||
response = client.post('/services/123/sms/send',
|
||||
response = client.post(url_for('main.send_sms', service_id=123),
|
||||
data=upload_data, follow_redirects=True)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -23,53 +17,49 @@ def test_upload_empty_csvfile_returns_to_upload_page(
|
||||
assert 'The file emtpy.csv contained no data' in content
|
||||
|
||||
|
||||
def test_upload_csvfile_with_invalid_phone_shows_check_page_with_errors(
|
||||
app_, db_, db_session,
|
||||
mocker):
|
||||
@moto.mock_s3
|
||||
def test_upload_csvfile_with_invalid_phone_shows_check_page_with_errors(app_,
|
||||
db_,
|
||||
db_session,
|
||||
mocker,
|
||||
active_user):
|
||||
|
||||
contents = 'phone\n+44 123\n+44 456'
|
||||
file_data = (BytesIO(contents.encode('utf-8')), 'invalid.csv')
|
||||
m_open = mock_open(read_data=contents)
|
||||
_setup_mocker_for_nonemtpy_file(mocker)
|
||||
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
user = create_test_user('active')
|
||||
client.login(user)
|
||||
client.login(active_user)
|
||||
upload_data = {'file': file_data}
|
||||
with mock.patch('app.main.views.sms._open', m_open):
|
||||
response = client.post('/services/123/sms/send',
|
||||
data=upload_data,
|
||||
follow_redirects=True)
|
||||
response = client.post(url_for('main.send_sms', service_id=123),
|
||||
data=upload_data,
|
||||
follow_redirects=True)
|
||||
assert response.status_code == 200
|
||||
content = response.get_data(as_text=True)
|
||||
|
||||
assert 'There was a problem with some of the numbers' in content
|
||||
assert 'The following numbers are invalid' in content
|
||||
assert '+44 123' in content
|
||||
assert '+44 456' in content
|
||||
assert 'Go back and resolve errors' in content
|
||||
|
||||
|
||||
def test_upload_csvfile_with_valid_phone_shows_first3_and_last3_numbers(
|
||||
app_, db_, db_session,
|
||||
mocker):
|
||||
@moto.mock_s3
|
||||
def test_upload_csvfile_with_valid_phone_shows_first3_and_last3_numbers(app_,
|
||||
db_,
|
||||
db_session,
|
||||
mocker,
|
||||
active_user):
|
||||
|
||||
contents = 'phone\n+44 7700 900981\n+44 7700 900982\n+44 7700 900983\n+44 7700 900984\n+44 7700 900985\n+44 7700 900986\n+44 7700 900987\n+44 7700 900988\n+44 7700 900989' # noqa
|
||||
|
||||
file_data = (BytesIO(contents.encode('utf-8')), 'valid.csv')
|
||||
m_open = mock_open(read_data=contents)
|
||||
_setup_mocker_for_nonemtpy_file(mocker)
|
||||
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
user = create_test_user('active')
|
||||
client.login(user)
|
||||
client.login(active_user)
|
||||
upload_data = {'file': file_data}
|
||||
with mock.patch('app.main.views.sms._open', m_open):
|
||||
response = client.post('/services/123/sms/send',
|
||||
data=upload_data,
|
||||
follow_redirects=True)
|
||||
response = client.post(url_for('main.send_sms', service_id=123),
|
||||
data=upload_data,
|
||||
follow_redirects=True)
|
||||
|
||||
content = response.get_data(as_text=True)
|
||||
|
||||
@@ -88,25 +78,24 @@ def test_upload_csvfile_with_valid_phone_shows_first3_and_last3_numbers(
|
||||
assert '+44 7700 900989' in content
|
||||
|
||||
|
||||
def test_upload_csvfile_with_valid_phone_shows_all_if_6_or_less_numbers(
|
||||
app_, db_, db_session,
|
||||
mocker):
|
||||
@moto.mock_s3
|
||||
def test_upload_csvfile_with_valid_phone_shows_all_if_6_or_less_numbers(app_,
|
||||
db_,
|
||||
db_session,
|
||||
mocker,
|
||||
active_user):
|
||||
|
||||
contents = 'phone\n+44 7700 900981\n+44 7700 900982\n+44 7700 900983\n+44 7700 900984\n+44 7700 900985\n+44 7700 900986' # noqa
|
||||
|
||||
file_data = (BytesIO(contents.encode('utf-8')), 'valid.csv')
|
||||
m_open = mock_open(read_data=contents)
|
||||
_setup_mocker_for_nonemtpy_file(mocker)
|
||||
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
user = create_test_user('active')
|
||||
client.login(user)
|
||||
client.login(active_user)
|
||||
upload_data = {'file': file_data}
|
||||
with mock.patch('app.main.views.sms._open', m_open):
|
||||
response = client.post('/services/123/sms/send',
|
||||
data=upload_data,
|
||||
follow_redirects=True)
|
||||
response = client.post(url_for('main.send_sms', service_id=123),
|
||||
data=upload_data,
|
||||
follow_redirects=True)
|
||||
|
||||
content = response.get_data(as_text=True)
|
||||
|
||||
@@ -121,34 +110,12 @@ def test_upload_csvfile_with_valid_phone_shows_all_if_6_or_less_numbers(
|
||||
assert '+44 7700 900986' in content
|
||||
|
||||
|
||||
def test_should_redirect_to_job(app_, db_,
|
||||
db_session, mocker):
|
||||
_setup_mocker_for_check(mocker)
|
||||
@moto.mock_s3
|
||||
def test_should_redirect_to_job(app_, db_, db_session, mocker, active_user):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
user = create_test_user('active')
|
||||
client.login(user)
|
||||
with client.session_transaction() as s:
|
||||
s[456] = 'test.csv'
|
||||
|
||||
response = client.post('/services/123/sms/check',
|
||||
data={'recipients': 'test.csv'})
|
||||
|
||||
client.login(active_user)
|
||||
response = client.post(url_for('main.check_sms',
|
||||
service_id=123,
|
||||
upload_id='someid'))
|
||||
assert response.status_code == 302
|
||||
|
||||
|
||||
def _setup_mocker_for_empty_file(mocker):
|
||||
mocker.patch('werkzeug.datastructures.FileStorage.save')
|
||||
mocker.patch('os.remove')
|
||||
ret = ValueError('The file emtpy.csv contained no data')
|
||||
mocker.patch('app.main.views.sms._check_file', side_effect=ret)
|
||||
|
||||
|
||||
def _setup_mocker_for_nonemtpy_file(mocker):
|
||||
mocker.patch('werkzeug.datastructures.FileStorage.save')
|
||||
mocker.patch('os.remove')
|
||||
mocker.patch('app.main.views.sms._check_file')
|
||||
|
||||
|
||||
def _setup_mocker_for_check(mocker):
|
||||
mocker.patch('app.main.views.sms.s3upload').return_value = 456
|
||||
|
||||
@@ -1,32 +1,49 @@
|
||||
from tests import create_test_user
|
||||
from flask import url_for
|
||||
|
||||
|
||||
def test_should_return_list_of_all_templates(app_, db_, db_session):
|
||||
def test_should_return_list_of_all_templates(app_, db_, db_session, active_user):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
user = create_test_user('active')
|
||||
client.login(user)
|
||||
response = client.get('/services/123/templates')
|
||||
client.login(active_user)
|
||||
response = client.get(url_for('.manage_templates', service_id=123))
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_should_show_page_for_one_templates(app_, db_, db_session):
|
||||
def test_should_show_page_for_one_templates(app_, db_, db_session, active_user):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
user = create_test_user('active')
|
||||
client.login(user)
|
||||
response = client.get('/services/123/templates/template')
|
||||
client.login(active_user)
|
||||
response = client.get(url_for('.edit_template', service_id=123, template_id=1))
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_should_redirect_when_saving_a_template(app_, db_, db_session):
|
||||
def test_should_redirect_when_saving_a_template(app_, db_, db_session, active_user):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
user = create_test_user('active')
|
||||
client.login(user)
|
||||
response = client.post('/services/123/templates/template')
|
||||
client.login(active_user)
|
||||
response = client.post(url_for('.edit_template', service_id=123, template_id=1))
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == 'http://localhost/services/123/templates'
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for('.manage_templates', service_id=123, _external=True)
|
||||
|
||||
|
||||
def test_should_show_delete_template_page(app_, db_, db_session, active_user):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(active_user)
|
||||
response = client.get(url_for('.delete_template', service_id=123, template_id=1))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'Are you sure' in response.get_data(as_text=True)
|
||||
|
||||
|
||||
def test_should_redirect_when_deleting_a_template(app_, db_, db_session, active_user):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(active_user)
|
||||
response = client.post(url_for('.delete_template', service_id=123, template_id=1))
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for('.manage_templates', service_id=123, _external=True)
|
||||
|
||||
@@ -32,7 +32,7 @@ def test_should_redirect_to_add_service_when_code_are_correct(app_,
|
||||
data={'sms_code': '12345',
|
||||
'email_code': '23456'})
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for('main.add_service', _external=True)
|
||||
assert response.location == url_for('main.add_service', first='first', _external=True)
|
||||
|
||||
|
||||
def test_should_activate_user_after_verify(app_, db_, db_session):
|
||||
|
||||
Reference in New Issue
Block a user