django restframework(简称drf)本质上其实就是一个别人编写好的app,里面集成了很多编写restful API的功能功能。

 

其目录中有很多我们以前写django程序见到过的,因为它就是一个别人写好了的app,我们拿来用。

因此 每次新建项目时要记得在settings中注册app。

同时在settings中也要加上如下字典,以后drf相关的全局配置都会在该字典中添加。

REST_FRAMEWORK = {
   
}

一、基本流程分析*

drf为我们封装了许许多多的功能,我们要了解其大概才能在日后的使用中更加灵活。

当路由匹配到如上user的时候,执行其后面的views.UserView中的as_view方法。我们可以看到在我们自己写的类UserView中并没有as_view方法,但它继承于APIView,我们可以在1中看到,APIView的as_view继承了父类View(也就是CBV模式下最先学习的基类)的全部功能,此时我们获得了原本的view函数。在1的retrun中我们看到了csrf_exempt(view),这一步的过程其实就像我们以前去掉csrf_token认证引入的装饰器一样,用于去除csrf_token认证。我们以后选择用jwt进行认证。

期间2中还执行了dispatch方法,由于我们自己写的UserView中没有重写dispatch,它向上找到APIView的dispatch。我们与View基类的dispatch做对比,看到它除了与基类同样运用反射获取请求方式外,其前后还分别对request和response进行了封装。详细内容后面会详细讲。

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):
        return Response({"code": 1000, "data": "xxx"})

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

注:此时你看到的request,不再是曾经django中的request,而是又被封装了一层,内部包含:django的request、认证、解析器等。

在源码中,大概是这样:有一个rest_framework.request.Request类,我们最后获得的request=Request(request, 认证,分页..)

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)

我们从源码中可以看到,想要从请求中获取数据,有两种方法:

第一种:

request._request.method
request._request.GET
request._request.POST
request._request.body

第二种:

# 直接读取新request对象中的值,一般此处会对原始的数据进行一些处理,方便开发者在视图中使用。
request.query_params  # 内部本质上就是 request._request.GET

# 内部读取请求体中的数据,并进行处理,例如:请求者发来JSON格式,他的内部会对json字符串进行反序列化。
request.data 
        
# 通过 __getattr__ 去访问 request._request 中的值
request.method

request.method这样的可以通过反射获取,因此不需要改变。这里我们用request.query_params 替换了request.GET,request.data替换了request.POST.

值得一提的是,request.data会对收到的json格式自动进行反序列化。此外无论是content-type: url-form-encoded还是content-type: application/json,该方法都可以获取到内容。

三、版本管理

在restful规范中要去,后端的API中需要体现版本。

drf框架中支持5种版本的设置,这里我们只介绍常用的两种。

1. URL的GET参数传递版本

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


class UserView(APIView):
    versioning_class = QueryParameterVersioning

    def get(self, request, *args, **kwargs):
        print(request.version)
        return Response({"code": 999, "data": "成功了"})

此时我们访问:127.0.0.1/api/user/  输出结果为None

              访问:127.0.0.1/api/user?version=v1 输出结果为v1

              访问:127.0.0.1/api/user?xx=oo 输出结果为None

即默认情况下通过get请求携带参数只认version这一字符串,等号后面是什么结果输出就是什么。

简化:

settings中配置

REST_FRAMEWORK = {
    "VERSION_PARAM": "v",  # 改名字 
    "DEFAULT_VERSION": "v1",  # 默认版本 不传的话就是v1
    "ALLOWED_VERSIONS": ["v1", "v2", "v3"],  # 不在里面返回404 Invalid version in query parameter.

    # 全局配置不用在视图类中写versioning_class = QueryParameterVersioning了(有优先级)
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning"
}

此时可以不在视图中声明versioning_class = QueryParameterVersioning了。但由于又是全局配置,版本还好,后面一些权限类的不想要可以在视图类中重新声明versioning_class为空就好了。

按照上面的配置,我们访问127.0.0.1/api/user?v=xx, xx不在允许的版本中,直接返回404不执行函数。访问127.0.0.1/api/user?v=v2,print(request.version)输出v2。

2. URL路径传递

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


class UserView(APIView):
    versioning_class = URLPathVersioning

    def get(self, request, *args, **kwargs):
        print(request.version)
        return Response({"code": 999, "data": "成功了"})

