1

drf快速使用 CBV源码分析 drf之APIView分析 drf之Request对象分析 - passion2021

 1 year ago
source link: https://www.cnblogs.com/passion2021/p/17017457.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

序列化和反序列化

api接口开发,最核心最常见的一个过程就是序列化,所谓序列化就是把数据转换格式,序列化可以分两个阶段:序列化、反序列化

序列化:把我们语言识别的数据转换成指定的格式提供给别人。

字典,列表,对象  --->  json/xml/prop,massagepack ---> 将json格式的数据提供给别人(前端或其他服务)

'''
序列化和反序列化的格式不仅仅用json格式。
json格式的可读性太高,安全性不足。可以采用prop、massagepack格式等。
'''

反序列化:把别人提供的数据转换/还原成我们需要的格式。


我们在django中获取到的数据默认是模型对象(qreryset对象),但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人,这个转换过程称为 ---> '序列化过程'

前端传入到后台的数据 ---> json格式字符串 ---> 后端将数据存到数据库中,需要将数据转成python中的对象 ---> 把json格式字符串转成python对象存到数据库的过程称为 ---> '反序列化'

drf介绍和安装

使用原生django写接口

原生django,不使用任何其他模块,也可以写出符合resful规范的接口,就是写起来麻烦一些。

# 查询所有图书
  地址:127.0.0.1:8080/books
  路由:path('/books',views.books)
  视图函数中:
      1. 通过orm查出所有图书对象(qreryset)
      2. 序列化(for循环取出数据自己拼成列表套字典):
		[{name:西游记,price:99},{name:红楼梦,price:99}]
      3. JsonResponse返回给前端

django DRF安装

# 定义
drf是django的一个app。

# 作用
帮助程序员快速在django上写符合restful规范的接口

# 安装:
	pip install djangorestframework
   
# 安装的注意事项:
  1.django的最新版本当前为 4.x , 一般我们将django升级到 3.x 或者 2.x
  2.drf的最新版本最低支持django 2.2及以上; 
'''
如果你的版本低于2.2:
	当你安装最新版drf的时候, 会把你老版本的django卸载,给你装上最新版,导致原来的django项目出现问题,运行不了
'''

# 建议:
  django 2.2 以下版本  --->  使用低版本的drf
  django 3.x          --->  使用最新版本的drf
 

drf快速使用

# 针对于一个表,通常需要写哪些接口?
通常需要写5个接口,以后看到的所有接口都是这5个接口的变形。


# 五个基本接口:
  '需求名'      '请求方式'            '访问的路由'
  -查询所有  --->  get    ->  http://127.0.0.1:8000/books/
  -查询一个  --->  get    ->  http://127.0.0.1:8000/books/1/
  -新增一个  --->  post   ->  http://127.0.0.1:8000/books/ 
                             请求体body中携带新增的数据
        
  -修改      --->  put,patch
				 (实际编码中,基本都用put)
  	                     ->  http://127.0.0.1:8000/books/1/ 
                             请求体body中传入修改的数据
        
  -删除一个  --->  delete -> http://127.0.0.1:8000/books/1/
  
# 注册、登录接口的本质:  
登陆接口 ---> 本质其实是查询一个
注册接口 ---> 本质是新增一个

# postman测试接口的特点:
postman的接口测试是严格的,对于一个路由地址,斜杠有和没有是有区别的。
postman不会像浏览器一样,自动补全斜杠再请求一次。

在模型层创建一个简单的只有3个字段的图书类。

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

新建一个py文件编写序列化类BookSerializer。

from .models import Book
from rest_framework import serializers

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

在视图层使用相对导入,导入刚刚创建的图书类。(也可以使用绝对导入,但是推荐使用相对导入)
导入ModelViewSet模块,自己写一个类继承这个模块。
类中属性serializer_class使用我们刚刚创建的序列化类。

from rest_framework.viewsets import ModelViewSet
from .models import Book # 模块导入推荐使用相对导入
# from app01.models import Book # 使用绝对导入
from .serializer import BookSerializer
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
from rest_framework.routers import SimpleRouter
from app01 import views

