Merge pull request #864 from alphagov/set-branding-org

Add a page to set branding and organisation
This commit is contained in:
Leo Hemsted
2016-08-16 17:08:50 +01:00
committed by GitHub
11 changed files with 265 additions and 7 deletions

View File

@@ -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.events_api_client import EventsApiClient
from app.notify_client.provider_client import ProviderClient
from app.notify_client.organisations_client import OrganisationsClient
login_manager = LoginManager()
csrf = CsrfProtect()
@@ -60,6 +61,7 @@ statistics_api_client = StatisticsApiClient()
template_statistics_client = TemplateStatisticsApiClient()
events_api_client = EventsApiClient()
provider_client = ProviderClient()
organisations_client = OrganisationsClient()
asset_fingerprinter = AssetFingerprinter()
# The current service attached to the request stack.
@@ -88,6 +90,7 @@ def create_app():
template_statistics_client.init_app(application)
events_api_client.init_app(application)
provider_client.init_app(application)
organisations_client.init_app(application)
login_manager.init_app(application)
login_manager.login_view = 'main.sign_in'
@@ -285,6 +288,8 @@ def load_user(user_id):
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 \
else session.get('service_id')
from flask.globals import _request_ctx_stack

View File

@@ -338,3 +338,29 @@ class ServiceSmsSender(Form):
import re
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')
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()
]
)

View File

@@ -24,10 +24,10 @@ from app.main.forms import (
ServiceNameForm,
RequestToGoLiveForm,
ServiceReplyToEmailFrom,
ServiceSmsSender
ServiceSmsSender,
ServiceBrandingOrg
)
from app import user_api_client
from app import current_service
from app import user_api_client, current_service, organisations_client
@main.route("/services/<service_id>/service-settings")
@@ -266,3 +266,47 @@ def service_set_sms_sender(service_id):
return render_template(
'views/service-settings/set-sms-sender.html',
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
}

View 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']

View File

@@ -90,7 +90,9 @@ class ServiceAPIClient(NotificationsAPIClient):
'email_from',
'reply_to_email_address',
'sms_sender',
'created_by'
'created_by',
'branding',
'organisation'
}
if disallowed_attributes:
raise TypeError('Not allowed to update service attributes: {}'.format(

View File

@@ -21,3 +21,44 @@
</fieldset>
</div>
{% 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 %}

View File

@@ -18,6 +18,10 @@
'title': 'Set email reply to address',
'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',
'link': url_for('.service_set_sms_sender', service_id=current_service.id)
@@ -51,7 +55,7 @@
'title': 'Take service out of research mode',
'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 {
},
}
]) }}
{% endblock %}

View File

@@ -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 %}

View File

@@ -49,7 +49,9 @@ def service_json(
email_from=None,
reply_to_email_address=None,
sms_sender=None,
research_mode=False
research_mode=False,
organisation='organisation-id',
branding='govuk'
):
return {
'id': id_,
@@ -61,7 +63,9 @@ def service_json(
'email_from': email_from,
'reply_to_email_address': reply_to_email_address,
'sms_sender': sms_sender,
'research_mode': research_mode
'research_mode': research_mode,
'organisation': organisation,
'branding': branding
}

View File

@@ -759,3 +759,74 @@ def test_set_text_message_sender_flash_messages(
element = page.find('div', {"class": "banner-default-with-tick"})
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'
)

View File

@@ -1133,3 +1133,20 @@ def mock_events(mocker):
@pytest.fixture(scope='function')
def mock_send_already_registered_email(mocker):
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
)