一、drf入门规范

drf入门规范

一、Web一能用模式

  • 前后端混合开发(前后端不分离:全栈):返回来的是html的内容,需要自己写模板
  • 前后端分离:只专注于写后端接口,返回的是json、xml(过时)格式的数据
  • 前后端分离需要中间媒介:api接口

二、API接口

  • 通过网络,规定前后台信息交互规则的url链接
  • 就是前后端交互的媒介,双方协议好传输数据的格式

三、Restful规范(重点)

# REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征性状态转移)。 它首次出现在2000年Roy Fielding的博士论文中。

# uri:资源唯一标识(抽象概念)
# url:资源唯一定位(具体实现)

RESTful是Web API接口的设计规范风格,这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,尤其适用于前后端分离的应用模式中。

所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。

其实,任何一个框架都可以实现符合restful规范的API接口。

十条规范

1.数据的安全保障:
	url链接一般都是采用哪个https协议进行传输
    注:采用https协议,可以提高数据交互过程中的安全性
2.接口特征表现,一看看就知道是以api接口
	用api关键字标识接口url
    	https://www.baidu.com/api
		[https://api.baidu.com](https://api.baidu.com/)
    注:看到api字眼,就代表该请求url链接是完成前后台数据交互的
3.多数据版本共存
	在url链接中标识数据版本
    	https://api.baidu.com/v1
    	https://api.baidu.com/v2
    注:url链接中的v1、v2就是不同数据版本的体现(只有在一种数据资源有多版本情况下)
4 数据即是资源,均使用名词(可复数)#####################重点####################
	接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
    	https://api.baidu.com/users
    注:一般提倡用资源的复数形式,在url链接中奖励不要出现操作资源的动词
    特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
        https://api.baidu.com/place/search
        https://api.baidu.com/login
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 - patch请求:局部修改主键为1的书
      	https://api.baidu.com/books/1 - delete请求:删除主键为1的书
6 过滤,通过在url上传参的形式传递搜索条件,或者说查询参数(query_params)
    - 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 响应状态码
   7.1 正常响应
    - 响应状态码2xx
      - 200:常规请求
      - 201:创建成功
   7.2 重定向响应
    - 响应状态码3xx
      - 301:永久重定向
      - 302:暂时重定向
   7.3 客户端异常
    - 响应状态码4xx
      - 403:请求无权限
      - 404:请求路径不存在
      - 405:请求方法不存在
	7.4 服务器异常
    - 响应状态码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请求的资源需要访问资源的请求链接
    - Hypermedia API,RESTful API最好做到Hypermedia,
    - 即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么
        {
            "status": 0,
            "msg": "ok",
            "results":[
                {
                    "name":"肯德基(罗餐厅)",
                    "img": "https://image.baidu.com/kfc/001.png"
                }
                ...
                ]
        }

四、drf的安装和简单使用

  • 安装:pip install djangorestframework==3.10.3

  • 使用

    1. 在settings.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),
      ]
      #这是什么意思?两个列表相加
      # router.urls  列表
      urlpatterns += router.urls
      
    6. 启动,在postman中测试即可