router = SimpleRouter()
router.register('books', views.BookView, 'books')
urlpatterns = [
    path('admin/', admin.site.urls),
]
urlpatterns += router.urls

datagrip

2614258-20221231220418353-836256538.png

pycharm是java写的 django链接数据库需要java链接数据库的驱动

2614258-20221231220418127-1632589909.png

类似navicat。datagrip是pycharm公司写的链接数据的软件。

使用postman测试接口

使用get请求获取所有图书:

2614258-20221231220418063-72352998.png

使用put请求修改图书:

2614258-20221231220418064-1175776192.png

使用patch请求修改图书:

2614258-20221231220417911-1374166506.png

可以只局部修改一部分。

CBV源码分析

1. cbv路由写法:
path('test/', views.TestView.as_view())
2. path的第二个参数实际是函数内存地址
3. as_view()执行完,实际是闭包函数view的内存地址
4. 当请求来了,路由匹配成功,会执行view(request),传入当次请求的request对象
5. view函数的返回值:return self.dispatch(request, *args, **kwargs)
6. View类中找dispatch
7. 如果是get请求就会执行视图类中的get方法,如果是post请求,就会执行视图类的post方法


# as_view 类的绑定方法--->View类的方法-->@classonlymethod
# dispatch核心代码--->getattr反射--->从当前对象(视图类的对象)--->如果是get请求-->会拿到get方法--->handler就是get方法--->handler(request)本质就是--->get(request)
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
return handler(request, *args, **kwargs)

# 通过描述符自己写装饰器来装饰类---》完成类似于property,classmethod。

查看源码推荐pycharm配置:

2614258-20221231220418289-828042252.png

打开这个show Members,可以查看py文件里面的变量名。

classonlymethod:
classonlymethod继承于classmethod.
相当于django开发人员自定义的classmethod。

2614258-20221231220418215-559471036.png

推荐阅读:
https://liuqingzheng.top/python/面向对象高阶/5-描述符(get%E5%92%8C__set__%E5%92%8C__delete__)/

drf之APIView分析

View类的导入方式

以后使用drf写接口,在视图层都是写视图类
drf最顶层的视图类是APIView,是drf中所有视图类的父类。
APIView又继承了django中的View类:

2614258-20221231220418351-798791143.png

这个View是这样导入的:
from django.view.genenic import View

不对呀,我们之前写视图类,是这样导入的:
from django import view
然后我们的类继承 view.View

为什么这两种方式都会导入同一个View类?

2614258-20221231220418407-203601877.png

不论是genenic还是view都是包名。
在包内的__init__文件注册View类就可以实现导入了。

# View类真实路径
from django.views.generic.base import View 
# 因为在generic包的init里注册了
from django.views.generic import View 
# 因为在views包的init里注册了
from django.views import View       

继承了django View类的类,就是视图类。
所以继承APIView的类,也是视图类。

2614258-20221231220418354-664387849.png

路由层写法和以前一样:

2614258-20221231220418255-1442894686.png

这里如果路由冲突了,会怎么样?(有两个视图类对应test/路由)
这里底层是for循环,将列表中的路由一个一个取出匹配,如果上面的匹配成功,for循环就break退出,不会继续匹配,所以下面这个视图类永远都不会执行。

APIView的执行流程

# APIView的执行流程
路由 path('order/', views.OrderView.as_view())---》第二个参数是函数内存地址---》APIView的as_view的执行结果---》本质还是用了View类的as_view内的view闭包函数,去掉了csrf认证----》当请求来了---》触发view闭包函数执行,并且传入request--->调用了self.dispatch-->self是视图类的对象,从OrderView中找dispatch,但是找不到----》父类APIView中有---》本质执行是APIView的dispatch----》

APIView的as_view方法