此时路由中要留个位置写版本:

path('api/<str:v>/user/', views.UserView.as_view()),

注:我们上面改过settings里的名字了,从默认的version改为了v,因此这里命名也必须是v。由于该种方法写在了视图类内部,比settings配置的QueryParameterVersioning优先级更高,因此我们可以访问127.0.0.1/api/v1/user/、127.0.0.1/api/v2/user/、127.0.0.1/api/v2/user/,由于该路由参数必须填写,因此不采取什么错的情况下,settings中的默认版本与该方法无关。 

REST_FRAMEWORK = {
    "VERSION_PARAM": "v",  # 改名字 
    "ALLOWED_VERSIONS": ["v1", "v2", "v3"],  # 不在里面返回404 Invalid version in query parameter.
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning"
}

一种解决不得不填写的办法:

urlpatterns = [
    # 两个路由映射同一个类 
    path('api/user/', views.UserView.as_view()),
    path('api/<str:v>/user/', views.UserView.as_view()),
]

四、认证

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

  • 无需认证,就可以访问并获取数据。

  • 需认证,用户需先登录,后续发送请求需携带登录时发放的凭证(后期会讲jwt)

建表如下:

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)

我们设定:从url中获取token,如果没有token或者token不正确或token对应的用户不存在,令其抛出异常。否则通过认证。

代码如下:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01.models import UserInfo


class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get('token')
        if not token:
            raise AuthenticationFailed({"code": 1001, "error": "认证失败"})
        user_obj = 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):
    authentication_classes = [MyAuthentication, ]
    def get(self, request, *args, **kwargs):
        return Response({"code": 999, "data": "成功了"})

此时我们不带token访问(后面后会通过postman进行调试)

当我们带上正确token:

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

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

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

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

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

我们也看到了authentication_classes列表里面可以添加多个认证类,比如

authentication_classes = [MyAuthenticationA, MyAuthenticationB, ]

三种情况

  • 认证类A正常结束,返回一个用户对象和token:return user_obj, token  结果:在 request.userrequest.auth 赋值,后续代码可以使用,此时认证结束不再执行认证类B。
  • 认证类A抛出异常,此时认证结束不再执行认证类B。
  • 认证类A返回一个None:return None, 表示继续调用后续的认证类。

如果所有的认证类`authenticate`都返回了None,则默认 request.user= AnonymousUser() (匿名用户对象)  和 request.auth=None,也可以通过修改配置文件来修改默认值。
REST_FRAMEWORK = {
      "UNAUTHENTICATED_USER": lambda: None,
      "UNAUTHENTICATED_TOKEN": lambda: None,
}

”返回None“的应用场景:

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

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

  • 未认证用户,访问API返回最新的的视频列表。

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

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

    def get(self, request, *args, **kwargs):
        if not request.user: # 这样写前提是settings里修改了名字 匿名改为None
            return Response({"code": "999", "data": "未认证看到的"})
        return Response({"code": "999", "data": "认证了看到的"})

关于多个认证类

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

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

  • 在请求中传递token进行验证。

  • 请求携带cookie进行验证。

  • 请求携带jwt进行验证(后期讲)。

  • 请求携带的加密的数据,需用特定算法解密(一般为app开发的接口都是有加密算法)

  • ...

全局配置:

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

对于一些不需要认证的url:,视图类中声明:authentication_classes = []

五、权限

认证:根据用户携带的 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)

 则:

class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get('token')
        if not token:
            raise AuthenticationFailed({"code": 1001, "error": "认证失败"})
        user_obj = 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 PermissionA(BasePermission):
    message = {"code": 1003, "data": "无权访问"}  # 无权访问的话返回的就是它了

    def has_permission(self, request, view):
        exists = request.user.role
        if exists == 2:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        pass


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

    def get(self, request, *args, **kwargs):
        return Response({"code": 999, "data": "成功了"})

这里我们简写,执行权限前必先执行了认证。我们这里让认证最后通过时必须是一个完整的用户对象,这样我们在Permission中写的代码request.user.role就不会报错(匿名用户或者None是.不出来role的)。

我们访问127.0.0.1:8000/api/order?token=df6e0d2f-4686-4544-b85b-45bde26f0c8d

 修改身份再次访问:

关于多个权限类

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

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

关于 has_object_permission【欠】

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

