示例#1
0
class FuncKeyTemplateUserSchema(BaseSchema):
    user_id = fields.Integer(attribute='id')
    template_id = fields.Integer(attribute='func_key_template_id')
    links = ListLink(
        Link('func_keys_templates', field='func_key_template_id', target='id'),
        Link('users', field='id'),
    )
示例#2
0
class UserVoicemailSchema(BaseSchema):
    user_id = fields.Integer(dump_only=True)
    voicemail_id = fields.Integer(required=True)
    links = ListLink(
        Link('voicemails', field='voicemail_id', target='id'),
        Link('users', field='user_id', target='id'),
    )
示例#3
0
class LineDeviceSchema(BaseSchema):
    line_id = fields.Integer()
    device_id = fields.String()
    links = ListLink(
        Link('lines', field='line_id', target='id'),
        Link('devices', field='device_id', target='id'),
    )
示例#4
0
class LineExtensionLegacySchema(BaseSchema):
    line_id = fields.Integer(dump_only=True)
    extension_id = fields.Integer(required=True)
    links = ListLink(
        Link('lines', field='line_id', target='id'),
        Link('extensions', field='extension_id', target='id'),
    )
示例#5
0
class UserCallPermissionSchema(BaseSchema):
    user_id = fields.Integer()
    call_permission_id = fields.Integer()
    links = ListLink(
        Link('users', field='user_id', target='id'),
        Link('callpermissions', field='call_permission_id', target='id'),
    )
示例#6
0
class LineSipSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    username = fields.String(validate=Regexp(USERNAME_REGEX))
    secret = fields.String(validate=Regexp(SECRET_REGEX))
    callerid = fields.String(validate=Regexp(CALLERID_REGEX), allow_none=True)
    device_slot = fields.Integer(validate=Range(min=0))
    context = fields.String(required=True)
    provisioning_extension = fields.String(
        validate=(Length(equal=6), Predicate('isdigit'))
    )
    links = ListLink(Link('lines'), Link('lines_sip'))
示例#7
0
class VoicemailSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(validate=Length(max=80), required=True)
    number = fields.String(validate=Regexp(NUMBER_REGEX), required=True)
    context = fields.String(required=True)
    password = fields.String(validate=Regexp(PASSWORD_REGEX), allow_none=True)
    email = fields.String(validate=Length(max=80), allow_none=True)
    language = fields.String(validate=Regexp(LANGUAGE_REGEX), allow_none=True)
    timezone = fields.String(allow_none=True)
    pager = fields.String(validate=Length(max=80), allow_none=True)
    max_messages = fields.Integer(validate=Range(min=0), allow_none=True)
    attach_audio = StrictBoolean(allow_none=True)
    delete_messages = StrictBoolean()
    ask_password = StrictBoolean()
    enabled = StrictBoolean()
    options = fields.List(
        fields.List(fields.String(), validate=Length(equal=2)))
    links = ListLink(Link('voicemails'))

    users = Nested(
        'UserSchema',
        only=['uuid', 'firstname', 'lastname', 'links'],
        many=True,
        dump_only=True,
    )
示例#8
0
class ExternalAppSchema(BaseSchema):
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(dump_only=True)
    label = fields.String(validate=Length(max=256), allow_none=True)
    configuration = fields.Dict(allow_none=True)

    links = ListLink(Link('external_apps', field='name'))
示例#9
0
class ExtensionSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    exten = fields.String(validate=Length(max=40), required=True)
    context = fields.String(required=True)
    commented = fields.Boolean(attribute='legacy_commented')
    enabled = fields.Boolean()
    links = ListLink(Link('extensions'))

    conference = Nested('ConferenceSchema',
                        only=['id', 'name', 'links'],
                        dump_only=True)
    parking_lot = Nested('ParkingLotSchema',
                         only=['id', 'name', 'links'],
                         dump_only=True)
    group = Nested('GroupSchema',
                   only=['uuid', 'id', 'name', 'links'],
                   dump_only=True)
    incall = Nested('IncallSchema', only=['id', 'links'], dump_only=True)
    lines = Nested('LineSchema',
                   only=['id', 'name', 'links'],
                   many=True,
                   dump_only=True)
    outcall = Nested('OutcallSchema',
                     only=['id', 'name', 'links'],
                     dump_only=True)
    queue = Nested('QueueSchema',
                   only=['id', 'name', 'label', 'links'],
                   dump_only=True)
