Add remaining endpoints for PDFs and PNGs

Right now we can show what a letter template looks like as a PDF or PNG.

This commit completes the work so this is also possible when:

- showing a template with the placeholders replaced
- showing any version of a template

Also removes dependency on `Exception().message`, which was deprecated
in Python 2.6. See
97f82d565f
for full details.
This commit is contained in:
Chris Hill-Scott
2016-12-20 14:38:34 +00:00
parent a159f351a8
commit 559433c5d2
8 changed files with 214 additions and 33 deletions

View File

@@ -96,7 +96,7 @@ class UKMobileNumber(TelField):
try: try:
validate_phone_number(self.data) validate_phone_number(self.data)
except InvalidPhoneError as e: except InvalidPhoneError as e:
raise ValidationError(e.message) raise ValidationError(str(e))
def mobile_number(): def mobile_number():

View File

@@ -1,9 +1,11 @@
import itertools import itertools
from string import ascii_uppercase from string import ascii_uppercase
from io import BytesIO
from contextlib import suppress from contextlib import suppress
from zipfile import BadZipFile from zipfile import BadZipFile
from xlrd.biffh import XLRDError from xlrd.biffh import XLRDError
from wand.image import Image
from flask import ( from flask import (
request, request,
@@ -13,10 +15,13 @@ from flask import (
flash, flash,
abort, abort,
session, session,
current_app current_app,
send_file,
) )
from flask_login import login_required, current_user from flask_login import login_required, current_user
from flask_weasyprint import HTML, render_pdf
from notifications_utils.columns import Columns from notifications_utils.columns import Columns
from notifications_utils.recipients import RecipientCSV, first_column_headings, validate_and_format_phone_number from notifications_utils.recipients import RecipientCSV, first_column_headings, validate_and_format_phone_number
@@ -87,7 +92,11 @@ def choose_template(service_id, template_type):
return render_template( return render_template(
'views/templates/choose.html', 'views/templates/choose.html',
templates=[ templates=[
get_template(template, current_service) get_template(
template,
current_service,
letter_preview_url=url_for('.view_template', service_id=service_id, template_id=template['id']),
)
for template in service_api_client.get_service_templates(service_id)['data'] for template in service_api_client.get_service_templates(service_id)['data']
if template['template_type'] == template_type if template['template_type'] == template_type
], ],
@@ -104,7 +113,8 @@ def send_messages(service_id, template_id):
template = get_template( template = get_template(
service_api_client.get_service_template(service_id, template_id)['data'], service_api_client.get_service_template(service_id, template_id)['data'],
current_service, current_service,
show_recipient=True show_recipient=True,
letter_preview_url=url_for('.view_template', service_id=service_id, template_id=template_id),
) )
form = CsvUploadForm() form = CsvUploadForm()
@@ -165,7 +175,8 @@ def send_test(service_id, template_id):
template = get_template( template = get_template(
service_api_client.get_service_template(service_id, template_id)['data'], service_api_client.get_service_template(service_id, template_id)['data'],
current_service, current_service,
show_recipient=True show_recipient=True,
letter_preview_url=url_for('.view_template', service_id=service_id, template_id=template_id),
) )
if len(template.placeholders) == 0 or request.method == 'POST': if len(template.placeholders) == 0 or request.method == 'POST':
@@ -208,7 +219,9 @@ def send_from_api(service_id, template_id):
return render_template( return render_template(
'views/send-from-api.html', 'views/send-from-api.html',
template=get_template( template=get_template(
service_api_client.get_service_template(service_id, template_id)['data'], current_service service_api_client.get_service_template(service_id, template_id)['data'],
current_service,
letter_preview_url=url_for('.view_template', service_id=service_id, template_id=template_id)
) )
) )
@@ -233,7 +246,13 @@ def _check_messages(service_id, template_type, upload_id, letters_as_pdf=False):
session['upload_data'].get('template_id') session['upload_data'].get('template_id')
)['data'], )['data'],
current_service, current_service,
show_recipient=True show_recipient=True,
letter_preview_url=url_for(
'.check_messages',
service_id=service_id,
template_type=template_type,
upload_id=upload_id
) if not letters_as_pdf else None
) )
recipients = RecipientCSV( recipients = RecipientCSV(
@@ -304,6 +323,30 @@ def check_messages(service_id, template_type, upload_id):
) )
@main.route("/services/<service_id>/<template_type>/check/<upload_id>.pdf", methods=['GET'])
@login_required
@user_has_permissions('send_texts', 'send_emails', 'send_letters')
def check_messages_as_pdf(service_id, template_type, upload_id):
template = _check_messages(
service_id, template_type, upload_id, letters_as_pdf=True
)['template']
return render_pdf(HTML(string=str(template)))
@main.route("/services/<service_id>/<template_type>/check/<upload_id>.png", methods=['GET'])
@login_required
@user_has_permissions('send_texts', 'send_emails', 'send_letters')
def check_messages_as_png(service_id, template_type, upload_id):
output = BytesIO()
with Image(
blob=check_messages_as_pdf(service_id, template_type, upload_id).get_data()
) as image:
with image.convert('png') as converted:
converted.save(file=output)
output.seek(0)
return send_file(output, mimetype='image/png')
@main.route("/services/<service_id>/<template_type>/check/<upload_id>", methods=['POST']) @main.route("/services/<service_id>/<template_type>/check/<upload_id>", methods=['POST'])
@login_required @login_required
@user_has_permissions('send_texts', 'send_emails', 'send_letters') @user_has_permissions('send_texts', 'send_emails', 'send_letters')