全局配置

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

六、限流

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

  • 对于匿名用户,使用用户IP作为唯一标识。

  • 对于登录用户,使用用户ID或名称作为唯一标识。

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": "",
        }
    }
}

代码如下:

import uuid
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework import exceptions
from app01.models import UserInfo
from rest_framework.permissions import BasePermission
from rest_framework import status
from rest_framework.throttling import SimpleRateThrottle
from django.core.cache import cache as default_cache



class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get('token')
        if not token:
            raise AuthenticationFailed({"code": 1001, "error": "认证失败"})
        user_obj = 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 PermissionA(BasePermission):
    message = {"code": 1003, "data": "无权访问"}  # 无权访问的话返回的就是它了

    def has_permission(self, request, view):
        exists = request.user.role
        if exists == 2:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        pass


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_user_2 # 未登录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):
    authentication_classes = [MyAuthentication, ]
    permission_classes = [PermissionA, ]
    throttle_classes = [MyRateThrottle, ]

    def get(self, request, *args, **kwargs):
        return Response({"code": 999, "data": "成功了"})

多个限流类

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

  • 返回True,表示当前限流类允许访问,继续执行后续的限流类。

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

  • 抛出异常,表示当前限流类不允许访问,后续限流类不再执行。

全局配置

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

前六章小结

  • 请求的封装

  • 版本的处理

    • 过程:选择版本处理类,获取用户传入的版本信息

    • 结果:在 request.version = 版本request.versioning_scheme=版本处理类的对象

  • 认证组件,在视图执行之前判断用户是否认证成功。

    • 过程:执行所有的认证类中的 authenticate 方法

      • 返回None,继续执行后续的认证类(都未认证成功,request.user 和 auth有默认值,也可以全局配置)

      • 返回2个元素的元组,中断

      • 抛出 AuthenticationFailed,中断

    • 结果:在 request.userrequest.auth 赋值(后续代码可以使用)

  • 权限

    • 过程:执行所有的权限类的has_permission方法,只有所有都返回True时,才表示具有权限

    • 结果:有权限则可以执行后续的视图,无权限则直接返回 自定义的错误信息

  • 限流类

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

  •  返回True,表示当前限流类允许访问,继续执行后续的限流类。

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

  • 抛出异常,表示当前限流类不允许访问,后续限流类不再执行。

REST_FRAMEWORK = {
    "VERSION_PARAM": "v", 
    "DEFAULT_VERSION": "v1", 
    "ALLOWED_VERSIONS": ["v1", "v2", "v3"], 
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning",

    # 认证
    "UNAUTHENTICATED_USER": lambda: None,
    "UNAUTHENTICATED_TOKEN": lambda: None,
    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.auth.TokenAuthentication", ],

    # 权限
    "DEFAULT_PERMISSION_CLASSES": ["app01.permission.PermissionA", ],

    # 限流 一般不会统一加 一半只在一些特殊的url加
    "DEFAULT_THROTTLE_CLASSES": ["app01.limits.MyRateThrottle", ],
    "DEFAULT_THROTTLE_RATES": { 
        "user": "5/m", 
        "xx": "100/h"
    },
}

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "",
        }
    }
}

七、 Serializer*

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

  • 对请求数据校验(底层调用Django的Form和ModelForm)

  • 对数据库查询到的对象进行序列化

我们新建一个项目用来学习serializer(此时没有认证权限什么的了)。

建表如下:

# 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")

1. 校验数据

a. 继承Serializer

  

import re
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from app01 import models
from rest_framework import exceptions
from django.core.validators import EmailValidator


class RegexValidator:
    def __init__(self, base):
        self.base = str(base)

    def __call__(self, value):
        match_object = re.match(self.base, value)
        if not match_object:
            raise serializers.ValidationError("格式错误")


class UserSerializer(serializers.Serializer):
    username = serializers.CharField(label="姓名", min_length=6, max_length=32)
    age = serializers.IntegerField(label="年龄", min_value=0, max_value=200)

    level = serializers.ChoiceField(label="级别", choices=models.UserInfo.level_choices)

    email = serializers.CharField(label="邮箱", validators=[EmailValidator, ])
    email1 = serializers.EmailField(label="邮箱1", min_length=6, max_length=32)
    email2 = serializers.CharField(label="邮箱2", validators=[RegexValidator(r"^\w+@\w+\.\w+$"), ])
    email3 = serializers.CharField(label="邮箱3", min_length=6, max_length=32)

    def validate_email3(self, value):
        if re.match(r"^\w+@\w+\.\w+$", value):
            return value
        raise exceptions.ValidationError("邮箱格式错误")


