diff --git a/app/assets/stylesheets/components/letter.scss b/app/assets/stylesheets/components/letter.scss
new file mode 100644
index 000000000..009c53286
--- /dev/null
+++ b/app/assets/stylesheets/components/letter.scss
@@ -0,0 +1,13 @@
+$outline-width: 5px;
+
+.letter {
+ font-family: Helvetica, Arial, sans-serif;
+ box-shadow:
+ 1px 1px 0 0 $panel-colour,
+ 2px 2px 0 0 rgba($panel-colour, 0.5),
+ -1px 1px 0 0 $panel-colour,
+ -2px 2px 0 0 rgba($panel-colour, 0.5);
+ outline: $outline-width solid rgba($text-colour, 0.1);
+ padding: 20px;
+ margin: $outline-width $outline-width $gutter;
+}
diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss
index 830a68391..f5a0e4d4f 100644
--- a/app/assets/stylesheets/main.scss
+++ b/app/assets/stylesheets/main.scss
@@ -58,6 +58,7 @@ $path: '/static/images/';
@import 'components/research-mode';
@import 'components/tick-cross';
@import 'components/list-entry';
+@import 'components/letter';
@import 'views/job';
@import 'views/edit-template';
diff --git a/app/main/__init__.py b/app/main/__init__.py
index de243703f..b70ccb942 100644
--- a/app/main/__init__.py
+++ b/app/main/__init__.py
@@ -26,6 +26,5 @@ from app.main.views import (
invites,
feedback,
providers,
- platform_admin,
- letters
+ platform_admin
)
diff --git a/app/main/forms.py b/app/main/forms.py
index 95afc47c6..57599ee78 100644
--- a/app/main/forms.py
+++ b/app/main/forms.py
@@ -276,6 +276,10 @@ class EmailTemplateForm(SMSTemplateForm):
validators=[DataRequired(message="Can’t be empty")])
+class LetterTemplateForm(EmailTemplateForm):
+ pass
+
+
class ForgotPasswordForm(Form):
email_address = email_address(gov_user=False)
diff --git a/app/main/views/letters.py b/app/main/views/letters.py
deleted file mode 100644
index bf268a12e..000000000
--- a/app/main/views/letters.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from flask import render_template, abort
-from flask_login import login_required
-
-from app import current_service
-from app.main import main
-from app.utils import user_has_permissions
-
-
-@main.route("/services//letters")
-@login_required
-def letters(service_id):
- if not current_service['can_send_letters']:
- abort(403)
- return render_template('views/letters.html')
diff --git a/app/main/views/send.py b/app/main/views/send.py
index 90f423d13..5e6f5b60c 100644
--- a/app/main/views/send.py
+++ b/app/main/views/send.py
@@ -17,8 +17,9 @@ from flask import (
)
from flask_login import login_required, current_user
+from notifications_utils.columns import Columns
from notifications_utils.template import Template
-from notifications_utils.recipients import RecipientCSV, first_column_heading, validate_and_format_phone_number
+from notifications_utils.recipients import RecipientCSV, first_column_headings, validate_and_format_phone_number
from app.main import main
from app.main.forms import CsvUploadForm, ChooseTimeForm, get_next_days_until, get_furthest_possible_scheduled_time
@@ -33,7 +34,8 @@ from app.utils import user_has_permissions, get_errors_for_csv, Spreadsheet, get
def get_page_headings(template_type):
return {
'email': 'Email templates',
- 'sms': 'Text message templates'
+ 'sms': 'Text message templates',
+ 'letter': 'Letter templates'
}[template_type]
@@ -47,14 +49,27 @@ def get_example_csv_fields(column_headers, use_example_as_example, submitted_fie
def get_example_csv_rows(template, use_example_as_example=True, submitted_fields=False):
- return [
- {
- 'email': 'test@example.com' if use_example_as_example else current_user.email_address,
- 'sms': '07700 900321' if use_example_as_example else validate_and_format_phone_number(
- current_user.mobile_number, human_readable=True
+ return {
+ 'email': ['test@example.com'] if use_example_as_example else [current_user.email_address],
+ 'sms': ['07700 900321'] if use_example_as_example else [validate_and_format_phone_number(
+ current_user.mobile_number, human_readable=True
+ )],
+ 'letter': [
+ (submitted_fields or {}).get(
+ key, get_example_letter_address(key) if use_example_as_example else key
)
- }[template.template_type]
- ] + get_example_csv_fields(template.placeholders, use_example_as_example, submitted_fields)
+ for key in first_column_headings['letter']
+ ]
+ }[template.template_type] + get_example_csv_fields(template.placeholders, use_example_as_example, submitted_fields)
+
+
+def get_example_letter_address(key):
+ return {
+ 'address line 1': 'A. Name',
+ 'address line 2': '123 Example Street',
+ 'address line 3': 'Example town',
+ 'postcode': 'XM4 5HQ'
+ }.get(key, '')
@main.route("/services//send/", methods=['GET'])
@@ -66,8 +81,10 @@ def get_example_csv_rows(template, use_example_as_example=True, submitted_fields
'manage_api_keys',
admin_override=True, any_=True)
def choose_template(service_id, template_type):
- if template_type not in ['email', 'sms']:
+ if template_type not in ['email', 'sms', 'letter']:
abort(404)
+ if not current_service['can_send_letters'] and template_type == 'letter':
+ abort(403)
return render_template(
'views/templates/choose.html',
templates=[
@@ -114,14 +131,13 @@ def send_messages(service_id, template_id):
form.file.data.filename
))
+ column_headings = first_column_headings[template.template_type] + list(template.placeholders)
+
return render_template(
'views/send.html',
template=template,
- column_headings=list(ascii_uppercase[:len(template.placeholders) + 1]),
- example=[
- [first_column_heading[template.template_type]] + list(template.placeholders),
- get_example_csv_rows(template)
- ],
+ column_headings=list(ascii_uppercase[:len(column_headings)]),
+ example=[column_headings, get_example_csv_rows(template)],
form=form
)
@@ -132,7 +148,7 @@ def send_messages(service_id, template_id):
def get_example_csv(service_id, template_id):
template = Template(service_api_client.get_service_template(service_id, template_id)['data'])
return Spreadsheet.from_rows([
- [first_column_heading[template.template_type]] + list(template.placeholders),
+ first_column_headings[template.template_type] + list(template.placeholders),
get_example_csv_rows(template)
]).as_csv_data, 200, {
'Content-Type': 'text/csv; charset=utf-8',
@@ -159,7 +175,7 @@ def send_test(service_id, template_id):
{
'file_name': file_name,
'data': Spreadsheet.from_rows([
- [first_column_heading[template.template_type]] + list(template.placeholders),
+ first_column_headings[template.template_type] + list(template.placeholders),
get_example_csv_rows(template, use_example_as_example=False, submitted_fields=request.form)
]).as_csv_data
},
@@ -181,7 +197,7 @@ def send_test(service_id, template_id):
return render_template(
'views/send-test.html',
template=template,
- recipient_column=first_column_heading[template.template_type],
+ recipient_columns=first_column_headings[template.template_type],
example=[get_example_csv_rows(template, use_example_as_example=False)],
help=get_help_argument()
)
@@ -233,7 +249,7 @@ def check_messages(service_id, template_type, upload_id):
max_initial_rows_shown=50,
max_errors_shown=50,
whitelist=itertools.chain.from_iterable(
- [user.mobile_number, user.email_address] for user in users
+ [user.name, user.mobile_number, user.email_address] for user in users
) if current_service['restricted'] else None,
remaining_messages=remaining_messages
)
@@ -255,7 +271,10 @@ def check_messages(service_id, template_type, upload_id):
with suppress(StopIteration):
template.values = next(recipients.rows)
- first_recipient = template.values.get(recipients.recipient_column_header, '')
+ first_recipient = template.values.get(
+ Columns.make_key(recipients.recipient_column_headers[0]),
+ ''
+ )
session['upload_data']['notification_count'] = len(list(recipients.rows))
session['upload_data']['valid'] = not recipients.has_errors
@@ -306,6 +325,14 @@ def start_job(service_id, upload_id):
session.pop('upload_data')
+ template = service_api_client.get_service_template(
+ service_id,
+ upload_data.get('template_id')
+ )['data']
+
+ if template['template_type'] == 'letter':
+ abort(403)
+
job_api_client.create_job(
upload_id,
service_id,
diff --git a/app/main/views/templates.py b/app/main/views/templates.py
index d835fd5af..a970a4b21 100644
--- a/app/main/views/templates.py
+++ b/app/main/views/templates.py
@@ -6,19 +6,20 @@ from flask_login import login_required
from dateutil.parser import parse
from notifications_utils.template import Template
-from notifications_utils.recipients import first_column_heading
+from notifications_utils.recipients import first_column_headings
from notifications_python_client.errors import HTTPError
from app.main import main
from app.utils import user_has_permissions
-from app.main.forms import SMSTemplateForm, EmailTemplateForm
+from app.main.forms import SMSTemplateForm, EmailTemplateForm, LetterTemplateForm
from app.main.views.send import get_example_csv_rows
from app import service_api_client, current_service, template_statistics_client
form_objects = {
'email': EmailTemplateForm,
- 'sms': SMSTemplateForm
+ 'sms': SMSTemplateForm,
+ 'letter': LetterTemplateForm
}
page_headings = {
@@ -74,8 +75,10 @@ def view_template_version(service_id, template_id, version):
@login_required
@user_has_permissions('manage_templates', admin_override=True)
def add_service_template(service_id, template_type):
- if template_type not in ['sms', 'email']:
+ if template_type not in ['sms', 'email', 'letter']:
abort(404)
+ if not current_service['can_send_letters'] and template_type == 'letter':
+ abort(403)
form = form_objects[template_type]()
if form.validate_on_submit():
@@ -133,7 +136,7 @@ def edit_service_template(service_id, template_id):
new_template=new_template,
column_headings=list(ascii_uppercase[:len(new_template.placeholders) + 1]),
example_rows=[
- [first_column_heading[new_template.template_type]] + list(new_template.placeholders),
+ first_column_headings[new_template.template_type] + list(new_template.placeholders),
get_example_csv_rows(new_template),
get_example_csv_rows(new_template)
],
diff --git a/app/templates/components/letter.html b/app/templates/components/letter.html
new file mode 100644
index 000000000..8440ba31b
--- /dev/null
+++ b/app/templates/components/letter.html
@@ -0,0 +1,7 @@
+{% macro letter(
+ body
+) %}
+
+ {{ body }}
+
+{% endmacro %}
diff --git a/app/templates/components/message-count-label.html b/app/templates/components/message-count-label.html
index a4d59afed..23a0a3ee6 100644
--- a/app/templates/components/message-count-label.html
+++ b/app/templates/components/message-count-label.html
@@ -11,5 +11,11 @@
{%- else -%}
emails
{%- endif -%}
+ {%- elif template_type == 'letter' -%}
+ {%- if count == 1 -%}
+ letter
+ {%- else -%}
+ letters
+ {%- endif -%}
{%- endif %} {{ suffix }}
{%- endmacro %}
diff --git a/app/templates/main_nav.html b/app/templates/main_nav.html
index 01711286a..0ef4ddc35 100644
--- a/app/templates/main_nav.html
+++ b/app/templates/main_nav.html
@@ -51,7 +51,7 @@
Email templates
Text message templates
{% if current_service.can_send_letters %}
- Letter templates
+ Letter templates
{% endif %}
{% endif %}
{% if current_user.has_permissions(['manage_users', 'manage_settings'], admin_override=True) %}
diff --git a/app/templates/partials/check/too-many-messages.html b/app/templates/partials/check/too-many-messages.html
index b063640ee..761ca0801 100644
--- a/app/templates/partials/check/too-many-messages.html
+++ b/app/templates/partials/check/too-many-messages.html
@@ -15,10 +15,24 @@
You can still send {{ remaining_messages }} messages today, but
{% endif %}
‘{{ original_file_name }}’ contains
- {{ count_of_recipients }} {{ recipients.recipient_column_header }}
- {%- if count_of_recipients != 1 -%}
- {{ 'es' if 'email address' == recipients.recipient_column_header else 's' }}
- {%- endif %}.
+ {{ count_of_recipients }}
+ {% if count_of_recipients == 1 -%}
+ {%- if template.template_type == 'email' -%}
+ email address
+ {%- elif template.template_type == 'sms' -%}
+ phone number
+ {%- elif template.template_type == 'letter' -%}
+ address
+ {%- endif -%}
+ {%- else -%}
+ {%- if template.template_type == 'email' -%}
+ email addresses
+ {%- elif template.template_type == 'sms' -%}
+ phone numbers
+ {%- elif template.template_type == 'letter' -%}
+ addresses
+ {%- endif -%}
+ {%- endif -%}.
{% endcall %}
diff --git a/app/templates/partials/templates/guidance-formatting-letters.html b/app/templates/partials/templates/guidance-formatting-letters.html
new file mode 100644
index 000000000..77d8d6be4
--- /dev/null
+++ b/app/templates/partials/templates/guidance-formatting-letters.html
@@ -0,0 +1,19 @@
+Formatting
+
+ To put a title in your template, use a hash:
+
+
+
+ # This is a title
+
+
+
+ To make bullet points, use asterisks:
+
+
+
+ * point 1
+ * point 2
+ * point 3
+
+
diff --git a/app/templates/views/check.html b/app/templates/views/check.html
index a942579e9..5c3410a8f 100644
--- a/app/templates/views/check.html
+++ b/app/templates/views/check.html
@@ -3,6 +3,7 @@
{% from "components/email-message.html" import email_message %}
{% from "components/radios.html" import radio_select %}
{% from "components/sms-message.html" import sms_message %}
+{% from "components/letter.html" import letter %}
{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %}
{% from "components/file-upload.html" import file_upload %}
{% from "components/page-footer.html" import page_footer %}
@@ -30,7 +31,7 @@
{% endcall %}
- {% elif not recipients.has_recipient_column %}
+ {% elif not recipients.has_recipient_columns %}
{% call banner_wrapper(type='dangerous') %}
@@ -148,7 +149,9 @@
)}}
- {% endif %}
+ {% elif 'letter' == template.template_type %}
+ {{ letter(template.formatted_as_markup if errors else template.replaced|safe) }}
+ {% endif %}
{% if errors %}
{% if request.args.from_test %}
@@ -190,7 +193,9 @@
{{ item['columns'][column].data if item['columns'][column].data != None }}
{% endcall %}
{% else %}
- {{ text_field(item['columns'][column].data) }}
+ {% call field() %}
+ {{ item['columns'][column].data if item['columns'][column].data != None }}
+ {% endcall %}
{% endif %}
{% endfor %}
{% if item['columns'].get(None) %}
diff --git a/app/templates/views/edit-letter-template.html b/app/templates/views/edit-letter-template.html
new file mode 100644
index 000000000..1dc29f6fb
--- /dev/null
+++ b/app/templates/views/edit-letter-template.html
@@ -0,0 +1,36 @@
+{% extends "withnav_template.html" %}
+{% from "components/textbox.html" import textbox %}
+{% from "components/page-footer.html" import page_footer %}
+
+{% block page_title %}
+ {{ heading_action }} letter template – GOV.UK Notify
+{% endblock %}
+
+{% block maincolumn_content %}
+
+
+ {{ heading_action }} letter template
+
+
+
+
+{% endblock %}
diff --git a/app/templates/views/letters.html b/app/templates/views/letters.html
deleted file mode 100644
index 3a5fdf16f..000000000
--- a/app/templates/views/letters.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{% extends "withnav_template.html" %}
-{% from "components/sms-message.html" import sms_message %}
-{% from "components/email-message.html" import email_message %}
-{% from "components/page-footer.html" import page_footer %}
-{% from "components/file-upload.html" import file_upload %}
-{% from "components/table.html" import list_table, text_field, index_field, index_field_heading %}
-
-{% block page_title %}
- Upload recipients – GOV.UK Notify
-{% endblock %}
-
-{% block maincolumn_content %}
-
- Letters 📩📨💌📮📜📑👌👌👌
-
- [insert content here]
-
-
-{% endblock %}
diff --git a/app/templates/views/send-test.html b/app/templates/views/send-test.html
index 498eaf8da..4f8fa4980 100644
--- a/app/templates/views/send-test.html
+++ b/app/templates/views/send-test.html
@@ -1,6 +1,7 @@
{% extends "withnav_template.html" %}
{% from "components/sms-message.html" import sms_message %}
{% from "components/email-message.html" import email_message %}
+{% from "components/letter.html" import letter %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/file-upload.html" import file_upload %}
{% from "components/table.html" import list_table, field, text_field, index_field, index_field_heading %}
@@ -38,17 +39,19 @@
recipient='',
show_placeholder_for_recipient=True
) }}
+ {% elif 'letter' == template.template_type %}
+ {{ letter(template.formatted_as_markup) }}
{% endif %}