Files
notifications-admin/app/main/views/templates.py
Chris Hill-Scott d0f90eac7e Add an endpoint for generating a PDF of a letter
Previewing a letter is different to previewing an email or text message
because:

- a letter has a layout
- the layout is fixed, ie it doesn’t depend on the user’s device
- the ‘send yourself a test’ feature won’t be as useful because it has
  a lead time, so the feedback loop will be much longer

For these reasons a HTML-only preview of the letter won’t be enough (we
don’t think). A PDF is more appropriate because:

- it can replicate the layout of the letter exactly
- it is a print format, so the user could even print themselves a copy
  locally to get a feel for how it will look

This commit makes use of Flask WeasyPrint [1] to take a HTML
representation of the letter, convert it to a PDF and serve it back to
the user.

The actual work to generate the HTML and specify the layout is done in
utils, same as we do for rendering other messages.

1. https://pythonhosted.org/Flask-WeasyPrint/
2016-12-13 10:34:18 +00:00

290 lines
9.9 KiB
Python

from datetime import datetime, timedelta
from string import ascii_uppercase
from flask import request, render_template, redirect, url_for, flash, abort
from flask_login import login_required
from flask_weasyprint import HTML, render_pdf
from dateutil.parser import parse
from notifications_utils.template import Template
from notifications_utils.recipients import first_column_headings
from notifications_utils.renderers import LetterPreview
from notifications_python_client.errors import HTTPError
from app.main import main
from app.utils import user_has_permissions, get_renderer
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,
'letter': LetterTemplateForm
}
page_headings = {
'email': 'email',
'sms': 'text message'
}
@main.route("/services/<service_id>/templates/<template_id>")
@login_required
@user_has_permissions(
'view_activity',
'send_texts',
'send_emails',
'manage_templates',
'manage_api_keys',
admin_override=True, any_=True
)
def view_template(service_id, template_id):
template = Template(
service_api_client.get_service_template(service_id, template_id)['data']
)
template.renderer = get_renderer(
template.template_type, current_service, show_recipient=False, expand_emails=True
)
return render_template(
'views/templates/template.html',
template=template
)
@main.route("/services/<service_id>/templates/<template_id>.pdf")
@login_required
@user_has_permissions('view_activity', admin_override=True)
def view_letter_template_as_pdf(service_id, template_id):
template = Template(
service_api_client.get_service_template(service_id, template_id)['data'],
renderer=LetterPreview()
)
return render_pdf(HTML(string=template.rendered))
@main.route("/services/<service_id>/templates/<template_id>/version/<int:version>")
@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(service_id, template_id, version):
template = Template(
service_api_client.get_service_template(service_id, template_id, version)['data']
)
template.renderer = get_renderer(
template.template_type, current_service, show_recipient=False, expand_emails=True
)
return render_template(
'views/templates/template_history.html',
template=template
)
@main.route("/services/<service_id>/templates/add-<template_type>", methods=['GET', 'POST'])
@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', '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():
try:
service_api_client.create_service_template(
form.name.data,
template_type,
form.template_content.data,
service_id,
form.subject.data if hasattr(form, 'subject') else None
)
except HTTPError as e:
if e.status_code == 400:
if 'content' in e.message and any(['character count greater than' in x for x in e.message['content']]):
form.template_content.errors.extend(e.message['content'])
else:
raise e
else:
raise e
else:
return redirect(
url_for('.choose_template', service_id=service_id, template_type=template_type)
)
return render_template(
'views/edit-{}-template.html'.format(template_type),
form=form,
template_type=template_type,
heading_action='Add'
)
@main.route("/services/<service_id>/templates/<template_id>/edit", methods=['GET', 'POST'])
@login_required
@user_has_permissions('manage_templates', admin_override=True)
def edit_service_template(service_id, template_id):
template = service_api_client.get_service_template(service_id, template_id)['data']
template['template_content'] = template['content']
form = form_objects[template['template_type']](**template)
if form.validate_on_submit():
subject = form.subject.data if hasattr(form, 'subject') else None
new_template = Template({
'name': form.name.data,
'content': form.template_content.data,
'subject': subject,
'template_type': template['template_type'],
'id': template['id']
})
template_change = Template(template).compare_to(new_template)
if template_change.has_different_placeholders and not request.form.get('confirm'):
return render_template(
'views/templates/breaking-change.html',
template_change=template_change,
new_template=new_template,
column_headings=list(ascii_uppercase[:len(new_template.placeholders) + 1]),
example_rows=[
first_column_headings[new_template.template_type] + list(new_template.placeholders),
get_example_csv_rows(new_template),
get_example_csv_rows(new_template)
],
form=form
)
try:
service_api_client.update_service_template(
template_id,
form.name.data,
template['template_type'],
form.template_content.data,
service_id,
subject
)
except HTTPError as e:
if e.status_code == 400:
if 'content' in e.message and any(['character count greater than' in x for x in e.message['content']]):
form.template_content.errors.extend(e.message['content'])
else:
raise e
else:
raise e
else:
return redirect(url_for(
'.view_template',
service_id=service_id,
template_id=template_id
))
return render_template(
'views/edit-{}-template.html'.format(template['template_type']),
form=form,
template_id=template_id,
template_type=template['template_type'],
heading_action='Edit'
)
@main.route("/services/<service_id>/templates/<template_id>/delete", methods=['GET', 'POST'])
@login_required
@user_has_permissions('manage_templates', admin_override=True)
def delete_service_template(service_id, template_id):
template = service_api_client.get_service_template(service_id, template_id)['data']
if request.method == 'POST':
service_api_client.delete_service_template(service_id, template_id)
return redirect(url_for(
'.choose_template',
service_id=service_id,
template_type=template['template_type']
))
template['template_content'] = template['content']
form = form_objects[template['template_type']](**template)
try:
last_used_notification = template_statistics_client.get_template_statistics_for_template(
service_id, template['id']
)
message = '{} was last used {} ago'.format(
last_used_notification['template']['name'],
get_human_readable_delta(
parse(last_used_notification['created_at']).replace(tzinfo=None),
datetime.utcnow())
)
except HTTPError as e:
if e.status_code == 404:
message = '{} has never been used'.format(template['name'])
else:
raise e
flash('{}. Are you sure you want to delete it?'.format(message), 'delete')
return render_template(
'views/edit-{}-template.html'.format(template['template_type']),
h1='Edit template',
form=form,
template_id=template_id)
@main.route('/services/<service_id>/templates/<template_id>/versions')
@login_required
@user_has_permissions(
'view_activity',
'send_texts',
'send_emails',
'manage_templates',
'manage_api_keys',
admin_override=True,
any_=True
)
def view_template_versions(service_id, template_id):
versions = []
for template in service_api_client.get_service_template_versions(service_id, template_id)['data']:
template = Template(template)
template.renderer = get_renderer(
template.template_type, current_service, show_recipient=False, expand_emails=True
)
versions.append(template)
return render_template(
'views/templates/choose_history.html',
versions=versions
)
def get_last_use_message(template_name, template_statistics):
try:
most_recent_use = max(
parse(template_stats['updated_at']).replace(tzinfo=None)
for template_stats in template_statistics
)
except ValueError:
return '{} has never been used'.format(template_name)
return '{} was last used {} ago'.format(
template_name,
get_human_readable_delta(most_recent_use, datetime.utcnow())
)
def get_human_readable_delta(from_time, until_time):
delta = until_time - from_time
if delta < timedelta(seconds=60):
return 'under a minute'
elif delta < timedelta(hours=1):
minutes = int(delta.seconds / 60)
return '{} minute{}'.format(minutes, '' if minutes == 1 else 's')
elif delta < timedelta(days=1):
hours = int(delta.seconds / 3600)
return '{} hour{}'.format(hours, '' if hours == 1 else 's')
else:
days = delta.days
return '{} day{}'.format(days, '' if days == 1 else 's')