DRF

DRF

django restframework(简称drf)本质上其实就是一个别人编写好的app,里面集成了很多编写restful API的功能功能,接下里咱们就来学习drf并用他来开发restful API。

快速上手

  • 安装

    pip install djangorestframework==3.12.4
    
    版本要求:djangorestframework==3.12.4
    	Python (3.5, 3.6, 3.7, 3.8, 3.9)
    	Django (2.2, 3.0, 3.1)
        
    版本要求:djangorestframework==3.11.2
    	Python (3.5, 3.6, 3.7, 3.8)
    	Django (1.11, 2.0, 2.1, 2.2, 3.0)
    
  • 配置,在settings.py中添加配置

    INSTALLED_APPS = [
        ...
        # 注册rest_framework(drf)
        'rest_framework',
    ]
    
    # drf相关配置以后编写在这里 
    REST_FRAMEWORK = {
       
    }
    
  • URL和视图

    # urls.py
    
    from django.urls import path
    from app01 import views
    
    urlpatterns = [
        path('users/', views.UserView.as_view()),
    ]
    
    # views.py
    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    
    class UserView(APIView):
        def get(self, request, *args, **kwargs):
            return Response({"code": 1000, "data": "xxx"})
    
        def post(self, request, *args, **kwargs):
            return Response({"code": 1000, "data": "xxx"})
    

其实drf框架是在django基础进行的扩展,所以上述执行过得底层实现流程(同django的CBV):

drf中重写了 as_viewdispatch方法,其实就是在原来django的功能基础上添加了一些功能,例如:

  • as_view,免除了csrf 验证,一般前后端分离不会使用csrf token认证(后期会使用jwt认证)。
  • dispatch,内部添加了 版本处理、认证、权限、访问频率限制等诸多功能(后期逐一讲解)。

请求数据封装

以前我们通过django开发项目时,视图中的request是 django.core.handlers.wsgi.WSGIRequest 类的对象,其中包含了请求相关的所有数据。

# Django FBV
def index(request):
	request.method
	request.POST
	request.GET
	request.body

# Django CBV
from django.views import View
class UserView(View):
	def get(self,request):
        request.method
        request.POST
        request.GET
        request.body

而在使用drf框架时,视图中的request是rest_framework.request.Request类的对象,其是又对django的request进行了一次封装,包含了除django原request对象以外,还包含其他后期会使用的其他对象。

from rest_framework.views import APIView
from rest_framework.response import Response


class UserView(APIView):
    def get(self, request, *args, **kwargs):
        # request,不再是django中的request,而是又被封装了一层,内部包含:django的request、认证、解析器等。
        return Response({"code": 1000, "data": "xxx"})

    def post(self, request, *args, **kwargs):
        return Response({"code": 1000, "data": "xxx"})

对象 = (request, 其他数据)
# rest_framework.request.Request 类

class Request:
    """
    Wrapper allowing to enhance a standard `HttpRequest` instance.
    Kwargs:
        - request(HttpRequest). The original request instance. (django中的request)
        - parsers(list/tuple). The parsers to use for parsing the
          request content.
        - authenticators(list/tuple). The authenticators used to try
          authenticating the request's user.
    """

    def __init__(self, request, parsers=None, authenticators=None,negotiator=None, parser_context=None):
    	self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        ...
	
    @property
    def query_params(self):
        """
        More semantically correct name for request.GET.
        """
        return self._request.GET

    @property
    def data(self):
        if not _hasattr(self, '_full_data'):
            self._load_data_and_files()
        return self._full_data
    
	def __getattr__(self, attr):
        try:
            return getattr(self._request, attr) # self._request.method
        except AttributeError:
            return self.__getattribute__(attr)

所以,在使用drf框架开发时,视图中的request对象与原来的有些不同,例如:

from rest_framework.views import APIView
from rest_framework.response import Response
from django.views import View
from rest_framework.request import Request

class UserView(APIView):
        def get(self, request, *args, **kwargs):
        # 读取body中的参数
        print(request.data)

        # 读取url中的参数
        print(request.query_params)

        # 读取请求头中的参数
        print(request.META.get("HTTP_COOKIE"))

        # 获取上传的文件参数
        print(request.FILES.get('file_name'))

        return Response({"code": 1000, "data": "你好"})

版本管理

在Restful规范中要求,后端的api中需要体现版本

drf框架中支持5种版本的设置

URL的GET参数传递

class UserView(APIView):
    versioning_class = QueryParameterVersioning

    def get(self, request, *args, **kwargs):
        # 获取版本号
        print(request.version) # v1
        return Response({"code": 1000, "data": "你好"})

请求url: http://<>/users/?version=v1

# rest_framework 配置
REST_FRAMEWORK = {
    # 定义版本的key值
    # "VERSION_PARAM": "v",
    # 定义版本的默认值,当有传递版本值时取传递的
    "DEFAULT_VERSION": "v1",
    # 定义允许的版本
    "ALLOWED_VERSIONS": ["v1", "v2", "v3"],
    # 定义版本控制
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning"
}

URL路径传参

urls.py

urlpatterns = [
    path('users/', views.UserView.as_view()),
    re_path(r'^api/(?P<version>\w+)/users/$', views.UserView.as_view()),
]

views.py

class UserView(APIView):
    # versioning_class = QueryParameterVersioning
    versioning_class = URLPathVersioning
    def get(self, request, *args, **kwargs):
        # 获取版本号
        print(request.version)
        return Response({"code": 1000, "data": "你好"})

    def post(self, request, *args, **kwargs):
        return Response({"code": 1000, "data": "xxx"})

settings.py

# rest_framework 配置
REST_FRAMEWORK = {
    # 定义版本的key值
    # "VERSION_PARAM": "v",
    # 定义版本的默认值,当有传递版本值时取传递的
    "DEFAULT_VERSION": "v1",
    # 定义允许的版本
    "ALLOWED_VERSIONS": ["v1", "v2", "v3"],
    # 定义版本控制
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning"
}

请求url:http://<>/api/v2/users/

请求头传递

urls.py

urlpatterns = [
    # url get参数
    path('users/', views.UserView.as_view()),
    # url参数
    re_path(r'^api/(?P<version>\w+)/users/$', views.UserView.as_view()),
    # 请求头
    path('api/users/', views.UserView.as_view()),
]

views.py

from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning,AcceptHeaderVersioning


# Create your views here.
class UserView(APIView):
    # versioning_class = QueryParameterVersioning
    # versioning_class = URLPathVersioning
    versioning_class = AcceptHeaderVersioning
    def get(self, request, *args, **kwargs):
        # 获取版本号
        print(request.version)
        return Response({"code": 1000, "data": "你好"})

    def post(self, request, *args, **kwargs):
        return Response({"code": 1000, "data": "xxx"})

二级域名传递

路由的namespace传递

全局配置

上述示例中,如果想要应用某种 版本 的形式,需要在每个视图类中定义类变量:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning


class UserView(APIView):
    versioning_class = QueryParameterVersioning
    ...

如果你项目比较大,需要些很多的视图类,在每一个类中都写一遍会比较麻烦,所有drf中也支持了全局配置。

# settings.py

REST_FRAMEWORK = {
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning",  # 处理版本的类的路径
    "VERSION_PARAM": "version",  # URL参数传参时的key,例如:xxxx?version=v1
    "ALLOWED_VERSIONS": ["v1", "v2", "v3"],  # 限制支持的版本,None表示无限制
    "DEFAULT_VERSION": "v1",  # 默认版本
}

