mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-05 14:08:25 -04:00
Merge pull request #864 from alphagov/set-branding-org
Add a page to set branding and organisation
This commit is contained in:
@@ -45,6 +45,7 @@ from app.notify_client.template_statistics_api_client import TemplateStatisticsA
|
|||||||
from app.notify_client.user_api_client import UserApiClient
|
from app.notify_client.user_api_client import UserApiClient
|
||||||
from app.notify_client.events_api_client import EventsApiClient
|
from app.notify_client.events_api_client import EventsApiClient
|
||||||
from app.notify_client.provider_client import ProviderClient
|
from app.notify_client.provider_client import ProviderClient
|
||||||
|
from app.notify_client.organisations_client import OrganisationsClient
|
||||||
|
|
||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
csrf = CsrfProtect()
|
csrf = CsrfProtect()
|
||||||
@@ -60,6 +61,7 @@ statistics_api_client = StatisticsApiClient()
|
|||||||
template_statistics_client = TemplateStatisticsApiClient()
|
template_statistics_client = TemplateStatisticsApiClient()
|
||||||
events_api_client = EventsApiClient()
|
events_api_client = EventsApiClient()
|
||||||
provider_client = ProviderClient()
|
provider_client = ProviderClient()
|
||||||
|
organisations_client = OrganisationsClient()
|
||||||
asset_fingerprinter = AssetFingerprinter()
|
asset_fingerprinter = AssetFingerprinter()
|
||||||
|
|
||||||
# The current service attached to the request stack.
|
# The current service attached to the request stack.
|
||||||
@@ -88,6 +90,7 @@ def create_app():
|
|||||||
template_statistics_client.init_app(application)
|
template_statistics_client.init_app(application)
|
||||||
events_api_client.init_app(application)
|
events_api_client.init_app(application)
|
||||||
provider_client.init_app(application)
|
provider_client.init_app(application)
|
||||||
|
organisations_client.init_app(application)
|
||||||
|
|
||||||
login_manager.init_app(application)
|
login_manager.init_app(application)
|
||||||
login_manager.login_view = 'main.sign_in'
|
login_manager.login_view = 'main.sign_in'
|
||||||
@@ -285,6 +288,8 @@ def load_user(user_id):
|
|||||||
|
|
||||||
|
|
||||||
def load_service_before_request():
|
def load_service_before_request():
|
||||||
|
if '/static/' in request.url:
|
||||||
|
return
|
||||||
service_id = request.view_args.get('service_id', session.get('service_id')) if request.view_args \
|
service_id = request.view_args.get('service_id', session.get('service_id')) if request.view_args \
|
||||||
else session.get('service_id')
|
else session.get('service_id')
|
||||||
from flask.globals import _request_ctx_stack
|
from flask.globals import _request_ctx_stack
|
||||||
|
|||||||
@@ -338,3 +338,29 @@ class ServiceSmsSender(Form):
|
|||||||
import re
|
import re
|
||||||
if field.data and not re.match('^[a-zA-Z0-9\s]+$', field.data):
|
if field.data and not re.match('^[a-zA-Z0-9\s]+$', field.data):
|
||||||
raise ValidationError('Sms text message sender can only contain alpha-numeric characters')
|
raise ValidationError('Sms text message sender can only contain alpha-numeric characters')
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceBrandingOrg(Form):
|
||||||
|
|
||||||
|
def __init__(self, organisations=[], *args, **kwargs):
|
||||||
|
self.organisation.choices = organisations
|
||||||
|
super(ServiceBrandingOrg, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
branding_type = RadioField(
|
||||||
|
'Branding',
|
||||||
|
choices=[
|
||||||
|
('govuk', 'GOV.UK only'),
|
||||||
|
('both', 'GOV.UK and organisation'),
|
||||||
|
('org', 'Organisation only')
|
||||||
|
],
|
||||||
|
validators=[
|
||||||
|
DataRequired()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
organisation = RadioField(
|
||||||
|
'Organisation',
|
||||||
|
validators=[
|
||||||
|
DataRequired()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ from app.main.forms import (
|
|||||||
ServiceNameForm,
|
ServiceNameForm,
|
||||||
RequestToGoLiveForm,
|
RequestToGoLiveForm,
|
||||||
ServiceReplyToEmailFrom,
|
ServiceReplyToEmailFrom,
|
||||||
ServiceSmsSender
|
ServiceSmsSender,
|
||||||
|
ServiceBrandingOrg
|
||||||
)
|
)
|
||||||
from app import user_api_client
|
from app import user_api_client, current_service, organisations_client
|
||||||
from app import current_service
|
|
||||||
|
|
||||||
|
|
||||||
@main.route("/services/<service_id>/service-settings")
|
@main.route("/services/<service_id>/service-settings")
|
||||||
@@ -266,3 +266,47 @@ def service_set_sms_sender(service_id):
|
|||||||
return render_template(
|
return render_template(
|
||||||
'views/service-settings/set-sms-sender.html',
|
'views/service-settings/set-sms-sender.html',
|
||||||
form=form)
|
form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@main.route("/services/<service_id>/service-settings/set-branding-and-org", methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@user_has_permissions(admin_override=True)
|
||||||
|
def service_set_branding_and_org(service_id):
|
||||||
|
organisations = organisations_client.get_organisations()
|
||||||
|
|
||||||
|
form = ServiceBrandingOrg(branding_type=current_service.get('branding'))
|
||||||
|
# dynamically create org choices, including the null option
|
||||||
|
form.organisation.choices = [('None', 'None')] + get_branding_as_value_and_label(organisations)
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
organisation = None if form.organisation.data == 'None' else form.organisation.data
|
||||||
|
service_api_client.update_service(
|
||||||
|
service_id,
|
||||||
|
branding=form.branding_type.data,
|
||||||
|
organisation=organisation
|
||||||
|
)
|
||||||
|
return redirect(url_for('.service_settings', service_id=service_id))
|
||||||
|
|
||||||
|
form.organisation.data = current_service['organisation'] or 'None'
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'views/service-settings/set-branding-and-org.html',
|
||||||
|
form=form,
|
||||||
|
branding_dict=get_branding_as_dict(organisations)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_branding_as_value_and_label(organisations):
|
||||||
|
return [
|
||||||
|
(organisation['id'], organisation['name'])
|
||||||
|
for organisation in organisations
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_branding_as_dict(organisations):
|
||||||
|
return {
|
||||||
|
organisation['id']: {
|
||||||
|
'logo': '/static/images/email-template/crests/{}'.format(organisation['logo']),
|
||||||
|
'colour': organisation['colour']
|
||||||
|
} for organisation in organisations
|
||||||
|
}
|
||||||
|
|||||||
19
app/notify_client/organisations_client.py
Normal file
19
app/notify_client/organisations_client.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from notifications_python_client.base import BaseAPIClient
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationsClient(BaseAPIClient):
|
||||||
|
|
||||||
|
def __init__(self, base_url=None, client_id=None, secret=None):
|
||||||
|
super(self.__class__, self).__init__(
|
||||||
|
base_url=base_url or 'base_url',
|
||||||
|
client_id=client_id or 'client_id',
|
||||||
|
secret=secret or 'secret'
|
||||||
|
)
|
||||||
|
|
||||||
|
def init_app(self, app):
|
||||||
|
self.base_url = app.config['API_HOST_NAME']
|
||||||
|
self.client_id = app.config['ADMIN_CLIENT_USER_NAME']
|
||||||
|
self.secret = app.config['ADMIN_CLIENT_SECRET']
|
||||||
|
|
||||||
|
def get_organisations(self):
|
||||||
|
return self.get(url='/organisation')['organisations']
|
||||||
@@ -90,7 +90,9 @@ class ServiceAPIClient(NotificationsAPIClient):
|
|||||||
'email_from',
|
'email_from',
|
||||||
'reply_to_email_address',
|
'reply_to_email_address',
|
||||||
'sms_sender',
|
'sms_sender',
|
||||||
'created_by'
|
'created_by',
|
||||||
|
'branding',
|
||||||
|
'organisation'
|
||||||
}
|
}
|
||||||
if disallowed_attributes:
|
if disallowed_attributes:
|
||||||
raise TypeError('Not allowed to update service attributes: {}'.format(
|
raise TypeError('Not allowed to update service attributes: {}'.format(
|
||||||
|
|||||||
@@ -21,3 +21,44 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
{% macro branding_radios(
|
||||||
|
field,
|
||||||
|
hint=None,
|
||||||
|
branding_dict={}
|
||||||
|
) %}
|
||||||
|
<div class="form-group {% if field.errors %} error{% endif %}">
|
||||||
|
<fieldset>
|
||||||
|
<legend class="form-label">
|
||||||
|
{{ field.label }}
|
||||||
|
{% if field.errors %}
|
||||||
|
<span class="error-message">
|
||||||
|
{{ field.errors[0] }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</legend>
|
||||||
|
{% for value, option, checked in field.iter_choices() %}
|
||||||
|
<label class="block-label" for="{{ field.name }}-{{ loop.index }}">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="{{ field.name }}"
|
||||||
|
id="{{ field.name }}-{{ loop.index }}"
|
||||||
|
value="{{ value }}"
|
||||||
|
{% if checked %}checked="checked"{% endif %}
|
||||||
|
/>
|
||||||
|
{% if branding_dict.get(value, {}).get('colour') %}
|
||||||
|
<span style="background: {{ branding_dict[value].colour }}; display: inline-block; width: 3px; height: 27px"></span>
|
||||||
|
{% endif %}
|
||||||
|
{% if branding_dict.get(value, {}).get('logo') %}
|
||||||
|
<img
|
||||||
|
src="{{ branding_dict[value].logo }}"
|
||||||
|
height="27"
|
||||||
|
/>
|
||||||
|
{% endif %}
|
||||||
|
{{option}}
|
||||||
|
</label>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
'title': 'Set email reply to address',
|
'title': 'Set email reply to address',
|
||||||
'link': url_for('.service_set_reply_to_email', service_id=current_service.id)
|
'link': url_for('.service_set_reply_to_email', service_id=current_service.id)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'title': 'Set email branding',
|
||||||
|
'link': url_for('.service_set_branding_and_org', service_id=current_service.id)
|
||||||
|
} if current_user.has_permissions([], admin_override=True) else {},
|
||||||
{
|
{
|
||||||
'title': 'Set text message sender name',
|
'title': 'Set text message sender name',
|
||||||
'link': url_for('.service_set_sms_sender', service_id=current_service.id)
|
'link': url_for('.service_set_sms_sender', service_id=current_service.id)
|
||||||
@@ -51,7 +55,7 @@
|
|||||||
'title': 'Take service out of research mode',
|
'title': 'Take service out of research mode',
|
||||||
'link': url_for('.service_switch_research_mode', service_id=current_service.id)
|
'link': url_for('.service_switch_research_mode', service_id=current_service.id)
|
||||||
} if current_service.research_mode and current_user.has_permissions([], admin_override=True) else {
|
} if current_service.research_mode and current_user.has_permissions([], admin_override=True) else {
|
||||||
},
|
}
|
||||||
]) }}
|
]) }}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends "withnav_template.html" %}
|
||||||
|
{% from "components/radios.html" import radios, branding_radios %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
|
{% block page_title %}
|
||||||
|
Set branding and organisation – GOV.UK Notify
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
<h1 class="heading-large">Set email branding</h1>
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="column-three-quarters">
|
||||||
|
<form method="post">
|
||||||
|
{{ radios(form.branding_type) }}
|
||||||
|
{{ branding_radios(form.organisation, branding_dict=branding_dict) }}
|
||||||
|
{{ page_footer(
|
||||||
|
'Save',
|
||||||
|
back_link=url_for('.service_settings', service_id=current_service.id)
|
||||||
|
) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -49,7 +49,9 @@ def service_json(
|
|||||||
email_from=None,
|
email_from=None,
|
||||||
reply_to_email_address=None,
|
reply_to_email_address=None,
|
||||||
sms_sender=None,
|
sms_sender=None,
|
||||||
research_mode=False
|
research_mode=False,
|
||||||
|
organisation='organisation-id',
|
||||||
|
branding='govuk'
|
||||||
):
|
):
|
||||||
return {
|
return {
|
||||||
'id': id_,
|
'id': id_,
|
||||||
@@ -61,7 +63,9 @@ def service_json(
|
|||||||
'email_from': email_from,
|
'email_from': email_from,
|
||||||
'reply_to_email_address': reply_to_email_address,
|
'reply_to_email_address': reply_to_email_address,
|
||||||
'sms_sender': sms_sender,
|
'sms_sender': sms_sender,
|
||||||
'research_mode': research_mode
|
'research_mode': research_mode,
|
||||||
|
'organisation': organisation,
|
||||||
|
'branding': branding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -759,3 +759,74 @@ def test_set_text_message_sender_flash_messages(
|
|||||||
element = page.find('div', {"class": "banner-default-with-tick"})
|
element = page.find('div', {"class": "banner-default-with-tick"})
|
||||||
|
|
||||||
assert element.text.strip() == expected_flash_message
|
assert element.text.strip() == expected_flash_message
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_show_branding(
|
||||||
|
mocker, app_, platform_admin_user, service_one, mock_get_organisations
|
||||||
|
):
|
||||||
|
with app_.test_request_context(), app_.test_client() as client:
|
||||||
|
client.login(platform_admin_user, mocker, service_one)
|
||||||
|
response = client.get(url_for(
|
||||||
|
'main.service_set_branding_and_org', service_id=service_one['id']
|
||||||
|
))
|
||||||
|
assert response.status_code == 200
|
||||||
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
|
|
||||||
|
assert page.find('input', attrs={"id": "branding_type-0"})['value'] == 'govuk'
|
||||||
|
assert page.find('input', attrs={"id": "branding_type-1"})['value'] == 'both'
|
||||||
|
assert page.find('input', attrs={"id": "branding_type-2"})['value'] == 'org'
|
||||||
|
|
||||||
|
assert 'checked' in page.find('input', attrs={"id": "branding_type-0"}).attrs
|
||||||
|
assert 'checked' not in page.find('input', attrs={"id": "branding_type-1"}).attrs
|
||||||
|
assert 'checked' not in page.find('input', attrs={"id": "branding_type-2"}).attrs
|
||||||
|
|
||||||
|
app.organisations_client.get_organisations.assert_called_once_with()
|
||||||
|
app.service_api_client.get_service.assert_called_once_with(service_one['id'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_show_organisations(
|
||||||
|
mocker, app_, platform_admin_user, service_one, mock_get_organisations
|
||||||
|
):
|
||||||
|
with app_.test_request_context(), app_.test_client() as client:
|
||||||
|
client.login(platform_admin_user, mocker, service_one)
|
||||||
|
response = client.get(url_for(
|
||||||
|
'main.service_set_branding_and_org', service_id=service_one['id']
|
||||||
|
))
|
||||||
|
assert response.status_code == 200
|
||||||
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
|
|
||||||
|
assert page.find('input', attrs={"id": "branding_type-0"})['value'] == 'govuk'
|
||||||
|
assert page.find('input', attrs={"id": "branding_type-1"})['value'] == 'both'
|
||||||
|
assert page.find('input', attrs={"id": "branding_type-2"})['value'] == 'org'
|
||||||
|
|
||||||
|
assert 'checked' in page.find('input', attrs={"id": "branding_type-0"}).attrs
|
||||||
|
assert 'checked' not in page.find('input', attrs={"id": "branding_type-1"}).attrs
|
||||||
|
assert 'checked' not in page.find('input', attrs={"id": "branding_type-2"}).attrs
|
||||||
|
|
||||||
|
app.organisations_client.get_organisations.assert_called_once_with()
|
||||||
|
app.service_api_client.get_service.assert_called_once_with(service_one['id'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_set_branding_and_organisations(
|
||||||
|
mocker, app_, platform_admin_user, service_one, mock_get_organisations, mock_update_service
|
||||||
|
):
|
||||||
|
with app_.test_request_context(), app_.test_client() as client:
|
||||||
|
client.login(platform_admin_user, mocker, service_one)
|
||||||
|
response = client.post(
|
||||||
|
url_for(
|
||||||
|
'main.service_set_branding_and_org', service_id=service_one['id']
|
||||||
|
),
|
||||||
|
data={
|
||||||
|
'branding_type': 'org',
|
||||||
|
'organisation': 'organisation-id'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert response.location == url_for('main.service_settings', service_id=service_one['id'], _external=True)
|
||||||
|
|
||||||
|
app.organisations_client.get_organisations.assert_called_once_with()
|
||||||
|
app.service_api_client.update_service.assert_called_once_with(
|
||||||
|
service_one['id'],
|
||||||
|
branding='org',
|
||||||
|
organisation='organisation-id'
|
||||||
|
)
|
||||||
|
|||||||
@@ -1133,3 +1133,20 @@ def mock_events(mocker):
|
|||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def mock_send_already_registered_email(mocker):
|
def mock_send_already_registered_email(mocker):
|
||||||
return mocker.patch('app.user_api_client.send_already_registered_email')
|
return mocker.patch('app.user_api_client.send_already_registered_email')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def mock_get_organisations(mocker):
|
||||||
|
def _get_organisations():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'logo': 'example.png',
|
||||||
|
'name': 'Organisation name',
|
||||||
|
'id': 'organisation-id',
|
||||||
|
'colour': '#f00'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return mocker.patch(
|
||||||
|
'app.organisations_client.get_organisations', side_effect=_get_organisations
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user