django DRF理解

django restframework(DRF)

最近的开发过程当中,发现restframework的功能很强大,所以尝试解读了一下源码,写篇博客分享给大家,有错误的地方还请各位多多指出
禁止盗图

视图部分

视图部分,主要负责查询方法,在编写代码的过程当中,按照具体功能和请求动作进行了拆分,方便开发者进行自定义的拼接。

mixin

Mixin 即 Mix-in,常被译为“混入”,是一种编程模式,

像C或C++这类语言都支持多重继承,一个子类可以有多个父类,这样的设计常被人诟病。因为继承应该是个”is-a”关系。比如轿车类继承交通工具类,因为轿车是一个(“is-a”)交通工具。一个物品不可能是多种不同的东西,因此就不应该存在多重继承。不过有没有这种情况,一个类的确是需要继承多个类呢?(这段是网络上一个老大哥的话,我这里引用一下,感觉特别有道理)

功能部分

功能部分来源于restframework定义的mixin文件当中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EsyPJDfK-1608107829157)(file:///C:/Users/dell/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg)]

翻译:基于类的通用视图的基本构建块。 我们还没有将行为绑定到http方法处理程序, 它允许mixin类以有趣的方式组合。

在这里提供了下面的几个方法,这些方法无论在自定义接口或者DRF操作的时候都在平凡的继承使用,为开发者提供了更加灵活的开发类拼接方式。但是要注意的是这里并没有强调具体的请求方式,比如GET请求进行数据展示,所以给开发者更细粒度的类拼接开发可能,下面是mixin包含的所有方法:

具体功能

我们首先列出这些方法的功能:

CreateModelMixin: 创建一个模型实例。

ListModelMixin: 列出一个数据,返回的格式是django熟悉的queryset格式,注意,在这里并没有返回字典或者JSON,这部分工作并不归他管理。

RetrieveModelMixin:检索一个数据。

UpdateModelMixin:跟新一个模型数据。

DestroyModelMixin: 摧毁一个数据模型,注意这里是摧毁而不是删除,所以想要逻辑删除的小伙伴要注意了

源码分析

我们以CreateModelMixin的源码为例子,来看一下mixin的功能

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 {}
1、serializer = self.get_serializer(data=request.data) 

在这段代码当中,可能好多小伙伴会很疑惑,当前这个文件很简单,但是有很多方法实际上现在是找不到的,比如get_serializer这个方法,实际上,在这个文件当中确实没有,这个方法来源与DRF的另外的一个模块,数据序列化的方法,rest_framework.generics.GenericAPIView:

def get_serializer(self, *args, **kwargs):
    """
    Return the serializer instance that should be used for validating and
    deserializing input, and for serializing output.
  	返回一个用于序列化和反序列化输入的可以进行数据校验的序列化实例。
    """
    serializer_class = self.get_serializer_class()
    kwargs.setdefault('context', self.get_serializer_context())
    return serializer_class(*args, **kwargs)

接着看

2、serializer.is_valid(raise_exception=True) #raise_exception 是否引发异常,默认为False,源码如下:
def is_valid(self, raise_exception=False):
    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)

然后,这句就很好理解了,就是进行数据校验的过程。

3、self.perform_create(serializer)

这句就是当前类编写的一个保存方法

4、
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

这里就是对保存后的数据头部和响应的包装,有兴趣的同学可以看一声Response的源码:

"""
The Response class in REST framework is similar to HTTPResponse, except that
it is initialized with unrendered data, instead of a pre-rendered string.
REST框架中的响应类类似于HTTPResponse,除了它是用未呈现的数据而不是预先呈现的字符串初始化的。

The appropriate renderer is called during Django's template response rendering.
在Django的模板响应呈现期间调用适当的呈现器,比如json
"""
from http.client import responses

from django.template.response import SimpleTemplateResponse

from rest_framework.serializers import Serializer