# APIView的as_view方法
  view = super().as_view(**initkwargs) # 调用APIView的父类(View)的as_view方法
  return csrf_exempt(view) # 去掉csrf_exempt的认证,以后只要继承了APIView后,csrf就无效了,无论中间件是否注释掉


# crsf的局部禁用--》在视图函数上加装饰器---》csrf_exempt装饰器---》装饰器本质就是一个函数
@csrf_exempt       # 装饰器的@ 是一个语法糖(特殊语法)-->把下面的函数当参数传入装饰器中并且把返回结果赋值给函数名:index=csrf_exempt(index)
def index(request)
	pass
跟 csrf_exempt(index) 一毛一样

路由 path('order/', views.OrderView.as_view()):

2614258-20221231220418331-1610749830.png

path第二个参数是函数内存地址:
我们自己的视图类OrderView里面没有as_view方法,所以回去父类APIView找as_view方法,
由于APIView里面有as_view,所以不会去APIView的父类View找。

APIView里的as_view:

2614258-20221231220417975-489692671.png

重要的两行代码:

view = super().as_view(**initkwargs) # 调用APIView的父类(View)的as_view方法

return csrf_exempt(view) # 去掉csrf_exempt的认证,以后只要继承了APIView后,csrf就无效了,无论中间件是否注释掉

父类(View)的as_view方法最终会拿到 ---> 我们自己编写的视图类中的方法 :
2614258-20221231220417911-1355652037.png
如果来了一个get请求 dispatch方法会通过反射从我们视图类产生的对象中获取方法:

2614258-20221231220418028-2130553253.png

如何理解这行代码return csrf_exempt(view)

@csrf_exempt
def index(request):
    pass
跟 csrf_exempt(index) 一模一样

# 因为装饰器的本质是:
index = csrf_exempt(index)

CBV本质上就是FBV。
发送请求到后端实际上就是执行了我们视图类中的一个函数。
如果视图层有一个视图函数:

def index(request):
    return Httpresponse('你好')

正常情况下,我们给FBV添加装饰器语法糖实际上是执行了:index = csrf_exempt(index)

@csrf_exempt
def index(request):
    return Httpresponse('你好')

当路由匹配成功时,看起来是执行index函数,实际是执行crsf_exempt(index)
FBV路由层看起来是这样子:
path('index/', views.index)
实际是:
path('index/', csrf_exempt(index))
CBV路由层看起来是这样子:
path('index/', views.TestView.as_view())
实际是:
path('index/', crsf_exempt(View))
这个View最终是我们视图类中的一个方法的函数地址(通过反射拿到的)

总结:APIView的as_view的作用只是给你自己写的视图类加了个crsf_exempt装饰器(去掉了crsf认证)

APIView的dispatch方法

# APIView的dispatch
  def dispatch(self, request, *args, **kwargs):
    	# request是新的drf提供的request,它是由老的django的request得到的
      	# 通过老request,生成一个新request,drf的Request的对象
        request = self.initialize_request(request, *args, **kwargs)
        # 把新的request,放到了我们自己的视图类对象中
        self.request = request
        try:
           # 执行了三大认证(认证,权限,频率)
            self.initial(request, *args, **kwargs)
            # self.http_method_names是个列表
            if request.method.lower() in self.http_method_names:
              	# 原来dispatch的核心代码
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)  # 通过反射获取我们视图类中的方法
            else:
                handler = self.http_method_not_allowed
            # 这里也是原来dispatch写的代码,但是request已经不是老request了,是上面生成的新request
            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

# dispatch方法总结
请求来了之后,dispatch方法,先处理request产生新的request对象,将这个新的request对象放入我们自己视图类产生的对象中。
再进行三大认证,认证完了之后获取我们类中的方法并执行,最后dispatch方法返回一个返回值。

# APIView执行流程
  1 包装了新的Request对象,以后视图类中的方法中传入的request对象都是新的
  2 在进入视图函数之前,执行了三大认证
  3 无论三大认证还是视图函数的方法,执行过程中出了异常,都会被处理掉

把新的request,放到了我们自己的视图类对象中:

