2017-06-12 17:21:25 +01:00
|
|
|
|
# -*- coding: utf-8 -*-
|
2018-03-08 12:25:07 +00:00
|
|
|
|
import base64
|
2018-03-08 17:10:34 +00:00
|
|
|
|
import io
|
2019-10-30 11:28:38 +00:00
|
|
|
|
import json
|
2018-03-08 12:25:07 +00:00
|
|
|
|
import os
|
2019-06-18 18:00:36 +01:00
|
|
|
|
from datetime import datetime
|
2017-12-30 16:54:39 +00:00
|
|
|
|
|
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,
|
2020-04-07 17:13:28 +01:00
|
|
|
|
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
|
|
|
|
)
|
2018-03-08 12:25:07 +00:00
|
|
|
|
from notifications_python_client.errors import APIError
|
2018-11-27 16:49:01 +00:00
|
|
|
|
from notifications_utils.letter_timings import (
|
|
|
|
|
|
get_letter_timings,
|
|
|
|
|
|
letter_can_be_cancelled,
|
|
|
|
|
|
)
|
2018-03-08 17:10:34 +00:00
|
|
|
|
from notifications_utils.pdf import pdf_page_count
|
2019-01-08 17:34:57 +00:00
|
|
|
|
from PyPDF2.utils import PdfReadError
|
2018-03-01 15:38:42 +00:00
|
|
|
|
|
2017-06-12 17:21:25 +01:00
|
|
|
|
from app import (
|
2017-12-30 16:54:39 +00:00
|
|
|
|
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,
|
|
|
|
|
|
generate_notifications_csv,
|
2017-06-12 17:21:25 +01:00
|
|
|
|
get_help_argument,
|
2019-06-18 18:00:36 +01:00
|
|
|
|
get_letter_printing_statement,
|
2019-10-30 11:28:38 +00:00
|
|
|
|
get_letter_validation_error,
|
2017-06-12 17:21:25 +01:00
|
|
|
|
get_template,
|
2018-02-20 11:22:17 +00:00
|
|
|
|
parse_filter_args,
|
|
|
|
|
|
set_status_filters,
|
|
|
|
|
|
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))
|
2018-01-03 10:44:36 +00:00
|
|
|
|
notification['template'].update({'reply_to_text': notification['reply_to_text']})
|
|
|
|
|
|
|
2018-11-02 14:18:58 +00:00
|
|
|
|
personalisation = get_all_personalisation_from_notification(notification)
|
2019-10-30 11:28:38 +00:00
|
|
|
|
error_message = None
|
2018-03-08 17:10:34 +00:00
|
|
|
|
if notification['template']['is_precompiled_letter']:
|
2019-01-08 17:34:57 +00:00
|
|
|
|
try:
|
2020-04-07 17:13:28 +01:00
|
|
|
|
file_contents, metadata = get_letter_file_data(
|
2019-10-30 11:28:38 +00:00
|
|
|
|
service_id, notification_id, "pdf", with_metadata=True
|
2019-04-04 12:05:17 +01:00
|
|
|
|
)
|
2019-10-30 11:28:38 +00:00
|
|
|
|
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
|
|
|
|
|
|
)
|
2019-01-08 17:34:57 +00:00
|
|
|
|
except PdfReadError:
|
|
|
|
|
|
return render_template(
|
|
|
|
|
|
'views/notifications/invalid_precompiled_letter.html',
|
|
|
|
|
|
created_at=notification['created_at']
|
|
|
|
|
|
)
|
2018-03-08 17:10:34 +00:00
|
|
|
|
else:
|
2018-11-02 14:18:58 +00:00
|
|
|
|
page_count = get_page_count_for_letter(notification['template'], values=personalisation)
|
2018-03-08 17:10:34 +00:00
|
|
|
|
|
2018-09-10 11:21:43 +01:00
|
|
|
|
if notification.get('postage'):
|
2020-03-03 11:39:31 +00:00
|
|
|
|
if notification["status"] == "validation-failed":
|
|
|
|
|
|
notification['template']['postage'] = None
|
|
|
|
|
|
else:
|
|
|
|
|
|
notification['template']['postage'] = notification['postage']
|
2017-06-24 17:12:45 +01:00
|
|
|
|
template = get_template(
|
|
|
|
|
|
notification['template'],
|
|
|
|
|
|
current_service,
|
|
|
|
|
|
letter_preview_url=url_for(
|
2017-11-10 08:42:49 +00:00
|
|
|
|
'.view_letter_notification_as_preview',
|
2017-06-24 17:12:45 +01:00
|
|
|
|
service_id=service_id,
|
2017-07-13 13:28:11 +01:00
|
|
|
|
notification_id=notification_id,
|
2017-11-10 08:42:49 +00:00
|
|
|
|
filetype='png',
|
2017-06-24 17:12:45 +01:00
|
|
|
|
),
|
2018-03-08 17:10:34 +00:00
|
|
|
|
page_count=page_count,
|
2017-06-24 17:12:45 +01:00
|
|
|
|
show_recipient=True,
|
2017-06-24 17:29:28 +01:00
|
|
|
|
redact_missing_personalisation=True,
|
2017-06-24 17:12:45 +01:00
|
|
|
|
)
|
2018-11-02 14:18:58 +00:00
|
|
|
|
template.values = personalisation
|
2017-06-24 17:12:45 +01:00
|
|
|
|
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)
|
|
|
|
|
|
|
2019-06-07 16:16:13 +01:00
|
|
|
|
if get_help_argument() or request.args.get('help') == '0':
|
|
|
|
|
|
# help=0 is set when you’ve just sent a notification. We
|
|
|
|
|
|
# only want to show the back link when you’ve navigated to a
|
|
|
|
|
|
# notification, not when you’ve just sent it.
|
|
|
|
|
|
back_link = None
|
2019-05-09 17:33:22 +01:00
|
|
|
|
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'),
|
|
|
|
|
|
)
|
2019-05-09 17:33:22 +01:00
|
|
|
|
else:
|
|
|
|
|
|
back_link = url_for(
|
|
|
|
|
|
'main.view_notifications',
|
|
|
|
|
|
service_id=current_service.id,
|
|
|
|
|
|
message_type=template.template_type,
|
2019-05-15 10:04:43 +01:00
|
|
|
|
status='sending,delivered,failed',
|
2019-05-09 17:33:22 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
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'],
|
2019-10-30 11:28:38 +00:00
|
|
|
|
message=error_message,
|
2017-06-12 17:21:25 +01:00
|
|
|
|
uploaded_file_name='Report',
|
2017-06-24 17:12:45 +01:00
|
|
|
|
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),
|
2017-06-24 17:12:45 +01:00
|
|
|
|
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'],
|
2018-07-20 08:43:02 +01:00
|
|
|
|
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
|
|
|
|
|
|
),
|
2019-05-09 17:33:22 +01:00
|
|
|
|
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':
|
|
|
|
|
|
notification_api_client.update_notification_to_cancelled(current_service.id, notification_id)
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-11-04 11:20:08 +00:00
|
|
|
|
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>.<letter_file_extension:filetype>")
|
2018-03-01 10:30:17 +00:00
|
|
|
|
@user_has_permissions('view_activity')
|
2019-10-30 11:28:38 +00:00
|
|
|
|
def view_letter_notification_as_preview(
|
|
|
|
|
|
service_id, notification_id, filetype, with_metadata=False
|
|
|
|
|
|
):
|
2020-04-07 17:13:28 +01:00
|
|
|
|
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:
|
2019-10-30 11:28:38 +00:00
|
|
|
|
preview = notification_api_client.get_notification_letter_preview(
|
|
|
|
|
|
service_id,
|
|
|
|
|
|
notification_id,
|
|
|
|
|
|
filetype,
|
|
|
|
|
|
page=request.args.get('page')
|
|
|
|
|
|
)
|
2018-03-01 13:01:54 +00:00
|
|
|
|
|
2018-03-08 12:25:07 +00:00
|
|
|
|
display_file = base64.b64decode(preview['content'])
|
|
|
|
|
|
except APIError:
|
|
|
|
|
|
display_file = get_preview_error_image()
|
2019-10-30 11:28:38 +00:00
|
|
|
|
preview = {"metadata": {}}
|
2017-07-13 13:28:11 +01:00
|
|
|
|
|
2019-10-30 11:28:38 +00:00
|
|
|
|
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
|
|
|
|
|
2018-02-27 16:33:55 +00: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
|
|
|
|
),
|
|
|
|
|
|
}
|
2017-06-24 17:12:45 +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'] = {}
|
|
|
|
|
|
|
2017-06-24 17:12:45 +01:00
|
|
|
|
if notification['template']['template_type'] == 'email':
|
2017-06-30 15:18:03 +01:00
|
|
|
|
notification['personalisation']['email_address'] = notification['to']
|
2017-06-24 17:29:28 +01:00
|
|
|
|
|
2017-06-24 17:12:45 +01:00
|
|
|
|
if notification['template']['template_type'] == 'sms':
|
2017-06-30 15:18:03 +01:00
|
|
|
|
notification['personalisation']['phone_number'] = notification['to']
|
2017-06-24 17:29:28 +01:00
|
|
|
|
|
2017-06-30 15:18:03 +01:00
|
|
|
|
return notification['personalisation']
|
2017-12-30 16:54:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
2019-11-04 11:07:55 +00:00
|
|
|
|
@main.route("/services/<uuid:service_id>/download-notifications.csv")
|
2018-03-01 10:30:17 +00:00
|
|
|
|
@user_has_permissions('view_activity')
|
2017-12-30 16:54:39 +00:00
|
|
|
|
def download_notifications_csv(service_id):
|
|
|
|
|
|
filter_args = parse_filter_args(request.args)
|
|
|
|
|
|
filter_args['status'] = set_status_filters(filter_args)
|
|
|
|
|
|
|
2018-12-03 17:57:11 +00:00
|
|
|
|
service_data_retention_days = current_service.get_days_of_retention(filter_args.get('message_type')[0])
|
2017-12-30 16:54:39 +00:00
|
|
|
|
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),
|
2018-07-02 15:48:00 +01:00
|
|
|
|
page_size=10000,
|
2018-01-15 12:29:03 +00:00
|
|
|
|
format_for_csv=True,
|
2018-06-25 16:29:40 +01:00
|
|
|
|
template_type=filter_args.get('message_type'),
|
2018-09-27 14:51:36 +01:00
|
|
|
|
limit_days=service_data_retention_days,
|
2017-12-30 16:54:39 +00:00
|
|
|
|
)
|
|
|
|
|
|
),
|
|
|
|
|
|
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],
|
2018-07-20 08:42:01 +01:00
|
|
|
|
current_service.name)
|
2017-12-30 16:54:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
)
|