class InvitationSerializer(DynamicFieldsMixin, HyperlinkedMixin, serializers.ModelSerializer): hyperlinks = ( ('self', 'invitation-detail', ('instance.name', 'pk')), ('resend', 'invitation-resend', ('instance.name', 'pk')), ) role = DisplayedChoiceField(source='role_id', choices=Role.ROLE_CHOICES.as_choices(), default=Role.ROLE_CHOICES.FULL) inviter = serializers.CharField(source='inviter.email', read_only=True) state = DisplayedChoiceField(choices=Invitation.STATE_CHOICES.as_choices(), read_only=True) class Meta: model = Invitation fields = ( 'id', 'email', 'role', 'key', 'inviter', 'created_at', 'updated_at', 'state', ) extra_kwargs = {'key': {'read_only': True}}
class AdminInstanceRoleSerializer(DynamicFieldsMixin, HyperlinkedMixin, serializers.HyperlinkedModelSerializer): hyperlinks = (('self', 'instance-admin-detail', ( 'instance.name', 'admin_id', )), ) id = serializers.ReadOnlyField(source='admin_id') email = serializers.ReadOnlyField(source='admin.email') first_name = serializers.ReadOnlyField(source='admin.first_name') last_name = serializers.ReadOnlyField(source='admin.last_name') role = DisplayedChoiceField(source='role_id', choices=Role.ROLE_CHOICES.as_choices(), default=Role.ROLE_CHOICES.FULL) class Meta: model = AdminInstanceRole fields = ( 'id', 'role', 'email', 'first_name', 'last_name', )
class RestoreSerializer(HyperlinkedMixin, ModelSerializer): hyperlinks = (('self', 'restores-detail', ('instance.name', 'id')), ) backup = PrimaryKeyRelatedField(required=False, allow_null=True, queryset=Backup.objects.none()) status = DisplayedChoiceField(Backup.STATUSES.as_choices(), read_only=True) author = AdminFullSerializer(read_only=True, source="owner") class Meta: model = Restore fields = ('id', 'backup', 'created_at', 'updated_at', 'status', 'archive', 'status_info', 'author') read_only_fields = ('created_at', 'id', 'status', 'status_info', 'author') def get_fields(self): fields = super().get_fields() if 'request' in self.context: fields['backup'].queryset = Backup.objects.filter( owner=self.context['view'].request.user, status=Backup.STATUSES.SUCCESS, location=settings.LOCATION, ) return fields def validate(self, attrs): has_archive = bool(attrs.get('archive', False)) has_backup = bool(attrs.get('backup', False)) if has_backup and has_archive or (not has_backup and not has_archive): raise ValidationError( 'You have to provide either backup or archive.') if has_archive and not self.context['request'].user.is_staff: raise PermissionDenied() return super().validate(attrs)
class ChannelSerializer(RevalidateMixin, DynamicFieldsMixin, HyperlinkedMixin, HStoreSerializer): hyperlinks = ( ('self', 'channel-detail', ( 'instance.name', 'name', )), ('group', 'group-detail', ( 'instance.name', 'group_id', )), ('poll', 'channel-poll', ( 'instance.name', 'name', )), ('publish', 'channel-publish', ( 'instance.name', 'name', )), ('history', 'change-list', ( 'instance.name', 'name', )), ) name = LowercaseCharField(validators=[ UniqueValidator(queryset=Channel.objects.all()), DjangoValidator() ]) type = DisplayedChoiceField(choices=Channel.TYPES.as_choices(), default=Channel.TYPES.DEFAULT) group_permissions = DisplayedChoiceField( choices=Channel.PERMISSIONS.as_choices(), default=Channel.PERMISSIONS.NONE) other_permissions = DisplayedChoiceField( choices=Channel.PERMISSIONS.as_choices(), default=Channel.PERMISSIONS.NONE) class Meta: model = Channel fields = ('name', 'description', 'type', 'group', 'group_permissions', 'other_permissions', 'created_at', 'updated_at', 'custom_publish')
class SocketSerializer(RevalidateMixin, MetadataMixin, DynamicFieldsMixin, HyperlinkedMixin, ModelSerializer): hyperlinks = ( ('self', 'socket-detail', ( 'instance.name', 'name', )), ('update', 'socket-update', ( 'instance.name', 'name', )), ('endpoints', 'socket-endpoint-endpoint', ( 'instance.name', 'name', )), ('handlers', 'socket-handler-list', ( 'instance.name', 'name', )), ('zip_file', 'socket-zip-file', ( 'instance.name', 'name', )), ) name = LowercaseCharField(validators=[ UniqueValidator(queryset=Socket.objects.all()), DjangoValidator() ]) zip_file = FileField(write_only=True) zip_file_list = JSONField(validators=[FileListValidator()], default=None, write_only=True) config = JSONField(validators=[validate_config], default={}) install_config = JSONField(write_only=True, default={}) status = DisplayedChoiceField(Socket.STATUSES.as_choices(), read_only=True) status_info = JSONField(read_only=True) installed = JSONField(read_only=True) files = serializers.SerializerMethodField() environment = SlugRelatedField(slug_field='name', queryset=SocketEnvironment.objects.all(), default=None, allow_null=True) class Meta: model = Socket fields = ('name', 'description', 'created_at', 'updated_at', 'version', 'status', 'status_info', 'install_url', 'metadata', 'config', 'zip_file', 'zip_file_list', 'installed', 'files', 'environment', 'install_config', ) read_only_fields = ('install_url', 'version') def get_files(self, obj): file_list = copy.deepcopy(obj.file_list) for val in file_list.values(): if not val['file'].startswith('<'): val['file'] = default_storage.url(val['file']) return file_list
class AdminInvitationSerializer(HyperlinkedMixin, serializers.ModelSerializer): hyperlinks = (('self', 'admin-invitation-detail', ('pk', )), ('instance', 'instance-detail', ('instance.name', ))) role = DisplayedChoiceField(source='role.name', choices=Role.ROLE_CHOICES.as_choices()) state = DisplayedChoiceField(choices=Invitation.STATE_CHOICES.as_choices(), default=Invitation.STATE_CHOICES.NEW) inviter = serializers.CharField(source='inviter.email') instance = serializers.CharField(source='instance.name') class Meta: model = Invitation fields = ( 'id', 'email', 'role', 'key', 'instance', 'inviter', 'created_at', 'updated_at', 'state', )
class ChangeSerializer(DynamicFieldsMixin, HyperlinkedMixin, serializers.Serializer): hyperlinks = (('self', 'change-detail', ( 'instance.name', 'channel.name', 'id', )), ) id = serializers.IntegerField() created_at = serializers.DateTimeField() action = DisplayedChoiceField(choices=Change.ACTIONS.as_choices()) author = JSONField() metadata = JSONField() payload = JSONField()
class InvoiceSerializer(DynamicFieldsMixin, HyperlinkedMixin, serializers.ModelSerializer): hyperlinks = ( ('self', 'invoice-detail', ('pk', )), ('pdf', 'invoice-pdf', ('pk', )), ('retry-payment', 'invoice-retry-payment', ('pk', )), ) period = serializers.CharField(source='formatted_period') amount = serializers.CharField(source='formatted_amount') items = InvoiceItemSerializer(many=True) status = DisplayedChoiceField(choices=Invoice.STATUS_CHOICES.as_choices()) class Meta: model = Invoice fields = ('id', 'period', 'amount', 'status', 'created_at', 'updated_at', 'items')
class SocketEnvironmentSerializer(RevalidateMixin, MetadataMixin, DynamicFieldsMixin, HyperlinkedMixin, ModelSerializer): hyperlinks = ( ('self', 'socket-environment-detail', ( 'instance.name', 'name', )), ) name = LowercaseCharField(validators=[ UniqueValidator(queryset=SocketEnvironment.objects.all()), DjangoValidator() ]) zip_file = FileField(write_only=True) status = DisplayedChoiceField(SocketEnvironment.STATUSES.as_choices(), read_only=True) status_info = JSONField(read_only=True) checksum = CharField(read_only=True) class Meta: model = SocketEnvironment fields = ('name', 'description', 'created_at', 'updated_at', 'status', 'status_info', 'metadata', 'zip_file', 'checksum')
class BackupSerializer(MetadataMixin, ModelSerializer): instance = SlugRelatedField(slug_field='name', required=False, read_only=True, allow_null=False) status = DisplayedChoiceField(Backup.STATUSES.as_choices(), read_only=True) author = AdminFullSerializer(read_only=True, source="owner") details = JSONField(read_only=True) class Meta: model = Backup read_only_fields = ('id', 'instance', 'created_at', 'updated_at', 'archive', 'size', 'status', 'status_info', 'author', 'details') fields = read_only_fields + ('description', 'label', 'query_args', 'metadata') extra_kwargs = { 'description': { 'required': False }, 'label': { 'required': False } }
class BalanceItemSerializer(serializers.Serializer): amount = serializers.CharField(source='formatted_amount') quantity = serializers.IntegerField() source = DisplayedChoiceField(choices=InvoiceItem.SOURCES.as_choices())
class HostingSerializer(DynamicFieldsMixin, HyperlinkedMixin, ModelSerializer): hyperlinks = ( ('self', 'hosting-detail', ( 'instance.name', 'pk', )), ('files', 'hosting-file-list', ( 'instance.name', 'pk', )), ('set_default', 'hosting-set-default', ( 'instance.name', 'pk', )), ('enable_ssl', 'hosting-enable-ssl', ( 'instance.name', 'pk', )), ) name = serializers.CharField( max_length=253, validators=[ UniqueValidator(queryset=Hosting.objects.all()), HostingNameValidator() ]) domains = serializers.ListField(child=LowercaseCharField( max_length=253, validators=[DomainValidator()]), ) ssl_status = DisplayedChoiceField( choices=Hosting.SSL_STATUSES.as_choices(), read_only=True) class Meta: model = Hosting read_only_fields = ('is_default', ) fields = ('id', 'name', 'is_default', 'description', 'created_at', 'updated_at', 'domains', 'is_active', 'ssl_status') def validate_domains(self, value): value_set = set(value) only_domains = [ v for v in value_set if re.match(VALID_DOMAIN_REGEX, v) ] # Only 1 domain per hosting allowed so that we can process SSL properly if len(only_domains) > 1: raise OnlyOneDomainAllowed() # this checks the global domains; if Instance.objects.exclude(pk=get_current_instance().pk).filter( domains__overlap=only_domains).exists(): raise DomainAlreadyUsed() # prevent for creating hosting with the same domain; # but allow to update the same object with the same domains; validate_queryset = Hosting.objects.all() # update case; if empty - create case; current_hosting = self.instance if current_hosting: validate_queryset = validate_queryset.exclude( pk=current_hosting.pk) # use a all values here - this will check if no two hosting objects exists with the same instance name # and suffix combination; if validate_queryset.filter(domains__overlap=list(value_set)).exists(): raise DomainAlreadyUsed() return list(value_set) def validate(self, data): if self.instance and self.instance.is_locked: raise HostingLocked() return super().validate(data) def to_internal_value(self, data): reverted_data = super().to_internal_value(data) # Automatically add name to domains if reverted_data: if 'name' in reverted_data: name = reverted_data['name'] else: name = self.instance.name if 'domains' in reverted_data: domains = reverted_data['domains'] else: domains = self.instance if isinstance(domains, list): if name not in domains: domains.append(name) else: reverted_data['domains'] = [name] return reverted_data
class APNSMessageSerializer(HyperlinkedMixin, ModelSerializer): hyperlinks = ( ('self', 'apns-messages-detail', ('instance.name', 'pk')), ) SCHEMA = { 'type': 'object', 'maxItems': 128, 'required': [ 'registration_ids', 'environment', 'aps' ], 'properties': { # Targets 'registration_ids': { 'type': 'array', 'uniqueItems': True, 'maxItems': 1000, 'items': { 'type': 'string' } }, 'environment': { 'type': 'string', 'enum': [ 'development', 'production', ] }, 'aps': { 'type': 'object', 'required': ['alert'], 'properties': { 'alert': { 'oneOf': [ {'type': 'string'}, { 'type': 'object', 'required': ['title', 'body'], 'properties': { 'title': { 'type': 'string' }, 'body': { 'type': 'string' }, 'title-loc-key': { 'type': 'string' }, 'title-loc-args': { 'type': 'array', 'items': { 'type': 'string' } }, 'action-loc-key': { 'type': 'string' }, 'loc-key': { 'type': 'string' }, 'loc-args': { 'type': 'array', 'items': { 'type': 'string' } }, 'launch-image': { 'type': 'string' } } }, ] }, 'badge': { 'type': 'integer' }, 'sound': { 'type': 'string' }, 'content-available': { 'type': 'integer' }, 'category': { 'type': 'string' } } }, } } status = DisplayedChoiceField(choices=APNSMessage.STATUSES.as_choices(), read_only=True) content = JSONField(required=True, schema=SCHEMA) result = JSONField(default={}, read_only=True) class Meta: model = APNSMessage read_only_fields = ( 'created_at', 'updated_at', 'status', 'result', ) fields = '__all__' def validate(self, data): config = Cached(APNSConfig, kwargs={'id': 1}).get() if 'content' in data: environment = data['content']['environment'] else: environment = self.instance.content['environment'] certificate = getattr(config, '{}_certificate'.format(environment)) bundle_identifier = getattr(config, '{}_bundle_identifier'.format(environment)) if not certificate: raise ValidationError('APNS certificate for "{}" environment is required.'.format(environment)) if not bundle_identifier: raise ValidationError('APNS bundle identifier for "{}" environment is required.'.format(environment)) return data
class GCMMessageSerializer(HyperlinkedMixin, ModelSerializer): hyperlinks = ( ('self', 'gcm-messages-detail', ('instance.name', 'pk')), ) # More info: https://developers.google.com/cloud-messaging/http-server-ref SCHEMA = { 'type': 'object', 'additionalProperties': False, 'required': [ 'registration_ids', 'environment', ], 'properties': { # Targets 'registration_ids': { 'type': 'array', 'uniqueItems': True, 'maxItems': 1000, 'items': { 'type': 'string' } }, 'environment': { 'type': 'string', 'enum': [ 'development', 'production', ] }, # Payload 'data': { 'type': 'object', 'maxProperties': 256 }, 'notification': { 'type': 'object', 'maxProperties': 256 }, # Options 'collapse_key': { 'type': 'string' }, 'priority': { 'type': 'string', 'enum': ['normal', 'high'] }, 'content_available': { 'type': 'boolean' }, 'delay_while_idle': { 'type': 'boolean' }, 'time_to_live': { 'type': 'integer', 'maximum': 3600 * 24 * 28, # 4 weeks 'minimum': 1 }, 'restricted_package_name': { 'type': 'string' }, 'dry_run': { 'type': 'boolean' } } } status = DisplayedChoiceField(choices=GCMMessage.STATUSES.as_choices(), read_only=True) content = JSONField(required=True, schema=SCHEMA) result = JSONField(default={}, read_only=True) class Meta: model = GCMMessage read_only_fields = ( 'created_at', 'updated_at', 'status', 'result', ) fields = '__all__' def validate(self, data): config = Cached(GCMConfig, kwargs={'id': 1}).get() if 'content' in data: environment = data['content']['environment'] else: environment = self.instance.content['environment'] api_key = getattr(config, '{}_api_key'.format(environment)) if not api_key: raise ValidationError('GCM api key for "{}" environment is required.'.format(environment)) return data