diff --git a/app/main/views/index.py b/app/main/views/index.py index c68296f9a..1247c0aa2 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -10,9 +10,9 @@ from flask_login import current_user, login_required from notifications_utils.international_billing_rates import ( INTERNATIONAL_BILLING_RATES, ) -from notifications_utils.template import HTMLEmailTemplate +from notifications_utils.template import HTMLEmailTemplate, LetterImageTemplate -from app import email_branding_client +from app import email_branding_client, letter_branding_client from app.main import main from app.main.forms import FieldWithNoneOption, SearchByNameForm from app.main.views.sub_navigation_dictionaries import features_nav @@ -174,6 +174,35 @@ def email_template(): return resp +@main.route('/_letter') +def letter_template(): + branding_style = request.args.get('branding_style') + + if branding_style == FieldWithNoneOption.NONE_OPTION_VALUE: + branding_style = None + + if branding_style: + filename = letter_branding_client.get_letter_branding(branding_style)['filename'] + else: + filename = 'no-branding' + + template = {'subject': '', 'content': ''} + image_url = url_for('main.letter_branding_preview_image', filename=filename) + + template_image = str(LetterImageTemplate( + template, + image_url=image_url, + page_count=1, + )) + + resp = make_response( + render_template('views/service-settings/letter-preview.html', template=template_image) + ) + + resp.headers['X-Frame-Options'] = 'SAMEORIGIN' + return resp + + @main.route('/documentation') def documentation(): return render_template('views/documentation.html') diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py index 69b477de2..063c8730a 100644 --- a/app/main/views/service_settings.py +++ b/app/main/views/service_settings.py @@ -810,6 +810,28 @@ def service_set_letter_branding(service_id): current_branding=current_service.letter_branding_id, ) + if form.validate_on_submit(): + return redirect(url_for( + '.service_preview_letter_branding', + service_id=service_id, + branding_style=form.branding_style.data, + )) + + return render_template( + 'views/service-settings/set-letter-branding.html', + form=form, + search_form=SearchByNameForm() + ) + + +@main.route("/services//service-settings/preview-letter-branding", methods=['GET', 'POST']) +@login_required +@user_is_platform_admin +def service_preview_letter_branding(service_id): + branding_style = request.args.get('branding_style') + + form = ServicePreviewBranding(branding_style=branding_style) + if form.validate_on_submit(): current_service.update( letter_branding=form.branding_style.data @@ -817,9 +839,10 @@ def service_set_letter_branding(service_id): return redirect(url_for('.service_settings', service_id=service_id)) return render_template( - 'views/service-settings/set-letter-branding.html', + 'views/service-settings/preview-letter-branding.html', form=form, - search_form=SearchByNameForm() + service_id=service_id, + action=url_for('main.service_preview_letter_branding', service_id=service_id), ) diff --git a/app/main/views/templates.py b/app/main/views/templates.py index 25d8dadba..3d72eb131 100644 --- a/app/main/views/templates.py +++ b/app/main/views/templates.py @@ -36,6 +36,7 @@ from app.utils import ( get_template, should_skip_template_page, user_has_permissions, + user_is_platform_admin, ) form_objects = { @@ -222,6 +223,34 @@ def view_letter_template_preview(service_id, template_id, filetype): return TemplatePreview.from_database_object(db_template, filetype, page=request.args.get('page')) +@main.route("/templates/letter-preview-image/") +@login_required +@user_is_platform_admin +def letter_branding_preview_image(filename): + template = { + 'subject': 'An example letter', + 'content': ( + 'Lorem Ipsum is simply dummy text of the printing and typesetting ' + 'industry.\n\nLorem Ipsum has been the industry’s standard dummy ' + 'text ever since the 1500s, when an unknown printer took a galley ' + 'of type and scrambled it to make a type specimen book.\n\n' + '# History\n\nIt has survived not only\n\n' + '* five centuries\n' + '* but also the leap into electronic typesetting\n\n' + 'It was popularised in the 1960s with the release of Letraset ' + 'sheets containing Lorem Ipsum passages, and more recently with ' + 'desktop publishing software like Aldus PageMaker including ' + 'versions of Lorem Ipsum.\n\n' + 'The point of using Lorem Ipsum is that it has a more-or-less ' + 'normal distribution of letters, as opposed to using ‘Content ' + 'here, content here’, making it look like readable English.' + ) + } + filename = None if filename == 'no-branding' else filename + + return TemplatePreview.from_example_template(template, filename) + + def _view_template_version(service_id, template_id, version, letters_as_pdf=False): return dict(template=get_template( service_api_client.get_service_template(service_id, template_id, version=version)['data'], diff --git a/app/navigation.py b/app/navigation.py index befb5e046..69a5814ca 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -173,6 +173,8 @@ class HeaderNavigation(Navigation): 'information_security', 'invite_org_user', 'invite_user', + 'letter_branding_preview_image', + 'letter_template', 'link_service_to_organisation', 'manage_org_users', 'manage_template_folder', @@ -228,6 +230,7 @@ class HeaderNavigation(Navigation): 'service_name_change', 'service_name_change_confirm', 'service_preview_email_branding', + 'service_preview_letter_branding', 'service_set_auth_type', 'service_set_channel', 'service_set_contact_link', @@ -235,6 +238,7 @@ class HeaderNavigation(Navigation): 'service_set_inbound_number', 'service_set_inbound_sms', 'service_set_international_sms', + 'service_set_letter_branding', 'service_set_letter_contact_block', 'service_set_letters', 'service_set_reply_to_email', @@ -248,7 +252,6 @@ class HeaderNavigation(Navigation): 'service_switch_research_mode', 'services_or_dashboard', 'set_free_sms_allowance', - 'service_set_letter_branding', 'set_organisation_type', 'set_sender', 'set_template_sender', @@ -348,6 +351,7 @@ class MainNavigation(Navigation): 'service_name_change', 'service_name_change_confirm', 'service_preview_email_branding', + 'service_preview_letter_branding', 'service_set_auth_type', 'service_set_channel', 'service_set_contact_link', @@ -436,7 +440,9 @@ class MainNavigation(Navigation): 'integration_testing', 'invite_org_user', 'letter_branding', + 'letter_branding_preview_image', 'live_services', + 'letter_template', 'manage_org_users', 'new_password', 'old_integration_testing', @@ -622,6 +628,8 @@ class CaseworkNavigation(Navigation): 'invite_org_user', 'invite_user', 'letter_branding', + 'letter_branding_preview_image', + 'letter_template', 'link_service_to_organisation', 'live_services', 'manage_org_users', @@ -682,6 +690,7 @@ class CaseworkNavigation(Navigation): 'service_name_change', 'service_name_change_confirm', 'service_preview_email_branding', + 'service_preview_letter_branding', 'service_set_auth_type', 'service_set_channel', 'service_set_contact_link', @@ -854,6 +863,8 @@ class OrgNavigation(Navigation): 'integration_testing', 'invite_user', 'letter_branding', + 'letter_branding_preview_image', + 'letter_template', 'link_service_to_organisation', 'live_services', 'manage_template_folder', @@ -914,6 +925,7 @@ class OrgNavigation(Navigation): 'service_name_change', 'service_name_change_confirm', 'service_preview_email_branding', + 'service_preview_letter_branding', 'service_set_auth_type', 'service_set_channel', 'service_set_contact_link', diff --git a/app/template_previews.py b/app/template_previews.py index b154bcc29..2dbcba122 100644 --- a/app/template_previews.py +++ b/app/template_previews.py @@ -24,6 +24,21 @@ class TemplatePreview: ) return (resp.content, resp.status_code, resp.headers.items()) + @classmethod + def from_example_template(cls, template, filename): + data = { + 'letter_contact_block': template.get('reply_to_text'), + 'template': template, + 'values': None, + 'filename': filename + } + resp = requests.post( + '{}/preview.png'.format(current_app.config['TEMPLATE_PREVIEW_API_HOST']), + json=data, + headers={'Authorization': 'Token {}'.format(current_app.config['TEMPLATE_PREVIEW_API_KEY'])} + ) + return (resp.content, resp.status_code, resp.headers.items()) + @classmethod def from_utils_template(cls, template, filetype, page=None): return cls.from_database_object( diff --git a/app/templates/views/service-settings/letter-preview.html b/app/templates/views/service-settings/letter-preview.html new file mode 100644 index 000000000..526b71065 --- /dev/null +++ b/app/templates/views/service-settings/letter-preview.html @@ -0,0 +1,31 @@ + + + + + + Letter preview + + + + + + + {{ template }} + + + diff --git a/app/templates/views/service-settings/preview-letter-branding.html b/app/templates/views/service-settings/preview-letter-branding.html new file mode 100644 index 000000000..4c84ce508 --- /dev/null +++ b/app/templates/views/service-settings/preview-letter-branding.html @@ -0,0 +1,24 @@ +{% extends "views/platform-admin/_base_template.html" %} +{% from "components/form.html" import form_wrapper %} +{% block per_page_title %} + Preview letter branding +{% endblock %} + +{% block platform_admin_content %} + +

Preview letter branding

+
+
+ + {% call form_wrapper(action=action) %} +
+ {{ form.hidden_tag() }} + +
+ {% endcall %} +
+
+{% endblock %} diff --git a/app/templates/views/service-settings/set-letter-branding.html b/app/templates/views/service-settings/set-letter-branding.html index d9aad44c1..c84047a3a 100644 --- a/app/templates/views/service-settings/set-letter-branding.html +++ b/app/templates/views/service-settings/set-letter-branding.html @@ -11,12 +11,20 @@ {% block maincolumn_content %}

Set letter branding

- {% call form_wrapper() %} - {{ live_search(target_selector='.multiple-choice', show=True, form=search_form, label='Search by name') }} - {{ radios(form.branding_style, hide_legend=True) }} + {% call form_wrapper(data_kwargs={'preview-type': 'letter'}) %} +
+
+
+
+
+
+ {{ live_search(target_selector='.multiple-choice', show=True, form=search_form, label='Search by name') }} + {{ radios(form.branding_style, hide_legend=True) }} +
+
{{ page_footer( - 'Save', + 'Preview', back_link=url_for('.service_settings', service_id=current_service.id), back_link_text='Back to settings' ) }} diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index 965c5b7e4..8c0884ffa 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -4,6 +4,7 @@ import pytest from bs4 import BeautifulSoup from flask import url_for +from app.main.forms import FieldWithNoneOption from tests.conftest import ( active_user_with_permissions, normalize_spaces, @@ -288,3 +289,41 @@ def test_email_branding_preview( **extra_args ) assert mock_get_email_branding.called is email_branding_retrieved + + +@pytest.mark.parametrize('branding_style, filename', [ + ('hm-government', 'hm-government'), + (None, 'no-branding'), + (FieldWithNoneOption.NONE_OPTION_VALUE, 'no-branding') +]) +def test_letter_template_preview_links_to_the_correct_image( + client_request, + mocker, + mock_get_letter_branding_by_id, + branding_style, + filename, +): + page = client_request.get( + 'main.letter_template', + _test_page_title=False, + branding_style=branding_style + ) + + image_link = page.find('img')['src'] + + assert image_link == url_for( + 'main.letter_branding_preview_image', + filename=filename, + page=1 + ) + + +def test_letter_template_preview_headers( + client, + mock_get_letter_branding_by_id, +): + response = client.get( + url_for('main.letter_template', branding_style='hm-government') + ) + + assert response.headers.get('X-Frame-Options') == 'SAMEORIGIN' diff --git a/tests/app/main/views/test_service_settings.py b/tests/app/main/views/test_service_settings.py index 5df553da4..552c9e75b 100644 --- a/tests/app/main/views/test_service_settings.py +++ b/tests/app/main/views/test_service_settings.py @@ -2175,7 +2175,44 @@ def test_service_set_letter_branding_prepopulates( (str(UUID(int=1)), str(UUID(int=1))), ('__NONE__', None), ]) -def test_service_set_letter_branding_saves( +def test_service_set_letter_branding_redirects_to_preview_page_when_form_submitted( + logged_in_platform_admin_client, + service_one, + mock_get_all_letter_branding, + selected_letter_branding, + expected_post_data +): + response = logged_in_platform_admin_client.post( + url_for('main.service_set_letter_branding', service_id=service_one['id']), + data={'branding_style': selected_letter_branding}, + ) + assert response.status_code == 302 + assert response.location == url_for( + 'main.service_preview_letter_branding', + service_id=service_one['id'], + branding_style=expected_post_data, + _external=True) + + +def test_service_preview_letter_branding_shows_preview_letter( + logged_in_platform_admin_client, + service_one, + mock_get_all_letter_branding, +): + response = logged_in_platform_admin_client.get( + url_for('main.service_preview_letter_branding', service_id=service_one['id'], branding_style='hm-government') + ) + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + + assert response.status_code == 200 + assert page.find('iframe')['src'] == url_for('main.letter_template', branding_style='hm-government') + + +@pytest.mark.parametrize('selected_letter_branding, expected_post_data', [ + (str(UUID(int=1)), str(UUID(int=1))), + ('__NONE__', None), +]) +def test_service_preview_letter_branding_saves( logged_in_platform_admin_client, service_one, mock_update_service, @@ -2184,7 +2221,7 @@ def test_service_set_letter_branding_saves( expected_post_data ): response = logged_in_platform_admin_client.post( - url_for('main.service_set_letter_branding', service_id=service_one['id']), + url_for('main.service_preview_letter_branding', service_id=service_one['id']), data={'branding_style': selected_letter_branding} ) assert response.status_code == 302 diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index 175cabc77..725c15489 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -798,6 +798,28 @@ def test_dont_show_preview_letter_templates_for_bad_filetype( assert mock_get_service_template.called is False +@pytest.mark.parametrize('original_filename, new_filename', [ + ('geo', 'geo'), + ('no-branding', None) +]) +def test_letter_branding_preview_image( + mocker, + logged_in_platform_admin_client, + original_filename, + new_filename, +): + mocked_preview = mocker.patch( + 'app.main.views.templates.TemplatePreview.from_example_template', + return_value='foo' + ) + resp = logged_in_platform_admin_client.get( + url_for('.letter_branding_preview_image', filename=original_filename) + ) + + mocked_preview.assert_called_with(ANY, new_filename) + assert resp.get_data(as_text=True) == 'foo' + + def test_choosing_to_copy_redirects( client_request, service_one, diff --git a/tests/app/test_template_previews.py b/tests/app/test_template_previews.py index fdf78f345..dbfcbd159 100644 --- a/tests/app/test_template_previews.py +++ b/tests/app/test_template_previews.py @@ -102,3 +102,20 @@ def test_page_count_unpacks_from_json_response( assert partial_call({'template_type': 'letter'}) == 99 mock_template_preview.assert_called_once_with(*expected_template_preview_args) + + +def test_from_example_template_makes_request(mocker): + request_mock = mocker.patch('app.template_previews.requests.post') + template = {} + filename = 'geo' + + TemplatePreview.from_example_template(template, filename) + + request_mock.assert_called_once_with( + 'http://localhost:9999/preview.png', + headers={'Authorization': 'Token my-secret-key'}, + json={'values': None, + 'template': template, + 'filename': filename, + 'letter_contact_block': None} + )