From 718f4407203ccbb253dd3879536d7d5a3781132f Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 4 Apr 2019 11:18:22 +0100 Subject: [PATCH 01/11] Get info about organisations from database table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the first step of replacing the `domains.yml` file. In order to replicate the same functionality we get from the `domains.yml` file and its associated code this commit adds a `Organisation` model. This model copies a lot of methods from the `AgreementInfo` class which wrapped the `domains.yml` file. It factors out some stuff that would otherwise be duplicated between the `Organisation` and `Service` model, in such a way that could be reused for making other models in the future. This commit doesn’t change other parts of the code to make use of this new model yet – that will come in subsequent commits. --- app/models/__init__.py | 27 ++++ app/models/organisation.py | 140 ++++++++++++++++++ app/models/service.py | 31 ++-- app/models/user.py | 17 +++ app/notify_client/organisations_api_client.py | 12 ++ 5 files changed, 209 insertions(+), 18 deletions(-) create mode 100644 app/models/__init__.py create mode 100644 app/models/organisation.py diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 000000000..f9efbffeb --- /dev/null +++ b/app/models/__init__.py @@ -0,0 +1,27 @@ +from flask import abort + + +class JSONModel(): + + ALLOWED_PROPERTIES = set() + + def __init__(self, _dict): + # in the case of a bad request _dict may be `None` + self._dict = _dict or {} + + def __bool__(self): + return self._dict != {} + + def __getattr__(self, attr): + if attr in self.ALLOWED_PROPERTIES: + return self._dict[attr] + raise AttributeError('`{}` is not a {} attribute'.format( + attr, + self.__class__.__name__.lower(), + )) + + def _get_by_id(self, things, id): + try: + return next(thing for thing in things if thing['id'] == str(id)) + except StopIteration: + abort(404) diff --git a/app/models/organisation.py b/app/models/organisation.py new file mode 100644 index 000000000..d3753a431 --- /dev/null +++ b/app/models/organisation.py @@ -0,0 +1,140 @@ +from flask import Markup, abort + +from app.models import JSONModel + + +class Organisation(JSONModel): + + ALLOWED_PROPERTIES = { + 'id', + 'name', + 'active', + 'crown', + 'organisation_type', + 'letter_branding_id', + 'email_branding_id', + 'agreement_signed', + 'agreement_signed_at', + 'agreement_signed_by_id', + 'agreement_signed_version', + 'domains', + } + + def __init__(self, _dict): + + super().__init__(_dict) + + if self._dict == {}: + self.name, self.crown, self.agreement_signed = None, None, None + + @property + def crown_status(self): + return self.crown + + @property + def as_human_readable(self): + if 'dwp.' in ''.join(self.domains): + return 'DWP - Requires OED approval' + if self.agreement_signed: + return 'Yes, on behalf of {}'.format(self.name) + elif self.owner: + return '{} (organisation is {}, {})'.format( + { + False: 'No', + None: 'Can’t tell', + }.get(self.agreement_signed), + self.name, + { + True: 'a crown body', + False: 'a non-crown body', + None: 'crown status unknown', + }.get(self.crown_status), + ) + else: + return 'Can’t tell (domain is {})'.format(self._domain) + + @property + def as_info_for_branding_request(self): + return self.owner or 'Can’t tell (domain is {})'.format(self._domain) + + @property + def as_jinja_template(self): + if self.crown_status is None: + return 'agreement-choose' + if self.agreement_signed: + return 'agreement-signed' + return 'agreement' + + def as_terms_of_use_paragraph(self, **kwargs): + return Markup(self._as_terms_of_use_paragraph(**kwargs)) + + def _as_terms_of_use_paragraph(self, terms_link, download_link, support_link, signed_in): + + if not signed_in: + return (( + '{} Sign in to download a copy ' + 'or find out if one is already in place.' + ).format(self._acceptance_required, terms_link)) + + if self.agreement_signed is None: + return (( + '{} Download the agreement or ' + 'contact us to find out if we already ' + 'have one in place with your organisation.' + ).format(self._acceptance_required, download_link, support_link)) + + if self.agreement_signed is False: + return (( + '{} Download a copy.' + ).format(self._acceptance_required, download_link)) + + return ( + 'Your organisation ({}) has already accepted the ' + 'GOV.UK Notify data sharing and financial ' + 'agreement.'.format(self.name) + ) + + def as_pricing_paragraph(self, **kwargs): + return Markup(self._as_pricing_paragraph(**kwargs)) + + def _as_pricing_paragraph(self, pricing_link, download_link, support_link, signed_in): + + if not signed_in: + return (( + 'Sign in to download a copy or find ' + 'out if one is already in place with your organisation.' + ).format(pricing_link)) + + if self.agreement_signed is None: + return (( + 'Download the agreement or ' + 'contact us to find out if we already ' + 'have one in place with your organisation.' + ).format(download_link, support_link)) + + return ( + 'Download the agreement ' + '({} {}).'.format( + download_link, + self.name, + { + True: 'has already accepted it', + False: 'hasn’t accepted it yet' + }.get(self.agreement_signed) + ) + ) + + @property + def _acceptance_required(self): + return ( + 'Your organisation {} must also accept our data sharing ' + 'and financial agreement.'.format( + '({})'.format(self.name) if self.name else '', + ) + ) + + @property + def crown_status_or_404(self): + if self.crown_status is None: + abort(404) + return self.crown_status diff --git a/app/models/service.py b/app/models/service.py index 64805f430..425fceb02 100644 --- a/app/models/service.py +++ b/app/models/service.py @@ -2,6 +2,8 @@ from flask import abort, current_app from notifications_utils.field import Field from werkzeug.utils import cached_property +from app.models import JSONModel +from app.models.organisation import Organisation from app.notify_client.api_key_api_client import api_key_api_client from app.notify_client.billing_api_client import billing_api_client from app.notify_client.email_branding_client import email_branding_client @@ -18,7 +20,7 @@ from app.notify_client.user_api_client import user_api_client from app.utils import get_default_sms_sender -class Service(): +class Service(JSONModel): ALLOWED_PROPERTIES = { 'active', @@ -50,25 +52,12 @@ class Service(): ) def __init__(self, _dict): - # in the case of a bad request current service may be `None` - self._dict = _dict or {} + + super().__init__(_dict) + if 'permissions' not in self._dict: self.permissions = {'email', 'sms', 'letter'} - def __bool__(self): - return self._dict != {} - - def __getattr__(self, attr): - if attr in self.ALLOWED_PROPERTIES: - return self._dict[attr] - raise AttributeError('`{}` is not a service attribute'.format(attr)) - - def _get_by_id(self, things, id): - try: - return next(thing for thing in things if thing['id'] == str(id)) - except StopIteration: - abort(404) - def update(self, **kwargs): return service_api_client.update_service(self.id, **kwargs) @@ -402,8 +391,14 @@ class Service(): return None @cached_property + def organisation(self): + return Organisation( + organisations_client.get_service_organisation(self.id) + ) + + @property def organisation_name(self): - return organisations_client.get_service_organisation(self.id).get('name', None) + return self.organisation.name @cached_property def inbound_number(self): diff --git a/app/models/user.py b/app/models/user.py index e54315ed5..e9ec02707 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -2,7 +2,10 @@ from itertools import chain from flask import abort, request, session from flask_login import AnonymousUserMixin, UserMixin +from werkzeug.utils import cached_property +from app.models.organisation import Organisation +from app.notify_client.organisations_api_client import organisations_client from app.utils import is_gov_user roles = { @@ -192,6 +195,16 @@ class User(UserMixin): def is_locked(self): return self.failed_login_count >= self.max_failed_login_count + @property + def email_domain(self): + return self.email_address.split('@')[-1] + + @cached_property + def default_organisation(self): + return Organisation( + organisations_client.get_organisation_by_domain(self.email_domain) + ) + def serialize(self): dct = { "id": self.id, @@ -322,3 +335,7 @@ class AnonymousUser(AnonymousUserMixin): # set the anonymous user so that if a new browser hits us we don't error http://stackoverflow.com/a/19275188 def logged_in_elsewhere(self): return False + + @property + def default_organisation(self): + return Organisation(None) diff --git a/app/notify_client/organisations_api_client.py b/app/notify_client/organisations_api_client.py index d1d082e82..ed54537cc 100644 --- a/app/notify_client/organisations_api_client.py +++ b/app/notify_client/organisations_api_client.py @@ -1,3 +1,5 @@ +from notifications_python_client.errors import HTTPError + from app.notify_client import NotifyAdminAPIClient, _attach_current_user, cache @@ -9,6 +11,16 @@ class OrganisationsClient(NotifyAdminAPIClient): def get_organisation(self, org_id): return self.get(url='/organisations/{}'.format(org_id)) + def get_organisation_by_domain(self, domain): + try: + return self.get( + url='/organisations/by-domain?domain={}'.format(domain), + ) + except HTTPError as error: + if error.status_code == 404: + return None + raise error + def create_organisation(self, name): data = { "name": name From 72f49a9a1e93c313d8fa27eefb8c532726dc9ede Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 4 Apr 2019 11:12:53 +0100 Subject: [PATCH 02/11] Remove dependence on domains.yml from static pages This will allow us to remove the `domains.yml` file, by using information about organisations that is now stored in the database instead. --- app/main/views/index.py | 4 +--- app/templates/views/pricing.html | 2 +- app/templates/views/terms-of-use.html | 2 +- tests/__init__.py | 10 +++++---- tests/app/main/views/test_index.py | 31 +++++++++++++++++---------- tests/conftest.py | 21 ++++++++++++++++++ 6 files changed, 50 insertions(+), 20 deletions(-) diff --git a/app/main/views/index.py b/app/main/views/index.py index 1247c0aa2..f53bcdbb2 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -16,7 +16,7 @@ from app import email_branding_client, letter_branding_client from app.main import main from app.main.forms import FieldWithNoneOption, SearchByNameForm from app.main.views.sub_navigation_dictionaries import features_nav -from app.utils import AgreementInfo, get_logo_cdn_domain +from app.utils import get_logo_cdn_domain @main.route('/') @@ -75,7 +75,6 @@ def pricing(): for cc, country in INTERNATIONAL_BILLING_RATES.items() ], key=lambda x: x[0]), search_form=SearchByNameForm(), - agreement_info=AgreementInfo.from_current_user(), ) @@ -249,7 +248,6 @@ def terms(): return render_template( 'views/terms-of-use.html', navigation_links=features_nav(), - agreement_info=AgreementInfo.from_current_user(), ) diff --git a/app/templates/views/pricing.html b/app/templates/views/pricing.html index cad28eb2e..3a4538716 100644 --- a/app/templates/views/pricing.html +++ b/app/templates/views/pricing.html @@ -123,7 +123,7 @@

How to pay

You can find details of how to pay for Notify in our data sharing and financial agreement.

