Merge pull request #186 from alphagov/email-templates

Email templates
This commit is contained in:
Adam Shimali
2016-02-22 13:34:45 +00:00
18 changed files with 395 additions and 48 deletions

View File

@@ -1,11 +1,17 @@
.column-one-quarter {
@include grid-column(1/4);
}
.column-three-quarters {
@include grid-column(3/4);
}
.column-one-eighth {
@include grid-column(1/8);
}
.column-seven-eighths {
@include grid-column(7/8);
}
.bottom-gutter {
margin-bottom: $gutter;
clear: both;
}

View File

@@ -1,24 +1,30 @@
.email-message {
margin-bottom: $gutter;
border: 1px solid $border-colour;
&-subject {
@include bold-19;
border-bottom: 1px solid $border-colour;
padding: 10px;
}
&-body {
border-bottom: 1px solid $white;
padding: 10px;
overflow: hidden;
max-height: 103px;
}
&-name {
@include bold-19;
margin: 50px 0 10px 0;
margin: 20px 0 10px 0;
}
&-subject,
&-from {
margin: 10px 0;
}
&-from {
padding-top: 15px;
border-top: 1px solid $border-colour;
}
&-body {
width: 100%;
box-sizing: border-box;
padding: $gutter-half 0 0 0;
margin: 0 0 $gutter 0;
clear: both;
border-top: 1px solid $border-colour;
border-bottom: 1px solid $border-colour;
}
}

View File

@@ -8,6 +8,7 @@
border-radius: 5px;
white-space: normal;
margin: 0 0 $gutter 0;
clear: both;
}
.sms-message-wrapper-with-radio {
@@ -53,3 +54,8 @@
}
}
.sms-message-from {
@include bold-19;
display: block;
}

View File

@@ -85,3 +85,7 @@
.textbox-help-link {
margin: 5px 0 0 0;
}
.textbox-right-aligned {
text-align: right;
}

View File

@@ -1,5 +1,6 @@
from flask import url_for
from app import notifications_api_client
from notifications_python_client.errors import HTTPError
from app.utils import BrowsableItem
@@ -26,6 +27,16 @@ def get_service_by_id(id_):
return notifications_api_client.get_service(id_)
def get_service_by_id_or_404(id_):
try:
return get_service_by_id(id_)
except HTTPError as e:
if e.status_code == 404:
abort(404)
else:
raise e
def get_services(user_id=None):
if user_id:
return notifications_api_client.get_services({'user_id': str(user_id)})

View File

@@ -1,4 +1,5 @@
from datetime import datetime
from notifications_python_client import HTTPError
from sqlalchemy.orm import load_only
@@ -47,9 +48,15 @@ def activate_user(user):
def is_email_unique(email_address):
if user_api_client.get_user_by_email(email_address):
return False
return True
try:
if user_api_client.get_user_by_email(email_address):
return False
return True
except HTTPError as ex:
if ex.status_code == 404:
return True
else:
raise ex
def request_password_reset(user):

View File

@@ -1,4 +1,6 @@
from flask import render_template, redirect, session, url_for
import re
from flask import render_template, request, redirect, session, url_for
from flask_login import login_required, current_user
from app.main import main
from app.main.dao import services_dao, users_dao
@@ -15,13 +17,33 @@ def add_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.id)
session['service_name'] = form.name.data
return redirect(url_for('main.service_dashboard', service_id=service_id))
return redirect(url_for('main.add_from_address'))
else:
return render_template(
'views/add-service.html',
form=form,
heading=heading
)
@main.route("/confirm-add-service", methods=['GET', 'POST'])
@login_required
def add_from_address():
if request.method == 'POST':
user = users_dao.get_user_by_id(session['user_id'])
service_id = services_dao.insert_new_service(session['service_name'], user.id)
return redirect(url_for('main.service_dashboard', service_id=service_id))
else:
return render_template(
'views/add-from-address.html',
service_name=session['service_name'],
from_address="{}@notifications.service.gov.uk".format(_email_safe(session['service_name']))
)
def _email_safe(string):
return "".join([
character.lower() if character.isalnum() or character == "." else ""
for character in re.sub("\s+", ".", string.strip())
])

