Merge pull request #77 from alphagov/manage-templates-page

Manage templates page
This commit is contained in:
Chris Hill-Scott
2016-01-14 14:11:30 +00:00
20 changed files with 307 additions and 85 deletions
+10 -1
View File
@@ -1,7 +1,7 @@
import os import os
import re 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._compat import string_types
from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.sqlalchemy import SQLAlchemy
from flask_login import LoginManager 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(placeholders)
application.add_template_filter(replace_placeholders) application.add_template_filter(replace_placeholders)
application.add_template_filter(nl2br)
application.after_request(useful_headers_after_request) application.after_request(useful_headers_after_request)
register_errorhandlers(application) 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): def replace_placeholders(template, values):
if not template: if not template:
return template return template
+2
View File
@@ -1 +1,3 @@
$(() => GOVUK.modules.start()); $(() => GOVUK.modules.start());
$(() => new GOVUK.SelectionButtons('.block-label input'));
@@ -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;
}
}
@@ -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; 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 {}
.button-destructive { .button-destructive {
@@ -45,4 +45,9 @@
margin: -$gutter-half 0 $gutter 0; margin: -$gutter-half 0 $gutter 0;
} }
&-name {
@include bold-19;
margin: 50px 0 10px 0;
}
} }
+1
View File
@@ -44,6 +44,7 @@
@import 'components/browse-list'; @import 'components/browse-list';
@import 'components/management-navigation'; @import 'components/management-navigation';
@import 'components/dropdown'; @import 'components/dropdown';
@import 'components/email-message';
@import 'views/job'; @import 'views/job';
+39
View File
@@ -0,0 +1,39 @@
templates = [
{
'type': 'sms',
'name': 'Confirmation',
'body': 'Lasting power of attorney: Weve 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 youve 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 youre able to make your own decisions.
Your attorney can also register it for you. Youll 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.'
},
]
+6 -18
View File
@@ -22,22 +22,10 @@ from app.main import main
from app.main.forms import CsvUploadForm from app.main.forms import CsvUploadForm
from app.main.uploader import s3upload from app.main.uploader import s3upload
# TODO move this to the templates directory from ._templates import templates
message_templates = [
{ sms_templates = [
'name': 'Reminder', template for template in templates if template['type'] == 'sms'
'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
"""
},
] ]
@@ -66,7 +54,7 @@ def sendsms(service_id):
return redirect(url_for('.sendsms', service_id=service_id)) return redirect(url_for('.sendsms', service_id=service_id))
return render_template('views/send-sms.html', return render_template('views/send-sms.html',
message_templates=message_templates, message_templates=sms_templates,
form=form, form=form,
service_id=service_id) service_id=service_id)
@@ -88,7 +76,7 @@ def checksms(service_id):
'views/check-sms.html', 'views/check-sms.html',
upload_result=upload_result, upload_result=upload_result,
filename=filename, filename=filename,
message_template=message_templates[0]['body'], message_template=sms_templates[0]['body'],
service_id=service_id service_id=service_id
) )
elif request.method == 'POST': elif request.method == 'POST':
+37 -6
View File
@@ -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 flask_login import login_required
from app.main import main from app.main import main
from app.main.forms import TemplateForm from app.main.forms import TemplateForm
from ._templates import templates
@main.route("/services/<int:service_id>/templates") @main.route("/services/<int:service_id>/templates")
@login_required @login_required
def manage_templates(service_id): def manage_templates(service_id):
return render_template( return render_template(
'views/manage-templates.html', '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)) 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 @login_required
def edit_template(service_id, template_id): def edit_template(service_id, template_id):
form = TemplateForm() form = TemplateForm()
form.template_name.data = 'Reminder' form.template_name.data = templates[template_id - 1]['name']
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_body.data = templates[template_id - 1]['body']
if request.method == 'GET': if request.method == 'GET':
return render_template( return render_template(
'views/edit-template.html', 'views/edit-template.html',
h1='Edit template', h1='Edit template',
form=form, form=form,
service_id=service_id service_id=service_id,
template_id=template_id
) )
elif request.method == 'POST': elif request.method == 'POST':
return redirect(url_for('.manage_templates', service_id=service_id)) 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))
+15 -9
View File
@@ -68,16 +68,22 @@
{% endif %} {% endif %}
<main id="content" role="main" class="page-container"> <main id="content" role="main" class="page-container">
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %} {% if messages %}
<div class="error-summary"> <ul class="banner-dangerous">
<ul> {% for category, message in messages %}
{% for message in messages %} <li class="flash-message">
<li>{{ message }}</li> {{ message }}
{% endfor %} {% 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> </ul>
</div> {% endif %}
{% endif %}
{% endwith %} {% endwith %}
{% block fullwidth_content %}{% endblock %} {% block fullwidth_content %}{% endblock %}
</main> </main>
@@ -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 %}
+14 -2
View File
@@ -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"> <div class="page-footer">
{% if button_text %} {% if button_text %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" /> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="submit" class="button{% if destructive %}-destructive{% endif %}" value="{{ button_text }}" /> <input type="submit" class="button{% if destructive %}-destructive{% endif %}" value="{{ button_text }}" />
{% endif %} {% endif %}
{% if delete_link %}
<span class="page-footer-delete-link">
or <a href="{{ delete_link }}">{{ delete_link_text }}</a>
</span>
{% endif %}
{% if back_link %} {% 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 %} {% endif %}
</div> </div>
{% endmacro %} {% endmacro %}
+10 -1
View File
@@ -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">
<div class="sms-message-wrapper"> <div class="sms-message-wrapper">
{{ body|placeholders }} {{ body|placeholders }}
+1
View File
@@ -6,6 +6,7 @@
<li><a href="{{ url_for('.sendsms', service_id=123) }}">Send text messages</a></li> <li><a href="{{ url_for('.sendsms', service_id=123) }}">Send text messages</a></li>
<li><a href="{{ url_for('.sendemail', service_id=123) }}">Send emails</a></li> <li><a href="{{ url_for('.sendemail', service_id=123) }}">Send emails</a></li>
<li><a href="{{ url_for('.showjobs', service_id=123) }}">Activity</a></li> <li><a href="{{ url_for('.showjobs', service_id=123) }}">Activity</a></li>
<li><a href="{{ url_for('.manage_templates', service_id=123) }}">Templates</a></li>
</ul> </ul>
<ul> <ul>
<li><a href="{{ url_for('.apikeys', service_id=123) }}">API keys and documentation</a></li> <li><a href="{{ url_for('.apikeys', service_id=123) }}">API keys and documentation</a></li>
+4 -3
View File
@@ -14,9 +14,10 @@ GOV.UK Notify | Edit template
{{ textbox(form.template_name) }} {{ textbox(form.template_name) }}
{{ textbox(form.template_body, highlight_tags=True) }} {{ textbox(form.template_body, highlight_tags=True) }}
{{ page_footer( {{ page_footer(
'Save and continue', 'Save',
back_link=url_for('.dashboard', service_id=service_id), delete_link=url_for('.delete_template', service_id=service_id, template_id=template_id) if template_id or None,
back_link_text='Back to manage templates' back_link=url_for('.manage_templates', service_id=service_id),
back_link_text='Back to templates'
) }} ) }}
</form> </form>
+26 -9
View File
@@ -1,4 +1,7 @@
{% extends "withnav_template.html" %} {% 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 %} {% block page_title %}
GOV.UK Notify | Manage templates GOV.UK Notify | Manage templates
@@ -6,18 +9,32 @@ GOV.UK Notify | Manage templates
{% block maincolumn_content %} {% 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> {% for template in templates %}
<a href="{{ url_for('.edit_template', service_id=service_id, template_id=1) }}">Here is my first template</a> {% if template.type == 'sms' %}
</p> {{ sms_message(
template.body,
<p> name=template.name,
<a class="button" href="{{ url_for('.add_template', service_id=service_id) }}" role="button">Add a new message template</a> edit_link=url_for('.edit_template', service_id=service_id, template_id=loop.index)
</p> ) }}
{% 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 %} {% endblock %}
+27 -30
View File
@@ -4,45 +4,42 @@
{% from "components/textbox.html" import textbox %} {% from "components/textbox.html" import textbox %}
{% block page_title %} {% block page_title %}
GOV.UK Notify | Send text messages GOV.UK Notify | Send text messages
{% endblock %} {% endblock %}
{% block maincolumn_content %} {% 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> <fieldset class='form-group'>
{% for template in message_templates %} <legend class="heading-medium">1. Choose text message template</legend>
<div class="template-picker-option"> {% for template in message_templates %}
<div class="template-picker-option-radio"> <label class="block-label" for="template-{{loop.index}}">
<label class="block-label" for="template-{{loop.index}}"> {{ template.name }}
{{ template.name }} <input type="radio" name="template" id="template-{{loop.index}}" value="{{ template.name }}" />
<input type="radio" name="template" id="template-{{loop.index}}" value="{{ template.name }}" /> </label>
</label> {% endfor %}
</div> </fieldset>
{{ sms_message(template.body) }}
</div>
{% endfor %}
<p> <p>
or <a href="{{ url_for(".add_template", service_id=service_id) }}">create a new template</a> or <a href="{{ url_for(".add_template", service_id=service_id) }}">create a new template</a>
</p> </p>
<h2 class="heading-medium">2. Add recipients</h2> <h2 class="heading-medium">2. Add recipients</h2>
<p> <p>
Upload a CSV file to add your recipients details. Upload a CSV file to add your recipients details.
</p> </p>
<p> <p>
You can also <a href="#">download an example CSV</a>. You can also <a href="#">download an example CSV</a>.
</p> </p>
<p> <p>
{{textbox(form.file)}} {{textbox(form.file)}}
</p> </p>
{{ page_footer("Continue") }} {{ page_footer("Continue") }}
</form> </form>
{% endblock %} {% endblock %}
+1
View File
@@ -32,6 +32,7 @@ gulp.task('copy:govuk_template:assets', () => gulp.src('bower_components/govuk_t
gulp.task('javascripts', () => gulp gulp.task('javascripts', () => gulp
.src([ .src([
paths.src + 'govuk_frontend_toolkit/javascripts/govuk/modules.js', paths.src + 'govuk_frontend_toolkit/javascripts/govuk/modules.js',
paths.src + 'govuk_frontend_toolkit/javascripts/govuk/selection-buttons.js',
paths.src + 'javascripts/highlightTags.js', paths.src + 'javascripts/highlightTags.js',
paths.src + 'javascripts/dropdown.js', paths.src + 'javascripts/dropdown.js',
paths.src + 'javascripts/main.js' paths.src + 'javascripts/main.js'
+29 -6
View File
@@ -1,3 +1,4 @@
from flask import url_for
from tests.app.main import create_test_user from tests.app.main import create_test_user
@@ -6,17 +7,17 @@ def test_should_return_list_of_all_templates(notifications_admin, notifications_
with notifications_admin.test_client() as client: with notifications_admin.test_client() as client:
user = create_test_user('active') user = create_test_user('active')
client.login(user) client.login(user)
response = client.get('/services/123/templates') response = client.get(url_for('.manage_templates', service_id=123))
assert response.status_code == 200 assert response.status_code == 200
def test_should_show_page_for_one_templates(notifications_admin, notifications_admin_db, notify_db_session): def test_should_show_page_for_one_template(notifications_admin, notifications_admin_db, notify_db_session):
with notifications_admin.test_request_context(): with notifications_admin.test_request_context():
with notifications_admin.test_client() as client: with notifications_admin.test_client() as client:
user = create_test_user('active') user = create_test_user('active')
client.login(user) client.login(user)
response = client.get('/services/123/templates/template') response = client.get(url_for('.edit_template', service_id=123, template_id=1))
assert response.status_code == 200 assert response.status_code == 200
@@ -26,7 +27,29 @@ def test_should_redirect_when_saving_a_template(notifications_admin, notificatio
with notifications_admin.test_client() as client: with notifications_admin.test_client() as client:
user = create_test_user('active') user = create_test_user('active')
client.login(user) client.login(user)
response = client.post('/services/123/templates/template') response = client.post(url_for('.edit_template', service_id=123, template_id=1))
assert response.status_code == 302 assert response.status_code == 302
assert response.location == 'http://localhost/services/123/templates' assert response.location == url_for('.manage_templates', service_id=123, _external=True)
def test_should_show_delete_template_page(notifications_admin, notifications_admin_db, notify_db_session):
with notifications_admin.test_request_context():
with notifications_admin.test_client() as client:
user = create_test_user('active')
client.login(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(notifications_admin, notifications_admin_db, notify_db_session):
with notifications_admin.test_request_context():
with notifications_admin.test_client() as client:
user = create_test_user('active')
client.login(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)