示例#10
0
class ConferenceSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(allow_none=True, validate=Length(max=128))
    preprocess_subroutine = fields.String(allow_none=True,
                                          validate=Length(max=39))
    max_users = fields.Integer(validate=Range(min=0))
    record = fields.Boolean()
    pin = fields.String(allow_none=True,
                        validate=(Length(max=80), Predicate('isdigit')))
    admin_pin = fields.String(allow_none=True,
                              validate=(Length(max=80), Predicate('isdigit')))
    quiet_join_leave = fields.Boolean()
    announce_join_leave = fields.Boolean()
    announce_user_count = fields.Boolean()
    announce_only_user = fields.Boolean()
    music_on_hold = fields.String(allow_none=True, validate=Length(max=128))
    links = ListLink(Link('conferences'))

    extensions = Nested(
        'ExtensionSchema',
        only=['id', 'exten', 'context', 'links'],
        many=True,
        dump_only=True,
    )
    incalls = Nested('IncallSchema',
                     only=['id', 'extensions', 'links'],
                     many=True,
                     dump_only=True)
示例#11
0
class TrunkSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    context = fields.String(allow_none=True)
    twilio_incoming = StrictBoolean(allow_none=True)
    links = ListLink(Link('trunks'))

    endpoint_sip = Nested(
        'EndpointSIPSchema',
        only=[
            'uuid',
            'label',
            'name',
            'auth_section_options.username',
            'registration_section_options.client_uri',
            'links',
        ],
        dump_only=True,
    )
    endpoint_custom = Nested(
        'CustomSchema', only=['id', 'interface', 'links'], dump_only=True
    )
    endpoint_iax = Nested('IAXSchema', only=['id', 'name', 'links'], dump_only=True)
    outcalls = Nested(
        'OutcallSchema', only=['id', 'name', 'links'], many=True, dump_only=True
    )
    register_iax = Nested('RegisterIAXSchema', only=['id', 'links'], dump_only=True)