class UserView(APIView):
    def post(self, request, *args, **kwargs):
        ser = UserSerializer(data=request.data, )
        print(ser)
        print(ser.is_valid())
        if not ser.is_valid():
            return Response({"code": 1006, "data": ser.errors})
        print(ser.validated_data)
        return Response({'code': 1000, 'data': '创建成功'})

'''
UserSerializer(data=<QueryDict: {'username': ['suichu'], 'password': ['xxx'], 'age': ['18'], 'level': ['3'], 'email': ['xxx@qq.com'], 'email1': ['xxx@qq.com'], 'email2': ['xxx@qq.com'], 'email3': ['xxx@qq.com'], '': ['']}>):
    username = CharField(label='姓名', max_length=32, min_length=6)
    age = IntegerField(label='年龄', max_value=200, min_value=0)
    level = ChoiceField(choices=((1, '普通会员'), (2, 'VIP'), (3, 'SVIP')), label='级别')
    email = CharField(label='邮箱', validators=[<class 'django.core.validators.EmailValidator'>])
    email1 = EmailField(label='邮箱1', max_length=32, min_length=6)
    email2 = CharField(label='邮箱2', validators=[<app01.views.RegexValidator object>])
    email3 = CharField(label='邮箱3', max_length=32, min_length=6)
True
OrderedDict([('username', 'suichu'), ('age', 18), ('level', 3), ('email', 'xxx@qq.com'), ('email1', 'xxx@qq.com'), ('email2', 'xxx@qq.com'), ('email3', 'xxx@qq.com')])
'''

注:UserSerializer中有的字段,默认传参时都必须要有。如果传过来了一些没有的键值对也没关系,其内部只会对有的进行匹配对比认证。而ser.validated_data返回的仅是匹配字段的数据

将settings里语言修改则可以看到中文的回复

LANGUAGE_CODE = 'zh-hans'

b. 继承ModelSerializer

import re
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from app01 import models
from rest_framework import exceptions
from django.core.validators import EmailValidator


class RegexValidator:
    def __init__(self, base):
        self.base = str(base)

    def __call__(self, value):
        match_object = re.match(self.base, value)
        if not match_object:
            raise serializers.ValidationError("格式错误")


class UserSerializer(serializers.ModelSerializer):
    # 字段补充
    email2 = serializers.CharField(label="邮箱2", validators=[RegexValidator(r"^\w+@\w+\.\w+$"), ])
    email3 = serializers.CharField(label="邮箱3", min_length=6, max_length=32)

    class Meta:
        # 表声明
        model = models.UserInfo
        # 表中字段声明
        fields = ['username', 'age', 'email', 'email2', 'email3', 'depart', 'roles']
        # 额外扩充的要求
        extra_kwargs = {
            'username': {"min_length": 6, "max_length": 32},
            "emali": {"validators": [EmailValidator]}
        }

        def validate_email3(self, value):
            if re.match(r"^\w+@\w+\.\w+$", value):
                return value
            raise exceptions.ValidationError("邮箱格式错误")


class UserView(APIView):
    def post(self, request, *args, **kwargs):
        ser = UserSerializer(data=request.data, )
        if not ser.is_valid():
            return Response({"code": 1006, "data": ser.errors})

        ser.validated_data.pop('email2')
        ser.validated_data.pop('email3')

        #  save有返回值的 是新增的那个对象
        ser.save(level=1, password='123')

        return Response({'code': 1000, 'data': '创建成功'})

可以看到,以username为例它需要长度在6-32(models里只限定了最大长度)。以age为例我虽然没有传age但是数据库里age有默认值0虽然写在fields里了但可以不传。以password为例我虽然传了password='xxx' 但最后保存的还是ser.save()里的password=123

2. 序列化

a. 序列化基本字段

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

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        # 表声明
        model = models.UserInfo
        # 表中字段声明
        fields = ['username', 'age', 'email']


