# 源码分析-rest_framework

# 背景 preview

  1. 开发模式:
  • 普通开发方式(前后端放在一起写)
  • 前后端分离(PC端、移动端等只需开发后端一套接口)
  1. 后端开发:
  • 为前端提供URL(API/接口开发)
  • 永远返回 HttpResponse()
  1. 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')
  1. 列表生成式
class Foo:
    pass
class Bar:
    pass

# 对象列表
v = [item() for item in [Foo, Bar]]
  1. 面向对象的封装体现在两个方面
  • 对同一类方法封装到类中
class File:
    文件增删改查方法

class Database:
    数据库增删改查方法
  • 将同一类数据封装到对象中 入口 def __init __ 方法
def Bar:
    def __init__(self, x, y):
        self.name = x
        self.age = y

b = Bar('alex', 20)
  1. 把多个东西放到一个小箱子里,一层套着一层
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 图谱

image

  • 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
上次更新: 8/28/2022, 12:39:12 PM