class Response(SimpleTemplateResponse):
    """
    An HttpResponse that allows its data to be rendered into
    arbitrary media types.
    """

    def __init__(self, data=None, status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None):
        """
        Alters the init arguments slightly.
        For example, drop 'template_name', and instead use 'data'.

        Setting 'renderer' and 'media_type' will typically be deferred,
        For example being set automatically by the `APIView`.
        """
        super().__init__(None, status=status)

        if isinstance(data, Serializer):
            msg = (
                'You passed a Serializer instance as data, but '
                'probably meant to pass serialized `.data` or '
                '`.error`. representation.'
            )
            raise AssertionError(msg)

        self.data = data
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

        if headers:
            for name, value in headers.items():
                self[name] = value

    @property
    def rendered_content(self):
        renderer = getattr(self, 'accepted_renderer', None)
        accepted_media_type = getattr(self, 'accepted_media_type', None)
        context = getattr(self, 'renderer_context', None)

        assert renderer, ".accepted_renderer not set on Response"
        assert accepted_media_type, ".accepted_media_type not set on Response"
        assert context is not None, ".renderer_context not set on Response"
        context['response'] = self

        media_type = renderer.media_type
        charset = renderer.charset
        content_type = self.content_type

        if content_type is None and charset is not None:
            content_type = "{}; charset={}".format(media_type, charset)
        elif content_type is None:
            content_type = media_type
        self['Content-Type'] = content_type

        ret = renderer.render(self.data, accepted_media_type, context)
        if isinstance(ret, str):
            assert charset, (
                'renderer returned unicode, and did not specify '
                'a charset value.'
            )
            return ret.encode(charset)

        if not ret:
            del self['Content-Type']

        return ret

    @property
    def status_text(self):
        """
        Returns reason text corresponding to our HTTP response status code.
        Provided for convenience.
        """
        return responses.get(self.status_code, '')

    def __getstate__(self):
        """
        Remove attributes from the response that shouldn't be cached.
        """
        state = super().__getstate__()
        for key in (
            'accepted_renderer', 'renderer_context', 'resolver_match',
            'client', 'request', 'json', 'wsgi_request'
        ):
            if key in state:
                del state[key]
        state['_closable_objects'] = []
        return state

如果还有小伙伴还想了解更加详细的数据,这个时候建议大家编写一个小的案例,然后断点一次,嘿嘿嘿

动作部分

由于DRF在类的编写力度上变的的很小,所以动作部分有了好多的方法,我们先大概的拆分一下这个继承的结构:

这里还是需要按照功能和继承的脉络来细分以下(嘿嘿嘿,这个也行就是面向对象的特点,你不得不花时间区分各个类的类型,他是那一伙的其实很重要):

首先DRF是基于Django的接口操作插件,所以沿袭了django的django.views.View方法,这个方法其实其他小伙伴并不陌生,就是Django CBV的核心,类视图的基类(如果这里还是有疑惑的小伙伴,建议去查看类视图编写博客)

restframework.views.APIView

基于django.views.View DRF定义了第一个类,restframework.views.APIView:

这个类实现了基于DRF全局使用的那些方法,比如:

class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES 
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

restframework.viewsets.ViewSetMixin

接着定义了另外的一个更加灵活的方法,restframework.views.APIView:

这是一个魔术方法,重写了as_view方法以便可以传递一个动作关键字来执行绑定http方法到资源上的操作

比如 创建绑定get和post请求方法的的视图方法 list和create

class ViewSetMixin:
    """
    This is the magic.

    Overrides `.as_view()` so that it takes an `actions` keyword that performs
    the binding of HTTP methods to actions on the Resource.

    For example, to create a concrete view binding the 'GET' and 'POST' methods
    to the 'list' and 'create' actions...

    view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
    """

    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
        """
        Because of the way class based views create a closure around the
        instantiated view, we need to totally reimplement `.as_view`,
        and slightly modify the view function that is created and returned.
        """
        # The name and description initkwargs may be explicitly overridden for
        # certain route configurations. eg, names of extra actions.
        cls.name = None
        cls.description = None

        # The suffix initkwarg is reserved for displaying the viewset type.
        # This initkwarg should have no effect if the name is provided.
        # eg. 'List' or 'Instance'.
        cls.suffix = None

        # The detail initkwarg is reserved for introspecting the viewset type.
        cls.detail = None

        # Setting a basename allows a view to reverse its action urls. This
        # value is provided by the router through the initkwargs.
        cls.basename = None

        # actions must not be empty
        if not actions:
            raise TypeError("The `actions` argument must be provided when "
                            "calling `.as_view()` on a ViewSet. For example "
                            "`.as_view({'get': 'list'})`")

        # sanitize keyword arguments
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r" % (
                    cls.__name__, key))

        # name and suffix are mutually exclusive
        if 'name' in initkwargs and 'suffix' in initkwargs:
            raise TypeError("%s() received both `name` and `suffix`, which are "
                            "mutually exclusive arguments." % (cls.__name__))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)

            if 'get' in actions and 'head' not in actions:
                actions['head'] = actions['get']

            # We also store the mapping of request methods to actions,
            # so that we can later set the action attribute.
            # eg. `self.action = 'list'` on an incoming GET request.
            self.action_map = actions

            # Bind methods to actions
            # This is the bit that's different to a standard view
            for method, action in actions.items():
                handler = getattr(self, action)
                setattr(self, method, handler)

            self.request = request
            self.args = args
            self.kwargs = kwargs

            # And continue as usual
            return self.dispatch(request, *args, **kwargs)

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())

        # We need to set these on the view function, so that breadcrumb
        # generation can pick out these bits of information from a
        # resolved URL.
        view.cls = cls
        view.initkwargs = initkwargs
        view.actions = actions
        return csrf_exempt(view)
        ........

