Files
notifications-admin/app/main/views/templates.py
Chris Hill-Scott 7e185f67b1 Fix HTML showing up on breaking change page
The breaking change page temporarily holds the changes in hidden inputs
on the page. The messages content it gets from the `.content` property
on the subject. This is raw and not transformed in any way, so fine.

For the subject it gets the value from the `.subject` attribute on the
template. For email templates, this will be transformed to highlight
placeholders with `<span class='placeholder'>…`. This means that when
the change is confirmed, it’s this encoded version that gets sent to the
API. Which is bad, because we then save `<span class='placeholder'>` in
the database.

This commit changes the page to look at the `._subject` attribute
instead, which is the internal, untransformed version of the subject.
2017-03-07 15:11:37 +00:00

415 lines
14 KiB
Python

from datetime import datetime, timedelta
from string import ascii_uppercase
from flask import request, render_template, redirect, url_for, flash, abort, send_file
from flask_login import login_required, current_user
from flask_weasyprint import HTML, render_pdf
from dateutil.parser import parse
from notifications_utils.field import escape_html
from notifications_utils.template import LetterPreviewTemplate
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, get_template, png_from_pdf
from app.main.forms import ChooseTemplateType, 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", methods=['GET'])
@login_required
@user_has_permissions(
'view_activity',
'send_texts',
'send_emails',
'manage_templates',
'manage_api_keys',
admin_override=True,
any_=True,
)
def choose_template(service_id):
return render_template(
'views/templates/choose.html',
templates=[
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']
if should_show_template(template['template_type'])
],
)
@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):
return render_template(
'views/templates/template.html',
template=get_template(
service_api_client.get_service_template(service_id, template_id)['data'],
current_service,
expand_emails=True,
letter_preview_url=url_for('.view_template', service_id=service_id, template_id=template_id)
)
)
@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):
return render_pdf(HTML(string=str(
LetterPreviewTemplate(
service_api_client.get_service_template(service_id, template_id)['data'],
contact_block=current_service['letter_contact_block'],
)
)))
@main.route("/services/<service_id>/templates/<template_id>.png")
@login_required
@user_has_permissions('view_activity', admin_override=True)
def view_letter_template_as_png(service_id, template_id):
return send_file(**png_from_pdf(
view_letter_template_as_pdf(service_id, template_id)
))
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'],
current_service,
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
))
@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):
return render_template(
'views/templates/template_history.html',
**_view_template_version(service_id=service_id, template_id=template_id, version=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'],
contact_block=current_service['letter_contact_block'],
)
)))
@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):
return send_file(**png_from_pdf(
view_template_version_as_pdf(service_id, template_id, version)
))
@main.route("/services/<service_id>/templates/add", methods=['GET', 'POST'])
@login_required
@user_has_permissions('manage_templates', admin_override=True)
def add_template_by_type(service_id):
form = ChooseTemplateType(
include_letters=current_service['can_send_letters']
)
if form.validate_on_submit():
return redirect(url_for(
'.add_service_template',
service_id=service_id,
template_type=form.template_type.data,
))
return render_template('views/templates/add.html', form=form)
@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():
if form.process_type.data == 'priority':
abort_403_if_not_admin_user()
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,
form.process_type.data
)
except HTTPError as e:
if (
e.status_code == 400 and
'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:
return redirect(
url_for('.choose_template', service_id=service_id)
)
return render_template(
'views/edit-{}-template.html'.format(template_type),
form=form,
template_type=template_type,
heading_action='Add'
)
def abort_403_if_not_admin_user():
if not current_user.has_permissions([], admin_override=True):
abort(403)
@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():
if form.process_type.data != template['process_type']:
abort_403_if_not_admin_user()
subject = escape_html(form.subject.data) if hasattr(form, 'subject') else None
new_template = get_template({
'name': form.name.data,
'content': form.template_content.data,
'subject': subject,
'template_type': template['template_type'],
'id': template['id'],
'process_type': form.process_type.data
}, current_service)
template_change = get_template(template, current_service).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,
form.process_type.data
)
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):
return render_template(
'views/templates/choose_history.html',
versions=[
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']
]
)
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')
def should_show_template(template_type):
return (
template_type != 'letter' or
current_service['can_send_letters']
)