不会DRF?源码都分析透了确定不来看?

可以更方便的使用django写出符合restful规范的接口

下载安装

  • pip3 install djangorestframework
  • pycharm下载

注意

  • rest_framework是一个app需要注册!

官网:https://www.django-rest-framework.org/

  • drf安装默认安装的最新版本,如果django版本过低会自动升级到3.x版本

  • 版本支持对应关系

示例

'''views.py'''
from rest_framework.views import APIView
from rest_framework.response import Response class Test(APIView):
def get(self,request):
data = {'status':200,'msg':'success'}
return Response(data)
def post(self,request):
data = {'status': 200, 'msg': 'success'}
return Response(data) '''urls.py'''
path('test/', views.Test.as_view()), '''settings.py'''
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'rest_framework'
]

get请求

post请求

注意:前后端分离csrf已经没有用了,查看一下源码

def as_view(cls, **initkwargs)
return csrf_exempt(view)

我们发现返回值是csrf_exempt(view)局部不验证,在Django总结到了不懂可以看一下:CSRF 跨站请求伪造


快速使用DRF写出接口

序列化和反序列化

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

  • 序列化:把我们语言识别的数据转换成指定的格式提交给别人(前端)

    • 比如python中的字典,列表,对象等转json,xml,prop····
  • 反序列化:把别人提供的数据转换成我们所需的格式
    • 最常见的比如我们使用json模块来对数据进行处理····

在Djangorestframework中的序列化反序列化又是如何?

  • 序列化: 在Django中获取到的数据默认是模型对象(QuerySet对象),但是模型对象数据无法直接提供给前端或别的平台使用,我们需要把数据进行序列化,变成字符串或json数据提供给前端或其他平台;
  • 反序列化: 前端或其他平台传入数据到后台,比如是json格式字符串,后端需要存入数据库,需要转换成python中的对象,然后处理存入数据库;
  • 总结:
    • python对象 -----》json格式字符串 (序列化)
    • json格式字符串 -----》 python 对象 (反序列化)

如何使用DRF快速写出增删查改的接口?

drf快速使用

快速写5个接口

使用Django写五个接口得配5个路由,5个视图函数去处理,现在使用drf不需要了,如下:

