mirror of
https://github.com/GSA/notifications-api.git
synced 2026-02-03 18:01:08 -05:00
reformat
This commit is contained in:
@@ -25,12 +25,14 @@ from app.service.service_callback_api_schema import (
|
||||
update_service_callback_api_schema,
|
||||
)
|
||||
|
||||
service_callback_blueprint = Blueprint('service_callback', __name__, url_prefix='/service/<uuid:service_id>')
|
||||
service_callback_blueprint = Blueprint(
|
||||
"service_callback", __name__, url_prefix="/service/<uuid:service_id>"
|
||||
)
|
||||
|
||||
register_errors(service_callback_blueprint)
|
||||
|
||||
|
||||
@service_callback_blueprint.route('/inbound-api', methods=['POST'])
|
||||
@service_callback_blueprint.route("/inbound-api", methods=["POST"])
|
||||
def create_service_inbound_api(service_id):
|
||||
data = request.get_json()
|
||||
validate(data, create_service_callback_api_schema)
|
||||
@@ -39,45 +41,51 @@ def create_service_inbound_api(service_id):
|
||||
try:
|
||||
save_service_inbound_api(inbound_api)
|
||||
except SQLAlchemyError as e:
|
||||
return handle_sql_error(e, 'service_inbound_api')
|
||||
return handle_sql_error(e, "service_inbound_api")
|
||||
|
||||
return jsonify(data=inbound_api.serialize()), 201
|
||||
|
||||
|
||||
@service_callback_blueprint.route('/inbound-api/<uuid:inbound_api_id>', methods=['POST'])
|
||||
@service_callback_blueprint.route(
|
||||
"/inbound-api/<uuid:inbound_api_id>", methods=["POST"]
|
||||
)
|
||||
def update_service_inbound_api(service_id, inbound_api_id):
|
||||
data = request.get_json()
|
||||
validate(data, update_service_callback_api_schema)
|
||||
|
||||
to_update = get_service_inbound_api(inbound_api_id, service_id)
|
||||
|
||||
reset_service_inbound_api(service_inbound_api=to_update,
|
||||
updated_by_id=data["updated_by_id"],
|
||||
url=data.get("url", None),
|
||||
bearer_token=data.get("bearer_token", None))
|
||||
reset_service_inbound_api(
|
||||
service_inbound_api=to_update,
|
||||
updated_by_id=data["updated_by_id"],
|
||||
url=data.get("url", None),
|
||||
bearer_token=data.get("bearer_token", None),
|
||||
)
|
||||
return jsonify(data=to_update.serialize()), 200
|
||||
|
||||
|
||||
@service_callback_blueprint.route('/inbound-api/<uuid:inbound_api_id>', methods=['GET'])
|
||||
@service_callback_blueprint.route("/inbound-api/<uuid:inbound_api_id>", methods=["GET"])
|
||||
def fetch_service_inbound_api(service_id, inbound_api_id):
|
||||
inbound_api = get_service_inbound_api(inbound_api_id, service_id)
|
||||
|
||||
return jsonify(data=inbound_api.serialize()), 200
|
||||
|
||||
|
||||
@service_callback_blueprint.route('/inbound-api/<uuid:inbound_api_id>', methods=['DELETE'])
|
||||
@service_callback_blueprint.route(
|
||||
"/inbound-api/<uuid:inbound_api_id>", methods=["DELETE"]
|
||||
)
|
||||
def remove_service_inbound_api(service_id, inbound_api_id):
|
||||
inbound_api = get_service_inbound_api(inbound_api_id, service_id)
|
||||
|
||||
if not inbound_api:
|
||||
error = 'Service inbound API not found'
|
||||
error = "Service inbound API not found"
|
||||
raise InvalidRequest(error, status_code=404)
|
||||
|
||||
delete_service_inbound_api(inbound_api)
|
||||
return '', 204
|
||||
return "", 204
|
||||
|
||||
|
||||
@service_callback_blueprint.route('/delivery-receipt-api', methods=['POST'])
|
||||
@service_callback_blueprint.route("/delivery-receipt-api", methods=["POST"])
|
||||
def create_service_callback_api(service_id):
|
||||
data = request.get_json()
|
||||
validate(data, create_service_callback_api_schema)
|
||||
@@ -87,56 +95,85 @@ def create_service_callback_api(service_id):
|
||||
try:
|
||||
save_service_callback_api(callback_api)
|
||||
except SQLAlchemyError as e:
|
||||
return handle_sql_error(e, 'service_callback_api')
|
||||
return handle_sql_error(e, "service_callback_api")
|
||||
|
||||
return jsonify(data=callback_api.serialize()), 201
|
||||
|
||||
|
||||
@service_callback_blueprint.route('/delivery-receipt-api/<uuid:callback_api_id>', methods=['POST'])
|
||||
@service_callback_blueprint.route(
|
||||
"/delivery-receipt-api/<uuid:callback_api_id>", methods=["POST"]
|
||||
)
|
||||
def update_service_callback_api(service_id, callback_api_id):
|
||||
data = request.get_json()
|
||||
validate(data, update_service_callback_api_schema)
|
||||
|
||||
to_update = get_service_callback_api(callback_api_id, service_id)
|
||||
|
||||
reset_service_callback_api(service_callback_api=to_update,
|
||||
updated_by_id=data["updated_by_id"],
|
||||
url=data.get("url", None),
|
||||
bearer_token=data.get("bearer_token", None))
|
||||
reset_service_callback_api(
|
||||
service_callback_api=to_update,
|
||||
updated_by_id=data["updated_by_id"],
|
||||
url=data.get("url", None),
|
||||
bearer_token=data.get("bearer_token", None),
|
||||
)
|
||||
return jsonify(data=to_update.serialize()), 200
|
||||
|
||||
|
||||
@service_callback_blueprint.route('/delivery-receipt-api/<uuid:callback_api_id>', methods=["GET"])
|
||||
@service_callback_blueprint.route(
|
||||
"/delivery-receipt-api/<uuid:callback_api_id>", methods=["GET"]
|
||||
)
|
||||
def fetch_service_callback_api(service_id, callback_api_id):
|
||||
callback_api = get_service_callback_api(callback_api_id, service_id)
|
||||
|
||||
return jsonify(data=callback_api.serialize()), 200
|
||||
|
||||
|
||||
@service_callback_blueprint.route('/delivery-receipt-api/<uuid:callback_api_id>', methods=['DELETE'])
|
||||
@service_callback_blueprint.route(
|
||||
"/delivery-receipt-api/<uuid:callback_api_id>", methods=["DELETE"]
|
||||
)
|
||||
def remove_service_callback_api(service_id, callback_api_id):
|
||||
callback_api = get_service_callback_api(callback_api_id, service_id)
|
||||
|
||||
if not callback_api:
|
||||
error = 'Service delivery receipt callback API not found'
|
||||
error = "Service delivery receipt callback API not found"
|
||||
raise InvalidRequest(error, status_code=404)
|
||||
|
||||
delete_service_callback_api(callback_api)
|
||||
return '', 204
|
||||
return "", 204
|
||||
|
||||
|
||||
def handle_sql_error(e, table_name):
|
||||
if hasattr(e, 'orig') and hasattr(e.orig, 'pgerror') and e.orig.pgerror \
|
||||
and ('duplicate key value violates unique constraint "ix_{}_service_id"'.format(table_name)
|
||||
in e.orig.pgerror):
|
||||
return jsonify(
|
||||
result='error',
|
||||
message={'name': ["You can only have one URL and bearer token for your service."]}
|
||||
), 400
|
||||
elif hasattr(e, 'orig') and hasattr(e.orig, 'pgerror') and e.orig.pgerror \
|
||||
and ('insert or update on table "{0}" violates '
|
||||
'foreign key constraint "{0}_service_id_fkey"'.format(table_name)
|
||||
in e.orig.pgerror):
|
||||
return jsonify(result='error', message="No result found"), 404
|
||||
if (
|
||||
hasattr(e, "orig")
|
||||
and hasattr(e.orig, "pgerror")
|
||||
and e.orig.pgerror
|
||||
and (
|
||||
'duplicate key value violates unique constraint "ix_{}_service_id"'.format(
|
||||
table_name
|
||||
)
|
||||
in e.orig.pgerror
|
||||
)
|
||||
):
|
||||
return (
|
||||
jsonify(
|
||||
result="error",
|
||||
message={
|
||||
"name": [
|
||||
"You can only have one URL and bearer token for your service."
|
||||
]
|
||||
},
|
||||
),
|
||||
400,
|
||||
)
|
||||
elif (
|
||||
hasattr(e, "orig")
|
||||
and hasattr(e.orig, "pgerror")
|
||||
and e.orig.pgerror
|
||||
and (
|
||||
'insert or update on table "{0}" violates '
|
||||
'foreign key constraint "{0}_service_id_fkey"'.format(table_name)
|
||||
in e.orig.pgerror
|
||||
)
|
||||
):
|
||||
return jsonify(result="error", message="No result found"), 404
|
||||
else:
|
||||
raise e
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,9 +21,10 @@ from app.v2.errors import BadRequestError
|
||||
def validate_created_by(service, created_by_id):
|
||||
user = get_user_by_id(created_by_id)
|
||||
if service not in user.services:
|
||||
message = 'Can’t create notification - {} is not part of the "{}" service'.format(
|
||||
user.name,
|
||||
service.name
|
||||
message = (
|
||||
'Can’t create notification - {} is not part of the "{}" service'.format(
|
||||
user.name, service.name
|
||||
)
|
||||
)
|
||||
raise BadRequestError(message=message)
|
||||
|
||||
@@ -36,16 +37,15 @@ def create_one_off_reference(template_type):
|
||||
def send_one_off_notification(service_id, post_data):
|
||||
service = dao_fetch_service_by_id(service_id)
|
||||
template = dao_get_template_by_id_and_service_id(
|
||||
template_id=post_data['template_id'],
|
||||
service_id=service_id
|
||||
template_id=post_data["template_id"], service_id=service_id
|
||||
)
|
||||
|
||||
personalisation = post_data.get('personalisation', None)
|
||||
personalisation = post_data.get("personalisation", None)
|
||||
|
||||
validate_template(template.id, personalisation, service, template.template_type)
|
||||
|
||||
validate_and_format_recipient(
|
||||
send_to=post_data['to'],
|
||||
send_to=post_data["to"],
|
||||
key_type=KEY_TYPE_NORMAL,
|
||||
service=service,
|
||||
notification_type=template.template_type,
|
||||
@@ -53,28 +53,28 @@ def send_one_off_notification(service_id, post_data):
|
||||
)
|
||||
client_reference = None
|
||||
|
||||
validate_created_by(service, post_data['created_by'])
|
||||
validate_created_by(service, post_data["created_by"])
|
||||
|
||||
sender_id = post_data.get('sender_id', None)
|
||||
sender_id = post_data.get("sender_id", None)
|
||||
reply_to = get_reply_to_text(
|
||||
notification_type=template.template_type,
|
||||
sender_id=sender_id,
|
||||
service=service,
|
||||
template=template
|
||||
template=template,
|
||||
)
|
||||
notification = persist_notification(
|
||||
template_id=template.id,
|
||||
template_version=template.version,
|
||||
recipient=post_data['to'],
|
||||
recipient=post_data["to"],
|
||||
service=service,
|
||||
personalisation=personalisation,
|
||||
notification_type=template.template_type,
|
||||
api_key_id=None,
|
||||
key_type=KEY_TYPE_NORMAL,
|
||||
created_by_id=post_data['created_by'],
|
||||
created_by_id=post_data["created_by"],
|
||||
reply_to_text=reply_to,
|
||||
reference=create_one_off_reference(template.template_type),
|
||||
client_reference=client_reference
|
||||
client_reference=client_reference,
|
||||
)
|
||||
|
||||
queue_name = QueueNames.PRIORITY if template.process_type == PRIORITY else None
|
||||
@@ -84,7 +84,7 @@ def send_one_off_notification(service_id, post_data):
|
||||
queue=queue_name,
|
||||
)
|
||||
|
||||
return {'id': str(notification.id)}
|
||||
return {"id": str(notification.id)}
|
||||
|
||||
|
||||
def get_reply_to_text(notification_type, sender_id, service, template):
|
||||
@@ -92,11 +92,13 @@ def get_reply_to_text(notification_type, sender_id, service, template):
|
||||
if sender_id:
|
||||
try:
|
||||
if notification_type == EMAIL_TYPE:
|
||||
message = 'Reply to email address not found'
|
||||
message = "Reply to email address not found"
|
||||
reply_to = dao_get_reply_to_by_id(service.id, sender_id).email_address
|
||||
elif notification_type == SMS_TYPE:
|
||||
message = 'SMS sender not found'
|
||||
reply_to = dao_get_service_sms_senders_by_id(service.id, sender_id).get_reply_to_text()
|
||||
message = "SMS sender not found"
|
||||
reply_to = dao_get_service_sms_senders_by_id(
|
||||
service.id, sender_id
|
||||
).get_reply_to_text()
|
||||
except NoResultFound:
|
||||
raise BadRequestError(message=message)
|
||||
else:
|
||||
|
||||
@@ -13,26 +13,30 @@ from app.notifications.process_notifications import (
|
||||
)
|
||||
|
||||
|
||||
def send_notification_to_service_users(service_id, template_id, personalisation=None, include_user_fields=None):
|
||||
def send_notification_to_service_users(
|
||||
service_id, template_id, personalisation=None, include_user_fields=None
|
||||
):
|
||||
personalisation = personalisation or {}
|
||||
include_user_fields = include_user_fields or []
|
||||
template = dao_get_template_by_id(template_id)
|
||||
service = dao_fetch_service_by_id(service_id)
|
||||
active_users = dao_fetch_active_users_for_service(service.id)
|
||||
notify_service = dao_fetch_service_by_id(current_app.config['NOTIFY_SERVICE_ID'])
|
||||
notify_service = dao_fetch_service_by_id(current_app.config["NOTIFY_SERVICE_ID"])
|
||||
|
||||
for user in active_users:
|
||||
personalisation = _add_user_fields(user, personalisation, include_user_fields)
|
||||
notification = persist_notification(
|
||||
template_id=template.id,
|
||||
template_version=template.version,
|
||||
recipient=user.email_address if template.template_type == EMAIL_TYPE else user.mobile_number,
|
||||
recipient=user.email_address
|
||||
if template.template_type == EMAIL_TYPE
|
||||
else user.mobile_number,
|
||||
service=notify_service,
|
||||
personalisation=personalisation,
|
||||
notification_type=template.template_type,
|
||||
api_key_id=None,
|
||||
key_type=KEY_TYPE_NORMAL,
|
||||
reply_to_text=notify_service.get_default_reply_to_email_address()
|
||||
reply_to_text=notify_service.get_default_reply_to_email_address(),
|
||||
)
|
||||
send_notification_to_queue(notification, queue=QueueNames.NOTIFY)
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ create_service_callback_api_schema = {
|
||||
"properties": {
|
||||
"url": https_url,
|
||||
"bearer_token": {"type": "string", "minLength": 10},
|
||||
"updated_by_id": uuid
|
||||
"updated_by_id": uuid,
|
||||
},
|
||||
"required": ["url", "bearer_token", "updated_by_id"]
|
||||
"required": ["url", "bearer_token", "updated_by_id"],
|
||||
}
|
||||
|
||||
update_service_callback_api_schema = {
|
||||
@@ -21,7 +21,7 @@ update_service_callback_api_schema = {
|
||||
"properties": {
|
||||
"url": https_url,
|
||||
"bearer_token": {"type": "string", "minLength": 10},
|
||||
"updated_by_id": uuid
|
||||
"updated_by_id": uuid,
|
||||
},
|
||||
"required": ["updated_by_id"]
|
||||
"required": ["updated_by_id"],
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ add_service_data_retention_request = {
|
||||
"days_of_retention": {"type": "integer"},
|
||||
"notification_type": {"enum": ["sms", "email"]},
|
||||
},
|
||||
"required": ["days_of_retention", "notification_type"]
|
||||
"required": ["days_of_retention", "notification_type"],
|
||||
}
|
||||
|
||||
|
||||
@@ -19,5 +19,5 @@ update_service_data_retention_request = {
|
||||
"properties": {
|
||||
"days_of_retention": {"type": "integer"},
|
||||
},
|
||||
"required": ["days_of_retention"]
|
||||
"required": ["days_of_retention"],
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ add_service_email_reply_to_request = {
|
||||
"title": "Add new email reply to address for service",
|
||||
"properties": {
|
||||
"email_address": {"type": "string", "format": "email_address"},
|
||||
"is_default": {"type": "boolean"}
|
||||
"is_default": {"type": "boolean"},
|
||||
},
|
||||
"required": ["email_address", "is_default"]
|
||||
"required": ["email_address", "is_default"],
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ add_service_sms_sender_request = {
|
||||
"properties": {
|
||||
"sms_sender": {"type": "string"},
|
||||
"is_default": {"type": "boolean"},
|
||||
"inbound_number_id": uuid
|
||||
"inbound_number_id": uuid,
|
||||
},
|
||||
"required": ["sms_sender", "is_default"]
|
||||
"required": ["sms_sender", "is_default"],
|
||||
}
|
||||
|
||||
@@ -23,12 +23,17 @@ def format_admin_stats(statistics):
|
||||
counts = create_stats_dict()
|
||||
|
||||
for row in statistics:
|
||||
if row.key_type == 'test':
|
||||
counts[row.notification_type]['test-key'] += row.count
|
||||
if row.key_type == "test":
|
||||
counts[row.notification_type]["test-key"] += row.count
|
||||
else:
|
||||
counts[row.notification_type]['total'] += row.count
|
||||
if row.status in ('technical-failure', 'permanent-failure', 'temporary-failure', 'virus-scan-failed'):
|
||||
counts[row.notification_type]['failures'][row.status] += row.count
|
||||
counts[row.notification_type]["total"] += row.count
|
||||
if row.status in (
|
||||
"technical-failure",
|
||||
"permanent-failure",
|
||||
"temporary-failure",
|
||||
"virus-scan-failed",
|
||||
):
|
||||
counts[row.notification_type]["failures"][row.status] += row.count
|
||||
|
||||
return counts
|
||||
|
||||
@@ -38,35 +43,32 @@ def create_stats_dict():
|
||||
for template in NOTIFICATION_TYPES:
|
||||
stats_dict[template] = {}
|
||||
|
||||
for status in ('total', 'test-key'):
|
||||
for status in ("total", "test-key"):
|
||||
stats_dict[template][status] = 0
|
||||
|
||||
stats_dict[template]['failures'] = {
|
||||
'technical-failure': 0,
|
||||
'permanent-failure': 0,
|
||||
'temporary-failure': 0,
|
||||
'virus-scan-failed': 0,
|
||||
stats_dict[template]["failures"] = {
|
||||
"technical-failure": 0,
|
||||
"permanent-failure": 0,
|
||||
"temporary-failure": 0,
|
||||
"virus-scan-failed": 0,
|
||||
}
|
||||
return stats_dict
|
||||
|
||||
|
||||
def format_monthly_template_notification_stats(year, rows):
|
||||
stats = {
|
||||
datetime.strftime(date, '%Y-%m'): {}
|
||||
for date in [
|
||||
datetime(year, month, 1) for month in range(4, 13)
|
||||
] + [
|
||||
datetime(year + 1, month, 1) for month in range(1, 4)
|
||||
]
|
||||
datetime.strftime(date, "%Y-%m"): {}
|
||||
for date in [datetime(year, month, 1) for month in range(4, 13)]
|
||||
+ [datetime(year + 1, month, 1) for month in range(1, 4)]
|
||||
}
|
||||
|
||||
for row in rows:
|
||||
formatted_month = row.month.strftime('%Y-%m')
|
||||
formatted_month = row.month.strftime("%Y-%m")
|
||||
if str(row.template_id) not in stats[formatted_month]:
|
||||
stats[formatted_month][str(row.template_id)] = {
|
||||
"name": row.name,
|
||||
"type": row.template_type,
|
||||
"counts": dict.fromkeys(NOTIFICATION_STATUS_TYPES, 0)
|
||||
"counts": dict.fromkeys(NOTIFICATION_STATUS_TYPES, 0),
|
||||
}
|
||||
stats[formatted_month][str(row.template_id)]["counts"][row.status] += row.count
|
||||
|
||||
@@ -75,30 +77,33 @@ def format_monthly_template_notification_stats(year, rows):
|
||||
|
||||
def create_zeroed_stats_dicts():
|
||||
return {
|
||||
template_type: {
|
||||
status: 0 for status in ('requested', 'delivered', 'failed')
|
||||
} for template_type in NOTIFICATION_TYPES
|
||||
template_type: {status: 0 for status in ("requested", "delivered", "failed")}
|
||||
for template_type in NOTIFICATION_TYPES
|
||||
}
|
||||
|
||||
|
||||
def _update_statuses_from_row(update_dict, row):
|
||||
if row.status != 'cancelled':
|
||||
update_dict['requested'] += row.count
|
||||
if row.status in ('delivered', 'sent'):
|
||||
update_dict['delivered'] += row.count
|
||||
if row.status != "cancelled":
|
||||
update_dict["requested"] += row.count
|
||||
if row.status in ("delivered", "sent"):
|
||||
update_dict["delivered"] += row.count
|
||||
elif row.status in (
|
||||
'failed', 'technical-failure', 'temporary-failure',
|
||||
'permanent-failure', 'validation-failed', 'virus-scan-failed'):
|
||||
update_dict['failed'] += row.count
|
||||
"failed",
|
||||
"technical-failure",
|
||||
"temporary-failure",
|
||||
"permanent-failure",
|
||||
"validation-failed",
|
||||
"virus-scan-failed",
|
||||
):
|
||||
update_dict["failed"] += row.count
|
||||
|
||||
|
||||
def create_empty_monthly_notification_status_stats_dict(year):
|
||||
utc_month_starts = get_months_for_financial_year(year)
|
||||
# nested dicts - data[month][template type][status] = count
|
||||
return {
|
||||
start.strftime('%Y-%m'): {
|
||||
template_type: defaultdict(int)
|
||||
for template_type in NOTIFICATION_TYPES
|
||||
start.strftime("%Y-%m"): {
|
||||
template_type: defaultdict(int) for template_type in NOTIFICATION_TYPES
|
||||
}
|
||||
for start in utc_month_starts
|
||||
}
|
||||
@@ -106,7 +111,7 @@ def create_empty_monthly_notification_status_stats_dict(year):
|
||||
|
||||
def add_monthly_notification_status_stats(data, stats):
|
||||
for row in stats:
|
||||
month = row.month.strftime('%Y-%m')
|
||||
month = row.month.strftime("%Y-%m")
|
||||
|
||||
data[month][row.notification_type][row.notification_status] += row.count
|
||||
|
||||
|
||||
@@ -21,17 +21,15 @@ def get_guest_list_objects(service_id, request_json):
|
||||
return [
|
||||
ServiceGuestList.from_string(service_id, type, recipient)
|
||||
for type, recipient in (
|
||||
get_recipients_from_request(request_json,
|
||||
'phone_numbers',
|
||||
MOBILE_TYPE) +
|
||||
get_recipients_from_request(request_json,
|
||||
'email_addresses',
|
||||
EMAIL_TYPE)
|
||||
get_recipients_from_request(request_json, "phone_numbers", MOBILE_TYPE)
|
||||
+ get_recipients_from_request(request_json, "email_addresses", EMAIL_TYPE)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def service_allowed_to_send_to(recipient, service, key_type, allow_guest_list_recipients=True):
|
||||
def service_allowed_to_send_to(
|
||||
recipient, service, key_type, allow_guest_list_recipients=True
|
||||
):
|
||||
if key_type == KEY_TYPE_TEST:
|
||||
return True
|
||||
|
||||
@@ -46,18 +44,12 @@ def service_allowed_to_send_to(recipient, service, key_type, allow_guest_list_re
|
||||
[user.mobile_number, user.email_address] for user in service.users
|
||||
)
|
||||
guest_list_members = [
|
||||
member.recipient for member in service.guest_list
|
||||
if allow_guest_list_recipients
|
||||
member.recipient for member in service.guest_list if allow_guest_list_recipients
|
||||
]
|
||||
|
||||
if (
|
||||
(key_type == KEY_TYPE_NORMAL and service.restricted) or
|
||||
(key_type == KEY_TYPE_TEAM)
|
||||
if (key_type == KEY_TYPE_NORMAL and service.restricted) or (
|
||||
key_type == KEY_TYPE_TEAM
|
||||
):
|
||||
return allowed_to_send_to(
|
||||
recipient,
|
||||
itertools.chain(
|
||||
team_members,
|
||||
guest_list_members
|
||||
)
|
||||
recipient, itertools.chain(team_members, guest_list_members)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user