diff --git a/app/__init__.py b/app/__init__.py index f39d4eaae..cac27c73d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -67,6 +67,7 @@ from app.notify_client.inbound_number_client import inbound_number_client from app.notify_client.billing_api_client import billing_api_client from app.notify_client.complaint_api_client import complaint_api_client from app.notify_client.platform_stats_api_client import platform_stats_api_client +from app.notify_client.template_folder_api_client import template_folder_api_client from app.commands import setup_commands from app.utils import get_cdn_domain, gmt_timezones, id_safe @@ -128,6 +129,7 @@ def create_app(application): billing_api_client.init_app(application) complaint_api_client.init_app(application) platform_stats_api_client.init_app(application) + template_folder_api_client.init_app(application) login_manager.init_app(application) login_manager.login_view = 'main.sign_in' diff --git a/app/main/forms.py b/app/main/forms.py index 643fdcd31..eda0bcca9 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -850,21 +850,22 @@ class DateFilterForm(StripWhitespaceForm): class ChooseTemplateType(StripWhitespaceForm): template_type = RadioField( - 'What kind of template do you want to add?', + '', validators=[ DataRequired() ] ) - def __init__(self, include_letters=False, include_copy=False, *args, **kwargs): + def __init__(self, include_letters=False, include_copy=False, include_folder=False, *args, **kwargs): super().__init__(*args, **kwargs) self.template_type.choices = filter(None, [ - ('email', 'Email'), - ('sms', 'Text message'), - ('letter', 'Letter') if include_letters else None, + ('email', 'Email template'), + ('sms', 'Text message template'), + ('letter', 'Letter template') if include_letters else None, ('copy-existing', 'Copy of an existing template') if include_copy else None, + ('folder', 'Folder') if include_folder else None, ]) diff --git a/app/main/views/templates.py b/app/main/views/templates.py index 849cb2e5e..e178d1d3c 100644 --- a/app/main/views/templates.py +++ b/app/main/views/templates.py @@ -183,6 +183,7 @@ def add_template_by_type(service_id): service_api_client.count_service_templates(service_id) > 0, len(user_api_client.get_service_ids_for_user(current_user)) > 1, )), + include_folder=current_service.has_permission('edit_folders') ) if form.validate_on_submit(): @@ -288,6 +289,20 @@ def action_blocked(service_id, notification_type, return_to, template_id): ) +@main.route("/services//templates/add-folder", methods=['GET', 'POST']) +def add_folder(service_id): + if not current_service.has_permission('edit_folders'): + abort(403) + + form = NewFolderForm() + + if form.validate_on_submit(): + folder_api_client.create_template_folder(form.name.data, current_service.id, parent_id=None) + return redirect( + url_for('.view_templates', service_id=service_id) + ) + + @main.route("/services//templates/add-", methods=['GET', 'POST']) @login_required @user_has_permissions('manage_templates') diff --git a/app/models/service.py b/app/models/service.py index c663962d3..293d7d6e8 100644 --- a/app/models/service.py +++ b/app/models/service.py @@ -8,6 +8,7 @@ from app.notify_client.job_api_client import job_api_client from app.notify_client.organisations_api_client import organisations_client from app.notify_client.service_api_client import service_api_client from app.notify_client.user_api_client import user_api_client +from app.notify_client.template_folder_client import template_folder_client from app.utils import get_default_sms_sender @@ -252,3 +253,7 @@ class Service(): @property def has_inbound_number(self): return bool(self.inbound_number) + + @cached_property + def template_folders(self): + return template_folder_client.get_template_folders(self.id) diff --git a/app/navigation.py b/app/navigation.py index cf893c5ca..4bbc81fe0 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -109,6 +109,7 @@ class HeaderNavigation(Navigation): 'add_service', 'add_service_template', 'add_template_by_type', + 'add_folder', 'agreement', 'api_callbacks', 'api_documentation', @@ -299,6 +300,7 @@ class MainNavigation(Navigation): 'action_blocked', 'add_service_template', 'add_template_by_type', + 'add_folder', 'check_messages', 'check_notification', 'choose_template', @@ -552,6 +554,7 @@ class CaseworkNavigation(Navigation): 'add_service', 'add_service_template', 'add_template_by_type', + 'add_folder', 'agreement', 'api_callbacks', 'api_documentation', @@ -787,6 +790,7 @@ class OrgNavigation(Navigation): 'add_service', 'add_service_template', 'add_template_by_type', + 'add_folder', 'agreement', 'api_callbacks', 'api_documentation', diff --git a/app/notify_client/service_api_client.py b/app/notify_client/service_api_client.py index 539fbbb73..78d03a563 100644 --- a/app/notify_client/service_api_client.py +++ b/app/notify_client/service_api_client.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from app.notify_client import NotifyAdminAPIClient, _attach_current_user, cache diff --git a/app/notify_client/template_folder_api_client.py b/app/notify_client/template_folder_api_client.py new file mode 100644 index 000000000..680838a55 --- /dev/null +++ b/app/notify_client/template_folder_api_client.py @@ -0,0 +1,28 @@ +from app.notify_client import NotifyAdminAPIClient, cache + + +class TemplateFolderAPIClient(NotifyAdminAPIClient): + # Fudge assert in the super __init__ so + # we can set those variables later. + def __init__(self): + super().__init__('a' * 73, 'b') + + @cache.delete('service-{service_id}-template-folders') + def create_template_folder( + self, + service_id, + name, + parent_id=None + ): + data = { + 'name': name, + 'parent_id': parent_id + } + return self.post('/service/{}/template-folder'.format(service_id), data)['data']['id'] + + @cache.set('service-{service_id}-template-folders') + def get_template_folders(self, service_id): + return self.get('/service/{}/template-folder'.format(service_id))['data'] + + +template_folder_api_client = TemplateFolderAPIClient() diff --git a/app/templates/views/templates/add.html b/app/templates/views/templates/add.html index c46f73da4..923acfb63 100644 --- a/app/templates/views/templates/add.html +++ b/app/templates/views/templates/add.html @@ -5,12 +5,12 @@ {% extends "withnav_template.html" %} {% block service_page_title %} - Add new template + Add new template or folder {% endblock %} {% block maincolumn_content %} -

