mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-18 21:49:37 -04:00
Merge with master.
This commit is contained in:
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user