Files
notifications-admin/app/main/views/notifications.py
T

319 lines
12 KiB
Python
Raw Normal View History

2017-06-12 17:21:25 +01:00
# -*- coding: utf-8 -*-
2018-03-08 12:25:07 +00:00
import base64
import io
import json
2018-03-08 12:25:07 +00:00
import os
from datetime import datetime
2018-11-27 16:49:01 +00:00
from dateutil import parser
2017-06-12 17:21:25 +01:00
from flask import (
2018-02-20 11:22:17 +00:00
Response,
2018-12-05 10:58:27 +00:00
flash,
2017-06-12 17:21:25 +01:00
jsonify,
2018-12-05 10:58:27 +00:00
redirect,
2018-02-20 11:22:17 +00:00
render_template,
2017-06-12 17:21:25 +01:00
request,
send_file,
2018-02-20 11:22:17 +00:00
stream_with_context,
2017-06-12 17:21:25 +01:00
url_for,
2018-02-20 11:22:17 +00:00
)
2021-07-27 15:05:45 +01:00
from notifications_python_client.errors import APIError, HTTPError
from notifications_utils import LETTER_MAX_PAGE_COUNT
2018-11-27 16:49:01 +00:00
from notifications_utils.letter_timings import (
get_letter_timings,
letter_can_be_cancelled,
)
from notifications_utils.pdf import pdf_page_count
from PyPDF2.utils import PdfReadError
2018-03-01 15:38:42 +00:00
2017-06-12 17:21:25 +01:00
from app import (
current_service,
2018-02-20 11:22:17 +00:00
format_date_numeric,
job_api_client,
notification_api_client,
)
2017-06-12 17:21:25 +01:00
from app.main import main
2018-09-06 10:41:59 +01:00
from app.notify_client.api_key_api_client import KEY_TYPE_TEST
2018-03-08 12:25:07 +00:00
from app.template_previews import get_page_count_for_letter
2017-06-12 17:21:25 +01:00
from app.utils import (
2018-02-20 11:22:17 +00:00
DELIVERED_STATUSES,
FAILURE_STATUSES,
2017-06-12 17:21:25 +01:00
get_help_argument,
2018-02-20 11:22:17 +00:00
parse_filter_args,
set_status_filters,
)
from app.utils.csv import generate_notifications_csv
2021-06-09 13:59:06 +01:00
from app.utils.letters import (
get_letter_printing_statement,
get_letter_validation_error,
)
from app.utils.templates import get_template
2021-06-09 13:19:05 +01:00
from app.utils.user import user_has_permissions
2017-06-12 17:21:25 +01:00
2019-11-04 11:07:55 +00:00
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>")
2018-06-12 16:17:20 +01:00
@user_has_permissions('view_activity', 'send_messages')
2017-06-12 17:21:25 +01:00
def view_notification(service_id, notification_id):
2017-07-13 13:28:11 +01:00
notification = notification_api_client.get_notification(service_id, str(notification_id))
notification['template'].update({'reply_to_text': notification['reply_to_text']})
personalisation = get_all_personalisation_from_notification(notification)
error_message = None
if notification['template']['is_precompiled_letter']:
try:
file_contents, metadata = get_letter_file_data(
service_id, notification_id, "pdf", with_metadata=True
)
page_count = int(
metadata["page_count"]
) if metadata.get("page_count") else pdf_page_count(io.BytesIO(file_contents))
if notification["status"] == "validation-failed":
invalid_pages = metadata.get("invalid_pages")
invalid_pages = json.loads(invalid_pages) if invalid_pages else invalid_pages
error_message = get_letter_validation_error(
metadata.get("message"), invalid_pages, page_count
)
except PdfReadError:
return render_template(
'views/notifications/invalid_precompiled_letter.html',
created_at=notification['created_at']
)
else:
page_count = get_page_count_for_letter(notification['template'], values=personalisation)
if page_count and page_count > LETTER_MAX_PAGE_COUNT:
# We check page count here to show the right error message for a letter that is too long.
# Another way to do this would be to get the status and error message from letter metadata.
# This would be a significant amount of work though, out of scope for this bug fix.
# This is because currently we do not pull the letter from S3 when showing preview.
# Instead, we generate letter preview based on the letter template and personalisation.
# Additionally, when a templated letter is sent via the api and the personalisation pushes the
# page count over 10 pages, it takes a while for validation status to come through.
# Checking page count here will enable us to show the error message even if the letter is not
# fully processed yet.
error_message = get_letter_validation_error(
"letter-too-long", [1], page_count
)
if notification.get('postage'):
if notification["status"] == "validation-failed":
notification['template']['postage'] = None
else:
notification['template']['postage'] = notification['postage']
template = get_template(
notification['template'],
current_service,
letter_preview_url=url_for(
'.view_letter_notification_as_preview',
service_id=service_id,
2017-07-13 13:28:11 +01:00
notification_id=notification_id,
filetype='png',
),
page_count=page_count,
show_recipient=True,
2017-06-24 17:29:28 +01:00
redact_missing_personalisation=True,
sms_sender=notification['reply_to_text'],
email_reply_to=notification['reply_to_text'],
)
template.values = personalisation
if notification['job']:
job = job_api_client.get_job(service_id, notification['job']['id'])['data']
else:
job = None
2017-07-13 13:05:41 +01:00
2018-11-27 16:49:01 +00:00
letter_print_day = get_letter_printing_statement(notification['status'], notification['created_at'])
2018-12-05 10:58:27 +00:00
notification_created = parser.parse(notification['created_at']).replace(tzinfo=None)
show_cancel_button = notification['notification_type'] == 'letter' and \
letter_can_be_cancelled(notification['status'], notification_created)
if get_help_argument() or request.args.get('help') == '0':
# help=0 is set when youve just sent a notification. We
# only want to show the back link when youve navigated to a
# notification, not when youve just sent it.
back_link = None
elif request.args.get('from_job'):
back_link = url_for(
'main.view_job',
service_id=current_service.id,
job_id=request.args.get('from_job'),
)
2020-05-11 14:20:53 +01:00
elif request.args.get('from_uploaded_letters'):
back_link = url_for(
'main.uploaded_letters',
service_id=current_service.id,
letter_print_day=request.args.get('from_uploaded_letters'),
)
else:
back_link = url_for(
'main.view_notifications',
service_id=current_service.id,
message_type=template.template_type,
status='sending,delivered,failed',
)
2020-05-22 10:43:12 +01:00
if notification['notification_type'] == 'letter':
estimated_letter_delivery_date = get_letter_timings(
notification['created_at'],
postage=notification['postage']
).earliest_delivery
else:
estimated_letter_delivery_date = None
2017-06-12 17:21:25 +01:00
return render_template(
'views/notifications/notification.html',
finished=(notification['status'] in (DELIVERED_STATUSES + FAILURE_STATUSES)),
2018-10-11 10:54:39 +01:00
notification_status=notification['status'],
message=error_message,
2017-06-12 17:21:25 +01:00
uploaded_file_name='Report',
template=template,
job=job,
2017-06-12 17:21:25 +01:00
updates_url=url_for(
".view_notification_updates",
service_id=service_id,
notification_id=notification['id'],
status=request.args.get('status'),
help=get_help_argument()
),
partials=get_single_notification_partials(notification),
created_by=notification.get('created_by'),
created_at=notification['created_at'],
2018-10-11 10:54:39 +01:00
updated_at=notification['updated_at'],
2017-07-13 13:05:41 +01:00
help=get_help_argument(),
2020-05-22 10:43:12 +01:00
estimated_letter_delivery_date=estimated_letter_delivery_date,
2017-10-16 10:30:59 +01:00
notification_id=notification['id'],
2018-09-24 11:29:33 +01:00
postage=notification['postage'],
can_receive_inbound=(current_service.has_permission('inbound_sms')),
2018-11-27 16:49:01 +00:00
is_precompiled_letter=notification['template']['is_precompiled_letter'],
2018-12-05 10:58:27 +00:00
letter_print_day=letter_print_day,
2019-01-17 14:22:58 +00:00
show_cancel_button=show_cancel_button,
sent_with_test_key=(
notification.get('key_type') == KEY_TYPE_TEST
),
back_link=back_link,
2017-06-12 17:21:25 +01:00
)
2019-11-04 11:07:55 +00:00
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>/cancel", methods=['GET', 'POST'])
2018-12-05 10:58:27 +00:00
@user_has_permissions('view_activity', 'send_messages')
def cancel_letter(service_id, notification_id):
if request.method == 'POST':
2021-07-27 15:05:45 +01:00
try:
notification_api_client.update_notification_to_cancelled(current_service.id, notification_id)
except HTTPError as e:
message_fragments = ["already been cancelled", "too late to cancel"]
if e.status_code == 400 and any(fragment in e.message for fragment in message_fragments):
flash(e.message)
else:
raise e
2018-12-05 10:58:27 +00:00
return redirect(url_for('main.view_notification', service_id=service_id, notification_id=notification_id))
flash("Are you sure you want to cancel sending this letter?", 'cancel')
return view_notification(service_id, notification_id)
2018-03-08 12:25:07 +00:00
def get_preview_error_image():
2018-03-08 12:35:35 +00:00
path = os.path.join(os.path.dirname(__file__), "..", "..", "static", "images", "preview_error.png")
2018-03-08 12:25:07 +00:00
with open(path, "rb") as file:
return file.read()
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>.<letter_file_extension:filetype>")
2021-02-23 16:00:23 +00:00
@user_has_permissions('view_activity', 'send_messages')
def view_letter_notification_as_preview(
service_id, notification_id, filetype, with_metadata=False
):
image_data = get_letter_file_data(service_id, notification_id, filetype, with_metadata)
file = io.BytesIO(image_data)
mimetype = 'image/png' if filetype == 'png' else 'application/pdf'
return send_file(
filename_or_fp=file,
mimetype=mimetype,
)
def get_letter_file_data(service_id, notification_id, filetype, with_metadata=False):
2018-03-08 12:25:07 +00:00
try:
preview = notification_api_client.get_notification_letter_preview(
service_id,
notification_id,
filetype,
page=request.args.get('page')
)
2017-07-13 13:28:11 +01:00
2018-03-08 12:25:07 +00:00
display_file = base64.b64decode(preview['content'])
except APIError:
display_file = get_preview_error_image()
preview = {"metadata": {}}
if with_metadata:
return display_file, preview['metadata']
2018-03-08 12:25:07 +00:00
return display_file
2017-07-13 13:28:11 +01:00
2019-11-04 11:07:55 +00:00
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>.json")
2018-06-12 16:17:20 +01:00
@user_has_permissions('view_activity', 'send_messages')
2017-06-12 17:21:25 +01:00
def view_notification_updates(service_id, notification_id):
return jsonify(**get_single_notification_partials(
notification_api_client.get_notification(service_id, notification_id)
))
def get_single_notification_partials(notification):
return {
'status': render_template(
'partials/notifications/status.html',
2018-09-06 10:41:59 +01:00
notification=notification,
sent_with_test_key=(
notification.get('key_type') == KEY_TYPE_TEST
),
2017-06-12 17:21:25 +01:00
),
}
def get_all_personalisation_from_notification(notification):
2017-06-24 17:29:28 +01:00
if notification['template'].get('redact_personalisation'):
notification['personalisation'] = {}
if notification['template']['template_type'] == 'email':
notification['personalisation']['email_address'] = notification['to']
2017-06-24 17:29:28 +01:00
if notification['template']['template_type'] == 'sms':
notification['personalisation']['phone_number'] = notification['to']
2017-06-24 17:29:28 +01:00
return notification['personalisation']
2019-11-04 11:07:55 +00:00
@main.route("/services/<uuid:service_id>/download-notifications.csv")
@user_has_permissions('view_activity')
def download_notifications_csv(service_id):
filter_args = parse_filter_args(request.args)
filter_args['status'] = set_status_filters(filter_args)
service_data_retention_days = current_service.get_days_of_retention(filter_args.get('message_type')[0])
return Response(
stream_with_context(
generate_notifications_csv(
service_id=service_id,
job_id=None,
status=filter_args.get('status'),
page=request.args.get('page', 1),
page_size=10000,
format_for_csv=True,
template_type=filter_args.get('message_type'),
limit_days=service_data_retention_days,
)
),
mimetype='text/csv',
headers={
'Content-Disposition': 'inline; filename="{} - {} - {} report.csv"'.format(
format_date_numeric(datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")),
filter_args['message_type'][0],
current_service.name)
}
)