class LDAPConfig(BaseSchema): tenant_uuid = fields.String(dump_only=True, default=None) host = fields.String(validate=Length(max=512), required=True, default=None) port = fields.Integer(required=True, default=None) protocol_version = fields.Integer(validate=Range(min=2, max=3), missing=3, default=None) protocol_security = fields.String( validate=OneOf(['ldaps', 'tls']), allow_none=True, default=None, ) bind_dn = fields.String(validate=Length(max=256), allow_none=True, default=None) user_base_dn = fields.String(validate=Length(max=256), required=True, default=None) user_login_attribute = fields.String(validate=Length(max=64), required=True, default=None) user_email_attribute = fields.String(validate=Length(max=64), required=True, default=None) search_filters = fields.String(allow_none=True, default=None)
class MarketListRequestSchema(Schema): direction = fields.String(validate=OneOf(['asc', 'desc']), missing='asc') order = fields.String(validate=Length(min=1), missing='name') limit = fields.Integer(validate=Range(min=0), missing=None) offset = fields.Integer(validate=Range(min=0), missing=0) search = fields.String(missing=None) installed = fields.Boolean()
class ListSchema(BaseSchema): limit = fields.Integer(validate=validate.Range(min=0)) offset = fields.Integer(validate=validate.Range(min=0)) search = fields.String() recurse = fields.Boolean() class Meta: unknown = marshmallow.INCLUDE
class SubscriptionLogSchema(Schema): uuid = fields.UUID(dump_only=True) status = fields.String() started_at = fields.DateTime() ended_at = fields.DateTime() attempts = fields.Integer() max_attempts = fields.Integer() event = fields.Dict() detail = fields.Dict()
class CDRSchema(Schema): id = fields.Integer() tenant_uuid = fields.UUID() start = fields.DateTime(attribute='date') end = fields.DateTime(attribute='date_end') answered = fields.Boolean(attribute='marshmallow_answered') answer = fields.DateTime(attribute='date_answer') duration = fields.TimeDelta(default=None, attribute='marshmallow_duration') call_direction = fields.String(attribute='direction') destination_extension = fields.String(attribute='destination_exten') destination_internal_context = fields.String() destination_internal_extension = fields.String( attribute='destination_internal_exten' ) destination_line_id = fields.Integer() destination_name = fields.String() destination_user_uuid = fields.UUID() requested_name = fields.String() requested_context = fields.String() requested_extension = fields.String(attribute='requested_exten') requested_internal_context = fields.String() requested_internal_extension = fields.String(attribute='requested_internal_exten') source_extension = fields.String(attribute='source_exten') source_internal_context = fields.String() source_internal_name = fields.String() source_internal_extension = fields.String(attribute='source_internal_exten') source_line_id = fields.Integer() source_name = fields.String() source_user_uuid = fields.UUID() tags = fields.List(fields.String(), attribute='marshmallow_tags') recordings = fields.Nested('RecordingSchema', many=True, default=[]) @pre_dump def _compute_fields(self, data, **kwargs): data.marshmallow_answered = True if data.date_answer else False if data.date_answer and data.date_end: data.marshmallow_duration = data.date_end - data.date_answer return data @post_dump def fix_negative_duration(self, data, **kwargs): if data['duration'] is not None: data['duration'] = max(data['duration'], 0) return data @pre_dump def _populate_tags_field(self, data, **kwargs): data.marshmallow_tags = set() for participant in data.participants: data.marshmallow_tags.update(participant.tags) return data
class ConfdConfigSchema(BaseSchema): host = fields.String(validate=Length(min=1, max=1024), missing='localhost') port = fields.Integer(validate=Range(min=1, max=65535), missing=9486) verify_certificate = VerifyCertificateField(missing=True) timeout = fields.Float(validate=Range(min=0, max=3660)) https = fields.Boolean(missing=True) version = fields.String(validate=Length(min=1, max=16), missing='1.1')
class AuthConfigSchema(BaseSchema): host = fields.String(validate=Length(min=1, max=1024), missing='localhost') port = fields.Integer(validate=Range(min=1, max=65535), missing=9497) key_file = fields.String(validate=Length(min=1, max=1024), allow_none=True) username = fields.String(validate=Length(min=1, max=512), allow_none=True) password = fields.String(validate=Length(min=1, max=512), allow_none=True) verify_certificate = VerifyCertificateField(missing=True) timeout = fields.Float(validate=Range(min=0, max=3660)) version = fields.String(validate=Length(min=1, max=16), missing='0.1') @validates_schema def validate_auth_info(self, data): key_file = data.get('key_file') username = data.get('username') if key_file and username: raise exceptions.ValidationError( 'a "key_file" or a "username" and "password" must be specified', ) if key_file or username: return raise exceptions.ValidationError( 'a "key_file" or a "username" and "password" must be specified', )
class QueueStatisticsSchema(_StatisticsPeriodSchema): queue_id = fields.Integer(default=None) queue_name = fields.String(default=None) received = fields.Integer(attribute='total', default=0) answered = fields.Integer(default=0) abandoned = fields.Integer(default=0) closed = fields.Integer(default=0) not_answered = fields.Integer(attribute='timeout', default=0) saturated = fields.Integer(default=0) blocked = fields.Integer(attribute='blocking', default=0) average_waiting_time = fields.Integer(default=None) answered_rate = fields.Float(default=None) quality_of_service = fields.Float(attribute='qos', default=None)
class BaseAuthConfigSchema(BaseSchema): host = fields.String(validate=Length(min=1, max=1024), missing='localhost') port = fields.Integer(validate=Range(min=1, max=65535), missing=443) https = fields.Boolean(missing=True) verify_certificate = VerifyCertificateField(missing=True) prefix = fields.String(allow_none=True, missing='/api/auth') version = fields.String(validate=Length(min=1, max=16), missing='0.1') timeout = fields.Float(validate=Range(min=0, max=3660))
class CDRListRequestSchema(CDRListingBase): direction = fields.String(validate=OneOf(['asc', 'desc']), missing='desc') order = fields.String( validate=OneOf(set(CDRSchema().fields) - {'end', 'tags', 'recordings'}), missing='start', ) limit = fields.Integer(validate=Range(min=0), missing=1000) offset = fields.Integer(validate=Range(min=0), missing=None) distinct = fields.String(validate=OneOf(['peer_exten']), missing=None) recorded = fields.Boolean(missing=None) format = fields.String(validate=OneOf(['csv', 'json']), missing=None) @post_load def map_order_field(self, in_data, **kwargs): mapped_order = CDRSchema().fields[in_data['order']].attribute if mapped_order: in_data['order'] = mapped_order return in_data
class ContactSchema(BaseSchema): id = fields.Integer() uuid = fields.String() firstname = fields.String() lastname = fields.String() email = fields.String() exten = fields.String() mobile_phone_number = fields.String() voicemail_number = fields.String()
class TokenRequestSchema(BaseSchema): backend = fields.String(missing='wazo_user') expiration = fields.Integer(validate=Range(min=1)) access_type = fields.String(validate=OneOf(['online', 'offline'])) client_id = fields.String(validate=Length(min=1, max=1024)) refresh_token = fields.String() tenant_id = fields.String() domain_name = fields.String() @validates_schema def check_access_type_usage(self, data, **kwargs): access_type = data.get('access_type') if access_type != 'offline': return refresh_token = data.get('refresh_token') if refresh_token: raise ValidationError( 'cannot use the "access_type" "offline" with a refresh token' ) client_id = data.get('client_id') if not client_id: raise ValidationError( '"client_id" must be specified when using "access_type" is "offline"' ) @validates_schema def check_backend_type_for_tenant_id_and_domain_name(self, data, **kwargs): backend = data.get('backend') if not backend == 'ldap_user': return tenant_id = data.get('tenant_id') domain_name = data.get('domain_name') if tenant_id and domain_name: raise ValidationError( '"tenant_id" and "domain_name" must be mutually exclusive' ) if not tenant_id and not domain_name: raise ValidationError( '"tenant_id" or "domain_name" must be specified when using the "ldap_user" backend' ) @validates_schema def check_refresh_token_usage(self, data, **kwargs): refresh_token = data.get('refresh_token') if not refresh_token: return client_id = data.get('client_id') if not client_id: raise ValidationError( '"client_id" must be specified when using a "refresh_token"' )
class ParticipantSchema(Schema): class Meta: strict = True ordered = True id = fields.String() caller_id_name = fields.String() caller_id_number = fields.String() muted = fields.Boolean() join_time = fields.Integer() admin = fields.Boolean() language = fields.String() call_id = fields.String()
class AgentStatisticsSchema(_StatisticsPeriodSchema): agent_id = fields.Integer(default=None) agent_number = fields.String(default=None) answered = fields.Integer(default=0) conversation_time = fields.Integer(default=0) login_time = fields.Integer(default=0) pause_time = fields.Integer(default=0) wrapup_time = fields.Integer(default=0)
class ParticipantSchema(Schema): class Meta: ordered = True unknown = EXCLUDE id = fields.String() caller_id_name = fields.String() caller_id_number = fields.String() muted = fields.Boolean() join_time = fields.Integer() admin = fields.Boolean() language = fields.String() call_id = fields.String() user_uuid = fields.String(allow_none=True)
class LinePresenceSchema(Schema): id = fields.Integer(dump_only=True) state = fields.String(dump_only=True) @post_dump(pass_original=True) def _set_state(self, data, raw_data, **kwargs): if 'ringing' in raw_data.channels_state: merged_state = 'ringing' elif 'progressing' in raw_data.channels_state: merged_state = 'progressing' elif 'holding' in raw_data.channels_state: merged_state = 'holding' elif 'talking' in raw_data.channels_state: merged_state = 'talking' else: merged_state = raw_data.endpoint_state or 'unavailable' data['state'] = merged_state return data
class ContactSchema(BaseSchema): id = fields.Integer() name = fields.String() extensions = fields.Nested(ExtensionSchema, many=True) incalls = fields.Nested(ExtensionSchema, many=True) @pre_dump def unpack_extensions(self, data, **kwargs): extension_schema = ExtensionSchema(many=True) incalls = [] extensions = extension_schema.dump(data['extensions']) for incall in data['incalls']: incalls += extension_schema.dump(incall['extensions']) data['extensions'] = extensions data['incalls'] = incalls return data
class CDRListingBase(Schema): from_ = fields.DateTime(data_key='from', attribute='start', missing=None) until = fields.DateTime(attribute='end', missing=None) search = fields.String(missing=None) call_direction = fields.String( validate=OneOf(['internal', 'inbound', 'outbound']), missing=None ) number = fields.String(validate=Regexp(NUMBER_REGEX), missing=None) tags = fields.List(fields.String(), missing=[]) user_uuid = fields.List(fields.String(), missing=[], attribute='user_uuids') from_id = fields.Integer(validate=Range(min=0), attribute='start_id', missing=None) recurse = fields.Boolean(missing=False) @pre_load def convert_tags_and_user_uuid_to_list(self, data, **kwargs): result = data.to_dict() if data.get('tags'): result['tags'] = data['tags'].split(',') if data.get('user_uuid'): result['user_uuid'] = data['user_uuid'].split(',') return result
class ContactSchema(BaseSchema): id = fields.Integer() name = fields.String() extensions = fields.Nested(ExtensionSchema, many=True) incalls = fields.Nested(ExtensionSchema, many=True) @pre_dump(pass_many=True) def unpack_extensions(self, data, many): extension_schema = ExtensionSchema(many=True) for contact in data: incalls = [] extensions = extension_schema.dump(contact['extensions']).data for incall in contact['incalls']: incalls += extension_schema.dump(incall['extensions']).data contact['extensions'] = extensions contact['incalls'] = incalls return data
class QueueStatisticsQoSRequestSchema(_StatisticsListRequestSchema): interval = fields.String(validate=OneOf(['hour', 'day', 'month'])) qos_thresholds = fields.List(fields.Integer(validate=Range(min=0)), missing=[]) @pre_load def convert_qos_thresholds_to_list(self, data, **kwargs): result = data.copy() if not isinstance(result, dict): result = result.to_dict() if data.get('qos_thresholds'): result['qos_thresholds'] = list( set(data['qos_thresholds'].split(','))) return result @post_load def sort_qos_thresholds(self, data, **kwargs): result = data.copy() if data.get('qos_thresholds'): result['qos_thresholds'] = sorted(data['qos_thresholds']) return result
class PluginMetadataSchema(Schema): version_fields = ['version', 'max_wazo_version', 'min_wazo_version'] current_version = None name = fields.String(validate=Regexp(_PLUGIN_NAME_REGEXP), required=True) namespace = fields.String(validate=Regexp(_PLUGIN_NAMESPACE_REGEXP), required=True) version = fields.String(required=True) plugin_format_version = fields.Integer( validate=Range(min=0, max=_MAX_PLUGIN_FORMAT_VERSION), missing=_DEFAULT_PLUGIN_FORMAT_VERSION) max_wazo_version = fields.String() min_wazo_version = fields.String() depends = fields.Nested(MarketInstallOptionsSchema, many=True, unknown=EXCLUDE) @pre_load def ensure_string_versions(self, data): for field in self.version_fields: if field not in data: continue value = data[field] if not isinstance(value, (float, int)): continue data[field] = str(value) return data def on_bind_field(self, field_name, field_obj): if field_name == 'max_wazo_version': self._set_max_wazo_version_parameters(field_obj) elif field_name == 'min_wazo_version': self._set_min_wazo_version_parameters(field_obj) def _set_max_wazo_version_parameters(self, field_obj): field_obj.validators = [Range(min=self.current_version)] def _set_min_wazo_version_parameters(self, field_obj): field_obj.validators = [Range(max=self.current_version)]
class RetentionSchema(Schema): tenant_uuid = fields.UUID(dump_only=True) cdr_days = fields.Integer(validate=Range(min=0), missing=None) export_days = fields.Integer(validate=Range(min=0), missing=None) recording_days = fields.Integer(validate=Range(min=0), missing=None) default_cdr_days = fields.Integer(dump_only=True) default_export_days = fields.Integer(dump_only=True) default_recording_days = fields.Integer(dump_only=True) @validates_schema def validate_days(self, data, **kwargs): cdr_days = data.get('cdr_days') recording_days = data.get('recording_days') if cdr_days is None or recording_days is None: return if recording_days > cdr_days: raise ValidationError( '"recording_days" must be higher or equal than "cdr_days"', field_name='recording_days', )
class QueueStatisticsQoSSchema(_StatisticsPeriodSchema): queue_id = fields.Integer(default=None) queue_name = fields.String(default=None) quality_of_service = fields.List(fields.Nested(_QoSSchema))
class MicrosoftSchema(schemas.BaseSchema): scope = fields.List(fields.String(validate=Length(min=1, max=512))) access_token = fields.String(dump_only=True) token_expiration = fields.Integer(dump_only=True)
class QueueStatisticsQoSSchemaList(Schema): items = fields.Nested(QueueStatisticsQoSSchema, many=True) total = fields.Integer()
class SetupSchema(Schema): engine_language = fields.String(required=True, validate=validate.OneOf(['en_US', 'fr_FR'])) engine_password = fields.String(required=True) engine_license = fields.Boolean(required=True, validate=validate.Equal(True)) engine_internal_address = fields.String() engine_instance_uuid = fields.UUID(missing=None) engine_rtp_icesupport = fields.Boolean(required=False, missing=False) engine_rtp_stunaddr = fields.String(validate=validate.Length(min=1, max=1024), missing=None) nestbox_host = fields.String() nestbox_port = fields.Integer( validate=validate.Range(min=0, max=65535, error='Not a valid TCP/IP port number.'), missing=443, ) nestbox_verify_certificate = fields.Boolean(missing=True) nestbox_service_id = fields.String() nestbox_service_key = fields.String() nestbox_instance_name = fields.String() nestbox_engine_host = fields.String() nestbox_engine_port = fields.Integer( validate=validate.Range(min=0, max=65535, error='Not a valid TCP/IP port number.'), missing=443, ) @validates_schema def nestbox_all_or_nothing(self, data): if not data.get('nestbox_host'): return if 'nestbox_service_id' not in data: raise ValidationError( 'Missing keys for Nestbox configuration: nestbox_service_id') if 'nestbox_service_key' not in data: raise ValidationError( 'Missing keys for Nestbox configuration: nestbox_service_key') if 'nestbox_instance_name' not in data: raise ValidationError( 'Missing keys for Nestbox configuration: nestbox_instance_name' ) if 'nestbox_engine_host' not in data: raise ValidationError( 'Missing keys for Nestbox configuration: nestbox_engine_host') if 'engine_internal_address' not in data: raise ValidationError( 'Missing keys for Nestbox configuration: engine_internal_address' ) @validates_schema def check_rtp_fields(self, data): if not data.get('engine_rtp_icesupport'): return required_field = 'engine_rtp_stunaddr' if not data.get(required_field): raise ValidationError( 'Missing keys for rtp configuration: {}'.format( required_field), field_name=required_field, )
class AgentStatisticsSchemaList(Schema): items = fields.Nested(AgentStatisticsSchema, many=True) total = fields.Integer()
class _QoSSchema(Schema): min_ = fields.Integer(default=None, attribute='min', data_key='min') max_ = fields.Integer(default=None, attribute='max', data_key='max') answered = fields.Integer(default=0) abandoned = fields.Integer(default=0)
class LookupGigasetSchema(Schema): set_first = fields.String(attribute='term', missing='') count = fields.Integer(attribute='limit', missing=None) first = fields.Integer(attribute='offset', missing=1)
class _StatisticsListRequestSchema(Schema): from_ = fields.DateTime(data_key='from', missing=None) until = fields.DateTime(missing=None) day_start_time = fields.String(attribute='start_time', validate=Regexp(HOUR_REGEX)) day_end_time = fields.String(attribute='end_time', validate=Regexp(HOUR_REGEX)) week_days = fields.List( fields.Integer(), missing=[1, 2, 3, 4, 5, 6, 7], validate=ContainsOnly([1, 2, 3, 4, 5, 6, 7]), ) timezone = fields.String(validate=OneOf(pytz.all_timezones), missing='UTC') def _normalize_datetime(self, dt, timezone): if not dt.tzinfo: return timezone.normalize(timezone.localize(dt)) else: utc_dt = pytz.utc.normalize(dt) return timezone.normalize(utc_dt) @pre_load def convert_week_days_to_list(self, data, **kwargs): result = data.copy() if not isinstance(data, dict): result = data.to_dict() if data.get('week_days'): result['week_days'] = list(set(data['week_days'].split(','))) return result @post_load def convert_time_to_hour(self, data, **kwargs): if data.get('start_time'): start_time = time.fromisoformat(data['start_time']) data['start_time'] = start_time.hour if data.get('end_time'): end_time = time.fromisoformat(data['end_time']) data['end_time'] = end_time.hour return data @post_load def default_timezone_on_datetime(self, data, **kwargs): timezone = pytz.timezone(data.get('timezone')) from_ = data.get('from_') until = data.get('until') if from_: data['from_'] = self._normalize_datetime(from_, timezone) if until: data['until'] = self._normalize_datetime(until, timezone) return data @validates_schema def validate_dates(self, data, **kwargs): from_ = data.get('from_', None) until = data.get('until', None) timezone = pytz.timezone(data.get('timezone')) if from_: from_ = self._normalize_datetime(from_, timezone) if until: until = self._normalize_datetime(until, timezone) if from_ and until and until <= from_: raise ValidationError({'until': 'Field must be greater than from'}) @validates_schema def validate_start_end_times(self, data, **kwargs): if data.get('start_time') and data.get('end_time'): if data['start_time'] >= data['end_time']: raise ValidationError({ 'day_start_time': 'Field must be lower than day_end_time' })