restframework.generics.GenericAPIView

一个所有通用类的基类,这里包含这所有在快速开发涉及到的参数在简单的功能当中,不太建议去重写这个功能,当然如果从写注意下面的注释

class GenericAPIView(views.APIView):
    """
    Base class for all other generic views.
    """
    # You'll need to either set these attributes, #你需要设置一些方法或者从写get_queryset,get_serializer_class这两个方法
    # or override `get_queryset()`/`get_serializer_class()`.                                 

    # If you are overriding a view method, it is important that you call #如果要重写视图方法,则调用get_queryset()而不是直接访 
    # `get_queryset()` instead of accessing the `queryset` property directly, #问'queryset'属性,因为“queryset”只计算一次,并且对于所有
    # as `queryset` will get evaluated only once, and those results are cached #后续请求缓存这些结果。
    # for all subsequent requests.
    queryset = None
    serializer_class = None

    # If you want to use object lookups other than pk, set 'lookup_field'.
    # For more complex lookup requirements override `get_object()`.
    lookup_field = 'pk'
    lookup_url_kwarg = None

    # The filter backend classes to use for queryset filtering
    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS

    # The style to use for queryset pagination.
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS

restframework.viewsets.ViewSet

这是一个不提供任何操作的类视图,基于restframework.views.APIView和restframework.viewsets.ViewSetMixin,所以在路由上需要指出请求方式和对应的处理方法,至于其他的就需要自己编写了,当然这个过程会比较繁琐。但是开发的空间足够大。

测试案例需要如下编写

视图

from rest_framework.viewsets import ViewSet
from rest_framework.response import Response

class OurViewSet(ViewSet):
    def get(self,request):
        data = Tc.objects.all()[:100]
        serializer = CarSerializers(instance=data,many=True)
        return Response(data = serializer.data)

路由

from Api.views import *
urlpatterns += [
    path('ovt/', OurViewSet.as_view({'get': 'get'})),
]

restframework.generics.ListAPIView

这一系列的方法,已经进行了更加完整的拼接,分为了两个部分,一部分描述请求的类型,另外一部分描述处理的方式,而处理的方式来源于ListMixin部分,数据和配置来源与GenericAPIView,所以需要进行一部分配置

