Merge branch 'master' into flexible-data-retention

This commit is contained in:
Rebecca Law
2018-08-06 15:37:11 +01:00
70 changed files with 1378 additions and 363 deletions

View File

@@ -47,7 +47,7 @@ class NotifyAdminAPIClient(BaseAPIClient):
# if the current service is inactive and the user isn't a platform admin, we should block them from making any
# stateful modifications to that service
if current_service and not current_service['active'] and not current_user.platform_admin:
if current_service and not current_service.active and not current_user.platform_admin:
abort(403)
def post(self, *args, **kwargs):

View File

@@ -7,26 +7,12 @@ class BillingAPIClient(NotifyAdminAPIClient):
def __init__(self):
super().__init__("a" * 73, "b")
def get_billable_units(self, service_id, year):
return self.get(
'/service/{0}/billing/monthly-usage'.format(service_id),
params=dict(year=year)
)
def get_service_usage(self, service_id, year=None):
return self.get(
'/service/{0}/billing/yearly-usage-summary'.format(service_id),
params=dict(year=year)
)
# Temporary methods to compare the usage before and after using ft_billing
def get_billable_units_ft(self, service_id, year):
return self.get(
'/service/{0}/billing/ft-monthly-usage'.format(service_id),
params=dict(year=year)
)
# Temporary methods to compare the usage before and after using ft_billing
def get_service_usage_ft(self, service_id, year=None):
return self.get(
'/service/{0}/billing/ft-yearly-usage-summary'.format(service_id),

View File

@@ -1,6 +1,6 @@
from collections import defaultdict
from app.notify_client import NotifyAdminAPIClient, _attach_current_user
from app.notify_client import NotifyAdminAPIClient, _attach_current_user, cache
class JobApiClient(NotifyAdminAPIClient):
@@ -16,6 +16,8 @@ class JobApiClient(NotifyAdminAPIClient):
'sent to dvla'
}
NON_SCHEDULED_JOB_STATUSES = JOB_STATUSES - {'scheduled', 'cancelled'}
def __init__(self):
super().__init__("a" * 73, "b")
@@ -60,8 +62,38 @@ class JobApiClient(NotifyAdminAPIClient):
return jobs
def get_page_of_jobs(self, service_id, page):
return self.get_jobs(
service_id,
statuses=self.NON_SCHEDULED_JOB_STATUSES,
page=page,
)
def get_immediate_jobs(self, service_id):
return self.get_jobs(
service_id,
limit_days=7,
statuses=self.NON_SCHEDULED_JOB_STATUSES,
)['data']
def get_scheduled_jobs(self, service_id):
return sorted(
self.get_jobs(service_id, statuses=['scheduled'])['data'],
key=lambda job: job['scheduled_for']
)
@cache.set('has_jobs-{service_id}')
def has_jobs(self, service_id):
return bool(self.get_jobs(service_id)['data'])
def create_job(self, job_id, service_id, scheduled_for=None):
self.redis_client.set(
'has_jobs-{}'.format(service_id),
b'true',
ex=cache.TTL,
)
data = {"id": job_id}
if scheduled_for:
@@ -78,6 +110,7 @@ class JobApiClient(NotifyAdminAPIClient):
return job
@cache.delete('has_jobs-{service_id}')
def cancel_job(self, service_id, job_id):
job = self.post(

View File

@@ -59,6 +59,7 @@ class User(UserMixin):
self.failed_login_count = fields.get('failed_login_count')
self.state = fields.get('state')
self.max_failed_login_count = max_failed_login_count
self.logged_in_at = fields.get('logged_in_at')
self.platform_admin = fields.get('platform_admin')
self.current_session_id = fields.get('current_session_id')
self.services = fields.get('services', [])
@@ -260,3 +261,45 @@ 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
class Service(dict):
ALLOWED_PROPERTIES = {
'active',
'branding',
'dvla_organisation',
'email_branding',
'email_from',
'id',
'inbound_api',
'letter_contact_block',
'message_limit',
'name',
'organisation_type',
'permissions',
'prefix_sms',
'research_mode',
'service_callback_api',
}
def __init__(self, _dict):
# in the case of a bad request current service may be `None`
super().__init__(_dict or {})
def __getattr__(self, attr):
if attr in self.ALLOWED_PROPERTIES:
return self[attr]
raise AttributeError
@property
def trial_mode(self):
return self['restricted']
def has_permission(self, permission):
return permission in self.permissions
def has_jobs(self):
# Cant import at top-level because app isnt yet initialised
from app import job_api_client
return job_api_client.has_jobs(self.id)

View File

@@ -18,6 +18,7 @@ class NotificationApiClient(NotifyAdminAPIClient):
include_from_test_key=None,
format_for_csv=None,
to=None,
include_one_off=None,
):
params = {}
if page is not None:
@@ -36,6 +37,8 @@ class NotificationApiClient(NotifyAdminAPIClient):
params['format_for_csv'] = format_for_csv
if to is not None:
params['to'] = to
if include_one_off is not None:
params['include_one_off'] = include_one_off
if job_id:
return self.get(
url='/service/{}/job/{}/notifications'.format(service_id, job_id),
@@ -64,7 +67,12 @@ class NotificationApiClient(NotifyAdminAPIClient):
return self.get(url='/service/{}/notifications/{}'.format(service_id, notification_id))
def get_api_notifications_for_service(self, service_id):
ret = self.get_notifications_for_service(service_id, include_jobs=False, include_from_test_key=True)
ret = self.get_notifications_for_service(
service_id,
include_jobs=False,
include_from_test_key=True,
include_one_off=False
)
return self.map_letters_to_accepted(ret)
@staticmethod

View File

@@ -1,3 +1,5 @@
from itertools import chain
from notifications_python_client.errors import HTTPError
from app.notify_client import NotifyAdminAPIClient, cache
@@ -179,6 +181,12 @@ class UserApiClient(NotifyAdminAPIClient):
data = {'email': email_address}
self.post(endpoint, data=data)
def find_users_by_full_or_partial_email(self, email_address):
endpoint = '/user/find-users-by-email'
data = {'email': email_address}
users = self.post(endpoint, data=data)
return users
def is_email_already_in_use(self, email_address):
if self.get_user_by_email_or_none(email_address):
return True
@@ -203,3 +211,17 @@ class UserApiClient(NotifyAdminAPIClient):
def get_organisations_and_services_for_user(self, user):
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)
return orgs_and_services_for_user['services_without_organisations'] + next(chain(
org['services'] for org in orgs_and_services_for_user['organisations']
), [])
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 service_id in self.get_service_ids_for_user(user)