安迪_963

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

DRF serializer

serializer中的类并不多,主要有BaseSerializer, serializer, ListSerializer, ModelSerializer, HyperlinkedModelSerializer,它们之间的继承关系如下:没错,它们的父类是Field.

在前面的view中我们知道,除了list之外有对数据库操作的就是create, update, destory等。而destory只需要对实例执行delete即可。剩下的create,update我们查看一下它的源代码:

CreateModelMixin & UpdateModelMixin

class CreateModelMixin:
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

class UpdateModelMixin:
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

我们可以看到,不管是create,update最后都是执行serializer.注意,view中的create,update并不执行数据库操作,真正执行操作的是serializer的save方法。这个方法继承自BaseSerializer中,我们看源码(源码太多,去掉了其它部分):
其中的update,create对子类做了限制,是必须实现的方法,也是save方法最终要执行的动作,即我们在子类中实现的update, create方法。

BaseSerializer

class BaseSerializer(Field):

    def update(self, instance, validated_data):
        raise NotImplementedError('`update()` must be implemented.')

    def create(self, validated_data):
        raise NotImplementedError('`create()` must be implemented.')

    def save(self, **kwargs):
        assert not hasattr(self, 'save_object'), (
            'Serializer `%s.%s` has old-style version 2 `.save_object()` '
            'that is no longer compatible with REST framework 3. '
            'Use the new-style `.create()` and `.update()` methods instead.' %
            (self.__class__.__module__, self.__class__.__name__)
        )

        assert hasattr(self, '_errors'), (
            'You must call `.is_valid()` before calling `.save()`.'
        )

        assert not self.errors, (
            'You cannot call `.save()` on a serializer with invalid data.'
        )

        # Guard against incorrect use of `serializer.save(commit=False)`
        assert 'commit' not in kwargs, (
            "'commit' is not a valid keyword argument to the 'save()' method. "
            "If you need to access data before committing to the database then "
            "inspect 'serializer.validated_data' instead. "
            "You can also pass additional keyword arguments to 'save()' if you "
            "need to set extra attributes on the saved model instance. "
            "For example: 'serializer.save(owner=request.user)'.'"
        )

        assert not hasattr(self, '_data'), (
            "You cannot call `.save()` after accessing `serializer.data`."
            "If you need to access data before committing to the database then "
            "inspect 'serializer.validated_data' instead. "
        )

        validated_data = dict(
            list(self.validated_data.items()) +
            list(kwargs.items())
        )

        if self.instance is not None:
            self.instance = self.update(self.instance, validated_data)
            assert self.instance is not None, (
                '`update()` did not return an object instance.'
            )
        else:
            self.instance = self.create(validated_data)
            assert self.instance is not None, (
                '`create()` did not return an object instance.'
            )

        return self.instance

    def is_valid(self, raise_exception=False):
        assert not hasattr(self, 'restore_object'), (
            'Serializer `%s.%s` has old-style version 2 `.restore_object()` '
            'that is no longer compatible with REST framework 3. '
            'Use the new-style `.create()` and `.update()` methods instead.' %
            (self.__class__.__module__, self.__class__.__name__)
        )

        assert hasattr(self, 'initial_data'), (
            'Cannot call `.is_valid()` as no `data=` keyword argument was '
            'passed when instantiating the serializer instance.'
        )

        if not hasattr(self, '_validated_data'):
            try:
                self._validated_data = self.run_validation(self.initial_data)
            except ValidationError as exc:
                self._validated_data = {}
                self._errors = exc.detail
            else:
                self._errors = {}

        if self._errors and raise_exception:
            raise ValidationError(self.errors)

        return not bool(self._errors)

我们看看serializer

serializer

可以看到serializer中并没有实现create, update方法,所以如果我们直接使用Serializer,必须自己实现create, update方法。否则是会报错的。

class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
    default_error_messages = {
        'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.')
    }

    @cached_property
    def fields(self):
        pass

    @property
    def _writable_fields(self):
        pass

    @property
    def _readable_fields(self):
        pass

    def get_fields(self):
        pass

    def get_validators(self):
        pass

    def get_initial(self):
        pass

    def get_value(self, dictionary):
        pass

    def run_validation(self, data=empty):
        pass

    def _read_only_defaults(self):
        pass

    def run_validators(self, value):
        pass

    def to_internal_value(self, data):
        pass

    def to_representation(self, instance):
        pass

    def validate(self, attrs):
        return attrs

    def __repr__(self):
        return representation.serializer_repr(self, indent=1)

    def __iter__(self):
        for field in self.fields.values():
            yield self[field.field_name]

    def __getitem__(self, key):
        pass

    @property
    def data(self):
        ret = super().data
        return ReturnDict(ret, serializer=self)

    @property
    def errors(self):
        pass