View File

@@ -26,7 +26,6 @@ def register():
return redirect(url_for('main.choose_service'))
form = RegisterUserForm()
if form.validate_on_submit():
if users_dao.is_email_unique(form.email_address.data):
try:

View File

@@ -127,6 +127,7 @@ def check_sms(service_id, upload_id):
template_id = upload_data.get('template_id')
raw_template = templates_dao.get_service_template_or_404(service_id, template_id)['data']
upload_result = _get_rows(contents, raw_template)
print(upload_result)
template = Template(
raw_template,
values=upload_result['rows'][0] if upload_result['valid'] else {},

View File

@@ -24,7 +24,7 @@ class NotificationsAdminAPIClient(NotificationsAPIClient):
"name": service_name,
"active": active,
"limit": limit,
"users": [user_id],
"user_id": user_id,
"restricted": restricted
}
return self.post("/service", data)

View File

@@ -1,4 +1,4 @@
{% macro email_message(subject, body, name=None, edit_link=None) %}
{% macro email_message(subject, body, name=None, edit_link=None, from_name=None, from_address=None) %}
{% if name %}
<h3 class="email-message-name">
{% if edit_link %}
@@ -9,9 +9,30 @@
</h3>
{% endif %}
<div class="email-message">
<div class="email-message-subject">
{{ subject }}
</div>
{% if from_name and from_address %}
<div class="email-message-from">
<div class="grid-row">
<div class="column-one-eighth">
<span class="form-hint">From</span>
</div>
<div class="column-seven-eighths">
{{ from_name }} &lt;{{ from_address }}&gt;
</div>
</div>
</div>
{% endif %}
{% if subject %}
<div class="email-message-subject">
<div class="grid-row">
<div class="column-one-eighth">
<span class="form-hint">Subject</span>
</div>
<div class="column-seven-eighths">
{{ subject }}
</div>
</div>
</div>
{% endif %}
<div class="email-message-body">
{{ body|nl2br }}
</div>

View File

@@ -1,5 +1,5 @@
{% macro sms_message(
body, recipient=None, name=None, id=None, edit_link=None
body, recipient=None, name=None, id=None, edit_link=None, from=None
) %}
{% if name %}
<h3 class="sms-message-name">
@@ -11,6 +11,11 @@
</h3>
{% endif %}
<div class="sms-message-wrapper{% if input_name %}-with-radio{% endif %}">
{% if from %}
<span class="sms-message-from">
{{ from }}
</span>
{% endif %}
{{ body }}
</div>
{% if recipient %}

View File

@@ -5,7 +5,8 @@
autofocus=False,
help_link=None,
help_link_text=None,
width='2-3'
width='2-3',
suffix=None
) %}
<div class="form-group{% if field.errors %} error{% endif %}" {% if autofocus %}data-module="autofocus"{% endif %}>
<label class="form-label" for="{{ field.name }}">
@@ -22,9 +23,12 @@
{% endif %}
</label>
{{ field(**{
'class': 'form-control form-control-{} textbox-highlight-textbox'.format(width) if highlight_tags else 'form-control form-control-{}'.format(width),
'class': 'form-control form-control-{} textbox-highlight-textbox'.format(width) if highlight_tags else 'form-control form-control-{} {}'.format(width, 'textbox-right-aligned' if suffix else ''),
'data-module': 'highlight-tags' if highlight_tags else ''
}) }}
{% if suffix %}
<span>{{ suffix }}</span>
{% endif %}
{% if help_link and help_link_text %}
<p class="textbox-help-link">
<a href='{{ help_link }}'>{{ help_link_text }}</a>

View File