View File

@@ -47,7 +47,8 @@ def view_template(service_id, template_id):
template=get_template( template=get_template(
service_api_client.get_service_template(service_id, template_id)['data'], service_api_client.get_service_template(service_id, template_id)['data'],
current_service, current_service,
expand_emails=True expand_emails=True,
letter_preview_url=url_for('.view_template', service_id=service_id, template_id=template_id)
) )
) )
@@ -66,7 +67,7 @@ def view_letter_template_as_pdf(service_id, template_id):
@main.route("/services/<service_id>/templates/<template_id>.png") @main.route("/services/<service_id>/templates/<template_id>.png")
@login_required @login_required
@user_has_permissions('view_activity', admin_override=True) @user_has_permissions('view_activity', admin_override=True)
def view_letter_template_as_image(service_id, template_id): def view_letter_template_as_png(service_id, template_id):
output = BytesIO() output = BytesIO()
with Image( with Image(
blob=view_letter_template_as_pdf(service_id, template_id).get_data() blob=view_letter_template_as_pdf(service_id, template_id).get_data()
@@ -79,9 +80,15 @@ def view_letter_template_as_image(service_id, template_id):
def _view_template_version(service_id, template_id, version, letters_as_pdf=False): def _view_template_version(service_id, template_id, version, letters_as_pdf=False):
return dict(template=get_template( return dict(template=get_template(
service_api_client.get_service_template(service_id, template_id, version)['data'], service_api_client.get_service_template(service_id, template_id, version=version)['data'],
current_service, current_service,
expand_emails=True, expand_emails=True,
letter_preview_url=url_for(
'.view_template_version',
service_id=service_id,
template_id=template_id,
version=version,
) if not letters_as_pdf else None
)) ))
@@ -103,6 +110,47 @@ def view_template_version(service_id, template_id, version):
) )
@main.route("/services/<service_id>/templates/<template_id>/version/<int:version>.pdf")
@login_required
@user_has_permissions(
'view_activity',
'send_texts',
'send_emails',
'manage_templates',
'manage_api_keys',
admin_override=True,
any_=True
)
def view_template_version_as_pdf(service_id, template_id, version):
return render_pdf(HTML(string=str(
LetterPreviewTemplate(
service_api_client.get_service_template(service_id, template_id, version=version)['data'],
)
)))
@main.route("/services/<service_id>/templates/<template_id>/version/<int:version>.png")
@login_required
@user_has_permissions(
'view_activity',
'send_texts',
'send_emails',
'manage_templates',
'manage_api_keys',
admin_override=True,
any_=True
)
def view_template_version_as_png(service_id, template_id, version):
output = BytesIO()
with Image(
blob=view_template_version_as_pdf(service_id, template_id, version).get_data()
) as image:
with image.convert('png') as converted:
converted.save(file=output)
output.seek(0)
return send_file(output, mimetype='image/png')
@main.route("/services/<service_id>/templates/add-<template_type>", methods=['GET', 'POST']) @main.route("/services/<service_id>/templates/add-<template_type>", methods=['GET', 'POST'])
@login_required @login_required
@user_has_permissions('manage_templates', admin_override=True) @user_has_permissions('manage_templates', admin_override=True)
@@ -266,7 +314,17 @@ def view_template_versions(service_id, template_id):
return render_template( return render_template(
'views/templates/choose_history.html', 'views/templates/choose_history.html',
versions=[ versions=[
get_template(template, current_service, expand_emails=True) get_template(
template,
current_service,
expand_emails=True,
letter_preview_url=url_for(
'.view_template_version',
service_id=service_id,
template_id=template_id,
version=template['version'],
)
)
for template in service_api_client.get_service_template_versions(service_id, template_id)['data'] for template in service_api_client.get_service_template_versions(service_id, template_id)['data']
] ]
) )