urls.py

urlpatterns = [
    path('api/users/', views.UserView.as_view()),
    path('api/admin/', views.AdminView.as_view()),
    path('api/<str:version>/order/', views.OrderView.as_view())
]

views.py

# 默认使用全局配置的版本
class UserView(APIView):
    # versioning_class = QueryParameterVersioning
    # versioning_class = URLPathVersioning
    # versioning_class = AcceptHeaderVersioning

    def get(self, request, *args, **kwargs):
        # 获取版本号
        print(request.version)
        return Response({"code": 1000, "data": "你好"})
    
# 默认使用全局配置的版本
class AdminVew(APIView):
    def get(self, request, *args, **kwargs):
        print(request.version)
        return Response({"code": 1000, "data": "你好"})


#覆盖默认版本类,用URLPathVersioning
class OderView(APIView):
    versioning_class = URLPathVersioning

    def get(self, request, *args, **kwargs):
        print(request.version)
        return Response({"code": 1000, "data": "你好"})

访问RUL

http://127.0.0.1:8000/api/users/?version=v1
http://127.0.0.1:8000/api/users/?version=v2
http://127.0.0.1:8000/api/users/?version=v3

http://127.0.0.1:8000/api/admin/?version=v1
http://127.0.0.1:8000/api/admin/?version=v2
http://127.0.0.1:8000/api/admin/?version=v3

http://127.0.0.1:8000/api/v1/order/
http://127.0.0.1:8000/api/v2/order/
http://127.0.0.1:8000/api/v3/order/

小结

版本的处理

  • 过程:选择版本处理类,获取用户传入的版本信息
  • 结果:在 request.version = 版本request.versioning_scheme=版本处理类的对象

认证

在开发后端的app时,不同的功能会有不同的限制,例如:

  • 无需认证,就可以访问并获取的数据
  • 需要认证,用户需要先登录,后续发送请求需携带登录时发放的凭证

在drf中也给我们提供了 认证组件 ,帮助我们快速实现认证相关的功能,例如:

# models.py

from django.db import models

class UserInfo(models.Model):
    username = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=64)
    token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)

views.py

class AuthView(APIView):
    def post(self, request):
        print(request.data)
        username = request.data.get("username")
        password = request.data.get("password")
        user_obj = models.UserInfo.objects.filter(username=username, password=password).first()
        if not user_obj:
            return Response({"code": 1001, "error": "用户名或密码错误"})
        token = str(uuid.uuid4())
        user_obj.token = token
        user_obj.save()
        return Response({"code": 1000, "data": {"token": token, "name": username}})

自定义认证类

class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:
            raise AuthenticationFailed({"code": 1001, "error": "认证失败"})
        user_obj = models.UserInfo.objects.filter(token=token).first()
        if not user_obj:
            raise AuthenticationFailed({"code": 1001, "error": "认证失败"})
        return (user_obj, token)

    def authenticate_header(self, request):
        return 'bearer realm="api"'
    
class OrderView(APIView):
    versioning_class = URLPathVersioning
    authentication_classes = [MyAuthentication, ]

    def get(self, request, *args, **kwargs):
        print(request.version)
        return Response({"code": 1000, "data": "你好"})

在视图类中设置类变量 authentication_classes的值为 认证类 MyAuthentication,表示此视图在执行内部功能之前需要先经过 认证。

认证类的内部就是去执行:authenticate方法,根据返回值来表示认证结果。

  • 抛出异常AuthenticationFailed,表示认证失败。内部还会执行 authenticate_header将返回值设置给响应头 WWW-Authenticate

  • 返回含有两个元素的元组,表示认证成功,并且会将元素的第1个元素赋值给 request.user、第2个值赋值给request.auth

    第1个值,一般是用户对象。
    第2个值,一般是token
    
  • 返回None,表示继续调用 后续的认证类 进行认证(上述案例未涉及)

**关于 ”返回None“ **

接下来说说 “返回None” 是咋回事。

在视图类的 authentication_classes 中定义认证类时,传入的是一个列表,支持定义多个认证类。

当出现多个认证类时,drf内部会按照列表的顺序,逐一执行认证类的 authenticate 方法,如果 返回元组 或 抛出异常 则会终止后续认证类的执行;如果返回None,则意味着继续执行后续的认证类。

如果所有的认证类authenticate都返回了None,则默认 request.user="AnonymousUser" 和 request.auth=None,也可以通过修改配置文件来修改默认值。

REST_FRAMEWORK = {
"UNAUTHENTICATED_USER": lambda: None,
"UNAUTHENTICATED_TOKEN": lambda: None,
}

”返回None“的应用场景:

当某个API,已认证 和 未认证 的用户都可以方法时,比如:

  • 已认证用户,访问API返回该用户的视频播放记录列表。
  • 未认证用户,访问API返回最新的的视频列表。

注意:不同于之前的案例,之前案例是:必须认证成功后才能访问,而此案例则是已认证和未认证均可访问。

class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:
            raise AuthenticationFailed({"code": 1001, "error": "认证失败"})
        user_obj = models.UserInfo.objects.filter(token=token).first()
        if not user_obj:
            return None
        return (user_obj, token)

    def authenticate_header(self, request):
        return 'bearer realm="api"'
    

关于多个认证类

一般情况下,编写一个认证类足矣。

当项目中可能存在多种认证方式时,就可以写多个认证类。例如,项目认证支持:

  • 在请求中传递token进行验证。
  • 请求携带cookie进行验证。
  • 请求携带jwt进行验证(后期讲)。
  • 请求携带的加密的数据,需用特定算法解密(一般为app开发的接口都是有加密算法)
  • ...

此时,就可以编写多个认证类,并按照需要应用在相应的视图中,例如:

settings.py

# rest_framework 配置
REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": lambda: None,
    "UNAUTHENTICATED_TOKEN": lambda: None,
}

views.py

class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:
            return None
        user_obj = models.UserInfo.objects.filter(token=token).first()
        if not user_obj:
            return None
        return (user_obj, token)

    def authenticate_header(self, request):
        return 'bearer realm="api"'


class CookieAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("session_id")
        if not token:
            return None
        user_obj = models.UserInfo.objects.filter(token=token).first()
        if not user_obj:
            return None
        return (user_obj, token)

    def authenticate_header(self, request):
        return 'bearer realm="api"'


class OrderView2(APIView):
    versioning_class = URLPathVersioning
    authentication_classes = [MyAuthentication, CookieAuthentication]

    def get(self, request, *args, **kwargs):
        print(request.version)
        return Response({"code": 1000, "data": "你好"})


class PayView(APIView):
    authentication_classes = [CookieAuthentication, ]

    def post(self, request):
        print(request.user)
        return Response({"code": 1000, "data": "支付成功"})

注意:此示例后续在视图中读取的 request.user 的值为None时,表示未认证成功;不为None时,则表示认证成功。

全局配置

在每个视图类的类变量 authentication_classes 中可以定义,其实在配置文件中也可以进行全局配置,例如:

REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": lambda: None,
    "UNAUTHENTICATED_TOKEN": lambda: None,
    "DEFAULT_AUTHENTICATION_CLASSES":["xxxx.xxxx.xx.类名","xxxx.xxxx.xx.类名",]
}