五、CBV源码阅读

  • 代码分析

    1. 切入点: urls.py中的.as_view()方法

      url(r'^books1/', views.Books.as_view())
      
    2. 视图中Books类

      class Books(View):
          # 如果有个需求,该视图只能接受get请求?
          # 重写http_method_names列表,只保留get请求即可
          http_method_names = ['get']
          
          def get(self,request):
              print(self.request) # 这里可以通过self.request获取到request对象
              return HttpResponse("ok")        
      
    3. 项目启动时执行

      @classonlymethod # 就是classmethod装饰器,django重写了classmethod而已
      def as_view(cls, **initkwargs):
      	...
      
          def view(request, *args, **kwargs):
              ...
      
          ...
          return view
      
    4. 路由匹配时执行, 将执行的类进行实例化, 通过执行路由匹配中类实例化出的对象就可以执行到继承父类View中定义的dispatch方法

      def view(request, *args, **kwargs):
          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)
      
    5. 通过dispatch方法中使用反射获取请求的类型去执行对应类中定义的请求的方法

      def dispatch(self, 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
          return handler(request, *args, **kwargs) # 直接加括号调用handler
      
    6. 上面的self.http_method_names判断, 如果子类中没有派生那么就会通过View中定义的进行判断

      class View(object):
          http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
          ...
      
  • 图示分析

    1. 切入点: urls.py中的.as_view()方法image-20201227201430401

    2. 视图中Books类

      image-20201227201636023

    3. 项目启动时执行

      image-20201227201824849

    4. 路由匹配时执行, 将执行的类进行实例化, 通过执行路由匹配中类实例化出的对象就可以执行到继承父类View中定义的dispatch方法

      image-20201227202012642

    5. 通过dispatch方法中使用反射获取请求的类型去执行对应类中定义的请求的方法

      image-20201227202159850

    6. 上面的self.http_method_names判断, 如果子类中没有派生那么就会通过View中定义的进行判断

      image-20201227202236755

  • 总结

    1. 项目启动: views.Books.as_view()就是View类中的view函数内存地址
    2. 路由匹配: 将view加括号调用, 并传入request对象.
    3. 执行view: 将Books类进行实例化, 实例化出Books类的对象. 这里的self就是Books类实例化出的对象
    4. 执行self.dispatch方法: self就是Books类实例化出的对象, 如果之类以及对象中都没有定义, 就会执行View类中的dispatch方法
    5. dispatch方法中就是通过用户当前的请求的小写字符串进行反射. 当继承View的类中有get方法, 根据对象属性查找顺序就会调用子类中定义的get方法

六、drf中的APIView源码阅读

  • 代码分析

    1. 切入点: urls.py中的.as_view()方法

      url(r'^booksapiview/', views.BooksAPIView.as_view()),
      
    2. 视图中BooksAPIView类

      class BooksAPIView(APIView):
          def get(self, request):
              # request 已经不是原生django的request了,是drf自己定义的request对象
              # print(request._request)
              # print(request.data)
              print(request.method)
              print(request.query_params)  # get请求,地址中的参数
              print(request.GET)
              return HttpResponse('ok')
      
          def post(self, request):
              print(request.data)  # urlencoded方式有数据,json格式也有,formdata也有数据
              print(type(request.POST))  # 原生的POST
              print(request._request.POST)
      
              return HttpResponse('ok')
      
    3. 项目启动时执行

      @classmethod 
      def as_view(cls, **initkwargs):
          ···
          view = super().as_view(**initkwargs) # 调用父类的as_view方法
          view.cls = cls # cls就是BooksAPIView
          return csrf_exempt(view) # 整个网站就取消的csrf的验证
      
    4. 执行APIView父类(View)的as_view方法,返回父类的view方法

      @classonlymethod
      def as_view(cls, **initkwargs):
          ···
          def view(request, *args, **kwargs):
              ···
              return self.dispatch(request, *args, **kwargs)
          return view
      
    5. 执行父类的view方法,并且调用dispatch方法,但是APIView自己写了一个dispatch方法,所以不会执行父类的dispatch方法

      # APIView的dispatch方法
          def dispatch(self, request, *args, **kwargs):
              self.args = args
              self.kwargs = kwargs
              # 重新包装成一个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)
                  # Get the appropriate handler method
                  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
      
    6. APIView自己封装的request对象方法initialize_request()方法

      def dispatch(self, request, *args, **kwargs):
      	···
          request = self.initialize_request(request, *args, **kwargs)
          self.request = request # 已不再是django原来的request对象了
      
          
      def initialize_request(self, request, *args, **kwargs):
          parser_context = self.get_parser_context(request)
          return Request(
              request,
              parsers=self.get_parsers(),
              authenticators=self.get_authenticators(),
              negotiator=self.get_content_negotiator(),
              parser_context=parser_context
          )
      
    7. APIView自己的认证方法initial()方法

      def dispatch(self, request, *args, **kwargs):
          ···
          try:
                  # 三大认证模块
              self.initial(request, *args, **kwargs)
              # 和view里面的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:
              ···
      
      # 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)
      
    8. 继承APIView视图的类中含有的方法分析:

      • django drf
        request _request
        FILES FILES
        GET query_params
      • drf中的data对标django中的request,但是django中的request只能接收urlencoded和form-data格式的数据,drf中的data能接收urlencoded、form-data和json格式的数据

      from rest_framework.request import Request
      # 只要继承了APIView,视图类中的request对象,都是新的,也就是上面那个request的对象了
      # 老的request在新的request._request
      # 以后使用reqeust对象,就像使用之前的request是一模一样(因为重写了__getattr__方法)
        def __getattr__(self, attr):
              try:
                  return getattr(self._request, attr) #通过反射,取原生的request对象,取出属性或方法
              except AttributeError:
                  return self.__getattribute__(attr)
      
       # request.data 感觉是个数据属性,其实是个方法,@property,修饰了
      	它是一个字典,post请求不管使用什么编码,传过来的数据,都在request.data
       #get请求传过来数据,从哪取?
      	request.GET
          @property
          def query_params(self):
              """
              More semantically correct name for request.GET.
              """
              return self._request.GET
          
          #视图类中
           print(request.query_params)  #get请求,地址中的参数
           # 原来在
           print(request.GET)
      
  • 图示分析

    1. 切入点: urls.py中的.as_view()方法

      image-20201227211143295

    2. 视图中BooksAPIView类

      image-20201227211211468

    3. 项目启动时执行

      image-20201227211718502

    4. 执行APIView父类(View)的as_view方法,返回父类的view方法

      image-20201227212237716

    5. 执行父类的view方法,并且调用dispatch方法,但是APIView自己写了一个dispatch方法,所以不会执行父类的dispatch方法

      image-20201227212204787

    6. APIView自己封装的request对象方法initialize_request()方法

      image-20201227212408525

    7. APIView自己的认证方法initial()方法

      image-20201227212700980

    8. 继承APIView视图的类中含有的方法分析:

      • data,原装的request == _request

        image-20201227213015905

      • query_params,原装的GET == _request.GET

        image-20201227213134927

      • FILES

        image-20201227213255068

七、拓展

  • 一切皆对象,函数也是对象

    def foo(a, b):
        return a + b
    foo.name = 'lqz'  # 由于一切皆对象,函数也是个对象,对象放值
    
    print(foo(2, 3))
    
    print(foo.name)  # lqz
    
  • 局部禁用csrf方法和csrf_exempt

    # 提示: 在视图函数上加装饰器@csrf_exempt. 与csrf_exempt(view)这么写和在视图函数上加装饰器是一模一样的.
    
    # urls.py中这种写法本质就是返回的views.test, 只是在原来基础之上不修改的调用方式, 以及原代码的情况下, 去除了csrf认证
    path('test/', csrf_exempt(views.test)),
    
posted @ 2021-01-26 15:44  今天捡到一百块钱  阅读(177)  评论(0)    收藏  举报