From b598af1ecaf9bad5db2f94f879b4599a73ce653f Mon Sep 17 00:00:00 2001
From: Chris Hill-Scott
Date: Sun, 15 Mar 2020 19:13:07 +0000
Subject: [PATCH] Add an endpoint to download a contact list
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
In case you need to download it, modify it, and re-upload it when you
don’t have access to the existing list.
---
app/main/views/uploads.py | 14 ++++++-
app/models/contact_list.py | 13 ++++++-
app/navigation.py | 4 ++
.../uploads/contact-list/contact-list.html | 1 +
tests/app/main/views/test_uploads.py | 37 ++++++++++++++++++-
5 files changed, 66 insertions(+), 3 deletions(-)
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'
+ )