再看看我们比较常用的ModelSerializer

ModelSerializer

class ModelSerializer(Serializer):
    def create(self, validated_data):
        """
        We have a bit of extra checking around this in order to provide
        descriptive messages when something goes wrong, but this method is
        essentially just:

            return ExampleModel.objects.create(**validated_data)

        If there are many to many fields present on the instance then they
        cannot be set until the model is instantiated, in which case the
        implementation is like so:

            example_relationship = validated_data.pop('example_relationship')
            instance = ExampleModel.objects.create(**validated_data)
            instance.example_relationship = example_relationship
            return instance

        The default implementation also does not handle nested relationships.
        If you want to support writable nested relationships you'll need
        to write an explicit `.create()` method.
        """
        raise_errors_on_nested_writes('create', self, validated_data)

        ModelClass = self.Meta.model

        # Remove many-to-many relationships from validated_data.
        # They are not valid arguments to the default `.create()` method,
        # as they require that the instance has already been saved.
        info = model_meta.get_field_info(ModelClass)
        many_to_many = {}
        for field_name, relation_info in info.relations.items():
            if relation_info.to_many and (field_name in validated_data):
                many_to_many[field_name] = validated_data.pop(field_name)

        try:
            instance = ModelClass._default_manager.create(**validated_data)
        except TypeError:
            tb = traceback.format_exc()
            msg = (
                'Got a `TypeError` when calling `%s.%s.create()`. '
                'This may be because you have a writable field on the '
                'serializer class that is not a valid argument to '
                '`%s.%s.create()`. You may need to make the field '
                'read-only, or override the %s.create() method to handle '
                'this correctly.\nOriginal exception was:\n %s' %
                (
                    ModelClass.__name__,
                    ModelClass._default_manager.name,
                    ModelClass.__name__,
                    ModelClass._default_manager.name,
                    self.__class__.__name__,
                    tb
                )
            )
            raise TypeError(msg)

        # Save many-to-many relationships after the instance is created.
        if many_to_many:
            for field_name, value in many_to_many.items():
                field = getattr(instance, field_name)
                field.set(value)

        return instance

    def update(self, instance, validated_data):
        raise_errors_on_nested_writes('update', self, validated_data)
        info = model_meta.get_field_info(instance)

        # Simply set each attribute on the instance, and then save it.
        # Note that unlike `.create()` we don't need to treat many-to-many
        # relationships as being a special case. During updates we already
        # have an instance pk for the relationships to be associated with.
        m2m_fields = []
        for attr, value in validated_data.items():
            if attr in info.relations and info.relations[attr].to_many:
                m2m_fields.append((attr, value))
            else:
                setattr(instance, attr, value)

        instance.save()

        # Note that many-to-many fields are set after updating instance.
        # Setting m2m fields triggers signals which could potentially change
        # updated instance and we do not want it to collide with .update()
        for attr, value in m2m_fields:
            field = getattr(instance, attr)
            field.set(value)

        return instance

    # Determine the fields to apply...

    def get_fields(self):
        pass

    def get_field_names(self, declared_fields, info):
        pass

    def get_default_field_names(self, declared_fields, model_info):
        pass

    def build_field(self, field_name, info, model_class, nested_depth):
        pass

    def build_standard_field(self, field_name, model_field):
        pass

    def build_relational_field(self, field_name, relation_info):
        pass

    def build_nested_field(self, field_name, relation_info, nested_depth):
        pass

    def build_property_field(self, field_name, model_class):
        pass

    def build_url_field(self, field_name, model_class):
        pass

    def build_unknown_field(self, field_name, model_class):
        pass

    def include_extra_kwargs(self, kwargs, extra_kwargs):
        pass

    def get_extra_kwargs(self):
        pass

    def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs):
        pass

    def _get_model_fields(self, field_names, declared_fields, extra_kwargs):
        pass

    def get_validators(self):
        """
        Determine the set of validators to use when instantiating serializer.
        """
        # If the validators have been declared explicitly then use that.
        validators = getattr(getattr(self, 'Meta', None), 'validators', None)
        if validators is not None:
            return list(validators)

        # Otherwise use the default set of validators.
        return (
            self.get_unique_together_validators() +
            self.get_unique_for_date_validators()
        )

    def get_unique_together_validators(self):
        """
        Determine a default set of validators for any unique_together constraints.
        """
        model_class_inheritance_tree = (
            [self.Meta.model] +
            list(self.Meta.model._meta.parents)
        )

        # The field names we're passing though here only include fields
        # which may map onto a model field. Any dotted field name lookups
        # cannot map to a field, and must be a traversal, so we're not
        # including those.
        field_names = {
            field.source for field in self._writable_fields
            if (field.source != '*') and ('.' not in field.source)
        }

        # Special Case: Add read_only fields with defaults.
        field_names |= {
            field.source for field in self.fields.values()
            if (field.read_only) and (field.default != empty) and (field.source != '*') and ('.' not in field.source)
        }

        # Note that we make sure to check `unique_together` both on the
        # base model class, but also on any parent classes.
        validators = []
        for parent_class in model_class_inheritance_tree:
            for unique_together in parent_class._meta.unique_together:
                if field_names.issuperset(set(unique_together)):
                    validator = UniqueTogetherValidator(
                        queryset=parent_class._default_manager,
                        fields=unique_together
                    )
                    validators.append(validator)
        return validators

    def get_unique_for_date_validators(self):
        """
        Determine a default set of validators for the following constraints:

        * unique_for_date
        * unique_for_month
        * unique_for_year
        """
        info = model_meta.get_field_info(self.Meta.model)
        default_manager = self.Meta.model._default_manager
        field_names = [field.source for field in self.fields.values()]

        validators = []

        for field_name, field in info.fields_and_pk.items():
            if field.unique_for_date and field_name in field_names:
                validator = UniqueForDateValidator(
                    queryset=default_manager,
                    field=field_name,
                    date_field=field.unique_for_date
                )
                validators.append(validator)

            if field.unique_for_month and field_name in field_names:
                validator = UniqueForMonthValidator(
                    queryset=default_manager,
                    field=field_name,
                    date_field=field.unique_for_month
                )
                validators.append(validator)

            if field.unique_for_year and field_name in field_names:
                validator = UniqueForYearValidator(
                    queryset=default_manager,
                    field=field_name,
                    date_field=field.unique_for_year
                )
                validators.append(validator)

        return validators