class UserView(APIView):
    def get(self, request, *args, **kwargs):
        user_objs = models.UserInfo.objects.all()
        ser = UserSerializer(instance=user_objs, many=True)
        print(ser.data) 
        return Response({'code': 1000, 'data': ser.data})

# [OrderedDict([('username', 'suichu'), ('age', 0), ('email', 'xxx@qq.com')])]

如上图,ser.data返回OrderedDict,但通过Response后传给前台的就是json字符串。

# 切记, 如果从数据库获取的不是QuerySet对象,而是单一对象,例如:
data_object = modes.UserInfo.objects.filter(id=2).first()
ser = UserModelSerializer(instance=data_object,many=False)
print(ser.data)

b. 自定义字段

为方便展示我在数据库里又加了一条用户。

class UserSerializer(serializers.ModelSerializer):
    # choicefield中文显示
    level_text = serializers.CharField(source="get_level_display")
    # 一对多显示其title
    depart = serializers.CharField(source="depart.title")
    # 多对多 下面自定义
    roles = serializers.SerializerMethodField()
    # 额外新增字段  下面自定义
    extra = serializers.SerializerMethodField()

    class Meta:
        model = models.UserInfo
        fields = ['username', 'age', 'email', 'depart', 'roles', 'level_text', '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 666


class UserView(APIView):
    def get(self, request, *args, **kwargs):
        user_objs = models.UserInfo.objects.all()
        ser = UserSerializer(instance=user_objs, many=True)
        print(ser.data)
        return Response({'code': 1000, 'data': ser.data})

c. 序列化字段嵌套

class DepartModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Department
        fields = "__all__"


class RoleModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = "__all__"


class UserSerializer(serializers.ModelSerializer):
    level_text = serializers.CharField(source="get_level_display")
    depart = DepartModelSerializer()
    roles = RoleModelSerializer(many=True)

    class Meta:
        model = models.UserInfo
        fields = ['username', 'age', 'email', 'depart', 'roles', 'level_text']


class UserView(APIView):
    def get(self, request, *args, **kwargs):
        user_objs = models.UserInfo.objects.all()
        ser = UserSerializer(instance=user_objs, many=True)
        print(ser.data)
        return Response({'code': 1000, 'data': ser.data})

3. 数据校验&序列化

上述示例均属于单一功能(要么校验,要么序列化),其实当我们编写一个序列化类既可以做数据校验,也可以做序列化,例如:

class DepartModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Department
        fields = ['id', "title"]
        extra_kwargs = {
            "id": {"read_only": False},  # 数据验证 即post时必须传递  在get时显示
            "title": {"read_only": True}  # 序列化	即在get时显示 post时不用传递
        }


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进行序列化)
        # 相当于 ser = UserModelSerializer(instance=instance, many=False)
        # ser.data
        print(instance)
 
        return Response({'code': 0, 'data': ser.data})

注:

  • 传入数据时,必须是fields中全部字段(read_only、detault除外) 
  • read_only只会在返回的时候给你展现出来(对应字段的内容/新增字段的内容),哪怕是你传递的时候带着read_only的数据也没什么用,它只匹配非read_only的字段。
  • 对于返回时,返回的内容是校验器中所有非write_only的字段 ,而不是传进去的字段。

如果校验器中传参 partial = True,那么允许局部校验。

ser = UserModelSerializer(data=request.data,partial = True)

八、视图*

1. APIView

APIView,是drf中 “顶层” 的视图类,在他的内部主要实现drf基础的组件的使用 ,在请求到来时,新增了:免除csrf、请求封装、版本、认证、权限、限流的功能。

# 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': "..."})

drf中除了APIView,还有两个常用的View:GenericAPIView 和 GenericViewSet 关系如下:

class GenericAPIView(APIView):
    pass # 10功能

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

class UserView(GenericViewSet): # 前面的功能都有了
    def get(self,request):
        pass

2. GenericAPIView

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

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

from rest_framework.generics import GenericAPIView

class UserView(GenericAPIView):
    queryset = models.UserInfo.objects.filter(status=True)# 没加.all()的话会内部自动加上
    serializer_class = UserSerializer

    def get(self, request):
        # 获取数据库中的数据(其实就是上面写的类变量queryset)
        queryset = self.get_queryset()
        # 获取Serializer并实例化(其实就是上面写的类变量UserSerializer)
        ser = self.get_serializer(intance=queryset, many=True)
        print(ser.data)
        return Response({"code": 0, 'data': "..."})

