# 源码分析-rest_framework
# 背景 preview
- 开发模式:
- 普通开发方式(前后端放在一起写)
- 前后端分离(PC端、移动端等只需开发后端一套接口)
- 后端开发:
- 为前端提供URL(API/接口开发)
- 永远返回 HttpResponse()
- Django CBV
from django.views import View
# 路由中自动匹配 StudentView.as_view()
class StudentView(View):
def get(self, req, *arg, **kw):
return HttpResponse('GET')
def post(self, req, *arg, **kw):
return HttpResponse('POST')
- 列表生成式
class Foo:
pass
class Bar:
pass
# 对象列表
v = [item() for item in [Foo, Bar]]
- 面向对象的封装体现在两个方面
- 对同一类方法封装到类中
class File:
文件增删改查方法
class Database:
数据库增删改查方法
- 将同一类数据封装到对象中 入口 def __init __ 方法
def Bar:
def __init__(self, x, y):
self.name = x
self.age = y
b = Bar('alex', 20)
- 把多个东西放到一个小箱子里,一层套着一层
class Request(object):
def __init__(self, obj):
self.obj = obj
@property
def user(self):
return self.obj.authenticate()
class Auth(object):
def __init__(self, name, age):
self.name = name
self.age = age
def authenticate(self):
return self.name
class APIView(object):
def dispatch(self):
self.func()
def func(self):
a = Auth('alex', 18)
b = Auth('linda', 20)
req = Request(b)
print(req.user)
a = APIView()
a.dispatch()
# 接口 restful api
加密方式
- https 更安全
- http
请求方式
- URL方 式https://www.domain.com/api/ 更简单
- 子域名方式(需要解决跨域问题) https://api.domain.com
版本控制 https://www.doamin/api/v1/
面向资源编程 https://www.doamin/api/v1/名词/
根据method不同做不同的操作
- GET /order/
- POST /order/
- GET /order/1/
- PUT /order/1/
- DELETE /order/1/
过滤 通过在URL上传递参数的形式传递搜索条件
状态码 是远远不够的 status_code + 自定义code 解决,前端有时不关注status_code
- 401 Unauthorized
- 403 Forbidden
错误信息处理,msg
Hypermedia API 后端拼接好URL返回
规范(一种推荐) 视情况而定
# 请求
POST https://localhost/api/v1/名词/
# 响应
{
"code": 0,
"msg": "成功",
"data": null
}
# 结构 structure
版本 3.11.0
rest_framework
|- request.py # rest_framework enhance request对象
|- settings.py # REST_FRAMEWORK = {默认配置}
|- status.py # http命名状态码 如 200 404
|- authentication.py # 认证 请求信息 -> reqeust.user
|- views.py # 定义APIView, 重构请求视图dispatch
|- permissions.py # 权限 限制访问数据范围
|- throttling.py # 访问频率控制
|- versioning.py # 版本控制 可不用
|- parsers.py # 解析器 如解析request.body中内容
|- serializers.py # 序列化器
视图函数+装饰器 可以理解为 main 函数,包括对请求和响应的处理
1. 对请求处理
- 认证
- 权限
- 解析器
- 访问频率控制
2. 对响应处理
- 序列化器
- 渲染器
# 请求 request.py
APIView 内部是dispatch通过request.method反射得到视图函数去匹配对应的路由,并且丰富了reqeust属性
先定义, 后使用 是编程的基本规范, 同样request中属性名也要遵守这个规范,明白request中变量属性的来源有助于灵活使用
常规初始化变量
- request.parsers 用于解析 request's content
- request.authenticators 用于认证 request's user
计算属性property
- request.content_type 来源django的请求META属性
- request.stream 来自request._stream,可来自request.body
- request.query_params 来源django的请求.GET
- request.data 会结合request.parsers进行解析 request._full_data request._data
- request.user 执行self._authenticate()
- 如果满足其中一条 user_auth_tuple = authenticator.authenticate(self) 直接返回
- 最后有默认的 self._not_authenticated(),如request.user可为django的匿名用户
- 中间有异常时 raise exceptions.APIException
- request.auth 和self._authenticate()有关
桥接代理属性
- request._request
def __getattr__(self, attr):
"""
If an attribute does not exist on this instance, then we also attempt
to proxy it to the underlying HttpRequest object.
"""
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
小技巧: @property用于读, 如request.user,对应request._user为实际的存储属性,所以写的时候就可以选择数据来源和数据校验
# 配置 settings.py
django项目配置settings.py添加对应配置项 REST_FRAMEWORK = {...}
- IMPORT_STRINGS设置的key会动态import为对象
- api_settings 如果用户在django项目settings设置配置,优先使用用户设置的配置,否则为rest_framework中默认配置
- self._cached_attrs = set() 记录被获取的属性,清空则可以实现动态reload
- APIView中的局部配置会覆盖settings.py中的全局配置
# 认证 authentication.py
有些API需要用户登录之后才能访问,有些不需要登录。
- 认证authenticate用来检测用户请求的信息中是否有认证标识,如django请求头'HTTP_AUTHORIZATION', 甚至url可以自定义key
- 后端根据此标识认证通过后,可以在request注入user信息 request.user
- 视图函数view中就可以使用该用户信息 request.user
- 可以全局配置认证不通过的默认用户,如django中的匿名用户
- 创建类:继承BaseAuthentication; 实现:authenticate方法
- 返回值:
- None,我不管了,下一认证来执行。
- raise exceptions.AuthenticationFailed('用户认证失败') # from rest_framework import exceptions
- (元素1,元素2) # 元素1赋值给request.user; 元素2赋值给request.auth
- 局部使用
from rest_framework.authentication import BaseAuthentication,BasicAuthentication
class UserInfoView(APIView):
"""
订单相关业务
"""
authentication_classes = [BasicAuthentication,]
def get(self,request,*args,**kwargs):
print(request.user)
return HttpResponse('用户信息')
- 全局使用:
REST_FRAMEWORK = {
# 全局使用的认证类
"DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.FirstAuthtication','api.utils.auth.Authtication', ],
# "UNAUTHENTICATED_USER":lambda :"匿名用户"
"UNAUTHENTICATED_USER":None, # 匿名,request.user = None
"UNAUTHENTICATED_TOKEN":None,# 匿名,request.auth = None
}
小结: APIView当设置局部认证类authentication_classes时,会替代全局配置中默认认证类
# 视图 views.py
APIView 内部是dispatch通过request.method反射得到视图函数去匹配对应的路由,并且丰富了reqeust属性
- 可局部配置 如authentication_classes
- 重构as_view类方法,类方法内部可以动态实例化 self = cls(**initkwargs)
- 丰富强化了 reqeust
- 各类策略叠加
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
# 丰富强化reqeust
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
# 抛出异常 与 处理异常 编程结构体
try:
# 各种策略叠加,如版本控制、内容解析、登录信息认证、权限认证、限流等
# reqeust.version, reqeust.versioning_scheme
# request.user, request.auth
# check_permissions: permission.has_permission(request, self)
# check_throttles: throttle.allow_request(request, self)
self.initial(request, *args, **kwargs)
# 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
# 权限 permissions.py
权限: 解决的是不同用户进入视图之后,查看数据范围的问题
# 自定义权限
class MyPermission(BasePermission):
message = '必须是SVIP才能查看'
def has_permission(self,request,view):
if request.user.user_type != 3:
return False
return True
# 业务视图中配置具体权限,若未配置则使用默认的全局配置
class OrderView(APIView):
"""
订单相关业务(只有SVIP用户有权限)
"""
permission_classes = [MyPermission, ]
# 默认的全局配置用户登录认证通过即可
class IsAuthenticated(BasePermission):
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
# 初始化请求进行依次权限的校验,需要全部满足,有一个不满足就抛出异常,不同于认证逻辑
def check_permissions(self, request):
for permission in self.get_permissions():
if not permission.has_permission(request, self):
# 抛出异常,外层会接到该异常
self.permission_denied(
request, message=getattr(permission, 'message', None)
)
# 限流 throttling.py
访问频率控制思路
- 定位标识id
- 存储该标识id在规定时间访问的时间戳
- 超过最大次数时,返回提示
- 规定时间内访问时间戳随时间动态剔除
import time
VISIT_RECORD = {}
class VisitThrottle(object):
"""60s内只能访问3次"""
def __init__(self):
self.history = None
def allow_request(self,request,view):
# 1. 获取用户IP
remote_addr = request.META.get('REMOTE_ADDR')
ctime = time.time()
if remote_addr not in VISIT_RECORD:
VISIT_RECORD[remote_addr] = [ctime,]
return True
history = VISIT_RECORD.get(remote_addr)
self.history = history
while history and history[-1] < ctime - 60:
history.pop()
if len(history) < 3:
history.insert(0,ctime)
return True
# return True # 表示可以继续访问
# return False # 表示访问频率太高,被限制
def wait(self):
"""
还需要等多少秒才能访问
:return:
"""
ctime = time.time()
return 60 - (ctime - self.history[-1])
继承:BaseThrottle,实现:allow_request、wait
# 版本 versioning.py
- 可继承 类BaseVersioning 实现方法determine_version即可 注入到reqeust.version中
# 解析 parsers.py
# django 原始的request可以好好研究一下
from django.http.request import HttpRequest
from django.core.handlers.wsgi import WSGIRequest
request.POST有值满足条件
- 请求头要求: Content-Type: application/x-www-form-urlencoded
- 数据格式要求: 'name=linda&age=18&gender=female'
PS: 如果请求头中的Content-Type: application/x-www-form-urlencoded,request.POST中才有值(去request.body中解析数据)
如前端提交数据:
a. form表单提交
<form method>
input
</form>
b. ajax提交
$.ajax({
url: ...
type:POST,
data: {name: linda, age: 18, gender=female} 内部转换为'name=linda&age=18&gender=female'
})
另外ajax可以定制请求头 headers json形式 JSON.stringfy({name: linda, age: 18, gender=female})
rest_framework 解析器,对请求体数据进行解析
- JSONParser 允许用户发送JSON格式数据 即content_type='application/json',解析后结果存在request.data中
- FormParser 允许用户发送Form表单格式数据 即content_type=application/x-www-form-urlencoded,解析后结果存在request.data中
# 渲染 renderers.py
封装渲染返回的数据格式
- 常见渲染格式 JSONRenderer、BrowsableAPIRenderer
- 可全局 'DEFAULT_RENDERER_CLASSES' 和视图 renderer_classes 配置
- 可继承自定义 BaseRenderer
# 序列化 serializers.py
- 构造序列化器
from rest_framework import serializers
# 可执行即可
def my_validate(value):
# 自定义验证字段
return value
class UserInfoSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True) # 指定参数规则
new_field = serializers.CharField(source='field') # 指定字段来源 row.field
display = serializers.CharField(source='get_field_display') # 可执行
group_id = serializers.IntegerField(source='group.id') # 外键关联
field = serializers.CharField(max_length=32, validators=[my_validate, ]) # 验证
method = serializers.SerializerMethodField() # 自定义显示
# 单个字段验证
def validate_title(self, value):
# 异常时,抛出
# raise serializers.ValidationError('XXXXX')
return value
def validate(self, attrs):
# 校验多个字段,注意需要返回值
return attrs
def get_method(self, row):
# 处理材料 self 信息就很多了
# row 为一条记录对象
return self, row
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
class CustomizeSerializer(serializers.ModelSerializer):
class Meta:
# 根据serializer_field_mapping映射关系,Custom字段自动序列化字段
model = Custom
# 全部字段
fields = '__all__'
# 包含某些字段
fields = ['id', 'title']
# 排除某些字段
# exclude = ['user']
# 代表嵌套第几层
depth = 1
# 额外限制
read_only_fields = ["id"]
extra_kwargs = {"title": {"validators": [my_validate, ]}}
- 基本用法
`data=`传入参数时,数据校验, 然后保存或更新
- 创建POST Serializer(data=request.data)
- 更新PUT Serializer(instance, data=request.data, partial=True)
ser.is_valid() 进行数据校验
- 校验成功 ser.validated_data -> ser.save() 内部执行 ser.create 或 ser.update 操作
- 校验失败 ser.errors
`data=`未传入参数时,为对象或queryset序列化
- 列表GET Serializer(query_set, many=True).data 多个
- 详情GET Serializer(instance).data 单个
- 源码分析
class BaseSerializer(Field):
"""
序列化器比较特殊有严格的执行顺序
In particular, if a `data=` 参数有值:
.is_valid() - Available.
.initial_data - Available.
.validated_data - Only available after calling `is_valid()`
.errors - Only available after calling `is_valid()`
.data - Only available after calling `is_valid()`
If a `data=` 参数未传:
.is_valid() - Not available.
.initial_data - Not available.
.validated_data - Not available.
.errors - Not available.
.data - Available.
"""
def __init__(self, instance=None, data=empty, **kwargs):
self.instance = instance
if data is not empty:
self.initial_data = data
self.partial = kwargs.pop('partial', False)
self._context = kwargs.pop('context', {})
kwargs.pop('many', None)
super().__init__(**kwargs)
def __new__(cls, *args, **kwargs):
# 实例化时先执行__new__,再执行__init__。当many=True进行多个和单个的区分
# 根据类cls创建对象并返回
# 执行返回对象对应类型的__init__方法 ***
if kwargs.pop('many', False):
return cls.many_init(*args, **kwargs)
return super().__new__(cls, *args, **kwargs)
@classmethod
def many_init(cls, *args, **kwargs):
list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer)
return list_serializer_class(*args, **list_kwargs)
self.data --> self.to_representation(self.instance) --> attribute = field.get_attribute(instance) --> ret[field.field_name] = field.to_representation(attribute)
# 视图集 viewsets.py
视图函数多次封装 关联 generics.py 和 routers.py
ViewSetMixin:
@classonlymethod
def as_view(cls, actions=None, **kw)
"""
重构as_view({'get': 'list', 'post': 'create'}),
或者as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})
把 actions对应的动作函数映射为对应的方法
"""
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler)
# 对核心数据如queryset、serializer做一层浅浅的封装
class GenericAPIView(views.APIView):
queryset = None
serializer_class = None
lookup_field = 'pk'
lookup_url_kwarg = None
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get_queryset(self): pass
def get_object(self): pass
def get_serializer_class(self): pass
def get_serializer(self): pass
def filter_queryset(self): pass
def paginator(self):pass
def paginate_queryset(self): pass
def get_paginated_response(self):pass
# 大集合
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
pass
# 示例
from django.urls import path
from rest_framework.routers import DefaultRouter
from delta import apis
"""
自动生成路由
api/v1/ ^books/router/$ [name='books-list']
api/v1/ ^books/router\.(?P<format>[a-z0-9]+)/?$ [name='books-list']
api/v1/ ^books/router/(?P<pk>[^/.]+)/$ [name='books-detail']
api/v1/ ^books/router/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='books-detail']
"""
router = DefaultRouter()
router.register('books/router', apis.BookModelViewSet)
urlpatterns = [
# 需要指定 method, serializer, model
path('books/', apis.BookView.as_view()),
path('books/<int:pk>/', apis.BookDetail.as_view()),
# 第一次封装:提取公共 serializer, model 手动映射method -> view
path('books/generic/', apis.BookGenericView.as_view()),
path('books/generic/<int:pk>/', apis.BookGenericDetail.as_view()),
# 第二次封装:组合 list create retrieve update destroy
path('books/lc/', apis.BookListCreate.as_view()),
path('books/rud/<int:pk>/', apis.BookRetrieveUpdateDestroy.as_view()),
# 第三次封装:自动映射 method -> view
path('books/viewset/', apis.BookModelViewSet.as_view({'get': 'list', 'post': 'create'})),
path('books/viewset/<int:pk>/', apis.BookModelViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
urlpatterns += router.urls
- Django REST Framework View 图谱
- rest_framework 导入汇总
from rest_framework import views
from rest_framework import generics
from rest_framework import mixins
from rest_framework import viewsets
ViewSetMixin 各类method映射集合所以叫ViewSet Mixin不能单独使用需要和其他类配合如GenericAPIView
- as_view 中 参数actions用来method映射为对应方法,如解决get->list;get->retrieve
GenericAPIView(views.APIView) 继承APIView所以叫GenericAPIView
- 属性: queryset、serializer_class、lookup_field、lookup_url_kwarg、filter_backends、pagination_class
- 方法: get_queryset 子类一般会重构
- 方法: get_object
- 方法: filter_queryset 结合 filter_backends 过滤queryset
- 方法: get_serializer_class
- 方法: get_serializer
- 计算属性: paginator
- 方法: paginate_queryset
- 方法: get_paginated_response
CreateModelMixin
- 实现 create 方法 内部serializer先校验再保存
- 涉及到get_serializer 需要结合GenericAPIView使用
- 对外接口 CreateAPIView -> post
ListModelMixin
- 实现 list 方法 内部queryset、分页、序列化
- 涉及到get_serializer paginate_queryset需要结合GenericAPIView使用
- 对外接口 ListAPIView -> get
RetrieveModelMixin
- 实现 retrieve 方法 内部获取对象 序列化
- 涉及到get_serializer get_object需要结合GenericAPIView使用
- 对外接口 RetrieveAPIView ->get
UpdateModelMixin
- 实现 update 方法 内部获取对象 序列化校验更新
- 涉及到get_serializer get_object需要结合GenericAPIView使用
- 对外接口 UpdateAPIView - put/patch
DestroyModelMixin
- 实现 destroy 方法 内部获取对象删除
- 涉及到get_serializer get_object需要结合GenericAPIView使用
- 对外接口 DestroyAPIView -> delete
ListCreateAPIView
- get
- post
RetrieveUpdateAPIView
- get
- update/patch
RetrieveDestroyAPIView
- get
- delete
RetrieveUpdateDestroyAPIView
- get
- put/patch
- delete