版本

数据库设计

class UserGroup(models.Model):
    title = models.CharField(max_length=32)


class UserInfo(models.Model):
    user_type_choices = (
        (1, '普通用户'),
        (2, 'VIP'),
        (3, 'SVIP')
    )
    username = models.CharField(max_length=32, unique=True)
    group = models.ForeignKey('UserGroup')
    password = models.CharField(max_length=64)
    user_type = models.IntegerField(choices=user_type_choices)
    roles = models.ManyToManyField('Role')


class UserToken(models.Model):
    user = models.OneToOneField(to='UserInfo')
    token = models.CharField(max_length=64)


class Role(models.Model):
    title = models.CharField(max_length=32)

自定义类来解析版本

class ParamVersion(object):
    def determine_version(self, request, *args, **kwargs):
        # version = request._request.GET.get('version')
        version = request.query_params.get('version')
        return version


class UsersView(APIView):
    versioning_class = ParamVersion

    def get(self, request, *args, **kwargs):
        print(request.version)
        return HttpResponse('用户列表')

通过设置versioning_class,这不是一个列表,而是一个字符串,通过自定义版本类,可以拿到用户传来的版本
url调用方式:127.0.0.1:8000/api/users/?version=v2
image.png

通过在路径中传参

from rest_framework.versioning import URLPathVersioning
全局路由urls.py
完成了api路由的分发

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^api/', include('api.urls')),

]

api/urls.py

url(r'^(?P<version>[v1|v2]+)/users/$',views.UsersView.as_view(),name='user'),

settings.py进行全局配置

REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',
    'DEFAULT_VERSION':'v1',
    'ALLOWED_VERSIONS':['v1','v2'],
    'VERSION_PARAM':'version',
}

views.py
通过request.version获取

class UsersView(APIView):

    def get(self, request, *args, **kwargs):
        print(request.version)
        return HttpResponse('用户列表')

源码流程

通过dispatch函数,进入inital,inital函数中有这样一段代码

# Determine the API version, if versioning is in use.
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme

经过determine_version函数返回的值一个赋值给了request.version,另一个赋值给了request.versioning_scheme
determin_version方法里干了这些事

    def determine_version(self, request, *args, **kwargs):
        """
        If versioning is being used, then determine any API version for the
        incoming request. Returns a two-tuple of (version, versioning_scheme)
        """
        if self.versioning_class is None:
            return (None, None)
        scheme = self.versioning_class()
        return (scheme.determine_version(request, *args, **kwargs), scheme)

scheme得到版本类的实例对象,最后返回版本和版本实例

通过查看URLPathVersioning的源码,可以知道

invalid_version_message = _('Invalid version in URL path.')

    def determine_version(self, request, *args, **kwargs):
        version = kwargs.get(self.version_param, self.default_version)
        if version is None:
            version = self.default_version

        if not self.is_allowed_version(version):
            raise exceptions.NotFound(self.invalid_version_message)
        return version

    def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
        if request.version is not None:
            kwargs = {} if (kwargs is None) else kwargs
            kwargs[self.version_param] = request.version

        return super().reverse(
            viewname, args, kwargs, request, format, **extra
        )

reverse主要用来生成版本路由,就跟django.urls自带的reverse一样,传入viewname是视图名,即可生成url
u1url = request.versioning_scheme.reverse(``viewname``=``'user'``,``request``=request)
image.png

解析器

django request.POST/request.body

如果request.POST中要有值,必须满足一下两个要求:

  1. 请求头要求:如果请求头中是Content-Type:application/x-www-form-urlencoded,request.POST中才有值(去request.body中解析数据)
  2. 数据格式要求:name=alex&age=18&gender=男

restful framework解析器

对请求体数据进行解析

使用:

如果要全局配置就在settings.py中做出如下配置:

REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES':['rest_framework.parsers.JSONParser','rest_framework.parsers.FormParser']
}

如果单单针对某个类使用特定的解析器,可以通过设置parser_classes = [JSONParser,FormParser]
来解决

整体处理流程

class ParserView(APIView):
    # parser_classes = [JSONParser,FormParser]
    """
    JSONParser能解析Content-Type:application/json
    FormParser能解析Content-Type:x-www-form-urlencoded
    """
    def post(self, request, *args, **kwargs):
        """
        1.获取用户请求头
        2.获取用户请求体
        3.根据用户的请求头和 parser_classes = [JSONParser,FormParser]中支持的请求头进行比较,哪个符合由谁来处理
        4.由对应的类去解析
        5.将数据返回给request.data
        """
        # 获取解析后的结果
        print(request.data)
        self.dispatch()
        return HttpResponse('ParserView')

