2024-05-23 13:59:51 -07:00
|
|
|
from datetime import timedelta
|
2021-03-10 13:55:06 +00:00
|
|
|
from uuid import UUID
|
|
|
|
|
|
2022-05-13 09:49:18 +01:00
|
|
|
from dateutil.parser import parse
|
2025-05-14 11:20:45 -04:00
|
|
|
from flask import current_app
|
2016-04-04 12:21:38 +01:00
|
|
|
from marshmallow import (
|
2022-05-06 15:56:49 +01:00
|
|
|
EXCLUDE,
|
2025-05-16 16:23:59 -04:00
|
|
|
Schema,
|
2016-04-04 12:21:38 +01:00
|
|
|
ValidationError,
|
2025-05-16 16:23:59 -04:00
|
|
|
fields,
|
2021-03-10 13:55:06 +00:00
|
|
|
post_dump,
|
|
|
|
|
post_load,
|
|
|
|
|
pre_dump,
|
|
|
|
|
pre_load,
|
2016-04-04 12:21:38 +01:00
|
|
|
validates,
|
|
|
|
|
validates_schema,
|
|
|
|
|
)
|
2025-05-28 13:47:35 -04:00
|
|
|
from marshmallow_enum import EnumField as BaseEnumField
|
2025-05-16 16:23:59 -04:00
|
|
|
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, auto_field, field_for
|
2024-05-16 10:17:45 -04:00
|
|
|
|
2025-05-16 16:23:59 -04:00
|
|
|
from app import models
|
2024-05-16 10:17:45 -04:00
|
|
|
from app.dao.permissions_dao import permission_dao
|
2025-05-28 13:47:35 -04:00
|
|
|
from app.enums import (
|
|
|
|
|
NotificationStatus,
|
|
|
|
|
OrganizationType,
|
|
|
|
|
ServicePermissionType,
|
2025-05-28 14:01:55 -04:00
|
|
|
TemplateProcessType,
|
2025-05-28 13:47:35 -04:00
|
|
|
TemplateType,
|
|
|
|
|
)
|
2024-05-16 10:17:45 -04:00
|
|
|
from app.models import ServicePermission
|
2025-05-16 14:14:50 -04:00
|
|
|
from app.utils import DATETIME_FORMAT_NO_TIMEZONE, utc_now
|
2016-04-13 15:31:08 +01:00
|
|
|
from notifications_utils.recipients import (
|
2016-04-04 12:21:38 +01:00
|
|
|
InvalidEmailError,
|
|
|
|
|
InvalidPhoneError,
|
2021-03-10 13:55:06 +00:00
|
|
|
validate_and_format_phone_number,
|
|
|
|
|
validate_email_address,
|
|
|
|
|
validate_phone_number,
|
2016-03-06 13:21:46 +00:00
|
|
|
)
|
2016-02-22 17:17:29 +00:00
|
|
|
|
2016-01-08 17:51:46 +00:00
|
|
|
|
2025-05-28 13:47:35 -04:00
|
|
|
class SafeEnumField(BaseEnumField):
|
|
|
|
|
def fail(self, key, **kwargs):
|
|
|
|
|
kwargs["values"] = ", ".join([str(mem.value) for mem in self.enum])
|
|
|
|
|
kwargs["names"] = ", ".join([mem.name for mem in self.enum])
|
|
|
|
|
msg = self.error or self.default_error_messages.get(key, "Invalid input")
|
|
|
|
|
raise ValidationError(msg.format(**kwargs))
|
|
|
|
|
|
|
|
|
|
|
2016-05-05 10:45:47 +01:00
|
|
|
def _validate_positive_number(value, msg="Not a positive integer"):
|
|
|
|
|
try:
|
|
|
|
|
page_int = int(value)
|
2017-11-28 13:49:14 +00:00
|
|
|
except ValueError:
|
|
|
|
|
raise ValidationError(msg)
|
|
|
|
|
if page_int < 1:
|
2016-05-05 10:45:47 +01:00
|
|
|
raise ValidationError(msg)
|
|
|
|
|
|
|
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
def _validate_datetime_not_more_than_96_hours_in_future(
|
|
|
|
|
dte, msg="Date cannot be more than 96hrs in the future"
|
|
|
|
|
):
|
2024-05-23 13:59:51 -07:00
|
|
|
if dte > utc_now() + timedelta(hours=96):
|
2016-08-24 16:00:21 +01:00
|
|
|
raise ValidationError(msg)
|
|
|
|
|
|
|
|
|
|
|
2016-08-30 12:47:33 +01:00
|
|
|
def _validate_datetime_not_in_past(dte, msg="Date cannot be in the past"):
|
2024-05-23 13:59:51 -07:00
|
|
|
if dte < utc_now():
|
2016-08-24 16:00:21 +01:00
|
|
|
raise ValidationError(msg)
|
|
|
|
|
|
|
|
|
|
|
2022-05-13 09:49:18 +01:00
|
|
|
class FlexibleDateTime(fields.DateTime):
|
|
|
|
|
"""
|
|
|
|
|
Allows input data to not contain tz info.
|
|
|
|
|
Outputs data using the output format that marshmallow version 2 used to use, OLD_MARSHMALLOW_FORMAT
|
|
|
|
|
"""
|
|
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
DEFAULT_FORMAT = "flexible"
|
2022-05-13 09:49:18 +01:00
|
|
|
OLD_MARSHMALLOW_FORMAT = "%Y-%m-%dT%H:%M:%S+00:00"
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, allow_none=True, **kwargs):
|
|
|
|
|
super().__init__(*args, allow_none=allow_none, **kwargs)
|
2023-08-29 14:54:30 -07:00
|
|
|
self.DESERIALIZATION_FUNCS["flexible"] = parse
|
|
|
|
|
self.SERIALIZATION_FUNCS["flexible"] = lambda x: x.strftime(
|
|
|
|
|
self.OLD_MARSHMALLOW_FORMAT
|
|
|
|
|
)
|
2022-05-13 09:49:18 +01:00
|
|
|
|
|
|
|
|
|
2020-06-18 15:23:43 +01:00
|
|
|
class UUIDsAsStringsMixin:
|
|
|
|
|
@post_dump()
|
2022-05-06 15:55:53 +01:00
|
|
|
def __post_dump(self, data, **kwargs):
|
2020-06-18 15:23:43 +01:00
|
|
|
for key, value in data.items():
|
|
|
|
|
if isinstance(value, UUID):
|
|
|
|
|
data[key] = str(value)
|
|
|
|
|
|
2020-06-26 16:31:49 +01:00
|
|
|
if isinstance(value, list):
|
|
|
|
|
data[key] = [
|
2023-08-29 14:54:30 -07:00
|
|
|
(str(item) if isinstance(item, UUID) else item) for item in value
|
2020-06-26 16:31:49 +01:00
|
|
|
]
|
2022-05-06 15:59:00 +01:00
|
|
|
return data
|
2020-06-26 16:31:49 +01:00
|
|
|
|
2020-06-18 15:23:43 +01:00
|
|
|
|
2025-05-16 16:23:59 -04:00
|
|
|
class BaseSchema(SQLAlchemyAutoSchema):
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta:
|
|
|
|
|
load_instance = True
|
|
|
|
|
include_relationships = True
|
2022-05-06 15:56:49 +01:00
|
|
|
unknown = EXCLUDE
|
2020-12-31 14:04:38 +00:00
|
|
|
|
2016-04-04 12:21:38 +01:00
|
|
|
def __init__(self, load_json=False, *args, **kwargs):
|
2016-01-28 11:41:21 +00:00
|
|
|
self.load_json = load_json
|
2016-01-29 11:11:00 +00:00
|
|
|
super(BaseSchema, self).__init__(*args, **kwargs)
|
2016-01-11 15:07:13 +00:00
|
|
|
|
2016-01-28 11:41:21 +00:00
|
|
|
@post_load
|
2022-05-06 15:55:53 +01:00
|
|
|
def make_instance(self, data, **kwargs):
|
2016-01-28 11:41:21 +00:00
|
|
|
"""Deserialize data to an instance of the model. Update an existing row
|
|
|
|
|
if specified in `self.instance` or loaded by primary key(s) in the data;
|
|
|
|
|
else create a new row.
|
|
|
|
|
:param data: Data to deserialize.
|
|
|
|
|
"""
|
|
|
|
|
if self.load_json:
|
|
|
|
|
return data
|
2016-01-29 11:11:00 +00:00
|
|
|
return super(BaseSchema, self).make_instance(data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserSchema(BaseSchema):
|
2016-02-26 15:57:24 +00:00
|
|
|
permissions = fields.Method("user_permissions", dump_only=True)
|
2023-08-29 14:54:30 -07:00
|
|
|
password_changed_at = field_for(
|
|
|
|
|
models.User, "password_changed_at", format=DATETIME_FORMAT_NO_TIMEZONE
|
|
|
|
|
)
|
|
|
|
|
created_at = field_for(
|
|
|
|
|
models.User, "created_at", format=DATETIME_FORMAT_NO_TIMEZONE
|
|
|
|
|
)
|
2022-05-13 09:49:18 +01:00
|
|
|
updated_at = FlexibleDateTime()
|
|
|
|
|
logged_in_at = FlexibleDateTime()
|
2025-05-07 18:43:54 -04:00
|
|
|
auth_type = auto_field(by_value=True)
|
2022-05-06 15:25:14 +01:00
|
|
|
password = fields.String(required=True, load_only=True)
|
2016-02-26 15:57:24 +00:00
|
|
|
|
|
|
|
|
def user_permissions(self, usr):
|
|
|
|
|
retval = {}
|
2016-06-15 16:32:52 +01:00
|
|
|
for x in permission_dao.get_permissions_by_user_id(usr.id):
|
2016-02-26 15:57:24 +00:00
|
|
|
service_id = str(x.service_id)
|
|
|
|
|
if service_id not in retval:
|
|
|
|
|
retval[service_id] = []
|
|
|
|
|
retval[service_id].append(x.permission)
|
|
|
|
|
return retval
|
|
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-01-29 11:11:00 +00:00
|
|
|
model = models.User
|
|
|
|
|
exclude = (
|
2021-01-19 14:00:03 +00:00
|
|
|
"_password",
|
2018-02-19 15:03:36 +00:00
|
|
|
"created_at",
|
2021-01-19 14:00:03 +00:00
|
|
|
"email_access_validated_at",
|
|
|
|
|
"updated_at",
|
|
|
|
|
"verify_codes",
|
2018-02-19 15:03:36 +00:00
|
|
|
)
|
2016-01-28 11:41:21 +00:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("name")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_name(self, value, data_key):
|
2017-11-09 14:18:47 +00:00
|
|
|
if not value:
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(f"{data_key}: Invalid name")
|
|
|
|
|
raise ValidationError("Invalid name")
|
2017-11-09 14:18:47 +00:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("email_address")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_email_address(self, value, data_key):
|
2017-11-09 14:18:47 +00:00
|
|
|
try:
|
|
|
|
|
validate_email_address(value)
|
|
|
|
|
except InvalidEmailError as e:
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(f"{data_key}: {str(e)}")
|
2025-05-09 15:14:07 -04:00
|
|
|
raise ValidationError(str(e))
|
2017-11-09 14:18:47 +00:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("mobile_number")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_mobile_number(self, value, data_key):
|
2017-11-09 14:18:47 +00:00
|
|
|
try:
|
|
|
|
|
if value is not None:
|
|
|
|
|
validate_phone_number(value, international=True)
|
|
|
|
|
except InvalidPhoneError as error:
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(f"{data_key}: {str(error)}")
|
|
|
|
|
raise ValidationError(f"Invalid phone number: {str(error)}")
|
2017-11-09 14:18:47 +00:00
|
|
|
|
2016-01-08 17:51:46 +00:00
|
|
|
|
2016-11-07 17:41:49 +00:00
|
|
|
class UserUpdateAttributeSchema(BaseSchema):
|
2025-05-07 18:43:54 -04:00
|
|
|
auth_type = auto_field(by_value=True)
|
2022-05-13 09:49:18 +01:00
|
|
|
email_access_validated_at = FlexibleDateTime()
|
2016-11-07 17:41:49 +00:00
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-11-07 17:41:49 +00:00
|
|
|
model = models.User
|
|
|
|
|
exclude = (
|
2023-08-29 14:54:30 -07:00
|
|
|
"_password",
|
|
|
|
|
"created_at",
|
|
|
|
|
"failed_login_count",
|
|
|
|
|
"id",
|
|
|
|
|
"logged_in_at",
|
|
|
|
|
"password_changed_at",
|
|
|
|
|
"platform_admin",
|
|
|
|
|
"state",
|
|
|
|
|
"updated_at",
|
|
|
|
|
"verify_codes",
|
2021-01-19 14:00:03 +00:00
|
|
|
)
|
2016-11-07 17:41:49 +00:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("name")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_name(self, value, data_key):
|
2016-11-07 17:41:49 +00:00
|
|
|
if not value:
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(f"{data_key}: Invalid name")
|
|
|
|
|
raise ValidationError("Invalid name")
|
2016-11-07 17:41:49 +00:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("email_address")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_email_address(self, value, data_key):
|
2016-11-07 17:41:49 +00:00
|
|
|
try:
|
|
|
|
|
validate_email_address(value)
|
|
|
|
|
except InvalidEmailError as e:
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(f"{data_key}: {str(e)}")
|
2025-05-09 15:14:07 -04:00
|
|
|
raise ValidationError(str(e))
|
2016-11-07 17:41:49 +00:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("mobile_number")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_mobile_number(self, value, data_key):
|
2016-11-07 17:41:49 +00:00
|
|
|
try:
|
2017-11-09 14:18:47 +00:00
|
|
|
if value is not None:
|
|
|
|
|
validate_phone_number(value, international=True)
|
2016-11-07 17:41:49 +00:00
|
|
|
except InvalidPhoneError as error:
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(
|
|
|
|
|
f"{data_key}: Invalid phone number ({str(error)})"
|
|
|
|
|
)
|
|
|
|
|
raise ValidationError(f"Invalid phone number: {str(error)}")
|
2016-11-07 17:41:49 +00:00
|
|
|
|
|
|
|
|
@validates_schema(pass_original=True)
|
2022-05-06 15:55:53 +01:00
|
|
|
def check_unknown_fields(self, data, original_data, **kwargs):
|
2016-11-07 17:41:49 +00:00
|
|
|
for key in original_data:
|
|
|
|
|
if key not in self.fields:
|
2024-02-07 12:55:15 -05:00
|
|
|
raise ValidationError(f"Unknown field name {key}")
|
2016-11-07 17:41:49 +00:00
|
|
|
|
|
|
|
|
|
2017-02-07 11:27:13 +00:00
|
|
|
class UserUpdatePasswordSchema(BaseSchema):
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2017-02-07 11:27:13 +00:00
|
|
|
model = models.User
|
|
|
|
|
|
|
|
|
|
@validates_schema(pass_original=True)
|
2022-05-06 15:55:53 +01:00
|
|
|
def check_unknown_fields(self, data, original_data, **kwargs):
|
2017-02-07 11:27:13 +00:00
|
|
|
for key in original_data:
|
|
|
|
|
if key not in self.fields:
|
2024-02-07 12:55:15 -05:00
|
|
|
raise ValidationError(f"Unknown field name {key}")
|
2017-02-07 11:27:13 +00:00
|
|
|
|
|
|
|
|
|
2016-05-10 09:04:22 +01:00
|
|
|
class ProviderDetailsSchema(BaseSchema):
|
2023-08-29 14:54:30 -07:00
|
|
|
created_by = fields.Nested(
|
|
|
|
|
UserSchema, only=["id", "name", "email_address"], dump_only=True
|
|
|
|
|
)
|
2022-05-13 09:49:18 +01:00
|
|
|
updated_at = FlexibleDateTime()
|
2017-03-02 18:10:33 +00:00
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-05-10 09:04:22 +01:00
|
|
|
model = models.ProviderDetails
|
|
|
|
|
|
|
|
|
|
|
2017-03-08 17:21:02 +00:00
|
|
|
class ProviderDetailsHistorySchema(BaseSchema):
|
2023-08-29 14:54:30 -07:00
|
|
|
created_by = fields.Nested(
|
|
|
|
|
UserSchema, only=["id", "name", "email_address"], dump_only=True
|
|
|
|
|
)
|
2022-05-13 09:49:18 +01:00
|
|
|
updated_at = FlexibleDateTime()
|
2017-03-08 17:21:02 +00:00
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2017-03-02 18:10:33 +00:00
|
|
|
model = models.ProviderDetailsHistory
|
|
|
|
|
|
|
|
|
|
|
2020-06-26 14:10:12 +01:00
|
|
|
class ServiceSchema(BaseSchema, UUIDsAsStringsMixin):
|
2023-08-29 14:54:30 -07:00
|
|
|
created_by = field_for(models.Service, "created_by", required=True)
|
2025-05-28 13:47:35 -04:00
|
|
|
organization_type = SafeEnumField(
|
|
|
|
|
OrganizationType, by_value=True, required=False, allow_none=True
|
|
|
|
|
)
|
|
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
permissions = fields.Method(
|
|
|
|
|
"serialize_service_permissions", "deserialize_service_permissions"
|
|
|
|
|
)
|
|
|
|
|
email_branding = field_for(models.Service, "email_branding")
|
|
|
|
|
organization = field_for(models.Service, "organization")
|
|
|
|
|
go_live_at = field_for(
|
|
|
|
|
models.Service, "go_live_at", format=DATETIME_FORMAT_NO_TIMEZONE
|
|
|
|
|
)
|
2022-10-25 11:53:24 -04:00
|
|
|
|
2022-05-13 09:13:48 +01:00
|
|
|
def serialize_service_permissions(self, service):
|
2017-05-26 17:23:01 +01:00
|
|
|
return [p.permission for p in service.permissions]
|
2016-04-29 12:24:43 +01:00
|
|
|
|
2022-05-13 09:13:48 +01:00
|
|
|
def deserialize_service_permissions(self, in_data):
|
2023-08-29 14:54:30 -07:00
|
|
|
if isinstance(in_data, dict) and "permissions" in in_data:
|
|
|
|
|
str_permissions = in_data["permissions"]
|
2022-05-13 09:13:48 +01:00
|
|
|
permissions = []
|
|
|
|
|
for p in str_permissions:
|
|
|
|
|
permission = ServicePermission(service_id=in_data["id"], permission=p)
|
|
|
|
|
permissions.append(permission)
|
|
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
in_data["permissions"] = permissions
|
2022-05-13 09:13:48 +01:00
|
|
|
|
|
|
|
|
return in_data
|
|
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-01-08 17:51:46 +00:00
|
|
|
model = models.Service
|
2017-05-22 11:33:24 +01:00
|
|
|
exclude = (
|
2023-08-29 14:54:30 -07:00
|
|
|
"all_template_folders",
|
|
|
|
|
"annual_billing",
|
|
|
|
|
"api_keys",
|
|
|
|
|
"complaints",
|
|
|
|
|
"created_at",
|
|
|
|
|
"data_retention",
|
|
|
|
|
"guest_list",
|
|
|
|
|
"inbound_number",
|
|
|
|
|
"inbound_sms",
|
|
|
|
|
"jobs",
|
|
|
|
|
"reply_to_email_addresses",
|
|
|
|
|
"service_sms_senders",
|
|
|
|
|
"templates",
|
|
|
|
|
"updated_at",
|
|
|
|
|
"users",
|
|
|
|
|
"version",
|
2017-05-22 11:33:24 +01:00
|
|
|
)
|
2016-01-13 11:04:13 +00:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("permissions")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_permissions(self, value, data_key):
|
2017-05-22 19:03:59 +01:00
|
|
|
permissions = [v.permission for v in value]
|
|
|
|
|
for p in permissions:
|
2024-02-07 12:55:15 -05:00
|
|
|
if p not in {e for e in ServicePermissionType}:
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(
|
|
|
|
|
f"{data_key}: Invalid Service Permission: '{p}'"
|
|
|
|
|
)
|
|
|
|
|
raise ValidationError(f"Invalid Service Permission: '{p}'")
|
2017-05-22 19:03:59 +01:00
|
|
|
|
|
|
|
|
if len(set(permissions)) != len(permissions):
|
|
|
|
|
duplicates = list(set([x for x in permissions if permissions.count(x) > 1]))
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(
|
|
|
|
|
f"{data_key}: Duplicate Service Permission: {duplicates}"
|
|
|
|
|
)
|
2024-02-07 12:55:15 -05:00
|
|
|
raise ValidationError(f"Duplicate Service Permission: {duplicates}")
|
2017-05-22 19:03:59 +01:00
|
|
|
|
2017-05-22 11:33:24 +01:00
|
|
|
@pre_load()
|
2022-05-06 15:55:53 +01:00
|
|
|
def format_for_data_model(self, in_data, **kwargs):
|
2023-08-29 14:54:30 -07:00
|
|
|
if isinstance(in_data, dict) and "permissions" in in_data:
|
|
|
|
|
str_permissions = in_data["permissions"]
|
2017-05-22 11:33:24 +01:00
|
|
|
permissions = []
|
2017-05-26 15:27:49 +01:00
|
|
|
for p in str_permissions:
|
2017-05-24 10:45:05 +01:00
|
|
|
permission = ServicePermission(service_id=in_data["id"], permission=p)
|
2017-05-22 11:33:24 +01:00
|
|
|
permissions.append(permission)
|
|
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
in_data["permissions"] = permissions
|
2017-05-22 11:33:24 +01:00
|
|
|
|
2022-05-06 15:59:00 +01:00
|
|
|
return in_data
|
|
|
|
|
|
2016-01-13 11:04:13 +00:00
|
|
|
|
2025-05-16 16:23:59 -04:00
|
|
|
class TemplateTypeFieldOnlySchema(Schema):
|
|
|
|
|
template_type = fields.String(required=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NotificationStatusFieldOnlySchema(Schema):
|
|
|
|
|
status = fields.String(required=True)
|
|
|
|
|
|
|
|
|
|
|
2016-07-18 12:03:44 +01:00
|
|
|
class DetailedServiceSchema(BaseSchema):
|
|
|
|
|
statistics = fields.Dict()
|
2023-08-29 14:54:30 -07:00
|
|
|
organization_type = field_for(models.Service, "organization_type")
|
2022-05-13 09:49:18 +01:00
|
|
|
go_live_at = FlexibleDateTime()
|
|
|
|
|
created_at = FlexibleDateTime()
|
|
|
|
|
updated_at = FlexibleDateTime()
|
2016-07-18 12:03:44 +01:00
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-07-18 12:03:44 +01:00
|
|
|
model = models.Service
|
|
|
|
|
exclude = (
|
2023-08-29 14:54:30 -07:00
|
|
|
"all_template_folders",
|
|
|
|
|
"annual_billing",
|
|
|
|
|
"api_keys",
|
|
|
|
|
"created_by",
|
|
|
|
|
"email_branding",
|
|
|
|
|
"email_from",
|
|
|
|
|
"guest_list",
|
|
|
|
|
"inbound_api",
|
|
|
|
|
"inbound_number",
|
|
|
|
|
"inbound_sms",
|
|
|
|
|
"jobs",
|
|
|
|
|
"message_limit",
|
2023-08-31 10:28:44 -04:00
|
|
|
"total_message_limit",
|
2023-08-29 14:54:30 -07:00
|
|
|
"permissions",
|
|
|
|
|
"rate_limit",
|
|
|
|
|
"reply_to_email_addresses",
|
|
|
|
|
"service_sms_senders",
|
|
|
|
|
"templates",
|
|
|
|
|
"users",
|
|
|
|
|
"version",
|
2016-07-18 12:03:44 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2016-04-04 13:13:29 +01:00
|
|
|
class NotificationModelSchema(BaseSchema):
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-04-04 13:13:29 +01:00
|
|
|
model = models.Notification
|
2023-08-29 14:54:30 -07:00
|
|
|
exclude = (
|
|
|
|
|
"_personalisation",
|
|
|
|
|
"job",
|
|
|
|
|
"service",
|
|
|
|
|
"template",
|
|
|
|
|
"api_key",
|
|
|
|
|
)
|
2017-05-10 17:30:09 +01:00
|
|
|
|
2025-05-07 17:48:05 -04:00
|
|
|
status = auto_field(by_value=True)
|
2022-05-13 09:49:18 +01:00
|
|
|
created_at = FlexibleDateTime()
|
|
|
|
|
sent_at = FlexibleDateTime()
|
|
|
|
|
updated_at = FlexibleDateTime()
|
2016-04-04 13:13:29 +01:00
|
|
|
|
|
|
|
|
|
2016-04-08 13:34:46 +01:00
|
|
|
class BaseTemplateSchema(BaseSchema):
|
2017-11-21 14:51:12 +00:00
|
|
|
reply_to = fields.Method("get_reply_to", allow_none=True)
|
2018-01-04 15:56:58 +00:00
|
|
|
reply_to_text = fields.Method("get_reply_to_text", allow_none=True)
|
2025-05-06 13:31:50 -04:00
|
|
|
template_type = auto_field(by_value=True)
|
2017-11-21 14:51:12 +00:00
|
|
|
|
|
|
|
|
def get_reply_to(self, template):
|
|
|
|
|
return template.reply_to
|
|
|
|
|
|
2018-01-04 15:56:58 +00:00
|
|
|
def get_reply_to_text(self, template):
|
|
|
|
|
return template.get_reply_to_text()
|
|
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-01-13 11:04:13 +00:00
|
|
|
model = models.Template
|
2023-03-02 20:20:31 -05:00
|
|
|
exclude = ("service_id", "jobs")
|
2016-01-11 15:07:13 +00:00
|
|
|
|
2016-01-08 17:51:46 +00:00
|
|
|
|
2020-06-18 15:23:43 +01:00
|
|
|
class TemplateSchema(BaseTemplateSchema, UUIDsAsStringsMixin):
|
2023-08-29 14:54:30 -07:00
|
|
|
created_by = field_for(models.Template, "created_by", required=True)
|
2025-05-06 13:31:50 -04:00
|
|
|
process_type = auto_field(by_value=True)
|
2017-06-29 18:16:03 +01:00
|
|
|
redact_personalisation = fields.Method("redact")
|
2022-05-13 09:49:18 +01:00
|
|
|
created_at = FlexibleDateTime()
|
|
|
|
|
updated_at = FlexibleDateTime()
|
2017-06-29 18:16:03 +01:00
|
|
|
|
|
|
|
|
def redact(self, template):
|
|
|
|
|
return template.redact_personalisation
|
2016-04-08 13:34:46 +01:00
|
|
|
|
|
|
|
|
@validates_schema
|
2022-05-06 15:55:53 +01:00
|
|
|
def validate_type(self, data, **kwargs):
|
2024-01-10 12:45:03 -05:00
|
|
|
if data.get("template_type") == TemplateType.EMAIL:
|
2023-08-29 14:54:30 -07:00
|
|
|
subject = data.get("subject")
|
|
|
|
|
if not subject or subject.strip() == "":
|
|
|
|
|
raise ValidationError("Invalid template subject", "subject")
|
2016-04-08 13:34:46 +01:00
|
|
|
|
|
|
|
|
|
2020-06-16 17:10:25 +01:00
|
|
|
class TemplateSchemaNoDetail(TemplateSchema):
|
|
|
|
|
class Meta(TemplateSchema.Meta):
|
|
|
|
|
exclude = TemplateSchema.Meta.exclude + (
|
2023-08-29 14:54:30 -07:00
|
|
|
"archived",
|
|
|
|
|
"created_at",
|
|
|
|
|
"created_by",
|
|
|
|
|
"created_by_id",
|
|
|
|
|
"hidden",
|
|
|
|
|
"process_type",
|
|
|
|
|
"redact_personalisation",
|
|
|
|
|
"reply_to",
|
|
|
|
|
"reply_to_text",
|
|
|
|
|
"service",
|
|
|
|
|
"subject",
|
|
|
|
|
"template_redacted",
|
|
|
|
|
"updated_at",
|
|
|
|
|
"version",
|
2020-06-16 17:10:25 +01:00
|
|
|
)
|
|
|
|
|
|
2022-10-20 20:49:49 +00:00
|
|
|
@pre_dump
|
|
|
|
|
def remove_content_for_non_broadcast_templates(self, template, **kwargs):
|
|
|
|
|
template.content = None
|
|
|
|
|
|
|
|
|
|
return template
|
|
|
|
|
|
2020-06-16 17:10:25 +01:00
|
|
|
|
2016-05-10 14:55:59 +01:00
|
|
|
class TemplateHistorySchema(BaseSchema):
|
2018-02-14 11:20:09 +00:00
|
|
|
reply_to = fields.Method("get_reply_to", allow_none=True)
|
|
|
|
|
reply_to_text = fields.Method("get_reply_to_text", allow_none=True)
|
2025-05-28 14:01:55 -04:00
|
|
|
process_type = SafeEnumField(TemplateProcessType, by_value=True)
|
2025-05-02 14:56:46 -04:00
|
|
|
template_type = auto_field(by_value=True)
|
2017-11-21 14:51:12 +00:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
created_by = fields.Nested(
|
|
|
|
|
UserSchema, only=["id", "name", "email_address"], dump_only=True
|
|
|
|
|
)
|
|
|
|
|
created_at = field_for(
|
|
|
|
|
models.Template, "created_at", format=DATETIME_FORMAT_NO_TIMEZONE
|
|
|
|
|
)
|
2022-05-13 09:49:18 +01:00
|
|
|
updated_at = FlexibleDateTime()
|
2016-05-10 14:55:59 +01:00
|
|
|
|
2017-11-21 14:51:12 +00:00
|
|
|
def get_reply_to(self, template):
|
|
|
|
|
return template.reply_to
|
|
|
|
|
|
2018-02-14 11:20:09 +00:00
|
|
|
def get_reply_to_text(self, template):
|
|
|
|
|
return template.get_reply_to_text()
|
|
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-08-08 16:57:39 +01:00
|
|
|
model = models.TemplateHistory
|
2016-05-06 15:42:43 +01:00
|
|
|
|
|
|
|
|
|
2016-04-29 12:24:43 +01:00
|
|
|
class ApiKeySchema(BaseSchema):
|
2023-08-29 14:54:30 -07:00
|
|
|
created_by = field_for(models.ApiKey, "created_by", required=True)
|
2025-05-06 13:31:50 -04:00
|
|
|
key_type = auto_field(by_value=True)
|
2022-05-13 09:49:18 +01:00
|
|
|
expiry_date = FlexibleDateTime()
|
|
|
|
|
created_at = FlexibleDateTime()
|
|
|
|
|
updated_at = FlexibleDateTime()
|
2016-04-29 12:24:43 +01:00
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-01-19 12:07:00 +00:00
|
|
|
model = models.ApiKey
|
2017-06-19 14:32:22 +01:00
|
|
|
exclude = ("service", "_secret")
|
2016-01-14 11:30:45 +00:00
|
|
|
|
2016-01-13 09:25:46 +00:00
|
|
|
|
2016-01-29 11:11:00 +00:00
|
|
|
class JobSchema(BaseSchema):
|
2023-08-29 14:54:30 -07:00
|
|
|
created_by_user = fields.Nested(
|
|
|
|
|
UserSchema,
|
|
|
|
|
attribute="created_by",
|
|
|
|
|
data_key="created_by",
|
|
|
|
|
only=["id", "name"],
|
|
|
|
|
dump_only=True,
|
|
|
|
|
)
|
|
|
|
|
created_by = field_for(models.Job, "created_by", required=True, load_only=True)
|
2022-05-13 09:49:18 +01:00
|
|
|
created_at = FlexibleDateTime()
|
|
|
|
|
updated_at = FlexibleDateTime()
|
|
|
|
|
processing_started = FlexibleDateTime()
|
|
|
|
|
processing_finished = FlexibleDateTime()
|
2016-05-11 12:03:25 +01:00
|
|
|
|
2025-05-02 14:56:46 -04:00
|
|
|
job_status = auto_field(by_value=True)
|
2016-08-24 14:35:22 +01:00
|
|
|
|
2022-05-13 09:49:18 +01:00
|
|
|
scheduled_for = FlexibleDateTime()
|
2017-04-07 14:36:00 +01:00
|
|
|
service_name = fields.Nested(
|
2023-08-29 14:54:30 -07:00
|
|
|
ServiceSchema,
|
|
|
|
|
attribute="service",
|
|
|
|
|
data_key="service_name",
|
|
|
|
|
only=["name"],
|
|
|
|
|
dump_only=True,
|
|
|
|
|
)
|
2016-08-24 16:00:21 +01:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
template_name = fields.Method("get_template_name", dump_only=True)
|
|
|
|
|
template_type = fields.Method("get_template_type", dump_only=True)
|
2020-02-25 16:05:39 +00:00
|
|
|
|
2020-05-12 12:56:01 +01:00
|
|
|
def get_template_name(self, job):
|
|
|
|
|
return job.template.name
|
|
|
|
|
|
2020-02-25 16:05:39 +00:00
|
|
|
def get_template_type(self, job):
|
2025-05-02 14:56:46 -04:00
|
|
|
return job.template.template_type.value
|
2020-02-25 16:05:39 +00:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("scheduled_for")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_scheduled_for(self, value, data_key):
|
2016-08-30 12:47:33 +01:00
|
|
|
_validate_datetime_not_in_past(value)
|
2016-10-12 07:45:31 +01:00
|
|
|
_validate_datetime_not_more_than_96_hours_in_future(value)
|
2016-08-24 16:00:21 +01:00
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-01-15 15:48:05 +00:00
|
|
|
model = models.Job
|
2016-08-23 16:46:58 +01:00
|
|
|
exclude = (
|
2023-08-29 14:54:30 -07:00
|
|
|
"notifications",
|
|
|
|
|
"notifications_delivered",
|
|
|
|
|
"notifications_failed",
|
|
|
|
|
"notifications_sent",
|
2021-01-19 14:00:03 +00:00
|
|
|
)
|
2016-01-15 15:48:05 +00:00
|
|
|
|
|
|
|
|
|
2025-05-16 16:23:59 -04:00
|
|
|
class NotificationSchema(Schema):
|
2022-05-06 15:56:49 +01:00
|
|
|
class Meta:
|
|
|
|
|
unknown = EXCLUDE
|
|
|
|
|
|
2025-05-07 17:48:05 -04:00
|
|
|
status = fields.Enum(NotificationStatus, by_value=True, required=False)
|
2016-02-29 11:23:34 +00:00
|
|
|
personalisation = fields.Dict(required=False)
|
2016-02-03 13:16:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class SmsNotificationSchema(NotificationSchema):
|
|
|
|
|
to = fields.Str(required=True)
|
|
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("to")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_to(self, value, data_key):
|
2016-03-06 12:58:28 +00:00
|
|
|
try:
|
2017-04-26 15:56:45 +01:00
|
|
|
validate_phone_number(value, international=True)
|
2016-03-06 12:58:28 +00:00
|
|
|
except InvalidPhoneError as error:
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(
|
|
|
|
|
f"{data_key}: Invalid phone number ({str(error)}"
|
|
|
|
|
)
|
|
|
|
|
raise ValidationError(f"Invalid phone number: {str(error)}")
|
2016-02-03 13:16:19 +00:00
|
|
|
|
2016-03-06 13:21:46 +00:00
|
|
|
@post_load
|
2022-05-06 15:55:53 +01:00
|
|
|
def format_phone_number(self, item, **kwargs):
|
2023-08-29 14:54:30 -07:00
|
|
|
item["to"] = validate_and_format_phone_number(item["to"], international=True)
|
2016-03-06 13:21:46 +00:00
|
|
|
return item
|
2016-02-03 13:16:19 +00:00
|
|
|
|
|
|
|
|
|
2016-02-22 17:17:29 +00:00
|
|
|
class EmailNotificationSchema(NotificationSchema):
|
|
|
|
|
to = fields.Str(required=True)
|
2016-04-08 13:34:46 +01:00
|
|
|
template = fields.Str(required=True)
|
2016-02-22 17:17:29 +00:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("to")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_to(self, value, data_key):
|
2016-03-06 12:58:28 +00:00
|
|
|
try:
|
|
|
|
|
validate_email_address(value)
|
2016-03-08 17:46:00 +00:00
|
|
|
except InvalidEmailError as e:
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(f"{data_key}: {str(e)}")
|
2025-05-09 15:14:07 -04:00
|
|
|
raise ValidationError(str(e))
|
2016-02-22 17:17:29 +00:00
|
|
|
|
|
|
|
|
|
2016-02-03 13:16:19 +00:00
|
|
|
class SmsTemplateNotificationSchema(SmsNotificationSchema):
|
2016-04-08 13:34:46 +01:00
|
|
|
template = fields.Str(required=True)
|
2016-02-08 14:54:15 +00:00
|
|
|
job = fields.String()
|
2016-02-03 13:16:19 +00:00
|
|
|
|
|
|
|
|
|
2016-07-26 12:34:39 +01:00
|
|
|
class NotificationWithTemplateSchema(BaseSchema):
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2025-05-09 15:14:07 -04:00
|
|
|
unknown = EXCLUDE
|
2016-07-26 14:33:14 +01:00
|
|
|
model = models.Notification
|
2023-08-29 14:54:30 -07:00
|
|
|
exclude = ("_personalisation",)
|
2016-02-09 14:17:42 +00:00
|
|
|
|
2016-08-09 16:53:09 +01:00
|
|
|
template = fields.Nested(
|
2023-03-02 20:20:31 -05:00
|
|
|
TemplateSchema,
|
2018-03-05 18:47:45 +00:00
|
|
|
only=[
|
2023-08-29 14:54:30 -07:00
|
|
|
"id",
|
|
|
|
|
"version",
|
|
|
|
|
"name",
|
|
|
|
|
"template_type",
|
|
|
|
|
"content",
|
|
|
|
|
"subject",
|
|
|
|
|
"redact_personalisation",
|
2018-03-05 18:47:45 +00:00
|
|
|
],
|
2023-08-29 14:54:30 -07:00
|
|
|
dump_only=True,
|
2016-08-09 16:53:09 +01:00
|
|
|
)
|
2025-05-07 17:48:05 -04:00
|
|
|
template_version = fields.Integer()
|
2016-03-16 16:47:18 +00:00
|
|
|
job = fields.Nested(JobSchema, only=["id", "original_file_name"], dump_only=True)
|
2023-08-29 14:54:30 -07:00
|
|
|
created_by = fields.Nested(
|
|
|
|
|
UserSchema, only=["id", "name", "email_address"], dump_only=True
|
|
|
|
|
)
|
2025-05-07 17:48:05 -04:00
|
|
|
status = auto_field(by_value=True)
|
2016-06-20 16:23:56 +01:00
|
|
|
personalisation = fields.Dict(required=False)
|
2025-05-06 13:31:50 -04:00
|
|
|
notification_type = auto_field(by_value=True)
|
|
|
|
|
key_type = auto_field(by_value=True)
|
2016-09-23 14:44:15 +01:00
|
|
|
key_name = fields.String()
|
2022-05-13 09:49:18 +01:00
|
|
|
created_at = FlexibleDateTime()
|
|
|
|
|
updated_at = FlexibleDateTime()
|
|
|
|
|
sent_at = FlexibleDateTime()
|
2016-09-23 14:44:15 +01:00
|
|
|
|
|
|
|
|
@pre_dump
|
2022-05-06 15:55:53 +01:00
|
|
|
def add_api_key_name(self, in_data, **kwargs):
|
2016-09-23 14:44:15 +01:00
|
|
|
if in_data.api_key:
|
|
|
|
|
in_data.key_name = in_data.api_key.name
|
|
|
|
|
else:
|
|
|
|
|
in_data.key_name = None
|
|
|
|
|
return in_data
|
2016-03-15 14:24:10 +00:00
|
|
|
|
2016-06-20 16:23:56 +01:00
|
|
|
|
2016-02-24 14:01:19 +00:00
|
|
|
class InvitedUserSchema(BaseSchema):
|
2025-05-07 18:43:54 -04:00
|
|
|
auth_type = auto_field(by_value=True)
|
2022-05-13 09:49:18 +01:00
|
|
|
created_at = FlexibleDateTime()
|
2025-05-07 18:43:54 -04:00
|
|
|
status = auto_field(by_value=True)
|
2016-02-29 09:49:12 +00:00
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-02-24 14:01:19 +00:00
|
|
|
model = models.InvitedUser
|
|
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("email_address")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_to(self, value, data_key):
|
2016-03-06 12:58:28 +00:00
|
|
|
try:
|
|
|
|
|
validate_email_address(value)
|
2016-03-08 17:46:00 +00:00
|
|
|
except InvalidEmailError as e:
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(f"{data_key}: {str(e)}")
|
2025-05-09 15:14:07 -04:00
|
|
|
raise ValidationError(str(e))
|
2016-02-24 14:01:19 +00:00
|
|
|
|
|
|
|
|
|
2025-05-16 16:23:59 -04:00
|
|
|
class EmailDataSchema(Schema):
|
2022-05-06 15:56:49 +01:00
|
|
|
class Meta:
|
|
|
|
|
unknown = EXCLUDE
|
|
|
|
|
|
2016-07-08 10:57:20 +01:00
|
|
|
email = fields.Str(required=True)
|
2022-05-06 15:56:49 +01:00
|
|
|
next = fields.Str(required=False)
|
|
|
|
|
admin_base_url = fields.Str(required=False)
|
2016-03-07 15:21:05 +00:00
|
|
|
|
2018-07-09 17:25:13 +01:00
|
|
|
def __init__(self, partial_email=False):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.partial_email = partial_email
|
|
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("email")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_email(self, value, data_key):
|
2018-07-09 17:25:13 +01:00
|
|
|
if self.partial_email:
|
|
|
|
|
return
|
2016-03-08 15:47:35 +00:00
|
|
|
try:
|
|
|
|
|
validate_email_address(value)
|
2016-03-08 17:46:00 +00:00
|
|
|
except InvalidEmailError as e:
|
2025-05-14 11:20:45 -04:00
|
|
|
current_app.logger.exception(f"{data_key}: {str(e)}")
|
2025-05-09 15:14:07 -04:00
|
|
|
raise ValidationError(str(e))
|
2016-03-07 15:21:05 +00:00
|
|
|
|
2016-03-21 12:37:34 +00:00
|
|
|
|
2025-05-16 16:23:59 -04:00
|
|
|
class NotificationsFilterSchema(Schema):
|
2022-05-06 15:56:49 +01:00
|
|
|
class Meta:
|
|
|
|
|
unknown = EXCLUDE
|
|
|
|
|
|
2025-05-16 16:23:59 -04:00
|
|
|
template_type = fields.Nested(TemplateTypeFieldOnlySchema, many=True)
|
|
|
|
|
status = fields.Nested(NotificationStatusFieldOnlySchema, many=True)
|
2016-03-21 12:37:34 +00:00
|
|
|
page = fields.Int(required=False)
|
2016-04-19 10:52:52 +01:00
|
|
|
page_size = fields.Int(required=False)
|
2016-04-28 16:10:35 +01:00
|
|
|
limit_days = fields.Int(required=False)
|
2016-09-15 15:59:34 +01:00
|
|
|
include_jobs = fields.Boolean(required=False)
|
2016-09-23 10:35:31 +01:00
|
|
|
include_from_test_key = fields.Boolean(required=False)
|
2016-11-23 11:44:38 +00:00
|
|
|
older_than = fields.UUID(required=False)
|
2025-05-28 13:47:35 -04:00
|
|
|
format_for_csv = fields.Boolean()
|
2017-05-08 17:20:21 +01:00
|
|
|
to = fields.String()
|
2018-07-18 10:54:20 +01:00
|
|
|
include_one_off = fields.Boolean(required=False)
|
2019-01-07 17:12:00 +00:00
|
|
|
count_pages = fields.Boolean(required=False)
|
2016-03-21 12:37:34 +00:00
|
|
|
|
2016-04-04 13:13:29 +01:00
|
|
|
@pre_load
|
2022-05-06 15:55:53 +01:00
|
|
|
def handle_multidict(self, in_data, **kwargs):
|
2025-05-28 13:47:35 -04:00
|
|
|
out_data = dict(in_data)
|
|
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
if isinstance(in_data, dict) and hasattr(in_data, "getlist"):
|
|
|
|
|
if "template_type" in in_data:
|
|
|
|
|
out_data["template_type"] = [
|
|
|
|
|
{"template_type": x} for x in in_data.getlist("template_type")
|
|
|
|
|
]
|
|
|
|
|
if "status" in in_data:
|
|
|
|
|
out_data["status"] = [{"status": x} for x in in_data.getlist("status")]
|
2016-04-04 13:13:29 +01:00
|
|
|
|
|
|
|
|
return out_data
|
|
|
|
|
|
|
|
|
|
@post_load
|
2022-05-06 15:55:53 +01:00
|
|
|
def convert_schema_object_to_field(self, in_data, **kwargs):
|
2023-08-29 14:54:30 -07:00
|
|
|
if "template_type" in in_data:
|
|
|
|
|
in_data["template_type"] = [
|
2025-05-16 16:23:59 -04:00
|
|
|
x["template_type"] for x in in_data["template_type"]
|
2023-08-29 14:54:30 -07:00
|
|
|
]
|
|
|
|
|
if "status" in in_data:
|
2025-05-16 16:23:59 -04:00
|
|
|
in_data["status"] = [x["status"] for x in in_data["status"]]
|
2016-04-04 13:13:29 +01:00
|
|
|
return in_data
|
|
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("page")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_page(self, value, data_key):
|
2016-05-05 10:45:47 +01:00
|
|
|
_validate_positive_number(value)
|
2016-04-19 10:52:52 +01:00
|
|
|
|
2023-08-29 14:54:30 -07:00
|
|
|
@validates("page_size")
|
2025-05-05 15:29:24 -04:00
|
|
|
def validate_page_size(self, value, data_key):
|
2016-05-05 10:45:47 +01:00
|
|
|
_validate_positive_number(value)
|
2016-04-19 10:52:52 +01:00
|
|
|
|
2016-04-04 13:13:29 +01:00
|
|
|
|
2025-05-16 16:23:59 -04:00
|
|
|
class ServiceHistorySchema(Schema):
|
2022-05-06 15:56:49 +01:00
|
|
|
class Meta:
|
|
|
|
|
unknown = EXCLUDE
|
|
|
|
|
|
2016-04-21 16:32:20 +01:00
|
|
|
id = fields.UUID()
|
|
|
|
|
name = fields.String()
|
2022-05-13 09:49:18 +01:00
|
|
|
created_at = FlexibleDateTime()
|
|
|
|
|
updated_at = FlexibleDateTime()
|
2016-04-21 16:32:20 +01:00
|
|
|
active = fields.Boolean()
|
|
|
|
|
message_limit = fields.Integer()
|
2023-04-28 12:37:06 -07:00
|
|
|
total_message_limit = fields.Integer()
|
2016-04-21 16:32:20 +01:00
|
|
|
restricted = fields.Boolean()
|
|
|
|
|
email_from = fields.String()
|
|
|
|
|
created_by_id = fields.UUID()
|
|
|
|
|
version = fields.Integer()
|
|
|
|
|
|
|
|
|
|
|
2025-05-16 16:23:59 -04:00
|
|
|
class ApiKeyHistorySchema(Schema):
|
2022-05-06 15:56:49 +01:00
|
|
|
class Meta:
|
|
|
|
|
unknown = EXCLUDE
|
|
|
|
|
|
2016-04-21 16:32:20 +01:00
|
|
|
id = fields.UUID()
|
|
|
|
|
name = fields.String()
|
|
|
|
|
service_id = fields.UUID()
|
2022-05-13 09:49:18 +01:00
|
|
|
expiry_date = FlexibleDateTime()
|
|
|
|
|
created_at = FlexibleDateTime()
|
|
|
|
|
updated_at = FlexibleDateTime()
|
2016-04-21 16:32:20 +01:00
|
|
|
created_by_id = fields.UUID()
|
|
|
|
|
|
|
|
|
|
|
2016-04-27 10:27:05 +01:00
|
|
|
class EventSchema(BaseSchema):
|
2022-05-13 09:49:18 +01:00
|
|
|
created_at = FlexibleDateTime()
|
|
|
|
|
|
2020-12-31 14:04:38 +00:00
|
|
|
class Meta(BaseSchema.Meta):
|
2016-04-27 10:27:05 +01:00
|
|
|
model = models.Event
|
|
|
|
|
|
|
|
|
|
|
2016-05-23 15:44:56 +01:00
|
|
|
class UnarchivedTemplateSchema(BaseSchema):
|
|
|
|
|
archived = fields.Boolean(required=True)
|
|
|
|
|
|
|
|
|
|
@validates_schema
|
2022-05-06 15:55:53 +01:00
|
|
|
def validate_archived(self, data, **kwargs):
|
2023-08-29 14:54:30 -07:00
|
|
|
if data["archived"]:
|
|
|
|
|
raise ValidationError("Template has been deleted", "template")
|
2016-05-23 15:44:56 +01:00
|
|
|
|
|
|
|
|
|
2018-03-06 17:47:29 +00:00
|
|
|
# should not be used on its own for dumping - only for loading
|
|
|
|
|
create_user_schema = UserSchema()
|
2016-11-07 17:41:49 +00:00
|
|
|
user_update_schema_load_json = UserUpdateAttributeSchema(load_json=True, partial=True)
|
2023-08-29 14:54:30 -07:00
|
|
|
user_update_password_schema_load_json = UserUpdatePasswordSchema(
|
|
|
|
|
only=("_password",), load_json=True, partial=True
|
|
|
|
|
)
|
2016-01-08 17:51:46 +00:00
|
|
|
service_schema = ServiceSchema()
|
2016-07-18 12:03:44 +01:00
|
|
|
detailed_service_schema = DetailedServiceSchema()
|
2016-01-13 11:04:13 +00:00
|
|
|
template_schema = TemplateSchema()
|
2020-06-16 17:10:25 +01:00
|
|
|
template_schema_no_detail = TemplateSchemaNoDetail()
|
2016-01-19 12:07:00 +00:00
|
|
|
api_key_schema = ApiKeySchema()
|
2016-02-03 13:16:19 +00:00
|
|
|
sms_template_notification_schema = SmsTemplateNotificationSchema()
|
|
|
|
|
email_notification_schema = EmailNotificationSchema()
|
2016-07-26 12:34:39 +01:00
|
|
|
notification_schema = NotificationModelSchema()
|
|
|
|
|
notification_with_template_schema = NotificationWithTemplateSchema()
|
2016-02-24 14:01:19 +00:00
|
|
|
invited_user_schema = InvitedUserSchema()
|
2016-03-07 15:21:05 +00:00
|
|
|
email_data_request_schema = EmailDataSchema()
|
2018-07-09 17:25:13 +01:00
|
|
|
partial_email_data_request_schema = EmailDataSchema(partial_email=True)
|
2016-03-21 12:37:34 +00:00
|
|
|
notifications_filter_schema = NotificationsFilterSchema()
|
2016-04-21 16:32:20 +01:00
|
|
|
service_history_schema = ServiceHistorySchema()
|
|
|
|
|
api_key_history_schema = ApiKeyHistorySchema()
|
2016-04-25 10:38:37 +01:00
|
|
|
template_history_schema = TemplateHistorySchema()
|
2016-04-27 10:27:05 +01:00
|
|
|
event_schema = EventSchema()
|
2016-05-10 09:04:22 +01:00
|
|
|
provider_details_schema = ProviderDetailsSchema()
|
2017-03-02 18:10:33 +00:00
|
|
|
provider_details_history_schema = ProviderDetailsHistorySchema()
|