-查询所有---》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的时候,地址严格写,不能缺少/
'''

views.py

from rest_framework.viewsets import ModelViewSet
from app import models
from app.serializer import BookSerializer
class BookView(ModelViewSet):
serializer_class =BookSerializer
queryset = models.Book.objects.all()

serializer.py

from rest_framework.serializers import ModelSerializer
from app import models
class BookSerializer(ModelSerializer):
class Meta:
model = models.Book
fields = '__all__'

urls.py

from rest_framework.routers import SimpleRouter
from app import views router = SimpleRouter()
router.register('books', views.BookView)
'''
register(prefix, viewset, base_name)
prefix 该视图集的路由前缀
viewset 视图集
base_name 路由名称的前缀
'''
urlpatterns = [
path('admin/', admin.site.urls),
]
urlpatterns += router.urls # 千万注意别把注释写到urlpatterns列表中,那样就不是注释了,成字符串了!!!

在settings的app中注册

INSTALLED_APPS = [
'rest_framework'
]

models.py

from django.db import models

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

postman测试

获取所有

获取一条

新增一条数据

全部修改:修改id为1的数据,这里尽量在body体里写全参数

局部修改:修改id为1的数据,修改啥写啥

删除一条数据:删除id为2的数据


CBV源码流程分析

因为DRF框架里大部分都是基于CBV(视图类)写,所以流程是什么,如何执行需要了解,同时也方便理解APIView,顺便提一嘴APIView也是继承了View ----> class APIView(View)

这里需要强调一下,CBV路由归根结底还是FBV都是函数的内存地址,比如views.类.as_view()底层仍然是函数的内存地址

CBV源码执行流程

'''views.py'''
from django.views import View
from django.http import HttpResponse class TestView(View):
def get(self,request):
return HttpResponse('get请求')
# post csrf认证注释掉就好了
def post(self,request):
return HttpResponse('post请求')
'''urls.py'''
path('test/',views.TestView.as_view())

写一个视图类,写了get方法和post方法,来了get请求就走get方法,来了post请求就走post方法,过程如何?且看分析源码执行过程~

'''请求来了在不考虑中间件的情况下,从路由的匹配关系和视图函数来看'''
1、cbv路由写法:path('test/', views.TestView.as_view())
# 第二个参数是函数内存地址,CBV的底层也是FBV,as_view是类的绑定方法,自己的类中没有去父类(View)找,as_view()执行完,也是一个内存地址,内存地址是谁的?是闭包函数view的如下源码
@classonlymethod
def as_view(cls, **initkwargs):
···
def view(request, *args, **kwargs):
···
return self.dispatch(request, *args, **kwargs)
···
return view
# @classonlymethod通过描述符自定义装饰器 2、请求来了,路由也匹配成功,执行上边返回的view(requets),加括号调用,并且传入了当次请求的request对象 3、然后又返回了当前对象的dispatch方法,自己的名称空间内没有,那么去父类中找,然后发现父类(View)的dispatch方法返回了return handler(request, *args, **kwargs) 4、dispatch方法处理请求,什么请求对应什么方法
# 父类dispatch方法:
def dispatch(self, request, *args, **kwargs):
# 请求方法小写如果在当前对象的http_method_names中(八个请求方法)
'''
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
'''
if request.method.lower() in self.http_method_names:
# 如果成立,那么执行下面的反射,从当前对象(视图类的对象)拿到请求方法,如果是get请求就拿到get方法,post请求就拿到post方法,然后赋给handler,handler就是相应请求对应的方法,最后返回handler(request),本质其实是get(request)
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
# 不成立,也就是没有该请求对应的方法,执行http_method_not_allowed方法,弹出警告
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs) # http_method_not_allowed源码
def http_method_not_allowed(self, request, *args, **kwargs):
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return HttpResponseNotAllowed(self._allowed_methods())
  • getattr(obj,pro,None):按pro判断是否有无obj.pro属性,没有返回None,None可以自定制
  • 反射忘记的可以看俺的这篇博客:python 多态、组合、反射

Django View和DRF APIView的小插曲

  • DRF中最“牛逼”的视图类是APIView,不过也继承了View

  • 导入:from rest_framework.views import APIView

  • Django中最“牛逼”的视图类是View

  • 导入:

    from django.views import View # 最简
    from django.views.generic import View
    from django.views.generic.base import View # 最完整 '''这三个导入的结果都是一个View,想知道缘由还得看源码和目录结构,如下:'''
    # 目录结构
    -django
    -··· # 此处省略n个包
    -views
    -- __init__.py
    -- generic
    ---- __init__.py
    ---- base.py
    # 源码分析
    '''问题:为什么上述不同的py文件都可以指向同一个类?'''
    我的思路是羊毛出在羊身上,既然都指向类,那么从结果出发是不是更好
    1、View类在base.py中,那么显而易见最完整的导入是没问题的,重点是为什么简写也可以
    2、base.py所在的包generic,如果从包中导入py文件我们知道可以通过“白名单”__all__来指定谁可以被其他文件导入,我们发现generic包中指定了:
    from django.views.generic.base import RedirectView, TemplateView, View和__all__ = [ 'View',·····],到此第二个导入解决 # 那么第一个这么短这么简单又是为啥?
    3、同样在views包的__init__.py文件中发现:
    from django.views.generic.base import View
    __all__ = ['View']
    View类被__all__变量包裹了

ps:不管是DRF中的APIView还是乱七八糟的xxView,最后只要继承了Django中的View就是视图类

DRF之APIView和Request对象分析

APIView的执行流程

# 同样和Django中一样写一个视图类,只不过DRF中用APIView底层还是View
'''views.py'''
from rest_framework.response import Response
class Test(APIView):
def get(self, request):
data = {'status': 200, 'msg': 'success'}
return Response(data) def post(self, request):
data = {'status': 200, 'msg': 'success'}
return Response(data)
'''urls.py'''
path('test/', views.Test.as_view())

APIView流程源码分析

1、路由:path('test/', views.Test.as_view()),path第二个参数任然返回函数内存地址,也还是类绑定方法,Test没有as_view方法,去继承的APIView中找,这次不需要和Django一样去View中找了,庆幸的是APIView中有as_view方法,核心源码如下:

@classmethod
def as_view(cls, **initkwargs):
# 校验反射的结果
if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
def force_evaluation():
raise RuntimeError(
'Do not evaluate the `.queryset` attribute directly, '····
)
··· # 核心代码
# 这里调用了父类的as_view,查看源码发现又回到了Django中的View类,所以本质还是和上面一样,用到了闭包返回的view
view = super().as_view(**initkwargs)
···
# 局部去掉了csrf校验和加装饰器的效果是一样的
return csrf_exempt(view) 2、view = super().as_view(**initkwargs),这里跳转了一下,其实看了父类(View)的源码是和上面Django中写视图类继承的View是一样的,这里的(APIView)的as_view只是进行了简单处理和去掉了csrf中间件校验,真实使用的还是View类中的as_view
3、然后还是闭包函数的返回值view加括号调用,传入了当前对象的request,继续执行了self.dicpatch(),当前类(Test)没有去父类(APIview)找dispatch方法,发现APIView类中有,千万注意了这里可不是View中的dispatch方法了
4、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是一个,因为放到了self中
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了,是新的
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 总结:
1、路由:path('test/', views.Test.as_view())
2、APIView类的as_view执行,最终使用View类的as_view
3、执行闭包返回view加括号调用到此就是as_view加括号调用
4、调用执行了view()返回dispatch,但是这里的父类不是View,是APIview所以执行的dispatch是APIView中的dispatch方法
5、dispatch方法中包装了新的Request对象,以后视图类中的方法传入的request都是新的,无论三大认证还是视图函数的方法,执行过程中出了异常,都会被处理掉
6、dispatch执行完毕返回reponse对象,跳转回进入视图函数继续执行as_view去掉了csrf校验

如何包装了新的request?

# APIView中的dispatch方法处理老的request
request = self.initialize_request(request, *args, **kwargs) # initialize_request方法,当前类(自己写的)没有去父类找,还是APIView
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request) return Request(
request, # 老的request,传入的非处理的,返回后就是新的了
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
注意:上面返回的Request对象是rest_framework导入的,然后实例化后返回的,实例化就少不了__init__构造函数
from rest_framework.request import Request
'''Request类'''
class Request:
# 这里即将要初始化的request是传入的老的request
def __init__(self, request, parsers=None, authenticators=None,negotiator=None, parser_context=None):
···
# 初始化,将传入的老的request放入新的_request中,所以request._request是老的,self.request._request等价于request._request
self._request = request
··· # 验证一下新老request
class Test(APIView):
def get(self, request):
data = {'status': 200, 'msg': 'success'}
# 新的request
print(type(request)) # <class 'rest_framework.request.Request'>
print(type(self.request))# <class 'rest_framework.request.Request'>
# 老的request
print(type(self.request._request)) # <class 'django.core.handlers.wsgi.WSGIRequest'>
print(type(request._request)) # <class 'django.core.handlers.wsgi.WSGIRequest'>
return Response(data) 新的:<class 'rest_framework.request.Request'>
老的:<class 'django.core.handlers.wsgi.WSGIRequest'>

三大认证如何执行?

# APIView类处理三大认证
self.initial(request, *args, **kwargs)
# 核心源码
def initial(self, request, *args, **kwargs):
····
self.perform_authentication(request) # 认证
self.check_permissions(request) # 权限
self.check_throttles(request) # 频率
···
# 经过了三大认证才进入了视图函数
中间件---路由---···---三大认证---视图函数····
# 类似二次校验

Request对象分析

这里是经过包装后的request

rest_framework.request.Request常用属性和方法

这里的request和原来的Django使用request一样,只是多了一个request.data

  • request.data前端POST提交的数据,可以处理多种格式的数据,无论前端传什么编码post提交的数据都在data中

    ps:原来提交的数据在request.POST里,有局限性只能处理urlencoded和formdata编码格式,json格式不能处理,是没有request.data的,request其余使用方法的都一样

# 如果用过新包装过的request来调用原来的方法呢?这样给使用者的感觉确实没什么变化源码如下:
'''重写了getattr'''
def __getattr__(self, attr):
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr) 重写getattr的结果是,新的request.method执行的时候本质是request._request.method执行了原来老的request的方法,通过这句getattr(self._request, attr)反射,所以才一样

总结新的request当老的用即可,只是多了个data前端post请求传入的数据,三种编码格式都可以获取

验证处理三种编码格式

  • json格式,只有request.data可以获取,结果是字典
  • form-data格式和urlencode格式都可以获取并且是QueryDict对象
from rest_framework.response import Response
class Test(APIView):
def post(self, request):
data = {'status': 200, 'msg': 'success'}
print(request.POST)
print(request._request.POST)
print(request.data)
return Response(data)

验证

# form-data格式结果
<QueryDict: {'name': ['hammer']}>
<QueryDict: {'name': ['hammer']}>
<QueryDict: {'name': ['hammer']}>
# urlencode格式结果
<QueryDict: {'name': ['hammer']}>
<QueryDict: {'name': ['hammer']}>
<QueryDict: {'name': ['hammer']}>
# json格式结果
<QueryDict: {}>
<QueryDict: {}>
{'name': 'Hammer'}

官网的一些解释

原来的django中没有request.data,造一个!

# 原来的django的request对象中没有data,使得request.data-->无论什么编码格式,post提交数据,data都有值
from django.views import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from functools import wraps
def outter(func):
@wraps(func)
def inner(request,*args,**kwargs):
import json
if request.POST:
request.data = request.POST
else:
# 将request.body从json对象转换为字典
request.data = json.loads(request.body)
res = func(request,*args,**kwargs)
return res
return inner @method_decorator(outter,name='post')
class TestView(View):
def get(self, request):
return HttpResponse('get请求') def post(self, request):
# 只测试了json格式其余都可以也测了
print(request.data) # {'name': 'Hammer'}
print(request.POST) # <QueryDict: {}>
return HttpResponse('post请求')

灵感来源:传送门

不会DRF?源码都分析透了确定不来看?的更多相关文章

  1. DRF源码系列分析

    DRF源码系列分析 DRF源码系列分析--版本 DRF源码系列分析--认证 DRF源码系列分析--权限 DRF源码系列分析--节流

  2. Django之DRF源码分析(二)---数据校验部分

    Django之DRF源码分析(二)---数据校验部分 is_valid() 源码 def is_valid(self, raise_exception=False): assert not hasat ...

  3. 【Orleans开胃菜系列2】连接Connect源码简易分析

    [Orleans开胃菜系列2]连接Connect源码简易分析 /** * prism.js Github theme based on GitHub's theme. * @author Sam Cl ...

  4. MapReduce的ReduceTask任务的运行源码级分析

    MapReduce的MapTask任务的运行源码级分析 这篇文章好不容易恢复了...谢天谢地...这篇文章讲了MapTask的执行流程.咱们这一节讲解ReduceTask的执行流程.ReduceTas ...

  5. MapReduce的MapTask任务的运行源码级分析

    TaskTracker任务初始化及启动task源码级分析 这篇文章中分析了任务的启动,每个task都会使用一个进程占用一个JVM来执行,org.apache.hadoop.mapred.Child方法 ...

  6. TaskTracker任务初始化及启动task源码级分析

    在监听器初始化Job.JobTracker相应TaskTracker心跳.调度器分配task源码级分析中我们分析的Tasktracker发送心跳的机制,这一节我们分析TaskTracker接受JobT ...

  7. MongoDB源码分析——mongod程序源码入口分析

    Edit 说明:第一次写笔记,之前都是看别人写的,觉得很简单,开始写了之后才发现真的很难,不知道该怎么分析,这篇文章也参考了很多前辈对MongoDB源码的分析,也有一些自己的理解,后续将会继续分析其他 ...

  8. FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分

    ===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...

  9. LinkedHashMap 源码详细分析(JDK1.8)

    1. 概述 LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题.除此之外,Linke ...

随机推荐

  1. Centos 7.6 使用 kubekey 部署 kubesphere v3.1.0

    文章目录 主要功能 硬件要求 Kubernetes版本要求 配置主机之间的免密 安装所需依赖 下载KubeKey 创建Kubernetes集群以及KubeSphere kk命令使用方式 修改配置文件 ...

  2. c++ StrVec等效vector(string)的类

    c++ StrVec等效vector<string>的类 知识点 静态成员变量要在类外定义和初始化 allocator类是使用和uninitialized_copy的配合使用,实现stri ...

  3. 从命令模式的维度理解Spring 之Application Event

    Spring的事件(Application Event)为Bean与Bean之间的信息通讯提供了支持.当一个Bean处理完一个任务之后,希望另一Bean指定并能做相应的处理,这时我们就需要让另外一个B ...

  4. CentOS8安装启用telnet服务

    CentOS8默认只安装telnet的客户端,其服务端telnet-server由于安全原因由用户自主选择安装.下面为实践安装启动telnet-server,注意其与vsftp服务安装的异同. 1.查 ...

  5. archery关闭导出功能

    https://github.com/hhyo/Archery/issues/1367 进入docker容器,找到sqlquery.html.修改showExport相关配置为以下内容. {% if ...

  6. SpringBoot实现QQ邮件发送

    建项目 创建一个SpringBoot项目 改pom,导入相关依赖 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE & ...

  7. 图解|12张图告诉你MySQL的主键查询为什么这么快

    这是图解MySQL的第3篇文章,这篇文章会让大家清楚地明白: 什么是InnoDB行格式?InnoDB页是什么? InnoDB页和InnoDB行格式都有哪些字段信息? 为什么推荐使用自增ID作为主键,而 ...

  8. Java高性能本地缓存框架Caffeine

    一.序言 Caffeine是一个进程内部缓存框架,使用了Java 8最新的[StampedLock]乐观锁技术,极大提高缓存并发吞吐量,一个高性能的 Java 缓存库,被称为最快缓存. 二.缓存简介 ...

  9. 网络测试仪实操手册 RENIX 机框管理

    本文主要阐述信而泰BIGTAO系列 网络测试仪器机框相关操作方法.文章分为机框添加.机框删除.机框重启.机框关机四部分. 第一部分:机框添加 1.添加过程 1.1打开软件 1.2添加端口 1.3输入I ...

  10. 【C#IO 操作】字符流(StreamWriter、StreamReader)

    StreamWaiter类和StreamReader类的用法 事实上, StreamReader为了性能的考虑, 在自己的内部内置并维护了一个byte buffer. 如果在声明StreamReade ...