源码分析:

依旧是通过dispatch进入,通过封装的request中可以看到

return Request(
            request,
            parsers=self.get_parsers(),  # 封装了所有的解析器
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

parsers就代表解析器类的实例化列表对象,get_authenticators方法里面就是

    def get_parsers(self):
        """
        Instantiates and returns the list of parsers that this view can use.
        """
        return [parser() for parser in self.parser_classes]

当我们调用request.data这个方法的时候,会重新执行parser方法

@property
def data(self):
    if not _hasattr(self, '_full_data'):
        self._load_data_and_files()
    return self._full_data

在这个方法其中,执行_load_data_and_files(),再执行_parse()方法

本质就是根据Content-Type的不同,让不同的解析器来处理post过来的数据

序列化

  1. 对请求数据的验证
  2. 对queryset的序列化

基于serializers.Serializer

from rest_framework import serializers
class UserInfoSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()
    user_type = serializers.CharField(source='get_userinfo_type_display')
    # user_type = serializers.SerializerMethodField()
    group = serializers.CharField(source='group.title')
    love = serializers.SerializerMethodField()  # 自定义显示

    def get_user_type(self, row):
        roles = row.userinfo_type
        return UserInfo.user_type_choices[int(roles)-1][1]


    def get_love(self, row):
        roles = row.roles.all()
        ret = []
        for item in roles:
            ret.append({'id':item.id,'title':item.title})
        return ret


class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):
        users = models.UserInfo.objects.all()
        ser = UserInfoSerializer(instance=users, many=True)
        print(ser.data)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

其中因为user_type是个choice展示的是数字,所以当我们想展示它所对应的用户类型时,有两种办法。

  1. 通过在Serializer括号内指定source=get__display方法,如果modelname是user_type,则为get_user_type_display,如果modelname是userinfo_type则为get_userinfo_type_display
  2. 通过serializers.SerializerMethodField()编写自定义类来实现,下面要指定好方法,方法名为get_{field_name} field_name为serializers.SerializerMethodField()对象名。

基于serializers.ModelSerializer

ModelSerializer是继承自Serializer,其中它可以设置

class Meta:
    model = models.UserInfo
    # fields = "__all__"

fields= "all"代表可直接把models里面的所有对象序列化出来
这里如果想自定义可以这么做

class UserInfoSerializer(serializers.ModelSerializer):
    user_type = serializers.CharField(source='get_userinfo_type_display')
    love = serializers.SerializerMethodField()  # 自定义显示
    group = serializers.CharField(source='group.title')

    def get_love(self, row):
        roles = row.roles.all()
        ret = []
        for item in roles:
            ret.append({'id': item.id, 'title': item.title})
        return ret

    class Meta:
        model = models.UserInfo
        # fields = "__all__"
        fields = ['id', 'username', 'password', 'group', 'user_type', 'love']

只需要把需要自定义的单独进行序列化即可

定制序列化深度

class UserInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.UserInfo
        fields = "__all__"
        depth = 1  # 建议在0~10

我们可以通过定制序列化深度来获取到值
结果如图,可以自动连表。
image.png

serializers.HyperlinkedIdentityField

现在出现一个这样的需求,我们需要在group上面直接显示group的url,例如
image.png
这时候就需要用到serializers.HyperlinkedIdentityField这个方法,首先我们需要在url中定制我们的group的url

url(r'^(?P<version>[v1|v2]+)/group/(?P<pk>\d+)',views.GroupView.as_view(),name='group'),

views.py中分别写好类以及序列化的类

class GroupSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.UserGroup
        fields = "__all__"


class GroupView(APIView):
    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        obj = models.UserGroup.objects.filter(pk=pk).first()
        ser = GroupSerializer(instance=obj, many=True)
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

其中我们通过kwargs拿到我们的pk的值,在数据库中拿到queyset对象,进行序列化最后返回
因为需要定制我们的UserInfo的序列号数据,所以接下来的更改在UserInfo中

class UserInfoSerializer(serializers.ModelSerializer):
    """
    这里生成的链接是根据PK来生成的,所以如果不指定lookup_field和lookup_url_kwarg,会出现bug:http://127.0.0.1:8000/api/v1/group/1/和http://127.0.0.1:8000/api/v1/group/2
    但是组是只有一个的,具体分析在源码讲解
    """
    group = serializers.HyperlinkedIdentityField(view_name='group', lookup_field='group_id',lookup_url_kwarg='pk')

    class Meta:
        model = models.UserInfo
        fields = "__all__"
        depth = 1