示例#12
0
class ApplicationSchema(BaseSchema):
    uuid = fields.String(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(validate=Length(max=128), allow_none=True)
    destination = fields.String(
        validate=OneOf(ApplicationDestinationOptionsField._options.keys()),
        allow_none=True,
        default=None,
    )
    destination_options = ApplicationDestinationOptionsField(default={})
    links = ListLink(
        Link('applications', field='uuid', target='application_uuid'))

    lines = Nested('LineSchema',
                   only=['id', 'name', 'links'],
                   many=True,
                   dump_only=True)

    @pre_dump
    def map_destination(self, obj, **kwargs):
        if obj.dest_node:
            obj.destination = 'node'
            obj.destination_options = obj.dest_node
        return obj

    @post_load
    def create_objects(self, data, **kwargs):
        dest = data.pop('destination', None)
        dest_options = data.pop('destination_options', {})
        data['dest_node'] = None
        if dest == 'node':
            data['dest_node'] = ApplicationDestNode(**dest_options)
        return data
示例#13
0
class ParkingLotSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(allow_none=True, validate=Length(max=128))
    slots_start = fields.String(validate=(Length(max=40),
                                          Predicate('isdigit')),
                                required=True)
    slots_end = fields.String(validate=(Length(max=40), Predicate('isdigit')),
                              required=True)
    timeout = fields.Integer(validate=Range(min=0),
                             allow_none=True,
                             missing=45)
    music_on_hold = fields.String(validate=Length(max=128),
                                  allow_none=True,
                                  missing='default')
    links = ListLink(Link('parkinglots'))

    extensions = Nested(
        'ExtensionSchema',
        only=['id', 'exten', 'context', 'links'],
        many=True,
        dump_only=True,
    )

    @validates_schema
    def validate_slots_range(self, data, **kwargs):
        # validates_schema is executed before fields validator, so the required
        # fields is not yet checked
        if not data.get('slots_start') or not data.get('slots_end'):
            return

        if int(data['slots_start']) > int(data['slots_end']):
            raise ValidationError('It is not a valid range')
示例#14
0
class MohSchema(BaseSchema):
    uuid = fields.UUID(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(dump_only=True)
    label = fields.String(validate=Length(max=128), required=True)
    mode = fields.String(validate=OneOf(['custom', 'files', 'mp3']),
                         required=True)
    application = fields.String(validate=Length(max=256), allow_none=True)
    sort = fields.String(validate=OneOf(
        ['alphabetical', 'random', 'random_start']),
                         allow_none=True)
    files = Nested(MohFileSchema, many=True, dump_only=True)

    links = ListLink(Link('moh', field='uuid'))

    # DEPRECATED 21.15
    @pre_load
    def copy_name_to_label(self, data, **kwargs):
        if 'label' in data:
            return data
        if 'name' in data:
            logger.warning(
                'the "name" field of moh is deprecated. use "label" instead')
            data['label'] = data['name']
        return data
示例#15
0
class AgentSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    number = fields.String(validate=Regexp(NUMBER_REGEX), required=True)
    firstname = fields.String(validate=Length(max=128), allow_none=True)
    lastname = fields.String(validate=Length(max=128), allow_none=True)
    password = fields.String(validate=Length(max=128),
                             allow_none=True,
                             attribute='passwd')
    language = fields.String(validate=Length(max=20), allow_none=True)
    preprocess_subroutine = fields.String(validate=Length(max=39),
                                          allow_none=True)
    description = fields.String(allow_none=True)
    links = ListLink(Link('agents'))

    queues = fields.Nested(
        'AgentQueuesMemberSchema',
        attribute='queue_queue_members',
        many=True,
        dump_only=True,
    )
    skills = fields.Nested('AgentSkillsSchema',
                           attribute='agent_queue_skills',
                           many=True,
                           dump_only=True)
    users = fields.Nested(
        'UserSchema',
        only=['uuid', 'firstname', 'lastname', 'links'],
        many=True,
        dump_only=True,
    )
示例#16
0
class SwitchboardSchema(BaseSchema):
    uuid = fields.UUID(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(validate=Length(max=128), required=True)
    timeout = fields.Integer(validate=Range(min=1), allow_none=True)
    queue_music_on_hold = fields.String(validate=Length(max=128), allow_none=True)
    waiting_room_music_on_hold = fields.String(
        validate=Length(max=128), allow_none=True
    )
    links = ListLink(Link('switchboards', field='uuid'))
    extensions = Nested(
        'ExtensionSchema',
        only=['id', 'exten', 'context', 'links'],
        many=True,
        dump_only=True,
    )
    incalls = Nested(
        'IncallSchema', only=['id', 'extensions', 'links'], many=True, dump_only=True
    )

    user_members = Nested(
        'UserSchema',
        only=['uuid', 'firstname', 'lastname', 'links'],
        many=True,
        dump_only=True,
    )
    fallbacks = Nested('SwitchboardFallbackSchema', dump_only=True)

    @post_dump
    def wrap_users(self, data, **kwargs):
        user_members = data.pop('user_members', [])
        if not self.only or 'members' in self.only:
            data['members'] = {'users': user_members}
        return data
示例#17
0
class OutcallSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(validate=Length(max=128), required=True)
    internal_caller_id = StrictBoolean()
    preprocess_subroutine = fields.String(validate=Length(max=39),
                                          allow_none=True)
    ring_time = fields.Integer(validate=Range(min=0), allow_none=True)
    description = fields.String(allow_none=True)
    enabled = StrictBoolean()
    links = ListLink(Link('outcalls'))
    trunks = Nested(
        'TrunkSchema',
        only=['tenant_uuid', 'id', 'endpoint_sip', 'endpoint_custom', 'links'],
        many=True,
        dump_only=True,
    )
    extensions = Nested('DialPatternSchema',
                        attribute='dialpatterns',
                        many=True,
                        dump_only=True)
    schedules = Nested(
        'ScheduleSchema',
        only=['tenant_uuid', 'id', 'name', 'links'],
        many=True,
        dump_only=True,
    )
    call_permissions = Nested(
        'CallPermissionSchema',
        only=['tenant_uuid', 'id', 'name', 'links'],
        many=True,
        dump_only=True,
    )
示例#18
0
class SipSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    username = fields.String(validate=Regexp(USERNAME_REGEX))
    name = fields.String(validate=Regexp(USERNAME_REGEX))
    secret = fields.String(validate=Regexp(SECRET_REGEX))
    type = fields.String(validate=OneOf(['friend', 'peer', 'user']))
    host = fields.String(validate=Length(max=255))
    options = fields.List(
        fields.List(fields.String(), validate=Length(equal=2)))
    links = ListLink(Link('endpoint_sip'))

    trunk = fields.Nested('TrunkSchema', only=['id', 'links'], dump_only=True)
    line = fields.Nested('LineSchema', only=['id', 'links'], dump_only=True)

    # The set_name_to_username_if_missing method is a compatibility method that
    # was added in 19.15 to avoid breaking the API. In the old version, the name
    # could not be specified in the API the username was always copied.
    @post_load
    def set_name_to_username_if_missing(self, data, **kwargs):
        name = data.get('name')
        if not name and 'username' in data:
            logger.warning(
                'DEPRECATION: creating a SIP endpoint with a "username" and no "name" is'
                ' deprecated. Populate the name field if it is required')
            data['name'] = data['username']
        return data
示例#19
0
class CallPermissionSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(validate=Length(min=1, max=128), required=True)
    password = fields.String(validate=Regexp(PASSWORD_REGEX), allow_none=True)
    mode = fields.String(validate=OneOf(['allow', 'deny']))
    extensions = fields.List(fields.String(validate=Regexp(EXTENSION_REGEX)))
    enabled = StrictBoolean()
    description = fields.String(allow_none=True)
    links = ListLink(Link('callpermissions'))

    outcalls = Nested('OutcallSchema',
                      only=['id', 'name', 'links'],
                      many=True,
                      dump_only=True)
    groups = Nested('GroupSchema',
                    only=['uuid', 'id', 'name', 'links'],
                    many=True,
                    dump_only=True)
    users = Nested(
        'UserSchema',
        only=['uuid', 'firstname', 'lastname', 'links'],
        many=True,
        dump_only=True,
    )
示例#20
0
class TrunkSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    context = fields.String(allow_none=True)
    twilio_incoming = StrictBoolean(allow_none=True)
    links = ListLink(Link('trunks'))

    endpoint_sip = fields.Nested('SipSchema',
                                 only=['id', 'username', 'links'],
                                 dump_only=True)
    endpoint_custom = fields.Nested('CustomSchema',
                                    only=['id', 'interface', 'links'],
                                    dump_only=True)
    endpoint_iax = fields.Nested('IAXSchema',
                                 only=['id', 'name', 'links'],
                                 dump_only=True)
    outcalls = fields.Nested('OutcallSchema',
                             only=['id', 'name', 'links'],
                             many=True,
                             dump_only=True)
    register_iax = fields.Nested('RegisterIAXSchema',
                                 only=['id', 'links'],
                                 dump_only=True)
    register_sip = fields.Nested('RegisterSIPSchema',
                                 only=['id', 'links'],
                                 dump_only=True)
示例#21
0
class ExtensionFeatureSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    exten = fields.String(validate=Regexp(EXTEN_REGEX), required=True)
    context = fields.String(dump_only=True)
    feature = fields.String(attribute='typeval', dump_only=True)
    enabled = fields.Boolean()
    links = ListLink(Link('extensions_features'))
示例#22
0
class SccpSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    options = fields.List(fields.List(fields.String(), validate=Length(equal=2)))
    links = ListLink(Link('endpoint_sccp'))

    line = Nested('LineSchema', only=['id', 'links'], dump_only=True)
示例#23
0
class FuncKeyTemplateSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    name = fields.String(validate=Length(max=128))
    keys = FuncKeyPositionField(
        fields.Integer(validate=Range(min=1)),
        fields.Nested(FuncKeySchema, required=True, unknown=EXCLUDE),
    )
    links = ListLink(Link('func_keys_templates'))
示例#24
0
class MeetingSchema(BaseSchema):
    uuid = fields.UUID(dump_only=True)
    owner_uuids = fields.List(fields.UUID())
    name = fields.String(validate=Length(max=512), required=True)
    ingress_http_uri = fields.Method('_uri', dump_only=True)
    guest_sip_authorization = fields.Method('_guest_sip_authorization',
                                            dump_only=True)
    persistent = fields.Boolean(missing=False)
    links = ListLink(Link('meetings', field='uuid'))
    tenant_uuid = fields.String(dump_only=True)
    creation_time = fields.DateTime(attribute='created_at', dump_only=True)
    exten = fields.Method('_exten', dump_only=True)
    require_authorization = fields.Boolean(missing=False)

    def _uri(self, meeting):
        if meeting.ingress_http:
            return meeting.ingress_http.uri

        default_ingress_http = self.context['default_ingress_http']
        if default_ingress_http:
            return default_ingress_http.uri

        raise NoIngressHTTPException()

    def _exten(self, meeting):
        prefix = self.context['exten_prefix']
        if not prefix:
            logger.debug(
                'cannot add the meeting exten, no "meetingjoin" extension_features configured'
            )
            return
        return '{}{}'.format(prefix, meeting.number)

    def _guest_sip_authorization(self, model):
        if not model.guest_endpoint_sip:
            return None

        if model.require_authorization:
            return None

        return self.format_sip_authorization(model.guest_endpoint_sip)

    @staticmethod
    def format_sip_authorization(endpoint_sip):
        username = None
        password = None
        for option, value in endpoint_sip.auth_section_options:
            if option == 'username':
                username = value
            elif option == 'password':
                password = value

        if username is None or password is None:
            return None

        return b64encode('{}:{}'.format(username, password).encode()).decode()
示例#25
0
class CustomSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    interface = fields.String(validate=Regexp(INTERFACE_REGEX), required=True)
    interface_suffix = fields.String(validate=Length(max=32), allow_none=True)
    enabled = StrictBoolean()
    links = ListLink(Link('endpoint_custom'))

    trunk = Nested('TrunkSchema', only=['id', 'links'], dump_only=True)
    line = Nested('LineSchema', only=['id', 'links'], dump_only=True)
示例#26
0
class SkillSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(validate=(Regexp(NAME_REGEX), Length(max=64)), required=True)
    category = fields.String(validate=Length(max=64), allow_none=True)
    description = fields.String(allow_none=True)
    links = ListLink(Link('skills'))

    agents = fields.Nested(
        'SkillAgentsSchema', attribute='agent_queue_skills', many=True, dump_only=True
    )
示例#27
0
class IAXSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(validate=Regexp(NAME_REGEX))
    type = fields.String(validate=OneOf(['friend', 'peer', 'user']))
    host = fields.String(validate=Length(max=255))
    options = fields.List(fields.List(fields.String(), validate=Length(equal=2)))
    links = ListLink(Link('endpoint_iax'))

    trunk = fields.Nested(
        'TrunkSchema', only=['id', 'links'], dump_only=True, attribute='trunk_rel'
    )
示例#28
0
class ContextSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    name = fields.String(
        validate=(
            Regexp(CONTEXT_REGEX),
            Length(min=1, max=39),
            NoneOf(
                [
                    'authentication',
                    'general',
                    'global',
                    'globals',
                    'parkedcalls',
                    'xivo-features',
                    'zonemessages',
                ]
            ),
        ),
        required=True,
    )
    label = fields.String(validate=Length(max=128), allow_none=True)
    type = fields.String(
        validate=OneOf(['internal', 'incall', 'outcall', 'services', 'others'])
    )
    user_ranges = Nested(RangeSchema, many=True)
    group_ranges = Nested(RangeSchema, many=True)
    queue_ranges = Nested(RangeSchema, many=True)
    conference_room_ranges = Nested(RangeSchema, many=True)
    incall_ranges = Nested(IncallRangeSchema, many=True)
    description = fields.String(allow_none=True)
    tenant_uuid = fields.String(dump_only=True)
    enabled = StrictBoolean()
    links = ListLink(Link('contexts'))

    contexts = Nested(
        'ContextSchema',
        only=['id', 'name', 'label', 'links'],
        many=True,
        dump_only=True,
    )

    @post_load
    def create_objects(self, data, **kwargs):
        for key in [
            'user_ranges',
            'group_ranges',
            'queue_ranges',
            'conference_room_ranges',
            'incall_ranges',
        ]:
            if data.get(key):
                data[key] = [ContextNumbers(**d) for d in data[key]]
        return data
示例#29
0
class LineSchema(BaseSchema):
    id = fields.Integer(dump_only=True)
    tenant_uuid = fields.String(dump_only=True)
    name = fields.String(dump_only=True)
    protocol = fields.String(dump_only=True)
    device_id = fields.String(dump_only=True)
    device_slot = fields.Integer(dump_only=True)
    provisioning_extension = fields.String(dump_only=True)

    context = fields.String(required=True)
    provisioning_code = fields.String(validate=(Predicate('isdigit'),
                                                Length(equal=6)))
    position = fields.Integer(validate=Range(min=1))
    caller_id_name = fields.String(
        allow_none=True)  # Validate length callerid_name + num = max(160)
    caller_id_num = fields.String(validate=Predicate('isdigit'),
                                  allow_none=True)
    registrar = fields.String(validate=Length(max=128))
    links = ListLink(Link('lines'))

    application = Nested('ApplicationSchema',
                         only=['uuid', 'name', 'links'],
                         dump_only=True)
    endpoint_sip = Nested(
        'EndpointSIPSchema',
        # TODO(pc-m): Is it really useful to have the username/password on the relation?
        only=[
            'uuid',
            'label',
            'name',
            'auth_section_options.username',
            'links',
        ],
        dump_only=True,
    )
    endpoint_sccp = Nested('SccpSchema', only=['id', 'links'], dump_only=True)
    endpoint_custom = Nested('CustomSchema',
                             only=['id', 'interface', 'links'],
                             dump_only=True)
    extensions = Nested(
        'ExtensionSchema',
        only=['id', 'exten', 'context', 'links'],
        many=True,
        dump_only=True,
    )
    users = Nested(
        'UserSchema',
        only=['uuid', 'firstname', 'lastname', 'links'],
        many=True,
        dump_only=True,
    )
示例#30
0
class SoundSchema(BaseSchema):
    tenant_uuid = fields.String()
    name = fields.String(
        validate=[
            Length(max=149, min=1),
            NoneOf([ASTERISK_CATEGORY]),
            NoneOf(RESERVED_DIRECTORIES, error=RESERVED_DIRECTORIES_ERROR),
            Regexp(DIRECTORY_REGEX),
        ],
        required=True,
    )
    files = Nested(SoundFileSchema, many=True, dump_only=True)

    links = ListLink(Link('sounds', field='name', target='category'))