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

View File

@@ -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

View File

@@ -1 +1,3 @@
$(() => GOVUK.modules.start());
$(() => new GOVUK.SelectionButtons('.block-label input'));

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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 {

View File

@@ -45,4 +45,9 @@
margin: -$gutter-half 0 $gutter 0;
}
&-name {
@include bold-19;
margin: 50px 0 10px 0;
}
}

View File

@@ -44,6 +44,7 @@
@import 'components/browse-list';
@import 'components/management-navigation';
@import 'components/dropdown';
@import 'components/email-message';
@import 'views/job';

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.'
},
]

View File

@@ -22,22 +22,10 @@ from app.main import main
from app.main.forms import CsvUploadForm
from app.main.uploader import s3upload
# 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'
]
@@ -66,7 +54,7 @@ def sendsms(service_id):
return redirect(url_for('.sendsms', 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)
@@ -88,7 +76,7 @@ def checksms(service_id):
'views/check-sms.html',
upload_result=upload_result,
filename=filename,
message_template=message_templates[0]['body'],
message_template=sms_templates[0]['body'],
service_id=service_id
)
elif request.method == 'POST':

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 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))

View File

@@ -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>

View 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 %}

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">
{% 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 %}

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-wrapper">
{{ body|placeholders }}

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('.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('.manage_templates', service_id=123) }}">Templates</a></li>
</ul>
<ul>
<li><a href="{{ url_for('.apikeys', service_id=123) }}">API keys and documentation</a></li>

View File

@@ -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('.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>

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -32,6 +32,7 @@ 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 + 'govuk_frontend_toolkit/javascripts/govuk/selection-buttons.js',
paths.src + 'javascripts/highlightTags.js',
paths.src + 'javascripts/dropdown.js',
paths.src + 'javascripts/main.js'

View File

@@ -1,3 +1,4 @@
from flask import url_for
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:
user = create_test_user('active')
client.login(user)
response = client.get('/services/123/templates')
response = client.get(url_for('.manage_templates', service_id=123))
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_client() as client:
user = create_test_user('active')
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
@@ -26,7 +27,29 @@ def test_should_redirect_when_saving_a_template(notifications_admin, notificatio
with notifications_admin.test_client() as client:
user = create_test_user('active')
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.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(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)