class UserInfoView(APIView):
    def get(self, request, *args, **kwargs):
        users = models.UserInfo.objects.all()
        ser = UserInfoSerializer(
            instance=users, many=True, context={
                'request': request})
        ret = json.dumps(ser.data, ensure_ascii=False)
        return HttpResponse(ret)

其中view_name代表根据我们在url中配置好的name反向生成url。

AssertionError: HyperlinkedIdentityField requires the request in the serializer context. Add context={'request': request} when instantiating the serializer.

这个报错代表当我们需要进行HyperlinkedIdentityField序列化的时候,我们需要在初始化序列化的时候添加context={'request': request}

源码

对比两段代码

"""
UserInfo对于数据序列化的处理
"""
users = models.UserInfo.objects.all()
ser = UserInfoSerializer(
   instance=users, many=True, context={
      'request': request})


"""
UserGroup对于数据序列化的处理
"""
obj = models.UserGroup.objects.filter(pk=pk).first()
ser = GroupSerializer(instance=obj, many=False)

两者的不同在于第一个users是一个queryset,第二个obj是一个对象
在通过源码分析,new方法的执行在init之前决定了决定是否要使用该 init() 方法,因为new() 可以调用其他类的构造方法或者直接返回别的对象来作为本类的实例。

    def __new__(cls, *args, **kwargs):
        # We override this method in order to automagically create
        # `ListSerializer` classes instead when `many=True` is set.
        if kwargs.pop('many', False):
            return cls.many_init(*args, **kwargs)
        return super().__new__(cls, *args, **kwargs)

对于如果设置了many=True,就会执行cls.many_init(*args, **kwargs),而如果many=False,则直接调用本类的__init__方法

在many_init方法中

@classmethod
    def many_init(cls, *args, **kwargs):
        """
        This method implements the creation of a `ListSerializer` parent
        class when `many=True` is used. You can customize it if you need to
        control which keyword arguments are passed to the parent, and
        which are passed to the child.

        Note that we're over-cautious in passing most arguments to both parent
        and child classes in order to try to cover the general case. If you're
        overriding this method you'll probably want something much simpler, eg:

        @classmethod
        def many_init(cls, *args, **kwargs):
            kwargs['child'] = cls()
            return CustomListSerializer(*args, **kwargs)
        """
        allow_empty = kwargs.pop('allow_empty', None)
        child_serializer = cls(*args, **kwargs)
        list_kwargs = {
            'child': child_serializer,
        }
        if allow_empty is not None:
            list_kwargs['allow_empty'] = allow_empty
        list_kwargs.update({
            key: value for key, value in kwargs.items()
            if key in LIST_SERIALIZER_KWARGS
        })
        meta = getattr(cls, 'Meta', None)
        list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer)
        return list_serializer_class(*args, **list_kwargs)

通过简介和代码可以看到实际是交给了ListSerializer进行处理的。

所以对于
对象:Serializer类处理  通过self.to_representation处理
QuerySet:ListSerializer类处理  通过self.to_representation处理

由于to_representation在本类没有,所以需要去它的父类进行寻找ModelSerializer,ModelSerializer里面也没有这个方法,再去它的父类再次寻找,进入Serializer,可以看到是有这个方法的。

    def to_representation(self, instance):
        """
        Object instance -> Dict of primitive datatypes.
        """
        ret = OrderedDict()
        fields = self._readable_fields

        for field in fields:
            try:
                attribute = field.get_attribute(instance)
            except SkipField:
                continue

            # We skip `to_representation` for `None` values so that fields do
            # not have to explicitly deal with that case.
            #
            # For related fields with `use_pk_only_optimization` we need to
            # resolve the pk value.
            check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
            if check_for_none is None:
                ret[field.field_name] = None
            else:
                ret[field.field_name] = field.to_representation(attribute)

        return ret

其中fields代表了我们在序列化类里面定义的序列化字段,通过循环,调用field.get_attribute(instance)其中instance就是我们在调用时传入的instance参数

我们通过查看get_attribute的代码

    def get_attribute(self, instance):
        """
        Given the *outgoing* object instance, return the primitive value
        that should be used for this field.
        """
        try:
            #instance是对象
            #source_attrs: 可以是group.title / get_user_type_display / roles.all 等等
            return get_attribute(instance, self.source_attrs)  
        。。。。。

其中get_attribute帮我们做了,循环source_attrs

