mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-22 08:21:13 -05:00
* Rather than an abort 404 returned a 500 and InvalidRequest so that the error is more easily handled on the admin console. If the file is missing but expected to be there is actually an internal error for admin * Refactored the code to remove duplicate code in calls to template preview by creating a new private method which is called with specific parameters
266 lines
9.6 KiB
Python
266 lines
9.6 KiB
Python
import base64
|
|
|
|
import botocore
|
|
from flask import (
|
|
Blueprint,
|
|
current_app,
|
|
jsonify,
|
|
request)
|
|
from requests import post as requests_post
|
|
|
|
|
|
from app.dao.notifications_dao import get_notification_by_id
|
|
from app.dao.templates_dao import (
|
|
dao_update_template,
|
|
dao_create_template,
|
|
dao_redact_template,
|
|
dao_get_template_by_id_and_service_id,
|
|
dao_get_all_templates_for_service,
|
|
dao_get_template_versions,
|
|
dao_update_template_reply_to,
|
|
dao_get_template_by_id)
|
|
from notifications_utils.template import SMSMessageTemplate
|
|
from app.dao.services_dao import dao_fetch_service_by_id
|
|
from app.letters.utils import get_letter_pdf, is_precompiled_letter
|
|
from app.models import SMS_TYPE
|
|
from app.notifications.validators import service_has_permission, check_reply_to
|
|
from app.schemas import (template_schema, template_history_schema)
|
|
from app.errors import (
|
|
register_errors,
|
|
InvalidRequest
|
|
)
|
|
from app.utils import get_template_instance, get_public_notify_type_text
|
|
|
|
template_blueprint = Blueprint('template', __name__, url_prefix='/service/<uuid:service_id>/template')
|
|
|
|
|
|
register_errors(template_blueprint)
|
|
|
|
|
|
def _content_count_greater_than_limit(content, template_type):
|
|
if template_type != SMS_TYPE:
|
|
return False
|
|
template = SMSMessageTemplate({'content': content, 'template_type': template_type})
|
|
return template.content_count > current_app.config.get('SMS_CHAR_COUNT_LIMIT')
|
|
|
|
|
|
@template_blueprint.route('', methods=['POST'])
|
|
def create_template(service_id):
|
|
fetched_service = dao_fetch_service_by_id(service_id=service_id)
|
|
# permissions needs to be placed here otherwise marshmallow will intefere with versioning
|
|
permissions = fetched_service.permissions
|
|
new_template = template_schema.load(request.get_json()).data
|
|
|
|
if not service_has_permission(new_template.template_type, permissions):
|
|
message = "Creating {} templates is not allowed".format(
|
|
get_public_notify_type_text(new_template.template_type))
|
|
errors = {'template_type': [message]}
|
|
raise InvalidRequest(errors, 403)
|
|
|
|
new_template.service = fetched_service
|
|
over_limit = _content_count_greater_than_limit(new_template.content, new_template.template_type)
|
|
if over_limit:
|
|
char_count_limit = current_app.config.get('SMS_CHAR_COUNT_LIMIT')
|
|
message = 'Content has a character count greater than the limit of {}'.format(char_count_limit)
|
|
errors = {'content': [message]}
|
|
raise InvalidRequest(errors, status_code=400)
|
|
|
|
check_reply_to(service_id, new_template.reply_to, new_template.template_type)
|
|
|
|
dao_create_template(new_template)
|
|
return jsonify(data=template_schema.dump(new_template).data), 201
|
|
|
|
|
|
@template_blueprint.route('/<uuid:template_id>', methods=['POST'])
|
|
def update_template(service_id, template_id):
|
|
fetched_template = dao_get_template_by_id_and_service_id(template_id=template_id, service_id=service_id)
|
|
|
|
if not service_has_permission(fetched_template.template_type, fetched_template.service.permissions):
|
|
message = "Updating {} templates is not allowed".format(
|
|
get_public_notify_type_text(fetched_template.template_type))
|
|
errors = {'template_type': [message]}
|
|
|
|
raise InvalidRequest(errors, 403)
|
|
|
|
data = request.get_json()
|
|
|
|
# if redacting, don't update anything else
|
|
if data.get('redact_personalisation') is True:
|
|
return redact_template(fetched_template, data)
|
|
|
|
if "reply_to" in data:
|
|
check_reply_to(service_id, data.get("reply_to"), fetched_template.template_type)
|
|
updated = dao_update_template_reply_to(template_id=template_id, reply_to=data.get("reply_to"))
|
|
return jsonify(data=template_schema.dump(updated).data), 200
|
|
|
|
current_data = dict(template_schema.dump(fetched_template).data.items())
|
|
updated_template = dict(template_schema.dump(fetched_template).data.items())
|
|
updated_template.update(data)
|
|
|
|
# Check if there is a change to make.
|
|
if _template_has_not_changed(current_data, updated_template):
|
|
return jsonify(data=updated_template), 200
|
|
|
|
over_limit = _content_count_greater_than_limit(updated_template['content'], fetched_template.template_type)
|
|
if over_limit:
|
|
char_count_limit = current_app.config.get('SMS_CHAR_COUNT_LIMIT')
|
|
message = 'Content has a character count greater than the limit of {}'.format(char_count_limit)
|
|
errors = {'content': [message]}
|
|
raise InvalidRequest(errors, status_code=400)
|
|
|
|
update_dict = template_schema.load(updated_template).data
|
|
|
|
dao_update_template(update_dict)
|
|
return jsonify(data=template_schema.dump(update_dict).data), 200
|
|
|
|
|
|
@template_blueprint.route('', methods=['GET'])
|
|
def get_all_templates_for_service(service_id):
|
|
templates = dao_get_all_templates_for_service(service_id=service_id)
|
|
data = template_schema.dump(templates, many=True).data
|
|
return jsonify(data=data)
|
|
|
|
|
|
@template_blueprint.route('/<uuid:template_id>', methods=['GET'])
|
|
def get_template_by_id_and_service_id(service_id, template_id):
|
|
fetched_template = dao_get_template_by_id_and_service_id(template_id=template_id, service_id=service_id)
|
|
data = template_schema.dump(fetched_template).data
|
|
return jsonify(data=data)
|
|
|
|
|
|
@template_blueprint.route('/<uuid:template_id>/preview', methods=['GET'])
|
|
def preview_template_by_id_and_service_id(service_id, template_id):
|
|
fetched_template = dao_get_template_by_id_and_service_id(template_id=template_id, service_id=service_id)
|
|
data = template_schema.dump(fetched_template).data
|
|
template_object = get_template_instance(data, values=request.args.to_dict())
|
|
|
|
if template_object.missing_data:
|
|
raise InvalidRequest(
|
|
{'template': [
|
|
'Missing personalisation: {}'.format(", ".join(template_object.missing_data))
|
|
]}, status_code=400
|
|
)
|
|
|
|
data['subject'], data['content'] = template_object.subject, str(template_object)
|
|
|
|
return jsonify(data)
|
|
|
|
|
|
@template_blueprint.route('/<uuid:template_id>/version/<int:version>')
|
|
def get_template_version(service_id, template_id, version):
|
|
data = template_history_schema.dump(
|
|
dao_get_template_by_id_and_service_id(
|
|
template_id=template_id,
|
|
service_id=service_id,
|
|
version=version
|
|
)
|
|
).data
|
|
return jsonify(data=data)
|
|
|
|
|
|
@template_blueprint.route('/<uuid:template_id>/versions')
|
|
def get_template_versions(service_id, template_id):
|
|
data = template_history_schema.dump(
|
|
dao_get_template_versions(service_id=service_id, template_id=template_id),
|
|
many=True
|
|
).data
|
|
return jsonify(data=data)
|
|
|
|
|
|
def _template_has_not_changed(current_data, updated_template):
|
|
return all(
|
|
current_data[key] == updated_template[key]
|
|
for key in ('name', 'content', 'subject', 'archived', 'process_type')
|
|
)
|
|
|
|
|
|
def redact_template(template, data):
|
|
# we also don't need to check what was passed in redact_personalisation - its presence in the dict is enough.
|
|
if 'created_by' not in data:
|
|
message = 'Field is required'
|
|
errors = {'created_by': [message]}
|
|
raise InvalidRequest(errors, status_code=400)
|
|
|
|
# if it's already redacted, then just return 200 straight away.
|
|
if not template.redact_personalisation:
|
|
dao_redact_template(template, data['created_by'])
|
|
return 'null', 200
|
|
|
|
|
|
@template_blueprint.route('/preview/<uuid:notification_id>/<file_type>', methods=['GET'])
|
|
def preview_letter_template_by_notification_id(service_id, notification_id, file_type):
|
|
|
|
if file_type not in ('pdf', 'png'):
|
|
raise InvalidRequest({'content': ["file_type must be pdf or png"]}, status_code=400)
|
|
|
|
page = request.args.get('page')
|
|
|
|
notification = get_notification_by_id(notification_id)
|
|
|
|
template = dao_get_template_by_id(notification.template_id)
|
|
|
|
if is_precompiled_letter(template):
|
|
|
|
try:
|
|
|
|
pdf_file = get_letter_pdf(notification)
|
|
|
|
except botocore.exceptions.ClientError:
|
|
current_app.logger.info
|
|
raise InvalidRequest('Error getting letter file from S3 notification id {}'.format(notification_id),
|
|
status_code=500)
|
|
|
|
content = base64.b64encode(pdf_file).decode('utf-8')
|
|
|
|
if file_type == 'png':
|
|
|
|
url = '{}/precompiled-preview.png{}'.format(
|
|
current_app.config['TEMPLATE_PREVIEW_API_HOST'],
|
|
'?page={}'.format(page) if page else ''
|
|
)
|
|
|
|
content = _get_png_preview(url, content, notification.id)
|
|
|
|
else:
|
|
|
|
template_for_letter_print = {
|
|
"id": str(notification.template_id),
|
|
"subject": template.subject,
|
|
"content": template.content,
|
|
"version": str(template.version)
|
|
}
|
|
|
|
service = dao_fetch_service_by_id(service_id)
|
|
|
|
data = {
|
|
'letter_contact_block': notification.reply_to_text,
|
|
'template': template_for_letter_print,
|
|
'values': notification.personalisation,
|
|
'dvla_org_id': service.dvla_organisation_id,
|
|
}
|
|
|
|
url = '{}/preview.{}{}'.format(
|
|
current_app.config['TEMPLATE_PREVIEW_API_HOST'],
|
|
file_type,
|
|
'?page={}'.format(page) if page else ''
|
|
)
|
|
|
|
content = _get_png_preview(url, data, notification.id)
|
|
|
|
return jsonify({"content": content})
|
|
|
|
|
|
def _get_png_preview(url, data, notification_id):
|
|
resp = requests_post(
|
|
url,
|
|
data=data,
|
|
headers={'Authorization': 'Token {}'.format(current_app.config['TEMPLATE_PREVIEW_API_KEY'])}
|
|
)
|
|
|
|
if resp.status_code != 200:
|
|
raise InvalidRequest(
|
|
'Error generating preview for {}'.format(notification_id), status_code=500
|
|
)
|
|
|
|
return base64.b64encode(resp.content).decode('utf-8')
|