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 +

    + +
    +
    +
    + {{ textbox(form.name, width='1-1', hint='Your recipients won’t see this', rows=10) }} + {{ textbox(form.subject, width='1-1', highlight_tags=True, rows=2) }} +
    +
    + {{ textbox(form.template_content, highlight_tags=True, width='1-1', rows=8) }} + {{ page_footer( + 'Save', + delete_link=url_for('.delete_service_template', service_id=current_service.id, template_id=template_id) if template_id or None, + delete_link_text='Delete this 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 %}
    {% call(item, row_number) list_table( example, - caption="Fill in the {}".format('field' if template.placeholders|length == 1 else 'fields'), - field_headings=[recipient_column] + template.placeholders|list + caption="Fill in the {}".format('field' if (recipient_columns + template.placeholders|list)|length == 2 else 'fields'), + field_headings=recipient_columns + template.placeholders|list ) %} {% for column in item %} {% call field() %} - {% if loop.index > 1 %} + {% if loop.index > 1 or template.template_type == 'letter' %} {% else %} diff --git a/app/templates/views/send.html b/app/templates/views/send.html index 87309f156..398ea7eb6 100644 --- a/app/templates/views/send.html +++ b/app/templates/views/send.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, text_field, index_field, index_field_heading %} @@ -30,6 +31,8 @@ recipient='', show_placeholder_for_recipient=True ) }} + {% elif 'letter' == template.template_type %} + {{ letter(template.formatted_as_markup) }} {% endif %}