View File

@@ -12,6 +12,7 @@ from notifications_utils.template import (
SMSPreviewTemplate, SMSPreviewTemplate,
EmailPreviewTemplate, EmailPreviewTemplate,
LetterPDFLinkTemplate, LetterPDFLinkTemplate,
LetterPreviewTemplate,
) )
import pyexcel import pyexcel
@@ -224,7 +225,13 @@ def is_gov_user(email_address):
return bool(re.search(email_regex, email_address.lower())) return bool(re.search(email_regex, email_address.lower()))
def get_template(template, service, show_recipient=False, expand_emails=False): def get_template(
template,
service,
show_recipient=False,
expand_emails=False,
letter_preview_url=None,
):
if 'email' == template['template_type']: if 'email' == template['template_type']:
return EmailPreviewTemplate( return EmailPreviewTemplate(
template, template,
@@ -241,7 +248,12 @@ def get_template(template, service, show_recipient=False, expand_emails=False):
show_recipient=show_recipient show_recipient=show_recipient
) )
if 'letter' == template['template_type']: if 'letter' == template['template_type']:
return LetterPDFLinkTemplate( if letter_preview_url:
template, return LetterPDFLinkTemplate(
service_id=service['id'], template,
) preview_url=letter_preview_url,
)
else:
return LetterPreviewTemplate(
template
)

View File

@@ -25,4 +25,4 @@ wand==0.4.4
git+https://github.com/alphagov/notifications-python-client.git@3.0.1#egg=notifications-python-client==3.0.1 git+https://github.com/alphagov/notifications-python-client.git@3.0.1#egg=notifications-python-client==3.0.1
git+https://github.com/alphagov/notifications-utils.git@12.1.1#egg=notifications-utils==12.1.1 git+https://github.com/alphagov/notifications-utils.git@13.0.1#egg=notifications-utils==13.0.1

View File