可见GerericaAPIView内部定义了一些方法,我们将数据库查询、序列化类提取到类变量中,后期其它类再提供公共的get/post/put/delete等方法,让开发者只定义类变量,自动实现增删改查。

假如drf中有个XXXX类继承了GerericaAPIView实现了以下功能,那我们是不是以后不用写这种类似的代码,直接继承XXXX就可以了?

class XXXX(GenericAPIView):
    def get(self, request):
        queryset = self.get_queryset()
        ser = self.get_serializer(instance=queryset, many=True)
        return Response({'code': 0, "data": ser.data})

实际上确实是这样的:

当然我们也看到了还有GerericaAPIView里面paginator、filter_queryset方法,后面会讲。

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

3. GenericViewSet

平时我们写一些接口方法:

# urls.py
urlpatterns = [

    path('user/', views.UserView.as_view()),
    path('user/<int:pk>/', views.UserView.as_view()),
]

# views.py
class UserView(APIView):

    def get(self, request, pk=None):
        pass

    def post(self, request):
        pass

    def delete(self, request, pk):
        pass

    def put(self, request, pk):
        pass

    def patch(self, request, pk):
        pass
'''
接口:/users/			方法:GET     =>   用户列表
接口:/users/			方法:POST    =>   添加用户
接口:/users/(\d+)/	方法:GET     =>   获取单条数据
接口:/users/(\d+)/	方法:DELETE  =>   删除数据
接口:/users/(\d+)/	方法:PUT     =>   更新数据
接口:/users/(\d+)/	方法:PATCH   =>   局部更新
'''

而继承GenericViewSet后,可以这样写:

# urls.py
urlpatterns = [

    path('user/', views.UserView.as_view({
        'get': "list",
        'post': "create"
    })),
    path('user/<int:pk>/', views.UserView.as_view({
        'get': "retrieve",
        'put': "update",
        'patch': "partial_uptade",
        'delete': "destroy",
    })),
]

# views.py

class DepartModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Department
        fields = ['id', "title"]
        extra_kwargs = {
            "id": {"read_only": False},  # 数据验证 即post时必须传递  在get时显示
            "title": {"read_only": True}  # 序列化	即在get时显示 post时不用传递
        }

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(GenericViewSet):
    # 认证、权限、限流等
    queryset = models.UserInfo.objects
    serializer_class = UserModelSerializer

    def list(self, request):
        # 业务逻辑:查看列表
        queryset = self.get_queryset()
        ser = self.get_serializer(instance=queryset, many=True)
        print(ser.data)
        return Response({"code": 0, 'data': ser.data})

    def create(self, request):
        # 业务逻辑:新建
        ser = self.get_serializer(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)
        print(instance)
        return Response({'code': 0, 'data': ser.data}) # ser.data返回新增的对象

    def retrieve(self, request, pk):
        # 业务逻辑:查看某个数据的详细
        user_obj = self.get_queryset().filter(id=pk).first()
        ser = self.get_serializer(instance=user_obj, many=False)
        return Response({"code": 0, 'data': ser.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': "..."})

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

  • GenericAPIView,将数据库查询、序列化类的定义提取到类变量中,便于后期处理。

  • ViewSetMixin,将 get/post/put/delete 等方法映射到 list、create、retrieve、update、partial_update、destroy方法中,让视图不再需要两个类。

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

3. 五大类

在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
)

class UserView(CreateModelMixin,RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin,ListModelMixin,GenericViewSet):
    
	# 认证、权限、限流等

    queryset = models.UserInfo.objects.filter(status=True)
    serializer_class = 序列化类

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

  • 对于获取多个数据,走ListModelMixin的list方法

        get: 127.0.0.1/api/user/ 

class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)
  • 对于获取单个数据,走RetrieveModelMixin中的retrieve方法

        get: 127.0.0.1/api/user/1/ 

        注:self.get_object()来自于 GenericAPIView内部定义,用于根据pk获取单个用户对象。没有则告知找不到。

class RetrieveModelMixin:
    """
    Retrieve a model instance.
    """
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)
  • 对于新增数据,走CreateModelMixin中的create方法

        post: 127.0.0.1/api/user/  

class CreateModelMixin:
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

