mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-25 08:44:23 -04:00
notify-api-412 use black to enforce python coding style
This commit is contained in:
@@ -8,17 +8,21 @@ from orderedset._orderedset import OrderedSet
|
||||
from werkzeug.datastructures import MultiDict
|
||||
from werkzeug.routing import RequestRedirect
|
||||
|
||||
SENDING_STATUSES = ['created', 'pending', 'sending']
|
||||
DELIVERED_STATUSES = ['delivered', 'sent']
|
||||
FAILURE_STATUSES = ['failed', 'temporary-failure', 'permanent-failure',
|
||||
'technical-failure', 'validation-failed']
|
||||
SENDING_STATUSES = ["created", "pending", "sending"]
|
||||
DELIVERED_STATUSES = ["delivered", "sent"]
|
||||
FAILURE_STATUSES = [
|
||||
"failed",
|
||||
"temporary-failure",
|
||||
"permanent-failure",
|
||||
"technical-failure",
|
||||
"validation-failed",
|
||||
]
|
||||
REQUESTED_STATUSES = SENDING_STATUSES + DELIVERED_STATUSES + FAILURE_STATUSES
|
||||
|
||||
NOTIFICATION_TYPES = ["sms", "email"]
|
||||
|
||||
|
||||
def service_has_permission(permission):
|
||||
|
||||
from app import current_service
|
||||
|
||||
def wrap(func):
|
||||
@@ -27,12 +31,18 @@ def service_has_permission(permission):
|
||||
if not current_service or not current_service.has_permission(permission):
|
||||
abort(403)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrap_func
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
def get_help_argument():
|
||||
return request.args.get('help') if request.args.get('help') in ('1', '2', '3') else None
|
||||
return (
|
||||
request.args.get("help")
|
||||
if request.args.get("help") in ("1", "2", "3")
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
def parse_filter_args(filter_dict):
|
||||
@@ -40,43 +50,50 @@ def parse_filter_args(filter_dict):
|
||||
filter_dict = MultiDict(filter_dict)
|
||||
|
||||
return MultiDict(
|
||||
(
|
||||
key,
|
||||
(','.join(filter_dict.getlist(key))).split(',')
|
||||
)
|
||||
(key, (",".join(filter_dict.getlist(key))).split(","))
|
||||
for key in filter_dict.keys()
|
||||
if ''.join(filter_dict.getlist(key))
|
||||
if "".join(filter_dict.getlist(key))
|
||||
)
|
||||
|
||||
|
||||
def set_status_filters(filter_args):
|
||||
status_filters = filter_args.get('status', [])
|
||||
return list(OrderedSet(chain(
|
||||
(status_filters or REQUESTED_STATUSES),
|
||||
DELIVERED_STATUSES if 'delivered' in status_filters else [],
|
||||
SENDING_STATUSES if 'sending' in status_filters else [],
|
||||
FAILURE_STATUSES if 'failed' in status_filters else []
|
||||
)))
|
||||
status_filters = filter_args.get("status", [])
|
||||
return list(
|
||||
OrderedSet(
|
||||
chain(
|
||||
(status_filters or REQUESTED_STATUSES),
|
||||
DELIVERED_STATUSES if "delivered" in status_filters else [],
|
||||
SENDING_STATUSES if "sending" in status_filters else [],
|
||||
FAILURE_STATUSES if "failed" in status_filters else [],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def unicode_truncate(s, length):
|
||||
encoded = s.encode('utf-8')[:length]
|
||||
return encoded.decode('utf-8', 'ignore')
|
||||
encoded = s.encode("utf-8")[:length]
|
||||
return encoded.decode("utf-8", "ignore")
|
||||
|
||||
|
||||
def should_skip_template_page(db_template):
|
||||
return (
|
||||
current_user.has_permissions('send_messages')
|
||||
and not current_user.has_permissions('manage_templates', 'manage_api_keys')
|
||||
and not db_template['archived']
|
||||
current_user.has_permissions("send_messages")
|
||||
and not current_user.has_permissions("manage_templates", "manage_api_keys")
|
||||
and not db_template["archived"]
|
||||
)
|
||||
|
||||
|
||||
def get_default_sms_sender(sms_senders):
|
||||
return str(next((
|
||||
Field(x['sms_sender'], html='escape')
|
||||
for x in sms_senders if x['is_default']
|
||||
), "None"))
|
||||
return str(
|
||||
next(
|
||||
(
|
||||
Field(x["sms_sender"], html="escape")
|
||||
for x in sms_senders
|
||||
if x["is_default"]
|
||||
),
|
||||
"None",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class PermanentRedirect(RequestRedirect):
|
||||
@@ -85,6 +102,7 @@ class PermanentRedirect(RequestRedirect):
|
||||
308 status codes are not supported when Internet Explorer is used with Windows 7
|
||||
and Windows 8.1, so this class keeps the original status code of 301.
|
||||
"""
|
||||
|
||||
code = 301
|
||||
|
||||
|
||||
@@ -93,8 +111,9 @@ def hide_from_search_engines(f):
|
||||
def decorated_function(*args, **kwargs):
|
||||
g.hide_from_search_engines = True
|
||||
response = make_response(f(*args, **kwargs))
|
||||
response.headers['X-Robots-Tag'] = 'noindex'
|
||||
response.headers["X-Robots-Tag"] = "noindex"
|
||||
return response
|
||||
|
||||
return decorated_function
|
||||
|
||||
|
||||
|
||||
@@ -2,19 +2,22 @@ from app.models.organization import Organization
|
||||
|
||||
|
||||
def get_email_choices(service):
|
||||
organization_branding_id = service.organization.email_branding_id if service.organization else None
|
||||
organization_branding_id = (
|
||||
service.organization.email_branding_id if service.organization else None
|
||||
)
|
||||
|
||||
if (
|
||||
service.organization_type == Organization.TYPE_FEDERAL
|
||||
and service.email_branding_id is not None # GOV.UK is not current branding
|
||||
and organization_branding_id is None # no default to supersede it (GOV.UK)
|
||||
):
|
||||
yield ('govuk', 'GOV.UK')
|
||||
yield ("govuk", "GOV.UK")
|
||||
|
||||
if (
|
||||
service.organization_type == Organization.TYPE_FEDERAL
|
||||
and service.organization
|
||||
and organization_branding_id is None # don't offer both if org has default
|
||||
and service.email_branding_name.lower() != f'GOV.UK and {service.organization.name}'.lower()
|
||||
and service.email_branding_name.lower()
|
||||
!= f"GOV.UK and {service.organization.name}".lower()
|
||||
):
|
||||
yield ('govuk_and_org', f'GOV.UK and {service.organization.name}')
|
||||
yield ("govuk_and_org", f"GOV.UK and {service.organization.name}")
|
||||
|
||||
116
app/utils/csv.py
116
app/utils/csv.py
@@ -5,17 +5,16 @@ from app.utils.templates import get_sample_template
|
||||
|
||||
|
||||
def get_errors_for_csv(recipients, template_type):
|
||||
|
||||
errors = []
|
||||
|
||||
if any(recipients.rows_with_bad_recipients):
|
||||
number_of_bad_recipients = len(list(recipients.rows_with_bad_recipients))
|
||||
if 'sms' == template_type:
|
||||
if "sms" == template_type:
|
||||
if 1 == number_of_bad_recipients:
|
||||
errors.append("fix 1 phone number")
|
||||
else:
|
||||
errors.append("fix {} phone numbers".format(number_of_bad_recipients))
|
||||
elif 'email' == template_type:
|
||||
elif "email" == template_type:
|
||||
if 1 == number_of_bad_recipients:
|
||||
errors.append("fix 1 email address")
|
||||
else:
|
||||
@@ -26,23 +25,35 @@ def get_errors_for_csv(recipients, template_type):
|
||||
if 1 == number_of_rows_with_missing_data:
|
||||
errors.append("enter missing data in 1 row")
|
||||
else:
|
||||
errors.append("enter missing data in {} rows".format(number_of_rows_with_missing_data))
|
||||
errors.append(
|
||||
"enter missing data in {} rows".format(number_of_rows_with_missing_data)
|
||||
)
|
||||
|
||||
if any(recipients.rows_with_message_too_long):
|
||||
number_of_rows_with_message_too_long = len(list(recipients.rows_with_message_too_long))
|
||||
number_of_rows_with_message_too_long = len(
|
||||
list(recipients.rows_with_message_too_long)
|
||||
)
|
||||
if 1 == number_of_rows_with_message_too_long:
|
||||
errors.append("shorten the message in 1 row")
|
||||
else:
|
||||
errors.append("shorten the messages in {} rows".format(number_of_rows_with_message_too_long))
|
||||
errors.append(
|
||||
"shorten the messages in {} rows".format(
|
||||
number_of_rows_with_message_too_long
|
||||
)
|
||||
)
|
||||
|
||||
if any(recipients.rows_with_empty_message):
|
||||
number_of_rows_with_empty_message = len(list(recipients.rows_with_empty_message))
|
||||
number_of_rows_with_empty_message = len(
|
||||
list(recipients.rows_with_empty_message)
|
||||
)
|
||||
if 1 == number_of_rows_with_empty_message:
|
||||
errors.append("check you have content for the empty message in 1 row")
|
||||
else:
|
||||
errors.append("check you have content for the empty messages in {} rows".format(
|
||||
number_of_rows_with_empty_message
|
||||
))
|
||||
errors.append(
|
||||
"check you have content for the empty messages in {} rows".format(
|
||||
number_of_rows_with_empty_message
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
@@ -50,52 +61,71 @@ def get_errors_for_csv(recipients, template_type):
|
||||
def generate_notifications_csv(**kwargs):
|
||||
from app import notification_api_client
|
||||
from app.s3_client.s3_csv_client import s3download
|
||||
if 'page' not in kwargs:
|
||||
kwargs['page'] = 1
|
||||
|
||||
if kwargs.get('job_id'):
|
||||
original_file_contents = s3download(kwargs['service_id'], kwargs['job_id'])
|
||||
if "page" not in kwargs:
|
||||
kwargs["page"] = 1
|
||||
|
||||
if kwargs.get("job_id"):
|
||||
original_file_contents = s3download(kwargs["service_id"], kwargs["job_id"])
|
||||
original_upload = RecipientCSV(
|
||||
original_file_contents,
|
||||
template=get_sample_template(kwargs['template_type']),
|
||||
template=get_sample_template(kwargs["template_type"]),
|
||||
)
|
||||
original_column_headers = original_upload.column_headers
|
||||
fieldnames = ['Row number'] + original_column_headers + ['Template', 'Type', 'Job', 'Status', 'Time']
|
||||
fieldnames = (
|
||||
["Row number"]
|
||||
+ original_column_headers
|
||||
+ ["Template", "Type", "Job", "Status", "Time"]
|
||||
)
|
||||
else:
|
||||
fieldnames = ['Recipient', 'Template', 'Type', 'Sent by', 'Job', 'Status', 'Time']
|
||||
fieldnames = [
|
||||
"Recipient",
|
||||
"Template",
|
||||
"Type",
|
||||
"Sent by",
|
||||
"Job",
|
||||
"Status",
|
||||
"Time",
|
||||
]
|
||||
|
||||
yield ','.join(fieldnames) + '\n'
|
||||
yield ",".join(fieldnames) + "\n"
|
||||
|
||||
while kwargs['page']:
|
||||
notifications_resp = notification_api_client.get_notifications_for_service(**kwargs)
|
||||
for notification in notifications_resp['notifications']:
|
||||
if kwargs.get('job_id'):
|
||||
values = [
|
||||
notification['row_number'],
|
||||
] + [
|
||||
original_upload[notification['row_number'] - 1].get(header).data
|
||||
for header in original_column_headers
|
||||
] + [
|
||||
notification['template_name'],
|
||||
notification['template_type'],
|
||||
notification['job_name'],
|
||||
notification['status'],
|
||||
notification['created_at'],
|
||||
]
|
||||
while kwargs["page"]:
|
||||
notifications_resp = notification_api_client.get_notifications_for_service(
|
||||
**kwargs
|
||||
)
|
||||
for notification in notifications_resp["notifications"]:
|
||||
if kwargs.get("job_id"):
|
||||
values = (
|
||||
[
|
||||
notification["row_number"],
|
||||
]
|
||||
+ [
|
||||
original_upload[notification["row_number"] - 1].get(header).data
|
||||
for header in original_column_headers
|
||||
]
|
||||
+ [
|
||||
notification["template_name"],
|
||||
notification["template_type"],
|
||||
notification["job_name"],
|
||||
notification["status"],
|
||||
notification["created_at"],
|
||||
]
|
||||
)
|
||||
else:
|
||||
values = [
|
||||
notification['recipient'],
|
||||
notification['template_name'],
|
||||
notification['template_type'],
|
||||
notification['created_by_name'] or '',
|
||||
notification['job_name'] or '',
|
||||
notification['status'],
|
||||
notification['created_at']
|
||||
notification["recipient"],
|
||||
notification["template_name"],
|
||||
notification["template_type"],
|
||||
notification["created_by_name"] or "",
|
||||
notification["job_name"] or "",
|
||||
notification["status"],
|
||||
notification["created_at"],
|
||||
]
|
||||
yield Spreadsheet.from_rows([map(str, values)]).as_csv_data
|
||||
|
||||
if notifications_resp['links'].get('next'):
|
||||
kwargs['page'] += 1
|
||||
if notifications_resp["links"].get("next"):
|
||||
kwargs["page"] += 1
|
||||
else:
|
||||
return
|
||||
raise Exception("Should never reach here")
|
||||
|
||||
@@ -9,10 +9,11 @@ from app.utils.time import is_less_than_days_ago
|
||||
def redirect_to_sign_in(f):
|
||||
@wraps(f)
|
||||
def wrapped(*args, **kwargs):
|
||||
if 'user_details' not in session:
|
||||
return redirect(url_for('main.sign_in'))
|
||||
if "user_details" not in session:
|
||||
return redirect(url_for("main.sign_in"))
|
||||
else:
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
@@ -20,10 +21,10 @@ def log_in_user(user_id):
|
||||
try:
|
||||
user = User.from_id(user_id)
|
||||
# the user will have a new current_session_id set by the API - store it in the cookie for future requests
|
||||
session['current_session_id'] = user.current_session_id
|
||||
session["current_session_id"] = user.current_session_id
|
||||
# Check if coming from new password page
|
||||
if 'password' in session.get('user_details', {}):
|
||||
user.update_password(session['user_details']['password'])
|
||||
if "password" in session.get("user_details", {}):
|
||||
user.update_password(session["user_details"]["password"])
|
||||
user.activate()
|
||||
user.login()
|
||||
finally:
|
||||
@@ -35,11 +36,11 @@ def log_in_user(user_id):
|
||||
|
||||
|
||||
def redirect_when_logged_in(platform_admin):
|
||||
next_url = request.args.get('next')
|
||||
next_url = request.args.get("next")
|
||||
if next_url and is_safe_redirect_url(next_url):
|
||||
return redirect(next_url)
|
||||
|
||||
return redirect(url_for('main.show_accounts_or_dashboard'))
|
||||
return redirect(url_for("main.show_accounts_or_dashboard"))
|
||||
|
||||
|
||||
def email_needs_revalidating(user):
|
||||
@@ -49,7 +50,10 @@ def email_needs_revalidating(user):
|
||||
# see https://stackoverflow.com/questions/60532973/how-do-i-get-a-is-safe-url-function-to-use-with-flask-and-how-does-it-work # noqa
|
||||
def is_safe_redirect_url(target):
|
||||
from urllib.parse import urljoin, urlparse
|
||||
|
||||
host_url = urlparse(request.host_url)
|
||||
redirect_url = urlparse(urljoin(request.host_url, target))
|
||||
return redirect_url.scheme in ('http', 'https') and \
|
||||
host_url.netloc == redirect_url.netloc
|
||||
return (
|
||||
redirect_url.scheme in ("http", "https")
|
||||
and host_url.netloc == redirect_url.netloc
|
||||
)
|
||||
|
||||
@@ -2,9 +2,9 @@ from flask import request, url_for
|
||||
|
||||
|
||||
def get_page_from_request():
|
||||
if 'page' in request.args:
|
||||
if "page" in request.args:
|
||||
try:
|
||||
return int(request.args['page'])
|
||||
return int(request.args["page"])
|
||||
except ValueError:
|
||||
return None
|
||||
else:
|
||||
@@ -12,16 +12,20 @@ def get_page_from_request():
|
||||
|
||||
|
||||
def generate_previous_dict(view, service_id, page, url_args=None):
|
||||
return generate_previous_next_dict(view, service_id, page - 1, 'Previous page', url_args or {})
|
||||
return generate_previous_next_dict(
|
||||
view, service_id, page - 1, "Previous page", url_args or {}
|
||||
)
|
||||
|
||||
|
||||
def generate_next_dict(view, service_id, page, url_args=None):
|
||||
return generate_previous_next_dict(view, service_id, page + 1, 'Next page', url_args or {})
|
||||
return generate_previous_next_dict(
|
||||
view, service_id, page + 1, "Next page", url_args or {}
|
||||
)
|
||||
|
||||
|
||||
def generate_previous_next_dict(view, service_id, page, title, url_args):
|
||||
return {
|
||||
'url': url_for(view, service_id=service_id, page=page, **url_args),
|
||||
'title': title,
|
||||
'label': 'page {}'.format(page)
|
||||
"url": url_for(view, service_id=service_id, page=page, **url_args),
|
||||
"title": title,
|
||||
"label": "page {}".format(page),
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ from notifications_utils.template import EmailPreviewTemplate, SMSPreviewTemplat
|
||||
|
||||
|
||||
def get_sample_template(template_type):
|
||||
if template_type == 'email':
|
||||
return EmailPreviewTemplate({'content': 'any', 'subject': '', 'template_type': 'email'})
|
||||
if template_type == 'sms':
|
||||
return SMSPreviewTemplate({'content': 'any', 'template_type': 'sms'})
|
||||
if template_type == "email":
|
||||
return EmailPreviewTemplate(
|
||||
{"content": "any", "subject": "", "template_type": "email"}
|
||||
)
|
||||
if template_type == "sms":
|
||||
return SMSPreviewTemplate({"content": "any", "template_type": "sms"})
|
||||
|
||||
|
||||
def get_template(
|
||||
@@ -16,16 +18,16 @@ def get_template(
|
||||
email_reply_to=None,
|
||||
sms_sender=None,
|
||||
):
|
||||
if 'email' == template['template_type']:
|
||||
if "email" == template["template_type"]:
|
||||
return EmailPreviewTemplate(
|
||||
template,
|
||||
from_name=service.name,
|
||||
from_address='{}@notifications.service.gov.uk'.format(service.email_from),
|
||||
from_address="{}@notifications.service.gov.uk".format(service.email_from),
|
||||
show_recipient=show_recipient,
|
||||
redact_missing_personalisation=redact_missing_personalisation,
|
||||
reply_to=email_reply_to,
|
||||
)
|
||||
if 'sms' == template['template_type']:
|
||||
if "sms" == template["template_type"]:
|
||||
return SMSPreviewTemplate(
|
||||
template,
|
||||
prefix=service.name,
|
||||
|
||||
@@ -6,8 +6,8 @@ from dateutil import parser
|
||||
|
||||
def get_current_financial_year():
|
||||
now = datetime.now(pytz.utc)
|
||||
current_month = int(now.strftime('%-m'))
|
||||
current_year = int(now.strftime('%Y'))
|
||||
current_month = int(now.strftime("%-m"))
|
||||
current_year = int(now.strftime("%Y"))
|
||||
return current_year if current_month > 9 else current_year - 1
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ def user_has_permissions(*permissions, **permission_kwargs):
|
||||
if not current_user.has_permissions(*permissions, **permission_kwargs):
|
||||
abort(403)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrap_func
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
@@ -30,6 +32,7 @@ def user_is_gov_user(f):
|
||||
if not current_user.is_gov_user:
|
||||
abort(403)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
@@ -41,23 +44,24 @@ def user_is_platform_admin(f):
|
||||
if not current_user.platform_admin:
|
||||
abort(403)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def is_gov_user(email_address):
|
||||
return _email_address_ends_with(
|
||||
email_address, config.Config.GOVERNMENT_EMAIL_DOMAIN_NAMES
|
||||
) or _email_address_ends_with(
|
||||
email_address, organizations_client.get_domains()
|
||||
)
|
||||
) or _email_address_ends_with(email_address, organizations_client.get_domains())
|
||||
|
||||
|
||||
def _email_address_ends_with(email_address, known_domains):
|
||||
return any(
|
||||
email_address.lower().endswith((
|
||||
"@{}".format(known),
|
||||
".{}".format(known),
|
||||
))
|
||||
email_address.lower().endswith(
|
||||
(
|
||||
"@{}".format(known),
|
||||
".{}".format(known),
|
||||
)
|
||||
)
|
||||
for known in known_domains
|
||||
)
|
||||
|
||||
|
||||
@@ -2,22 +2,22 @@ from itertools import chain
|
||||
|
||||
permission_mappings = {
|
||||
# TODO: consider turning off email-sending permissions during SMS pilot
|
||||
'send_messages': ['send_texts', 'send_emails'],
|
||||
'manage_templates': ['manage_templates'],
|
||||
'manage_service': ['manage_users', 'manage_settings'],
|
||||
'manage_api_keys': ['manage_api_keys'],
|
||||
'view_activity': ['view_activity'],
|
||||
"send_messages": ["send_texts", "send_emails"],
|
||||
"manage_templates": ["manage_templates"],
|
||||
"manage_service": ["manage_users", "manage_settings"],
|
||||
"manage_api_keys": ["manage_api_keys"],
|
||||
"view_activity": ["view_activity"],
|
||||
}
|
||||
|
||||
all_ui_permissions = set(permission_mappings.keys())
|
||||
all_db_permissions = set(chain(*permission_mappings.values()))
|
||||
|
||||
permission_options = (
|
||||
('view_activity', 'See dashboard'),
|
||||
('send_messages', 'Send messages'),
|
||||
('manage_templates', 'Add and edit templates'),
|
||||
('manage_service', 'Manage settings, team and usage'),
|
||||
('manage_api_keys', 'Manage API integration'),
|
||||
("view_activity", "See dashboard"),
|
||||
("send_messages", "Send messages"),
|
||||
("manage_templates", "Add and edit templates"),
|
||||
("manage_service", "Manage settings, team and usage"),
|
||||
("manage_api_keys", "Manage API integration"),
|
||||
)
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ def translate_permissions_from_db_to_ui(db_permissions):
|
||||
unknown_database_permissions = set(db_permissions) - all_db_permissions
|
||||
|
||||
return {
|
||||
ui_permission for ui_permission, db_permissions_for_ui_permission in permission_mappings.items()
|
||||
ui_permission
|
||||
for ui_permission, db_permissions_for_ui_permission in permission_mappings.items()
|
||||
if set(db_permissions_for_ui_permission) <= set(db_permissions)
|
||||
} | unknown_database_permissions
|
||||
|
||||
@@ -42,6 +43,9 @@ def translate_permissions_from_ui_to_db(ui_permissions):
|
||||
|
||||
Looks them up in the mapping, falling back to just passing through if they're not recognised.
|
||||
"""
|
||||
return set(chain.from_iterable(
|
||||
permission_mappings.get(ui_permission, [ui_permission]) for ui_permission in ui_permissions
|
||||
))
|
||||
return set(
|
||||
chain.from_iterable(
|
||||
permission_mappings.get(ui_permission, [ui_permission])
|
||||
for ui_permission in ui_permissions
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user