@@ -4,10 +4,12 @@ from glob import glob
import re import re
from itertools import repeat from itertools import repeat
from functools import partial from functools import partial
from unittest.mock import Mock, patch
import pytest import pytest
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from flask import url_for from flask import url_for
from notifications_utils.template import LetterPreviewTemplate
from tests import validate_route_permission from tests import validate_route_permission
@@ -432,6 +434,54 @@ def test_cant_start_letters_job(
mock_create_job.assert_not_called() mock_create_job.assert_not_called()
@pytest.mark.parametrize(
'view, expected_content_type',
[
('.check_messages_as_pdf', 'application/pdf'),
('.check_messages_as_png', 'image/png'),
]
)
@patch('app.utils.LetterPreviewTemplate.jinja_template.render', return_value='')
def test_should_show_preview_letter_message(
mock_letter_preview,
view,
expected_content_type,
logged_in_client,
mock_get_service_letter_template,
mock_get_users_by_service,
mock_get_detailed_service_for_today,
fake_uuid,
mocker,
):
mocker.patch(
'app.main.views.send.s3download',
return_value='\n'.join(
['address line 1, postcode'] +
['123 street, abc123']
)
)
service_id = fake_uuid
template_id = fake_uuid
with logged_in_client.session_transaction() as session:
session['upload_data'] = {
'original_file_name': 'example.csv',
'template_id': fake_uuid,
'notification_count': 1,
'valid': True
}
response = logged_in_client.get(url_for(view, service_id=service_id, template_type='letter', upload_id=fake_uuid))
assert response.status_code == 200
assert response.content_type == expected_content_type
mock_get_service_letter_template.assert_called_with(service_id, template_id)
assert mock_letter_preview.call_args[0][0]['message'] == (
'<h2>Subject</h2>\n'
'<p>Your vehicle tax is about to expire</p>'
)
def test_check_messages_should_revalidate_file_when_uploading_file( def test_check_messages_should_revalidate_file_when_uploading_file(
app_, app_,
service_one, service_one,

View File

@@ -1,4 +1,5 @@
from functools import partial from functools import partial
from itertools import repeat
from datetime import datetime from datetime import datetime
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
@@ -9,6 +10,10 @@ from freezegun import freeze_time
from notifications_python_client.errors import HTTPError from notifications_python_client.errors import HTTPError
from tests import validate_route_permission, template_json, single_notification_json from tests import validate_route_permission, template_json, single_notification_json
from tests.conftest import (
mock_get_service_email_template,
mock_get_template_version,
)
from app.main.views.templates import get_last_use_message, get_human_readable_delta from app.main.views.templates import get_last_use_message, get_human_readable_delta
@@ -40,17 +45,20 @@ def test_should_show_page_for_one_template(
service_id, template_id) service_id, template_id)
@pytest.mark.parametrize( @pytest.mark.parametrize('view_suffix, expected_content_type', [
'view, expected_content_type', ('as_pdf', 'application/pdf'),
[ ('as_png', 'image/png'),
('.view_letter_template_as_pdf', 'application/pdf'), ])
('.view_letter_template_as_image', 'image/png'), @pytest.mark.parametrize('view, extra_view_args', [
] ('.view_letter_template', {}),
) ('.view_template_version', {'version': 1}),
@patch("app.main.views.templates.LetterPreviewTemplate") ])
@patch('app.main.views.templates.LetterPreviewTemplate.jinja_template.render', return_value='foo')
def test_should_show_preview_letter_templates( def test_should_show_preview_letter_templates(
mock_letter_preview, mock_letter_preview,
view, view,
extra_view_args,
view_suffix,
expected_content_type, expected_content_type,
client, client,
api_user_active, api_user_active,
@@ -60,17 +68,27 @@ def test_should_show_preview_letter_templates(
mock_get_user, mock_get_user,
mock_get_user_by_email, mock_get_user_by_email,
mock_has_permissions, mock_has_permissions,
fake_uuid fake_uuid,
mocker
): ):
client.login(api_user_active) client.login(api_user_active)
service_id = fake_uuid service_id, template_id = repeat(fake_uuid, 2)
template_id = fake_uuid response = client.get(url_for(
response = client.get(url_for(view, service_id=service_id, template_id=template_id)) '{}_{}'.format(view, view_suffix),
service_id=service_id,
template_id=template_id,
**extra_view_args
))
assert response.status_code == 200 assert response.status_code == 200
assert response.content_type == expected_content_type assert response.content_type == expected_content_type
mock_get_service_email_template.assert_called_with(service_id, template_id) mock_get_service_email_template.assert_called_with(service_id, template_id, **extra_view_args)
assert mock_letter_preview.call_args[0][0]['content'] == "Your vehicle tax expires on ((date))" print(mock_letter_preview)
print(mock_letter_preview.call_args)
assert mock_letter_preview.call_args[0][0]['message'] == (
"<h2>Your <span class='placeholder'>((thing))</span> is due soon</h2>\n"
"<p>Your vehicle tax expires on <span class='placeholder'>((date))</span></p>"
)
def test_should_redirect_when_saving_a_template(app_, def test_should_redirect_when_saving_a_template(app_,

View File

@@ -316,7 +316,7 @@ def mock_get_service_template_with_placeholders(mocker):
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def mock_get_service_email_template(mocker): def mock_get_service_email_template(mocker):
def _create(service_id, template_id): def _get(service_id, template_id, version=None):
template = template_json( template = template_json(
service_id, service_id,
template_id, template_id,
@@ -328,7 +328,7 @@ def mock_get_service_email_template(mocker):
return {'data': template} return {'data': template}
return mocker.patch( return mocker.patch(
'app.service_api_client.get_service_template', side_effect=_create) 'app.service_api_client.get_service_template', side_effect=_get)
@pytest.fixture(scope='function') @pytest.fixture(scope='function')