• DRF之serializer


    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.
    Original exception was:
     %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对象。

  • 相关阅读:
    perf + 火焰图用法 小结
    忽略多年的地理基本知识
    windows7安装docker异常:looks like something went wrong in step ‘looking for vboxmanage.exe’
    我的选择
    CSS3 width的min/max-content、fill-available以及fit-content
    Redis入门与命令汇总
    javascript中的原型详解
    Promise实现及原理
    nodejs中的垃圾回收
    javascript中的闭包
  • 原文地址:https://www.cnblogs.com/Andy963/p/12357945.html
Copyright © 2020-2023  润新知