@@ -0,0 +1,85 @@
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<!-- This disables auto detection of phone numbers in some clients. Remove if not needed. -->
<meta name="format-detection" content="telephone=no">
<title>Page title</title>
</head>
<body style="font-family: Helvetica, Arial, sans-serif;font-size: 16px;margin: 0;color:#0b0c0c">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td width="100%" height="53px" bgcolor="#0b0c0c">
<table width="580" cellpadding="0" cellspacing="0" border="0" align="center">
<tr>
<td width="100%" bgcolor="#0b0c0c" valign="middle">
<!-- This asset should be hosted by the service -->
<img src="http://govuk-prototyping.herokuapp.com/hmrc-email/govuk_logotype_email.png" alt="GOV.UK" border="0">
</td>
</tr>
</table>
</td>
</tr>
</table>
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#FFFFFF" style="margin-bottom: 19px">
<tr>
<td width="100%">
<table width="580" cellpadding="0" cellspacing="0" border="0" align="center">
<tr>
<td width="75%">
<h1 style="font-size: 36px; line-height: 1.315789474; font-weight: 700; margin: 19px 0 38px 0;">
This is the page title
</h1>
<p style="font-size: 19px; line-height: 1.315789474; margin: 30px 0 30px 0;">
Dear A Name,
</p>
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
This is the first paragraph.
</p>
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
This is the second paragraph. It is longer than the first and wraps on to two lines.
</p>
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
This paragraph includes a line-break:<br/>
This is the line after the line-break.
</p>
<p style="border-left: 10px #dee0e2 solid; padding: 14px 0 14px 14px; font-size: 19px; line-height: 1.315789474; margin: 0 0 19px 0;">
This is an inset paragraph used for data or callouts<br/>
Application reference: 123456789<br/>
Date received: 15 March 2015<br/>
</p>
<h2 style="font-size: 24px; line-height: 1.315789474; font-weight: 700; margin: 38px 0 19px 0;">
Second level heading</h2>
<h3 style="font-size: 19px; line-height: 1.315789474; font-weight: 700; margin: 38px 0 19px 0;">
Third level heading
</h3>
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
Intro before a url:
<!-- URLs have white-space: nowrap so that they don't wrap -->
<a style="white-space: nowrap" href="www.gov.uk/service-start-page">www.gov.uk/service-start-page</a>
</p>
<p style="font-weight: 700; font-size: 19px; line-height: 1.315789474; margin: 38px 0 38px 0;">
Department or agency name
</p>
</td>
<td width="25%">&nbsp;</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,95 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Page title</title>
<style>
@media only screen and (min-device-width: 581px) {
.content {
width: 580px !important;
}
}
</style>
</head>
<body style="font-family: Helvetica, Arial, sans-serif;font-size: 16px;margin: 0;color:#0b0c0c">
<!--[if (gte mso 9)|(IE)]>
<table width="580" align="center" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>
<![endif]-->
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td width="100%" height="53px" bgcolor="#0b0c0c">
<table width="580" cellpadding="0" cellspacing="0" border="0" align="center">
<tr>
<td width="100%" bgcolor="#0b0c0c" valign="middle">
<!-- This asset should be hosted by the service -->
<img src="http://govuk-prototyping.herokuapp.com/hmrc-email/govuk_logotype_email.png" alt="GOV.UK" border="0">
</td>
</tr>
</table>
</td>
</tr>
</table>
<table class="content" align="center" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width: 580px;">
<tr>
<td>
<h1 style="font-size: 36px; line-height: 1.315789474; font-weight: 700; margin: 19px 0 38px 0;">
This is the page title
</h1>
<p style="font-size: 19px; line-height: 1.315789474; margin: 30px 0 30px 0;">
Dear A Name,
</p>
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
This is the first paragraph.
</p>
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
This is the second paragraph. It is longer than the first and wraps on to two lines.
</p>
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
This paragraph includes a line-break:<br/>
This is the line after the line-break.
</p>
<p style="border-left: 10px #dee0e2 solid; padding: 14px 0 14px 14px; font-size: 19px; line-height: 1.315789474; margin: 0 0 19px 0;">
This is an inset paragraph used for data or callouts<br/>
Application reference: 123456789<br/>
Date received: 15 March 2015<br/>
</p>
<h2 style="font-size: 24px; line-height: 1.315789474; font-weight: 700; margin: 38px 0 19px 0;">
Second level heading</h2>
<h3 style="font-size: 19px; line-height: 1.315789474; font-weight: 700; margin: 38px 0 19px 0;">
Third level heading
</h3>
<p style="font-size: 19px; line-height: 1.315789474; margin: 0 0 30px 0;">
Intro before a url:
<!-- URLs have white-space: nowrap so that they don't wrap -->
<a style="white-space: nowrap" href="www.gov.uk/service-start-page">www.gov.uk/service-start-page</a>
</p>
<p style="font-weight: 700; font-size: 19px; line-height: 1.315789474; margin: 38px 0 38px 0;">
Department or agency name
</p>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</body>
</html>