2614258-20221231220418205-728818857.png

http_method_names列表:

2614258-20221231220418200-1409242759.png

列表里面放了八大请求方法。

drf之Request对象分析

如何包装的新的request

initialize_request方法如何将django产生的老request包装成新的request?

#  如何包装的新的request
request = self.initialize_request(request, *args, **kwargs)---》由于我们的对象里没有initialize_request方法,所以去APIView找initialize_request方法---》核心代码

# initialize_request方法核心代码
from rest_framework.request import Request  # 导入drf新Request类
return Request(
            request,  # 老的request
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
新的Request类中的__init__方法有如下代码:
self._request = request ---》新的request._request是老的request

新的:<class 'rest_framework.request.Request'>
老的:<class 'django.core.handlers.wsgi.WSGIRequest'>

APIView中的initialize_request方法:

2614258-20221231220418341-2066832863.png

drf 新Request类:

2614258-20221231220418327-1791770616.png

Request类__init__:

2614258-20221231220418315-441833138.png

在我们写的视图类中查看老的request:

2614258-20221231220418260-497271625.png

执行get方法里面的代码之前,我们的request已经被换成了新的request。get函数里面的request是drf产生的新request。
更多示例:

2614258-20221231220418270-1985449135.png

request._requestself.request._request存放的都是django产生的老request。

查看request的类型:

2614258-20221231220418467-1802511117.png

三大认证执行顺序

# 三大认证是如何走的
self.initial(request, *args, **kwargs)---》APIView的
核心代码:
   self.perform_authentication(request)  # 认证
   self.check_permissions(request)   #权限
   self.check_throttles(request) # 频率

# 三大认证执行顺序
认证  --->  权限  --->  频率

# 总结
'''
路由匹配成功   ---三大认证--->   执行视图类中方法

三大认证有点类似于中间件: 

请求到达后端服务器   ---中间件--->   路由匹配
'''

新Request常用属性和方法

request.data

# rest_framework.request.Request --->常用属性和方法

#  request.data定义
新的request有一个data方法(此方法被伪装成属性),前端post请求传入的数据都在equest.data里面。

# 与老的request.POST的区别
request.POST:
只能处理urlencoded和formdata编码格式。
request.data:
无论前端用什么编码格式的post提交的数据,都可以从request.data中获取。

未改变的方法

新的request也有一些方法和老request使用方式相同:


request.files # 新的request.files也是获取上传的文件对象

以后直接使用新的request.method  request.path 拿到的就是老的request.method...  # 跟之前用法相同

使用getattr调用老的request的方法

如何使用新request调用老request中的方法:

# 原理
对象.调用属性或方法会触发 魔法方法 __getattr__
原因在于新的request类重写了__getattr__,以后新的request.method用的时候本质就是request._request.method


# 代码
1.使用新的request方法 ---> 执行request.method

2.当新request类中没有method这个名字时,触发新request类中的__getattr__方法

  def __getattr__(self, attr):  # 传入字符串'method'
       try:
            return getattr(self._request, attr)  # 通过反射去老的里面取 self._request存的是老的request
        except AttributeError:
            return self.__getattribute__(attr)
        
3. 新的request.method用的时候本质就是:
	request._request.method
           
          
# 总结:新的request当老的用即可,只是多了个data属性,存放前端post请求传入的数据,三种编码格式都可以存放在data中。

新的request.data和老的request.POST的区别:

发送formdata编码格式:

2614258-20221231220418424-1403191696.png

发送json编码格式:

2614258-20221231220418452-1360808648.png
1 APIView和Request源码部分
2 原来的django的request对象中没有data,写个装饰器,装在视图函数上(中间件),使得request.data-->无论什么编码格式,post提交数据,data都有值

def before(func):
    def inner(request,*args,**kwargs):
        request.data=request.POST
        
        res=func(request,*args,**kwargs)
        return res
    return inner

@before
def test(request):
    print(request.data) 
    return HttpResponse('ok')

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK