zoukankan      html  css  js  c++  java
  • DRF之Restful规范APIView源码分析

    一、Restful规范

    Restful规范是一种web API接口的设计风格,在前后端分离的应用模式中适用较多。

    这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。

    十条规范

    1、是数据的安全保障:url链接一般都采用HTTPS协议进行传输

    2、接口特征表现,一看就知道是个api接口

    用api关键字标识接口url:

    https://api.baidu.com
    https://www.baidu.com/api
    

    3、多数据版本共存

    在url链接中标识数据版本:url链接中的v1、v2就是不同数据版本的体现(只有在一种数据资源有多版本情况下)

    https://api.baidu.com/v1
    https://api.baidu.com/v2
    

    4、数据即是资源,均使用名词(可复数)

    接口一般都是完成前后台数据的交互,交互的数据我们称为资源

    https://api.baidu.com/users
    https://api.baidu.com/books
    https://api.baidu.com/book
    

    5、资源操作由请求方式决定(method)

    操作资源一般都会涉及到增删改查,使用提供请求方式来标识增删改查动作

    https://api.baidu.com/books 	- get请求:获取所有书
    https://api.baidu.com/books/1 	- get请求:获取主键为1的书
    https://api.baidu.com/books 	- post请求:新增一本书书
    https://api.baidu.com/books/1 	- put请求:整体修改主键为1的书
    https://api.baidu.com/books/1 	- delete请求:删除主键为1的书
    

    6、过滤,通过在url上传参的形式传递搜索条件

    https://api.example.com/v1/zoos?limit=10:			指定返回记录的数量
    https://api.example.com/v1/zoos?offset=10:			指定返回记录的开始位置
    https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数
    https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
    https://api.example.com/v1/zoos?animal_type_id=1:	指定筛选条件
    

    7、响应状态码

    正常响应
        响应状态码2xx
        200:常规请求
        201:创建成功
    重定向响应
        响应状态码3xx
        301:永久重定向
        302:暂时重定向
    客户端异常
        响应状态码4xx
        403:请求无权限
        404:请求路径不存在
        405:请求方法不存在
    服务器异常
        响应状态码5xx
        500:服务器异常
    

    8、错误处理、应返回错误信息,error当做key

    {
        "error": "无权限操作"
    }
    

    9、返回结果,针对不同操作,服务器向返回的结果应该符合以下规范

    GET /collection:			返回资源对象的列表(数组)
    GET /collection/resource:	返回单个资源对象
    POST /collection:			返回新生成的资源对象
    PUT /collection/resource:	返回完整的资源对象
    PATCH /collection/resource:	返回完整的资源对象
    DELETE /collection/resource:返回一个空文档
    

    10、需要url请求的资源需要访问资源的请求链接

    {
        "status": 0,
        "msg": "ok",
        "results":[
            {
                "name":"肯德基(罗餐厅)",
                "img": "https://image.baidu.com/kfc/001.png"
            }
        ]
    }
    
    

    二、drf的简单使用

    1、在setting.py 的app中注册

    INSTALLED_APPS = [
        'rest_framework'
    ]
    

    2、在models.py中写表模型

    class Book(models.Model):
        nid=models.AutoField(primary_key=True)
        name=models.CharField(max_length=32)
        price=models.DecimalField(max_digits=5,decimal_places=2)
        author=models.CharField(max_length=32)
    

    3、新建一个序列化类

    from rest_framework.serializers import ModelSerializer
    from app01.models import  Book
    
    class BookModelSerializer(ModelSerializer):
        class Meta:
            model = Book
            fields = "__all__"
    
    

    4、在视图中写视图类

    from rest_framework.viewsets import ModelViewSet
    from .models import Book
    from .ser import BookModelSerializer
    
    class BooksViewSet(ModelViewSet):
        queryset = Book.objects.all()
        serializer_class = BookModelSerializer
    

    5、写路由关系

    from app01 import views
    from rest_framework.routers import DefaultRouter
    
    router = DefaultRouter()  # 可以处理视图的路由器
    router.register('book', views.BooksViewSet)  # 向路由器中注册视图集
    
    # 将路由器中的所以路由信息追到到django的路由列表中
    urlpatterns = [
        path('admin/', admin.site.urls),
    ]
    # 两个列表相加
    urlpatterns += router.urls
    

    三、APIView源码分析

    ModelViewSet继承View(django原生View)

    APIView继承了View

    先读View的源码

    CBV源码分析

    from django.views import View
    
    # urls.py
    # views.Books.as_view()是一个函数内存地址,as_view是一个类方法,Books类直接调用,会把类自动传入
    path('books/', views.Books.as_view()), 
    # as_view返回了一个内层函数view,相当于放了一个view的内存地址(View——>as_view——>内层函数)
    
    # 请求来了,如果路径匹配,会执行————>  view(request)
    def view(request, *args, **kwargs):
        self = cls(**initkwargs)
        self.setup(request, *args, **kwargs)
        if not hasattr(self, 'request'):
            raise AttributeError(
                "%s instance has no 'request' attribute. Did you override "
                "setup() and forget to call super()?" % cls.__name__
            )
            return self.dispatch(request, *args, **kwargs)
    # 然后返回一个self.dispatch方法,self是谁调用就是谁,由于是Books.as_view(),所以self是Books对象,而Books类继承了View类,所以执行在View类中的dispatch方法
    
    
    def dispatch(self, request, *args, **kwargs):
        if request.method.lower() in self.http_method_names:	# 先判断请求方式是否存在
            
            # 存在则反射self(Books)中的请求方法的内存地址————>handler=getattr(self,'get')
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)	# 执行get(request)
    

    APIView源码分析

    from rest_framework.views import APIView
    
    # urls.py
    # 这里同样写的是个函数的内存地址,不过是用的APIView里的as_view方法
    path('booksapiview/', views.BooksAPIView.as_view()),	
    
    # APIView的as_view方法(类的绑定方法)
    @classmethod
    def as_view(cls, **initkwargs):
        
    		......
            
        # 主要的如下
        # 先调用了父类(View)的as_view方法
        view = super().as_view(**initkwargs)	
        # 当调用了父类(View)的as_view方法会执行self.dispatch(),此时的self是BooksAPIView,并且继承了APIView类,所以此时的dispatch()方法是APIView的dispatch()方法(原dispatch方法被重写了)
        
        view.cls = cls
        view.initkwargs = initkwargs
        # 以后的所有请求都没有csrf认证了,只要继承了APIView,就没有CSRF认证
        return csrf_exempt(view)	
    	# 局部禁用csrf,在视图上加装饰器@csrf_exempt,和csrf_exempt(view)是一样的
        
        
    # 请求来了——》路由匹配上——》view(request)——》调用了self.dispatch()——》执行APIView的dispatch方法
    # APIView的dispatch方法
    def dispatch(self, request, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        
        # 将原生的request包装成一个新的request,以后在用request,就是新的request对象了
        request = self.initialize_request(request, *args, **kwargs)
        
        self.request = request
        self.headers = self.default_response_headers  # deprecate?
    
        try:
            # 三大认证模块
            self.initial(request, *args, **kwargs)
    
            # 这里就相当于原生的dispatch方法的反射,先判断请求方法是否存在,在去反射获取请求方法的内存地址
            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
    
    
    
    # APIView的initial方法(三大认证模块)
    def initial(self, request, *args, **kwargs):
        	...
            
        # 认证组件:校验用户 - 游客、合法用户、非法用户
        # 游客:代表校验通过,直接进入下一步校验(权限校验)
        # 合法用户:代表校验通过,将用户存储在request.user中,再进入下一步校验(权限校验)
        # 非法用户:代表校验失败,抛出异常,返回403权限异常结果
        self.perform_authentication(request)
        
        # 权限组件:校验用户权限 - 必须登录、所有用户、登录读写游客只读、自定义用户角色
        # 认证通过:可以进入下一步校验(频率认证)
        # 认证失败:抛出异常,返回403权限异常结果
        self.check_permissions(request)
        
        # 频率组件:限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数(3/s)
        # 没有达到限次:正常访问接口
        # 达到限次:限制时间内不能访问,限制时间达到后,可以重新访问
        self.check_throttles(request)
    

    只要继承了APIView,视图类中的request对象都是新的,也就是上面那个request对象,

    原生的request在新的request._request中

    以后使用request对象,就像使用之前的request是一样的(因为重写了__getattr__方法)

    from rest_framework.request import Request
    
    # 点拦截,当新request.的时候触发
    def __getattr__(self, attr):
        try:
            return getattr(self._request, attr)	# 通过反射,获取原生request对象,取出属性或方法
        except AttributeError:
            return self.__getattribute__(attr)
        
    # request.data 是一个方法,不过是被@property装饰了
    # request.data 是一个字典,post请求不管使用什么编码,传过来的数据都在request.data中
    
    # get请求的数据还可以在request.query_params中取
    @property
    def query_params(self):
        return self._request.GET
    
    # 视图类中
    print(request.query_params)	# get请求,地址中的参数
    # 原来在
    print(request.GET)
    
    学习之旅
  • 相关阅读:
    [Git & GitHub] 利用Git Bash进行第一次提交文件
    Linux下 Unison 实现文件双向同步
    Linux SSH使用公钥私钥实现免登陆
    SSH自动断开连接的原因
    hosts.deny 和hosts.allow 配置不生效
    bind启动时提示953端口被使用
    Linux查询系统配置常用命令
    Linux 查硬件配置
    BIND rndc—使用说明
    rndc 错误解决 和 远程配置
  • 原文地址:https://www.cnblogs.com/XiaoYang-sir/p/14967023.html
Copyright © 2011-2022 走看看