Add new template

+

What do you want to add?

{% call form_wrapper() %} {{ radios(form.template_type) }} diff --git a/tests/app/main/views/test_letters.py b/tests/app/main/views/test_letters.py index 09597c427..8c861eea2 100644 --- a/tests/app/main/views/test_letters.py +++ b/tests/app/main/views/test_letters.py @@ -56,11 +56,11 @@ def test_letters_lets_in_without_permission( @pytest.mark.parametrize('permissions, choices', [ ( ['email', 'sms', 'letter'], - ['Email', 'Text message', 'Letter', 'Copy of an existing template'] + ['Email template', 'Text message template', 'Letter template', 'Copy of an existing template'] ), ( ['email', 'sms'], - ['Email', 'Text message', 'Copy of an existing template'] + ['Email template', 'Text message template', 'Copy of an existing template'] ), ]) def test_given_option_to_add_letters_if_allowed( diff --git a/tests/app/main/views/test_template_folders.py b/tests/app/main/views/test_template_folders.py new file mode 100644 index 000000000..b7ac1bee8 --- /dev/null +++ b/tests/app/main/views/test_template_folders.py @@ -0,0 +1,28 @@ + + +def test_add_folder( + client_request, + service_one, + mocker, + mock_get_service_templates, + mock_get_organisations_and_services_for_user, +): + service_one['permissions'] += ['edit_folders'] + mocker.patch('app.service_api_client.get_service', return_value={"data": service_one}) + + page = client_request.get( + 'main.add_template_by_type', + service_id=service_one['id'], + _test_page_title=False + ) + + radios = page.select('input[type=radio]') + labels = page.select('label') + + assert [x['value'] for x in radios] == ['email', 'sms', 'copy-existing', 'folder'] + assert [x.text.strip() for x in labels] == [ + 'Email template', + 'Text message template', + 'Copy of an existing template', + 'Folder' + ] diff --git a/tests/app/notify_client/test_template_folder_client.py b/tests/app/notify_client/test_template_folder_client.py new file mode 100644 index 000000000..e2e0cad27 --- /dev/null +++ b/tests/app/notify_client/test_template_folder_client.py @@ -0,0 +1,42 @@ +import pytest +import uuid + +from app.notify_client.template_folder_api_client import TemplateFolderAPIClient + + +@pytest.mark.parametrize('parent_id', [uuid.uuid4(), None]) +def test_create_template_folder_calls_correct_api_endpoint(mocker, api_user_active, parent_id): + mock_redis_delete = mocker.patch('app.notify_client.RedisClient.delete') + + some_service_id = uuid.uuid4() + expected_url = '/service/{}/template-folder'.format(some_service_id) + data = {'name': 'foo', 'parent_id': parent_id} + + client = TemplateFolderAPIClient() + + mock_post = mocker.patch('app.notify_client.template_folder_api_client.TemplateFolderAPIClient.post') + + client.create_template_folder(some_service_id, name='foo', parent_id=parent_id) + + mock_post.assert_called_once_with(expected_url, data) + mock_redis_delete.assert_called_once_with('service-{}-template-folders'.format(some_service_id)) + + +def test_get_template_folders_calls_correct_api_endpoint(mocker, api_user_active): + mock_redis_get = mocker.patch('app.notify_client.RedisClient.get', return_value=None) + mock_api_get = mocker.patch('app.notify_client.NotifyAdminAPIClient.get', return_value={'data': {'a': 'b'}}) + mock_redis_set = mocker.patch('app.notify_client.RedisClient.set') + + some_service_id = uuid.uuid4() + expected_url = '/service/{}/template-folder'.format(some_service_id) + redis_key = 'service-{}-template-folders'.format(some_service_id) + + client = TemplateFolderAPIClient() + + ret = client.get_template_folders(some_service_id) + + assert ret == {'a': 'b'} + + mock_redis_get.assert_called_once_with(redis_key) + mock_api_get.assert_called_once_with(expected_url) + mock_redis_set.assert_called_once_with(redis_key, '{"a": "b"}', ex=604800)