diff --git a/app/main/views/send.py b/app/main/views/send.py index c6d720be1..c3415c78b 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -15,8 +15,9 @@ from flask import ( ) from flask_login import current_user from notifications_python_client.errors import HTTPError -from notifications_utils import SMS_CHAR_COUNT_LIMIT +from notifications_utils import LETTER_MAX_PAGE_COUNT, SMS_CHAR_COUNT_LIMIT from notifications_utils.columns import Columns +from notifications_utils.pdf import is_letter_too_long from notifications_utils.recipients import ( RecipientCSV, first_column_headings, @@ -522,6 +523,7 @@ def _check_messages(service_id, template_id, upload_id, preview_row, letters_as_ email_reply_to = get_email_reply_to_address_from_session() elif db_template['template_type'] == 'sms': sms_sender = get_sms_sender_from_session() + template = get_template( db_template, current_service, @@ -567,6 +569,8 @@ def _check_messages(service_id, template_id, upload_id, preview_row, letters_as_ elif preview_row > 2: abort(404) + page_count = get_page_count_for_letter(db_template, template.values) + return dict( recipients=recipients, template=template, @@ -589,7 +593,10 @@ def _check_messages(service_id, template_id, upload_id, preview_row, letters_as_ preview_row=preview_row, sent_previously=job_api_client.has_sent_previously( service_id, template.id, db_template['version'], request.args.get('original_file_name', '') - ) + ), + letter_too_long=is_letter_too_long(page_count), + letter_max_pages=LETTER_MAX_PAGE_COUNT, + page_count=page_count ) @@ -601,12 +608,12 @@ def check_messages(service_id, template_id, upload_id, row_index=2): data = _check_messages(service_id, template_id, upload_id, row_index) if ( - data['recipients'].too_many_rows or - not data['count_of_recipients'] or - not data['recipients'].has_recipient_columns or - data['recipients'].duplicate_recipient_column_headers or - data['recipients'].missing_column_headers or - data['sent_previously'] + data['recipients'].too_many_rows + or not data['count_of_recipients'] + or not data['recipients'].has_recipient_columns + or data['recipients'].duplicate_recipient_column_headers + or data['recipients'].missing_column_headers + or data['sent_previously'] ): return render_template('views/check/column-errors.html', **data) @@ -614,8 +621,8 @@ def check_messages(service_id, template_id, upload_id, row_index=2): return render_template('views/check/row-errors.html', **data) if ( - data['errors'] or - data['trying_to_send_letters_in_trial_mode'] + data['errors'] + or data['trying_to_send_letters_in_trial_mode'] ): return render_template('views/check/column-errors.html', **data) @@ -877,10 +884,14 @@ def _check_notification(service_id, template_id, exception=None): raise PermanentRedirect(back_link) template.values = get_recipient_and_placeholders_from_session(template.template_type) + page_count = get_page_count_for_letter(db_template, template.values) return dict( template=template, back_link=back_link, help=get_help_argument(), + letter_too_long=is_letter_too_long(page_count), + letter_max_pages=LETTER_MAX_PAGE_COUNT, + page_count=page_count, **(get_template_error_dict(exception) if exception else {}), ) diff --git a/app/main/views/templates.py b/app/main/views/templates.py index 7a5923e13..7e8fc6019 100644 --- a/app/main/views/templates.py +++ b/app/main/views/templates.py @@ -6,7 +6,9 @@ from flask import abort, flash, redirect, render_template, request, url_for from flask_login import current_user from markupsafe import Markup from notifications_python_client.errors import HTTPError +from notifications_utils import LETTER_MAX_PAGE_COUNT from notifications_utils.formatters import nl2br +from notifications_utils.pdf import is_letter_too_long from notifications_utils.recipients import first_column_headings from app import ( @@ -58,6 +60,8 @@ def view_template(service_id, template_id): '.send_one_off', service_id=service_id, template_id=template_id )) + page_count = get_page_count_for_letter(template) + return render_template( 'views/templates/template.html', template=get_template( @@ -74,6 +78,9 @@ def view_template(service_id, template_id): ), template_postage=template["postage"], user_has_template_permission=user_has_template_permission, + letter_too_long=is_letter_too_long(page_count), + letter_max_pages=LETTER_MAX_PAGE_COUNT, + page_count=page_count ) diff --git a/app/templates/components/banner.html b/app/templates/components/banner.html index 3b30cf1cb..1a272fa55 100644 --- a/app/templates/components/banner.html +++ b/app/templates/components/banner.html @@ -1,12 +1,15 @@ {% from "components/form.html" import form_wrapper %} -{% macro banner(body, type=None, with_tick=False, delete_button=None, subhead=None, context=None, action=None) %} +{% macro banner(body, type=None, with_tick=False, delete_button=None, subhead=None, context=None, action=None, id=None) %}
{% if subhead -%}

{{ subhead }}

@@ -26,6 +29,6 @@
{% endmacro %} -{% macro banner_wrapper(type=None, with_tick=False, delete_button=None, subhead=None, action=None) %} - {{ banner(caller()|safe, type=type, with_tick=with_tick, delete_button=delete_button, subhead=subhead, action=action) }} +{% macro banner_wrapper(type=None, with_tick=False, delete_button=None, subhead=None, action=None, id=None) %} + {{ banner(caller()|safe, type=type, with_tick=with_tick, delete_button=delete_button, subhead=subhead, action=action, id=id) }} {% endmacro %} diff --git a/app/templates/partials/check/letter-too-long.html b/app/templates/partials/check/letter-too-long.html new file mode 100644 index 000000000..578759bc7 --- /dev/null +++ b/app/templates/partials/check/letter-too-long.html @@ -0,0 +1,8 @@ +

+ Your letter is too long +

+

+ Letters must be {{ letter_max_pages }} pages or less. +
+ Your letter is {{ page_count }} pages long. +

diff --git a/app/templates/views/check/ok.html b/app/templates/views/check/ok.html index 693208bf9..d726395a9 100644 --- a/app/templates/views/check/ok.html +++ b/app/templates/views/check/ok.html @@ -25,10 +25,15 @@ back_link=back_link ) }} + {% if letter_too_long %} + {% call banner_wrapper(type='dangerous', id='letter-too-long') %} + {% include "partials/check/letter-too-long.html" %} + {% endcall %} + {% endif %} + {{ skip_to_file_contents() }} {{ template|string }} -
+ {% elif letter_too_long %} + {% set error = 'letter-too-long' %} + {{ govuk_back_link(back_link) }} +
+ {% call banner_wrapper(type='dangerous', id='letter-too-long') %} + {% include "partials/check/letter-too-long.html" %} + {% endcall %} +
{% else %} {{ page_header( 'Preview of ‘{}’'.format(template.name), diff --git a/app/templates/views/templates/_template.html b/app/templates/views/templates/_template.html index a6d907a23..f1112724b 100644 --- a/app/templates/views/templates/_template.html +++ b/app/templates/views/templates/_template.html @@ -1,4 +1,5 @@ {% from 'components/message-count-label.html' import message_count_label %} +{% from "components/banner.html" import banner_wrapper %}
{% if template._template.archived %} @@ -15,7 +16,12 @@
{% if template.template_type == 'letter' %} - {% if current_user.has_permissions('send_messages', restrict_admin_usage=True) %} + {% if letter_too_long %} + {% call banner_wrapper(type='dangerous', id='letter-too-long') %} + {% include "partials/check/letter-too-long.html" %} + {% endcall %} + {% endif %} + {% if current_user.has_permissions('send_messages', restrict_admin_usage=True) and not letter_too_long %}
Send diff --git a/app/utils.py b/app/utils.py index 723f29f4f..71e058fd3 100644 --- a/app/utils.py +++ b/app/utils.py @@ -43,6 +43,7 @@ FAILURE_STATUSES = ['failed', 'temporary-failure', 'permanent-failure', 'technical-failure', 'virus-scan-failed', 'validation-failed'] REQUESTED_STATUSES = SENDING_STATUSES + DELIVERED_STATUSES + FAILURE_STATUSES + with open('{}/email_domains.txt'.format( os.path.dirname(os.path.realpath(__file__)) )) as email_domains: diff --git a/requirements-app.txt b/requirements-app.txt index a650b656e..55df8fd15 100644 --- a/requirements-app.txt +++ b/requirements-app.txt @@ -23,5 +23,5 @@ awscli-cwlogs>=1.4,<1.5 # Putting upgrade on hold due to v1.0.0 using sha512 instead of sha1 by default itsdangerous==0.24 # pyup: <1.0.0 -git+https://github.com/alphagov/notifications-utils.git@34.1.0#egg=notifications-utils==34.1.0 +git+https://github.com/alphagov/notifications-utils.git@35.0.0#egg=notifications-utils==35.0.0 git+https://github.com/alphagov/govuk-frontend-jinja.git@v0.3.0-alpha#egg=govuk-frontend-jinja==0.3.0-alpha diff --git a/requirements.txt b/requirements.txt index 9f5399ffd..b05f94dc2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,18 +25,18 @@ awscli-cwlogs>=1.4,<1.5 # Putting upgrade on hold due to v1.0.0 using sha512 instead of sha1 by default itsdangerous==0.24 # pyup: <1.0.0 -git+https://github.com/alphagov/notifications-utils.git@34.1.0#egg=notifications-utils==34.1.0 +git+https://github.com/alphagov/notifications-utils.git@35.0.0#egg=notifications-utils==35.0.0 git+https://github.com/alphagov/govuk-frontend-jinja.git@v0.3.0-alpha#egg=govuk-frontend-jinja==0.3.0-alpha ## The following requirements were added by pip freeze: -awscli==1.16.251 +awscli==1.16.255 bleach==3.1.0 -boto3==1.6.16 -botocore==1.12.241 +boto3==1.9.221 +botocore==1.12.245 certifi==2019.9.11 chardet==3.0.4 Click==7.0 -colorama==0.3.9 +colorama==0.4.1 dnspython==1.16.0 docopt==0.6.2 docutils==0.15.2 @@ -46,7 +46,7 @@ future==0.17.1 greenlet==0.4.15 idna==2.8 jdcal==1.4.1 -Jinja2==2.10.1 +Jinja2==2.10.3 jmespath==0.9.4 lml==0.0.9 lxml==4.4.1 @@ -55,14 +55,14 @@ mistune==0.8.4 monotonic==1.5 openpyxl==2.5.14 orderedset==2.0.1 -phonenumbers==8.10.13 +phonenumbers==8.10.17 pyasn1==0.4.7 pyexcel-ezodf==0.3.4 PyJWT==1.7.1 PyPDF2==1.26.0 python-dateutil==2.8.0 python-json-logger==0.1.11 -PyYAML==4.2b1 +PyYAML==5.1.2 redis==3.3.8 requests==2.22.0 rsa==3.4.2 diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py index c63c9fe29..6b5ff2921 100644 --- a/tests/app/main/views/test_send.py +++ b/tests/app/main/views/test_send.py @@ -2735,6 +2735,56 @@ def test_check_messages_shows_trial_mode_error_for_letters( assert page.select_one('.table-field-index a').text == '3' +@pytest.mark.parametrize('number_of_rows, expected_error_message', [ + (1, 'This letter is'), + (11, 'These letters are'), # TODO: Pluralise too many pages error message for multiple letters +]) +def test_check_messages_does_not_allow_to_send_letter_longer_than_10_pages( + client_request, + api_user_active, + mock_get_service_letter_template, + mock_has_permissions, + mock_get_users_by_service, + mock_get_service_statistics, + mock_get_job_doesnt_exist, + mock_get_jobs, + mock_s3_set_metadata, + fake_uuid, + mocker, + mock_get_live_service, + number_of_rows, + expected_error_message, +): + mocker.patch('app.main.views.send.s3download', return_value='\n'.join( + ['address_line_1,address_line_2,postcode,'] + + ['First Last, 123 Street, SW1 1AA'] * number_of_rows + )) + mocker.patch( + 'app.main.views.send.get_page_count_for_letter', + return_value=11, + ) + + with client_request.session_transaction() as session: + session['file_uploads'] = { + fake_uuid: { + 'template_id': '', + } + } + + page = client_request.get( + 'main.check_messages', + service_id=SERVICE_ONE_ID, + template_id=fake_uuid, + upload_id=fake_uuid, + _test_page_title=False, + ) + + assert page.select('#letter-too-long') + + assert len(page.select('.letter img')) == 10 # if letter longer than 10 pages, only 10 first pages are displayed + assert not page.select('[type=submit]') + + def test_check_messages_shows_data_errors_before_trial_mode_errors_for_letters( mocker, client_request, @@ -3066,6 +3116,45 @@ def test_send_one_off_letter_errors_in_trial_mode( assert page.select_one('a[download]').text == 'Download as a PDF' +def test_send_one_off_letter_errors_if_letter_longer_than_10_pages( + client_request, + mocker, + mock_get_live_service, + mock_get_service_letter_template, + mock_has_permissions, + fake_uuid, + mock_get_users_by_service, + mock_get_service_statistics, + mock_get_job_doesnt_exist, + mock_s3_set_metadata, +): + + mocker.patch( + 'app.main.views.send.get_page_count_for_letter', + return_value=11, + ) + + with client_request.session_transaction() as session: + session['recipient'] = None + session['placeholders'] = { + 'address_line_1': 'First Last', + 'address_line_2': '123 Street', + 'postcode': 'SW1 1AA', + } + + page = client_request.get( + 'main.check_notification', + service_id=SERVICE_ONE_ID, + template_id=fake_uuid, + _test_page_title=False, + ) + + assert page.select('#letter-too-long') + assert len(page.select('.letter img')) == 10 + + assert not page.select('[type=submit]') + + def test_check_messages_shows_over_max_row_error( client_request, mock_get_users_by_service, diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index b9e0c041e..924636805 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -521,6 +521,31 @@ def test_view_non_letter_template_does_not_display_postage( assert "Postage" not in page.text +def test_view_letter_template_does_not_display_send_button_if_template_over_10_pages_long( + client_request, + service_one, + mock_get_service_templates, + mock_get_template_folders, + single_letter_contact_block, + mock_has_jobs, + active_user_with_permissions, + mocker, + fake_uuid, +): + mocker.patch('app.main.views.templates.get_page_count_for_letter', return_value=11) + client_request.login(active_user_with_permissions) + mock_get_service_letter_template(mocker, postage="second") + page = client_request.get( + 'main.view_template', + service_id=SERVICE_ONE_ID, + template_id=fake_uuid, + _test_page_title=False, + ) + + assert "Send" not in page.text + assert page.select('#letter-too-long') + + def test_edit_letter_template_postage_page_displays_correctly( client_request, service_one,