注:

        如果此步骤中Serializer中新增了额外数据库中没有的字段,或者加了但不是read_only,由于自带的create中没有ser.pop("xxx"),在serializer.save()时会发生报错。可以选择自己重写create、perform_create。当然如果用不同的serializer,可以通过调用关系自己用自己的。

class UserView(CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, ListModelMixin,
               GenericViewSet):

    # 认证、权限、限流等

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

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        from rest_framework import status
        serializer.validated_data.pop('email2')

        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)

        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
   def perform_create(self, serializer):
       serializer.save(password='xxx')

       # 也可以重写GenericAPIView中的get_serializer_class区分用哪个校验器

       # 记得把read_only和write_only写清楚 其实公用一个校验器就行

   def get_serializer_class(self):
       if self.request.method == 'POST':
           return UserModelSerializer2
       return UserModelSerializer1
  • 对于删除某个数据,走DestoryModelMixin中的destory方法

        delete: 127.0.0.1/api/user/4/

class DestroyModelMixin:
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()

对于更新某个数据,走UpdateModelMixin中的update/partial_update方法

        put:  127.0.0.1/api/user/1/

        patch:  127.0.0.1/api/user/1/

class UpdateModelMixin:
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

与上面post的create一样也有perform啥的,可以根据自己需求重写,这里不做演示。

 注:

  • put:校验器中要求的字段(fileds中)都必须填写(extra_kwargs或类中新增字段加read_only除外,或default除外),不然报错。其返回内容也是校验器中的字段(不是传上去的字段,传的时候read_only可以不传,就算传了也改不了,但返回时你会看到)。
  • patch:可以只传一个或几个要改的键值对
  • update方法默认情况下不支持可写嵌套字段, 我们可以重写update,或在嵌套的序列化程序字段上设置好'read_only=True'   ,但如此一来之前的create就没法创建一对多多对多关系了。用一个校验器的话建议重写get_serializer_class改为用两个
        depart = DepartModelSerializer(many=False, read_only=True)# 不建议
        roles = RoleModelSerializer(many=True, read_only=True)# 不建议

4. ModelViewSet

ModelViewSet是上面五大类的合并,当增删改查功能都出现时可以不用写那么多直接继承ModelViewSet。

from rest_framework.viewsets import ModelViewSet

当然如果只有一两个功能啥的还是建议单个继承。

5. 权限补充

在之前定义权限类时,类中可以定义两个方法:has_permissionhas_object_permission

  • has_permission ,在请求进入视图之前就会执行。

  • has_object_permission,当视图中调用 self.get_object时就会被调用(删除、更新、查看某个对象时都会调用),一般用于检查对某个对象是否具有权限进行操作。

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):
        # 根据request.user是谁看有没有权限
        return True

我们在调用上面三个类的时候,会根据传过来的id/pk执行GenericAPIView中的get_object()在该方法中

它的内部便是调用了has_object_permission看看是否有权限进行操作。

区别:

  • has_permission ,在访问url进入视图之前就会执行。

  • has_object_permission,当视图中调用 self.get_object时就会被调用(删除、更新、查看某个对象时都会调用),一般用于检查对某个对象是否具有权限进行操作。

所以,让我们在编写视图类时,如果是直接获取间接继承了 GenericAPIView,同时内部调用 get_object方法,这样在权限中通过 has_object_permission 就可以进行权限的处理。

九、条件搜索

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

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

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

1. 自定义filter

# 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")

流程:假如访问127.0.0.1/api/user?age=18&id=12

  • queryset = models.UserInfo.objects.all()获取queryset
  • 将queryset传给Filter1,Filter1返回queryset.filter(age=age)再将这个返回的queryset当作Filter2的queryset参数进行筛选,返回queryset.filter(id__gt=user_id)。
  • filter_backends里的都走完以后才是我们真正开始使用的queryset

当然如果访问带参数的url如127.0.0.1/api/user/100/?age=18&id=12则条件100会在最后得到的queryset获取查找。

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")

此时get: 127.0.0.1:8000/api/user?id=2 和 127.0.0.1:8000/api/user/2/效果基本是一样

这个就相当于直接url内部不带参数的get请求,直接返回一个queryset,不过这次的queryset经过filter筛选并覆盖后的queryset了。