小结

  • 认证组件,在视图执行之前判断用户是否认证成功。
    • 过程:执行所有的认证类中的 authenticate 方法
      • 返回None,继续执行后续的认证类(都未认证成功,request.user 和 auth有默认值,也可以全局配置)
      • 返回2个元素的元组,中断
      • 抛出 AuthenticationFailed,中断
    • 结果:在 request.userrequest.auth 赋值(后续代码可以使用)

权限

认证,根据用户携带的 token/其他 获取当前用户信息。

权限,读取认证中获取的用户信息,判断当前用户是否有权限访问,例如:普通用户、管理员、超级用户,不同用户具有不同的权限。

class UserInfo(models.Model):
    
    role_choices = ((1, "普通用户"), (2, "管理员"), (3, "超级管理员"),)
    role = models.IntegerField(verbose_name="角色", choices=role_choices, default=1)
    
    username = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=64)
    token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)
import uuid
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.permissions import BasePermission
from rest_framework.exceptions import AuthenticationFailed

from app01 import models


class AuthView(APIView):
    """ 用户登录认证 """
    authentication_classes = []
    permission_classes = []

    def post(self, request, *args, **kwargs):
        print(request.data)  # {"username": "wupeiqi", "password": "123"}
        username = request.data.get('username')
        password = request.data.get('password')

        user_object = models.UserInfo.objects.filter(username=username, password=password).first()
        if not user_object:
            return Response({"code": 1000, "data": "用户名或密码错误"})

        token = str(uuid.uuid4())

        user_object.token = token
        user_object.save()

        return Response({"code": 0, "data": {"token": token, "name": username}})


class TokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:
            raise AuthenticationFailed({"code": 1002, "data": "认证失败"})
        user_object = models.UserInfo.objects.filter(token=token).first()
        if not user_object:
            raise AuthenticationFailed({"code": 1002, "data": "认证失败"})
        return user_object, token

    def authenticate_header(self, request):
        return 'Bearer realm="API"'
 class PermissionA(BasePermission):
    message = {"code": 1003, 'data': "无权访问"}

    def has_permission(self, request, view):
        if request.user.role == 2:
            # 表示有权限
            return True
       	# 表示无权限
        return False
	
    # 暂时先这么写
    def has_object_permission(self, request, view, obj):
        return True


class OrderView(APIView):
    authentication_classes = [TokenAuthentication, ]

    permission_classes = [PermissionA,]

    def get(self, request, *args, **kwargs):
        print(request.user)
        return Response({"code": 0, "data": {"user": None, 'list': [1, 2, 3]}})


class PayView(APIView):
    authentication_classes = [TokenAuthentication, ]
    permission_classes = [PermissionA, ]

    def get(self, request, *args, **kwargs):
        print(request.user)
        return Response({"code": 0, "data": "数据..."})

关于多个权限类

当开发过程中需要用户同时具备多个权限(缺一不可)时,可以用多个权限类来实现。

权限组件内部处理机制:按照列表的顺序逐一执行 has_permission 方法,如果返回True,则继续执行后续的权限类;如果返回None或False,则抛出权限异常并停止后续权限类的执行。

# models.py

from django.db import models


