zoukankan      html  css  js  c++  java
  • 【Django】DRF源码分析对比原生Django

    纸上得来终觉浅,绝知此事要躬行。

    前言

    今天就来谈谈如何从Django过渡到Django REST framework,之所以要用Django REST framework,因为它解决了Django在实现前后端分离项目开发时的复杂性,其由于强大的序列化器、反序列化器、认证组件、权限组件、限流组件以及各种视图集很方便的实现各种符合Restful风格的接口。

    那么接下来让我们一起来探索这一过程,本章主要用来回顾以及对比DjangoDjango REST framework接口设计实现的差异,如果你已经了解可直接看其他章节的内容。

    回顾原生Django

    首先新建Django项目,之后创建一个app名称为api,包括一些数据库的配置、创建超级用户等等,在此就不做演示,直接从代码定义开始:

    • 定义模型类
    class UserModel(models.Model):
        GENDER = (
            (0, '女'),
            (1, '男')
        )
    
        username = models.CharField(max_length=64)
        password = models.CharField(max_length=32)
        phone = models.CharField(max_length=11, null=True, default=None)
        gender = models.IntegerField(choices=GENDER, default=1)
        avatar = models.ImageField(upload_to='avatar', default='avatar/default.jpeg')
    
        class Meta:
            db_table = "user" # 设置数据库表名
            verbose_name = "用户" # 设置后台
            verbose_name_plural = verbose_name
    
        def __str__(self):
            return self.username
    
    • 主路由urls
    from django.conf.urls import url
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        url('^api/', include('api.urls')),
    ]
    
    • 应用api下的路由urls
    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        url(r'^users/$', views.User.as_view()),
        url(r'^users/(?P<pk>d+)/$', views.User.as_view()),
    ]
    
    • CBV视图编写
    class User(View):
        def get(self, request, *args, **kwargs):
            print(args)  # ()
            print(kwargs)  # {'pk': '1'}
            pk = kwargs.get("pk")
    
            resp = {
                "status": 0,
                "message": "ok",
                "results": {}
            }
    
            if not pk:
                user_info = []
                users = UserModel.objects.all()
                for user in users:
                    dic = {
                        "username": user.username,
                        "phone": user.phone,
                        "gender": user.get_gender_display(),
                        "icon": str(user.icon)
                    }
                    user_info.append(dic)
                resp["results"] = user_info
            else:
                user = UserModel.objects.filter(pk=pk).first()
                resp["results"] = {
                    "username": user.username,
                    "phone": user.phone,
                    "gender": user.get_gender_display(),
                    "icon": str(user.icon)
                }
            # JsonResponse("ok", safe=false) 返回字符串,safe需要等于false,默认为True,要求参数为字典
            return JsonResponse(resp, json_dumps_params={'ensure_ascii': False})
    
        def post(self, request, *args, **kwargs):
            pass
    

    写了一个比较简单的获取用户的类视图,不够完善,异常也没有处理,主要是想回顾一下原生Django的写法,不作为重点。

    • 单个用户请求url地址为:http://127.0.0.1:8000/api/users/1/,下面是返回的结果:
    {
        "status": 0,
        "message": "ok",
        "results": {
            "username": "Tom",
            "phone": "22222222222",
            "gender": "男",
            "icon": "icon/home_t1aBbSD.jpg"
        }
    }
    
    • 多个用户请求url地址:http://127.0.0.1:8000/api/users/,下面是返回的结果:
    {
        "status": 0,
        "message": "ok",
        "results": [
            {
                "username": "Tom",
                "phone": "22222222222",
                "gender": "男",
                "icon": "icon/home_t1aBbSD.jpg"
            },
            {
                "username": "Jack",
                "phone": "11111111111",
                "gender": "女",
                "icon": "icon/logo_l7UKz0x.png"
            }
        ]
    }
    

    原生Django CBV请求源码分析

    1. 直接从应用的urls.py中的as_view()入手,进入as_view()的源码,

    应用下的urls

    1. 进入as_view方法发现是一个闭包,执行一些参数校验直接返回view方法的引用,也就是说url匹配的第二个参数其实就是view

    源码路径(django/views/generic/base.py) as_view()方法

    思考:as_view()方法返回的view何时被执行?下面我们来查看这一过程,我们把url改成了下面的方式,让as_view()返回的view赋给handle,然后把handle作为url的参数传递。

    from django.conf.urls import url
    from . import views
    
    print(views.User.as_view())
    handle = views.User.as_view() # 这个handle就是闭包返回的view
    urlpatterns = [
        url(r'^users/$', handle),
        url(r'^users/(?P<pk>d+)/$', views.User.as_view()),
    ]
    

    进入url的源码,发现调用的是re_path,这是Django为我们做了一层封装,其实可以直接用re_path进行路由匹配,接着我们通过打印,发现返回的是一个URLPattern类的实例:

    def url(regex, view, kwargs=None, name=None):
        print("=========", re_path(regex, view, kwargs, name))
        return re_path(regex, view, kwargs, name)
    
    ========= <URLPattern '^users/$'>
    ========= <URLPattern '^users/(?P<pk>d+)/$'>
    

    接着进入源码django/urls/resolvers.py找到URLPattern类,发现我们的在url中的参数全部传递到这里,至于最开始说的as_view闭包返回的view就传递给了callback,也就是我们的handle传递给了callback,每次当请求来临时,url解析器完成url的解析,匹配到相应的回调函数,然后执行(至于什么时候执行就不在深入了)。

    class URLPattern:
        def __init__(self, pattern, callback, default_args=None, name=None):
            self.pattern = pattern
            self.callback = callback  # the view
            self.default_args = default_args or {}
            self.name = name
    

    目前到这里,我们发现从url匹配===>as_view()执行返回view===>view传递给了URLPatterncallbak,这整个过程最终执行了as_view闭包内的view方法,view方法主要有实例化对象,为对象增加属性,然后调用dispath()查找执行方法几个步骤。而view方法最主要的就是下面的dispatch方法,ctrl+b进入查看dispatch()方法。

    1. dispatch()方法就是判断类视图实例(self就是我们自己定义的视图类的实例)是否存在方法,然后执行返回结果

    源码路径(django/views/generic/base.py) dispatch()方法

    调用顺序: as_view --> view --> dispatch

    • 可以看出as_view实际上是一个闭包, 它的作用做一些校验工作, 再返回view方法.
    • 而view方法的作用是给请求对象补充三个参数, 并调用 dispatch方法处理
    • dispatch方法查找到指定的请求方法, 并执行

    可以得出结论: 实际上真正实现查找的方法是 dispatch方法

    OK,到这里我们差不多理解了原生Django的整个请求流程,至于post请求主要步骤也就是接受参数request.POST,然后校验插入数据库,组织数据返回结果,底层请求也是上面的流程,最终由dispatch做分发执行类视图定义的方法。

    因此我们会发现在整个类视图中组织数据是最频繁的操作,假如当一个表字段很多时,我们重复组织的数据就会很多,当然也可以抽取公共的部分进行封装,但是总感觉有点拖泥带水,为了解决这个问题,便有了下面的Django REST framework框架的诞生。

    基于DRF的请求定义

    由于上面的路由以及模型类定义都是一样的,这里就主要定义一下类视图的编写:

    from rest_framework.views import APIView
    
    class User(APIView):
        def get(self, request, *args, **kwargs):
            pk = kwargs.get("pk")
            if pk:
                user = UserModel.objects.filter(pk=pk).first()
                if user:
                    return Response({
                        'status': 0,
                        'msg': 'OK',
                        'results': serializers.UserSerializer(instance=user).data
                    })
                else:
                    return Response({
                        'status': 2,
                        'msg': '用户不存在',
                    })
            else:
                users = UserModel.objects.all()
                return Response({
                    'status': 0,
                    'msg': 'OK',
                    'results': serializers.UserSerializer(instance=users, many=True).data
                })
    
        def post(self, request, *args, **kwargs):
            data = request.data
    
            # 校验数据
            user_des = serializers.UserDeserializer(data=data)
    
            if user_des.is_valid():
                # 校验通过
                user_obj = user_des.save()
                return Response({
                    'status': 0,
                    'msg': 'OK',
                    'results': serializers.UserSerializer(instance=user_obj).data
                })
            else:
                # 校验失败
                return Response({
                    'status': 1,
                    'msg': user_des.errors,
                })
    

    注意这个类视图继承的是rest_framework.view下面的APIView,然后类中定义了getpost的请求方法,至于其中的serializers这些东西是Django REST framework的序列化组件,这里不做过多解释,我们主要分析一下对于Django REST framework的请求流程。

    DRF请求源码分析

    1. 根据应用中urls.py,走as_view方法,但是视图类没有该方法,所以请求走的是父类APIViewas_view方法

    源码路径(rest_framework/views.py) as_view()方法

    1. APIViewas_view调用父类(django原生View)的as_view,同时还禁用了 csrf 认证,返回view

    2. 和原生一样接下来就是执行view,走dispatch方法,同样视图类没有该方法,所以请求走的是父类APIView,发现APIView重写了dispatch所以就不用走原生View内部的dispatch

    源码路径(rest_framework/views.py) dispatch()方法

    1. 执行完成dispatch的所有方法最终返回响应

    通过发现DRF的核心就是对原生dispatch方法做了很多的改动,从一个请求到最终的响应过程,首先对request做了调整,之后就是三大认证组件,然后做了一些异常捕获的处理,之后对响应的数据进行了封装,用过DRF的都知道,DRF自带了一个浏览器的访问调试界面,这个就是最后的渲染模块做的事。

    针对每个模块具体做了那些改进,我用了另一篇文章来详细介绍。

    相关参考:
    https://www.jianshu.com/p/17860becea09
    https://www.cnblogs.com/wangcuican/p/11674996.html

  • 相关阅读:
    centos 配置puTTY rsa自动登录
    Linux LVM 简单操作
    linux 系统下有sda和hda的硬件设备分别代表什么意思
    Centos 安装Sublime text 3
    编译安装MySQL-5.7.13
    药品查询网的数据库
    获得Android设备的唯一序列号
    Android中设置TextView的颜色setTextColor
    介绍几款网页数据抓取软件 分类: 业余 2015-08-07 18:09 5人阅读 评论(0) 收藏
    网上处方药物手册Rxlist 及其药学信息资源 分类: 业余 2015-08-07 14:16 8人阅读 评论(0) 收藏
  • 原文地址:https://www.cnblogs.com/ydongy/p/13122476.html
Copyright © 2011-2022 走看看