zoukankan      html  css  js  c++  java
  • Restful 1 -- REST、DRF(View源码解读、APIView源码解读)及框架实现

    一、REST

    1、什么是编程?

      数据结构和算法的结合

    2、什么是REST?

      - url用来唯一定位资源,http请求方式来区分用户行为

      首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下:

    127.0.0.1:9001/books/
       127.0.0.1:9001/get_all_books/   访问所有的数据
                
        127.0.0.1:9001/books/{id}/     
        127.0.0.1:9001/books/{id}?method=get   访问单条数据
                
        127.0.0.1:9001/books/add/
        127.0.0.1:9001/books/?type=create   创建数据
                
        127.0.0.1:9001/books/delete/
                
        127.0.0.1:9001/books/update/

      分析:以上定义的url虽然也可以实现功能,但是因个人命名习惯等的不同,同一个功能会产生五花八门url,而且响应回去的数据(包括错误提示等)格式也没有统一的规范,这就造成了前后端交互上的困难。

      因此,就产生了REST,REST的url唯一代表资源,HTTP请求方式来区分用户行为,下面就是符合REST的url设计规范的示例:

    url的设计规范:
        GET:     127.0.0.1:9001/books/           # 获取所有数据
        GET:      127.0.0.1:9001/books/{id}      # 获取单条数据
        POST:    127.0.0.1:9001/books/           # 增加数据
        DELETE:   127.0.0.1:9001/books/{id}      # 删除数据
        PUT:      127.0.0.1:9001/books/{id}      # 修改数据
      数据响应规范:
        GET:     127.0.0.1:9001/books/          # 返回[{}, {}, {}]
        GET:      127.0.0.1:9001/books/{id}      # 返回单条数据{}
        POST:    127.0.0.1:9001/books/          # 返回添加成功的数据{}
        DELETE:   127.0.0.1:9001/books/{id}      # 返回空"" 
        PUT:      127.0.0.1:9001/books/{id}      # 返回{} ,更新后完整的一条记录,注意并非一个字段
      错误处理:
            { "error": "error_message" }

      REST是只用软件架构设计风格,不是标准,也不是技术实现,只是提供了一组设计原则和约束条件,是目前最流行的API设计规范,用于web数据接口的设计。2000年,由Roy Fielding在他的博士论文中提出,Roy Fielding是HTTP规范的主要编写者之一。

      那么,我们接下来要学的Django Rest Framework与REST有什么关系呢?

      其实,DRF(Django REST Framework)是一套基于django开发的,帮助我们更好的设计符合REST规范的web应用的一个Django App,所以,本质上,它是要给Django App。

    二、知识储备

      学习新知识之前,先回顾以下几个知识点:

    1、CBV(class based view)

    django.views import View
      class LoginView(View):
          def get(self, request):
              pass
        def post(self, request):
          pass

    2、 类方法classmethod & classonlymethod

    class Person(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
       # 注意:Person类加载时,会执行装饰器函数classmethod(sleepping),并将结果赋给slepping
        @classmethod          # 相当于 sleepping = classmethod(sleepping)
        def sleepping(cls):
            print("Jihong is sleepping")
    
        @classonlymethod
        def guangjie(cls):
             print("Jihong is shopping")
    
    Person.sleepping()   # 类直接调用类方法
    Person.guangjie()    # 类直接调用方法
    jihong = Person("jihong", 20)
    jihong.sleepping()   # 对象可以调用类方法
    jihong.guangjie()    # 报错,对象不能调用由@classonlymethod装饰的方法

      总结:

        @classmethod装饰(python加的装饰器)的方法可以由对象和类调用;

        @classonlymethod装饰(django加的装饰器)只能由类直接调用

    3、反射

      getattr , hasattr , setattr

    4、self定位

      明确 slef 指向谁 - 始终指向调用者

    5、http请求协议

      协议就是沟通双方约定俗成的规范,即解析数据的规则

    6、form表单的enctype属性的有三种请求协议

      如果通过form表单提交用户数据,可以使用form表单的enctype属性来定义数据编码协议,该属性有三个值,代表三种数据编码协议:

      - application/x-www-form-urlencoded:使用&符号连接多个键值对,键值对用等号拼接,默认;

      - multipart/form-data:上传文件、图片时使用该方式;

    7、JavaScript中object,例如:{name:"pizza"}  <==>json的相互转换方式

      JSON.stringfy(data) =相当于=》 python json.dumps()

      JSON.parse(data) =相当于=》 pthon json.loads()

      注意:

        dumps是将dict转化成str格式,loads是将str转化成dict格式

    三、Django REST Framework(DRF序列化)

      为什么使用DRF?

      从概念可以看出,有了这样的一个App,能够帮助我们更好的设计符合RESTful规范的web应用,实际上,没有它,我们也能自己设计符合规范的web应用,如下代码中,我们就手动实现了符合RESTful规范的web应用:

    class CourseView(View):
        def get(self, request):
            course_list = list()
    
            for course in Course.objects.all():
                course = {
                    "course_name": course.course_name,
                    "description": course.description
                }
    
                course_list.append(course)
    
            return HttpResponse(json.dumps(course_list, ensure_ascii=False))

      如上代码所示,我们获取所有的课程数据,并根据REST规范,将所有资源通过列表返回给用户,可见,就算没有DRF,我们也能设计出符合RESTful规范的接口,甚至是整个App应用,但是,如果所有的接口都自定义,难免会出现重复代码,为了提高工作效率,我们建议使用优秀的工具,DRF就是这样一个优秀的工具,另外,它不仅能够帮助我们快速的设计符合REST规范的接口,还提供诸如认证、权限等等其他强大功能。

      什么时候使用DRF?

      前边提到,REST是目前最流行的API设计规范,如果使用Django开发你的web应用,那么请尽量使用DRF,如果使用的是Flask,可以使用Flask-RESRful。

      DRF官方文档中有详细介绍,使用如下命令安装,首先安装Django,然后安装DRF:

    1
    2
    >>> pip install django   # 安装django,已经安装的可以直接下载DRF
    >>> pip install djangorestframework   # 下载

      安装完成之后,我们就可以开始使用DRF框架来实现我们的web应用了,这部分内容包括如下知识点:

    - APIView
      - 解析器组件
      - 序列化组件
      - 视图组件
      - 认证组件
      - 权限组件
      - 频率控制组件
      - 分页组件
      - 相应器组件
      - url控制器

     介绍DRF,必须介绍APIView,它是重中之重,是下面所有组件的基础,因为所有的请求都是通过它来分发的,至于它究竟是如何分发请求的?想要弄明白这个问题,就必须解读它的源码,而想要解读DRF APIView的源码,就必须先解读django中views.View类的源码,为什么使用视图类调用as_view()之后,请求就可以被不同的函数处理?

    1、回顾CBV,View源码解读

    # views.py中代码如下:
    from django.views import View
    class LoginView(View):
      def get(self, request):
        pass
      def post(self, request):
        pass
    
    # urls.py中代码如下:
    from django.urls import path, include, re_path
    from classbasedView import views
    urlpatterns = [
      re_path("login/$", views.LoginView.as_view())
    ]

      1)启动django项目:python manage.py runserver 127.0.0.1:8000后

      2)开始加载settings配置文件

        - 读取models.py

        - 加载views.py

        - 加载urls.py,执行as_view( ): views.LoginView.as_view()

        LoginView中没有as_view,因此去执行父类View中as_view方法,父类View的相关源码如下:

    class View:
      http_method_names = ['get', 'post', 'put', ...]
        def __init__(self, **kwargs):
            for key, value in kwargs.items():
                setattr(self, key, value)
    
        @classonlymethod
        def as_view(cls, **initkwargs):
            for key in initkwargs:
                ...
            def view(request, *args, **kwargs):
            # 实例化一个对象,对象名称为self,self是cls的对象,谁调用了as_view
            # cls就是谁(当前调用as_view的是LoginView)
            # 所以,此时的self就是LoginView的实例化对象
                self = cls(**initkwargs)
                if hasattr(self, 'get') and not hasattr(self, 'head'):
                    self.head = self.get
                self.request = request
                self.args = args
                self.kwargs = kwargs
                return self.dispatch(request, *args, **kwargs)
            view.view_class = cls
            view.view_initkwargs = initkwargs
    
            update_wrapper(view, cls, updated=())
            update_wrapper(view, cls.dispatch, assigned=())
            return view
    
      def dispatch(self, request, *args, **kwargs):
           if request.method.lower() in self.http_method_names:
              # 通过getattr找到的属性,已和对象绑定,访问时不需要再指明对象了
              # 即不需要再:self.handler,直接handler()执行
               handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
           else:
               handler = self.http_method_not_allowed
           return handler(request, *args, **kwargs)

        上面源码中可以看出,as_view是一个类方法,并且方法中定义了view函数,且as_view将view函数返回,此时url与某一个函数的对应关系建立,并开始等待用户请求。

      3)当用户发来请求(如get请求),开始执行url对应的view函数,并传入request对象,view函数的执行结果是返回self.dispatch(request, *args, **kwargs)的执行结果,这里的self是指LoginView的实例化对象,LoginView中没有dispatch方法,所以去执行父类View中的dispatch方法,View类中的dispatch函数中通过反射找到self(此时self指LoginView的实例对象)所属类(即LoginView)中的get方法,dispatch函数中的handler(request, *args, **kwargs)表示执行LoginView类中的get方法,其执行结果就是dispatch的执行结果,也就是请求url对应view函数的执行结果,最后将结果返回给用户。

    2、APIView

      使用:

    # views.py中代码:
    from rest_framework.views import APIView       # 引入APIView
    class LoginView(APIView):   # LoginView继承APIView
      def get(self, request):
        pass
      def post(self, request):
        pass
    
    # urls.py中用法同CBV一样,如下示例:
    from django.urls import path, include, re_path
    from classbasedView import views
    urlpatterns = [
      re_path("login/$", views.LoginView.as_view())
    ]

      源码解读:

        1)启动django项目:python manage.py runserver 127.0.0.1:8000后

        2)开始加载settings配置文件

          - 读取models.py

          - 加载views.py

          - 加载urls.py,执行as_view( ): views.LoginView.as_view()

          LoginView中没有as_view,因此去执行父类APIView中as_view方法,父类APIView的相关源码如下:

    class APIView(View):
        ...
       # api_settings是APISettings类的实例化对象
        parser_classes = api_settings.DEFAULT_PARSER_CLASSES
        ...
        settings = api_settings
        schema = DefaultSchema()
    
        @classmethod
        def as_view(cls, **initkwargs):   # cls指LoginView
            if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
                ...
           # 下面一句表示去执行APIView父类(即View类)中的as_view方法
            view = super(APIView, cls).as_view(**initkwargs)
            view.cls = cls
            view.initkwargs = initkwargs
            return csrf_exempt(view)
    
       def dispatch(self, request, *args, **kwargs):
          ...
          request = self.initialize_request(request, *args, **kwargs)
          ...
          try:
              self.initial(request, *args, **kwargs)
              if request.method.lower() in self.http_method_names:
                  handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
              else:
                  handler = self.http_method_not_allowed
    
              response = handler(request, *args, **kwargs)
    
          except Exception as exc:
              response = self.handle_exception(exc)
    
          self.response = self.finalize_response(request, response, *args, **kwargs)
          return self.response

          参考View源码解读,我们知道View中的as_view方法返回view函数,此时url与view的对应关系已经建立,等待用户请求。

        3)当用户发来请求(如get请求),开始执行url对应的view函数,并传入request对象,View类中view函数的执行结果是返回self.dispatch(request, *args, **kwargs)的执行结果,这里的self是指LoginView的实例化对象,LoginView中没有dispatch方法,所以去执行父类APIView中的dispatch方法,同样,APIView类中的dispatch函数中也是通过反射找到self(此时self指LoginView的实例对象)所属类(即LoginView)中的get方法,dispatch函数中的handler(request, *args, **kwargs)表示执行LoginView类中的get方法,其执行结果就是dispatch的执行结果,也就是请求url对应view函数的执行结果,最后将结果返回给用户。

    四、基于Django Rest FrameWork框架的实现

    1.基本流程

      url.py

    from django.conf.urls import url, include
    from web.views.s1_api import TestView
     
    urlpatterns = [
        url(r'^test/', TestView.as_view()),
    ]
    View Code

      views.py

    from rest_framework.views import APIView
    from rest_framework.response import Response
     
     
    class TestView(APIView):
        def dispatch(self, request, *args, **kwargs):
            """
            请求到来之后,都要执行dispatch方法,dispatch方法根据请求方式不同触发 get/post/put等方法
             
            注意:APIView中的dispatch方法有好多好多的功能
            """
            return super().dispatch(request, *args, **kwargs)
     
        def get(self, request, *args, **kwargs):
            return Response('GET请求,响应内容')
     
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')
     
        def put(self, request, *args, **kwargs):
            return Response('PUT请求,响应内容')
    View Code

    上述是rest framework框架基本流程,重要的功能是在APIView的dispatch中触发。

    2.版本

      在Django rest-framework中提供了5中version设置方式。

      a. 基于url的get传参方式:如:/users?version=v1

    REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',            # 默认版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允许的版本
        'VERSION_PARAM': 'version'          # URL中获取值的key
    }
    settings.py
    from django.conf.urls import url, include
    from web.views import TestView
    
    urlpatterns = [
        url(r'^test/', TestView.as_view(),name='test'),
    ]
    urls.py
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import QueryParameterVersioning
    
    
    class TestView(APIView):
        versioning_class = QueryParameterVersioning
    
        def get(self, request, *args, **kwargs):
    
            # 获取版本
            print(request.version)
            # 获取版本管理的类
            print(request.versioning_scheme)
    
            # 反向生成URL
            reverse_url = request.versioning_scheme.reverse('test', request=request)
            print(reverse_url)
    
            return Response('GET请求,响应内容')
    
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')
    
        def put(self, request, *args, **kwargs):
            return Response('PUT请求,响应内容')
    
    views.py
    views.py

      b.基于url的正则方式(推荐使用這种方式):如:/v1/users/

    REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',            # 默认版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允许的版本
        'VERSION_PARAM': 'version'          # URL中获取值的key
    }
    settings.py
    from django.conf.urls import url, include
    from web.views import TestView
    
    urlpatterns = [
        url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'),
    ]
    urls.py
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import URLPathVersioning
    
    
    class TestView(APIView):
        versioning_class = URLPathVersioning
    
        def get(self, request, *args, **kwargs):
            # 获取版本
            print(request.version)
            # 获取版本管理的类
            print(request.versioning_scheme)
    
            # 反向生成URL
            reverse_url = request.versioning_scheme.reverse('test', request=request)
            print(reverse_url)
    
            return Response('GET请求,响应内容')
    
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')
    
        def put(self, request, *args, **kwargs):
            return Response('PUT请求,响应内容')
    
    views.py
    Views.py

      c.基于 accept 请求头方式:如:Accept: application/json; version=1.0

    REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',            # 默认版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允许的版本
        'VERSION_PARAM': 'version'          # URL中获取值的key
    }
    settings.py
    from django.conf.urls import url, include
    from web.views import TestView
    
    urlpatterns = [
        url(r'^test/', TestView.as_view(), name='test'),
    ]
    urls.py
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import AcceptHeaderVersioning
    
    
    class TestView(APIView):
        versioning_class = AcceptHeaderVersioning
    
        def get(self, request, *args, **kwargs):
            # 获取版本 HTTP_ACCEPT头
            print(request.version)
            # 获取版本管理的类
            print(request.versioning_scheme)
            # 反向生成URL
            reverse_url = request.versioning_scheme.reverse('test', request=request)
            print(reverse_url)
    
            return Response('GET请求,响应内容')
    
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')
    
        def put(self, request, *args, **kwargs):
            return Response('PUT请求,响应内容')
    
    views.py
    views.py

    五、补充知识点

    1、若类中有装饰器函数,那么当类加载的时候,装饰器函数就会执行,如下代码:

    class Person(object):
    @classmethod          # 相当于 sleepping = classmethod(sleepping)
      def sleepping(cls):
        print("Jihong is sleepping")
    
      print(sleepping)  # 加载类时执行,结果<classmethod object at 0x000001F2C29C8198>

    注意:类中直接print语句会执行打印输出结果,而函数只有调用时才会执行,如下:

    def func():
      print('hello world')  # 函数func加载不会执行打印,只有调用即func()才会执行打印

    2、__dict__方法

    class Person(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def sing(self):
            print('I am singing')
    
    p1 = Person('alex', 18)
    print(p1.__dict__)     # {'name': 'alex', 'age': 18}
    print(Person.__dict__)  
    # {'__module__': '__main__', '__init__': <function Person.__init__ at 0x0000021E1A46A8C8>, 
    'sing': <function Person.sing at 0x0000021E1A46A950>, 
    '__dict__': <attribute '__dict__' of 'Person' objects>, 
    '__weakref__': <attribute '__weakref__' of 'Person' objects>, 
    '__doc__': None}

      总结:

        对象.__dict__ 返回对象的所有成员字典;

        类.__dict__ 返回类的所有成员字典;

        我们可以通过(对象.name)取出成员,字典没有这种取值方式,使用对象.name的本质是执行类中的__getitem__方法。

    3、现在有如下两个需求:

    # 需求一:计算add函数的执行时间(不重写add函数的前提下)
    def add(x, y):
      return x+y
    
    # 解决方式:装饰器
    def outer(func):
      def inner(*args, **kwargs):
        import time
        start_time = time.time()
        ret = func(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)
      return inner
    
    @outer
    def add(x, y):
      return x+y
    # 需求二:扩展类中函数的功能(在不重写Father类的前提下)
    class Father(object):
      def show(self):
        print('father show is excuted')
    
    father = Father()
    father.show()
    
    # 解决方式:重新写一个类,继承Father类,重写show(),super()调用
    class Son(Father):
      def show(self):
        print('son show is excuted')
        super().show()
    
    son = Son()
    son.show()

      总结:

        面向过程的方式对程序进行功能扩展

          - 装饰器

        面向对象的方式对程序功能进行扩展

          - 类的继承

          - 方法重写

          - super()

    4、源码博客:https://www.cnblogs.com/wdliu/category/1211675.html?tdsourcetag=s_pcqq_aiomsg

  • 相关阅读:
    【洛谷P5158】 【模板】多项式快速插值
    【洛谷P4245】 【模板】任意模数NTT
    【洛谷4781】 【模板】拉格朗日插值
    BZOJ 3625:小朋友和二叉树 多项式开根+多项式求逆+生成函数
    【洛谷】5205 【模板】多项式开根
    nowcoder73E 白兔的刁难 单位根反演+NTT
    BZOJ 3328: PYXFIB 单位根反演+矩阵乘法+二项式定理
    loj #6485. LJJ 学二项式定理 单位根反演
    Struts 2 框架搭建HelloWorld
    Struts 2 概念介绍
  • 原文地址:https://www.cnblogs.com/wxj1129549016/p/10073464.html
Copyright © 2011-2022 走看看