- {{ agreement_info.as_pricing_paragraph( + {{ current_user.default_organisation.as_pricing_paragraph( pricing_link=url_for('main.sign_in', next=url_for('main.pricing', _anchor='paying')), download_link=url_for('main.agreement'), support_link=url_for('.feedback', ticket_type='ask-question-give-feedback', body='agreement'), diff --git a/app/templates/views/terms-of-use.html b/app/templates/views/terms-of-use.html index f0576bbbd..7962422c5 100644 --- a/app/templates/views/terms-of-use.html +++ b/app/templates/views/terms-of-use.html @@ -21,7 +21,7 @@ Terms of use

- {{ agreement_info.as_terms_of_use_paragraph( + {{ current_user.default_organisation.as_terms_of_use_paragraph( terms_link=url_for('main.sign_in', next=url_for('main.terms')), download_link=url_for('.agreement'), support_link=url_for('.feedback', ticket_type='ask-question-give-feedback', body='agreement'), diff --git a/tests/__init__.py b/tests/__init__.py index c3c934454..cbe1405b3 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -184,7 +184,7 @@ def service_json( def organisation_json( id_='1234', - name='Test Organisation', + name=False, users=None, active=True, created_at=None, @@ -192,6 +192,8 @@ def organisation_json( letter_branding_id=None, email_branding_id=None, domains=None, + crown=True, + agreement_signed=False, ): if users is None: users = [] @@ -199,7 +201,7 @@ def organisation_json( services = [] return { 'id': id_, - 'name': name, + 'name': 'Test Organisation' if name is False else name, 'active': active, 'users': users, 'services': services, @@ -207,8 +209,8 @@ def organisation_json( 'email_branding_id': email_branding_id, 'letter_branding_id': letter_branding_id, 'organisation_type': '', - 'crown': True, - 'agreement_signed': False, + 'crown': crown, + 'agreement_signed': agreement_signed, 'agreement_signed_at': None, 'agreement_signed_by': None, 'domains': domains or [], diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index 211d2b25e..ec0896d29 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -6,7 +6,7 @@ from flask import url_for from app.main.forms import FieldWithNoneOption from tests.conftest import ( - active_user_with_permissions, + mock_get_organisation_by_domain, normalize_spaces, sample_uuid, ) @@ -69,6 +69,7 @@ def test_robots(client): ]) def test_static_pages( client_request, + mock_get_organisation_by_domain, view, ): page = client_request.get('main.{}'.format(view)) @@ -166,13 +167,15 @@ def test_pricing_is_generic_if_user_is_not_logged_in( @pytest.mark.parametrize(( - 'email_address,' + 'name,' + 'agreement_signed,' 'expected_terms_paragraph,' 'expected_terms_link,' 'expected_pricing_paragraph' ), [ ( - 'test@cabinet-office.gov.uk', + 'Cabinet Office', + True, ( 'Your organisation (Cabinet Office) has already accepted ' 'the GOV.UK Notify data sharing and financial agreement.' @@ -184,7 +187,8 @@ def test_pricing_is_generic_if_user_is_not_logged_in( ), ), ( - 'test@aylesburytowncouncil.gov.uk', + 'Aylesbury Town Council', + False, ( 'Your organisation (Aylesbury Town Council) must also ' 'accept our data sharing and financial agreement. Download ' @@ -200,7 +204,8 @@ def test_pricing_is_generic_if_user_is_not_logged_in( ), ), ( - 'larry@downing-street.gov.uk', + None, + None, ( 'Your organisation must also accept our data sharing and ' 'financial agreement. Download the agreement or contact us ' @@ -217,7 +222,8 @@ def test_pricing_is_generic_if_user_is_not_logged_in( ), ), ( - 'michael.fish@metoffice.gov.uk', + 'Met Office', + False, ( 'Your organisation (Met Office) must also accept our data ' 'sharing and financial agreement. Download a copy.' @@ -236,14 +242,17 @@ def test_terms_tells_logged_in_users_what_we_know_about_their_agreement( mocker, fake_uuid, client_request, - email_address, + name, + agreement_signed, expected_terms_paragraph, expected_terms_link, expected_pricing_paragraph, ): - user = active_user_with_permissions(fake_uuid) - user.email_address = email_address - mocker.patch('app.user_api_client.get_user', return_value=user) + mock_get_organisation_by_domain( + mocker, + name=name, + agreement_signed=agreement_signed, + ) terms_page = client_request.get('main.terms') pricing_page = client_request.get('main.pricing') assert normalize_spaces(terms_page.select('main p')[1].text) == expected_terms_paragraph @@ -256,7 +265,7 @@ def test_terms_tells_logged_in_users_what_we_know_about_their_agreement( def test_css_is_served_from_correct_path(client_request): - page = client_request.get('main.pricing') # easy static page + page = client_request.get('main.documentation') # easy static page for index, link in enumerate( page.select('link[rel=stylesheet]') diff --git a/tests/conftest.py b/tests/conftest.py index b07b375da..0ba53ab17 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3125,6 +3125,27 @@ def mock_get_organisation( return mocker.patch('app.organisations_client.get_organisation', side_effect=_get_organisation) +@pytest.fixture(scope='function') +def mock_get_organisation_by_domain( + mocker, + name=False, + crown=True, + agreement_signed=False, +): + def _get_organisation_by_domain(org_id): + return organisation_json( + org_id, + name, + crown=crown, + agreement_signed=agreement_signed, + ) + + return mocker.patch( + 'app.organisations_client.get_organisation_by_domain', + side_effect=_get_organisation_by_domain, + ) + + @pytest.fixture(scope='function') def mock_get_service_organisation(mocker): def _get_service_organisation(service_id): From 98249158cb687b7c7e732a055971894545ffe483 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 4 Apr 2019 11:15:42 +0100 Subject: [PATCH 03/11] Stop trying to infer branding when adding services The API handles this now. --- app/main/views/add_service.py | 10 +---- tests/app/main/views/test_add_service.py | 55 ++---------------------- tests/conftest.py | 2 - 3 files changed, 6 insertions(+), 61 deletions(-) diff --git a/app/main/views/add_service.py b/app/main/views/add_service.py index 8e6d942f1..8fce160ae 100644 --- a/app/main/views/add_service.py +++ b/app/main/views/add_service.py @@ -2,17 +2,15 @@ from flask import current_app, redirect, render_template, session, url_for from flask_login import login_required from notifications_python_client.errors import HTTPError -from app import billing_api_client, email_branding_client, service_api_client +from app import billing_api_client, service_api_client from app.main import main from app.main.forms import CreateServiceForm -from app.utils import AgreementInfo, email_safe, user_is_gov_user +from app.utils import email_safe, user_is_gov_user def _create_service(service_name, organisation_type, email_from, form): free_sms_fragment_limit = current_app.config['DEFAULT_FREE_SMS_FRAGMENT_LIMITS'].get(organisation_type) - domain = 'nhs.uk' if organisation_type == 'nhs' else AgreementInfo.from_current_user().canonical_domain - email_branding = email_branding_client.get_email_branding_id_for_domain(domain) try: service_id = service_api_client.create_service( service_name=service_name, @@ -21,13 +19,9 @@ def _create_service(service_name, organisation_type, email_from, form): restricted=True, user_id=session['user_id'], email_from=email_from, - service_domain=domain ) session['service_id'] = service_id - if email_branding: - service_api_client.update_service(service_id, email_branding=email_branding) - billing_api_client.create_or_update_free_sms_fragment_limit(service_id, free_sms_fragment_limit) return service_id, None diff --git a/tests/app/main/views/test_add_service.py b/tests/app/main/views/test_add_service.py index 5395366e7..18d00b199 100644 --- a/tests/app/main/views/test_add_service.py +++ b/tests/app/main/views/test_add_service.py @@ -56,7 +56,6 @@ def test_should_add_service_and_redirect_to_tour_when_no_services( restricted=True, user_id=api_user_active.id, email_from='testing.the.post', - service_domain=None ) mock_create_service_template.assert_called_once_with( 'Example text message template', @@ -71,10 +70,10 @@ def test_should_add_service_and_redirect_to_tour_when_no_services( mock_create_or_update_free_sms_fragment_limit.assert_called_once_with(101, 25000) -@pytest.mark.parametrize('organisation_type, free_allowance, service_domain', [ - ('central', 250 * 1000, None), - ('local', 25 * 1000, None), - ('nhs', 25 * 1000, 'nhs.uk'), +@pytest.mark.parametrize('organisation_type, free_allowance', [ + ('central', 250 * 1000), + ('local', 25 * 1000), + ('nhs', 25 * 1000), ]) def test_should_add_service_and_redirect_to_dashboard_when_existing_service( app_, @@ -82,11 +81,9 @@ def test_should_add_service_and_redirect_to_dashboard_when_existing_service( mock_create_service, mock_create_service_template, mock_get_services, - mock_update_service, api_user_active, organisation_type, free_allowance, - service_domain, mock_create_or_update_free_sms_fragment_limit, mock_get_all_email_branding, ): @@ -111,56 +108,12 @@ def test_should_add_service_and_redirect_to_dashboard_when_existing_service( restricted=True, user_id=api_user_active.id, email_from='testing.the.post', - service_domain=service_domain ) mock_create_or_update_free_sms_fragment_limit.assert_called_once_with(101, free_allowance) assert len(mock_create_service_template.call_args_list) == 0 assert session['service_id'] == 101 -@pytest.mark.parametrize('organisation_type, email_address, expected_branding', [ - ('central', 'test@example.voa.gsi.gov.uk', '5'), - ('central', 'test@example.voa.gov.uk', '5'), - ('central', 'test@example.gov.uk', None), - # Anyone choosing ‘NHS’ for organisation type gets NHS branding no - # matter what their email domain is (but we look it up based on the - # `nhs.uk` domain to avoid hard-coding a branding ID anywhere) - ('nhs', 'test@example.voa.gov.uk', '4'), - ('nhs', 'test@nhs.uk', '4'), -]) -def test_should_lookup_branding_for_known_domain( - app_, - client_request, - active_user_with_permissions, - mock_create_service, - mock_get_services, - mock_update_service, - mock_create_or_update_free_sms_fragment_limit, - mock_get_all_email_branding, - organisation_type, - email_address, - expected_branding, -): - active_user_with_permissions.email_address = email_address - client_request.login(active_user_with_permissions) - client_request.post( - 'main.add_service', - _data={ - 'name': 'testing the post', - 'organisation_type': organisation_type, - } - ) - mock_get_all_email_branding.assert_called_once_with() - assert mock_create_service.called is True - if expected_branding: - mock_update_service.assert_called_once_with( - 101, - email_branding=expected_branding, - ) - else: - assert mock_update_service.called is False - - def test_should_return_form_errors_when_service_name_is_empty( client_request ): diff --git a/tests/conftest.py b/tests/conftest.py index 0ba53ab17..f29ffb2d2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -625,7 +625,6 @@ def mock_create_service(mocker): restricted, user_id, email_from, - service_domain, ): service = service_json( 101, service_name, [user_id], message_limit=message_limit, restricted=restricted, email_from=email_from) @@ -644,7 +643,6 @@ def mock_create_duplicate_service(mocker): restricted, user_id, email_from, - service_domain, ): json_mock = Mock(return_value={'message': {'name': ["Duplicate service name '{}'".format(service_name)]}}) resp_mock = Mock(status_code=400, json=json_mock) From 835d11d8d6958ac90246d52bacb853fd5fedf2db Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 4 Apr 2019 11:17:35 +0100 Subject: [PATCH 04/11] Remove domains.yml dependence from agreement pages This will allow us to remove the `domains.yml` file, by using information about organisations that is now stored in the database instead. --- app/main/views/agreement.py | 16 +++------- tests/app/main/views/test_agreement.py | 43 ++++++++++++++------------ 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/app/main/views/agreement.py b/app/main/views/agreement.py index 3862be01c..88fedeb8c 100644 --- a/app/main/views/agreement.py +++ b/app/main/views/agreement.py @@ -1,21 +1,17 @@ from flask import abort, render_template, request, send_file, url_for -from flask_login import login_required +from flask_login import current_user, login_required from app.main import main from app.main.views.sub_navigation_dictionaries import features_nav from app.s3_client.s3_mou_client import get_mou -from app.utils import AgreementInfo @main.route('/agreement') @login_required def agreement(): - - agreement_info = AgreementInfo.from_current_user() - return render_template( - 'views/{}.html'.format(agreement_info.as_jinja_template), - owner=agreement_info.owner, + 'views/{}.html'.format(current_user.default_organisation.as_jinja_template), + owner=current_user.default_organisation.name, navigation_links=features_nav(), ) @@ -24,7 +20,7 @@ def agreement(): @login_required def download_agreement(): return send_file(**get_mou( - AgreementInfo.from_current_user().crown_status_or_404 + current_user.default_organisation.crown_status_or_404 )) @@ -40,10 +36,8 @@ def public_agreement(variant): organisation_is_crown=(variant == 'crown') )) - agreement_info = AgreementInfo.from_current_user() - return render_template( 'views/agreement-public.html', - owner=agreement_info.owner, + owner=current_user.default_organisation.name, download_link=url_for('.public_download_agreement', variant=variant), ) diff --git a/tests/app/main/views/test_agreement.py b/tests/app/main/views/test_agreement.py index 8da0fd3e2..0b3806c15 100644 --- a/tests/app/main/views/test_agreement.py +++ b/tests/app/main/views/test_agreement.py @@ -4,7 +4,7 @@ from io import BytesIO import pytest from flask import url_for -from tests.conftest import active_user_with_permissions +from tests.conftest import mock_get_organisation_by_domain class _MockS3Object(): @@ -16,22 +16,22 @@ class _MockS3Object(): return {'Body': BytesIO(self.data)} -@pytest.mark.parametrize('email_address, expected_links', [ +@pytest.mark.parametrize('agreement_signed, crown, expected_links', [ ( - 'test@cabinet-office.gov.uk', + True, True, [ partial(url_for, 'main.download_agreement'), ] ), ( - 'test@aylesburytowncouncil.gov.uk', + False, False, [ partial(url_for, 'main.download_agreement'), lambda: 'mailto:notify-support@digital.cabinet-office.gov.uk', ] ), ( - 'test@unknown.gov.uk', + None, None, [ partial(url_for, 'main.public_download_agreement', variant='crown'), partial(url_for, 'main.public_download_agreement', variant='non-crown'), @@ -44,12 +44,15 @@ def test_show_agreement_page( client_request, mocker, fake_uuid, - email_address, + agreement_signed, + crown, expected_links, ): - user = active_user_with_permissions(fake_uuid) - user.email_address = email_address - mocker.patch('app.user_api_client.get_user', return_value=user) + mock_get_organisation_by_domain( + mocker, + crown=crown, + agreement_signed=agreement_signed, + ) page = client_request.get('main.agreement') links = page.select('main .column-two-thirds a') assert len(links) == len(expected_links) @@ -57,14 +60,14 @@ def test_show_agreement_page( assert link['href'] == expected_links[index]() -@pytest.mark.parametrize('email_address, expected_file_fetched, expected_file_served', [ +@pytest.mark.parametrize('crown, expected_file_fetched, expected_file_served', [ ( - 'test@cabinet-office.gov.uk', + True, 'crown.pdf', 'GOV.UK Notify data sharing and financial agreement.pdf', ), ( - 'test@aylesburytowncouncil.gov.uk', + False, 'non-crown.pdf', 'GOV.UK Notify data sharing and financial agreement (non-crown).pdf', ), @@ -73,7 +76,7 @@ def test_downloading_agreement( logged_in_client, mocker, fake_uuid, - email_address, + crown, expected_file_fetched, expected_file_served, ): @@ -81,9 +84,10 @@ def test_downloading_agreement( 'app.s3_client.s3_mou_client.get_s3_object', return_value=_MockS3Object(b'foo') ) - user = active_user_with_permissions(fake_uuid) - user.email_address = email_address - mocker.patch('app.user_api_client.get_user', return_value=user) + mock_get_organisation_by_domain( + mocker, + crown=crown, + ) response = logged_in_client.get(url_for('main.download_agreement')) assert response.status_code == 200 assert response.get_data() == b'foo' @@ -103,9 +107,10 @@ def test_agreement_cant_be_downloaded_unknown_crown_status( 'app.s3_client.s3_mou_client.get_s3_object', return_value=_MockS3Object() ) - user = active_user_with_permissions(fake_uuid) - user.email_address = 'test@unknown.gov.uk' - mocker.patch('app.user_api_client.get_user', return_value=user) + mock_get_organisation_by_domain( + mocker, + crown=None, + ) response = logged_in_client.get(url_for('main.download_agreement')) assert response.status_code == 404 assert mock_get_s3_object.call_args_list == [] From 3565ffc33f7c17cb42ac3d01880a0bd44215d147 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 4 Apr 2019 11:23:40 +0100 Subject: [PATCH 05/11] Remove dependence on domains.yml from settings Settings looked at `domains.yml` when users were making go live requests or email branding requests. This will allow us to remove the `domains.yml` file, by using information about organisations that is now stored in the database instead. --- app/main/views/service_settings.py | 24 +++---- app/models/organisation.py | 12 ++-- app/models/service.py | 4 -- app/templates/views/service-settings.html | 2 +- tests/app/main/views/test_service_settings.py | 70 ++++++++++++------- tests/conftest.py | 14 +++- 6 files changed, 72 insertions(+), 54 deletions(-) diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py index ba20ccbb1..966ca1649 100644 --- a/app/main/views/service_settings.py +++ b/app/main/views/service_settings.py @@ -54,7 +54,6 @@ from app.main.forms import ( branding_options_dict, ) from app.utils import ( - AgreementInfo, email_safe, user_has_permissions, user_is_gov_user, @@ -180,7 +179,7 @@ def estimate_usage(service_id): @user_has_permissions('manage_service') def request_to_go_live(service_id): - agreement_signed = AgreementInfo.from_current_user().agreement_signed + agreement_signed = current_service.organisation.agreement_signed return render_template( 'views/service-settings/request-to-go-live.html', @@ -226,7 +225,7 @@ def submit_request_to_go_live(service_id): service_name=current_service.name, service_dashboard=url_for('main.service_dashboard', service_id=current_service.id, _external=True), organisation_type=str(current_service.organisation_type).title(), - agreement=AgreementInfo.from_current_user().as_human_readable, + agreement=current_service.organisation.as_human_readable(current_user.email_domain), checklist=current_service.go_live_checklist_completed_as_yes_no, volume_email=print_if_number(current_service.volume_email), volume_email_formatted=format_if_number(current_service.volume_email), @@ -237,7 +236,7 @@ def submit_request_to_go_live(service_id): research_consent='Yes' if current_service.consent_to_research else 'No', existing_live='Yes' if user_api_client.user_has_live_services(current_user) else 'No', service_id=current_service.id, - organisation=AgreementInfo.from_current_user().owner, + organisation=current_service.organisation.name, user_name=current_user.name, user_email=current_user.email_address, date=datetime.now(tz=pytz.timezone('Europe/London')).strftime('%d/%m/%Y'), @@ -245,7 +244,7 @@ def submit_request_to_go_live(service_id): ticket_type=zendesk_client.TYPE_QUESTION, user_email=current_user.email_address, user_name=current_user.name, - tags=get_request_to_go_live_tags(current_service, current_user), + tags=get_request_to_go_live_tags(current_service), ) flash('Thanks for your request to go live. We’ll get back to you within one working day.', 'default') @@ -985,7 +984,7 @@ def branding_request(service_id): '\nCurrent branding: {current_branding}' '\nBranding requested: {branding_requested}' ).format( - organisation=AgreementInfo.from_current_user().as_info_for_branding_request, + organisation=current_service.organisation.as_info_for_branding_request(current_user.email_domain), service_name=current_service.name, dashboard_url=url_for('main.service_dashboard', service_id=current_service.id, _external=True), current_branding=current_service.email_branding_name, @@ -1073,20 +1072,17 @@ def check_contact_details_type(contact_details): return 'phone_number' -def get_request_to_go_live_tags(service, user): - return list(_get_request_to_go_live_tags( - service, - AgreementInfo.from_user(user).agreement_signed, - )) +def get_request_to_go_live_tags(service): + return list(_get_request_to_go_live_tags(service)) -def _get_request_to_go_live_tags(service, agreement_signed): +def _get_request_to_go_live_tags(service): BASE = 'notify_request_to_go_live' yield BASE - if service.go_live_checklist_completed and agreement_signed: + if service.go_live_checklist_completed and service.organisation.agreement_signed: yield BASE + '_complete' return @@ -1094,7 +1090,7 @@ def _get_request_to_go_live_tags(service, agreement_signed): (True, ''), (not service.volumes, '_volumes'), (not service.go_live_checklist_completed, '_checklist'), - (not agreement_signed, '_mou'), + (not service.organisation.agreement_signed, '_mou'), (service.needs_to_add_email_reply_to_address, '_email_reply_to'), (not service.has_team_members, '_team_member'), (not service.has_templates, '_template_content'), diff --git a/app/models/organisation.py b/app/models/organisation.py index d3753a431..e3f827ff7 100644 --- a/app/models/organisation.py +++ b/app/models/organisation.py @@ -31,13 +31,12 @@ class Organisation(JSONModel): def crown_status(self): return self.crown - @property - def as_human_readable(self): + def as_human_readable(self, fallback_domain): if 'dwp.' in ''.join(self.domains): return 'DWP - Requires OED approval' if self.agreement_signed: return 'Yes, on behalf of {}'.format(self.name) - elif self.owner: + elif self.name: return '{} (organisation is {}, {})'.format( { False: 'No', @@ -51,11 +50,10 @@ class Organisation(JSONModel): }.get(self.crown_status), ) else: - return 'Can’t tell (domain is {})'.format(self._domain) + return 'Can’t tell (domain is {})'.format(fallback_domain) - @property - def as_info_for_branding_request(self): - return self.owner or 'Can’t tell (domain is {})'.format(self._domain) + def as_info_for_branding_request(self, fallback_domain): + return self.name or 'Can’t tell (domain is {})'.format(fallback_domain) @property def as_jinja_template(self): diff --git a/app/models/service.py b/app/models/service.py index 425fceb02..c28e6ff62 100644 --- a/app/models/service.py +++ b/app/models/service.py @@ -396,10 +396,6 @@ class Service(JSONModel): organisations_client.get_service_organisation(self.id) ) - @property - def organisation_name(self): - return self.organisation.name - @cached_property def inbound_number(self): return inbound_number_client.get_inbound_sms_number_for_service(self.id)['data'].get('number', '') diff --git a/app/templates/views/service-settings.html b/app/templates/views/service-settings.html index 34b725d04..0a18bde59 100644 --- a/app/templates/views/service-settings.html +++ b/app/templates/views/service-settings.html @@ -300,7 +300,7 @@ {% call row() %} {{ text_field('Organisation')}} - {{ optional_text_field(current_service.organisation_name) }} + {{ optional_text_field(current_service.organisation.name) }} {{ edit_field('Change', url_for('.link_service_to_organisation', service_id=current_service.id)) }} {% endcall %} {% call row() %} diff --git a/tests/app/main/views/test_service_settings.py b/tests/app/main/views/test_service_settings.py index 847bc3536..6fcabf559 100644 --- a/tests/app/main/views/test_service_settings.py +++ b/tests/app/main/views/test_service_settings.py @@ -1,4 +1,3 @@ -from collections import namedtuple from functools import partial from unittest.mock import ANY, PropertyMock, call from urllib.parse import parse_qs, urlparse @@ -31,6 +30,7 @@ from tests.conftest import ( get_non_default_letter_contact_block, get_non_default_reply_to_email_address, get_non_default_sms_sender, + mock_get_service_organisation, multiple_letter_contact_blocks, multiple_reply_to_email_addresses, multiple_sms_senders, @@ -101,7 +101,7 @@ def mock_get_service_settings_page_common( 'Label Value Action', 'Live Off Change', 'Count in list of live services Yes Change', - 'Organisation Org 1 Change', + 'Organisation Test Organisation Change', 'Organisation type Central Change', 'Free text message allowance 250,000 Change', 'Email branding GOV.UK Change', @@ -616,6 +616,7 @@ def test_should_check_if_estimated_volumes_provided( single_reply_to_email_address, mock_get_service_templates, mock_get_users_by_service, + mock_get_service_organisation, volumes, consent_to_research, expected_estimated_volumes_item, @@ -675,6 +676,7 @@ def test_should_check_if_estimated_volumes_provided( def test_should_check_for_sending_things_right( client_request, mocker, + mock_get_service_organisation, single_sms_sender, count_of_users_with_manage_service, expected_user_checklist_item, @@ -685,7 +687,6 @@ def test_should_check_for_sending_things_right( reply_to_email_addresses, expected_reply_to_checklist_item, ): - def _templates_by_type(template_type): return { 'email': list(range(0, count_of_email_templates)), @@ -751,25 +752,22 @@ def test_should_not_show_go_live_button_if_checklist_not_complete( mocker, mock_get_service_templates, mock_get_users_by_service, + mock_get_service_organisation, single_sms_sender, checklist_completed, agreement_signed, expected_button, ): - - def _agreement_info(): - return namedtuple( - 'AgreementInfo', ['agreement_signed'] - )(agreement_signed=agreement_signed) - mocker.patch( 'app.models.service.Service.go_live_checklist_completed', new_callable=PropertyMock, return_value=checklist_completed, ) mocker.patch( - 'app.utils.AgreementInfo.from_current_user', - side_effect=_agreement_info, + 'app.models.organisation.Organisation.agreement_signed', + new_callable=PropertyMock, + return_value=agreement_signed, + create=True, ) for channel in ('email', 'sms', 'letter'): @@ -894,13 +892,13 @@ def test_should_check_for_sms_sender_on_go_live( client_request, service_one, mocker, + mock_get_service_organisation, organisation_type, count_of_sms_templates, sms_senders, expected_sms_sender_checklist_item, estimated_sms_volume, ): - service_one['organisation_type'] = organisation_type def _templates_by_type(template_type): @@ -953,18 +951,18 @@ def test_should_check_for_sms_sender_on_go_live( mock_get_sms_senders.assert_called_once_with(SERVICE_ONE_ID) -@pytest.mark.parametrize('email_address, expected_item', ( +@pytest.mark.parametrize('agreement_signed, expected_item', ( pytest.param( - 'test@unknown.gov.uk', + None, '', marks=pytest.mark.xfail(raises=IndexError) ), ( - 'test@education.gov.uk', + True, 'Sign our data sharing and financial agreement Completed', ), ( - 'test@aylesbury.gov.uk', + False, 'Sign our data sharing and financial agreement Not completed', ), )) @@ -972,7 +970,7 @@ def test_should_check_for_mou_on_request_to_go_live( client_request, service_one, mocker, - email_address, + agreement_signed, expected_item, ): mocker.patch( @@ -1000,9 +998,10 @@ def test_should_check_for_mou_on_request_to_go_live( return_value=None, ) - user = active_user_with_permissions(uuid4()) - user.email_address = email_address - client_request.login(user) + mock_get_service_organisation( + mocker, + agreement_signed=agreement_signed, + ) page = client_request.get( 'main.request_to_go_live', service_id=SERVICE_ONE_ID @@ -1017,6 +1016,7 @@ def test_non_gov_user_is_told_they_cant_go_live( client_request, api_nongov_user_active, mocker, + mock_get_service_organisation, ): mocker.patch( 'app.main.views.service_settings.user_api_client.get_count_of_users_with_permission', @@ -1287,7 +1287,6 @@ def test_should_redirect_after_request_to_go_live( active_user_with_permissions, single_reply_to_email_address, single_letter_contact_block, - mock_get_service_organisation, mock_get_organisations_and_services_for_user, single_sms_sender, mock_get_service_settings_page_common, @@ -1298,6 +1297,11 @@ def test_should_redirect_after_request_to_go_live( formatted_displayed_volumes, extra_tags, ): + mock_get_service_organisation( + mocker, + name=None, + agreement_signed=None, + ) for channel, volume in volumes: mocker.patch( 'app.models.service.Service.volume_{}'.format(channel), @@ -1516,6 +1520,11 @@ def test_ready_to_go_live( agreement_signed, expected_tags, ): + mock_get_service_organisation( + mocker, + agreement_signed=agreement_signed, + ) + for prop in { 'has_team_members', 'has_templates', @@ -1547,8 +1556,7 @@ def test_ready_to_go_live( }).go_live_checklist_completed_as_yes_no == expected_readyness assert list(app.main.views.service_settings._get_request_to_go_live_tags( - app.models.service.Service({'id': SERVICE_ONE_ID}), - agreement_signed, + app.models.service.Service({'id': SERVICE_ONE_ID}) )) == expected_tags @@ -3966,6 +3974,10 @@ def test_show_email_branding_request_page_when_email_branding_is_set( ('org_banner', 'Your logo on a colour'), pytest.param('foo', 'Nope', marks=pytest.mark.xfail(raises=AssertionError)), )) +@pytest.mark.parametrize('org_name, expected_organisation', ( + (None, 'Can’t tell (domain is user.gov.uk)'), + ('Test Organisation', 'Test Organisation'), +)) def test_submit_email_branding_request( client_request, mocker, @@ -3974,10 +3986,16 @@ def test_submit_email_branding_request( mock_get_service_settings_page_common, no_reply_to_email_addresses, no_letter_contact_blocks, - mock_get_service_organisation, single_sms_sender, + org_name, + expected_organisation, ): + mock_get_service_organisation( + mocker, + name=org_name, + ) + zendesk = mocker.patch( 'app.main.views.service_settings.zendesk_client.create_ticket', autospec=True, @@ -3993,14 +4011,14 @@ def test_submit_email_branding_request( zendesk.assert_called_once_with( message='\n'.join([ - 'Organisation: Can’t tell (domain is user.gov.uk)', + 'Organisation: {}', 'Service: service one', 'http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb', '', '---', 'Current branding: GOV.UK', 'Branding requested: {}', - ]).format(requested_branding), + ]).format(expected_organisation, requested_branding), subject='Email branding request - service one', ticket_type='question', user_email='test@user.gov.uk', diff --git a/tests/conftest.py b/tests/conftest.py index f29ffb2d2..21274f0ab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3145,9 +3145,19 @@ def mock_get_organisation_by_domain( @pytest.fixture(scope='function') -def mock_get_service_organisation(mocker): +def mock_get_service_organisation( + mocker, + name=False, + crown=True, + agreement_signed=None, +): def _get_service_organisation(service_id): - return organisation_json('7aa5d4e9-4385-4488-a489-07812ba13383', 'Org 1') + return organisation_json( + '7aa5d4e9-4385-4488-a489-07812ba13383', + name, + crown=crown, + agreement_signed=agreement_signed, + ) return mocker.patch('app.organisations_client.get_service_organisation', side_effect=_get_service_organisation) From 9684e962ae4850bb4d954beb0db2ee5c8f69eeff Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 4 Apr 2019 11:27:05 +0100 Subject: [PATCH 06/11] Refactor go live tags into service model Since this function only takes one argument, a service, it might as well be a method of the service. --- app/main/views/service_settings.py | 30 +------------------ app/models/service.py | 27 +++++++++++++++++ tests/app/main/views/test_service_settings.py | 6 ++-- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py index 966ca1649..409b9ad2e 100644 --- a/app/main/views/service_settings.py +++ b/app/main/views/service_settings.py @@ -244,7 +244,7 @@ def submit_request_to_go_live(service_id): ticket_type=zendesk_client.TYPE_QUESTION, user_email=current_user.email_address, user_name=current_user.name, - tags=get_request_to_go_live_tags(current_service), + tags=current_service.request_to_go_live_tags, ) flash('Thanks for your request to go live. We’ll get back to you within one working day.', 'default') @@ -1072,34 +1072,6 @@ def check_contact_details_type(contact_details): return 'phone_number' -def get_request_to_go_live_tags(service): - return list(_get_request_to_go_live_tags(service)) - - -def _get_request_to_go_live_tags(service): - - BASE = 'notify_request_to_go_live' - - yield BASE - - if service.go_live_checklist_completed and service.organisation.agreement_signed: - yield BASE + '_complete' - return - - for test, tag in ( - (True, ''), - (not service.volumes, '_volumes'), - (not service.go_live_checklist_completed, '_checklist'), - (not service.organisation.agreement_signed, '_mou'), - (service.needs_to_add_email_reply_to_address, '_email_reply_to'), - (not service.has_team_members, '_team_member'), - (not service.has_templates, '_template_content'), - (service.needs_to_change_sms_sender, '_sms_sender'), - ): - if test: - yield BASE + '_incomplete' + tag - - def print_if_number(value): return value if isinstance(value, int) else '' diff --git a/app/models/service.py b/app/models/service.py index c28e6ff62..c0b1b5e6b 100644 --- a/app/models/service.py +++ b/app/models/service.py @@ -543,3 +543,30 @@ class Service(JSONModel): def get_api_key(self, id): return self._get_by_id(self.api_keys, id) + + @property + def request_to_go_live_tags(self): + return list(self._get_request_to_go_live_tags()) + + def _get_request_to_go_live_tags(self): + + BASE = 'notify_request_to_go_live' + + yield BASE + + if self.go_live_checklist_completed and self.organisation.agreement_signed: + yield BASE + '_complete' + return + + for test, tag in ( + (True, ''), + (not self.volumes, '_volumes'), + (not self.go_live_checklist_completed, '_checklist'), + (not self.organisation.agreement_signed, '_mou'), + (self.needs_to_add_email_reply_to_address, '_email_reply_to'), + (not self.has_team_members, '_team_member'), + (not self.has_templates, '_template_content'), + (self.needs_to_change_sms_sender, '_sms_sender'), + ): + if test: + yield BASE + '_incomplete' + tag diff --git a/tests/app/main/views/test_service_settings.py b/tests/app/main/views/test_service_settings.py index 6fcabf559..78d25e59c 100644 --- a/tests/app/main/views/test_service_settings.py +++ b/tests/app/main/views/test_service_settings.py @@ -1555,9 +1555,9 @@ def test_ready_to_go_live( 'id': SERVICE_ONE_ID }).go_live_checklist_completed_as_yes_no == expected_readyness - assert list(app.main.views.service_settings._get_request_to_go_live_tags( - app.models.service.Service({'id': SERVICE_ONE_ID}) - )) == expected_tags + assert app.models.service.Service( + {'id': SERVICE_ONE_ID} + ).request_to_go_live_tags == expected_tags @pytest.mark.parametrize('route', [ From 470b8a2912c4c42318a7bc199d094825acc2e92b Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 4 Apr 2019 11:09:09 +0100 Subject: [PATCH 07/11] Remove domains from branding forms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’re deprecating storing the domain as text on a branding in favour of a database relationship between branding and organisation. We need to do this now in order to remove the validation on these fields (which depends on the data in `domains.yml`) --- app/main/forms.py | 19 +-- app/main/validators.py | 33 +---- app/main/views/email_branding.py | 3 - app/main/views/letter_branding.py | 13 +- app/notify_client/email_branding_client.py | 12 +- app/notify_client/letter_branding_client.py | 7 +- .../views/email-branding/manage-branding.html | 1 - .../views/email-branding/select-branding.html | 7 -- .../manage-letter-branding.html | 1 - .../select-letter-branding.html | 7 -- tests/app/main/views/test_email_branding.py | 119 ++---------------- tests/app/main/views/test_letter_branding.py | 95 ++++---------- .../test_email_branding_client.py | 8 +- .../test_letter_branding_client.py | 8 +- tests/conftest.py | 13 +- 15 files changed, 48 insertions(+), 298 deletions(-) diff --git a/app/main/forms.py b/app/main/forms.py index 5878b129c..184f65d5f 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -36,10 +36,8 @@ from wtforms.widgets import CheckboxInput, ListWidget from app.main.validators import ( Blacklist, - CanonicalGovernmentDomain, CsvFileValidator, DoesNotStartWithDoubleZero, - KnownGovernmentDomain, LettersNumbersAndFullStopsOnly, NoCommasInPlaceHolders, OnlyGSMCharacters, @@ -47,7 +45,7 @@ from app.main.validators import ( ValidGovEmail, ) from app.models.user import permissions, roles -from app.utils import AgreementInfo, guess_name_from_email_address +from app.utils import guess_name_from_email_address def get_time_value_and_label(future_time): @@ -990,23 +988,9 @@ class PreviewBranding(StripWhitespaceForm): branding_style = HiddenFieldWithNoneOption('branding_style') -class GovernmentDomainField(StringField): - validators = [ - KnownGovernmentDomain(), - CanonicalGovernmentDomain(), - ] - - def post_validate(self, form, validation_stopped): - if self.data == '': - self.data = None - if self.data and not self.errors: - self.data = AgreementInfo(self.data).canonical_domain - - class ServiceUpdateEmailBranding(StripWhitespaceForm): name = StringField('Name of brand') text = StringField('Text') - domain = GovernmentDomainField('Domain') colour = StringField( 'Colour', validators=[ @@ -1041,7 +1025,6 @@ class SVGFileUpload(StripWhitespaceForm): class ServiceLetterBrandingDetails(StripWhitespaceForm): name = StringField('Name of brand', validators=[DataRequired()]) - domain = GovernmentDomainField('Domain') class PDFUploadForm(StripWhitespaceForm): diff --git a/app/main/validators.py b/app/main/validators.py index 9b27e09e9..216038726 100644 --- a/app/main/validators.py +++ b/app/main/validators.py @@ -11,7 +11,7 @@ from wtforms.validators import Email from app import formatted_list from app.main._blacklisted_passwords import blacklisted_passwords -from app.utils import AgreementInfo, Spreadsheet, is_gov_user +from app.utils import Spreadsheet, is_gov_user class Blacklist: @@ -111,34 +111,3 @@ class DoesNotStartWithDoubleZero: def __call__(self, form, field): if field.data and field.data.startswith("00"): raise ValidationError(self.message) - - -class KnownGovernmentDomain: - - message = 'Not a known government domain (you might need to update domains.yml)' - - def __call__(self, form, field): - if field.data and AgreementInfo(field.data).owner is None: - raise ValidationError(self.message) - - -class CanonicalGovernmentDomain: - - message = 'Not {} domain (use {} if appropriate)' - - def __call__(self, form, field): - - if not field.data: - return - - domain = AgreementInfo(field.data) - - if not domain.is_canonical: - raise ValidationError( - self.message.format('a canonical', domain.canonical_domain) - ) - - if field.data != domain.canonical_domain: - raise ValidationError( - self.message.format('an organisation-level', domain.canonical_domain) - ) diff --git a/app/main/views/email_branding.py b/app/main/views/email_branding.py index 7fbb9aec8..521b7b1e8 100644 --- a/app/main/views/email_branding.py +++ b/app/main/views/email_branding.py @@ -39,7 +39,6 @@ def update_email_branding(branding_id, logo=None): name=email_branding['name'], text=email_branding['text'], colour=email_branding['colour'], - domain=email_branding['domain'], brand_type=email_branding['brand_type'] ) @@ -67,7 +66,6 @@ def update_email_branding(branding_id, logo=None): name=form.name.data, text=form.text.data, colour=form.colour.data, - domain=form.domain.data, brand_type=form.brand_type.data, ) @@ -115,7 +113,6 @@ def create_email_branding(logo=None): name=form.name.data, text=form.text.data, colour=form.colour.data, - domain=form.domain.data, brand_type=form.brand_type.data, ) diff --git a/app/main/views/letter_branding.py b/app/main/views/letter_branding.py index c2a126878..634dfa777 100644 --- a/app/main/views/letter_branding.py +++ b/app/main/views/letter_branding.py @@ -56,7 +56,6 @@ def update_letter_branding(branding_id, logo=None): file_upload_form = SVGFileUpload() letter_branding_details_form = ServiceLetterBrandingDetails( name=letter_branding['name'], - domain=letter_branding['domain'] ) file_upload_form_submitted = file_upload_form.file.data @@ -87,7 +86,6 @@ def update_letter_branding(branding_id, logo=None): branding_id=branding_id, filename=db_filename, name=letter_branding_details_form.name.data, - domain=letter_branding_details_form.domain.data ) return redirect(url_for('main.letter_branding')) @@ -98,7 +96,6 @@ def update_letter_branding(branding_id, logo=None): branding_id=branding_id, filename=db_filename, name=letter_branding_details_form.name.data, - domain=letter_branding_details_form.domain.data ) upload_letter_logos(logo, db_filename, png_file, session['user_id']) @@ -106,9 +103,7 @@ def update_letter_branding(branding_id, logo=None): return redirect(url_for('main.letter_branding')) except HTTPError as e: - if 'domain' in e.message: - letter_branding_details_form.domain.errors.append(e.message['domain'][0]) - elif 'name' in e.message: + if 'name' in e.message: letter_branding_details_form.name.errors.append(e.message['name'][0]) else: raise e @@ -118,7 +113,6 @@ def update_letter_branding(branding_id, logo=None): branding_id=branding_id, filename=letter_branding['filename'], name=letter_branding['name'], - domain=letter_branding['domain'] ) file_upload_form.file.errors = ['Error saving uploaded file - try uploading again'] @@ -165,7 +159,6 @@ def create_letter_branding(logo=None): letter_branding_client.create_letter_branding( filename=db_filename, name=letter_branding_details_form.name.data, - domain=letter_branding_details_form.domain.data, ) upload_letter_logos(logo, db_filename, png_file, session['user_id']) @@ -173,9 +166,7 @@ def create_letter_branding(logo=None): return redirect(url_for('main.letter_branding')) except HTTPError as e: - if 'domain' in e.message: - letter_branding_details_form.domain.errors.append(e.message['domain'][0]) - elif 'name' in e.message: + if 'name' in e.message: letter_branding_details_form.name.errors.append(e.message['name'][0]) else: raise e diff --git a/app/notify_client/email_branding_client.py b/app/notify_client/email_branding_client.py index 776c0d1e6..469e6f7c5 100644 --- a/app/notify_client/email_branding_client.py +++ b/app/notify_client/email_branding_client.py @@ -14,33 +14,25 @@ class EmailBrandingClient(NotifyAdminAPIClient): brandings.sort(key=lambda branding: branding[sort_key].lower()) return brandings - def get_email_branding_id_for_domain(self, domain): - for branding in self.get_all_email_branding(): - if domain and branding.get('domain') == domain: - return branding['id'] - return None - @cache.delete('email_branding') - def create_email_branding(self, logo, name, text, colour, domain, brand_type): + def create_email_branding(self, logo, name, text, colour, brand_type): data = { "logo": logo, "name": name, "text": text, "colour": colour, - "domain": domain, "brand_type": brand_type } return self.post(url="/email-branding", data=data) @cache.delete('email_branding') @cache.delete('email_branding-{branding_id}') - def update_email_branding(self, branding_id, logo, name, text, colour, domain, brand_type): + def update_email_branding(self, branding_id, logo, name, text, colour, brand_type): data = { "logo": logo, "name": name, "text": text, "colour": colour, - "domain": domain, "brand_type": brand_type } return self.post(url="/email-branding/{}".format(branding_id), data=data) diff --git a/app/notify_client/letter_branding_client.py b/app/notify_client/letter_branding_client.py index 0a577d55e..736d9d025 100644 --- a/app/notify_client/letter_branding_client.py +++ b/app/notify_client/letter_branding_client.py @@ -12,22 +12,19 @@ class LetterBrandingClient(NotifyAdminAPIClient): return self.get(url='/letter-branding') @cache.delete('letter_branding') - def create_letter_branding(self, filename, name, domain): + def create_letter_branding(self, filename, name): data = { "filename": filename, "name": name, - "domain": domain, } return self.post(url="/letter-branding", data=data) @cache.delete('letter_branding') @cache.delete('letter_branding-{branding_id}') - def update_letter_branding(self, branding_id, filename, name, domain): + def update_letter_branding(self, branding_id, filename, name): data = { "filename": filename, "name": name, - "domain": domain, - } return self.post(url="/letter-branding/{}".format(branding_id), data=data) diff --git a/app/templates/views/email-branding/manage-branding.html b/app/templates/views/email-branding/manage-branding.html index dad8e212d..42d695786 100644 --- a/app/templates/views/email-branding/manage-branding.html +++ b/app/templates/views/email-branding/manage-branding.html @@ -28,7 +28,6 @@

{{textbox(form.name)}}
{{textbox(form.text)}}
{{ textbox(form.colour, width='1-4', colour_preview=True) }} -
{{textbox(form.domain)}}
{{ radios(form.brand_type) }} {{ page_footer( 'Save', diff --git a/app/templates/views/email-branding/select-branding.html b/app/templates/views/email-branding/select-branding.html index 2d9f703f7..27c8086ef 100644 --- a/app/templates/views/email-branding/select-branding.html +++ b/app/templates/views/email-branding/select-branding.html @@ -24,13 +24,6 @@ {{ brand.name or 'Unnamed' }} -

- {% if brand.domain %} - Default for {{ brand.domain }} - {% else %} - – - {% endif %} -

{% endfor %} diff --git a/app/templates/views/letter-branding/manage-letter-branding.html b/app/templates/views/letter-branding/manage-letter-branding.html index 1dd6c7b8a..e815d1057 100644 --- a/app/templates/views/letter-branding/manage-letter-branding.html +++ b/app/templates/views/letter-branding/manage-letter-branding.html @@ -25,7 +25,6 @@ {% call form_wrapper() %}
{{textbox(letter_branding_details_form.name)}}
-
{{textbox(letter_branding_details_form.domain)}}
{{ page_footer( 'Save', button_name='operation', diff --git a/app/templates/views/letter-branding/select-letter-branding.html b/app/templates/views/letter-branding/select-letter-branding.html index b915c3b73..3b50744f5 100644 --- a/app/templates/views/letter-branding/select-letter-branding.html +++ b/app/templates/views/letter-branding/select-letter-branding.html @@ -24,13 +24,6 @@ {{ brand.name }}
-

- {% if brand.domain %} - Default for {{ brand.domain }} - {% else %} - – - {% endif %} -

{% endfor %} diff --git a/tests/app/main/views/test_email_branding.py b/tests/app/main/views/test_email_branding.py index be702a3bd..e49f8ecef 100644 --- a/tests/app/main/views/test_email_branding.py +++ b/tests/app/main/views/test_email_branding.py @@ -28,7 +28,6 @@ def test_email_branding_page_shows_full_branding_list( links = page.select('.message-name a') brand_names = [normalize_spaces(link.text) for link in links] hrefs = [link['href'] for link in links] - brand_hints = [normalize_spaces(hint.text) for hint in page.select('.message-type')] assert normalize_spaces( page.select_one('h1').text @@ -36,14 +35,12 @@ def test_email_branding_page_shows_full_branding_list( assert page.select_one('.column-three-quarters a')['href'] == url_for('main.create_email_branding') - assert list(zip( - brand_names, brand_hints - )) == [ - ('org 1', '–'), - ('org 2', '–'), - ('org 3', '–'), - ('org 4', 'Default for nhs.uk'), - ('org 5', 'Default for voa.gov.uk'), + assert brand_names == [ + 'org 1', + 'org 2', + 'org 3', + 'org 4', + 'org 5', ] assert hrefs == [ url_for('.update_email_branding', branding_id=1), @@ -70,7 +67,6 @@ def test_edit_email_branding_shows_the_correct_branding_info( assert page.select_one('#name').attrs.get('value') == 'Organisation name' assert page.select_one('#text').attrs.get('value') == 'Organisation text' assert page.select_one('#colour').attrs.get('value') == '#f00' - assert page.select_one('#domain').attrs.get('value') == 'sample.com' def test_create_email_branding_does_not_show_any_branding_info( @@ -89,27 +85,19 @@ def test_create_email_branding_does_not_show_any_branding_info( assert page.select_one('#name').attrs.get('value') == '' assert page.select_one('#text').attrs.get('value') == '' assert page.select_one('#colour').attrs.get('value') == '' - assert page.select_one('#domain').attrs.get('value') == '' -@pytest.mark.parametrize('posted_domain, persisted_domain', [ - ('voa.gov.uk', 'voa.gov.uk'), - ('', None), -]) def test_create_new_email_branding_without_logo( logged_in_platform_admin_client, mocker, fake_uuid, mock_create_email_branding, - posted_domain, - persisted_domain, ): data = { 'logo': None, 'colour': '#ff0000', 'text': 'new text', 'name': 'new name', - 'domain': posted_domain, 'brand_type': 'org' } @@ -128,96 +116,11 @@ def test_create_new_email_branding_without_logo( name=data['name'], text=data['text'], colour=data['colour'], - domain=persisted_domain, brand_type=data['brand_type'] ) assert mock_persist.call_args_list == [] -def test_cant_create_new_email_branding_with_unknown_domain( - client_request, - mocker, - fake_uuid, - mock_create_email_branding -): - mock_persist = mocker.patch('app.main.views.email_branding.persist_logo') - mocker.patch('app.main.views.email_branding.delete_email_temp_files_created_by') - - client_request.login(platform_admin_user(fake_uuid)) - page = client_request.post( - '.create_email_branding', - content_type='multipart/form-data', - _data={ - 'logo': None, - 'colour': '#ff0000', - 'text': 'new text', - 'name': 'new name', - 'domain': 'example.gov.uk', - 'brand_type': 'org', - }, - _expected_status=200, - ) - - assert mock_create_email_branding.called is False - assert mock_persist.called is False - - assert page.select_one('.error-message').text.strip() == ( - 'Not a known government domain (you might need to update domains.yml)' - ) - assert page.select_one('input[name=domain]')['value'] == ( - 'example.gov.uk' - ) - - -@pytest.mark.parametrize('posted_domain, expected_error', [ - ( - 'voa.gsi.gov.uk', - 'Not a canonical domain (use voa.gov.uk if appropriate)', - ), - ( - 'hmcts.net', - 'Not a canonical domain (use hmcts.gov.uk if appropriate)', - ), - ( - 'southend.essex.gov.uk', - 'Not an organisation-level domain (use essex.gov.uk if appropriate)', - ), - pytest.param( - 'voa.gov.uk', - '', - marks=pytest.mark.xfail(raises=AssertionError) - ), -]) -def test_rejects_non_canonical_domain_when_adding_email_branding( - client_request, - mocker, - fake_uuid, - mock_create_email_branding, - posted_domain, - expected_error, -): - mocker.patch('app.main.views.email_branding.persist_logo') - mocker.patch('app.main.views.email_branding.delete_email_temp_files_created_by') - data = { - 'logo': None, - 'colour': '#ff0000', - 'text': 'new text', - 'name': 'new name', - 'domain': posted_domain, - 'brand_type': 'org', - } - client_request.login(platform_admin_user(fake_uuid)) - page = client_request.post( - '.create_email_branding', - content_type='multipart/form-data', - _data=data, - _expected_status=200, - ) - - assert page.select_one('.error-message').text.strip() == expected_error - assert mock_create_email_branding.called is False - - def test_create_email_branding_requires_a_name_when_submitting_logo_details( client_request, mocker, @@ -232,7 +135,6 @@ def test_create_email_branding_requires_a_name_when_submitting_logo_details( 'colour': '#ff0000', 'text': 'new text', 'name': '', - 'domain': '', 'brand_type': 'org', } client_request.login(platform_admin_user(fake_uuid)) @@ -258,7 +160,6 @@ def test_create_email_branding_does_not_require_a_name_when_uploading_a_file( 'colour': '', 'text': '', 'name': '', - 'domain': '', 'brand_type': 'org', } client_request.login(platform_admin_user(fake_uuid)) @@ -286,7 +187,6 @@ def test_create_new_email_branding_when_branding_saved( 'colour': '#ff0000', 'text': 'new text', 'name': 'new name', - 'domain': 'voa.gov.uk', 'brand_type': 'org_banner' } @@ -307,7 +207,6 @@ def test_create_new_email_branding_when_branding_saved( 'name': data['name'], 'text': data['text'], 'cdn_url': 'https://static-logos.cdn.com', - 'domain': data['domain'], 'brand_type': data['brand_type'] } ) @@ -320,7 +219,6 @@ def test_create_new_email_branding_when_branding_saved( name=data['name'], text=data['text'], colour=data['colour'], - domain=data['domain'], brand_type=data['brand_type'] ) @@ -387,7 +285,6 @@ def test_update_existing_branding( 'colour': '#0000ff', 'text': 'new text', 'name': 'new name', - 'domain': 'voa.gov.uk', 'brand_type': 'both' } @@ -405,7 +302,7 @@ def test_update_existing_branding( content_type='multipart/form-data', data={'colour': data['colour'], 'name': data['name'], 'text': data['text'], 'cdn_url': 'https://static-logos.cdn.com', - 'domain': data['domain'], 'brand_type': data['brand_type'] + 'brand_type': data['brand_type'] } ) @@ -418,7 +315,6 @@ def test_update_existing_branding( name=data['name'], text=data['text'], colour=data['colour'], - domain=data['domain'], brand_type=data['brand_type'] ) @@ -526,7 +422,6 @@ def test_colour_regex_validation( 'colour': colour_hex, 'text': 'new text', 'name': 'new name', - 'domain': 'voa.gov.uk', 'brand_type': 'org' } diff --git a/tests/app/main/views/test_letter_branding.py b/tests/app/main/views/test_letter_branding.py index 2520d16b0..89ef8bc43 100644 --- a/tests/app/main/views/test_letter_branding.py +++ b/tests/app/main/views/test_letter_branding.py @@ -2,7 +2,6 @@ from io import BytesIO from unittest.mock import Mock, call from uuid import UUID -import pytest from botocore.exceptions import ClientError as BotoClientError from bs4 import BeautifulSoup from flask import current_app, url_for @@ -29,7 +28,6 @@ def test_letter_branding_page_shows_full_branding_list( links = page.select('.message-name a') brand_names = [normalize_spaces(link.text) for link in links] hrefs = [link['href'] for link in links] - brand_hints = [normalize_spaces(hint.text) for hint in page.select('.message-type')] assert normalize_spaces( page.select_one('h1').text @@ -37,12 +35,10 @@ def test_letter_branding_page_shows_full_branding_list( assert page.select_one('.column-three-quarters a')['href'] == url_for('main.create_letter_branding') - assert list(zip( - brand_names, brand_hints - )) == [ - ('HM Government', '–'), - ('Land Registry', 'Default for landregistry.gov.uk'), - ('Animal and Plant Health Agency', '–'), + assert brand_names == [ + 'HM Government', + 'Land Registry', + 'Animal and Plant Health Agency', ] assert hrefs == [ @@ -66,7 +62,6 @@ def test_update_letter_branding_shows_the_current_letter_brand( assert page.find('h1').text == 'Update letter branding' assert page.select_one('#logo-img > img')['src'].endswith('/hm-government.svg') assert page.select_one('#name').attrs.get('value') == 'HM Government' - assert page.select_one('#domain').attrs.get('value') == 'cabinet-office.gov.uk' def test_update_letter_branding_with_new_valid_file( @@ -95,7 +90,6 @@ def test_update_letter_branding_with_new_valid_file( assert page.select_one('#logo-img > img')['src'].endswith(expected_temp_filename) assert page.select_one('#name').attrs.get('value') == 'HM Government' - assert page.select_one('#domain').attrs.get('value') == 'cabinet-office.gov.uk' assert mock_s3_upload.called mock_delete_temp_files.assert_not_called() @@ -161,7 +155,6 @@ def test_update_letter_branding_with_original_file_and_new_details( url_for('.update_letter_branding', branding_id=fake_uuid), data={ 'name': 'Updated name', - 'domain': 'bl.uk', 'operation': 'branding-details' }, follow_redirects=True @@ -175,42 +168,12 @@ def test_update_letter_branding_with_original_file_and_new_details( mock_client_update.assert_called_once_with( branding_id=fake_uuid, - domain='bl.uk', filename='hm-government', name='Updated name' ) -def test_update_letter_branding_does_not_require_a_domain( - mocker, - logged_in_platform_admin_client, - mock_get_all_letter_branding, - mock_get_letter_branding_by_id, - fake_uuid -): - mock_client_update = mocker.patch('app.main.views.letter_branding.letter_branding_client.update_letter_branding') - logo = permanent_letter_logo_name('hm-government', 'svg') - - response = logged_in_platform_admin_client.post( - url_for('.update_letter_branding', branding_id=fake_uuid, logo=logo), - data={ - 'name': 'Updated name', - 'domain': '', - 'operation': 'branding-details' - }, - follow_redirects=True - ) - assert response.status_code == 200 - - mock_client_update.assert_called_once_with( - branding_id=fake_uuid, - domain=None, - filename='hm-government', - name='Updated name' - ) - - -def test_update_letter_branding_shows_form_errors_on_name_and_domain_fields( +def test_update_letter_branding_shows_form_errors_on_name_fields( mocker, logged_in_platform_admin_client, mock_get_letter_branding_by_id, @@ -224,7 +187,6 @@ def test_update_letter_branding_shows_form_errors_on_name_and_domain_fields( url_for('.update_letter_branding', branding_id=fake_uuid, logo=logo), data={ 'name': '', - 'domain': 'example.com', 'operation': 'branding-details' }, follow_redirects=True @@ -234,18 +196,15 @@ def test_update_letter_branding_shows_form_errors_on_name_and_domain_fields( error_messages = page.find_all('span', class_='error-message') assert page.find('h1').text == 'Update letter branding' - assert len(error_messages) == 2 + assert len(error_messages) == 1 assert error_messages[0].text.strip() == 'This field is required.' - assert error_messages[1].text.strip() == 'Not a known government domain (you might need to update domains.yml)' -@pytest.mark.parametrize('error_field', ['name', 'domain']) -def test_update_letter_branding_shows_database_errors_on_name_and_domain_fields( +def test_update_letter_branding_shows_database_errors_on_name_field( mocker, logged_in_platform_admin_client, mock_get_letter_branding_by_id, fake_uuid, - error_field ): mocker.patch('app.main.views.letter_branding.get_png_file_from_svg') mocker.patch('app.main.views.letter_branding.letter_branding_client.update_letter_branding', side_effect=HTTPError( @@ -254,20 +213,19 @@ def test_update_letter_branding_shows_database_errors_on_name_and_domain_fields( json={ 'result': 'error', 'message': { - error_field: { - '{} already in use'.format(error_field) + 'name': { + 'name already in use' } } } ), - message={error_field: ['{} already in use'.format(error_field)]} + message={'name': ['name already in use']} )) response = logged_in_platform_admin_client.post( url_for('.update_letter_branding', branding_id='abc'), data={ 'name': 'my brand', - 'domain': None, 'operation': 'branding-details' } ) @@ -276,7 +234,7 @@ def test_update_letter_branding_shows_database_errors_on_name_and_domain_fields( error_message = page.find('span', class_='error-message').text.strip() assert page.find('h1').text == 'Update letter branding' - assert error_message == '{} already in use'.format(error_field) + assert error_message == 'name already in use' def test_update_letter_branding_with_new_file_and_new_details( @@ -300,7 +258,6 @@ def test_update_letter_branding_with_new_file_and_new_details( url_for('.update_letter_branding', branding_id=fake_uuid, logo=temp_logo), data={ 'name': 'Updated name', - 'domain': 'bl.uk', 'operation': 'branding-details' }, follow_redirects=True @@ -312,7 +269,6 @@ def test_update_letter_branding_with_new_file_and_new_details( assert mock_template_preview.called mock_client_update.assert_called_once_with( branding_id=fake_uuid, - domain='bl.uk', filename='{}-new_file'.format(fake_uuid), name='Updated name' ) @@ -343,7 +299,6 @@ def test_update_letter_branding_rolls_back_db_changes_and_shows_error_if_saving_ url_for('.update_letter_branding', branding_id=fake_uuid, logo=temp_logo), data={ 'name': 'Updated name', - 'domain': 'bl.uk', 'operation': 'branding-details' }, follow_redirects=True @@ -355,8 +310,8 @@ def test_update_letter_branding_rolls_back_db_changes_and_shows_error_if_saving_ assert mock_client_update.call_count == 2 assert mock_client_update.call_args_list == [ - call(branding_id=fake_uuid, domain='bl.uk', filename='{}-new_file'.format(fake_uuid), name='Updated name'), - call(branding_id=fake_uuid, domain='cabinet-office.gov.uk', filename='hm-government', name='HM Government') + call(branding_id=fake_uuid, filename='{}-new_file'.format(fake_uuid), name='Updated name'), + call(branding_id=fake_uuid, filename='hm-government', name='HM Government') ] @@ -370,7 +325,6 @@ def test_create_letter_branding_does_not_show_branding_info(logged_in_platform_a assert page.select_one('#logo-img > img') is None assert page.select_one('#name').attrs.get('value') == '' - assert page.select_one('#domain').attrs.get('value') == '' def test_create_letter_branding_when_uploading_valid_file( @@ -472,7 +426,6 @@ def test_create_letter_branding_shows_an_error_when_submitting_details_with_no_l url_for('.create_letter_branding'), data={ 'name': 'Test brand', - 'domain': 'bl.uk', 'operation': 'branding-details' } ) @@ -506,7 +459,6 @@ def test_create_letter_branding_persists_logo_when_all_data_is_valid( url_for('.create_letter_branding', logo=temp_logo), data={ 'name': 'Test brand', - 'domain': 'bl.uk', 'operation': 'branding-details' }, follow_redirects=True @@ -517,7 +469,7 @@ def test_create_letter_branding_persists_logo_when_all_data_is_valid( assert page.find('h1').text == 'Letter branding' mock_letter_client.create_letter_branding.assert_called_once_with( - domain='bl.uk', filename='{}-test'.format(fake_uuid), name='Test brand' + filename='{}-test'.format(fake_uuid), name='Test brand' ) assert mock_template_preview.called mock_persist_logo.assert_called_once_with( @@ -532,7 +484,7 @@ def test_create_letter_branding_persists_logo_when_all_data_is_valid( mock_delete_temp_files.assert_called_once_with(user_id) -def test_create_letter_branding_shows_form_errors_on_name_and_domain_fields( +def test_create_letter_branding_shows_form_errors_on_name_field( logged_in_platform_admin_client, fake_uuid ): @@ -545,7 +497,6 @@ def test_create_letter_branding_shows_form_errors_on_name_and_domain_fields( url_for('.create_letter_branding', logo=temp_logo), data={ 'name': '', - 'domain': 'example.com', 'operation': 'branding-details' } ) @@ -554,17 +505,14 @@ def test_create_letter_branding_shows_form_errors_on_name_and_domain_fields( error_messages = page.find_all('span', class_='error-message') assert page.find('h1').text == 'Add letter branding' - assert len(error_messages) == 2 + assert len(error_messages) == 1 assert error_messages[0].text.strip() == 'This field is required.' - assert error_messages[1].text.strip() == 'Not a known government domain (you might need to update domains.yml)' -@pytest.mark.parametrize('error_field', ['name', 'domain']) -def test_create_letter_branding_shows_database_errors_on_name_and_domain_fields( +def test_create_letter_branding_shows_database_errors_on_name_fields( mocker, logged_in_platform_admin_client, fake_uuid, - error_field ): with logged_in_platform_admin_client.session_transaction() as session: user_id = session["user_id"] @@ -576,13 +524,13 @@ def test_create_letter_branding_shows_database_errors_on_name_and_domain_fields( json={ 'result': 'error', 'message': { - error_field: { - '{} already in use'.format(error_field) + 'name': { + 'name already in use' } } } ), - message={error_field: ['{} already in use'.format(error_field)]} + message={'name': ['name already in use']} )) temp_logo = LETTER_TEMP_LOGO_LOCATION.format(user_id=user_id, unique_id=fake_uuid, filename='test.svg') @@ -591,7 +539,6 @@ def test_create_letter_branding_shows_database_errors_on_name_and_domain_fields( url_for('.create_letter_branding', logo=temp_logo), data={ 'name': 'my brand', - 'domain': None, 'operation': 'branding-details' } ) @@ -600,7 +547,7 @@ def test_create_letter_branding_shows_database_errors_on_name_and_domain_fields( error_message = page.find('span', class_='error-message').text.strip() assert page.find('h1').text == 'Add letter branding' - assert error_message == '{} already in use'.format(error_field) + assert error_message == 'name already in use' def test_get_png_file_from_svg(client, mocker, fake_uuid): diff --git a/tests/app/notify_client/test_email_branding_client.py b/tests/app/notify_client/test_email_branding_client.py index ef1b48587..3ea1aa293 100644 --- a/tests/app/notify_client/test_email_branding_client.py +++ b/tests/app/notify_client/test_email_branding_client.py @@ -53,13 +53,13 @@ def test_get_all_email_branding(mocker): def test_create_email_branding(mocker): org_data = {'logo': 'test.png', 'name': 'test name', 'text': 'test name', 'colour': 'red', - 'domain': 'sample.com', 'brand_type': 'org'} + 'brand_type': 'org'} mock_post = mocker.patch('app.notify_client.email_branding_client.EmailBrandingClient.post') mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') EmailBrandingClient().create_email_branding( logo=org_data['logo'], name=org_data['name'], text=org_data['text'], colour=org_data['colour'], - domain=org_data['domain'], brand_type='org' + brand_type='org' ) mock_post.assert_called_once_with( @@ -72,13 +72,13 @@ def test_create_email_branding(mocker): def test_update_email_branding(mocker, fake_uuid): org_data = {'logo': 'test.png', 'name': 'test name', 'text': 'test name', 'colour': 'red', - 'domain': 'sample.com', 'brand_type': 'org'} + 'brand_type': 'org'} mock_post = mocker.patch('app.notify_client.email_branding_client.EmailBrandingClient.post') mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') EmailBrandingClient().update_email_branding( branding_id=fake_uuid, logo=org_data['logo'], name=org_data['name'], text=org_data['text'], - colour=org_data['colour'], domain=org_data['domain'], brand_type='org') + colour=org_data['colour'], brand_type='org') mock_post.assert_called_once_with( url='/email-branding/{}'.format(fake_uuid), diff --git a/tests/app/notify_client/test_letter_branding_client.py b/tests/app/notify_client/test_letter_branding_client.py index a6e9db49d..3b2d74a32 100644 --- a/tests/app/notify_client/test_letter_branding_client.py +++ b/tests/app/notify_client/test_letter_branding_client.py @@ -39,13 +39,13 @@ def test_get_all_letter_branding(mocker): def test_create_letter_branding(mocker): - new_branding = {'filename': 'uuid-test', 'name': 'my letters', 'domain': 'example.com'} + new_branding = {'filename': 'uuid-test', 'name': 'my letters'} mock_post = mocker.patch('app.notify_client.letter_branding_client.LetterBrandingClient.post') mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') LetterBrandingClient().create_letter_branding( - filename=new_branding['filename'], name=new_branding['name'], domain=new_branding['domain'] + filename=new_branding['filename'], name=new_branding['name'], ) mock_post.assert_called_once_with( url='/letter-branding', @@ -56,12 +56,12 @@ def test_create_letter_branding(mocker): def test_update_letter_branding(mocker, fake_uuid): - branding = {'filename': 'uuid-test', 'name': 'my letters', 'domain': 'example.com'} + branding = {'filename': 'uuid-test', 'name': 'my letters'} mock_post = mocker.patch('app.notify_client.letter_branding_client.LetterBrandingClient.post') mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') LetterBrandingClient().update_letter_branding( - branding_id=fake_uuid, filename=branding['filename'], name=branding['name'], domain=branding['domain']) + branding_id=fake_uuid, filename=branding['filename'], name=branding['name']) mock_post.assert_called_once_with( url='/letter-branding/{}'.format(fake_uuid), diff --git a/tests/conftest.py b/tests/conftest.py index 21274f0ab..4abb3aaa5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2539,8 +2539,8 @@ def mock_get_all_email_branding(mocker): non_standard_values = [ {'idx': 1, 'colour': 'red'}, {'idx': 2, 'colour': 'orange'}, - {'idx': 3, 'text': None, 'domain': 'nhs.uk'}, - {'idx': 4, 'colour': 'blue', 'domain': 'voa.gov.uk'}, + {'idx': 3, 'text': None}, + {'idx': 4, 'colour': 'blue'}, ] shuffle = sort_key is None return create_email_brandings(5, non_standard_values=non_standard_values, shuffle=shuffle) @@ -2559,19 +2559,16 @@ def mock_get_all_letter_branding(mocker): 'id': str(UUID(int=0)), 'name': 'HM Government', 'filename': 'hm-government', - 'domain': None, }, { 'id': str(UUID(int=1)), 'name': 'Land Registry', 'filename': 'land-registry', - 'domain': 'landregistry.gov.uk', }, { 'id': str(UUID(int=2)), 'name': 'Animal and Plant Health Agency', 'filename': 'animal', - 'domain': None, } ] @@ -2587,7 +2584,6 @@ def mock_get_letter_branding_by_id(mocker): 'id': _id, 'name': 'HM Government', 'filename': 'hm-government', - 'domain': 'cabinet-office.gov.uk', } return mocker.patch( 'app.letter_branding_client.get_letter_branding', side_effect=_get_branding_by_id @@ -2611,7 +2607,6 @@ def create_email_branding(id, non_standard_values={}): 'text': 'Organisation text', 'id': id, 'colour': '#f00', - 'domain': 'sample.com', 'brand_type': 'org', } @@ -2674,7 +2669,7 @@ def mock_get_email_branding_without_brand_text(mocker, fake_uuid): @pytest.fixture(scope='function') def mock_create_email_branding(mocker): - def _create_email_branding(logo, name, text, colour, domain, brand_type): + def _create_email_branding(logo, name, text, colour, brand_type): return return mocker.patch( @@ -2684,7 +2679,7 @@ def mock_create_email_branding(mocker): @pytest.fixture(scope='function') def mock_update_email_branding(mocker): - def _update_email_branding(branding_id, logo, name, text, colour, domain, brand_type): + def _update_email_branding(branding_id, logo, name, text, colour, brand_type): return return mocker.patch( From 9d99c78c3adf7a69d76203c73fb919c1aad1a016 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 4 Apr 2019 11:13:41 +0100 Subject: [PATCH 08/11] Remove code that reads `domains.yml` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we’ve removed calls to this code from the rest of the app, it can safely be removed now. --- app/utils.py | 197 +++------------------------------------- tests/app/test_utils.py | 158 +------------------------------- 2 files changed, 16 insertions(+), 339 deletions(-) diff --git a/app/utils.py b/app/utils.py index 2a70ab3fb..de4ac69d8 100644 --- a/app/utils.py +++ b/app/utils.py @@ -13,15 +13,7 @@ import ago import dateutil import pyexcel import yaml -from flask import ( - Markup, - abort, - current_app, - redirect, - request, - session, - url_for, -) +from flask import abort, current_app, redirect, request, session, url_for from flask_login import current_user from notifications_utils.field import Field from notifications_utils.formatters import make_quotes_smart @@ -409,143 +401,23 @@ def set_status_filters(filter_args): _dir_path = os.path.dirname(os.path.realpath(__file__)) -class AgreementInfo: +class NotGovernmentEmailDomain(Exception): + pass - with open('{}/domains.yml'.format(_dir_path)) as domains: - domains = yaml.safe_load(domains) - domain_names = sorted(domains.keys(), key=len, reverse=True) + +class GovernmentEmailDomain(): + + with open('{}/email_domains.yml'.format(_dir_path)) as email_domains: + domain_names = yaml.safe_load(email_domains) def __init__(self, email_address_or_domain): - - self._match = next(filter( - self.get_matching_function(email_address_or_domain), - self.domain_names, - ), None) - - self._domain = email_address_or_domain.split('@')[-1] - - ( - self.owner, - self.crown_status, - self.agreement_signed, - self.canonical_domain, - ) = self._get_info() - - @classmethod - def from_user(cls, user): - return cls(user.email_address if user.is_authenticated else '') - - @classmethod - def from_current_user(cls): - return cls.from_user(current_user) - - @property - def as_human_readable(self): - if self.canonical_domain and 'dwp' in self.canonical_domain: - return 'DWP - Requires OED approval' - if self.agreement_signed: - return 'Yes, on behalf of {}'.format(self.owner) - elif self.owner: - return '{} (organisation is {}, {})'.format( - { - False: 'No', - None: 'Can’t tell', - }.get(self.agreement_signed), - self.owner, - { - True: 'a crown body', - False: 'a non-crown body', - None: 'crown status unknown', - }.get(self.crown_status), - ) - else: - return 'Can’t tell (domain is {})'.format(self._domain) - - @property - def as_info_for_branding_request(self): - return self.owner or 'Can’t tell (domain is {})'.format(self._domain) - - @property - def as_jinja_template(self): - if self.crown_status is None: - return 'agreement-choose' - if self.agreement_signed: - return 'agreement-signed' - return 'agreement' - - def as_terms_of_use_paragraph(self, **kwargs): - return Markup(self._as_terms_of_use_paragraph(**kwargs)) - - def _as_terms_of_use_paragraph(self, terms_link, download_link, support_link, signed_in): - - if not signed_in: - return (( - '{} Sign in to download a copy ' - 'or find out if one is already in place.' - ).format(self._acceptance_required, terms_link)) - - if self.agreement_signed is None: - return (( - '{} Download the agreement or ' - 'contact us to find out if we already ' - 'have one in place with your organisation.' - ).format(self._acceptance_required, download_link, support_link)) - - if self.agreement_signed is False: - return (( - '{} Download a copy.' - ).format(self._acceptance_required, download_link)) - - return ( - 'Your organisation ({}) has already accepted the ' - 'GOV.UK Notify data sharing and financial ' - 'agreement.'.format(self.owner) - ) - - def as_pricing_paragraph(self, **kwargs): - return Markup(self._as_pricing_paragraph(**kwargs)) - - def _as_pricing_paragraph(self, pricing_link, download_link, support_link, signed_in): - - if not signed_in: - return (( - 'Sign in to download a copy or find ' - 'out if one is already in place with your organisation.' - ).format(pricing_link)) - - if self.agreement_signed is None: - return (( - 'Download the agreement or ' - 'contact us to find out if we already ' - 'have one in place with your organisation.' - ).format(download_link, support_link)) - - return ( - 'Download the agreement ' - '({} {}).'.format( - download_link, - self.owner, - { - True: 'has already accepted it', - False: 'hasn’t accepted it yet' - }.get(self.agreement_signed) - ) - ) - - @property - def _acceptance_required(self): - return ( - 'Your organisation {} must also accept our data sharing ' - 'and financial agreement.'.format( - '({})'.format(self.owner) if self.owner else '', - ) - ) - - @property - def crown_status_or_404(self): - if self.crown_status is None: - abort(404) - return self.crown_status + try: + self._match = next(filter( + self.get_matching_function(email_address_or_domain), + self.domain_names, + )) + except StopIteration: + raise NotGovernmentEmailDomain() @staticmethod def get_matching_function(email_address_or_domain): @@ -564,45 +436,6 @@ class AgreementInfo: return fn - def _get_info(self): - - details = self.domains.get(self._match, {}) - - if details is None: - raise TypeError('Domain must have details ({})'.format(self._domain)) - - if isinstance(details, str): - self.is_canonical = False - return AgreementInfo(details)._get_info() - - elif isinstance(details, dict): - self.is_canonical = bool(details) - return( - details.get("owner"), - details.get("crown"), - details.get("agreement_signed"), - self._match, - ) - - -class NotGovernmentEmailDomain(Exception): - pass - - -class GovernmentEmailDomain(AgreementInfo): - - with open('{}/email_domains.yml'.format(_dir_path)) as email_domains: - domain_names = yaml.safe_load(email_domains) - - def __init__(self, email_address_or_domain): - try: - self._match = next(filter( - self.get_matching_function(email_address_or_domain), - self.domain_names, - )) - except StopIteration: - raise NotGovernmentEmailDomain() - def unicode_truncate(s, length): encoded = s.encode('utf-8')[:length] diff --git a/tests/app/test_utils.py b/tests/app/test_utils.py index c7fb087a8..cb5a85b1c 100644 --- a/tests/app/test_utils.py +++ b/tests/app/test_utils.py @@ -1,16 +1,13 @@ -from collections import Counter, OrderedDict +from collections import OrderedDict from csv import DictReader from io import StringIO from pathlib import Path import pytest from freezegun import freeze_time -from notifications_utils.recipients import validate_email_address from app import format_datetime_relative from app.utils import ( - AgreementInfo, - GovernmentEmailDomain, Spreadsheet, email_safe, generate_next_dict, @@ -292,159 +289,6 @@ def test_get_cdn_domain_on_non_localhost(client, mocker): assert domain == 'static-logos.admintest.com' -@pytest.mark.parametrize("domain_or_email_address", ( - "test@dclgdatamart.co.uk", "test@communities.gsi.gov.uk", "test@communities.gov.uk", -)) -def test_get_valid_agreement_info_known_details(domain_or_email_address): - agreement_info = AgreementInfo(domain_or_email_address) - assert agreement_info.crown_status is None - assert agreement_info.owner == "Ministry of Housing, Communities & Local Government" - assert agreement_info.agreement_signed is True - assert agreement_info.as_human_readable == ( - 'Yes, on behalf of Ministry of Housing, Communities & Local Government' - ) - - -@pytest.mark.parametrize("domain_or_email_address", ( - "test@dwp.gov.uk", "test@dwp.gsi.gov.uk", -)) -def test_dwp_go_live_requests_are_flagged(domain_or_email_address): - agreement_info = AgreementInfo(domain_or_email_address) - assert agreement_info.owner == "Department for Work and Pensions" - assert agreement_info.agreement_signed is True - assert agreement_info.as_human_readable == ( - 'DWP - Requires OED approval' - ) - - -@pytest.mark.parametrize("domain_or_email_address, is_canonical", ( - ("test@dclgdatamart.co.uk", False), - ("test@communities.gsi.gov.uk", False), - ("test@communities.gov.uk", True), -)) -def test_get_canonical_domain(domain_or_email_address, is_canonical): - assert AgreementInfo(domain_or_email_address).canonical_domain == 'communities.gov.uk' - assert AgreementInfo(domain_or_email_address).is_canonical == is_canonical - - -def test_get_canonical_domain_passes_through_unknown_domain(): - assert AgreementInfo('example.com').canonical_domain is None - assert AgreementInfo('example.com').is_canonical is False - - -@pytest.mark.parametrize("domain_or_email_address", ( - "test@police.gov.uk", "police.gov.uk", -)) -def test_get_valid_agreement_info_unknown_details(domain_or_email_address): - government_domain = AgreementInfo(domain_or_email_address) - assert government_domain.crown_status is None - assert government_domain.owner is None - assert government_domain.agreement_signed is None - assert government_domain.as_human_readable == 'Can’t tell (domain is police.gov.uk)' - - -def test_get_valid_agreement_info_only_org_known(): - agreement_info = AgreementInfo('nhs.net') - # Some parts of the NHS are Crown, some aren’t - assert agreement_info.crown_status is None - assert agreement_info.owner == 'NHS' - assert agreement_info.agreement_signed is None - assert agreement_info.as_human_readable == 'Can’t tell (organisation is NHS, crown status unknown)' - - -def test_get_valid_agreement_info_some_known_details(): - agreement_info = AgreementInfo("marinemanagement.org.uk") - assert agreement_info.crown_status is None - assert agreement_info.owner == "Marine Management Organisation" - assert agreement_info.agreement_signed is True - assert agreement_info.as_human_readable == ( - 'Yes, on behalf of Marine Management Organisation' - ) - - -def test_get_valid_local_agreement_info_some_known_details(): - # This example may need to be updated to use a different council if - # Babergh every sign the agreement - agreement_info = AgreementInfo("babergh.gov.uk") - assert agreement_info.crown_status is False - assert agreement_info.owner == "Babergh District Council" - assert agreement_info.agreement_signed is False - assert agreement_info.as_human_readable == ( - 'No (organisation is Babergh District Council, a non-crown body)' - ) - - -def test_get_valid_government_domain_gets_most_specific_first(): - - generic = AgreementInfo("gov.uk") - assert generic.crown_status is None - assert generic.owner is None - assert generic.agreement_signed is None - assert generic.as_human_readable == ( - 'Can’t tell (domain is gov.uk)' - ) - - specific = AgreementInfo("dacorum.gov.uk") - assert specific.crown_status is False - assert specific.owner == 'Dacorum Borough Council' - assert specific.agreement_signed is True - assert specific.as_human_readable == ( - 'Yes, on behalf of Dacorum Borough Council' - ) - - -def test_get_domain_info_for_branding_request(): - - assert AgreementInfo("gov.uk").as_info_for_branding_request == ( - 'Can’t tell (domain is gov.uk)' - ) - assert AgreementInfo("dacorum.gov.uk").as_info_for_branding_request == ( - 'Dacorum Borough Council' - ) - - -def test_domains_are_lowercased(): - for domain in AgreementInfo.domains.keys(): - assert domain == domain.lower() - - -def test_validate_government_domain_data(): - - for domain in AgreementInfo.domains.keys(): - - validate_email_address('test@{}'.format(domain)) - - agreement_info = AgreementInfo(domain) - - assert agreement_info.crown_status in { - True, False, None - } - - assert isinstance(agreement_info.owner, str) and agreement_info.owner.strip() - - assert agreement_info.agreement_signed in { - True, False, None - } - - -def test_domain_data_is_canonicalized(): - for owner, count in Counter( - AgreementInfo(domain).owner - for domain in AgreementInfo.domains.keys() - if AgreementInfo(domain).is_canonical - ).most_common(): - if count > 1: - raise ValueError( - '{} entries in domains.yml for {}'.format(count, owner) - ) - - -def test_validate_email_domain_data(): - - for domain in GovernmentEmailDomain.domains.keys(): - validate_email_address('test@{}'.format(domain)) - - @pytest.mark.parametrize('time, human_readable_datetime', [ ('2018-03-14 09:00', '14 March at 9:00am'), ('2018-03-14 15:00', '14 March at 3:00pm'), From 5d091b214a4d682ae14d084949e380de90786f1d Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 4 Apr 2019 11:13:53 +0100 Subject: [PATCH 09/11] Remove domains.yml file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we’ve now removed the code that reads this file, it can safely be deleted. --- app/domains.yml | 4429 ----------------------------------------------- 1 file changed, 4429 deletions(-) delete mode 100644 app/domains.yml diff --git a/app/domains.yml b/app/domains.yml deleted file mode 100644 index 830d0bbc7..000000000 --- a/app/domains.yml +++ /dev/null @@ -1,4429 +0,0 @@ ---- -# Central and NHS -slc.co.uk: - owner: Student Loans Company -dclgdatamart.co.uk: communities.gov.uk -communities.gsi.gov.uk: communities.gov.uk -communities.gov.uk: - owner: "Ministry of Housing, Communities & Local Government" - agreement_signed: true -hmcts.net: hmcts.gov.uk -hmcts.gsi.gov.uk: hmcts.gov.uk -hmcts.gov.uk: - owner: Her Majesty’s Courts and Tribunals Service - agreement_signed: true -cqc.org.uk: - owner: Care Quality Commission - agreement_signed: true -marinemanagement.org.uk: - owner: Marine Management Organisation - agreement_signed: true -crowncommercial.gov.uk: cabinet-office.gov.uk -cabinetoffice.gov.uk: cabinet-office.gov.uk -cabinet-office.gov.uk: - owner: Cabinet Office - crown: true - agreement_signed: true -cica.gsi.gov.uk: cica.gov.uk -cica.gov.uk: - owner: Criminal Injuries Compensation Authority - agreement_signed: true -dbs.gsi.gov.uk: dbs.gov.uk -dbs.gov.uk: - owner: Disclosure and Barring Service - agreement_signed: true -dvla.gsi.gov.uk: dvla.gov.uk -dvla.gov.uk: - owner: Driver and Vehicle Licensing Agency - agreement_signed: true -environment-agency.gsi.gov.uk: environment-agency.gov.uk -environment-agency.gov.uk: - owner: Environment Agency - agreement_signed: true -food.gov.uk: foodstandards.gov.uk -foodstandards.gsi.gov.uk: foodstandards.gov.uk -foodstandards.gov.uk: - owner: Food Standards Agency - agreement_signed: true -gchq.gov.uk: - owner: Government Communications Headquarters - crown: true - agreement_signed: true -gla.gsi.gov.uk: gla.gov.uk -gla.gov.uk: - owner: The Gangmasters and Labour Abuse Authority - agreement_signed: true -governmentlegal.gov.uk: - owner: Government Legal Department - agreement_signed: true -gov.scot: - owner: Scottish Government - crown: true - agreement_signed: true -innovateuk.gsi.gov.uk: innovateuk.gov.uk -innovateuk.gov.uk: - owner: Innovate UK - agreement_signed: true -ncsc.gov.uk: gchq.gov.uk -nhs.net: nhs.uk -nhs.uk: - owner: NHS -nhsdigital.nhs.uk: digital.nhs.uk -digital.nhs.uk: - owner: NHS Digital - agreement_signed: true -ofgem.gov.uk: - owner: Ofgem - crown: true - agreement_signed: true -ofsted.gsi.gov.uk: ofsted.gov.uk -ofsted.gov.uk: - owner: Ofsted - agreement_signed: true -ons.gsi.gov.uk: ons.gov.uk -statistics.gov.uk: ons.gov.uk -ons.gov.uk: - owner: Office for National Statistics - agreement_signed: true -pins.gsi.gov.uk: pins.gov.uk -pins.gov.uk: - owner: The Planning Inspectorate - agreement_signed: true - crown: true -publicguardian.gsi.gov.uk: publicguardian.gov.uk -publicguardian.gov.uk: - owner: Office of the Public Guardian - agreement_signed: true -srft.nhs.uk: - owner: Salford Royal NHS Trust - agreement_signed: true -btuh.nhs.uk: - owner: Basildon and Thurrock University Hospitals NHS Foundation Trust - agreement_signed: true -nationalarchives.gsi.gov.uk: nationalarchives.gov.uk -nationalarchives.gov.uk: - owner: National Archives - agreement_signed: true -voa.gsi.gov.uk: voa.gov.uk -voa.gov.uk: - owner: Valuation Office Agency - agreement_signed: true -daera-ni.gov.uk: - owner: Department of Agriculture Environment and Rural Affairs - agreement_signed: true -beis.gsi.gov.uk: beis.gov.uk -beis.gov.uk: - owner: Department for Business, Energy and Industrial Strategy - agreement_signed: true -education.gsi.gov.uk: education.gov.uk -education.gov.uk: - owner: Department for Education - agreement_signed: true -gad.gsi.gov.uk: gad.gov.uk -gad.gov.uk: - owner: Government Actuary's Department - agreement_signed: true -landregistry.gsi.gov.uk: landregistry.gov.uk -landregistry.gov.uk: - owner: HM Land Registry - agreement_signed: true -legalaid.gsi.gov.uk: legalaid.gov.uk -legalaid.gov.uk: - owner: Legal Aid Agency - agreement_signed: true -phe.gsi.gov.uk: phe.gov.uk -phe.gov.uk: - owner: Public Health England - agreement_signed: true -ssro.gsi.gov.uk: ssro.gov.uk -ssro.gov.uk: - owner: Single Source Regulations Office - agreement_signed: true -vmd.defra.gsi.gov.uk: vmd.defra.gov.uk -vmd.defra.gov.uk: - owner: Vetinary Medicines Directorate - agreement_signed: true -companieshouse.gsi.gov.uk: companieshouse.gov.uk -companieshouse.gov.uk: - owner: Companies House - agreement_signed: true -trade.gsi.gov.uk: trade.gov.uk -trade.gov.uk: - owner: Department for International Trade - agreement_signed: true -ips.gsi.gov.uk: ips.gov.uk -ips.gov.uk: hmpo.gov.uk -hmpo.gov.uk: homeoffice.gov.uk -hmpo.gsi.gov.uk: homeoffice.gov.uk -homeoffice.gsi.gov.uk: homeoffice.gov.uk -homeoffice.gov.uk: - owner: Home Office - agreement_signed: true -ipo.gsi.gov.uk: ipo.gov.uk -ipo.gov.uk: - owner: Intellectual Property Office - agreement_signed: true -judicialappointments.digital: justice.gov.uk -judicialappointments.gov.uk: justice.gov.uk -justice.gsi.gov.uk: justice.gov.uk -justice.gov.uk: - owner: Ministry of Justice - agreement_signed: true -rpa.gsi.gov.uk: rpa.gov.uk -rpa.gov.uk: - owner: Rural Payments Agency - agreement_signed: true -mcga.gov.uk: - owner: Maritime and Coastguard Agency - agreement_signed: true -metoffice.gov.uk: - owner: Met Office - agreement_signed: false - crown: true -dft.gsi.gov.uk: dft.gov.uk -dft.gov.uk: - owner: Department for Transport - agreement_signed: true - crown: true -innovateuk.gov.uk: - owner: Innovate UK - agreement_signed: false - crown: false -gamblingcommission.gov.uk: - owner: Gambling Commission - agreement_signed: true - crown: false -geo.gov.uk: - owner: Government Equalities Office - agreement_signed: true - crown: true -insolvency.gsi.gov.uk: insolvency.gov.uk -insolvency.gov.uk: - owner: The Insolvency Service - agreement_signed: true - crown: true -dwp.gsi.gov.uk: dwp.gov.uk -dwp.gov.uk: - owner: Department for Work and Pensions - agreement_signed: true - crown: true -vca.gov.uk: - owner: Vehicle Certification Agency - agreement_signed: false - crown: true -vca.gsi.gov.uk: vca.gov.uk -olev.gov.uk: dft.gov.uk -olev.gsi.gov.uk: olev.gov.uk -apha.gsi.gov.uk: apha.gov.uk -apha.gov.uk: - owner: Animal & Plant Health Agency - crown: true - agreement_signed: true -mil.uk: mod.gov.uk -moduk.org: mod.gov.uk -dera.gov.uk: mod.gov.uk -dstl.gov.uk: mod.gov.uk -mod.gov.uk: - owner: Ministry of Defence - crown: true - agreement_signed: -defra.gsi.gov.uk: defra.gov.uk -defra.gov.uk: - owner: Department for Environment, Food & Rural Affairs - crown: true - agreement_signed: -hmrc.gsi.gov.uk: hmrc.gov.uk -hmrc.gov.uk: - owner: HM Revenue and Customs - crown: true - agreement_signed: - -# Crown bodies who haven’t signed the MOU -acas.org.uk: - owner: Advisory, Conciliation and Arbitration Service - crown: true - agreement_signed: false -charitycommission.gsi.gov.uk: charitycommission.gov.uk -charitycommission.gov.uk: - owner: Charity Commission - crown: true - agreement_signed: false -cps.gov.uk: - owner: The Crown Prosecution Service - crown: true - agreement_signed: false -culture.gov.uk: - owner: Department for Digital, Culture, Media & Sport - crown: true - agreement_signed: true -culture.gsi.gov.uk: culture.gov.uk -dexeu.gov.uk: - owner: Department for Exiting the European Union - crown: true - agreement_signed: false -dvsa.gov.uk: - owner: Driver & Vehicle Standards Agency - crown: true - agreement_signed: true -dfid.gov.uk: - owner: Department for International Development - crown: true - agreement_signed: false -fco.gov.uk: - owner: Foreign & Commonwealth Office - crown: true - agreement_signed: false -hse.gov.uk: - owner: Health and Safety Executive - crown: true - agreement_signed: false -hmtreasury.gsi.gov.uk: hmtreasury.gov.uk -hmtreasury.gov.uk: - owner: HM Treasury - crown: true - agreement_signed: false -nio.gov.uk: - owner: Northern Ireland Office - crown: true - agreement_signed: false -ukexportfinance.gov.uk: - owner: UK Export Finance - crown: true - agreement_signed: false -ppf.gsi.gov.uk: ppf.gov.uk -ppf.gov.uk: pensionprotectionfund.org.uk -pensionprotectionfund.org.uk: - owner: Pension Protection Fund - crown: true - agreement_signed: true -naturalresources.wales: cyfoethnaturiolcymru.gov.uk -cyfoethnaturiolcymru.gov.uk: - owner: Natural Resources Wales - crown: true - agreement_signed: true -gov.wales: - owner: Welsh Government - crown: true - agreement_signed: false - -# Other non-crown -bl.uk: - owner: British Library - crown: false - agreement_signed: false -nao.org.uk: - owner: National Audit Office - crown: false - agreement_signed: false - -# Local Government -aberdeencityandshire-sdpa.gov.uk: aberdeencity.gov.uk -aberdeencity.gov.uk: - owner: Aberdeen City Council - crown: false - agreement_signed: false -aberdeenshire.gov.uk: - owner: Aberdeenshire Council - crown: false - agreement_signed: true -abergavennytowncouncil.gov.uk: - owner: Abergavenny Town Council - crown: false - agreement_signed: false -aberporthcommunitycouncil.gov.uk: - owner: Aberporth Community Council - crown: false - agreement_signed: false -abertilleryandllanhilleth-wcc.gov.uk: - owner: Abertilly and Llanhilleth Community Council - crown: false - agreement_signed: false -aberystwyth.gov.uk: - owner: Aberystwyth Town Council - crown: false - agreement_signed: false -abingdon.gov.uk: - owner: Abingdon Town Council - crown: false - agreement_signed: false -abinger-pc.gov.uk: molevalley.gov.uk -actoncommunitycouncil.gov.uk: - owner: Acton Community Council - crown: false - agreement_signed: false -adurdc.gov.uk: adur.gov.uk -adur.gov.uk: - owner: Adur District Council - crown: false - agreement_signed: false -adur-worthing.gov.uk: adur.gov.uk -alcester-tc.gov.uk: - owner: Alcester Town Council - crown: false - agreement_signed: false -allerdale.gov.uk: - owner: Allerdale Borough Council - crown: false - agreement_signed: false -alnwick-tc.gov.uk: - owner: Alnwick Town Council - crown: false - agreement_signed: false -alton.gov.uk: - owner: Alton Town Council - crown: false - agreement_signed: false -ambervalley.gov.uk: - owner: Amber Valley Borough Council - crown: false - agreement_signed: false -amble.gov.uk: - owner: Amble Town Council - crown: false - agreement_signed: false -amersham-tc.gov.uk: - owner: Amersham Town Council - crown: false - agreement_signed: false -amesburytowncouncil.gov.uk: - owner: Amesbury Town Council - crown: false - agreement_signed: false -andover-tc.gov.uk: - owner: Andover Town Council - crown: false - agreement_signed: false -anglesey.gov.uk: - owner: Isle of Anglesey County Council - crown: false - agreement_signed: false -angliarevenues.gov.uk: westsuffolk.gov.uk -angus.gov.uk: - owner: Angus Council - crown: false - agreement_signed: true -antrimandnewtownabbey.gov.uk: antrim.gov.uk -antrim.gov.uk: - owner: Antrim Borough Council - crown: false - agreement_signed: true -archifaugwent.gov.uk: blaenau-gwent.gov.uk -archifaumorgannwg.gov.uk: cardiff.gov.uk -ardsandnorthdown.gov.uk: - owner: North Down Borough Council - crown: false - agreement_signed: false -argoedcommunitycouncil.gov.uk: - owner: Argoed Community Council - crown: false - agreement_signed: false -argyll-bute.gov.uk: - owner: Argyll and Bute Council - crown: false - agreement_signed: false -armaghbanbridgecraigavon.gov.uk: - owner: Armagh City and District Council - crown: false - agreement_signed: false -arundeltowncouncil.gov.uk: - owner: Arundel Town Council - crown: false - agreement_signed: false -arun.gov.uk: - owner: Arun District Council - crown: false - agreement_signed: false -ashbournetowncouncil.gov.uk: - owner: Ashbourne Town Council - crown: false - agreement_signed: false -ashfield-dc.gov.uk: ashfield.gov.uk -ashfield.gov.uk: - owner: Ashfield District Council - crown: false - agreement_signed: true -ashford.gov.uk: - owner: Ashford Borough Council - crown: false - agreement_signed: false -ashingtontowncouncil.gov.uk: - owner: Ashington Town Council - crown: false - agreement_signed: false -ashurstwood-vc.gov.uk: - owner: Ashurst Wood Village Council - crown: false - agreement_signed: false -askderbyshire.gov.uk: ne-derbyshire.gov.uk -aspatria-tc.gov.uk: - owner: Aspatria Town Council - crown: false - agreement_signed: false -atherstone-tc.gov.uk: - owner: Atherstone Town Council - crown: false - agreement_signed: false -aws.gov.uk: worthing.gov.uk -axbridge-tc.gov.uk: - owner: Axbridge Town Council - crown: false - agreement_signed: false -axminstertowncouncil.gov.uk: - owner: Axminster Town Council - crown: false - agreement_signed: false -aylesbury.gov.uk: aylesburyvale.gov.uk -aylesburytowncouncil.gov.uk: - owner: Aylesbury Town Council - crown: false - agreement_signed: false -aylesburyvaledc.gov.uk: aylesburyvale.gov.uk -aylesburyvale.gov.uk: - owner: Aylesbury Vale District Council - crown: false - agreement_signed: false -aylsham-tc.gov.uk: - owner: Aylsham Town Council - crown: false - agreement_signed: false -ayrshire.gov.uk: south-ayrshire.gov.uk -ayrshire-jsu.gov.uk: south-ayrshire.gov.uk -babergh.gov.uk: - owner: Babergh District Council - crown: false - agreement_signed: false -baberghmidsuffolk.gov.uk: babergh.gov.uk -bagilltcommunitycouncil.gov.uk: - owner: Bagillt Community Council - crown: false - agreement_signed: false -baildontowncouncil.gov.uk: - owner: Baildon Town Council - crown: false - agreement_signed: false -bakewelltowncouncil.gov.uk: - owner: Bakewell Town Council - crown: false - agreement_signed: false -ballymoney.gov.uk: - owner: Ballymoney Borough Council - crown: false - agreement_signed: false -bamptontowncouncil.gov.uk: - owner: BamptonTown Council - crown: false - agreement_signed: false -banbury.gov.uk: - owner: Banbury Town Council - crown: false - agreement_signed: false -barking-dagenham.gov.uk: - owner: Barking & Dagenham London Borough Council - crown: false - agreement_signed: false -barmouthtowncouncil.gov.uk: - owner: Barmouth Town Council - crown: false - agreement_signed: false -barnardcastletowncouncil.gov.uk: - owner: Barnard Castle Town Council - crown: false - agreement_signed: false -barnet.gov.uk: - owner: Barnet London Borough Council - crown: false - agreement_signed: false -barnoldswicktowncouncil.gov.uk: - owner: Barnoldswick Town Council - crown: false - agreement_signed: false -barnsley.gov.uk: - owner: Barnsley Metropolitan Borough Council - crown: false - agreement_signed: true -barrowbc.gov.uk: - owner: Barrow in Furness Borough Council - crown: false - agreement_signed: false -barrytowncouncil.gov.uk: valeofglamorgan.gov.uk -basildon.gov.uk: - owner: Basildon District Council - crown: false - agreement_signed: true -basingstoke.gov.uk: - owner: Basingstoke and Deane Borough Council - crown: false - agreement_signed: false -bassetlaw.gov.uk: - owner: Bassetlaw District Council - crown: false - agreement_signed: false -batchworth-ecc.gov.uk: batchworth-pc.gov.uk -batchworth-pc.gov.uk: - owner: Batchworth Community Council - crown: false - agreement_signed: false -bathnes.gov.uk: - owner: Bath and North East Somerset Council - crown: false - agreement_signed: true -battletowncouncil.gov.uk: - owner: Battle Town Council - crown: false - agreement_signed: false -beaconsfieldtowncouncil.gov.uk: - owner: Beaconsfield Town Council - crown: false - agreement_signed: false -beaminster-tc.gov.uk: - owner: Beaminster Town Council - crown: false - agreement_signed: false -beaumaristowncouncil.gov.uk: - owner: Beaumaris Town Council - crown: false - agreement_signed: false -bedale-tc.gov.uk: - owner: Bedale Town Council - crown: false - agreement_signed: false -bedford.gov.uk: - owner: Bedford Borough Council - crown: false - agreement_signed: false -bedscc.gov.uk: bedford.gov.uk -bedsparishes.gov.uk: bedford.gov.uk -belfastcity.gov.uk: - owner: Belfast City Council - crown: false - agreement_signed: false -belpertowncouncil.gov.uk: - owner: Belper Town Council - crown: false - agreement_signed: false -berkeley-tc.gov.uk: - owner: Berkeley Town Council - crown: false - agreement_signed: false -berkhamstedtowncouncil.gov.uk: - owner: Berkhamsted Town Council - crown: false - agreement_signed: false -berwick-tc.gov.uk: - owner: Berwick-upon-Tweed Town Council - crown: false - agreement_signed: false -betchworth-pc.gov.uk: molevalley.gov.uk -beverley.gov.uk: - owner: Beverley Town Council - crown: false - agreement_signed: false -bexley.gov.uk: - owner: Bexley London Borough Council - crown: false - agreement_signed: false -bicester.gov.uk: - owner: Bicester Town Council - crown: false - agreement_signed: false -bideford-tc.gov.uk: - owner: Bideford Town Council - crown: false - agreement_signed: false -bidfordonavon-pc.gov.uk: stratford.gov.uk -biggleswadetowncouncil.gov.uk: - owner: Biggleswade Town Council - crown: false - agreement_signed: false -billericaytowncouncil.gov.uk: - owner: Billericay Town Council - crown: false - agreement_signed: false -bingham-tc.gov.uk: - owner: Bingham Town Council - crown: false - agreement_signed: false -bingleytowncouncil.gov.uk: - owner: Bingley Town Council - crown: false - agreement_signed: false -birmingham.gov.uk: - owner: Birmingham City Council - crown: false - agreement_signed: true -birminghamprepared.gov.uk: birmingham.gov.uk -bishopauckland-tc.gov.uk: - owner: Bishop Auckland Town Council - crown: false - agreement_signed: false -bishopsitchington-pc.gov.uk: stratford.gov.uk -bishopsstortfordtc.gov.uk: - owner: Bishop's Stortford Town Council - crown: false - agreement_signed: false -blaby.gov.uk: - owner: Blaby District Council - crown: false - agreement_signed: false -blackburn.gov.uk: - owner: Blackburn with Darwen Borough Council - crown: false - agreement_signed: false -blackpool.gov.uk: - owner: Blackpool Council - crown: false - agreement_signed: true -blackwaterandhawleytowncouncil.gov.uk: - owner: Blackwater and Hawley Town Council - crown: false - agreement_signed: false -blaenau-gwent.gov.uk: - owner: Blaenau Gwent County Borough Council - crown: false - agreement_signed: true -blaenhonddan-wcc.gov.uk: - owner: Blaenhonddan Community Council - crown: false - agreement_signed: false -blandfordforum-tc.gov.uk: - owner: Blandford Forum Town Council - crown: false - agreement_signed: false -bletchleyfennystratford-tc.gov.uk: - owner: Bletchley and Fenny Stratford Town Council - crown: false - agreement_signed: false -bodfaricommunitycouncil.gov.uk: - owner: Bodfari Community Council - crown: false - agreement_signed: false -bodmin.gov.uk: - owner: Bodmin Town Council - crown: false - agreement_signed: false -bognorregis.gov.uk: - owner: Bognor Regis Town Council - crown: false - agreement_signed: false -bollington-tc.gov.uk: - owner: Bollington Town Council - crown: false - agreement_signed: false -bolsover.gov.uk: - owner: Bolsover District Council - crown: false - agreement_signed: false -bolton.gov.uk: - owner: Bolton Metropolitan Borough Council - crown: false - agreement_signed: false -boston.gov.uk: - owner: Boston Borough Council - crown: false - agreement_signed: false -bournemouth.gov.uk: - owner: Bournemouth Borough Council - crown: false - agreement_signed: true -boveytracey.gov.uk: - owner: Bovey Tracey Town Council - crown: false - agreement_signed: false -bracklacommunitycouncil.gov.uk: - owner: Brackla Community Council - crown: false - agreement_signed: false -brackleynorthants-tc.gov.uk: - owner: Brackley Town Council - crown: false - agreement_signed: false -bracknell-forest.gov.uk: bracknell.gov.uk -bracknellforest.gov.uk: bracknell.gov.uk -bracknell.gov.uk: - owner: Bracknell Forest Borough Council - crown: false - agreement_signed: true -bracknelltowncouncil.gov.uk: - owner: Bracknell Town Council - crown: false - agreement_signed: false -bradford.gov.uk: - owner: City of Bradford Metropolitan District Council - crown: false - agreement_signed: true -bradfordonavontowncouncil.gov.uk: - owner: Bradford on Avon Town Council - crown: false - agreement_signed: false -brading.gov.uk: - owner: Brading Town Council - crown: false - agreement_signed: false -bradleystoke.gov.uk: - owner: Bradley Stoke Town Council - crown: false - agreement_signed: false -bradninch-tc.gov.uk: - owner: Bradninch Town Council - crown: false - agreement_signed: false -braintree.gov.uk: - owner: Braintree District Council - crown: false - agreement_signed: false -bramshaw.gov.uk: newforest.gov.uk -breamore.gov.uk: newforest.gov.uk -breckland.gov.uk: - owner: Breckland District Council - crown: false - agreement_signed: false -breckland-sholland.gov.uk: breckland.gov.uk -brent.gov.uk: - owner: Brent London Borough Council - crown: false - agreement_signed: false -brentwood.gov.uk: - owner: Brentwood Borough Council - crown: false - agreement_signed: false -bridgend.gov.uk: - owner: Bridgend County Borough Council - crown: false - agreement_signed: false -bridgendtowncouncil.gov.uk: - owner: Bridgend Town Council - crown: false - agreement_signed: false -bridgnorthtowncouncil.gov.uk: - owner: Bridgnorth Town Council - crown: false - agreement_signed: false -bridgwatertowncouncil.gov.uk: - owner: Bridgwater Town Council - crown: false - agreement_signed: false -bridlington.gov.uk: - owner: Bridlington Town Council - crown: false - agreement_signed: false -bridport-tc.gov.uk: - owner: Bridport Town Council - crown: false - agreement_signed: false -brierfield.gov.uk: - owner: Brierfield Town Council - crown: false - agreement_signed: false -brighton-hove.gov.uk: - owner: Brighton & Hove City Council - crown: false - agreement_signed: true -bristol-city.gov.uk: bristol.gov.uk -bristol.gov.uk: - owner: Bristol City Council - crown: false - agreement_signed: false -brixhamtowncouncil.gov.uk: - owner: Brixham Town Council - crown: false - agreement_signed: false -broadland.gov.uk: - owner: Broadland District Council - crown: false - agreement_signed: false -broadstairs.gov.uk: - owner: Broadstairs and St Peters Town Council - crown: false - agreement_signed: false -brockenhurst.gov.uk: newforest.gov.uk -bromorgannwg.gov.uk: valeofglamorgan.gov.uk -bromsgroveandredditch.gov.uk: bromsgrove.gov.uk -bromsgrove.gov.uk: - owner: Bromsgrove District Council - crown: false - agreement_signed: false -bromyardandwinslow-tc.gov.uk: - owner: Bromley and Winslow Town Council - crown: false - agreement_signed: false -broughtonandbrettoncommunitycouncil.gov.uk: - owner: Broughton and Bretton Community Council - crown: false - agreement_signed: false -broughtonwrexhamcommunitycouncil.gov.uk: - owner: Broughton Community Council - crown: false - agreement_signed: false -broxbourne.gov.uk: - owner: Broxbourne Borough Council - crown: false - agreement_signed: false -broxtowe.gov.uk: - owner: Broxtowe Borough Council - crown: false - agreement_signed: false -brutontowncouncil.gov.uk: - owner: Bruton Town Council - crown: false - agreement_signed: false -brynfordcommunitycouncil.gov.uk: - owner: Brynford Community Council - crown: false - agreement_signed: false -buckfastleigh.gov.uk: - owner: Buckfastleigh Town Council - crown: false - agreement_signed: false -buckingham-tc.gov.uk: - owner: Buckingham Town Council - crown: false - agreement_signed: false -bucksandsurreytradingstandards.gov.uk: surreycc.gov.uk -buckinghamshirepartnership.gov.uk: buckscc.gov.uk -bucksalc.gov.uk: buckscc.gov.uk -buckscc.gov.uk: - owner: Buckinghamshire County Council - crown: false - agreement_signed: true -buckshomechoice.gov.uk: aylesburyvale.gov.uk -bude-stratton.gov.uk: - owner: Bude Stratton Town Council - crown: false - agreement_signed: false -budleighsaltertontowncouncil.gov.uk: - owner: Budleigh Salterton Town Council - crown: false - agreement_signed: false -buildingcontrolpartnershiphants.gov.uk: fareham.gov.uk -bungaytowncouncil.gov.uk: - owner: Bungay Town Council - crown: false - agreement_signed: false -buntingford-tc.gov.uk: - owner: Buntingford Town Council - crown: false - agreement_signed: false -burgesshill.gov.uk: - owner: Burgess Hill Town Council - crown: false - agreement_signed: false -burleyparishcouncil.gov.uk: newforest.gov.uk -burnhamoncrouchtowncouncil.gov.uk: - owner: Burnham-On-Crouch Town Council - crown: false - agreement_signed: false -burnley.gov.uk: - owner: Burnley Borough Council - crown: false - agreement_signed: false -burntwood-tc.gov.uk: - owner: Burntwood Town Council - crown: false - agreement_signed: false -bury.gov.uk: - owner: Bury Metropolitan Borough Council - crown: false - agreement_signed: false -burystedmunds-tc.gov.uk: westsuffolk.gov.uk -caerdydd.gov.uk: cardiff.gov.uk -caerffili.gov.uk: caerphilly.gov.uk -caernarfontowncouncil.gov.uk: - owner: Caernarfon Royal Town Council - crown: false - agreement_signed: false -caerphilly.gov.uk: - owner: Caerphilly County Borough Council - crown: false - agreement_signed: false -caiapark.gov.uk: - owner: Caia Park Community Council - crown: false - agreement_signed: false -cainscross-pc.gov.uk: stroud.gov.uk -calderdale.gov.uk: - owner: Calderdale Metropolitan Borough Council - crown: false - agreement_signed: true -callington-tc.gov.uk: - owner: Callington Town Council - crown: false - agreement_signed: false -calne.gov.uk: - owner: Calne Town Council - crown: false - agreement_signed: false -camborne-tc.gov.uk: - owner: Camborne Town Council - crown: false - agreement_signed: false -cambridge.gov.uk: - owner: Cambridge City Council - crown: false - agreement_signed: false -cambridgeshire.gov.uk: - owner: Cambridgeshire County Council - crown: false - agreement_signed: true -camden.gov.uk: - owner: Camden Council - crown: false - agreement_signed: true -camelford-tc.gov.uk: - owner: Camelford Town Council - crown: false - agreement_signed: false -cannockchasedc.gov.uk: - owner: Cannock Chase District Council - crown: false - agreement_signed: false -canterbury.gov.uk: - owner: Canterbury City Council - crown: false - agreement_signed: false -canveyisland-tc.gov.uk: - owner: Canvey Island Town Council - crown: false - agreement_signed: false -capel-pc.gov.uk: molevalley.gov.uk -cardiff.gov.uk: - owner: Cardiff Council - crown: false - agreement_signed: false -carlisle.gov.uk: - owner: Carlisle City Council - crown: false - agreement_signed: false -carltoncolvilletowncouncil.gov.uk: - owner: Carlton Colville Town Council - crown: false - agreement_signed: false -carmarthenshire.gov.uk: carmarthenshire.gov.wales -carmarthenshire.gov.wales: - owner: Carmarthenshire County Council - crown: false - agreement_signed: true -carmarthentowncouncil.gov.uk: carmarthenshire.gov.wales -carterton-tc.gov.uk: - owner: Carterton Town Council - crown: false - agreement_signed: false -castlepoint.gov.uk: - owner: Castle Point Borough Council - crown: false - agreement_signed: false -causewaycoastandglens.gov.uk: colerainebc.gov.uk -centralbedfordshire.gov.uk: - owner: Central Bedfordshire Council - crown: false - agreement_signed: false -ceredigion.llyw.cymru: ceredigion.gov.uk -ceredigion.gov.uk: - owner: Ceredigion County Council - crown: false - agreement_signed: false -chalford-pc.gov.uk: stroud.gov.uk -chard.gov.uk: - owner: Chard Town Council - crown: false - agreement_signed: false -charnwood.gov.uk: - owner: Charnwood Borough Council - crown: false - agreement_signed: false -chelmsford.gov.uk: - owner: Chelmsford City Council - crown: false - agreement_signed: false -chelmsleywood-tc.gov.uk: - owner: Chelmsley Wood Town Council - crown: false - agreement_signed: false -cheltenham.gov.uk: - owner: Cheltenham Borough Council - crown: false - agreement_signed: false -cherwellandsouthnorthants.gov.uk: southnorthants.gov.uk -cherwell-dc.gov.uk: cherwell.gov.uk -cherwell.gov.uk: - owner: Cherwell District Council - crown: false - agreement_signed: false -chesham.gov.uk: - owner: Chesham Town Council - crown: false - agreement_signed: false -cheshireeast.gov.uk: - owner: Cheshire East Council - crown: false - agreement_signed: true -cheshire.gov.uk: - owner: Cheshire West and Chester Council - crown: false - agreement_signed: true -cheshireprepared.gov.uk: cheshire.gov.uk -cheshiresharedservices.gov.uk: - owner: Crewe & Nantwich Borough Council - crown: false - agreement_signed: false -cheshirewestandchester.gov.uk: cheshire.gov.uk -cheshirewest.gov.uk: cheshire.gov.uk -chesterfield.gov.uk: - owner: Chesterfield Borough Council - crown: false - agreement_signed: false -chester.gov.uk: cheshire.gov.uk -chichestercity.gov.uk: - owner: Chichester City Council - crown: false - agreement_signed: false -chichester.gov.uk: - owner: Chichester District Council - crown: false - agreement_signed: false -chickerell-tc.gov.uk: - owner: Chickerell Town Council - crown: false - agreement_signed: false -chiltern.gov.uk: - owner: Chiltern District Council - crown: false - agreement_signed: true -chiltondurham-tc.gov.uk: - owner: Chilton Town Council - crown: false - agreement_signed: false -chippenham.gov.uk: - owner: Chippenham Town Council - crown: false - agreement_signed: false -chorley.gov.uk: - owner: Chorley Borough Council - crown: false - agreement_signed: false -christchurchandeastdorset.gov.uk: christchurch.gov.uk -christchurch.gov.uk: - owner: Christchurch Borough Council - crown: false - agreement_signed: false -chudleigh-tc.gov.uk: - owner: Chudleigh Town Council - crown: false - agreement_signed: false -churchaston-pc.gov.uk: telford.gov.uk -churchstretton-tc.gov.uk: - owner: Church Stretton Town Council - crown: false - agreement_signed: false -cilcaincommunitycouncil.gov.uk: - owner: Cilcain Community Council - crown: false - agreement_signed: false -cinderfordtowncouncil.gov.uk: - owner: Cinderford Town Council - crown: false - agreement_signed: false -cirencester.gov.uk: - owner: Cirencester Town Council - crown: false - agreement_signed: false -cityofworcester.gov.uk: worcester.gov.uk -clacks.gov.uk: - owner: Clackmannanshire Council - crown: false - agreement_signed: true -clevedon.gov.uk: - owner: Clevedon Town Council - crown: false - agreement_signed: false -clitheroetowncouncil.gov.uk: - owner: Clitheroe Town Council - crown: false - agreement_signed: false -cmktowncouncil.gov.uk: - owner: Central Milton Keynes Town Council - crown: false - agreement_signed: false -cncbuildingcontrol.gov.uk: south-norfolk.gov.uk -coastalwight.gov.uk: iow.gov.uk -coedffranc-wcc.gov.uk: - owner: Coedffranc Community Council - crown: false - agreement_signed: false -colchester.gov.uk: - owner: Colchester Borough Council - crown: false - agreement_signed: false -colefordtowncouncil.gov.uk: - owner: Coleford Town Council - crown: false - agreement_signed: false -colerainebc.gov.uk: - owner: Coleraine Borough Council - crown: false - agreement_signed: false -coleshilltowncouncil.gov.uk: - owner: Coleshill Town Council - crown: false - agreement_signed: false -colwyn-tc.gov.uk: - owner: Bay of Colwyn Town Council - crown: false - agreement_signed: false -cne-siar.gov.uk: - owner: Comhairle nan Eilean Siar - crown: false - agreement_signed: true -congleton-tc.gov.uk: - owner: Congleton Town Council - crown: false - agreement_signed: false -conwy.gov.uk: - owner: Conwy County Borough Council - crown: false - agreement_signed: true -conwytowncouncil.gov.uk: - owner: Conwy Town Council - crown: false - agreement_signed: false -cookstown.gov.uk: - owner: Cookstown District Council - crown: false - agreement_signed: false -copelandbc.gov.uk: copeland.gov.uk -copeland.gov.uk: - owner: Copeland Borough Council - crown: false - agreement_signed: false -corby.gov.uk: - owner: Corby Borough Council - crown: false - agreement_signed: true -cornwall-aonb.gov.uk: cornwall.gov.uk -cornwall.gov.uk: - owner: Cornwall Council - crown: false - agreement_signed: true -corsham.gov.uk: - owner: Corsham Town Council - crown: false - agreement_signed: false -cotgrave-tc.gov.uk: - owner: Cotgrave Town Council - crown: false - agreement_signed: false -cotswold.gov.uk: - owner: Cotswold District Council - crown: false - agreement_signed: false -coventry.gov.uk: - owner: Coventry City Council - crown: false - agreement_signed: false -cowbridge-tc.gov.uk: - owner: Cowbridge (Ancient Borough) and Llanblethian Town Council - crown: false - agreement_signed: false -cramlingtontowncouncil.gov.uk: - owner: Cramlington Town Council - crown: false - agreement_signed: false -cranbrooktowncouncil.gov.uk: - owner: Cranbrook Town Council - crown: false - agreement_signed: false -cravendc.gov.uk: - owner: Craven District Council - crown: false - agreement_signed: false -crawley.gov.uk: - owner: Crawley Borough Council - crown: false - agreement_signed: false -crediton.gov.uk: - owner: Credition Town Council - crown: false - agreement_signed: false -crewetowncouncil.gov.uk: - owner: Crewe Town Council - crown: false - agreement_signed: false -crewkerne-tc.gov.uk: - owner: Crewkerne Town Council - crown: false - agreement_signed: false -cricklade-tc.gov.uk: crickladetowncouncil.gov.uk -crickladetowncouncil.gov.uk: - owner: Cricklade Town Council - crown: false - agreement_signed: false -cringlefordparishcouncil.gov.uk: - owner: Cringleford Parich Council - crown: false - agreement_signed: false -cromer-tc.gov.uk: - owner: Cromer Town Council - crown: false - agreement_signed: false -crowboroughtowncouncil.gov.uk: - owner: Crowborough Town Council - crown: false - agreement_signed: false -croydon.gov.uk: - owner: Croydon Council - crown: false - agreement_signed: true -cullomptontowncouncil.gov.uk: - owner: Cullompton Town Council - crown: false - agreement_signed: false -culturalpropertyadvice.gov.uk: artscouncil.org.uk -cumbriacc.gov.uk: cumbria.gov.uk -cumbriafire.gov.uk: cumbria.gov.uk -cumbria.gov.uk: - owner: Cumbria County Council - crown: false - agreement_signed: false -currie-scc.gov.uk: - owner: Currie Community Council - crown: false - agreement_signed: false -cwmbran.gov.uk: torfaen.gov.uk -cyngortrefcaerfyrddin.gov.uk: carmarthenshire.gov.wales -cyngortrefrhuthun.gov.uk: ruthintowncouncil.gov.uk -cyngortrefybarri.gov.uk: valeofglamorgan.gov.uk -dab-vjb.gov.uk: west-dunbarton.gov.uk -dacorum.gov.uk: - owner: Dacorum Borough Council - crown: false - agreement_signed: true -darleydale.gov.uk: - owner: Darley Dale Town Council - crown: false - agreement_signed: false -darlington.gov.uk: - owner: Darlington Borough Council - crown: false - agreement_signed: false -dartford.gov.uk: - owner: Dartford Borough Council - crown: false - agreement_signed: false -daventrydc.gov.uk: - owner: Daventry District Council - crown: false - agreement_signed: false -daventrytowncouncil.gov.uk: - owner: Daventry Town Council - crown: false - agreement_signed: false -dawlish.gov.uk: - owner: Dawlish Town Council - crown: false - agreement_signed: false -deal.gov.uk: - owner: Deal Town Council - crown: false - agreement_signed: false -denbighshire.gov.uk: - owner: Denbighshire County Council - crown: false - agreement_signed: false -denbightowncouncil.gov.uk: denbighshire.gov.uk -derby.gov.uk: - owner: Derby City Council - crown: false - agreement_signed: false -derbyshirealc.gov.uk: - owner: Derbyshire Association of Local Councils - crown: false - agreement_signed: false -derbyshiredales.gov.uk: - owner: Derbyshire Dales District Council - crown: false - agreement_signed: false -derbyshire.gov.uk: - owner: Derbyshire County Council - crown: false - agreement_signed: false -derbyshirepartnership.gov.uk: derbyshire.gov.uk -desboroughtowncouncil.gov.uk: - owner: Desborough Town Council - crown: false - agreement_signed: false -devizes-tc.gov.uk: - owner: Devizes Town Council - crown: false - agreement_signed: false -devonaudit.gov.uk: devon.gov.uk -devonebiz.gov.uk: devon.gov.uk -devon.gov.uk: - owner: Devon County Council - crown: false - agreement_signed: true -devonjobs.gov.uk: devon.gov.uk -didcot.gov.uk: - owner: Didcot Town Council - crown: false - agreement_signed: false -diss.gov.uk: - owner: Diss Town Council - crown: false - agreement_signed: false -doncaster.gov.uk: - owner: Doncaster Metropolitan Borough Council - crown: false - agreement_signed: true -dorchester-tc.gov.uk: - owner: Dorchester Town Council - crown: false - agreement_signed: false -dorsetcouncil.gov.uk: dorsetcc.gov.uk -dorsetcc.gov.uk: - owner: Dorset County Council - crown: false - agreement_signed: true -dorsetforyou.gov.uk: weymouth.gov.uk -dorset.gov.uk: - owner: West Dorset District Council - crown: false - agreement_signed: false -dorsetwastepartnership.gov.uk: dorsetcc.gov.uk -dorset.gov.uk: dorsetcc.gov.uk -dover.gov.uk: - owner: Dover District Council - crown: false - agreement_signed: false -dovertowncouncil.gov.uk: - owner: Dover Town Council - crown: false - agreement_signed: false -downdc.gov.uk: - owner: Down District Council - crown: false - agreement_signed: false -droitwichspa.gov.uk: - owner: Droitwich Spa Town Council - crown: false - agreement_signed: false -dronfield.gov.uk: - owner: Dronfield Town Council - crown: false - agreement_signed: false -dudley.gov.uk: - owner: Dudley Metropolitan Borough Council - crown: false - agreement_signed: true -dumgal.gov.uk: - owner: Dumfries and Galloway Council - crown: false - agreement_signed: false -dundeecity.gov.uk: - owner: Dundee City Council - crown: false - agreement_signed: false -dungannon.gov.uk: - owner: Dungannon and South Tyrone Borough Council - crown: false - agreement_signed: false -dunstable.gov.uk: - owner: Dunstable Town Council - crown: false - agreement_signed: false -durhamcity.gov.uk: durham.gov.uk -durham.gov.uk: - owner: Durham County Council - crown: false - agreement_signed: false -durringtontowncouncil.gov.uk: - owner: Durrington Town Council - crown: false - agreement_signed: false -dursleytowncouncil.gov.uk: - owner: Dursley Town Council - crown: false - agreement_signed: false -ealing.gov.uk: - owner: Ealing Council - crown: false - agreement_signed: true -earley-tc.gov.uk: - owner: Earley Town Council - crown: false - agreement_signed: false -easingwold.gov.uk: - owner: Easingwold Town Council - crown: false - agreement_signed: false -east-ayrshire.gov.uk: - owner: East Ayrshire Council - crown: false - agreement_signed: false -eastbourne.gov.uk: - owner: Eastbourne Borough Council - crown: false - agreement_signed: false -eastcambs.gov.uk: - owner: East Cambridgeshire District Council - crown: false - agreement_signed: false -eastdevon.gov.uk: - owner: East Devon District Council - crown: false - agreement_signed: true -eastdorsetdc.gov.uk: eastdorset.gov.uk -eastdorset.gov.uk: - owner: East Dorset District Council - crown: false - agreement_signed: false -eastdunbarton.gov.uk: - owner: East Dunbartonshire Council - crown: false - agreement_signed: true -eastgrinstead.gov.uk: - owner: East Grinstead Town Council - crown: false - agreement_signed: false -easthants.gov.uk: - owner: East Hampshire District Council - crown: false - agreement_signed: false -eastherts.gov.uk: - owner: East Herts District Council - crown: false - agreement_signed: false -eastkent.gov.uk: thanet.gov.uk -eastleigh.gov.uk: - owner: Eastleigh Borough Council - crown: false - agreement_signed: false -eastlindsey.gov.uk: - owner: East Lindsey District Council - crown: false - agreement_signed: false -eastlothian.gov.uk: - owner: East Lothian Council - crown: false - agreement_signed: false -east-northamptonshire.gov.uk: - owner: East Northamptonshire Council - crown: false - agreement_signed: false -eastrenfrewshire.gov.uk: - owner: East Renfrewshire Council - crown: false - agreement_signed: true -eastriding.gov.uk: - owner: East Riding of Yorkshire Council - crown: false - agreement_signed: true -eaststaffsbc.gov.uk: - owner: East Staffordshire Borough Council - crown: false - agreement_signed: true -eastsuffolk.gov.uk: waveney.gov.uk -eastsussexcc.gov.uk: eastsussex.gov.uk -eastsussex.gov.uk: - owner: East Sussex County Council - crown: false - agreement_signed: true -e-chorley.gov.uk: chorley.gov.uk -edenbridgetowncouncil.gov.uk: - owner: Edenbridge Town Council - crown: false - agreement_signed: false -eden.gov.uk: - owner: Eden District Council - crown: false - agreement_signed: false -edinburgh.gov.uk: - owner: The City of Edinburgh Council - crown: false - agreement_signed: true -eldc.gov.uk: eastlindsey.gov.uk -e-lindsey.gov.uk: eastlindsey.gov.uk -ellesmere-tc.gov.uk: - owner: Ellesmere Town Council - crown: false - agreement_signed: false -elmbridge.gov.uk: - owner: Elmbridge Borough Council - crown: false - agreement_signed: false -elstreeborehamwood-tc.gov.uk: - owner: Elstree & Borehamwood Town Council - crown: false - agreement_signed: false -emergencynorthyorks.gov.uk: northyorks.gov.uk -emersonsgreen-tc.gov.uk: - owner: Emersons Green Town Council - crown: false - agreement_signed: false -encor.gov.uk: corby.gov.uk -enfield.gov.uk: - owner: Enfield London Borough Council - crown: false - agreement_signed: true -eppingforestdc.gov.uk: - owner: Epping Forest District Council - crown: false - agreement_signed: false -eppingtowncouncil.gov.uk: - owner: Epping Town Council - crown: false - agreement_signed: false -epsom-ewell.gov.uk: - owner: Epsom and Ewell Borough Council - crown: false - agreement_signed: false -erewash.gov.uk: - owner: Erewash Borough Council - crown: false - agreement_signed: false -esfrs.org.uk: - owner: East Sussex Fire and Rescue Service - crown: false - agreement_signed: true -essexcc.gov.uk: essex.gov.uk -essex.gov.uk: - owner: Essex County Council - crown: false - agreement_signed: true -essexlegalservices.gov.uk: essex.gov.uk -eveshamtowncouncil.gov.uk: - owner: Evesham Town Council - crown: false - agreement_signed: false -exeterandeastdevon.gov.uk: eastdevon.gov.uk -exeter.gov.uk: - owner: Exeter City Council - crown: false - agreement_signed: false -exmouth.gov.uk: - owner: Exmouth Town Council - crown: false - agreement_signed: false -fairfordtowncouncil.gov.uk: - owner: Fairford Town Council - crown: false - agreement_signed: false -falkirk-council.gov.uk: falkirk.gov.uk -falkirk.gov.uk: - owner: Falkirk Council - crown: false - agreement_signed: true -fareham.gov.uk: - owner: Fareham Borough Council - crown: false - agreement_signed: false -faringdontowncouncil.gov.uk: - owner: Faringdon Town Council - crown: false - agreement_signed: false -farnham.gov.uk: - owner: Farnham Town Council - crown: false - agreement_signed: false -farrier-reg.gov.uk: - owner: The Farriers Registration Council - crown: false - agreement_signed: false -favershamtowncouncil.gov.uk: - owner: Faversham Town Council - crown: false - agreement_signed: false -fdean.gov.uk: forestofdean.gov.uk -featherstone-tc.gov.uk: - owner: Featherstone Town Council - crown: false - agreement_signed: false -felixstowe.gov.uk: - owner: Felixstowe Town Council - crown: false - agreement_signed: false -fenland.gov.uk: - owner: Fenland District Council - crown: false - agreement_signed: false -fennycompton-pc.gov.uk: stratford.gov.uk -ferndown.gov.uk: - owner: Ferndown Town Council - crown: false - agreement_signed: false -fernhurst-wsx-pc.gov.uk: chichester.gov.uk -ferryhill.gov.uk: - owner: Ferryhill Town Council - crown: false - agreement_signed: false -fife.gov.uk: - owner: Fife Council - crown: false - agreement_signed: false -filtontowncouncil.gov.uk: - owner: Filton Town Council - crown: false - agreement_signed: false -fishbourne-pc.gov.uk: chichester.gov.uk -flamborough-pc.gov.uk: eastriding.gov.uk -fleet-tc.gov.uk: - owner: Fleet Town Council - crown: false - agreement_signed: false -flintshire.gov.uk: - owner: Flintshire County Council - crown: false - agreement_signed: false -flitwick.gov.uk: - owner: Flitwick Town Council - crown: false - agreement_signed: false -folkestone-tc.gov.uk: shepway.gov.uk -fordingbridge.gov.uk: newforest.gov.uk -forest-heath.gov.uk: westsuffolk.gov.uk -forestofdean.gov.uk: - owner: Forest Of Dean District Council - crown: false - agreement_signed: false -foweytowncouncil.gov.uk: - owner: Restormel Borough Council - crown: false - agreement_signed: false -frodsham.gov.uk: - owner: Frodsham Town Council - crown: false - agreement_signed: false -frometowncouncil.gov.uk: - owner: Frome Town Council - crown: false - agreement_signed: false -fylde.gov.uk: - owner: Fylde Borough Council - crown: false - agreement_signed: true -galwgofal.gov.uk: conwy.gov.uk -gateshead.gov.uk: - owner: Gateshead Metropolitan Borough Council - crown: false - agreement_signed: true -gedling.gov.uk: - owner: Gedling Borough Council - crown: false - agreement_signed: true -gillinghamdorset-tc.gov.uk: - owner: Gillingham Town Council - crown: false - agreement_signed: false -glamarchives.gov.uk: cardiff.gov.uk -glasgow.gov.uk: - owner: Glasgow City Council - crown: false - agreement_signed: false -glastonbury.gov.uk: - owner: Glastonbury Town Council - crown: false - agreement_signed: false -gloscc.gov.uk: gloucestershire.gov.uk -gloucester.gov.uk: - owner: Gloucester City Council - crown: false - agreement_signed: false -gloucestershire.gov.uk: - owner: Gloucestershire County Council - crown: false - agreement_signed: false -glynneathtowncouncil.gov.uk: - owner: Glynneath Town Council - crown: false - agreement_signed: false -godalming-jbc.gov.uk: - owner: Godalming Town Council - crown: false - agreement_signed: false -godalming-tc.gov.uk: waverley.gov.uk -goole-tc.gov.uk: - owner: Goole Town Council - crown: false - agreement_signed: false -gorseinontowncouncil.gov.uk: - owner: Gorseinon Town Council - crown: false - agreement_signed: false -gosport.gov.uk: - owner: Gosport Borough Council - crown: false - agreement_signed: false -goytre.gov.uk: - owner: Goetre Fawr Community Council - crown: false - agreement_signed: false -grampiancaredata.gov.uk: aberdeenshire.gov.uk -grangeoversandstowncouncil.gov.uk: - owner: Grange Over Sands Town Council - crown: false - agreement_signed: false -gravesham.gov.uk: - owner: Gravesham Borough Council - crown: false - agreement_signed: true -greatalne-pc.gov.uk: stratford.gov.uk -great-aycliffe.gov.uk: - owner: Great Aycliffe Town Council - crown: false - agreement_signed: false -greatbursteadsouthgreenparishcouncil.gov.uk: - owner: Great Burstead and South Green Village Council - crown: false - agreement_signed: false -greatdunmow-tc.gov.uk: - owner: Great Dunmow Town Council - crown: false - agreement_signed: false -great-torringtontowncouncil.gov.uk: - owner: Great Torrington Town Council - crown: false - agreement_signed: false -great-yarmouth.gov.uk: - owner: Great Yarmouth Borough Council - crown: false - agreement_signed: false -guildford.gov.uk: - owner: Guildford Borough Council - crown: false - agreement_signed: false -gwentarchives.gov.uk: blaenau-gwent.gov.uk -gwynedd.gov.uk: - owner: Gwynedd County Council - crown: false - agreement_signed: true -hackney.gov.uk: - owner: Hackney London Borough Council - crown: false - agreement_signed: true -hailsham-tc.gov.uk: - owner: Hailsham Town Council - crown: false - agreement_signed: false -halchereford.gov.uk: - owner: Herefordshire Association of Local Councils - crown: false - agreement_signed: false -halewoodtowncouncil.gov.uk: - owner: Halewood Town Council - crown: false - agreement_signed: false -halton-borough.gov.uk: halton.gov.uk -halton.gov.uk: - owner: Halton Borough Council - crown: false - agreement_signed: true -haltwhistle-tc.gov.uk: - owner: Haltwhistle Town Council - crown: false - agreement_signed: false -hambleton.gov.uk: - owner: Hambleton District Council - crown: false - agreement_signed: false -hampshire.gov.uk: hants.gov.uk -hantsdirect.gov.uk: hants.gov.uk -hants.gov.uk: - owner: Hampshire County Council - crown: false - agreement_signed: true -hantsiowcaddie.gov.uk: hants.gov.uk -hantsnet.gov.uk: hants.gov.uk -hantsweb.gov.uk: hants.gov.uk -harborough.gov.uk: - owner: Harborough District Council - crown: false - agreement_signed: false -haringey.gov.uk: - owner: Haringey London Borough Council - crown: false - agreement_signed: true -harlow.gov.uk: - owner: Harlow District Council - crown: false - agreement_signed: false -harpenden.gov.uk: - owner: Harpenden Town Council - crown: false - agreement_signed: false -harrogate.gov.uk: - owner: Harrogate Borough Council - crown: false - agreement_signed: false -harrow.gov.uk: - owner: Harrow London Borough Council - crown: false - agreement_signed: false -hart.gov.uk: hants.gov.uk -hartlepool.gov.uk: - owner: Hartlepool Borough Council - crown: false - agreement_signed: true -hastings.gov.uk: - owner: Hastings Borough Council - crown: false - agreement_signed: false -hatfield.gov.uk: welwyn.gov.uk -hatfield-herts.gov.uk: - owner: Hatfield Town Council - crown: false - agreement_signed: false -havant.gov.uk: - owner: Havant District Council - crown: false - agreement_signed: false -haverhill-tc.gov.uk: - owner: Haverhill Town Council - crown: false - agreement_signed: false -hawardencommunitycouncil.gov.uk: - owner: Hawarden Community Council - crown: false - agreement_signed: false -hawkinge-tc.gov.uk: - owner: Hawkinge Town Council - crown: false - agreement_signed: false -haxbytowncouncil.gov.uk: - owner: Haxby Town Council - crown: false - agreement_signed: false -haytowncouncil.gov.uk: - owner: Hay Town Council - crown: false - agreement_signed: false -haywardsheath.gov.uk: - owner: Haywards Heath Town Council - crown: false - agreement_signed: false -headley-pc.gov.uk: molevalley.gov.uk -hedgeend-tc.gov.uk: - owner: Hedge End Town Council - crown: false - agreement_signed: false -hednesford-tc.gov.uk: - owner: Hednesford Town Council - crown: false - agreement_signed: false -hedon.gov.uk: - owner: Hedon Town Council - crown: false - agreement_signed: false -helston-tc.gov.uk: - owner: Helston Town Council - crown: false - agreement_signed: false -henley-in-arden-pc.gov.uk: stratford.gov.uk -henleytowncouncil.gov.uk: - owner: Henley-on-Thames Town Council - crown: false - agreement_signed: false -heps.gov.uk: eastriding.gov.uk -herefordcitycouncil.gov.uk: - owner: Hereford City Council - crown: false - agreement_signed: false -herefordshire.gov.uk: - owner: Herefordshire Council - crown: false - agreement_signed: true -hertford.gov.uk: - owner: Hertford Town Council - crown: false - agreement_signed: false -hertfordshire.gov.uk: - owner: Hertfordshire County Council - crown: false - agreement_signed: false -hertscc.gov.uk: hertfordshire.gov.uk -hertsfire.gov.uk: hertfordshire.gov.uk -hertsmere.gov.uk: - owner: Hertsmere Borough Council - crown: false - agreement_signed: false -hertspartnership-ala.gov.uk: eastherts.gov.uk -hessletowncouncil.gov.uk: - owner: Hessle Town Council - crown: false - agreement_signed: false -hettontowncouncil.gov.uk: - owner: Hetton Town Council - crown: false - agreement_signed: false -hexhamtowncouncil.gov.uk: - owner: Hexham Town Council - crown: false - agreement_signed: false -highamferrers-tc.gov.uk: - owner: Higham Ferrers Town Council - crown: false - agreement_signed: false -highland.gov.uk: - owner: The Highland Council - crown: false - agreement_signed: true -highpeak.gov.uk: - owner: High Peak Borough Council - crown: false - agreement_signed: false -highworthtowncouncil.gov.uk: - owner: Highworth Town Council - crown: false - agreement_signed: false -hillingdon.gov.uk: - owner: London Borough of Hillingdon - crown: false - agreement_signed: true -hinckley-bosworth.gov.uk: - owner: Hinckley & Bosworth Borough Council - crown: false - agreement_signed: false -hiow.gov.uk: hants.gov.uk -holmwood-pc.gov.uk: molevalley.gov.uk -holsworthytowncouncil.gov.uk: - owner: Holsworthy Town Council - crown: false - agreement_signed: false -holywell-town.gov.uk: - owner: Holywell Town Council - crown: false - agreement_signed: false -honiton.gov.uk: - owner: Honiton Town Council - crown: false - agreement_signed: false -hopecommunitycouncil.gov.uk: - owner: Hope Community Council - crown: false - agreement_signed: false -horleysurrey-tc.gov.uk: - owner: Horley Town Council - crown: false - agreement_signed: false -hornsea.gov.uk: - owner: Hornsea Town Council - crown: false - agreement_signed: false -horsforthtowncouncil.gov.uk: - owner: Horsforth Town Council - crown: false - agreement_signed: false -horsham.gov.uk: - owner: Horsham District Council - crown: false - agreement_signed: true -horwich.gov.uk: - owner: Horwich Town Council - crown: false - agreement_signed: false -hounslow.gov.uk: - owner: Hounslow London Borough Council - crown: false - agreement_signed: true -howden-tc.gov.uk: - owner: Howden Town Council - crown: false - agreement_signed: false -hullcc.gov.uk: hull.gov.uk -hull.gov.uk: - owner: Kingston Upon Hull City Council - crown: false - agreement_signed: false -humberlrf.gov.uk: eastriding.gov.uk -hungerford-tc.gov.uk: - owner: Hungerford Town Council - crown: false - agreement_signed: false -hunstantontowncouncil.gov.uk: - owner: Hunstanton Town Council - crown: false - agreement_signed: false -huntingdonshire.gov.uk: - owner: Huntingdonshire District Council - crown: false - agreement_signed: false -huntingdontown.gov.uk: - owner: Huntingdon Town Council - crown: false - agreement_signed: false -huntsdc.gov.uk: huntingdonshire.gov.uk -hyde-pc.gov.uk: newforest.gov.uk -hyndburnbc.gov.uk: hyndburn.gov.uk -hyndburn.gov.uk: - owner: Hyndburn Borough Council - crown: false - agreement_signed: false -hytheanddibden.gov.uk: newforest.gov.uk -hythe-tc.gov.uk: hythetowncouncil.gov.uk -hythetowncouncil.gov.uk: - owner: Hythe Town Council - crown: false - agreement_signed: false -ilfracombetowncouncil.gov.uk: - owner: Ilfracombe Town Council - crown: false - agreement_signed: false -ilminster.gov.uk: - owner: Ilminster Town Council - crown: false - agreement_signed: false -immingham-tc.gov.uk: - owner: Immingham Town Council - crown: false - agreement_signed: false -internalauditscotland.gov.uk: west-dunbarton.gov.uk -inverclyde.gov.uk: - owner: Inverclyde Council - crown: false - agreement_signed: true -iow.gov.uk: - owner: Isle of Wight Council - crown: false - agreement_signed: false -ipswich.gov.uk: - owner: Ipswich Borough Council - crown: false - agreement_signed: false -irthlingborough-tc.gov.uk: - owner: Irthlingborough Town Council - crown: false - agreement_signed: false -islington.gov.uk: - owner: Islington London Borough Council - crown: false - agreement_signed: true -isycoedcommunitycouncil.gov.uk: - owner: Isycoed Community Council - crown: false - agreement_signed: false -ivybridge.gov.uk: - owner: Ivybridge Town Council - crown: false - agreement_signed: false -keighley.gov.uk: - owner: Keighley Town Council - crown: false - agreement_signed: false -kendaltowncouncil.gov.uk: - owner: Kendal Town Council - crown: false - agreement_signed: false -kentalc.gov.uk: - owner: Kent Association of Local Councils - crown: false - agreement_signed: false -kentconnects.gov.uk: kent.gov.uk -kent.gov.uk: - owner: Kent County Council - crown: false - agreement_signed: true -kentparishes.gov.uk: kent.gov.uk -kerrier.gov.uk: cornwall.gov.uk -kerrycommunitycouncil.gov.uk: - owner: Kerry Community Council - crown: false - agreement_signed: false -keswicktowncouncil.gov.uk: - owner: Keswick Town Council - crown: false - agreement_signed: false -kettering.gov.uk: - owner: Kettering Borough Council - crown: false - agreement_signed: false -keynsham-tc.gov.uk: - owner: Keynsham Town Council - crown: false - agreement_signed: false -kidderminstertowncouncil.gov.uk: - owner: Kidderminster Town Council - crown: false - agreement_signed: false -kidsgrovetowncouncil.gov.uk: - owner: Kidsgrove Town Council - crown: false - agreement_signed: false -kidwelly.gov.uk: - owner: Kidwelly Town Council - crown: false - agreement_signed: false -kimberley-tc.gov.uk: - owner: Kimberley Town Council - crown: false - agreement_signed: false -kingsbridge.gov.uk: - owner: Kingsbridge Town Council - crown: false - agreement_signed: false -kingston.gov.uk: - owner: Kingston upon Thames London Borough Council - crown: false - agreement_signed: true -kingtontowncouncil.gov.uk: - owner: Kington Town Council - crown: false - agreement_signed: false -kirkbymoorsidetowncouncil.gov.uk: - owner: Kirkbymoorside Town Council - crown: false - agreement_signed: false -kirklees.gov.uk: - owner: Kirklees Council - crown: false - agreement_signed: false -kirtoninlindseytowncouncil.gov.uk: - owner: Kirton in Lindsey Town Council - crown: false - agreement_signed: false -knaresboroughtowncouncil.gov.uk: - owner: Knaresborough Town Council - crown: false - agreement_signed: false -knowsley.gov.uk: - owner: Knowsley Metropolitan Borough Council - crown: false - agreement_signed: true -knowsleytowncouncil.gov.uk: - owner: Knowsley Town Council - crown: false - agreement_signed: false -knutsfordtowncouncil.gov.uk: - owner: Knutsford Town Council - crown: false - agreement_signed: false -lalestoncommunitycouncil.gov.uk: - owner: Laleston Community Council - crown: false - agreement_signed: false -lambeth.gov.uk: - owner: Lambeth London Borough Council - crown: false - agreement_signed: false -lampeter-tc.gov.uk: - owner: Lampeter Town Council - crown: false - agreement_signed: false -lancashire.gov.uk: - owner: Lancashire County Council - crown: false - agreement_signed: true -lancaster.gov.uk: - owner: Lancaster City Council - crown: false - agreement_signed: true -laugharnetownship-wcc.gov.uk: - owner: Laugharne Township Community Council - crown: false - agreement_signed: false -launceston-tc.gov.uk: - owner: Launceston Town Council - crown: false - agreement_signed: false -lawhf.gov.uk: telford.gov.uk -lawleyoverdale-pc.gov.uk: telford.gov.uk -lbbd.gov.uk: barking-dagenham.gov.uk -lbhf.gov.uk: - owner: Hammersmith and Fulham London Borough Council - crown: false - agreement_signed: false -lbwf.gov.uk: walthamforest.gov.uk -leamingtonspatowncouncil.gov.uk: - owner: Royal Leamington Spa Town Council - crown: false - agreement_signed: false -ledburytowncouncil.gov.uk: - owner: Ledbury Town Council - crown: false - agreement_signed: false -leeds.gov.uk: - owner: Leeds City Council - crown: false - agreement_signed: true -leek-tc.gov.uk: leektowncouncil.gov.uk -leektowncouncil.gov.uk: - owner: Leek Town Council - crown: false - agreement_signed: false -leicester.gov.uk: - owner: Leicester City Council - crown: false - agreement_signed: true -leicestershireandrutlandalc.gov.uk: - owner: Leicestershire and Rutland Association of Local Councils - crown: false - agreement_signed: false -leicestershire-fire.gov.uk: leicestershire.gov.uk -leicestershire.gov.uk: - owner: Leicestershire County Council - crown: false - agreement_signed: false -leics.gov.uk: leicestershire.gov.uk -leighonseatowncouncil.gov.uk: - owner: Leigh-on-Sea Town Council - crown: false - agreement_signed: false -leightonlinslade-tc.gov.uk: - owner: Leighton Linslade Town Council - crown: false - agreement_signed: false -leistontowncouncil.gov.uk: - owner: Leiston-cum-Sizewell Town Council - crown: false - agreement_signed: false -leominstertowncouncil.gov.uk: - owner: Leominster Town Council - crown: false - agreement_signed: false -lewes-eastbourne.gov.uk: eastbourne.gov.uk -lewes.gov.uk: - owner: Lewes District Council - crown: false - agreement_signed: false -lewes-tc.gov.uk: - owner: Lewes Town Council - crown: false - agreement_signed: false -lewisham.gov.uk: - owner: Lewisham London Borough Council - crown: false - agreement_signed: true -lg-em.gov.uk: - owner: East Midlands Councils - crown: false - agreement_signed: false -lgyh.gov.uk: wakefield.gov.uk -lichfielddc.gov.uk: - owner: Lichfield District Council - crown: false - agreement_signed: false -lichfield.gov.uk: - owner: Lichfield City Council - crown: false - agreement_signed: false -limavady.gov.uk: - owner: Limavady Borough Council - crown: false - agreement_signed: false -lincoln.gov.uk: - owner: Lincoln City Council - crown: false - agreement_signed: false -lincolnshire.gov.uk: - owner: Lincolnshire County Council - crown: false - agreement_signed: true -lincsbc.gov.uk: eastlindsey.gov.uk -lisburncastlereagh.gov.uk: - owner: Lisburn City Council - crown: false - agreement_signed: false -lisburncityandcastlereagh.gov.uk: lisburncastlereagh.gov.uk -liskeard.gov.uk: - owner: Liskeard Town Council - crown: false - agreement_signed: false -littlechalfontparishcouncil.gov.uk: chiltern.gov.uk -littlechalfont-pc.gov.uk: chiltern.gov.uk -littlehampton-tc.gov.uk: - owner: Littlehampton Town Council - crown: false - agreement_signed: false -liverpoolcityregion-ca.gov.uk: knowsley.gov.uk -liverpool.gov.uk: - owner: Liverpool City Council - crown: false - agreement_signed: false -llanasacommunitycouncil.gov.uk: - owner: Llanasa Community Council - crown: false - agreement_signed: false -llandeilo.gov.uk: - owner: Llandeilo Town Council - crown: false - agreement_signed: false -llandudno.gov.uk: - owner: Cyngor Tref Llandudno Town Council - crown: false - agreement_signed: false -llanelli-rural.gov.uk: - owner: Llanelli Rural Council - crown: false - agreement_signed: false -llanellitowncouncil.gov.uk: - owner: Llanelli Town Council - crown: false - agreement_signed: false -llanfaelogcommunitycouncil.gov.uk: - owner: Llanfaelog Community Council - crown: false - agreement_signed: false -llanfaircommunitycouncil.gov.uk: - owner: Llanfair Community Council - crown: false - agreement_signed: false -llangennechcommunitycouncil.gov.uk: - owner: Llangennech Community Council - crown: false - agreement_signed: false -llangollentowncouncil.gov.uk: - owner: Llangollen Town Council - crown: false - agreement_signed: false -llantwitmajortowncouncil.gov.uk: - owner: Llantwit Major Town Council - crown: false - agreement_signed: false -llwchwrtowncouncil.gov.uk: - owner: Llwchwr Town Council - crown: false - agreement_signed: false -localgovernmentjobsni.gov.uk: ardsandnorthdown.gov.uk -londoncareplacements.gov.uk: londoncouncils.gov.uk -londoncouncils.gov.uk: - owner: London Councils - crown: false - agreement_signed: false -londontribunals.gov.uk: londoncouncils.gov.uk -longcompton-pc.gov.uk: stratford.gov.uk -longridge-tc.gov.uk: - owner: Longridge Town Council - crown: false - agreement_signed: false -looetowncouncil.gov.uk: - owner: Looe Town Council - crown: false - agreement_signed: false -lookinglocal.gov.uk: kirklees.gov.uk -lostwithieltowncouncil.gov.uk: - owner: Lostwithiel Town Council - crown: false - agreement_signed: false -loughton-tc.gov.uk: - owner: Loughton Town Council - crown: false - agreement_signed: false -louthtowncouncil.gov.uk: - owner: Louth Town Council - crown: false - agreement_signed: false -lowestofttowncouncil.gov.uk: - owner: Lowestoft Town Council - crown: false - agreement_signed: false -ludlow.gov.uk: - owner: Ludlow Town Council - crown: false - agreement_signed: false -luton.gov.uk: - owner: Luton Borough Council - crown: false - agreement_signed: true -lymeregistowncouncil.gov.uk: - owner: Lyme Regis Town Council - crown: false - agreement_signed: false -lymingtonandpennington-tc.gov.uk: - owner: Lymington and Pennington Town Council - crown: false - agreement_signed: false -lytchettminsterandupton-tc.gov.uk: - owner: Lytchett Minster & Upton Town Council - crown: false - agreement_signed: false -macclesfield-tc.gov.uk: - owner: Macclesfield Town Council - crown: false - agreement_signed: false -maethugogleddcymru.gov.uk: flintshire.gov.uk -maghull-tc.gov.uk: - owner: Maghull Town Council - crown: false - agreement_signed: false -maiden.gov.uk: gloucestershire.gov.uk -maidstone.gov.uk: - owner: Maidstone Borough Council - crown: false - agreement_signed: true -maldon.gov.uk: - owner: Maldon District Council - crown: false - agreement_signed: false -maldontowncouncil.gov.uk: - owner: Maldon Town Council - crown: false - agreement_signed: false -malmesbury.gov.uk: - owner: Malmesbury Town Council - crown: false - agreement_signed: false -malton-tc.gov.uk: - owner: Malton Town Council - crown: false - agreement_signed: false -malvernhills.gov.uk: - owner: Malvern Hills District Council - crown: false - agreement_signed: false -manchester.gov.uk: - owner: Manchester City Council - crown: false - agreement_signed: true -mansfield-dc.gov.uk: mansfield.gov.uk -mansfield.gov.uk: - owner: Mansfield District Council - crown: false - agreement_signed: true -marchtowncouncil.gov.uk: - owner: March Town Council - crown: false - agreement_signed: false -marketdrayton.gov.uk: - owner: Market Drayton Town Council - crown: false - agreement_signed: false -marketweightontowncouncil.gov.uk: eastriding.gov.uk -marlborough-tc.gov.uk: marlboroughtowncouncil.gov.uk -marlboroughtowncouncil.gov.uk: - owner: Marlborough Town Council - crown: false - agreement_signed: false -marlow-tc.gov.uk: - owner: Marlow Town Council - crown: false - agreement_signed: false -marstonsicca-pc.gov.uk: stratford.gov.uk -maryporttowncouncil.gov.uk: - owner: Maryport Town Council - crown: false - agreement_signed: false -matlock.gov.uk: - owner: Matlock Town Council - crown: false - agreement_signed: false -medway.gov.uk: - owner: Medway Council - crown: false - agreement_signed: true -melthamtowncouncil.gov.uk: - owner: Meltham Town Council - crown: false - agreement_signed: false -melton.gov.uk: - owner: Melton Borough Council - crown: false - agreement_signed: false -mendip.gov.uk: - owner: Mendip District Council - crown: false - agreement_signed: false -merthyr.gov.uk: - owner: Merthyr Tydfil County Borough Council - crown: false - agreement_signed: true -met.pnn.police.uk: - owner: Metropolitan Police Service - crown: false - agreement_signed: true -midandeastantrim.gov.uk: - owner: Mid and East Antrim Borough Council - crown: false - agreement_signed: false -middevon.gov.uk: - owner: Mid Devon District Council - crown: false - agreement_signed: false -middlesbrough.gov.uk: - owner: Middlesbrough Council - crown: false - agreement_signed: true -midhurst-tc.gov.uk: - owner: Midhurst Town Council - crown: false - agreement_signed: false -midkent.gov.uk: swale.gov.uk -midlothian.gov.uk: - owner: Midlothian Council - crown: false - agreement_signed: true -midsuffolk.gov.uk: - owner: Mid Suffolk District Council - crown: false - agreement_signed: false -midsussex.gov.uk: - owner: Mid Sussex District Council - crown: false - agreement_signed: true -milland-wsx-pc.gov.uk: chichester.gov.uk -milton-keynes.gov.uk: miltonkeynes.gov.uk -miltonkeynes.gov.uk: - owner: Milton Keynes Council - crown: false - agreement_signed: true -minstead.gov.uk: newforest.gov.uk -mirfieldtowncouncil.gov.uk: - owner: Mirfield Town Council - crown: false - agreement_signed: false -mole-valley.gov.uk: molevalley.gov.uk -molevalley.gov.uk: - owner: Mole Valley District Council - crown: false - agreement_signed: false -monmouth.gov.uk: - owner: Monmouth Town Council - crown: false - agreement_signed: false -monmouthshire.gov.uk: - owner: Monmouthshire County Council - crown: false - agreement_signed: false -moray-edunet.gov.uk: moray.gov.uk -moray.gov.uk: - owner: Moray Council - crown: false - agreement_signed: false -morecambe.gov.uk: - owner: Morecambe Town Council - crown: false - agreement_signed: false -morley.gov.uk: - owner: Morley Town Council - crown: false - agreement_signed: false -morpeth-tc.gov.uk: - owner: Morpeth Town Council - crown: false - agreement_signed: false -mostyncommunitycouncil.gov.uk: - owner: Mostyn Community Council - crown: false - agreement_signed: false -muchwenlock-tc.gov.uk: - owner: Much Wenlock Town Council - crown: false - agreement_signed: false -mumbles.gov.uk: - owner: Mumbles Community Council - crown: false - agreement_signed: false -mynottingham.gov.uk: nottingham.gov.uk -myoxfordshire.gov.uk: oxfordshire.gov.uk -nafn.gov.uk: tameside.gov.uk -nailseatowncouncil.gov.uk: - owner: Nailsea Town Council - crown: false - agreement_signed: false -nailsworthtowncouncil.gov.uk: - owner: Nailsworth Town Council - crown: false - agreement_signed: false -nalc.gov.uk: - owner: National Association of Local Councils - crown: false - agreement_signed: false -nantwichtowncouncil.gov.uk: - owner: Nantwich Town Council - crown: false - agreement_signed: false -neath-porttalbot.gov.uk: npt.gov.uk -neathtowncouncil.gov.uk: - owner: Neath Town Council - crown: false - agreement_signed: false -ne-derbyshire.gov.uk: - owner: North East Derbyshire District Council - crown: false - agreement_signed: false -ne-ifca.gov.uk: eastriding.gov.uk -nelincs.gov.uk: - owner: North East Lincolnshire Council - crown: false - agreement_signed: true -nelson-mid-glam.gov.uk: - owner: Nelson Community Council - crown: false - agreement_signed: false -nelsontowncouncil.gov.uk: - owner: Nelson Town Council - crown: false - agreement_signed: false -netleymarsh-pc.gov.uk: newforest.gov.uk -newark.gov.uk: - owner: Newark Town Council - crown: false - agreement_signed: false -newark-sherwooddc.gov.uk: - owner: Newark & Sherwood District Council - crown: false - agreement_signed: false -newbiggintowncouncil.gov.uk: - owner: Newbiggin Town Council - crown: false - agreement_signed: false -newbury.gov.uk: - owner: Newbury Town Council - crown: false - agreement_signed: false -newcastleemlyntowncouncil.gov.uk: - owner: Newcastle Emlyn Town Council - crown: false - agreement_signed: false -newcastle.gov.uk: - owner: Newcastle upon Tyne City Council - crown: false - agreement_signed: true -newcastle-staffs.gov.uk: - owner: Newcastle under Lyme Borough Council - crown: false - agreement_signed: true -newenttowncouncil.gov.uk: - owner: Newent Town Council - crown: false - agreement_signed: false -newforestcouncil.gov.uk: newforest.gov.uk -newforestdc.gov.uk: newforest.gov.uk -newforestdistrict.gov.uk: newforest.gov.uk -newforest.gov.uk: - owner: New Forest District Council - crown: false - agreement_signed: false -newham.gov.uk: - owner: Newham London Borough Council - crown: false - agreement_signed: true -newhaventowncouncil.gov.uk: - owner: Newhaven Town Council - crown: false - agreement_signed: false -newmarket.gov.uk: - owner: Newmarket Town Council - crown: false - agreement_signed: false -newmillstowncouncil.gov.uk: - owner: New Mills Town Council - crown: false - agreement_signed: false -newmiltontowncouncil.gov.uk: newforest.gov.uk -newport.gov.uk: - owner: Newport City Council - crown: false - agreement_signed: false -newportshropshire-tc.gov.uk: - owner: Newport Town Council - crown: false - agreement_signed: false -newtonabbot-tc.gov.uk: - owner: Newton Abbot Town Council - crown: false - agreement_signed: false -newtownabbey.gov.uk: - owner: Newtownabbey Borough Council - crown: false - agreement_signed: false -nfdc.gov.uk: newforest.gov.uk -n-kesteven.gov.uk: - owner: North Kesteven District Council - crown: false - agreement_signed: false -norfolkalc.gov.uk: - owner: Norfolk Association of Local Councils - crown: false - agreement_signed: false -norfolkedunet.gov.uk: norfolk.gov.uk -norfolkfireservice.gov.uk: norfolk.gov.uk -norfolk.gov.uk: - owner: Norfolk County Council - crown: false - agreement_signed: false -norfolkparishes.gov.uk: norfolk.gov.uk -norfolkprepared.gov.uk: norfolk.gov.uk -northallertontowncouncil.gov.uk: - owner: Northallerton Town Council - crown: false - agreement_signed: false -northampton.gov.uk: - owner: Northampton Borough Council - crown: false - agreement_signed: false -northamptonshire.gov.uk: - owner: Northamptonshire County Council - crown: false - agreement_signed: false -northamtowncouncil.gov.uk: - owner: Northam Town Council - crown: false - agreement_signed: false -northants-ecl.gov.uk: northamptonshire.gov.uk -northantslicensing.gov.uk: east-northamptonshire.gov.uk -north-ayrshire.gov.uk: - owner: North Ayrshire Council - crown: false - agreement_signed: false -northdevon.gov.uk: - owner: North Devon Council - crown: false - agreement_signed: false -north-dorset.gov.uk: - owner: North Dorset District Council - crown: false - agreement_signed: false -northeastca.gov.uk: newcastle.gov.uk -northeastcouncils.gov.uk: - owner: Association of North East Councils Limited - crown: false - agreement_signed: false -northeast-tadu.gov.uk: gateshead.gov.uk -north-herts.gov.uk: - owner: North Hertfordshire District Council - crown: false - agreement_signed: false -northhykehamtowncouncil.gov.uk: - owner: North Hykeham Town Council - crown: false - agreement_signed: false -northlanarkshire.gov.uk: - owner: North Lanarkshire Council - crown: false - agreement_signed: false -northlan.gov.uk: northlanarkshire.gov.uk -northleach.gov.uk: - owner: Northleach with Eastington Town Council - crown: false - agreement_signed: false -northlincs.gov.uk: - owner: North Lincolnshire Council - crown: false - agreement_signed: true -north-norfolk.gov.uk: - owner: North Norfolk District Council - crown: false - agreement_signed: false -northsomerset.gov.uk: - owner: North Somerset Council - crown: false - agreement_signed: true -northtyneside.gov.uk: - owner: North Tyneside Metropolitan Borough Council - crown: false - agreement_signed: true -northumberland.gov.uk: - owner: Northumberland County Council - crown: false - agreement_signed: true -northwalesadoption.gov.uk: wrexham.gov.uk -northwalesfostering.gov.uk: flintshire.gov.uk -northwarks.gov.uk: - owner: North Warwickshire Borough Council - crown: false - agreement_signed: false -northwestvotes.gov.uk: manchester.gov.uk -northwichtowncouncil.gov.uk: - owner: Northwich Town Council - crown: false - agreement_signed: false -northyorks.gov.uk: - owner: North Yorkshire County Council - crown: false - agreement_signed: true -norwich.gov.uk: - owner: Norwich City Council - crown: false - agreement_signed: false -nottinghamcity.gov.uk: nottingham.gov.uk -nottingham.gov.uk: - owner: Nottingham City Council - crown: false - agreement_signed: true -nottinghamshire.gov.uk: - owner: Nottinghamshire County Council - crown: false - agreement_signed: false -nottscc.gov.uk: nottinghamshire.gov.uk -npt.gov.uk: - owner: Neath Port Talbot County Borough Council - crown: false - agreement_signed: true -n-somerset.gov.uk: northsomerset.gov.uk -nuneatonandbedworth.gov.uk: - owner: Nuneaton and Bedworth Borough Council - crown: false - agreement_signed: false -nwleicestershire.gov.uk: - owner: North West Leicestershire District Council - crown: false - agreement_signed: false -nwleicsdc.gov.uk: nwleicestershire.gov.uk -nwleics.gov.uk: nwleicestershire.gov.uk -oadby-wigston.gov.uk: - owner: Oadby and Wigston Borough Council - crown: false - agreement_signed: false -oakengates-tc.gov.uk: telford.gov.uk -oakhamtowncouncil.gov.uk: - owner: Oakham Town Council - crown: false - agreement_signed: false -offacommunitycouncil.gov.uk: - owner: Offa Community Council - crown: false - agreement_signed: false -okehampton.gov.uk: - owner: Okehampton Town Council - crown: false - agreement_signed: false -oldbolsovertowncouncil.gov.uk: - owner: Old Bolsover Town Council - crown: false - agreement_signed: false -oldham.gov.uk: - owner: Oldham Metropolitan Borough Council - crown: false - agreement_signed: false -ollerton-tc.gov.uk: - owner: Ollerton & Boughton Town Council - crown: false - agreement_signed: false -olneytowncouncil.gov.uk: - owner: Olney Town Council - crown: false - agreement_signed: false -omagh.gov.uk: - owner: Omagh District Council - crown: false - agreement_signed: false -ongartowncouncil.gov.uk: - owner: Ongar Town Council - crown: false - agreement_signed: false -orchardpark.gov.uk: - owner: Orchard Park Community Council - crown: false - agreement_signed: false -orkney.gov.uk: - owner: Orkney Islands Council - crown: false - agreement_signed: true -oswestry-tc.gov.uk: - owner: Oswestry Town Council - crown: false - agreement_signed: false -otleytowncouncil.gov.uk: - owner: Otley Town Council - crown: false - agreement_signed: false -otterystmary-tc.gov.uk: - owner: Ottery St. Mary Town Council - crown: false - agreement_signed: false -oundle.gov.uk: - owner: Oundle Town Council - crown: false - agreement_signed: false -oxford.gov.uk: - owner: Oxford City Council - crown: false - agreement_signed: true -oxfordshire.gov.uk: - owner: Oxfordshire County Council - crown: false - agreement_signed: false -oxfordshire-online.gov.uk: oxfordshire.gov.uk -oxfordshireonline.gov.uk: oxfordshire.gov.uk -oxon.gov.uk: oxfordshire.gov.uk -padihamtowncouncil.gov.uk: - owner: Padiham Town Council - crown: false - agreement_signed: false -padstow-tc.gov.uk: - owner: Padstow Town Council - crown: false - agreement_signed: false -paessex.gov.uk: essex.gov.uk -patchwaytowncouncil.gov.uk: - owner: Patchway Town Council - crown: false - agreement_signed: false -peacehaventowncouncil.gov.uk: - owner: Peacehaven Town Council - crown: false - agreement_signed: false -pembreyburryport-tc.gov.uk: - owner: Pembrey & Burry Port Town Council - crown: false - agreement_signed: false -pembrokeshire.gov.uk: - owner: Pembrokeshire County Council - crown: false - agreement_signed: true -penarthtowncouncil.gov.uk: - owner: Penarth Town Council - crown: false - agreement_signed: false -pencoedtowncouncil.gov.uk: - owner: Pencoed Town Council - crown: false - agreement_signed: false -pendle.gov.uk: - owner: Pendle Borough Council - crown: false - agreement_signed: false -penistonetowncouncil.gov.uk: - owner: Penistone Town Council - crown: false - agreement_signed: false -penworthamtowncouncil.gov.uk: - owner: Penwortham Town Council - crown: false - agreement_signed: false -peoplesnetwork.gov.uk: artscouncil.org.uk -artscouncil.org.uk: - owner: Arts Council England - crown: false - agreement_signed: false -pershore-tc.gov.uk: pershoretowncouncil.gov.uk -pershoretowncouncil.gov.uk: - owner: Pershore Town Council - crown: false - agreement_signed: false -peterborough.gov.uk: - owner: Peterborough City Council - crown: false - agreement_signed: false -peterlee.gov.uk: - owner: Peterlee Town Council - crown: false - agreement_signed: false -petersfield-tc.gov.uk: - owner: Petersfield Town Council - crown: false - agreement_signed: false -petworth-tc.gov.uk: - owner: Petworth Town Council - crown: false - agreement_signed: false -pickering.gov.uk: - owner: Pickering Town Council - crown: false - agreement_signed: false -pitchcombe-pc.gov.uk: stroud.gov.uk -pkc.gov.uk: - owner: Perth & Kinross Council - crown: false - agreement_signed: true -plymouth.gov.uk: - owner: Plymouth City Council - crown: false - agreement_signed: true -plymouthmuseum.gov.uk: plymouth.gov.uk -pocklington.gov.uk: - owner: Pocklington Town Council - crown: false - agreement_signed: false -polegatetowncouncil.gov.uk: - owner: Polegate Town Council - crown: false - agreement_signed: false -polperrocommunitycouncil.gov.uk: - owner: Polperro Community Council - crown: false - agreement_signed: false -pontarddulaistowncouncil.gov.uk: - owner: Pontarddulais Town Council - crown: false - agreement_signed: false -ponteland-tc.gov.uk: - owner: Ponteland Town Council - crown: false - agreement_signed: false -ponthircommunitycouncil.gov.uk: - owner: Ponthir Community Council - crown: false - agreement_signed: false -pontypoolcc.gov.uk: torfaen.gov.uk -pontypriddtowncouncil.gov.uk: - owner: Pontypridd Town Council - crown: false - agreement_signed: false -portishead.gov.uk: - owner: Portishead & North Weston Town Council - crown: false - agreement_signed: false -portlandtowncouncil.gov.uk: - owner: Portland Town Council - crown: false - agreement_signed: false -portsmouthcc.gov.uk: portsmouth.gov.uk -portsmouth.gov.uk: - owner: Portsmouth City Council - crown: false - agreement_signed: true -powys.gov.uk: - owner: Powys County Council - crown: false - agreement_signed: false -poyntontowncouncil.gov.uk: - owner: Poynton Town Council - crown: false - agreement_signed: false -prescot-tc.gov.uk: - owner: Prescot Town Council - crown: false - agreement_signed: false -prestatyntowncouncil.gov.uk: - owner: Prestatyn Town Council - crown: false - agreement_signed: false -preston.gov.uk: - owner: Preston City Council - crown: false - agreement_signed: false -purbeck-dc.gov.uk: purbeck.gov.uk -purbeck.gov.uk: - owner: Purbeck District Council - crown: false - agreement_signed: false -push.gov.uk: hants.gov.uk -queensferrycommunitycouncil.gov.uk: - owner: Queensferry and District Community Council - crown: false - agreement_signed: false -queensparkcommunitycouncil.gov.uk: - owner: Queens Park Community Council - crown: false - agreement_signed: false -radstock-tc.gov.uk: - owner: Radstock Town Council - crown: false - agreement_signed: false -ramseytowncouncil.gov.uk: - owner: Ramsey Town Council - crown: false - agreement_signed: false -ratleyandupton-pc.gov.uk: stratford.gov.uk -raunds-tc.gov.uk: - owner: Raunds Town Council - crown: false - agreement_signed: false -rayleightowncouncil.gov.uk: - owner: Rayleigh Town Council - crown: false - agreement_signed: false -rbkc.gov.uk: - owner: Royal Borough of Kensington and Chelsea - crown: false - agreement_signed: true -rctcbc.gov.uk: rhondda-cynon-taf.gov.uk -reading.gov.uk: - owner: Reading Borough Council - crown: false - agreement_signed: true -readydumgal.gov.uk: dumgal.gov.uk -redbridge.gov.uk: - owner: Redbridge London Borough Council - crown: false - agreement_signed: true -redcar-cleveland.gov.uk: - owner: Redcar and Cleveland Borough Council - crown: false - agreement_signed: true -redditchbc.gov.uk: - owner: Redditch Borough Council - crown: false - agreement_signed: false -redruth-tc.gov.uk: - owner: Redruth Town Council - crown: false - agreement_signed: false -reephamnorfolk-tc.gov.uk: - owner: Reepham Town Council - crown: false - agreement_signed: false -reigate-banstead.gov.uk: - owner: Reigate and Banstead Borough Council - crown: false - agreement_signed: false -renfrewshire.gov.uk: - owner: Renfrewshire Council - crown: false - agreement_signed: false -rhondda-cynon-taff.gov.uk: rhondda-cynon-taf.gov.uk -rhondda-cynon-taf.gov.uk: - owner: Rhondda Cynon Taf County Borough Council - crown: false - agreement_signed: false -rhosdducommunitycouncil.gov.uk: - owner: Rhosddu Community Council - crown: false - agreement_signed: false -rhuddlantowncouncil.gov.uk: denbighshire.gov.uk -ribblevalley.gov.uk: - owner: Ribblevalley Borough Council - crown: false - agreement_signed: false -richmond.gov.uk: richmondandwandsworth.gov.uk -wandsworth.gov.uk: richmondandwandsworth.gov.uk -richmondandwandsworth.gov.uk: - owner: London Boroughs of Richmond and Wandsworth - crown: false - agreement_signed: true -richmondshire.gov.uk: - owner: Richmondshire District Council - crown: false - agreement_signed: false -ringwood.gov.uk: - owner: Ringwood Town Council - crown: false - agreement_signed: false -ripleytowncouncil.gov.uk: - owner: Ripley Town Council - crown: false - agreement_signed: false -riponcity.gov.uk: - owner: Ripon City Council - crown: false - agreement_signed: false -rochdale.gov.uk: - owner: Rochdale Metropolitan Borough Council - crown: false - agreement_signed: false -rochford.gov.uk: - owner: Rochford District Council - crown: false - agreement_signed: false -rossendalebc.gov.uk: rossendale.gov.uk -rossendale.gov.uk: - owner: Rossendale Borough Council - crown: false - agreement_signed: false -rosstc-herefordshire.gov.uk: - owner: Ross-on-Wye Town Council - crown: false - agreement_signed: false -rother.gov.uk: - owner: Rother District Council - crown: false - agreement_signed: true -rotherham.gov.uk: - owner: Rotherham Borough Council - crown: false - agreement_signed: false -royalgreenwich.gov.uk: - owner: Royal Borough of Greenwich - crown: false - agreement_signed: false -royalwoottonbassett.gov.uk: woottonbassett.gov.uk -roystontowncouncil.gov.uk: - owner: Royston Town Council - crown: false - agreement_signed: false -rugby.gov.uk: - owner: Rugby Borough Council - crown: false - agreement_signed: true -rugeleytowncouncil.gov.uk: - owner: Rugeley Town Council - crown: false - agreement_signed: false -runnymede.gov.uk: - owner: Runnymede Borough Council - crown: false - agreement_signed: true -rushcliffe.gov.uk: - owner: Rushcliffe Borough Council - crown: false - agreement_signed: false -rushdentowncouncil.gov.uk: - owner: Rushden Town Council - crown: false - agreement_signed: false -rushmoor.gov.uk: - owner: Rushmoor Borough Council - crown: false - agreement_signed: true -ruthintowncouncil.gov.uk: - owner: Ruthin Town Council - crown: false - agreement_signed: false -rutland.gov.uk: - owner: Rutland County Council - crown: false - agreement_signed: false -rydetowncouncil.gov.uk: - owner: Ryde Town Council - crown: false - agreement_signed: false -ryedale.gov.uk: - owner: Ryedale District Council - crown: false - agreement_signed: true -ryetowncouncil.gov.uk: - owner: Rye Town Council - crown: false - agreement_signed: false -saferderbyshire.gov.uk: derbyshire.gov.uk -saffronwalden.gov.uk: - owner: Saffron Walden Town Council - crown: false - agreement_signed: false -salcombetowncouncil.gov.uk: - owner: Salcombe Town Council - crown: false - agreement_signed: false -salford.gov.uk: - owner: Salford City Council - crown: false - agreement_signed: false -salisburycitycouncil.gov.uk: - owner: Salisbury City Council - crown: false - agreement_signed: false -saltash.gov.uk: - owner: Saltash Town Council - crown: false - agreement_signed: false -saltneytowncouncil.gov.uk: - owner: Saltney Town Council - crown: false - agreement_signed: false -sandbach.gov.uk: - owner: Sandbach Town Council - crown: false - agreement_signed: false -sandhurst.gov.uk: - owner: Sandhurst Town Council - crown: false - agreement_signed: false -sandleheath.gov.uk: newforest.gov.uk -sandowntowncouncil.gov.uk: - owner: Sandown Town Council - crown: false - agreement_signed: false -sandwell.gov.uk: - owner: Sandwell Metropolitan Borough Council - crown: false - agreement_signed: false -sandwichtowncouncil.gov.uk: - owner: Sandwich Town Council - crown: false - agreement_signed: false -sandytowncouncil.gov.uk: - owner: Sandy Town Council - crown: false - agreement_signed: false -sawbridgeworth-tc.gov.uk: - owner: Sawbridgeworth Town Council - crown: false - agreement_signed: false -scambs.gov.uk: - owner: South Cambridgeshire District Council - crown: false - agreement_signed: false -scarborough.gov.uk: - owner: Scarborough Borough Council - crown: false - agreement_signed: false -scilly.gov.uk: - owner: Council of the Isles of Scilly - crown: false - agreement_signed: false -scotborders.gov.uk: - owner: Scottish Borders Council - crown: false - agreement_signed: true -seafordtowncouncil.gov.uk: - owner: Seaford Town Council - crown: false - agreement_signed: false -seaham.gov.uk: - owner: Seaham Town Council - crown: false - agreement_signed: false -sealandcommunitycouncil.gov.uk: - owner: Sealand Community Council - crown: false - agreement_signed: false -seaton.gov.uk: - owner: Seaton Town Council - crown: false - agreement_signed: false -seatonvalleycommunitycouncil.gov.uk: - owner: Seaton Valley Council - crown: false - agreement_signed: false -secouncils.gov.uk: surreycc.gov.uk -sedgefieldtowncouncil.gov.uk: - owner: Sedgefield Town Council - crown: false - agreement_signed: false -sedgemoor.gov.uk: - owner: Sedgemoor District Council - crown: false - agreement_signed: false -sefton.gov.uk: - owner: Sefton Metropolitan Borough Council - crown: false - agreement_signed: true -selby.gov.uk: - owner: Selby District Council - crown: false - agreement_signed: false -selbytowncouncil.gov.uk: selby.gov.uk -selseytowncouncil.gov.uk: - owner: Selsey Town Council - crown: false - agreement_signed: false -sesplan.gov.uk: edinburgh.gov.uk -sevenoaks.gov.uk: - owner: Sevenoaks District Council - crown: false - agreement_signed: true -sevenoakstown.gov.uk: - owner: Sevenoaks Town Council - crown: false - agreement_signed: false -sewta.gov.uk: valeofglamorgan.gov.uk -shaftesbury-tc.gov.uk: - owner: Shaftesbury Town Council - crown: false - agreement_signed: false -sharedresourceservicewales.gov.uk: torfaen.gov.uk -sheffield.gov.uk: - owner: Sheffield City Council - crown: false - agreement_signed: false -sheptonmallettowncouncil.gov.uk: - owner: Shepton Mallet Town Council - crown: false - agreement_signed: false -shepway.gov.uk: - owner: Shepway District Council - crown: false - agreement_signed: false -sherborne-tc.gov.uk: - owner: Sherborne Town Council - crown: false - agreement_signed: false -shetland.gov.uk: - owner: Shetland Islands Council - crown: false - agreement_signed: false -shetland-library.gov.uk: shetland.gov.uk -shifnaltowncouncil.gov.uk: - owner: Shifnal Town Council - crown: false - agreement_signed: false -shildon.gov.uk: - owner: Shildon Town Council - crown: false - agreement_signed: false -shirebrooktowncouncil.gov.uk: - owner: Shirebrook Town Council - crown: false - agreement_signed: false -sholland.gov.uk: - owner: South Holland District Council - crown: false - agreement_signed: false -shrewsburytowncouncil.gov.uk: - owner: Shrewsbury Town Council - crown: false - agreement_signed: false -shropshire.gov.uk: - owner: Shropshire Council - crown: false - agreement_signed: false -sidmouth.gov.uk: - owner: Sidmouth Town Council - crown: false - agreement_signed: false -sigoma.gov.uk: barnsley.gov.uk -silloth-on-solway-tc.gov.uk: - owner: Silloth-on-Solway Town Council - crown: false - agreement_signed: false -sir-benfro.gov.uk: pembrokeshire.gov.uk -sirddinbych.gov.uk: denbighshire.gov.uk -sirfynwy.gov.uk: monmouthshire.gov.uk -sirgaerfyrddin.gov.uk: carmarthenshire.gov.wales -sirgar.gov.uk: carmarthenshire.gov.wales -siryfflint.gov.uk: flintshire.gov.uk -skegness.gov.uk: - owner: Skegness Town Council - crown: false - agreement_signed: false -skiptontowncouncil.gov.uk: - owner: Skipton Town Council - crown: false - agreement_signed: false -sleaford.gov.uk: - owner: Sleaford Town Council - crown: false - agreement_signed: false -slough.gov.uk: - owner: Slough Borough Council - crown: false - agreement_signed: false -s-norfolk.gov.uk: south-norfolk.gov.uk -sodburytowncouncil.gov.uk: - owner: Sodbury Town Council - crown: false - agreement_signed: false -soham-tc.gov.uk: - owner: Soham Town Council - crown: false - agreement_signed: false -solihull.gov.uk: - owner: Solihull Metropolitan Borough Council - crown: false - agreement_signed: true -somerset.gov.uk: - owner: Somerset County Council - crown: false - agreement_signed: true -somertontowncouncil.gov.uk: - owner: Somerton Town Council - crown: false - agreement_signed: false -sopley.gov.uk: newforest.gov.uk -southamcouncil-warks.gov.uk: - owner: Southam Town Council - crown: false - agreement_signed: false -southampton.gov.uk: - owner: Southampton City Council - crown: false - agreement_signed: false -southandvale.gov.uk: whitehorsedc.gov.uk -south-ayrshire.gov.uk: - owner: South Ayrshire Council - crown: false - agreement_signed: true -southbucks.gov.uk: - owner: South Buckinghamshire District Council - crown: false - agreement_signed: true -south-derbys.gov.uk: - owner: South Derbyshire District Council - crown: false - agreement_signed: false -southend.gov.uk: - owner: Southend-on-Sea Borough Council - crown: false - agreement_signed: true -southernuplandway.gov.uk: dumgal.gov.uk -southglos.gov.uk: - owner: South Gloucestershire Council - crown: false - agreement_signed: true -south-hams-dc.gov.uk: southhams.gov.uk -southhams.gov.uk: - owner: South Hams District Council - crown: false - agreement_signed: false -southkesteven.gov.uk: - owner: South Kesteven District Council - crown: false - agreement_signed: false -southkirkbyandmoorthorpetowncouncil.gov.uk: - owner: South Kirkby and Moorthorpe Town Council - crown: false - agreement_signed: false -southlakeland.gov.uk: - owner: South Lakeland District Council - crown: false - agreement_signed: false -southlanarkshire.gov.uk: - owner: South Lanarkshire Council - crown: false - agreement_signed: true -south-norfolk.gov.uk: - owner: South Norfolk Council - crown: false - agreement_signed: false -southnorthants.gov.uk: - owner: South Northamptonshire Council - crown: false - agreement_signed: false -southoxon.gov.uk: - owner: South Oxfordshire District Council - crown: false - agreement_signed: false -southribble.gov.uk: - owner: South Ribble Borough Council - crown: false - agreement_signed: false -southribbletourism.gov.uk: southribble.gov.uk -southsomerset.gov.uk: - owner: South Somerset District Council - crown: false - agreement_signed: false -southtyneside.gov.uk: - owner: South Tyneside Council - crown: false - agreement_signed: true -southwark.gov.uk: - owner: Southwark London Borough Council - crown: false - agreement_signed: true -southwell-tc.gov.uk: - owner: Southwell Town Council - crown: false - agreement_signed: false -southwest-ra.gov.uk: somerset.gov.uk -southwoodhamferrerstc.gov.uk: - owner: South Woodham Ferrers Town Council - crown: false - agreement_signed: false -southworcestershirebuildingcontrol.gov.uk: malvernhills.gov.uk -southworcestershirerevenues.gov.uk: wychavon.gov.uk -spelthorne.gov.uk: - owner: Spelthorne Borough Council - crown: false - agreement_signed: false -spennymoor-tc.gov.uk: - owner: Spennymoor Town Council - crown: false - agreement_signed: false -sprowston-tc.gov.uk: - owner: Sprowston Town Council - crown: false - agreement_signed: false -sstaffs.gov.uk: - owner: South Staffordshire District Council - crown: false - agreement_signed: false -staffordbc.gov.uk: - owner: Stafford Borough Council - crown: false - agreement_signed: false -staffordshire.gov.uk: - owner: Staffordshire County Council - crown: false - agreement_signed: true -staffordshireprepared.gov.uk: staffordshire.gov.uk -staffs.gov.uk: staffordshire.gov.uk -staffslc.gov.uk: staffordshire.gov.uk -staffsmoorlands.gov.uk: - owner: Staffordshire Moorlands District Council - crown: false - agreement_signed: false -stainforthtowncouncil.gov.uk: - owner: Stainforth Town Council - crown: false - agreement_signed: false -stalbans.gov.uk: - owner: St Albans District Council - crown: false - agreement_signed: false -stalbridgetowncouncil.gov.uk: - owner: Stalbridge Town Council - crown: false - agreement_signed: false -stamfordtowncouncil.gov.uk: - owner: Stamford Town Council - crown: false - agreement_signed: false -stanley-tc.gov.uk: - owner: Stanley Town Council - crown: false - agreement_signed: false -stannesonthesea-tc.gov.uk: - owner: St. Annes on the Sea Town Council - crown: false - agreement_signed: false -star-procurement.gov.uk: trafford.gov.uk -stasaphcitycouncil.gov.uk: - owner: St Asaph City Council - crown: false - agreement_signed: false -staustell-tc.gov.uk: - owner: St Austell Town Council - crown: false - agreement_signed: false -staveleytowncouncil.gov.uk: - owner: Staveley Town Council - crown: false - agreement_signed: false -stcolumbmajor-tc.gov.uk: - owner: St Columb Major Town Council - crown: false - agreement_signed: false -stdavids.gov.uk: - owner: St. David's City Council - crown: false - agreement_signed: false -stdonats-cc.gov.uk: - owner: St Donats communitiy Council - crown: false - agreement_signed: false -stedmundsbury.gov.uk: westsuffolk.gov.uk -stedsbc.gov.uk: westsuffolk.gov.uk -stevenage.gov.uk: - owner: Stevenage Borough Council - crown: false - agreement_signed: false -stharmoncommunitycouncil.gov.uk: - owner: St Harmon Community Council - crown: false - agreement_signed: false -sthelens.gov.uk: - owner: St Helens Metropolitan Borough Council - crown: false - agreement_signed: true -stirling.gov.uk: - owner: Stirling Council - crown: false - agreement_signed: true -stives-tc.gov.uk: stivestowncouncil.gov.uk -stivestowncouncil-cornwall.gov.uk: stivestowncouncil.gov.uk -stivestowncouncil.gov.uk: - owner: St Ives Town Council - crown: false - agreement_signed: false -stmarybourne-pc.gov.uk: basingstoke.gov.uk -stneots-tc.gov.uk: - owner: St Neots Town Council - crown: false - agreement_signed: false -stock-pc.gov.uk: - owner: Stock Village Council - crown: false - agreement_signed: false -stockport.gov.uk: - owner: Stockport Metropolitan Borough Council - crown: false - agreement_signed: true -stockportmbc.gov.uk: stockport.gov.uk -stockton.gov.uk: - owner: Stockton-on-Tees Borough Council - crown: false - agreement_signed: false -stockton-warks-pc.gov.uk: stratford.gov.uk -stoke.gov.uk: - owner: Stoke-on-Trent City Council - crown: false - agreement_signed: true -stonetowncouncil.gov.uk: - owner: Stone Town Council - crown: false - agreement_signed: false -stonystratford.gov.uk: - owner: Stony Stratford Town Council - crown: false - agreement_signed: false -stoploansharks.gov.uk: birmingham.gov.uk -stotfoldtowncouncil.gov.uk: - owner: Stotfold Town Council - crown: false - agreement_signed: false -stourporttowncouncil.gov.uk: - owner: Stourport-on-Severn Town Council - crown: false - agreement_signed: false -stowonthewold-tc.gov.uk: - owner: Stow-on-the-Wold Town Council - crown: false - agreement_signed: false -stratford-dc.gov.uk: stratford.gov.uk -stratford.gov.uk: - owner: Stratford-on-Avon District Council - crown: false - agreement_signed: false -stratford-tc.gov.uk: - owner: Stratford-upon-Avon Town Council - crown: false - agreement_signed: false -stroud.gov.uk: - owner: Stroud District Council - crown: false - agreement_signed: false -stroudtown.gov.uk: - owner: Stroud Town Council - crown: false - agreement_signed: false -sturminsternewton-tc.gov.uk: - owner: Sturminster Newton Town Council - crown: false - agreement_signed: false -s-tyneside-mbc.gov.uk: southtyneside.gov.uk -suffolkcc.gov.uk: suffolk.gov.uk -suffolkcoastal.gov.uk: suffolk.gov.uk -suffolk.gov.uk: - owner: Suffolk County Council - crown: false - agreement_signed: false -sunderland.gov.uk: - owner: Sunderland City Metropolitan Borough Council - crown: false - agreement_signed: false -supportwithconfidence.gov.uk: surreycc.gov.uk -surreycc.gov.uk: - owner: Surrey County Council - crown: false - agreement_signed: false -surreycoroner.gov.uk: surreycc.gov.uk -surreycountycouncil.gov.uk: surreycc.gov.uk -surrey-fire.gov.uk: surreycc.gov.uk -surrey.gov.uk: surreycc.gov.uk -surreyheath.gov.uk: - owner: Surrey Heath Borough Council - crown: false - agreement_signed: false -surreyheath-online.gov.uk: surreyheath.gov.uk -surreyheathonline.gov.uk: surreyheath.gov.uk -surreyi.gov.uk: surreycc.gov.uk -surreylocalgovernment.gov.uk: woking.gov.uk -sussexsaferroads.gov.uk: westsussex.gov.uk -suttoncoldfieldtowncouncil.gov.uk: - owner: Sutton Coldfield Town Council - crown: false - agreement_signed: false -sutton.gov.uk: - owner: Sutton London Borough Council - crown: false - agreement_signed: false -swaffhamtowncouncil.gov.uk: - owner: Swaffham Town Council - crown: false - agreement_signed: false -swale.gov.uk: - owner: Swale Borough Council - crown: false - agreement_signed: false -swanage.gov.uk: - owner: Swanage Town Council - crown: false - agreement_signed: false -swanleytowncouncil.gov.uk: - owner: Swanley Town Council - crown: false - agreement_signed: false -swanscombeandgreenhithetowncouncil.gov.uk: - owner: Swanscombe & Greenhithe Town Council - crown: false - agreement_signed: false -sway-pc.gov.uk: newforest.gov.uk -swdevon.gov.uk: southhams.gov.uk -swindon.gov.uk: - owner: Swindon Borough Council - crown: false - agreement_signed: false -tadleytowncouncil.gov.uk: - owner: Tadley Town Council - crown: false - agreement_signed: false -tameside.gov.uk: - owner: Tameside Metropolitan Borough Council - crown: false - agreement_signed: true -tamworth.gov.uk: - owner: Tamworth Borough Council - crown: false - agreement_signed: false -tandridgedc.gov.uk: tandridge.gov.uk -tandridge.gov.uk: - owner: Tandridge District Council - crown: false - agreement_signed: false -tauntondeane.gov.uk: - owner: Taunton Deane Borough Council - crown: false - agreement_signed: false -tavistock.gov.uk: - owner: Tavistock Town Council - crown: false - agreement_signed: false -tayplan-sdpa.gov.uk: dundeecity.gov.uk -taysideprocurement.gov.uk: dundeecity.gov.uk -teesvalley-ca.gov.uk: stockton.gov.uk -teesvalleyunlimited.gov.uk: stockton.gov.uk -teignbridge.gov.uk: - owner: Teignbridge District Council - crown: false - agreement_signed: true -teignmouth-devon.gov.uk: - owner: Teignmouth Town Council - crown: false - agreement_signed: false -telford.gov.uk: - owner: Telford & Wrekin Council - crown: false - agreement_signed: false -telscombetowncouncil.gov.uk: - owner: Telscombe Town Council - crown: false - agreement_signed: false -tendringdc.gov.uk: - owner: Tendring District Council - crown: false - agreement_signed: false -tenterdentowncouncil.gov.uk: - owner: Tenterden Town Council - crown: false - agreement_signed: false -testvalley.gov.uk: - owner: Test Valley Borough Council - crown: false - agreement_signed: true -tetbury.gov.uk: - owner: Tetbury Town Council - crown: false - agreement_signed: false -tewkesburybc.gov.uk: tewkesbury.gov.uk -tewkesbury.gov.uk: - owner: Tewkesbury Borough Council - crown: false - agreement_signed: false -tewkesburytowncouncil.gov.uk: - owner: Tewkesbury Town Council - crown: false - agreement_signed: false -thametowncouncil.gov.uk: - owner: Thame Town Council - crown: false - agreement_signed: false -thanet.gov.uk: - owner: Thanet District Council - crown: false - agreement_signed: false -thatchamtowncouncil.gov.uk: - owner: Thatcham Town Council - crown: false - agreement_signed: false -themcmanus-dundee.gov.uk: dundeecity.gov.uk -thetfordtowncouncil.gov.uk: - owner: Thetford Town Council - crown: false - agreement_signed: false -thirsk-tc.gov.uk: - owner: Thirsk Town Council - crown: false - agreement_signed: false -thornabytowncouncil.gov.uk: - owner: Thornaby Town Council - crown: false - agreement_signed: false -thornburytowncouncil.gov.uk: - owner: Thornbury Town Council - crown: false - agreement_signed: false -thorne-moorends.gov.uk: - owner: Thorne Moorends Town Council - crown: false - agreement_signed: false -thorpestandrew-tc.gov.uk: - owner: Thorpe St Andrew Town Council - crown: false - agreement_signed: false -thrapstontowncouncil.gov.uk: - owner: Thrapston Town Council - crown: false - agreement_signed: false -threerivers.gov.uk: - owner: Three Rivers District Council - crown: false - agreement_signed: false -thurrockfostering.gov.uk: thurrock.gov.uk -thurrock.gov.uk: - owner: Thurrock Council - crown: false - agreement_signed: false -tidworthtowncouncil.gov.uk: - owner: Tidworth Town Council - crown: false - agreement_signed: false -tivertontowncouncil.gov.uk: - owner: Tiverton Town Council - crown: false - agreement_signed: false -tmbc.gov.uk: - owner: Tonbridge & Malling Borough Council - crown: false - agreement_signed: true -tobaccoregisterni.gov.uk: belfastcity.gov.uk -torbay.gov.uk: - owner: Torbay Council - crown: false - agreement_signed: false -torfaen.gov.uk: - owner: Torfaen County Borough Council - crown: false - agreement_signed: true -torpointtowncouncil.gov.uk: - owner: Torpoint Town Council - crown: false - agreement_signed: false -torridge.gov.uk: - owner: Torridge District Council - crown: false - agreement_signed: true -totnestowncouncil.gov.uk: - owner: Totnes Town Council - crown: false - agreement_signed: false -totton-and-eling-tc.gov.uk: - owner: Totton and Eling Town Council - crown: false - agreement_signed: false -towcester-tc.gov.uk: - owner: Towcester Town Council - crown: false - agreement_signed: false -towerhamlets.gov.uk: - owner: Tower Hamlets London Borough Council - crown: false - agreement_signed: true -towynkinmelbay-tc.gov.uk: - owner: Towyn and Kinmel Bay Town Council - crown: false - agreement_signed: false -tracc.gov.uk: powys.gov.uk -trafford.gov.uk: - owner: Trafford Metropolitan Borough Council - crown: false - agreement_signed: true -traffordparishes.gov.uk: trafford.gov.uk -tring.gov.uk: - owner: Tring Town Council - crown: false - agreement_signed: false -trowbridge.gov.uk: - owner: Trowbridge Town Council - crown: false - agreement_signed: false -truro-city.gov.uk: truro.gov.uk -truro.gov.uk: - owner: Truro City Council - crown: false - agreement_signed: false -tunbridgewells.gov.uk: - owner: Tunbridge Wells Borough Council - crown: false - agreement_signed: true -twfire.gov.uk: - owner: Tyne and Wear Fire and Rescue Authority - crown: false - agreement_signed: true -twict.gov.uk: sunderland.gov.uk -tyneandwearltp.gov.uk: newcastle.gov.uk -uckfieldtc.gov.uk: - owner: Uckfield Town Council - crown: false - agreement_signed: false -uppinghamtowncouncil.gov.uk: - owner: Uppingham Town Council - crown: false - agreement_signed: false -uptonstleonards-pc.gov.uk: stroud.gov.uk -uttlesford.gov.uk: - owner: Uttlesford District Council - crown: false - agreement_signed: false -valeofglamorgan.gov.uk: - owner: Vale of Glamorgan Council - crown: false - agreement_signed: true -verwood.gov.uk: - owner: Verwood Town Council - crown: false - agreement_signed: false -visitsaffronwalden.gov.uk: saffronwalden.gov.uk -visitsouthribble.gov.uk: southribble.gov.uk -wadebridge-tc.gov.uk: - owner: Wadebridge Town Council - crown: false - agreement_signed: false -wakefield.gov.uk: - owner: Wakefield City Council - crown: false - agreement_signed: false -wallingfordtowncouncil.gov.uk: - owner: Wallingford Town Council - crown: false - agreement_signed: false -walsall.gov.uk: - owner: Walsall Metropolitan Borough Council - crown: false - agreement_signed: true -walthamabbey-tc.gov.uk: - owner: Waltham Abbey Town Council - crown: false - agreement_signed: false -walthamforestclass.gov.uk: walthamforest.gov.uk -walthamforest.gov.uk: - owner: Waltham Forest London Borough Council - crown: false - agreement_signed: false -waltoncommunitycouncil.gov.uk: - owner: Walton Community Council - crown: false - agreement_signed: false -wantagetowncouncil.gov.uk: - owner: Wantage Town Council - crown: false - agreement_signed: false -wareham-tc.gov.uk: - owner: Wareham Town Council - crown: false - agreement_signed: false -waretowncouncil.gov.uk: - owner: Ware Town Council - crown: false - agreement_signed: false -warminster-tc.gov.uk: - owner: Warminster Town Council - crown: false - agreement_signed: false -warrington.gov.uk: - owner: Warrington Borough Council - crown: false - agreement_signed: true -warwick-dc.gov.uk: warwick.gov.uk -warwickdc.gov.uk: warwick.gov.uk -warwick.gov.uk: - owner: Warwick District Council - crown: false - agreement_signed: false -warwickshire.gov.uk: - owner: Warwickshire County Council - crown: false - agreement_signed: true -warwicktowncouncil.gov.uk: - owner: Warwick Town Council - crown: false - agreement_signed: false -watford-council.gov.uk: watford.gov.uk -watford.gov.uk: - owner: Watford Borough Council - crown: false - agreement_signed: false -wattontowncouncil.gov.uk: - owner: Watton Town Council - crown: false - agreement_signed: false -waveney.gov.uk: - owner: Waveney District Council - crown: false - agreement_signed: false -waverley.gov.uk: - owner: Waverley Borough Council - crown: false - agreement_signed: false -wealden.gov.uk: - owner: Wealden District Council - crown: false - agreement_signed: true -welfordonavon-pc.gov.uk: stratford.gov.uk -welhat.gov.uk: welwyn.gov.uk -wellingborough.gov.uk: - owner: Wellingborough Borough Council - crown: false - agreement_signed: false -wellington-shropshire.gov.uk: - owner: Wellington Town Council - crown: false - agreement_signed: false -wells.gov.uk: - owner: Wells City Council - crown: false - agreement_signed: false -welshpooltowncouncil.gov.uk: - owner: Welshpool Town Council - crown: false - agreement_signed: false -welwyn.gov.uk: - owner: Welwyn Hatfield Borough Council - crown: false - agreement_signed: false -wem.gov.uk: - owner: Wem Town Council - crown: false - agreement_signed: false -westberks.gov.uk: - owner: West Berkshire Council - crown: false - agreement_signed: false -westbletchleycouncil.gov.uk: - owner: West Bletchley Council - crown: false - agreement_signed: false -westburytowncouncil.gov.uk: - owner: Westbury Town Council - crown: false - agreement_signed: false -westcumbria.gov.uk: allerdale.gov.uk -westdevon.gov.uk: - owner: West Devon Borough Council - crown: false - agreement_signed: false -westdorset-dc.gov.uk: dorset.gov.uk -westdorset-weymouth.gov.uk: - owner: West Dorset District Council - crown: false - agreement_signed: false -west-dunbarton.gov.uk: - owner: West Dunbartonshire Council - crown: false - agreement_signed: true -westerhamtowncouncil.gov.uk: - owner: Westerham Town Council - crown: false - agreement_signed: false -westgateonsea.gov.uk: - owner: Westgate-On-Sea Town Council - crown: false - agreement_signed: false -westlancashire.gov.uk: - owner: West Lancashire District Council - crown: false - agreement_signed: false -westlancsdc.gov.uk: westlancashire.gov.uk -westlancs.gov.uk: westlancashire.gov.uk -west-lindsey.gov.uk: - owner: West Lindsey District Council - crown: false - agreement_signed: false -westlothian.gov.uk: - owner: West Lothian Council - crown: false - agreement_signed: true -westmidlandsca.gov.uk: wolverhampton.gov.uk -westminster.gov.uk: - owner: Westminster City Council - crown: false - agreement_signed: false -west-norfolk.gov.uk: - owner: Borough Council of King's Lynn & West Norfolk - crown: false - agreement_signed: false -westofengland-ca.gov.uk: bathnes.gov.uk -westoxon.gov.uk: - owner: West Oxfordshire District Council - crown: false - agreement_signed: false -westsomerset.gov.uk: - owner: West Somerset District Council - crown: false - agreement_signed: false -westsomersetonline.gov.uk: westsomerset.gov.uk -westsuffolk.gov.uk: - owner: St Edmundsbury Borough Council - crown: false - agreement_signed: true -westsussex.gov.uk: - owner: West Sussex County Council - crown: false - agreement_signed: true -westwey.gov.uk: weymouth.gov.uk -westyorkshire-pcp.gov.uk: wakefield.gov.uk -weymouth.gov.uk: - owner: Weymouth and Portland Borough Council - crown: false - agreement_signed: false -whiston-tc.gov.uk: - owner: Whiston Town Council - crown: false - agreement_signed: false -whitbytowncouncil.gov.uk: - owner: Whitby Town Council - crown: false - agreement_signed: false -whitchurch-hampshire-tc.gov.uk: - owner: Whitchurch Town Council - crown: false - agreement_signed: false -whitehilltowncouncil.gov.uk: - owner: Whitehill Town Council - crown: false - agreement_signed: false -whitehorsedc.gov.uk: - owner: Vale of White Horse District Council - crown: false - agreement_signed: false -whitnashtowncouncil.gov.uk: - owner: Whitnash Town Council - crown: false - agreement_signed: false -whitworth.gov.uk: - owner: Whitworth Town Council - crown: false - agreement_signed: false -wigan.gov.uk: - owner: Wigan Metropolitan Borough Council - crown: false - agreement_signed: true -wiganmbc.gov.uk: wigan.gov.uk -wiltontowncouncil.gov.uk: - owner: Wilton Town Council - crown: false - agreement_signed: false -wiltshire.gov.uk: - owner: The Wiltshire Council - crown: false - agreement_signed: true -wimborne.gov.uk: - owner: Wimborne Minster Town Council - crown: false - agreement_signed: false -winchester.gov.uk: - owner: Winchester City Council - crown: false - agreement_signed: false -windermere-tc.gov.uk: - owner: Windermere Town Council - crown: false - agreement_signed: false -winsford.gov.uk: - owner: Winsford Town Council - crown: false - agreement_signed: false -winslowtowncouncil.gov.uk: - owner: Winslow Town Council - crown: false - agreement_signed: false -wirksworth.gov.uk: - owner: Wirksworth Town Council - crown: false - agreement_signed: false -wirksworthtowncouncil.gov.uk: wirksworth.gov.uk -wirral-mbc.gov.uk: wirral.gov.uk -wirral.gov.uk: - owner: Wirral Metropolitan Borough Council - crown: false - agreement_signed: true -wisbechtowncouncil.gov.uk: - owner: Wisbech Town Council - crown: false - agreement_signed: false -witham.gov.uk: - owner: Witham Town Council - crown: false - agreement_signed: false -witney-tc.gov.uk: - owner: Witney Town Council - crown: false - agreement_signed: false -wivenhoe.gov.uk: - owner: Wivenhoe Town Council - crown: false - agreement_signed: false -wixford-pc.gov.uk: stratford.gov.uk -wmfs.net: - owner: West Midlands Fire Service - crown: false - agreement_signed: true -woking.gov.uk: - owner: Woking Borough Council - crown: false - agreement_signed: true -wokingham.gov.uk: - owner: Wokingham Borough Council - crown: false - agreement_signed: true -wokingham-tc.gov.uk: - owner: Wokingham Town Council - crown: false - agreement_signed: false -wolverhampton.gov.uk: - owner: Wolverhampton City Council - crown: false - agreement_signed: true -wolvertonandgreenleystowncouncil.gov.uk: - owner: Wolverton and Greenleys Town Council - crown: false - agreement_signed: false -woodbridge-suffolk.gov.uk: - owner: Woodbridge Town Council - crown: false - agreement_signed: false -woodley.gov.uk: - owner: Woodley Town Council - crown: false - agreement_signed: false -woodstock-tc.gov.uk: - owner: Woodstock Town Council - crown: false - agreement_signed: false -woottonbassett.gov.uk: - owner: Wootton Bassett Town Council - crown: false - agreement_signed: false -woottonwawen-pc.gov.uk: stratford.gov.uk -worcester.gov.uk: - owner: City of Worcester Council - crown: false - agreement_signed: false -worcestershire.gov.uk: - owner: Worcestershire County Council - crown: false - agreement_signed: true -worcestershirehub.gov.uk: worcestershire.gov.uk -worcestershirets.gov.uk: worcestershire.gov.uk -worcsregservices.gov.uk: bromsgrove.gov.uk -workingtontowncouncil.gov.uk: - owner: Workington Town Council - crown: false - agreement_signed: false -worthing.gov.uk: - owner: Worthing Borough Council - crown: false - agreement_signed: false -wra.gov.wales: - owner: Welsh Revenue Authority - crown: false - agreement_signed: true -wrecsam.gov.uk: wrexham.gov.uk -wrexham.gov.uk: - owner: Wrexham County Borough Council - crown: false - agreement_signed: true -wsm-tc.gov.uk: - owner: Weston-super-Mare Town Council - crown: false - agreement_signed: false -wsx-donnington-pc.gov.uk: chichester.gov.uk -wychavon.gov.uk: - owner: Wychavon District Council - crown: false - agreement_signed: false -wycombe.gov.uk: - owner: Wycombe District Council - crown: false - agreement_signed: false -wyrebc.gov.uk: - owner: Wyre Borough Council - crown: false - agreement_signed: false -wyreforestdc.gov.uk: wyreforest.gov.uk -wyreforest.gov.uk: - owner: Wyre Forest District Council - crown: false - agreement_signed: true -wyre.gov.uk: - owner: Wyre Council - crown: false - agreement_signed: false -yateley-tc.gov.uk: - owner: Yateley Town Council - crown: false - agreement_signed: false -yatetowncouncil.gov.uk: - owner: Yate Town Council - crown: false - agreement_signed: false -yeovil.gov.uk: southsomerset.gov.uk -yeoviltonparishcouncil.gov.uk: - owner: Yeovil Town Council - crown: false - agreement_signed: false -ynysmon.gov.uk: anglesey.gov.uk -york.gov.uk: - owner: City of York Council - crown: false - agreement_signed: true -yorkshirelca.gov.uk: - owner: Yorkshire Local Councils Associations - crown: false - agreement_signed: false From 07c754404972f921afc0f0ee2caa767110c4603b Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 4 Apr 2019 15:02:30 +0100 Subject: [PATCH 10/11] Use crown not crown_status as prop name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s confusing having a property name which is different from the attribute returned in the JSON from the API. --- app/models/organisation.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/models/organisation.py b/app/models/organisation.py index e3f827ff7..c9cdef32b 100644 --- a/app/models/organisation.py +++ b/app/models/organisation.py @@ -27,10 +27,6 @@ class Organisation(JSONModel): if self._dict == {}: self.name, self.crown, self.agreement_signed = None, None, None - @property - def crown_status(self): - return self.crown - def as_human_readable(self, fallback_domain): if 'dwp.' in ''.join(self.domains): return 'DWP - Requires OED approval' @@ -47,7 +43,7 @@ class Organisation(JSONModel): True: 'a crown body', False: 'a non-crown body', None: 'crown status unknown', - }.get(self.crown_status), + }.get(self.crown), ) else: return 'Can’t tell (domain is {})'.format(fallback_domain) @@ -57,7 +53,7 @@ class Organisation(JSONModel): @property def as_jinja_template(self): - if self.crown_status is None: + if self.crown is None: return 'agreement-choose' if self.agreement_signed: return 'agreement-signed' @@ -133,6 +129,6 @@ class Organisation(JSONModel): @property def crown_status_or_404(self): - if self.crown_status is None: + if self.crown is None: abort(404) - return self.crown_status + return self.crown From 3212915a5126d94e1404f120af3c0198fc277818 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Mon, 8 Apr 2019 09:46:05 +0100 Subject: [PATCH 11/11] Simplifying code for checking email domains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since this code isn’t trying to inherit from the code that also looked up domain names in `domains.yml` it can go back to being a lot simpler. This code is thoroughly tested already here: https://github.com/alphagov/notifications-admin/blob/a249382e6990635ddef9fef7fe614c9c8e57d55e/tests/app/main/test_validators.py#L74-L155 --- app/utils.py | 56 +++++++++++----------------------------------------- 1 file changed, 12 insertions(+), 44 deletions(-) diff --git a/app/utils.py b/app/utils.py index de4ac69d8..175ce6318 100644 --- a/app/utils.py +++ b/app/utils.py @@ -36,6 +36,11 @@ FAILURE_STATUSES = ['failed', 'temporary-failure', 'permanent-failure', 'technical-failure', 'virus-scan-failed', 'validation-failed'] REQUESTED_STATUSES = SENDING_STATUSES + DELIVERED_STATUSES + FAILURE_STATUSES +with open('{}/email_domains.yml'.format( + os.path.dirname(os.path.realpath(__file__)) +)) as email_domains: + GOVERNMENT_EMAIL_DOMAIN_NAMES = yaml.safe_load(email_domains) + def user_has_permissions(*permissions, **permission_kwargs): def wrap(func): @@ -280,11 +285,13 @@ def get_help_argument(): def is_gov_user(email_address): - try: - GovernmentEmailDomain(email_address) - return True - except NotGovernmentEmailDomain: - return False + return any( + email_address.lower().endswith(( + "@{}".format(known), + ".{}".format(known), + )) + for known in GOVERNMENT_EMAIL_DOMAIN_NAMES + ) def get_template( @@ -398,45 +405,6 @@ def set_status_filters(filter_args): ))) -_dir_path = os.path.dirname(os.path.realpath(__file__)) - - -class NotGovernmentEmailDomain(Exception): - pass - - -class GovernmentEmailDomain(): - - with open('{}/email_domains.yml'.format(_dir_path)) as email_domains: - domain_names = yaml.safe_load(email_domains) - - def __init__(self, email_address_or_domain): - try: - self._match = next(filter( - self.get_matching_function(email_address_or_domain), - self.domain_names, - )) - except StopIteration: - raise NotGovernmentEmailDomain() - - @staticmethod - def get_matching_function(email_address_or_domain): - - email_address_or_domain = email_address_or_domain.lower() - - def fn(domain): - - return ( - email_address_or_domain == domain - ) or ( - email_address_or_domain.endswith("@{}".format(domain)) - ) or ( - email_address_or_domain.endswith(".{}".format(domain)) - ) - - return fn - - def unicode_truncate(s, length): encoded = s.encode('utf-8')[:length] return encoded.decode('utf-8', 'ignore')