View File

@@ -0,0 +1,42 @@
{% extends "withoutnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/sms-message.html" import sms_message %}
{% from "components/email-message.html" import email_message %}
{% block page_title %}
Preview your service name GOV.UK Notify
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-large">
Preview your service name
</h1>
<div class="grid-row">
<div class="column-two-thirds">
{{ sms_message(
"{}: we received your payment, thank you".format(service_name),
name="Text message",
recipient='Sent from 40604'
) }}
</div>
</div>
<div class="grid-row">
<div class='column-two-thirds'>
{{ email_message(
subject="We received your payment, thank you",
body="Dear Alice Smith,\n\nThank you for…",
from_name=service_name,
from_address=from_address,
name="Email",
) }}
</div>
</div>
<form method="post">
{{page_footer('Looks good', back_link=url_for(".add_service"))}}
</form>
{% endblock %}

View File

@@ -12,27 +12,21 @@
<div class="column-two-thirds">
<h1 class="heading-large">
{{ heading }}
When people receive notifications, who should they be from?
</h1>
<p>
Users will see your service name when they receive messages through GOV.UK
Notify:
Be specific. Remember that there might be other people in your
organisation using GOV.UK Notify.
</p>
<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, hint="You can change this later") }}
{{ page_footer('Continue') }}
</form>
</div>

View File

@@ -31,6 +31,45 @@ def test_should_add_service_and_redirect_to_next_page(app_,
url_for('main.add_service'),
data={'name': 'testing the post'})
assert response.status_code == 302
assert response.location == url_for('main.add_from_address', _external=True)
def test_should_confirm_add_service(
app_,
mock_login,
mock_get_services,
api_user_active,
mock_get_user,
mock_get_user_by_email
):
with app_.test_request_context():
with app_.test_client() as client:
client.login(api_user_active)
with client.session_transaction() as session:
session['service_name'] = 'Renew Your Pet Passport'
response = client.get(url_for('main.add_from_address'))
assert response.status_code == 200
assert 'Preview your service name' in response.get_data(as_text=True)
assert 'Renew Your Pet Passport' in response.get_data(as_text=True)
assert 'renew.your.pet.passport@notifications.service.gov.uk' in response.get_data(as_text=True)
def test_should_add_service_after_confirmation(
app_,
mock_login,
mock_create_service,
mock_get_services,
api_user_active,
mock_get_user,
mock_get_user_by_email
):
with app_.test_request_context():
with app_.test_client() as client:
client.login(api_user_active)
with client.session_transaction() as session:
session['service_name'] = 'Renew Your Pet Passport'
response = client.post(url_for('main.add_from_address'))
assert response.status_code == 302
assert response.location == url_for('main.service_dashboard', service_id=101, _external=True)
assert mock_create_service.called
assert mock_get_services.called