2018-12-03 17:57:11 +00:00
|
|
|
from flask import abort, current_app
|
2018-10-26 17:31:49 +01:00
|
|
|
from notifications_utils.field import Field
|
2018-10-26 15:58:44 +01:00
|
|
|
from werkzeug.utils import cached_property
|
|
|
|
|
|
2018-11-07 11:35:24 +00:00
|
|
|
from app.notify_client.api_key_api_client import api_key_api_client
|
2018-10-26 17:31:49 +01:00
|
|
|
from app.notify_client.billing_api_client import billing_api_client
|
|
|
|
|
from app.notify_client.email_branding_client import email_branding_client
|
|
|
|
|
from app.notify_client.inbound_number_client import inbound_number_client
|
2018-12-03 10:59:36 +00:00
|
|
|
from app.notify_client.invite_api_client import invite_api_client
|
2018-10-26 16:09:59 +01:00
|
|
|
from app.notify_client.job_api_client import job_api_client
|
2019-01-23 12:27:14 +00:00
|
|
|
from app.notify_client.letter_branding_client import letter_branding_client
|
2018-10-26 17:31:49 +01:00
|
|
|
from app.notify_client.organisations_api_client import organisations_client
|
2018-10-26 16:09:59 +01:00
|
|
|
from app.notify_client.service_api_client import service_api_client
|
2018-11-01 17:11:58 +00:00
|
|
|
from app.notify_client.template_folder_api_client import (
|
|
|
|
|
template_folder_api_client,
|
|
|
|
|
)
|
2018-10-26 16:09:59 +01:00
|
|
|
from app.notify_client.user_api_client import user_api_client
|
2018-10-26 15:58:44 +01:00
|
|
|
from app.utils import get_default_sms_sender
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Service():
|
|
|
|
|
|
|
|
|
|
ALLOWED_PROPERTIES = {
|
|
|
|
|
'active',
|
|
|
|
|
'contact_link',
|
|
|
|
|
'email_branding',
|
|
|
|
|
'email_from',
|
|
|
|
|
'id',
|
|
|
|
|
'inbound_api',
|
2019-02-01 16:34:54 +00:00
|
|
|
'letter_branding',
|
2018-10-26 15:58:44 +01:00
|
|
|
'letter_contact_block',
|
|
|
|
|
'message_limit',
|
|
|
|
|
'name',
|
|
|
|
|
'organisation_type',
|
|
|
|
|
'permissions',
|
|
|
|
|
'prefix_sms',
|
|
|
|
|
'research_mode',
|
|
|
|
|
'service_callback_api',
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-08 11:54:57 +00:00
|
|
|
TEMPLATE_TYPES = (
|
2018-11-12 09:16:14 +00:00
|
|
|
'sms',
|
|
|
|
|
'email',
|
|
|
|
|
'letter',
|
2018-11-08 11:54:57 +00:00
|
|
|
)
|
|
|
|
|
|
2018-10-26 15:58:44 +01:00
|
|
|
def __init__(self, _dict):
|
|
|
|
|
# in the case of a bad request current service may be `None`
|
|
|
|
|
self._dict = _dict or {}
|
|
|
|
|
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))
|
|
|
|
|
|
2018-11-14 10:16:55 +00:00
|
|
|
def _get_by_id(self, things, id):
|
|
|
|
|
try:
|
2019-01-07 14:49:33 +00:00
|
|
|
return next(thing for thing in things if thing['id'] == str(id))
|
2018-11-14 10:16:55 +00:00
|
|
|
except StopIteration:
|
|
|
|
|
abort(404)
|
|
|
|
|
|
2018-11-05 16:03:11 +00:00
|
|
|
def update(self, **kwargs):
|
|
|
|
|
return service_api_client.update_service(self.id, **kwargs)
|
|
|
|
|
|
2018-11-05 17:03:37 +00:00
|
|
|
def switch_permission(self, permission):
|
2018-11-05 16:53:03 +00:00
|
|
|
return self.force_permission(
|
|
|
|
|
permission,
|
|
|
|
|
on=not self.has_permission(permission),
|
|
|
|
|
)
|
|
|
|
|
|
2018-11-05 17:03:37 +00:00
|
|
|
def force_permission(self, permission, on=False):
|
2018-11-05 16:53:03 +00:00
|
|
|
|
|
|
|
|
permissions, permission = set(self.permissions), {permission}
|
|
|
|
|
|
|
|
|
|
return self.update_permissions(
|
|
|
|
|
permissions | permission if on else permissions - permission,
|
|
|
|
|
)
|
|
|
|
|
|
2018-11-05 17:03:37 +00:00
|
|
|
def update_permissions(self, permissions):
|
2018-11-05 17:37:50 +00:00
|
|
|
return self.update(permissions=list(permissions))
|
2018-11-05 16:53:03 +00:00
|
|
|
|
2018-11-05 16:17:44 +00:00
|
|
|
def toggle_research_mode(self):
|
2018-11-05 17:37:50 +00:00
|
|
|
self.update(research_mode=not self.research_mode)
|
2018-11-05 16:17:44 +00:00
|
|
|
|
2018-10-26 15:58:44 +01:00
|
|
|
@property
|
|
|
|
|
def trial_mode(self):
|
|
|
|
|
return self._dict['restricted']
|
|
|
|
|
|
|
|
|
|
def has_permission(self, permission):
|
|
|
|
|
return permission in self.permissions
|
|
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
|
def has_jobs(self):
|
|
|
|
|
return job_api_client.has_jobs(self.id)
|
|
|
|
|
|
2018-12-03 10:59:36 +00:00
|
|
|
@cached_property
|
|
|
|
|
def team_members(self):
|
2018-12-05 16:39:15 +00:00
|
|
|
return sorted(
|
2018-12-03 10:59:36 +00:00
|
|
|
(
|
|
|
|
|
invite_api_client.get_invites_for_service(service_id=self.id) +
|
|
|
|
|
user_api_client.get_users_for_service(service_id=self.id)
|
|
|
|
|
),
|
2019-02-01 13:09:02 +00:00
|
|
|
key=lambda user: user.email_address.lower(),
|
2018-12-05 16:39:15 +00:00
|
|
|
)
|
2018-12-03 10:59:36 +00:00
|
|
|
|
2018-10-26 15:58:44 +01:00
|
|
|
@cached_property
|
|
|
|
|
def has_team_members(self):
|
|
|
|
|
return user_api_client.get_count_of_users_with_permission(
|
|
|
|
|
self.id, 'manage_service'
|
|
|
|
|
) > 1
|
|
|
|
|
|
|
|
|
|
@cached_property
|
2018-11-05 15:26:59 +00:00
|
|
|
def all_templates(self):
|
2018-10-26 15:58:44 +01:00
|
|
|
|
|
|
|
|
templates = service_api_client.get_service_templates(self.id)['data']
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
template for template in templates
|
|
|
|
|
if template['template_type'] in self.available_template_types
|
|
|
|
|
]
|
|
|
|
|
|
2018-11-08 11:42:01 +00:00
|
|
|
@cached_property
|
|
|
|
|
def all_template_ids(self):
|
|
|
|
|
return {template['id'] for template in self.all_templates}
|
|
|
|
|
|
2018-11-05 15:26:59 +00:00
|
|
|
def get_templates(self, template_type='all', template_folder_id=None):
|
2018-10-26 16:30:37 +01:00
|
|
|
if isinstance(template_type, str):
|
|
|
|
|
template_type = [template_type]
|
2019-01-07 14:49:33 +00:00
|
|
|
if template_folder_id:
|
|
|
|
|
template_folder_id = str(template_folder_id)
|
2018-10-26 15:58:44 +01:00
|
|
|
return [
|
2018-11-05 15:26:59 +00:00
|
|
|
template for template in self.all_templates
|
|
|
|
|
if (set(template_type) & {'all', template['template_type']})
|
2018-11-08 15:53:33 +00:00
|
|
|
and template.get('folder') == template_folder_id
|
2018-10-26 15:58:44 +01:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def available_template_types(self):
|
2018-11-12 09:16:14 +00:00
|
|
|
return list(filter(self.has_permission, self.TEMPLATE_TYPES))
|
2018-11-08 11:54:57 +00:00
|
|
|
|
2018-10-26 15:58:44 +01:00
|
|
|
@property
|
|
|
|
|
def has_templates(self):
|
2018-11-19 16:52:21 +00:00
|
|
|
return bool(self.all_templates)
|
|
|
|
|
|
|
|
|
|
def has_folders(self):
|
|
|
|
|
return bool(self.all_template_folders)
|
2018-10-26 15:58:44 +01:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def has_multiple_template_types(self):
|
|
|
|
|
return len({
|
2018-11-05 15:26:59 +00:00
|
|
|
template['template_type'] for template in self.all_templates
|
2018-10-26 15:58:44 +01:00
|
|
|
}) > 1
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def has_email_templates(self):
|
2018-11-05 15:26:59 +00:00
|
|
|
return len(self.get_templates('email')) > 0
|
2018-10-26 15:58:44 +01:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def has_sms_templates(self):
|
2018-11-05 15:26:59 +00:00
|
|
|
return len(self.get_templates('sms')) > 0
|
2018-10-26 15:58:44 +01:00
|
|
|
|
|
|
|
|
@cached_property
|
2018-10-26 17:31:49 +01:00
|
|
|
def email_reply_to_addresses(self):
|
|
|
|
|
return service_api_client.get_reply_to_email_addresses(self.id)
|
|
|
|
|
|
|
|
|
|
@property
|
2018-10-26 15:58:44 +01:00
|
|
|
def has_email_reply_to_address(self):
|
2018-10-26 17:31:49 +01:00
|
|
|
return bool(self.email_reply_to_addresses)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def count_email_reply_to_addresses(self):
|
|
|
|
|
return len(self.email_reply_to_addresses)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def default_email_reply_to_address(self):
|
|
|
|
|
return next(
|
|
|
|
|
(
|
|
|
|
|
x['email_address']
|
|
|
|
|
for x in self.email_reply_to_addresses if x['is_default']
|
|
|
|
|
), None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def get_email_reply_to_address(self, id):
|
|
|
|
|
return service_api_client.get_reply_to_email_address(self.id, id)
|
2018-10-26 15:58:44 +01:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def needs_to_add_email_reply_to_address(self):
|
|
|
|
|
return self.has_email_templates and not self.has_email_reply_to_address
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def shouldnt_use_govuk_as_sms_sender(self):
|
|
|
|
|
return self.organisation_type in {'local', 'nhs'}
|
|
|
|
|
|
|
|
|
|
@cached_property
|
2018-10-26 17:31:49 +01:00
|
|
|
def sms_senders(self):
|
|
|
|
|
return service_api_client.get_sms_senders(self.id)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def sms_senders_with_hints(self):
|
|
|
|
|
|
|
|
|
|
def attach_hint(sender):
|
|
|
|
|
hints = []
|
|
|
|
|
if sender['is_default']:
|
|
|
|
|
hints += ["default"]
|
|
|
|
|
if sender['inbound_number_id']:
|
|
|
|
|
hints += ["receives replies"]
|
|
|
|
|
if hints:
|
|
|
|
|
sender['hint'] = "(" + " and ".join(hints) + ")"
|
|
|
|
|
return sender
|
|
|
|
|
|
|
|
|
|
return [attach_hint(sender) for sender in self.sms_senders]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def default_sms_sender(self):
|
|
|
|
|
return get_default_sms_sender(self.sms_senders)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def count_sms_senders(self):
|
|
|
|
|
return len(self.sms_senders)
|
|
|
|
|
|
|
|
|
|
@property
|
2018-10-26 15:58:44 +01:00
|
|
|
def sms_sender_is_govuk(self):
|
2018-10-26 17:31:49 +01:00
|
|
|
return self.default_sms_sender in {'GOVUK', 'None'}
|
|
|
|
|
|
|
|
|
|
def get_sms_sender(self, id):
|
|
|
|
|
return service_api_client.get_sms_sender(self.id, id)
|
2018-10-26 15:58:44 +01:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def needs_to_change_sms_sender(self):
|
|
|
|
|
return all((
|
|
|
|
|
self.has_sms_templates,
|
|
|
|
|
self.shouldnt_use_govuk_as_sms_sender,
|
|
|
|
|
self.sms_sender_is_govuk,
|
|
|
|
|
))
|
|
|
|
|
|
2018-10-26 17:31:49 +01:00
|
|
|
@cached_property
|
|
|
|
|
def letter_contact_details(self):
|
|
|
|
|
return service_api_client.get_letter_contacts(self.id)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def count_letter_contact_details(self):
|
|
|
|
|
return len(self.letter_contact_details)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def default_letter_contact_block(self):
|
|
|
|
|
return next(
|
|
|
|
|
(
|
|
|
|
|
Field(x['contact_block'], html='escape')
|
|
|
|
|
for x in self.letter_contact_details if x['is_default']
|
|
|
|
|
), None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def get_letter_contact_block(self, id):
|
|
|
|
|
return service_api_client.get_letter_contact(self.id, id)
|
|
|
|
|
|
2018-10-26 15:58:44 +01:00
|
|
|
@property
|
|
|
|
|
def go_live_checklist_completed(self):
|
|
|
|
|
return all((
|
|
|
|
|
self.has_team_members,
|
|
|
|
|
self.has_templates,
|
|
|
|
|
not self.needs_to_add_email_reply_to_address,
|
|
|
|
|
not self.needs_to_change_sms_sender,
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def go_live_checklist_completed_as_yes_no(self):
|
|
|
|
|
return 'Yes' if self.go_live_checklist_completed else 'No'
|
2018-10-26 17:31:49 +01:00
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
|
def free_sms_fragment_limit(self):
|
|
|
|
|
return billing_api_client.get_free_sms_fragment_limit_for_year(self.id) or 0
|
|
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
|
def data_retention(self):
|
|
|
|
|
return service_api_client.get_service_data_retention(self.id)
|
|
|
|
|
|
|
|
|
|
def get_data_retention_item(self, id):
|
2018-12-03 17:19:38 +00:00
|
|
|
return next(
|
|
|
|
|
(dr for dr in self.data_retention if dr['id'] == id),
|
|
|
|
|
None
|
|
|
|
|
)
|
|
|
|
|
|
2018-12-03 17:57:11 +00:00
|
|
|
def get_days_of_retention(self, notification_type):
|
2018-12-03 17:19:38 +00:00
|
|
|
return next(
|
|
|
|
|
(dr for dr in self.data_retention if dr['notification_type'] == notification_type),
|
|
|
|
|
{}
|
2018-12-03 17:57:11 +00:00
|
|
|
).get('days_of_retention', current_app.config['ACTIVITY_STATS_LIMIT_DAYS'])
|
2018-10-26 17:31:49 +01:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def email_branding_id(self):
|
|
|
|
|
return self._dict['email_branding']
|
|
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
|
def email_branding(self):
|
|
|
|
|
if self.email_branding_id:
|
|
|
|
|
return email_branding_client.get_email_branding(self.email_branding_id)['email_branding']
|
|
|
|
|
return None
|
|
|
|
|
|
2019-02-01 16:34:54 +00:00
|
|
|
@property
|
|
|
|
|
def letter_branding_id(self):
|
|
|
|
|
return self._dict['letter_branding']
|
|
|
|
|
|
2018-10-26 17:31:49 +01:00
|
|
|
@cached_property
|
|
|
|
|
def letter_branding(self):
|
2019-02-01 16:34:54 +00:00
|
|
|
if self.letter_branding_id:
|
|
|
|
|
return letter_branding_client.get_letter_branding(self.letter_branding_id)
|
|
|
|
|
return None
|
2018-10-26 17:31:49 +01:00
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
|
def organisation_name(self):
|
|
|
|
|
return organisations_client.get_service_organisation(self.id).get('name', None)
|
|
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
|
def inbound_number(self):
|
|
|
|
|
return inbound_number_client.get_inbound_sms_number_for_service(self.id)['data'].get('number', '')
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def has_inbound_number(self):
|
|
|
|
|
return bool(self.inbound_number)
|
2018-11-01 15:33:09 +00:00
|
|
|
|
|
|
|
|
@cached_property
|
2018-11-05 16:25:19 +00:00
|
|
|
def all_template_folders(self):
|
2018-11-09 16:04:49 +00:00
|
|
|
return sorted(
|
|
|
|
|
template_folder_api_client.get_template_folders(self.id),
|
|
|
|
|
key=lambda folder: folder['name'].lower(),
|
|
|
|
|
)
|
2018-11-05 16:25:19 +00:00
|
|
|
|
2018-11-08 11:42:01 +00:00
|
|
|
@cached_property
|
|
|
|
|
def all_template_folder_ids(self):
|
|
|
|
|
return {folder['id'] for folder in self.all_template_folders}
|
|
|
|
|
|
2018-11-13 12:29:57 +00:00
|
|
|
def get_template_folders(self, template_type='all', parent_folder_id=None):
|
2019-01-07 14:49:33 +00:00
|
|
|
|
|
|
|
|
if parent_folder_id:
|
|
|
|
|
parent_folder_id = str(parent_folder_id)
|
|
|
|
|
|
2018-11-05 16:25:19 +00:00
|
|
|
return [
|
|
|
|
|
folder for folder in self.all_template_folders
|
2018-11-12 10:17:57 +00:00
|
|
|
if (
|
|
|
|
|
folder['parent_id'] == parent_folder_id and
|
2018-11-13 12:31:31 +00:00
|
|
|
self.is_folder_visible(folder['id'], template_type)
|
2018-11-12 10:17:57 +00:00
|
|
|
)
|
2018-11-05 16:25:19 +00:00
|
|
|
]
|
|
|
|
|
|
2018-11-13 16:05:05 +00:00
|
|
|
def get_template_folder(self, folder_id):
|
2018-11-16 13:41:55 +00:00
|
|
|
if folder_id is None:
|
|
|
|
|
return {
|
|
|
|
|
'id': None,
|
|
|
|
|
'name': 'Templates',
|
|
|
|
|
'parent_id': None,
|
|
|
|
|
}
|
2018-11-14 10:16:55 +00:00
|
|
|
return self._get_by_id(self.all_template_folders, folder_id)
|
2018-11-13 16:05:05 +00:00
|
|
|
|
2018-11-13 12:31:31 +00:00
|
|
|
def is_folder_visible(self, template_folder_id, template_type='all'):
|
2018-11-12 10:17:57 +00:00
|
|
|
|
|
|
|
|
if template_type == 'all':
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
if self.get_templates(template_type, template_folder_id):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
if any(
|
2018-11-13 12:31:31 +00:00
|
|
|
self.is_folder_visible(child_folder['id'], template_type)
|
2018-11-13 12:29:57 +00:00
|
|
|
for child_folder in self.get_template_folders(template_type, template_folder_id)
|
2018-11-12 10:17:57 +00:00
|
|
|
):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
2018-11-05 16:25:19 +00:00
|
|
|
def get_template_folder_path(self, template_folder_id):
|
|
|
|
|
|
2018-11-16 13:41:55 +00:00
|
|
|
folder = self.get_template_folder(template_folder_id)
|
2018-11-05 16:25:19 +00:00
|
|
|
|
2018-11-16 13:41:55 +00:00
|
|
|
if folder['id'] is None:
|
|
|
|
|
return [folder]
|
2018-11-05 16:25:19 +00:00
|
|
|
|
2019-01-30 10:09:04 +00:00
|
|
|
return self.get_template_folder_path(folder['parent_id']) + [
|
|
|
|
|
self.get_template_folder(folder['id'])
|
2018-11-16 13:41:55 +00:00
|
|
|
]
|
2018-11-08 11:56:29 +00:00
|
|
|
|
2018-11-16 14:09:14 +00:00
|
|
|
def get_template_path(self, template):
|
2019-01-30 10:09:04 +00:00
|
|
|
return self.get_template_folder_path(template['folder']) + [
|
2018-11-16 14:09:14 +00:00
|
|
|
template,
|
|
|
|
|
]
|
|
|
|
|
|
2018-11-08 11:56:29 +00:00
|
|
|
def get_template_folders_and_templates(self, template_type, template_folder_id):
|
|
|
|
|
return (
|
|
|
|
|
self.get_templates(template_type, template_folder_id) +
|
2018-11-13 12:29:57 +00:00
|
|
|
self.get_template_folders(template_type, template_folder_id)
|
2018-11-08 11:56:29 +00:00
|
|
|
)
|
2018-11-08 14:46:18 +00:00
|
|
|
|
2018-11-20 12:18:18 +00:00
|
|
|
@property
|
|
|
|
|
def count_of_templates_and_folders(self):
|
2018-11-20 16:10:14 +00:00
|
|
|
return len(self.all_templates + self.all_template_folders)
|
2018-11-20 12:18:18 +00:00
|
|
|
|
2018-11-08 14:46:18 +00:00
|
|
|
def move_to_folder(self, ids_to_move, move_to):
|
|
|
|
|
|
|
|
|
|
ids_to_move = set(ids_to_move)
|
|
|
|
|
|
|
|
|
|
template_folder_api_client.move_to_folder(
|
|
|
|
|
service_id=self.id,
|
|
|
|
|
folder_id=move_to,
|
|
|
|
|
template_ids=ids_to_move & self.all_template_ids,
|
|
|
|
|
folder_ids=ids_to_move & self.all_template_folder_ids,
|
|
|
|
|
)
|
2018-11-07 11:35:24 +00:00
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
|
def api_keys(self):
|
2018-11-07 12:05:11 +00:00
|
|
|
return sorted(
|
|
|
|
|
api_key_api_client.get_api_keys(self.id)['apiKeys'],
|
|
|
|
|
key=lambda key: key['name'].lower(),
|
|
|
|
|
)
|
2018-11-07 11:35:24 +00:00
|
|
|
|
2018-11-07 11:53:29 +00:00
|
|
|
def get_api_key(self, id):
|
2018-11-14 10:16:55 +00:00
|
|
|
return self._get_by_id(self.api_keys, id)
|