源码:
class ListAPIView(mixins.ListModelMixin,
                  GenericAPIView):
    """
    Concrete view for listing a queryset.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
案例:

视图

from rest_framework.generics import ListAPIView

class OurViewSet_v1(ListAPIView):
    queryset = Tc.objects.all()[:5]
    serializer_class = CarSerializers

路由

from Api.views import *
urlpatterns += [
    path('ovt_v1/', OurViewSet_v1.as_view())
]
其他类似功能
方法 描述
restframework.generics.RetrieveAPIView 检索的视图
restframework.generics.CreateAPIView 创建实例的视图
restframework.generics.UpdateAPIView 修改实例的视图
restframework.generics.DestroyAPIView 删除实例的视图
restframework.generics.ListCreateAPIView 展示和创建视图
restframework.generics.RetrieveUpdateAPIView 检索和修改的视图
restframework.generics.RetrieveDestroyAPIView 检索和删除的视图
restframework.generics.RetrieveUpdateDestroyAPIView 检索,修改,删除的视图
GenericAPIView 参数
参数 描述
queryset 查询结果集,比如:PostNumber.objects.all()
serializer_class 序列化类, 比如:在serializer当中定义的数据
lookup_field 搜索的关键字,lookup_filed可以改变retrive查询时默认以pk查询的逻辑,如果要使用pk以外的对象查找,请设置lookup_field,对于更复杂的查找要求,请重写“get_object(),所谓retrive就是获取单条数据的查询
lookup_url_kwarg 查询单一数据时URL中的参数关键字名称,默认与look_field相同
filter_backends 过滤器后端,结合django_filter或者自己定义过滤条件,默认加载settings当中的DEFAULT_FILTER_BACKENDS配置
pagination_class 分页配置,djangoresetframework有三个分页类,默认加载settings当中的DEFAULT_PAGINATION_CLASS配置

序列化部分

序列化部分分为序列化和反序列化,数据校验功能,整体的功能类似django原生的Form类

class CarSerializers_v1(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField(max_length=255)
    year = serializers.CharField(max_length=20)
    kil = serializers.CharField(max_length=20)
    area = serializers.CharField(max_length=20)
    ori_price = serializers.CharField(max_length=20)
    price = serializers.CharField(max_length=20)
    img_url = serializers.CharField(max_length=255)

常用字段

字段 描述
BooleanField 布尔类型
NullBooleanField 可以为空的布尔类型,但是在3.14版本之后肯能会被移除,之后可以在BooleanField当中使用null=True来标识可以为空的布尔值
CharField 字符串格式
EmailField 邮件格式
RegexField 正则格式
SlugField 缩略名格式
URLField URL格式
UUIDField UUID格式
IPAddressField IP地址格式
IntegerField 整数格式
FloatField 小数格式
DecimalField 十进制数字校验
DateTimeField 时间个数,年月日时分秒
DateField 时间格式,年月日
TimeField 时间格式,时分秒
DurationField 自定义时间格式,可以通过 max_value 和 min_value来限定时间的范围
ChoiceField 选项格式,必须符合选项,通过choices来设定选择项可以设置空选
MultipleChoiceField 多选字段,必须在多选范围,通过choices来设定选择项,allow_blank可以设置空选
FilePathField 文件路径格式
FileField 文件格式
ImageField 图片文件格式
ListField 列表类型,比如多图片上传
DictField 字典类型
HStoreField 关键字类型,有sql注入的危险
JSONField json格式,有sql注入的危险
ReadOnlyField 只读字段,用于返回数据,比如外键字段要返回的内容
HiddenField 隐藏域类型
SerializerMethodField 序列化字段
ModelField 模型字段

自定义校验

validate_字段
class CarSerializers_v1(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField(max_length=255)
    year = serializers.CharField(max_length=20)
    kil = serializers.CharField(max_length=20)
    area = serializers.CharField(max_length=20)
    ori_price = serializers.CharField(max_length=20)
    price = serializers.CharField(max_length=20)
    img_url = serializers.CharField(max_length=255)

    def validate_title(self,value):
        if len(value) > 100:
            raise serializers.ValidationError("名称太长了")
validate
class CarSerializers_v2(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField(max_length=255)
    year = serializers.CharField(max_length=20)
    kil = serializers.CharField(max_length=20)
    area = serializers.CharField(max_length=20)
    ori_price = serializers.CharField(max_length=20)
    price = serializers.CharField(max_length=20)
    img_url = serializers.CharField(max_length=255)


    def validate(self, attrs):
        title = attrs['title']
        if len(title) > 100:
            raise serializers.ValidationError("名称太长了")

validators方法

def about_title(value):
    if len(value) > 100:
        raise serializers.ValidationError("名称太长了")

class CarSerializers_v3(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField(max_length=255,validators = [about_title])
    year = serializers.CharField(max_length=20)
    kil = serializers.CharField(max_length=20)
    area = serializers.CharField(max_length=20)
    ori_price = serializers.CharField(max_length=20)
    price = serializers.CharField(max_length=20)
    img_url = serializers.CharField(max_length=255)

validators方法

def about_title(value):
    if len(value) > 100:
        raise serializers.ValidationError("名称太长了")

class CarSerializers_v3(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField(max_length=255,validators = [about_title])
    year = serializers.CharField(max_length=20)
    kil = serializers.CharField(max_length=20)
    area = serializers.CharField(max_length=20)
    ori_price = serializers.CharField(max_length=20)
    price = serializers.CharField(max_length=20)
    img_url = serializers.CharField(max_length=255)

这里简单的讲了django DRF的基本功能,还有分页,排序,校验等功能需要完善,准备结合一篇数据融入案例进行分享。

原文地址:https://www.cnblogs.com/bianjinhui/p/14144791.html