def get_attribute(instance, attrs):
    """
    Similar to Python's built in `getattr(instance, attr)`,
    but takes a list of nested attributes, instead of a single attribute.

    Also accepts either attribute lookup on objects or dictionary lookups.
    """
    # attrs = [group, title] / get_user_type_display / roles.all
    for attr in attrs:
        try:
            if isinstance(instance, Mapping):
                instance = instance[attr]
            else:
                # 获取到instance对象的attr属性
                instance = getattr(instance, attr)
        except ObjectDoesNotExist:
            return None
        # 如果是可调用的就加上(),否则就是不加
        if is_simple_callable(instance):
            try:
                instance = instance()
            except (AttributeError, KeyError) as exc:
                # If we raised an Attribute or KeyError here it'd get treated
                # as an omitted field in `Field.get_attribute()`. Instead we
                # raise a ValueError to ensure the exception is not masked.
                raise ValueError('Exception raised in callable attribute "{}"; original exception was: {}'.format(attr, exc))

    return instance

验证用户请求数据

我们在api/urls.py中重新定义一个url

url(r'^(?P<version>[v1|v2]+)/usergroup/$',views.UserGroupView.as_view(),name='usergroup'),

同时我们编写它的类和序列化类:

class UserGroupSerializer(serializers.Serializer):
    title = serializers.CharField(error_messages={'required':"标题不能为空"},validators=[PostCheck('yang')])


class UserGroupView(APIView):
    """
    验证
    """
    def post(self, request, *args, **kwargs):
        ser = UserGroupSerializer(data=request.data)
        if ser.is_valid():
            print(ser.validated_data['title'])
        else:
            print(ser.errors)
        return HttpResponse('提交数据')

其中我们在序列化类中声明了错误信息,同时在UserGroupView这个类中,我们通过is_valid方法如果数据有效,我们就打印获得数据,否则输出错误方法

在序列话类中可以通过设置validators参数,来实现自己的验证函数

class PostCheck(object):
    def __init__(self, base):
        self.base = base

    def __call__(self,value):
        if not value.startswith(self.base):
            message = "this field must start with %s" % self.base
            raise serializers.ValidationError(message)

这里我写了所有提交上来的数据必须以'yang'开头否则不能通过

最后的验证结果,分别提交了不是yang开头和是yang开头的
image.png

钩子函数

因为判断是否有效是通过is_valid来判断的,所以我们找源码就从这里入手
is_valid函数里面运行了run_validate函数

    def run_validation(self, data=empty):
        """
        Validate a simple representation and return the internal value.

        The provided data may be `empty` if no representation was included
        in the input.

        May raise `SkipField` if the field should not be included in the
        validated data.
        """
        (is_empty_value, data) = self.validate_empty_values(data)
        if is_empty_value:
            return data
        value = self.to_internal_value(data)
        self.run_validators(value)
        return value

run_validate函数中,最后通过to_interval_value将值赋值给了value,进入to_interval_value
发现它只返回了一个异常,所以我们需要到父类中查找,进入serializers.Serializer可以查找这个方法

    def to_internal_value(self, data):
        """
        Dict of native values <- Dict of primitive datatypes.
        """
        。。。
        ret = OrderedDict()
        errors = OrderedDict()
        fields = self._writable_fields

        for field in fields:
            # 查找钩子函数
            validate_method = getattr(self, 'validate_' + field.field_name, None)
            primitive_value = field.get_value(data)
            try:
                validated_value = field.run_validation(primitive_value)
                # 执行钩子函数
                if validate_method is not None:
                    validated_value = validate_method(validated_value)
            except ValidationError as exc:
                errors[field.field_name] = exc.detail
            except DjangoValidationError as exc:
                errors[field.field_name] = get_error_detail(exc)
            except SkipField:
                pass
            else:
                set_value(ret, field.source_attrs, validated_value)

        if errors:
            raise ValidationError(errors)

        return ret

所以钩子函数可以通过validate_来定义

对于刚刚想要post上来的数据必须以'yang'开头同样还可以通过钩子函数来实现

class UserGroupSerializer(serializers.Serializer):
    title = serializers.CharField(error_messages={'required':"标题不能为空"})

    def validate_title(self, value):
        if not value.startswith('yang'):
            message = "this field must start with yang"
            raise serializers.ValidationError(message)
        else:
            return value

钩子函数从源码可以看出如果验证通过,必须return value,验证不通过可以通过抛出异常的方式。因为return回来的value是赋值给validate_value最后传到我们的类里面的。如果return 'hahahah'最后收到的数据也就会是'hahaha'

版权声明:本文为原创文章,版权归 heroyf 所有。
本文链接: https://heroyf.club/2019/07/django_learn5/


“苹果是给那些为了爱选择死亡的人的奖励”