ModelSerializer都实现了create, update方法,所以当使用ModelSerializer时,就不需要自己实现这些方法了。除非需要对它做额外的操作。

至于另外的ListSerializer,HyperlinkedModelSerializer因为使用得少,暂时不分析。

serializer使用

看了这么多源码,还不知道怎么用,显然我们的目的并不仅仅是看看源码,而是要应用它。

serializer

常用的field

CharField、BooleanField、IntegerField、DateTimeField,HiddenField.
除了HiddenField,其它字段都很常见,而HiddenField值不依靠输入,但需要设置默认的值,不需要用户自己post数据过来。比如用户,我们可以通过context上下文获取当前登陆的用户:

class CurrentUser(object):
    def set_context(self, serializer_field):
        self.user_obj = serializer_field.context['request'].user

    def __call__(self):
        return self.user_obj

class ArticleSerializer(serializers.Serializer):
    user = serializers.HiddenField(default=CurrentUser())
    title = serializers.CharField(max_length=64)
    content = serializers.CharField()

    def create(self, validated_data):
        user = self.context['request'].user
        title = validated_data['title']
        content = validated_data['content ']
        return Article.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.title = validated_data.get('title')
        instance.content = validated_data.get('content')
        instance.save()
        return instance

因为使用serializer,所以我们要实现create,update方法。而create中的参数都可以通过validated_data中获取 ,类似于form中的cleaned_data,而user则是通过上下文获取 。

至于update的前提则是已经获取到instance.。而create, update方法最后都可以返回instance,而这一点在view中调用了
serializer.is_valid()方法,此时就得到了instance对象。基于这一点,在使用ModelSerializer时,我们可以通过重写create, update, 或者perform_update,perform_create等方法,在判断is_valid()方法后获取到instance,在save之前可以对它进行操作,比如ser.save(user=user_obj).

核心参数

我们先看看serializer中的核心参数:

read_only:True表示不允许用户自己上传,只能用于api的输出。如果某个字段设置了read_only=True,那么就不需要进行数据验证,只会在返回时,将这个字段序列化后返回
write_only: 与read_only对应
required: 顾名思义,就是这个字段是否必填。
allow_null/allow_blank:是否允许为NULL/空 。
error_messages:出错时,信息提示,与form表单一样。
label: 字段显示设置,如 label=’验证码’
help_text: 在指定字段增加一些提示文字,这两个字段作用于api页面比较有用
style: 说明字段的类型,这样看可能比较抽象,如:
password = serializers.CharField(style={'input_type': 'password'})
validators:指定验证器。

validators

如果对django的form表单比较了解,可以很容易理解这些字段的意思。比如这里的validators,在form中也是存在的。

class UserSerializer(serializers.Serializer):
    phone = serializers.CharField(max_length=11, min_length=11)

这里的serializer虽然能提供简单的长度验证,但远远不够,此时我们就需要指定Validators:

def phone_validator(value):
    pattern = r"^1[3|4|5|6|7|8|9]\d{9}$"
    if not re.match(pattern, value):
        raise ValidationError('手机号格式错误')
    return value

class UserSerializer(serializers.Serializer):
    phone = serializers.CharField(max_length=11, min_length=11, validators=[phone_validator, ])

UniqueValidator

指定某一个对象是唯一的,如,电话号只能存在且唯一

phone = serializers.CharField(
    max_length=11,
    min_length=11,
    validators=[UniqueValidator(queryset=UserProfile.objects.all())
    )

UniqueTogetherValidator

queryset:required,用于明确验证唯一性集合,必须设置
fields: required,字段列表或者元组,字段必须是序列化类中存在的字段
message:当验证失败时的提示信息

UniqueTogetherValidator有一个隐性要求就是验证的字段必须要提供值,除非设置了一个默认值,并且它需要在Meta中设置:

比如要求用户昵称与邮箱联合唯一:

class UserSerializer(serializers.Serializer):
    class Meta:
        validators = [
            UniqueTogetherValidator(
                queryset=UserProfile.objects.all(),
                fields=('username', 'email')
            )
        ]

局部钩子validate_phone

这里以短信验证码登陆时的验证为例,我们在给用户发送短信验证码后会将它存入redis,当我们验证时,就需要与redis中进行对比,在form中我们获取初始数据是通过self.cleaned_data, 这里是通过self.initial_data,获取到phone然后去redis根据电话号取验证码,与用户传过来的进行对比。

def message_code_validator(value):
    if not value.isdecimal() or len(value) != 4:
        raise ValidationError('短信验证码格式错误')

class LoginSerializer(serializers.Serializer):
    phone = serializers.CharField(label='手机号', validators=[phone_validator, ])
    code = serializers.CharField(label="验证码", validators=[message_code_validator, ])

    def validate_code(self, value):
        phone = self.initial_data.get('phone')
        conn = get_redis_connection()
        code = conn.get(phone)
        if not code:
            raise ValidationError('短信验证码已失效')
        if value != code.decode('utf-8'):
            raise ValidationError('短信验证码错误')
        return value

全局钩子validate

class LoginSerializer(serializers.Serializer):
    password = serializers.CharField(label='密码', validators=[password_validator, ])
    password2 = serializers.CharField(label="验证密码", validators=[password_validator, ])

    def validate(self, value):
        password = self.initial_data.get('password')
        password2 = self.initial_data.get('password2')

        if password != password2:
            raise ValidationError('两次密码不一致')
        return value

serializer中的外键

外键关系在日常使用中非常常见的那么我们要怎么获取外键呢?

指定source

class ArticleSerializer(serializers.ModelSerializer):
    # 指定外键显示的字段来源,其中category是Article的一个字段
    category = serializers.CharField(source='category.name')

    class Meta:
        model = Article
        fields = "__all__" 

serializer嵌套

tag我们使用另一个serializer, TagSerializer. 这里因为是多对多,所以指定many=True

class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = '__all__'


class ArticleSerializer(serializers.ModelSerializer):
    # 指定外键显示的字段来源
    category = serializers.CharField(source='category.name')
    tag = TagSerializer(many=True)

    class Meta:
        model = Article
        fields = "__all__" 

指定depth

指定嵌套尝试是不推荐的方式。

class ArticleSerializer(serializers.ModelSerializer):

    class Meta:
        model = Article
        fields = "__all__"
        depth = 1

默认情况下depth=0, 即不会跨表取值 ,这里指定depth=1,它就会跨到category表中,把category表中的所有数据拿到(注意是所有,包括你并不需要的)。因为它会拿到你不需要的数据,如果尝试较深,就又比较影响效率,所以不推荐这种用法。

serializer中的choice

选择型字段也是非常常见的字段,为了方便存数据,我们一般不会存choice对应的文字,而是存一个数字,或者字符
那取值时要怎么取呢?

class Article(models.Model):
    """
    文章表
    """
    status_choices = (
        (1, '发表'),
        (2, '删除'),
    )
    status = models.IntegerField(verbose_name='状态', choices=status_choices, default=1)

source


class ArticleSerializer(seriliazers.Serializer):
    status_text = serializers.CharField(source='get_status_display')

serializers.MethodField

class ArticleSerializer(seriliazers.Serializer):
    status_text = serializers.SerializerMethodField()

    def get_status_text(self, obj):
        return obj.get_status_display()

可以看到这两种方法与form表单也保持了一致,都是通过get_status_display来实现的。其中第二种obj指的是当前article对象。

posted on 2020-02-24 17:48  Andy_963  阅读(595)  评论(0编辑  收藏  举报