class Role(models.Model):
    """ 角色表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class UserInfo(models.Model):
    """ 用户表 """
    username = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=64)
    token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)

    roles = models.ManyToManyField(verbose_name="角色", to="Role")
# urls.py

from django.urls import path, re_path, include
from app01 import views

urlpatterns = [
    path('api/auth/', views.AuthView.as_view()),
    path('api/order/', views.OrderView.as_view()),
]
# views.py

import uuid
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.permissions import BasePermission
from rest_framework.exceptions import AuthenticationFailed

from app01 import models


class AuthView(APIView):
    """ 用户登录认证 """

    def post(self, request, *args, **kwargs):
        print(request.data)  # {"username": "wupeiqi", "password": "123"}
        username = request.data.get('username')
        password = request.data.get('password')

        user_object = models.UserInfo.objects.filter(username=username, password=password).first()
        if not user_object:
            return Response({"code": 1000, "data": "用户名或密码错误"})

        token = str(uuid.uuid4())

        user_object.token = token
        user_object.save()

        return Response({"code": 0, "data": {"token": token, "name": username}})


class TokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:
            raise AuthenticationFailed({"code": 1002, "data": "认证失败"})
        user_object = models.UserInfo.objects.filter(token=token).first()
        if not user_object:
            raise AuthenticationFailed({"code": 1002, "data": "认证失败"})
        return user_object, token

    def authenticate_header(self, request):
        return 'Bearer realm="API"'
    
 class PermissionA(BasePermission):
    message = {"code": 1003, 'data': "无权访问"}

    def has_permission(self, request, view):
        exists = request.user.roles.filter(title="员工").exists()
        if exists:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        return True


class PermissionB(BasePermission):
    message = {"code": 1003, 'data': "无权访问"}

    def has_permission(self, request, view):
        exists = request.user.roles.filter(title="主管").exists()
        if exists:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        return True

class OrderView(APIView):
    authentication_classes = [TokenAuthentication, ]
    permission_classes = [PermissionA, PermissionA]

    def get(self, request, *args, **kwargs):
        return Response({"code": 0, "data": {"user": None, 'list': [1, 2, 3]}})


class PayView(APIView):
    authentication_classes = [TokenAuthentication, ]
    permission_classes = [PermissionA, ]

    def get(self, request, *args, **kwargs):
        return Response({"code": 0, "data": "数据..."})

关于 has_object_permission【欠】

当我们使用drf来编写 视图类时,如果是继承 APIView,则 has_object_permission不会被执行(没用),例如:

from rest_framework.views import APIView

class PayView(APIView):
    authentication_classes = [TokenAuthentication, ]
    permission_classes = [PermissionA, ]

    def get(self, request, *args, **kwargs):
        return Response({"code": 0, "data": "数据..."})

但是,当我们后期学习了 视图类的各种骚操作之后,发现视图也可以继承 GenericAPIView,此时 有可能 会执行 has_object_permission 用于判断是否有权限访问某个特定ID的对象(学完视图后,再细讲)。

调用 self.get_object 方法时,会按照 permission_classes中权限组件的顺序,依次执行他们的 has_object_permission 方法。

self.get_object其实就根据用户传入的 pk,搜索并获取某个对象的过程。

权限类全局配置

# settings.py
REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES":["xxxx.xxxx.xx.类名","xxxx.xxxx.xx.类名",]
}

小结

  • 权限
    • 过程:执行所有的权限类的has_permission方法,只有所有都返回True时,才表示具有权限
    • 结果:有权限则可以执行后续的视图,无权限则直接返回 自定义的错误信息

限流

限流,限制用户访问频率,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷。

  • 对于匿名用户,使用用户IP作为唯一标识。
  • 对于登录用户,使用用户ID或名称作为唯一标识。
缓存={
	用户标识:[12:33,12:32,12:31,12:30,12,]    1小时/5次   12:34   11:34
}
pip3 install django-redis
# settings.py
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "qwe123",
        }
    }
}
# urls.py
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    path('api/order/', views.OrderView.as_view()),
]
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import exceptions
from rest_framework import status
from rest_framework.throttling import SimpleRateThrottle
from django.core.cache import cache as default_cache


class ThrottledException(exceptions.APIException):
    status_code = status.HTTP_429_TOO_MANY_REQUESTS
    default_code = 'throttled'


class MyRateThrottle(SimpleRateThrottle):
    cache = default_cache  # 访问记录存放在django的缓存中(需设置缓存)
    scope = "user"  # 构造缓存中的key
    cache_format = 'throttle_%(scope)s_%(ident)s'

    # 设置访问频率,例如:1分钟允许访问10次
    # 其他:'s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day'
    THROTTLE_RATES = {"user": "10/m"}

    def get_cache_key(self, request, view):
        if request.user:
            ident = request.user.pk  # 用户ID
        else:
            ident = self.get_ident(request)  # 获取请求用户IP(去request中找请求头)

        # throttle_u # throttle_user_11.11.11.11ser_2

        return self.cache_format % {'scope': self.scope, 'ident': ident}

    def throttle_failure(self):
        wait = self.wait()
        detail = {
            "code": 1005,
            "data": "访问频率限制",
            'detail': "需等待{}s才能访问".format(int(wait))
        }
        raise ThrottledException(detail)
 
class OrderView(APIView):
    # 限流使用
    throttle_classes = [MyRateThrottle, ]

    def get(self, request):
        return Response({"code": 0, "data": "数据..."})

    def throttled(self, request, wait):
        detail = {
            "code": 1005,
            "data": "访问频率",
            'detail': "需等待{}s才能访问".format(int(wait))
        }
        raise ThrottledException(detail)

多个限流类

本质,每个限流的类中都有一个 allow_request 方法,此方法内部可以有三种情况:

  • 返回True,表示当前限流类允许访问,继续执行后续的限流类。
  • 返回False,表示当前限流类不允许访问,继续执行后续的限流类。所有的限流类执行完毕后,读取所有不允许的限流,并计算还需等待的时间。
  • 抛出异常,表示当前限流类不允许访问,后续限流类不再执行。

全局配置

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES":["xxx.xxx.xx.限流类", ],
    "DEFAULT_THROTTLE_RATES": {
        "user": "10/m",
        "xx":"100/h"
    }
}

Serializer(*)

drf中为我们提供了Serializer,他主要有两大功能:

  • 对请求数据校验(底层调用Django的Form和ModelForm)
  • 对数据库查询到的对象进行序列化

数据校验

  • 示例1: 基于Serializer

    class UserSerializer(serializers.Serializer):
        username = serializers.CharField(label='姓名', max_length=32, validators=[RegexValidator(r"^[a-zA-Z0-9_-]{4,16}$")])
        age = serializers.IntegerField(label="年龄", min_value=0, max_value=100)
        level = serializers.CharField(label='等级', choices=models.UserInfo.level_choices)
        email = serializers.EmailField(label='邮箱')
        email2 = serializers.EmailField(label='邮箱', validators=[RegexValidator(r"^[a-zA-Z0-9_-]{4,16}@qq.com$")])
    
        def validated_email(self, value):
            """ 利用钩子函数进行校验某个字段"""
            if re.match(r"^[a-zA-Z0-9_-]{4,16}@qq.com$", value):
                return value
            raise serializers.ValidationError("邮箱格式不正确")
    
    
    class UserView2(APIView):
        """ 用户管理 """
    
        def post(self, request):
            serializer = UserSerializer(data=request.data)
            if not serializer.is_valid():
                return Response({"code": 1006, "data": serializer.errors})
            print(serializer.validated_data)
            # 自行进行数据库操作
            return Response({"code": 1000, "data": "添加成功"})
    
  • 示例2:基于ModelSerializer

    # models.py
    
    from django.db import models
    
    
    class Role(models.Model):
        """ 角色表 """
        title = models.CharField(verbose_name="名称", max_length=32)
    
    
    class Department(models.Model):
        """ 部门表 """
        title = models.CharField(verbose_name="名称", max_length=32)
    
    
    class UserInfo(models.Model):
        """ 用户表 """
        level_choices = ((1, "普通会员"), (2, "VIP"), (3, "SVIP"),)
        level = models.IntegerField(verbose_name="级别", choices=level_choices, default=1)
    
        username = models.CharField(verbose_name="用户名", max_length=32)
        password = models.CharField(verbose_name="密码", max_length=64)
        age = models.IntegerField(verbose_name="年龄", default=0)
        email = models.CharField(verbose_name="邮箱", max_length=64)
        token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)
    
        # 外键
        depart = models.ForeignKey(verbose_name="部门", to="Department", on_delete=models.CASCADE)
        
        # 多对多
        roles = models.ManyToManyField(verbose_name="角色", to="Role")
    
    
    # views.py
    
    
    class UserModelSerializer(serializers.ModelSerializer):
       # 额外参数
        email2 = serializers.EmailField(label='邮箱')
        email3 = serializers.EmailField(label='邮箱', validators=[RegexValidator(r"^[a-zA-Z0-9_-]{4,16}@qq.com$")])
    
        class Meta:
            model = models.UserInfo
            fields = ["id", "username", "age", "level", "email", "email2","email3"]
            extra_kwargs = {
                "username": {'min_length': 6, 'max_length': 18},
                "email": {"validatiors": [EmailValidator]}
            }
    
        def validated_email2(self, value):
            """ 利用钩子函数进行校验某个字段"""
            if re.match(r"^[a-zA-Z0-9_-]{4,16}@qq.com$", value):
                return value
            raise serializers.ValidationError("邮箱格式不正确")
    
    
    class UserView3(APIView):
        """ 用户管理 """
    
        def post(self, request):
            serializer = UserModelSerializer(data=request.data)
            if not serializer.is_valid():
                return Response({"code": 1006, "data": serializer.errors})
            # print(serializer.validated_data)
            serializer.validated_data.pop("email2")
            # 提示:save方法会返回新生成的数据对象。
            serializer.save(level=1, password='123', depart_id=1)
            # 自行进行数据库操作
            return Response({"code": 1000, "data": "添加成功"})
    
  • 示例3:基于ModelSerializer(含FK+M2M) 只能用json格式传递

    class UserModelSerializer2(serializers.ModelSerializer):
        email3 = serializers.EmailField(label='邮箱')
        email2 = serializers.EmailField(label='邮箱', validators=[RegexValidator(r"^[a-zA-Z0-9_-]{4,16}@qq.com$")])
    
        class Meta:
            model = models.UserInfo
            fields = ["id", "username", "age", "level", "email", "email2",,"email3","depart","roles"]
            extra_kwargs = {
                "username": {'min_length': 6, 'max_length': 18},
                "email": {"validatiors": [EmailValidator]}
            }
    
        def validated_email3(self, value):
            """ 利用钩子函数进行校验某个字段"""
            if re.match(r"^[a-zA-Z0-9_-]{4,16}@qq.com$", value):
                return value
            raise serializers.ValidationError("邮箱格式不正确")
    
    
    class UserView4(APIView):
        """ 用户管理 """
    
        def post(self, request):
            serializer = UserModelSerializer(data=request.data)
            if not serializer.is_valid():
                return Response({"code": 1006, "data": serializer.errors})
            # print(serializer.validated_data)
            serializer.validated_data.pop("email2")
            serializer.save(level=1, password='123', depart_id=1)
            # 自行进行数据库操作
            return Response({"code": 1000, "data": "添加成功"})
    

    调用传参 只能使用json格式传递

    {
        "username":"tom",
        "age":23,
        "email": 'efow.com',
    	"depart":[1,],
        "roles":[1,2]
    }
    

序列化

通过ORM从数据库获取到的 QuerySet 或 对象 均可以被序列化为 json 格式数据。

# models.py

from django.db import models


class Role(models.Model):
    """ 角色表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class Department(models.Model):
    """ 部门表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class UserInfo(models.Model):
    """ 用户表 """
    level_choices = ((1, "普通会员"), (2, "VIP"), (3, "SVIP"),)
    level = models.IntegerField(verbose_name="级别", choices=level_choices, default=1)

    username = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=64)
    age = models.IntegerField(verbose_name="年龄", default=0)
    email = models.CharField(verbose_name="邮箱", max_length=64, null=True, blank=True)
    token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)

    depart = models.ForeignKey(verbose_name="部门", to="Department", on_delete=models.CASCADE, null=True, blank=True)
    roles = models.ManyToManyField(verbose_name="角色", to="Role")
  • 示例1 序列化基本字段

    class UserModelSerializer3(serializers.ModelSerializer):
        class Meta:
            model = models.UserInfo
            fields = ["id", "username", "age", "level", "email", "email2","depart","roles"]
            
            
    class UserView5(APIView):
        
        # 获取用户列表
        def get(self, request):
            queryset = models.UserInfo.objects.all()
            # many=True  包含多个对象
            serializer = UserModelSerializer3(instance=queryset, many=True)
            # serializer.data  序列化后的数据
            return Response(serializer.data)
    
    # 切记, 如果从数据库获取的不是QuerySet对象,而是单一对象,例如:
    data_object = modes.UserInfo.objects.filter(id=2).first()
    # many=False 单一对象
    ser = UserModelSerializer(instance=data_object,many=False)
    print(ser.data)
    
  • 示例2 自定义字段

    # 序列化 自定义字段
    class UserModelSerializer4(serializers.ModelSerializer):
        level_text = serializers.CharField(source="get_level_display", read_only=True)
        depart = serializers.CharField(source="depart.title", read_only=True)
    	# 自定义序列化钩子函数
        roles = serializers.SerializerMethodField()
        extra = serializers.SerializerMethodField()
    
        class Meta:
            model = models.UserInfo
            fields = ["id", "username", "age", "level", "level_text", "email", "email2", "depart", "roles", "extra"]
    
        def get_roles(self, obj):
            data_list = obj.roles.all()
            return [model_to_dict(item, ['id', 'title']) for item in data_list]
    
        def get_extra(self, obj):
            return 665
    
    
    class UserView6(APIView):
        def get(self, request):
            queryset = models.UserInfo.objects.all()
            serializer = UserModelSerializer4(instance=queryset, many=True)
            return Response(serializer.data)
    
  • 示例3 序列化的嵌套

    # 序列化的嵌套
    class DepartModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Department
            fields = "__all__"
    
    class RoleModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Role
            fields = "__all__"
    
    class UserModelSerializer5(serializers.ModelSerializer):
        depart = DepartModelSerializer() # FK
        roles = RoleModelSerializer(many=True) #m2m
        level_text = serializers.CharField(source="get_level_display", read_only=True)
        class Meta:
            model = models.UserInfo
            fields = ["id", "username", "age", "level", "level_text", "email", "email2", "depart", "roles"]
            
    class UserView7(APIView):
        def get(self, request):
            queryset = models.UserInfo.objects.all()
            serializer = UserModelSerializer5(instance=queryset, many=True)
            return Response(serializer.data)
        
        
    class UserInfo(models.Model):
        """ 用户表 """
        level_choices = ((1, "普通会员"), (2, "VIP"), (3, "SVIP"),)
        level = models.IntegerField(verbose_name="级别", choices=level_choices, default=1)
    
        username = models.CharField(verbose_name="用户名", max_length=32)
        password = models.CharField(verbose_name="密码", max_length=64)
        age = models.IntegerField(verbose_name="年龄", default=0)
        email = models.CharField(verbose_name="邮箱", max_length=64, null=True, blank=True)
        token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)
    
        depart = models.ForeignKey(verbose_name="部门", to="Department", on_delete=models.CASCADE, null=True, blank=True)
        roles = models.ManyToManyField(verbose_name="角色", to="Role")
    
    
    

数据校验&序列化

# urls.py

from django.urls import path, re_path, include
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view()),
]

# views.py

from django.core.validators import EmailValidator
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers

from app01 import models


class DepartModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Department
        fields = ['id', "title"]
        extra_kwargs = {
            "id": {"read_only": False},  # 数据验证
            "title": {"read_only": True}  # 序列化
        }
        
class RoleModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = ['id', "title"]
        extra_kwargs = {
            "id": {"read_only": False},  # 数据验证
            "title": {"read_only": True}  # 序列化
        }


class UserModelSerializer(serializers.ModelSerializer):
    # 只做展示
    level_text = serializers.CharField(source="get_level_display", read_only=True)

    # Serializer嵌套,不是read_only,一定要自定义create和update,自定义新增和更新的逻辑。
    depart = DepartModelSerializer(many=False)
    roles = RoleModelSerializer(many=True)

    extra = serializers.SerializerMethodField(read_only=True)
    # 只写字段 获取对象时不会展示
    email2 = serializers.EmailField(write_only=True)

    # 数据校验:username、email、email2、部门、角色信息
    class Meta:
        model = models.UserInfo
        fields = [
            "username", "age", "email", "level_text", "depart", "roles", "extra", "email2"
        ]
        extra_kwargs = {
            "age": {"read_only": True},
            "email": {"validators": [EmailValidator, ]},
        }

    def get_extra(self, obj):
        return 666

    def validate_username(self, value):
        return value

    # 新增加数据时
    def create(self, validated_data):
        """ 如果有嵌套的Serializer,在进行数据校验时,只有两种选择:
              1. 将嵌套的序列化设置成 read_only
              2. 自定义create和update方法,自定义新建和更新的逻辑
            注意:用户端提交数据的格式。
        """
        
        # 移除并返回指定键的值
        depart_id = validated_data.pop('depart')['id']

        role_id_list = [ele['id'] for ele in validated_data.pop('roles')]

        # 新增用户表
        validated_data['depart_id'] = depart_id
        user_object = models.UserInfo.objects.create(**validated_data)

        # 在用户表和角色表的关联表中添加对应关系
        user_object.roles.add(*role_id_list)

        return user_object
    
class UserView(APIView):
    """ 用户管理 """

    def get(self, request):
        """ 添加用户 """
        queryset = models.UserInfo.objects.all()
        ser = UserModelSerializer(instance=queryset, many=True)
        return Response({"code": 0, 'data': ser.data})

    def post(self, request):
        """ 添加用户 """
        ser = UserModelSerializer(data=request.data)
        if not ser.is_valid():
            return Response({'code': 1006, 'data': ser.errors})
		
        # 删除数据库中没有的字段
        ser.validated_data.pop('email2')

        instance = ser.save(age=18, password="123", depart_id=1)

        # 新增之后的一个对象(内部调用UserModelSerializer进行序列化)
        print(instance)
        # ser = UserModelSerializer(instance=instance, many=False)
        # ser.data

        return Response({'code': 0, 'data': ser.data})

底层源码实现:

序列化的底层源码实现有别于上述其他的组件,序列化器相关类的定义和执行都是在视图中被调用的,所以源码的分析过程可以分为:定义类、序列化、数据校验。

源码1:序列化过程

源码2:数据校验过程

视图

APIView

  • View,django
  • APIView,drf,在请求到来时,新增了:免除csrf、请求封装、版本、认证、权限、限流的功能。
class GenericAPIView(APIView):
    pass # 10功能

class GenericViewSet(xxxx.View-2个功能, GenericAPIView):
    pass # 5功能能

class UserView(GenericViewSet):
    def get(self,request):
        pass

APIView是drf中 “顶层” 的视图类,在他的内部主要实现drf基础的组件的使用,例如:版本、认证、权限、限流等。

# urls.py

from django.urls import path, re_path, include
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view()),
    path('api/users/<int:pk>/', views.UserDetailView.as_view()),
]
# views.py

from rest_framework.views import APIView
from rest_framework.response import Response

class UserView(APIView):
    
    # 认证、权限、限流等
    
    def get(self, request):
		# 业务逻辑:查看列表
        return Response({"code": 0, 'data': "..."})

    def post(self, request):
        # 业务逻辑:新建
        return Response({'code': 0, 'data': "..."})
    
class UserDetailView(APIView):
    
	# 认证、权限、限流等
        
    def get(self, request,pk):
		# 业务逻辑:查看某个数据的详细
        return Response({"code": 0, 'data': "..."})

    def put(self, request,pk):
        # 业务逻辑:全部修改
        return Response({'code': 0, 'data': "..."})
    
    def patch(self, request,pk):
        # 业务逻辑:局部修改
        return Response({'code': 0, 'data': "..."})
    
    def delete(self, request,pk):
        # 业务逻辑:删除
        return Response({'code': 0, 'data': "..."})

GenericAPIView

GenericAPIView 继承APIView,在APIView的基础上又增加了一些功能。例如:get_querysetget_object等。

实际在开发中一般不会直接继承它,他更多的是担任 中间人的角色,为子类提供公共功能。

# urls.py

from django.urls import path, re_path, include
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view()),
    path('api/users/<int:pk>/', views.UserDetailView.as_view()),
]
# views.py

from rest_framework.generics import GenericAPIView
from rest_framework.response import Response

class UserView(GenericAPIView):
    queryset = models.UserInfo.objects.filter(status=True)
    serializer_class = 序列化类
    
    def get(self, request):
        # 获取数据库中的数据,(读取类变量中的queryset字段)
        queryset = self.get_queryset()
        
        # 获取Serializer类并实例化 
        ser = self.get_serializer(intance=queryset,many=True)
        print(ser.data)
        return Response({"code": 0, 'data': "..."})    

注意:最大的意义,将数据库查询、序列化类提取到类变量中,后期再提供公共的get/post/put/delete等方法,让开发者只定义类变量,自动实现增删改查。

GenericViewSet

GenericViewSet类中没有定义任何代码,他就是继承 ViewSetMixinGenericAPIView,也就说他的功能就是将继承的两个类的功能继承到一起。

  • GenericAPIView,将数据库查询、序列化类的定义提取到类变量中,便于后期处理。
  • ViewSetMixin,将 get/post/put/delete 等方法映射到 list、create、retrieve、update、partial_update、destroy方法中,让视图不再需要两个类。
# urls.py

from django.urls import path, re_path, include
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view({"get":"list","post":"create"})),
    path('api/users/<int:pk>/', views.UserView.as_view({"get":"retrieve","put":"update","patch":"partial_update","delete":"destory"})),
]
# views.py

from rest_framework.viewsets import GenericViewSet
from rest_framework.response import Response

    
class UserView(GenericViewSet):
    
	# 认证、权限、限流等
    queryset = models.UserInfo.objects.filter(status=True)
    serializer_class = 序列化类
    
    def list(self, request):
		# 业务逻辑:查看列表
        queryset = self.get_queryset()
        ser = self.get_serializer(intance=queryset,many=True)
        print(ser.data)
        return Response({"code": 0, 'data': "..."})

    def create(self, request):
        # 业务逻辑:新建
        return Response({'code': 0, 'data': "..."})
    
    def retrieve(self, request,pk):
		# 业务逻辑:查看某个数据的详细
        return Response({"code": 0, 'data': "..."})

    def update(self, request,pk):
        # 业务逻辑:全部修改
        return Response({'code': 0, 'data': "..."})
    
    def partial_update(self, request,pk):
        # 业务逻辑:局部修改
        return Response({'code': 0, 'data': "..."})
    
    def destory(self, request,pk):
        # 业务逻辑:删除
        return Response({'code': 0, 'data': "..."})

注意:开发中一般也很少直接去继承他,因为他也属于是 中间人类,在原来 GenericAPIView 基础上又增加了一个映射而已。

五大类

在drf的为我们提供好了5个用于做 增、删、改(含局部修改)、查列表、查单个数据的5个类(需结合 GenericViewSet 使用)。

# urls.py

from django.urls import path, re_path, include
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view({"get":"list","post":"create"})),
    path('api/users/<int:pk>/', views.UserView.as_view({"get":"retrieve","put":"update","patch":"partial_update","delete":"destroy"})),
]
# views.py

from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import (
    ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin,
    DestroyModelMixin, ListModelMixin
)

class UserView(CreateModelMixin,RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin,ListModelMixin,GenericViewSet):
    
	# 认证、权限、限流等
    queryset = models.UserInfo.objects.filter(status=True)
    serializer_class = 序列化类

在这个5个类中已帮我们写好了 listcreateretrieveupdatepartial_updatedestory 方法,我们只需要在根据写 类变量:queryset、serializer_class即可。

示例1:

# urls.py

from django.urls import path
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view({"get": "list"})),
    path('api/users/<int:pk>/', views.UserView.as_view({"get": "retrieve"})),
]
# views.py

from rest_framework import serializers
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
from app01 import models


class UserModelSerializer(serializers.ModelSerializer):
    level_text = serializers.CharField(
        source="get_level_display",
        read_only=True
    )
    extra = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = models.UserInfo
        fields = ["username", "age", "email", "level_text", "extra"]

    def get_extra(self, obj):
        return 666


class UserView(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

示例2:

# urls.py

from django.urls import path
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view({"get": "list", "post": "create"})),
    path('api/users/<int:pk>/', views.UserView.as_view({"get": "retrieve"})),
]
# views.py

from rest_framework import serializers
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
from app01 import models


class UserModelSerializer(serializers.ModelSerializer):
    level_text = serializers.CharField(
        source="get_level_display",
        read_only=True
    )
    extra = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = models.UserInfo
        fields = ["username", "age", "email", "level_text", "extra"]

    def get_extra(self, obj):
        return 666


class UserView(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, GenericViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

    def perform_create(self, serializer):
        """ 序列化:对请求的数据校验成功后,执行保存。"""
        serializer.save(depart_id=1, password="123")

示例3:

# urls.py

from django.urls import path
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view(
        {"get": "list", "post": "create"}
    )),
    path('api/users/<int:pk>/', views.UserView.as_view(
        {"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"}
    )),
]

# views.py

from rest_framework import serializers
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
from app01 import models


class UserModelSerializer(serializers.ModelSerializer):
    level_text = serializers.CharField(
        source="get_level_display",
        read_only=True
    )
    extra = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = models.UserInfo
        fields = ["username", "age", "email", "level_text", "extra"]

    def get_extra(self, obj):
        return 666


class UserView(mixins.ListModelMixin,
               mixins.RetrieveModelMixin,
               mixins.CreateModelMixin,
               mixins.UpdateModelMixin,
               mixins.DestroyModelMixin,
               GenericViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

    def perform_create(self, serializer):
        """ 序列化:对请求的数据校验成功后,执行保存。"""
        serializer.save(depart_id=1, password="123")
	
	def perform_update(self, serializer):
        serializer.save()
        
    def perform_destroy(self, instance):
        instance.delete()

示例4:

# urls.py

from django.urls import path
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view(
        {"get": "list", "post": "create"}
    )),
    path('api/users/<int:pk>/', views.UserView.as_view(
        {"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"}
    )),
]

# views.py
from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from app01 import models


class UserModelSerializer(serializers.ModelSerializer):
    level_text = serializers.CharField(
        source="get_level_display",
        read_only=True
    )
    extra = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = models.UserInfo
        fields = ["username", "age", "email", "level_text", "extra"]

    def get_extra(self, obj):
        return 666


class UserView(ModelViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

    def perform_create(self, serializer):
        """ 序列化:对请求的数据校验成功后,执行保存。"""
        serializer.save(depart_id=1, password="123")

在开发过程中使用 五大类ModelViewSet 是比较常见的,并且如果他们内部的某些功能不够用,还可以进行重新某些方法进行扩展。

问题:drf中提供了这么多视图,以后那个用的比较多?

  • 接口与数据库操作无关,直接继承APIView

  • 接口背后需要对数据库进行操作,一般:ModelViewSetCreateModelMixin、ListModelMixin...

    - 利用钩子自定义功能。
    - 重写某个写方法,实现更加完善的功能。
    
  • 根据自己公司的习惯,自定义 :ModelViewSetCreateModelMixin、ListModelMixin...

条件搜索

如果某个API需要传递一些条件进行搜索,其实就在是URL后面通过GET传参即可,例如:

/api/users?age=19&category=12

在drf中也有相应组件可以支持条件搜索。

9.1 自定义Filter

image-20210825200814769

# urls.py

from django.urls import path
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view(
        {"get": "list", "post": "create"}
    )),
    path('api/users/<int:pk>/', views.UserView.as_view(
        {"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"}
    )),
]
# views.py

from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from rest_framework.filters import BaseFilterBackend
from app01 import models


class UserModelSerializer(serializers.ModelSerializer):
    level_text = serializers.CharField(
        source="get_level_display",
        read_only=True
    )
    extra = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = models.UserInfo
        fields = ["username", "age", "email", "level_text", "extra"]

    def get_extra(self, obj):
        return 666


class Filter1(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        age = request.query_params.get('age')
        if not age:
            return queryset
        return queryset.filter(age=age)


class Filter2(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        user_id = request.query_params.get('id')
        if not user_id:
            return queryset
        return queryset.filter(id__gt=user_id)


class UserView(ModelViewSet):
    filter_backends = [Filter1, Filter2]

    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

    def perform_create(self, serializer):
        """ 序列化:对请求的数据校验成功后,执行保存。"""
        serializer.save(depart_id=1, password="123")

9.2 第三方Filter

在drf开发中有一个常用的第三方过滤器:DjangoFilterBackend。

pip install django-filter

注册app:

INSTALLED_APPS = [
    ...
    'django_filters',
    ...
]

视图配置和应用(示例1):

# views.py
from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from django_filters.rest_framework import DjangoFilterBackend
from app01 import models


class UserModelSerializer(serializers.ModelSerializer):
    level_text = serializers.CharField(
        source="get_level_display",
        read_only=True
    )
    extra = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = models.UserInfo
        fields = ["username", "age", "email", "level_text", "extra"]

    def get_extra(self, obj):
        return 666


class UserView(ModelViewSet):
    filter_backends = [DjangoFilterBackend, ]
    filterset_fields = ["id", "age", "email"]

    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

    def perform_create(self, serializer):
        """ 序列化:对请求的数据校验成功后,执行保存。"""
        serializer.save(depart_id=1, password="123")

视图配置和应用(示例2):

from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from django_filters.rest_framework import DjangoFilterBackend
from django_filters import FilterSet, filters
from app01 import models


class UserModelSerializer(serializers.ModelSerializer):
    level_text = serializers.CharField(
        source="get_level_display",
        read_only=True
    )
    depart_title = serializers.CharField(
        source="depart.title",
        read_only=True
    )
    extra = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = models.UserInfo
        fields = ["id", "username", "age", "email", "level_text", "extra", "depart_title"]

    def get_extra(self, obj):
        return 666


class MyFilterSet(FilterSet):
    depart = filters.CharFilter(field_name="depart__title", lookup_expr="exact")
    min_id = filters.NumberFilter(field_name='id', lookup_expr='gte')

    class Meta:
        model = models.UserInfo
        fields = ["min_id", "depart"]


class UserView(ModelViewSet):
    filter_backends = [DjangoFilterBackend, ]
    filterset_class = MyFilterSet

    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

    def perform_create(self, serializer):
        """ 序列化:对请求的数据校验成功后,执行保存。"""
        serializer.save(depart_id=1, password="123")

视图配置和应用(示例3):

from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from django_filters.rest_framework import DjangoFilterBackend, OrderingFilter
from django_filters import FilterSet, filters
from app01 import models


class UserModelSerializer(serializers.ModelSerializer):
    level_text = serializers.CharField(
        source="get_level_display",
        read_only=True
    )
    depart_title = serializers.CharField(
        source="depart.title",
        read_only=True
    )
    extra = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = models.UserInfo
        fields = ["id", "username", "age", "email", "level_text", "extra", "depart_title"]

    def get_extra(self, obj):
        return 666


class MyFilterSet(FilterSet):
    # /api/users/?min_id=2  -> id>=2
    min_id = filters.NumberFilter(field_name='id', lookup_expr='gte')

    # /api/users/?name=wupeiqi  -> not ( username=wupeiqi )
    name = filters.CharFilter(field_name="username", lookup_expr="exact", exclude=True)

    # /api/users/?depart=xx     -> depart__title like %xx%
    depart = filters.CharFilter(field_name="depart__title", lookup_expr="contains")

    # /api/users/?token=true      -> "token" IS NULL
    # /api/users/?token=false     -> "token" IS NOT NULL
    token = filters.BooleanFilter(field_name="token", lookup_expr="isnull")

    # /api/users/?email=xx     -> email like xx%
    email = filters.CharFilter(field_name="email", lookup_expr="startswith")

    # /api/users/?level=2&level=1   -> "level" = 1 OR "level" = 2(必须的是存在的数据,否则报错-->内部有校验机制)
    # level = filters.AllValuesMultipleFilter(field_name="level", lookup_expr="exact")
    level = filters.MultipleChoiceFilter(field_name="level", lookup_expr="exact", choices=models.UserInfo.level_choices)

    # /api/users/?age=18,20     -> age in [18,20]
    age = filters.BaseInFilter(field_name='age', lookup_expr="in")

    # /api/users/?range_id_max=10&range_id_min=1    -> id BETWEEN 1 AND 10
    range_id = filters.NumericRangeFilter(field_name='id', lookup_expr='range')

    # /api/users/?ordering=id     -> order by id asc
    # /api/users/?ordering=-id     -> order by id desc
    # /api/users/?ordering=age     -> order by age asc
    # /api/users/?ordering=-age     -> order by age desc
    ordering = filters.OrderingFilter(fields=["id", "age"])

    # /api/users/?size=1     -> limit 1(自定义搜索)
    size = filters.CharFilter(method='filter_size', distinct=False, required=False)
    
    class Meta:
        model = models.UserInfo
        fields = ["id", "min_id", "name", "depart", "email", "level", "age", 'range_id', "size", "ordering"]

    def filter_size(self, queryset, name, value):
        int_value = int(value)
        return queryset[0:int_value]


class UserView(ModelViewSet):
    filter_backends = [DjangoFilterBackend, ]
    filterset_class = MyFilterSet

    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

    def perform_create(self, serializer):
        """ 序列化:对请求的数据校验成功后,执行保存。"""
        serializer.save(depart_id=1, password="123")

lookup_expr有很多常见选择:

'exact': _(''),
'iexact': _(''),

'contains': _('contains'),
'icontains': _('contains'),
'startswith': _('starts with'),
'istartswith': _('starts with'),
'endswith': _('ends with'),  
'iendswith': _('ends with'),
    
'gt': _('is greater than'),
'gte': _('is greater than or equal to'),
'lt': _('is less than'),
'lte': _('is less than or equal to'),

'in': _('is in'),
'range': _('is in range'),
'isnull': _(''),
    
'regex': _('matches regex'),
'iregex': _('matches regex'),

全局配置和应用:

# settings.py 全局配置

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend',]
}

9.3 内置Filter

drf源码中内置了2个filter,分别是:

  • OrderingFilter,支持排序。

    from rest_framework import serializers
    from rest_framework.viewsets import ModelViewSet
    from app01 import models
    from rest_framework.filters import OrderingFilter
    
    
    class UserModelSerializer(serializers.ModelSerializer):
        level_text = serializers.CharField(
            source="get_level_display",
            read_only=True
        )
        depart_title = serializers.CharField(
            source="depart.title",
            read_only=True
        )
        extra = serializers.SerializerMethodField(read_only=True)
    
        class Meta:
            model = models.UserInfo
            fields = ["id", "username", "age", "email", "level_text", "extra", "depart_title"]
    
        def get_extra(self, obj):
            return 666
    
    
    class UserView(ModelViewSet):
        filter_backends = [OrderingFilter, ]
        # ?order=id
        # ?order=-id
        # ?order=age
        ordering_fields = ["id", "age"]
    
        queryset = models.UserInfo.objects.all()
        serializer_class = UserModelSerializer
    
        def perform_create(self, serializer):
            """ 序列化:对请求的数据校验成功后,执行保存。"""
            serializer.save(depart_id=1, password="123")
    
  • SearchFilter,支持模糊搜索。

    from rest_framework import serializers
    from rest_framework.viewsets import ModelViewSet
    from app01 import models
    from rest_framework.filters import SearchFilter
    
    
    class UserModelSerializer(serializers.ModelSerializer):
        level_text = serializers.CharField(
            source="get_level_display",
            read_only=True
        )
        depart_title = serializers.CharField(
            source="depart.title",
            read_only=True
        )
        extra = serializers.SerializerMethodField(read_only=True)
    
        class Meta:
            model = models.UserInfo
            fields = ["id", "username", "age", "email", "level_text", "extra", "depart_title"]
    
        def get_extra(self, obj):
            return 666
    
    
    class UserView(ModelViewSet):
        # ?search=武沛%齐
        filter_backends = [SearchFilter, ]
        search_fields = ["id", "username", "age"]
    
        queryset = models.UserInfo.objects.all()
        serializer_class = UserModelSerializer
    
        def perform_create(self, serializer):
            """ 序列化:对请求的数据校验成功后,执行保存。"""
            serializer.save(depart_id=1, password="123")
    
    
    "app01_userinfo"."id" LIKE %武沛齐% ESCAPE '\' 
    OR 
    "app01_userinfo"."username" LIKE %武沛齐% ESCAPE '\' 
    OR 
    "app01_userinfo"."age" LIKE %武沛齐% ESCAPE '\'
    

分页

在查看数据列表的API中,如果 数据量 比较大,肯定不能把所有的数据都展示给用户,而需要通过分页展示。

在drf中为我们提供了一些分页先关类:

BasePagination,分页基类
PageNumberPagination(BasePagination)	支持 /accounts/?page=4&page_size=100 格式的分页
LimitOffsetPagination(BasePagination)	支持 ?offset=100&limit=10 格式的分页
CursorPagination(BasePagination)		支持 上一下 & 下一页 格式的分页(不常用)

APIView视图

如果编写视图是直接继承APIView,那么在使用分页时,就必须自己手动 实例化 和 调用相关方法。

PageNumberPagination

LimitOffsetPagination

CursorPagination

GenericAPIView派生类

如果是使用 ListModelMixinModelViewSet ,则只需要配置相关类即可,内部会自动执行相关分页的方法。

PageNumberPagination

LimitOffsetPagination

CursorPagination

路由

在之前进行drf开发时,对于路由我们一般进行两种配置:

  • 视图继承APIView

    from django.urls import path
    from app01 import views
    
    urlpatterns = [
        path('api/users/', views.UserView.as_view()),
    ]
    
  • 视图继承 ViewSetMixin(GenericViewSet、ModelViewSet)

    from django.urls import path, re_path, include
    from app01 import views
    
    urlpatterns = [
        path('api/users/', views.UserView.as_view({"get":"list","post":"create"})),
        path('api/users/<int:pk>/', views.UserView.as_view({"get":"retrieve","put":"update","patch":"partial_update","delete":"destory"})),
    ]
    

    对于这种形式的路由,drf中提供了更简便的方式:

    from rest_framework import routers
    from app01 import views
    
    router = routers.SimpleRouter()
    router.register(r'api/users', views.UserView)
    
    urlpatterns = [
        # 其他URL
        # path('xxxx/', xxxx.as_view()),
    ]
    
    urlpatterns += router.urls
    

    也可以利用include,给URL加前缀:

    from django.urls import path, include
    from rest_framework import routers
    from app01 import views
    
    router = routers.SimpleRouter()
    router.register(r'users', views.UserView)
    
    urlpatterns = [
        path('api/', include((router.urls, 'app_name'), namespace='instance_name')),
        # 其他URL
        # path('forgot-password/', ForgotPasswordFormView.as_view()),
    ]
    

解析器

之前使用 request.data 获取请求体中的数据。

这个 reqeust.data 的数据怎么来的呢?其实在drf内部是由解析器,根据请求者传入的数据格式 + 请求头来进行处理。

JSONParser

FormParser

MultiPartParser

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="http://127.0.0.1:8000/test/" method="post" enctype="multipart/form-data">
    <input type="text" name="user" />
    <input type="file" name="img">

    <input type="submit" value="提交">

</form>
</body>
</html>

FileUploadParser

解析器可以设置多个,默认解析器:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, JSONParser, FormParser


class UserView(APIView):
    parser_classes = [MultiPartParser, JSONParser, FormParser]
    def post(self, request):
        print(request.content_type)
        print(request.data)

        return Response("...")

写在最后

至此,drf相关的知识点就全部讲完了。

  • 你的感受:单独听的时候都明白,现在感觉自己已经忘记了 并且 还不知道怎么组合起来一起使用。

  • 切记:现在千万不要重复回去再看视频,接着往下看学习《drf实战案例》,结合实战功能 & drf各个组件的小项目。

posted @ 2024-11-30 11:12  小郑[努力版]  阅读(6)  评论(0编辑  收藏  举报