diff --git a/app/main/views/uploads.py b/app/main/views/uploads.py index b3a1021ef..329d8591c 100644 --- a/app/main/views/uploads.py +++ b/app/main/views/uploads.py @@ -3,7 +3,7 @@ import itertools import json import urllib import uuid -from io import BytesIO +from io import BytesIO, StringIO from zipfile import BadZipFile from flask import ( @@ -13,6 +13,7 @@ from flask import ( redirect, render_template, request, + send_file, url_for, ) from notifications_utils.columns import Columns @@ -437,3 +438,14 @@ def contact_list(service_id, contact_list_id): 'views/uploads/contact-list/contact-list.html', contact_list=ContactList.from_id(contact_list_id, service_id=service_id), ) + + +@main.route("/services//contact-list/.csv", methods=['GET']) +@user_has_permissions('send_messages') +def download_contact_list(service_id, contact_list_id): + contact_list = ContactList.from_id(contact_list_id, service_id=service_id) + return send_file( + filename_or_fp=StringIO(contact_list.contents), + attachment_filename=contact_list.saved_file_name, + as_attachment=True, + ) diff --git a/app/models/contact_list.py b/app/models/contact_list.py index dbf58a1d4..b617a4463 100644 --- a/app/models/contact_list.py +++ b/app/models/contact_list.py @@ -1,3 +1,5 @@ +from os import path + from flask import abort, current_app from notifications_utils.formatters import strip_whitespace from notifications_utils.recipients import RecipientCSV @@ -103,15 +105,24 @@ class ContactList(JSONModel): template_type=metadata['template_type'], )) + @property + def contents(self): + return self.download(self.service_id, self.id) + @cached_property def recipients(self): return RecipientCSV( - self.download(self.service_id, self.id), + self.contents, template_type=self.template_type, international_sms=True, max_initial_rows_shown=50, ) + @property + def saved_file_name(self): + file_name, extention = path.splitext(self.original_file_name) + return f'{file_name}.csv' + class ContactLists(ModelList): client_method = contact_list_api_client.get_contact_lists diff --git a/app/navigation.py b/app/navigation.py index 3221dd61b..4969af085 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -170,6 +170,7 @@ class HeaderNavigation(Navigation): 'delivery_and_failure', 'delivery_status_callback', 'design_content', + 'download_contact_list', 'download_notifications_csv', 'edit_data_retention', 'edit_organisation_agreement', @@ -506,6 +507,7 @@ class MainNavigation(Navigation): 'delivery_and_failure', 'design_content', 'documentation', + 'download_contact_list', 'download_notifications_csv', 'edit_data_retention', 'edit_organisation_agreement', @@ -744,6 +746,7 @@ class CaseworkNavigation(Navigation): 'delivery_status_callback', 'design_content', 'documentation', + 'download_contact_list', 'download_notifications_csv', 'edit_data_retention', 'edit_organisation_agreement', @@ -1045,6 +1048,7 @@ class OrgNavigation(Navigation): 'delivery_status_callback', 'design_content', 'documentation', + 'download_contact_list', 'download_notifications_csv', 'edit_data_retention', 'edit_provider', diff --git a/app/templates/views/uploads/contact-list/contact-list.html b/app/templates/views/uploads/contact-list/contact-list.html index f674e99ed..50c0cef79 100644 --- a/app/templates/views/uploads/contact-list/contact-list.html +++ b/app/templates/views/uploads/contact-list/contact-list.html @@ -22,6 +22,7 @@

+ Download this list  {{ contact_list.recipients|length|format_thousands }} {{ recipient_count_label(contact_list.recipients|length, contact_list.recipients.template_type) }}

diff --git a/tests/app/main/views/test_uploads.py b/tests/app/main/views/test_uploads.py index 6ee68999e..073b045da 100644 --- a/tests/app/main/views/test_uploads.py +++ b/tests/app/main/views/test_uploads.py @@ -1151,7 +1151,12 @@ def test_view_contact_list( 'Uploaded by Test User today at 10:59am' ) assert normalize_spaces(page.select('main p')[1].text) == ( - '51 email addresses' + 'Download this list 51 email addresses' + ) + assert page.select_one('a[download]')['href'] == url_for( + 'main.download_contact_list', + service_id=SERVICE_ONE_ID, + contact_list_id=fake_uuid, ) assert normalize_spaces(page.select_one('table').text).startswith( 'Email addresses ' @@ -1179,3 +1184,33 @@ def test_view_contact_list_404s_for_non_existing_list( contact_list_id=uuid.uuid4(), _expected_status=404, ) + + +def test_download_contact_list( + mocker, + logged_in_client, + fake_uuid, + mock_get_contact_lists, +): + mocker.patch( + 'app.models.contact_list.s3download', + return_value='phone number\n07900900321' + ) + response = logged_in_client.get(url_for( + 'main.download_contact_list', + service_id=SERVICE_ONE_ID, + contact_list_id=fake_uuid, + )) + assert response.status_code == 200 + assert response.headers['Content-Type'] == ( + 'text/csv; ' + 'charset=utf-8' + ) + assert response.headers['Content-Disposition'] == ( + 'attachment; ' + 'filename=EmergencyContactList.csv' + ) + assert response.get_data(as_text=True) == ( + 'phone number\n' + '07900900321' + )