示例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):
    # 传入Char   ?dd=it      则筛选通过跨表depart__title=it
    dd= filters.CharFilter(field_name="depart__title", lookup_expr="exact")
    # 传入Number ?min_id=10  则筛选成为id大于等于10的queryset
    min_id = filters.NumberFilter(field_name='id', lookup_expr='gte')

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


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

其实就是示例2字段的拓展 

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',]
}

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")

十、分页

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

我们在前面看源码时也接触到了GenericAPIView的分页功能,比如当我们get时走ListModelView,其内部便调用了这个功能。

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

BasePagination,分页基类

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

1. APIView视图

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

a.PageNumberPagination

 127.0.0.1:8000/api/user?page=3

注:此时于filter无关,我们也没在filter定义这个page字段,这是其内部规定?page

# settings.py 有优先级
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',  
    "PAGE_SIZE": 2
}


# views.py
from rest_framework.pagination import PageNumberPagination

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

    def get(self, request, *args, **kwargs):
        queryset = models.UserInfo.objects.all().order_by('id')
        pager = PageNumberPagination()
        paginate_queryset = pager.paginate_queryset(queryset, request, self)
        ser = UserModelSerializer(instance=paginate_queryset, many=True)
        return Response(ser.data)

注意要在settings中设置每页几条数据,不然得不到结果。

或者:

class MyPageNumberPagination(PageNumberPagination):
    page_size_query_param = 'size'
    page_size = 2
    max_page_size = 100

class UserView(APIView):

    def get(self, request, *args, **kwargs):
        queryset = models.UserInfo.objects.all().order_by('id')
        pager = MyPageNumberPagination()
        paginate_queryset = pager.paginate_queryset(queryset, request, self)
        ser = UserModelSerializer(instance=paginate_queryset, many=True)
        return Response(ser.data)

我们规定了size,以后再去get默认就只显示一个size的数据了。

当然如果127.0.0.1:8000/api/user?page=1&size=4  则一页显示4条数据

2.LimitOffsetPagination

与上面一样可以在settings中直接设置或者自己写

class MyPageNumberPagination(LimitOffsetPagination):
    page_size_query_param = 'size'
    page_size = 3
    max_page_size = 100

class UserView(APIView):

    def get(self, request, *args, **kwargs):
        queryset = models.UserInfo.objects.all().order_by('id')
        pager = MyPageNumberPagination()
        paginate_queryset = pager.paginate_queryset(queryset, request, self)
        ser = UserModelSerializer(instance=paginate_queryset, many=True)
        return Response(ser.data)

3.CursorPagination

要注意这里要加上ordering 否则报错

class MyPageNumberPagination(CursorPagination):
    ordering = 'id'
    page_size_query_param = 'size'
    page_size = 3
    max_page_size = 100

class UserView(APIView):

    def get(self, request, *args, **kwargs):
        queryset = models.UserInfo.objects.all().order_by('id')
        pager = MyPageNumberPagination()
        paginate_queryset = pager.paginate_queryset(queryset, request, self)
        ser = UserModelSerializer(instance=paginate_queryset, many=True)
        return Response(ser.data)

2. GenericAPIView派生类

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

a.PageNumberPagination

同时也会生成:上一页、下一页链接,根据你写的页码和每页个数。 

class MyPageNumberPagination(PageNumberPagination):
    page_size_query_param = 'size'
    page_size = 3
    max_page_size = 100


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

2.LimitOffsetPagination

class MyLimitOffsetPagination(LimitOffsetPagination):
    page_size_query_param = 'size'
    page_size = 3
    max_page_size = 100


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

3.CursorPagination

class MyCursorPagination(CursorPagination):
    ordering = 'id'
    page_size_query_param = 'size'
    page_size = 3
    max_page_size = 100


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

全局配置

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    #'DEFAULT_PAGINATION_CLASS': 'xx.xxx.MyPxxxPagination',
    "PAGE_SIZE": 2
}

十一、路由

在之前进行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) #这里users后面不要加/了

urlpatterns = [
    # 其他URL
    # path('xxxx/', xxxx.as_view()),
]

urlpatterns += router.urls

此时users后面不要加/了,且这句话会生成多个链接,比如带着pk的:

也可以利用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内部是由解析器,根据请求者传入的数据格式 + 请求头来进行处理。

1.JSONParser

2.FormParser

3.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>

4.FileUploadParser

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

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