diff --git a/app/assets/stylesheets/_grids.scss b/app/assets/stylesheets/_grids.scss
index fd6d38bee..48c1b60aa 100644
--- a/app/assets/stylesheets/_grids.scss
+++ b/app/assets/stylesheets/_grids.scss
@@ -49,6 +49,11 @@
margin-top: $gutter / 3;
}
+.top-gutter-2-3 {
+ @extend %top-gutter;
+ margin-top: $gutter * 2 / 3;
+}
+
%bottom-gutter,
.bottom-gutter {
@extend %contain-floats;
diff --git a/app/assets/stylesheets/components/browse-list.scss b/app/assets/stylesheets/components/browse-list.scss
index 95128534f..bc63bd9a6 100644
--- a/app/assets/stylesheets/components/browse-list.scss
+++ b/app/assets/stylesheets/components/browse-list.scss
@@ -37,6 +37,7 @@
&-hint {
@include core-19;
- margin-top: 5px;
+ margin: 5px 0 10px 0;
+ color: $secondary-text-colour;
}
}
diff --git a/app/main/views/choose_account.py b/app/main/views/choose_account.py
index 7af74bc0b..69a713aba 100644
--- a/app/main/views/choose_account.py
+++ b/app/main/views/choose_account.py
@@ -1,7 +1,6 @@
from flask import redirect, render_template, session, url_for
from flask_login import current_user, login_required
-from app import user_api_client
from app.main import main
from app.utils import PermanentRedirect
@@ -19,12 +18,8 @@ def services_or_dashboard():
@main.route("/accounts")
@login_required
def choose_account():
- orgs_and_services = user_api_client.get_organisations_and_services_for_user(current_user)
-
return render_template(
'views/choose-account.html',
- organisations=orgs_and_services['organisations'],
- services_without_organisations=orgs_and_services['services_without_organisations'],
can_add_service=current_user.is_gov_user,
)
@@ -36,17 +31,17 @@ def show_accounts_or_dashboard():
return redirect(url_for('.index'))
service_id = session.get('service_id')
- if service_id and (service_id in current_user.services or current_user.platform_admin):
+ if service_id and (current_user.belongs_to_service(service_id) or current_user.platform_admin):
return redirect(url_for('.service_dashboard', service_id=service_id))
organisation_id = session.get('organisation_id')
- if organisation_id and (organisation_id in current_user.organisations or current_user.platform_admin):
+ if organisation_id and (current_user.belongs_to_organisation(organisation_id) or current_user.platform_admin):
return redirect(url_for('.organisation_dashboard', org_id=organisation_id))
- if len(current_user.services) == 1 and not current_user.organisations:
- return redirect(url_for('.service_dashboard', service_id=current_user.services[0]))
+ if len(current_user.service_ids) == 1 and not current_user.organisation_ids:
+ return redirect(url_for('.service_dashboard', service_id=current_user.service_ids[0]))
- if len(current_user.organisations) == 1 and not current_user.services:
- return redirect(url_for('.organisation_dashboard', org_id=current_user.organisations[0]))
+ if len(current_user.organisation_ids) == 1 and not current_user.trial_mode_services:
+ return redirect(url_for('.organisation_dashboard', org_id=current_user.organisation_ids[0]))
return redirect(url_for('.choose_account'))
diff --git a/app/main/views/find_users.py b/app/main/views/find_users.py
index 3d04eb42e..ed364b954 100644
--- a/app/main/views/find_users.py
+++ b/app/main/views/find_users.py
@@ -31,12 +31,9 @@ def find_users_by_email():
@login_required
@user_is_platform_admin
def user_information(user_id):
- user = User.from_id(user_id)
- services = user_api_client.get_services_for_user(user)
return render_template(
'views/find-users/user-information.html',
- user=user,
- services=services,
+ user=User.from_id(user_id),
)
diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py
index 0d273c76d..874488587 100644
--- a/app/main/views/service_settings.py
+++ b/app/main/views/service_settings.py
@@ -222,7 +222,7 @@ def submit_request_to_go_live(service_id):
volume_sms_formatted=format_if_number(current_service.volume_sms),
volume_letter_formatted=format_if_number(current_service.volume_letter),
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',
+ existing_live='Yes' if current_user.live_services else 'No',
email_address=current_user.email_address,
),
ticket_type=zendesk_client.TYPE_QUESTION,
diff --git a/app/main/views/templates.py b/app/main/views/templates.py
index 5af350c24..27ac5d1c1 100644
--- a/app/main/views/templates.py
+++ b/app/main/views/templates.py
@@ -14,7 +14,6 @@ from app import (
service_api_client,
template_folder_api_client,
template_statistics_client,
- user_api_client,
)
from app.main import main
from app.main.forms import (
@@ -127,7 +126,7 @@ def choose_template(service_id, template_type='all', template_folder_id=None):
template_type=template_type,
allow_adding_letter_template=current_service.has_permission('letter'),
allow_adding_copy_of_template=(
- current_service.all_templates or len(current_user.services) > 1
+ current_service.all_templates or len(current_user.service_ids) > 1
),
)
option_hints = {template_folder_id: 'current folder'}
@@ -368,10 +367,7 @@ def choose_template_to_copy(
else:
return render_template(
'views/templates/copy.html',
- services_templates_and_folders=TemplateLists([
- Service(service) for service in
- user_api_client.get_services_for_user(current_user)
- ], user=current_user),
+ services_templates_and_folders=TemplateLists(current_user),
search_form=SearchByNameForm(),
)
@@ -402,7 +398,7 @@ def copy_template(service_id, template_id):
form=form,
template=template,
heading_action='Add',
- services=user_api_client.get_service_ids_for_user(current_user),
+ services=current_user.service_ids,
)
diff --git a/app/models/service.py b/app/models/service.py
index cae864e1e..7356b7db6 100644
--- a/app/models/service.py
+++ b/app/models/service.py
@@ -56,7 +56,6 @@ class Service(JSONModel):
def __init__(self, _dict):
super().__init__(_dict)
-
if 'permissions' not in self._dict:
self.permissions = {'email', 'sms', 'letter'}
@@ -94,6 +93,10 @@ class Service(JSONModel):
def trial_mode(self):
return self._dict['restricted']
+ @property
+ def live(self):
+ return not self.trial_mode
+
def has_permission(self, permission):
return permission in self.permissions
diff --git a/app/models/template_list.py b/app/models/template_list.py
index 678e72285..08de74044 100644
--- a/app/models/template_list.py
+++ b/app/models/template_list.py
@@ -65,9 +65,9 @@ class TemplateList():
class TemplateLists():
- def __init__(self, services, user=None):
+ def __init__(self, user):
self.services = sorted(
- services,
+ user.services,
key=lambda service: service.name.lower(),
)
self.user = user
diff --git a/app/models/user.py b/app/models/user.py
index 6af8e5aef..087157795 100644
--- a/app/models/user.py
+++ b/app/models/user.py
@@ -1,4 +1,5 @@
from collections.abc import Sequence
+from itertools import chain
from flask import abort, current_app, request, session
from flask_login import AnonymousUserMixin, UserMixin, login_user
@@ -38,11 +39,9 @@ class User(JSONModel, UserMixin):
'failed_login_count',
'logged_in_at',
'mobile_number',
- 'organisations',
'password_changed_at',
'permissions',
'platform_admin',
- 'services',
'state',
}
@@ -193,9 +192,9 @@ class User(JSONModel, UserMixin):
return True
if org_id:
- return org_id in self.organisations
+ return org_id in self.organisation_ids
if not permissions:
- return service_id in self.services
+ return service_id in self.service_ids
if service_id:
return any(x in self._permissions.get(service_id, []) for x in permissions)
@@ -223,12 +222,15 @@ class User(JSONModel, UserMixin):
]
def belongs_to_service(self, service_id):
- return str(service_id) in self.services
+ return str(service_id) in self.service_ids
def belongs_to_service_or_403(self, service_id):
if not self.belongs_to_service(service_id):
abort(403)
+ def belongs_to_organisation(self, organisation_id):
+ return str(organisation_id) in self.organisation_ids
+
@property
def locked(self):
return self.failed_login_count >= self.max_failed_login_count
@@ -237,6 +239,71 @@ class User(JSONModel, UserMixin):
def email_domain(self):
return self.email_address.split('@')[-1]
+ @cached_property
+ def orgs_and_services(self):
+ return user_api_client.get_organisations_and_services_for_user(self.id)
+
+ @property
+ def services(self):
+ return sorted(
+ self.services_with_organisation + self.services_without_organisations,
+ key=lambda service: service.name.lower(),
+ )
+
+ @property
+ def services_with_organisation(self):
+ from app.models.service import Service
+ return [
+ Service(service) for service in
+ next(chain(
+ org['services'] for org in self.orgs_and_services['organisations']
+ ), [])
+ ]
+
+ @property
+ def services_without_organisations(self):
+ from app.models.service import Service
+ return [
+ Service(service) for service in
+ self.orgs_and_services['services_without_organisations']
+ ]
+
+ @property
+ def service_ids(self):
+ return self._dict['services']
+
+ @property
+ def trial_mode_services(self):
+ return [
+ service for service in self.services if service.trial_mode
+ ]
+
+ @property
+ def live_services(self):
+ return [
+ service for service in self.services if service.live
+ ]
+
+ @property
+ def live_services_not_belonging_to_users_organisations(self):
+ from app.models.service import Service
+ return [
+ Service(service)
+ for service in self.orgs_and_services['services_without_organisations']
+ if not service['restricted']
+ ]
+
+ @property
+ def organisations(self):
+ return [
+ Organisation.from_id(organisation['id'])
+ for organisation in self.orgs_and_services['organisations']
+ ]
+
+ @property
+ def organisation_ids(self):
+ return self._dict['organisations']
+
@cached_property
def default_organisation(self):
return Organisation(
@@ -251,6 +318,14 @@ class User(JSONModel, UserMixin):
return 'nhs'
return None
+ @property
+ def has_access_to_live_and_trial_mode_services(self):
+ return (
+ self.organisations or self.live_services
+ ) and (
+ self.trial_mode_services
+ )
+
@property
def has_nhs_email_address(self):
return self.email_address.lower().endswith((
@@ -267,7 +342,7 @@ class User(JSONModel, UserMixin):
"state": self.state,
"failed_login_count": self.failed_login_count,
"permissions": [x for x in self._permissions],
- "organisations": self.organisations,
+ "organisations": self.organisation_ids,
"current_session_id": self.current_session_id
}
if hasattr(self, '_password'):
diff --git a/app/navigation.py b/app/navigation.py
index 82d73b849..2a2366d63 100644
--- a/app/navigation.py
+++ b/app/navigation.py
@@ -89,7 +89,6 @@ class HeaderNavigation(Navigation):
'letter_branding',
'live_services',
'live_services_csv',
- 'organisations',
'performance_platform_xlsx',
'platform_admin',
'platform_admin_letter_validation_preview',
@@ -214,6 +213,7 @@ class HeaderNavigation(Navigation):
'organisation_settings',
'organisation_preview_email_branding',
'organisation_preview_letter_branding',
+ 'organisations',
'privacy',
'public_agreement',
'public_download_agreement',
diff --git a/app/notify_client/user_api_client.py b/app/notify_client/user_api_client.py
index 091d8f1d5..ababbe5b5 100644
--- a/app/notify_client/user_api_client.py
+++ b/app/notify_client/user_api_client.py
@@ -1,5 +1,3 @@
-from itertools import chain
-
from notifications_python_client.errors import HTTPError
from app.models.roles_and_permissions import (
@@ -184,29 +182,9 @@ class UserApiClient(NotifyAdminAPIClient):
data = {'email': new_email}
self.post(endpoint, data)
- def get_organisations_and_services_for_user(self, user):
- endpoint = '/user/{}/organisations-and-services'.format(user.id)
+ def get_organisations_and_services_for_user(self, user_id):
+ endpoint = '/user/{}/organisations-and-services'.format(user_id)
return self.get(endpoint)
- def get_services_for_user(self, user):
- orgs_and_services_for_user = self.get_organisations_and_services_for_user(user)
- all_services = orgs_and_services_for_user['services_without_organisations'] + next(chain(
- org['services'] for org in orgs_and_services_for_user['organisations']
- ), [])
- return sorted(all_services, key=lambda service: service['name'])
-
- def user_has_live_services(self, user):
- return any(
- not service['restricted'] for service in self.get_services_for_user(user)
- )
-
- def get_service_ids_for_user(self, user):
- return {
- service['id'] for service in self.get_services_for_user(user)
- }
-
- def user_belongs_to_service(self, user, service_id):
- return str(service_id) in self.get_service_ids_for_user(user)
-
user_api_client = UserApiClient()
diff --git a/app/templates/views/choose-account.html b/app/templates/views/choose-account.html
index a205284c2..d60f43156 100644
--- a/app/templates/views/choose-account.html
+++ b/app/templates/views/choose-account.html
@@ -1,43 +1,119 @@
{% extends "withoutnav_template.html" %}
+{% macro service_list(
+ heading,
+ show_heading,
+ organisations=[],
+ services=[]
+) %}
+ {% if show_heading %}
+
+
+
+ {{ heading }}
+
+
+
+
+ {% else %}
+
+ {% endif %}
+ {% if show_add_service_button %}
+
+ {% endif %}
+
+{% endmacro %}
+
{% block per_page_title %}
Choose service
{% endblock %}
{% block maincolumn_content %}
-
+
Choose service
-
{{ user.email_address }}
- {{ user.mobile_number }}
- Services
+ {{ user.mobile_number or 'No mobile number'}}
+ Live services
+ Trial mode services
+
Last login
{% if not user.logged_in_at %}
- This person has never logged in
+ This person has never logged in
{% else %}
Last logged in