跳转至

DRF组件

add_circle2025-03-05update2025-03-05

为了方便接下来的学习,我们创建一个新的子应用 component

python manage.py startapp component

component/urls.py,子路由代码:

from django.urls import path
from . import views

urlpatterns = [

]

注册子应用,settings.py,代码:

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",

    "rest_framework",

    "stuapi",
    "students",
    "sers",  # 序列化阶段
    "req",
    'drfview',  # 视图
    "component", # 组件
]

总路由,代码:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("stuapi/", include("stuapi.urls")),
    path("drfapi/", include("students.urls")),
    path("sers/", include("sers.urls")),
    path("req/", include("req.urls")),
    path("drfview/", include("drfview.urls")),
    path("component/", include("component.urls")),
]

因为接下来的认证组件中需要使用到登陆功能,所以我们使用django内置admin站点并创建一个管理员.

admin运营站点的访问地址:http://127.0.0.1:8000/admin

python manage.py createsuperuser # root  123
# 如果之前有账号,但是忘了,可以通过终端下的命令修改指定用户的密码,这里的密码必须8位长度以上的
python manage.py changepassword 用户名

1557276390641

创建管理员以后,访问admin站点,先修改站点的语言配置,settings.py,代码:

LANGUAGE_CODE = "zh-hans"

TIME_ZONE = "Asia/Shanghai"

image-20231012084404610

访问admin 站点效果:

1553043054133

1. 认证Authentication

可以在配置文件中配置全局默认的认证方式/认证流程。所谓认证方式就是借助会话控制技术进行用户认证识别。

开发中常见的认证方式:cookie、sessiontoken

在drf的默认配置文件中,提供了DEFAULT_AUTHENTICATION_CLASSES配置项进行认证类的注册。

rest_framework/settings.py,代码:

REST_FRAMEWORK = {
    # 配置认证方式的选项
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication', # session认证,从request.user提取用户认证
        'rest_framework.authentication.BasicAuthentication',     # basic认证[基于请求头,使用用户ID与密码来完成用户认证]
    )
}

可以在具体的视图类中通过设置类属性authentication_classess来设置单独的不同的认证方式

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


# Create your views here.
class HomeAPIView(APIView):

    def get(self, request):
        """
        如果用户没有登陆,或者request.user无法获取用户的认证信息,则request.user的值是匿名用户:AnonymousUser
        如果用户已经登陆,经过中间件的处理以后,request.user的值是当前登陆用户的模型对象
        """
        user = request.user
        print(user, type(user))
        """
        未登录用户:
        AnonymousUser <class 'django.contrib.auth.models.AnonymousUser'>
        已登录用户:
        root <class 'django.utils.functional.SimpleLazyObject'> # 运行时结果就是用户模型auth.models.User
        """
        if user.id:
            print(user.username, user.first_name, user.last_name)
        """
        root 比尔 盖茨
        """
        return Response({"msg": "ok"})

认证失败会有两种可能的返回值,这个需要我们配合权限组件来使用:

  • 401 Unauthorized 未认证
  • 403 Permission Denied 权限被禁止

1.1 自定义认证类

component/authentication.py,代码:

from rest_framework import authentication
from django.contrib.auth import get_user_model


"""
drf中所有的认证类必须直接或间接继承于authentication.BaseAuthentication类。
"""


class CustomAuthentication(authentication.BaseAuthentication):
    def authenticate(self, request):
        User = get_user_model()
    # request._request.headers.get("键")     drf从django的请求头中提取数据
        user_id = request.query_params.get('user')
        password = request.query_params.get('password')

        if user_id is None or password is None:
            return None
        try:
            user = User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

        ret = user.check_password(password)
        if ret is False:
            return None
        return (user, None)

在项目中注册并使用上面自定义的认证类,settings.py,代码:

# 此处告诉Django,drf所有的配置在 REST_FRAMEWORK 字典中。
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        "component.authentication.CustomAuthentication",
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],
}

在postman中访问HomeAPIView视图,请求地址:http://127.0.0.1:8000/component/home/?user=1&password=123

注意:前提是你的用户ID=1,登陆密码是123,否则自己调整url地址。

效果如下:

image-20231012095803802

image-20231012095810115

上面自定义的认证类,是在主应用配置中进行全局注册的,所以有效范围是整个站点的所有视图类中都会优先使用我们自定义的认证类。

如果将来开发中,只希望自定义的认证类或者第三方认证类,仅仅用于部分视图类,则不在settings.py中进行全局注册,而是采用视图类APIView提供的authtication_classes来注册,这样就可以实现认证类仅仅在当前视图视图中有效了。

注释掉setttings.py中的配置。然后在视图类局部调用自定义认证

settings.py,代码:

# 此处告诉Django,drf所有的配置在 REST_FRAMEWORK 字典中。
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # "component.authentication.CustomAuthentication",
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],
}

视图代码:

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

# Create your views here.
class HomeAPIView(APIView):
    # 在视图中局部设置自定义认证类
    authentication_classes = [authentication.CustomAuthentication]
    def get(self, request):
        """
        如果用户没有登陆,或者request.user无法获取用户的认证信息,则request.user的值是匿名用户:AnonymousUser
        如果用户已经登陆,经过中间件的处理以后,request.user的值是当前登陆用户的模型对象
        """
        user = request.user
        print(user, type(user))
        """
        未登录用户:
        AnonymousUser <class 'django.contrib.auth.models.AnonymousUser'>
        已登录用户:
        root <class 'django.utils.functional.SimpleLazyObject'> # 运行时结果就是用户模型auth.models.User
        """
        if user.id:
            print(user.username, user.first_name, user.last_name)
        """
        root 比尔 盖茨
        """
        return Response({"msg": "ok"})

2. 权限Permissions

权限控制可以限制用户对于视图的访问和对于具有模型对象的访问。

  • 在APIView视图的dispatch()方法中调用initial方法中先进行视图访问权限的判断

self.check_permissions(request)

  • 在GenericAPIView通过get_object()获取具体模型对象时,会进行模型对象访问权限的判断

self.check_object_permissions(self.request, obj)

2.1 使用

可以在配置文件restframework/settings.py中默认的全局设置了权限管理类,源码:

REST_FRAMEWORK = {
    ....
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',  # AllowAny 表示允许任何用户访问站点视图
    ],
}

如果要在项目覆盖默认配置rest_framework/settings.py的设置,则可以在项目配置文件中,settings.py,代码:

"""drf的配置"""
# from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly
REST_FRAMEWORK = {
    # 配置认证方式的选项[全局配置]
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication', # session认证
        'rest_framework.authentication.BasicAuthentication',   # basic认证[基于账号密码]
    ],
    # 配置权限的选项[全局配置]
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ]
}

也可以在视图类中通过类属性permission_classes来进行局部设置,如

from rest_framework import permissions
from rest_framework.generics import ListCreateAPIView
from stuapi import models
from sers import serializers


class Home2APIView(ListCreateAPIView):
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
    # 设置访问权限
    # permission_classes = [permissions.IsAuthenticated] # 只允许登陆用户[包括管理员]访问
    # permission_classes = [permissions.IsAdminUser] # 只允许管理员访问【游客、普通用户不能访问】
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]  # 登录用户可以操作数据,但是游客只能看不能修改

2.2 用于判断权限的类

drf中权限类默认会把用户根据认证状态分为3种不同的用户:管理员、登陆用户、游客(未登录用户)。

  • AllowAny 默认权限,允许所有用户(管理员、登陆用户、游客)进行操作访问
  • IsAuthenticated 仅允许登录用户与管理员进行操作访问
  • IsAdminUser 仅允许管理员用户进行操作访问
  • IsAuthenticatedOrReadOnly 仅允许登陆用户可以对数据进行增删查改操作,而游客只能查看数据不能修改。

打开浏览器的无痕模式:Ctrl+Shift+N

视图代码:

from rest_framework import permissions
from rest_framework.generics import ListCreateAPIView
from stuapi import models
from sers import serializers


class Home2APIView(ListCreateAPIView):
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
    # 设置访问权限
    # permission_classes = [permissions.IsAuthenticated] # 只允许登陆用户[包括管理员]访问
    # permission_classes = [permissions.IsAdminUser] # 只允许管理员访问【游客、普通用户不能访问】
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]  # 登录用户可以操作数据,但是游客只能看不能修改

路由代码:

from django.urls import path
from . import views

urlpatterns = [
    path("home/", views.HomeAPIView.as_view()),
    path("home2/", views.Home2APIView.as_view()),
]

访问效果:

image-20231012105446845

2.3 自定义权限类

如需自定义权限,需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部

  • has_permission(self, request, view)

是否可以访问视图, view表示当前视图对象,request可以通过user属性获取当前用户

  • has_object_permission(self, request, view, obj)

是否可以访问模型对象, view表示当前视图, obj为模型数据对象,request可以通过user属性获取当前用户

例如:在当前子应用component下,创建一个权限文件permissions.py中声明自定义权限类:

from rest_framework.permissions import BasePermission


"""
drf中所有权限类都必须直接或间接继承于rest_framework.permissions.BasePermission
而且,自定义权限类时必须声明2个方法之中至少1个:has_permission(self, request, view) 与 has_object_permission(self, request, view, obj) 方法,否则报错
"""


class CustomPermission(BasePermission):
    """自定义权限类"""
    def has_permission(self, request, view):
        """
        判断视图访问权限
        :param request drf提供的HTTP请求对象
        :param view    当前被访问的视图对象
        :return  必须是True或者False,True表示允许访问 False不允许访问
        """
        if int(request.query_params.get("level", 0)) >= 10:
            return True

    def has_object_permission(self, request, view, obj):
        """
        判断模型操作权限
        :param request drf提供的HTTP请求对象
        :param view    当前被访问的视图对象
        :param obj    当前视图中基于get_object调用的模型对象
        :return  必须是True或者False,True表示允许访问 False不允许访问
        """
        return True

局部配置自定义权限,视图代码:

from rest_framework import permissions
from rest_framework.generics import ListCreateAPIView
from stuapi import models
from sers import serializers

from .permissions import CustomPermission


class LevelAPIView(APIView):
    permission_classes = [CustomPermission]

    def get(self, request):
        return Response({"msg": "VIP10->专属功能"})

访问地址:http://127.0.0.1:8000/component/xm/

访问地址:http://127.0.0.1:8000/component/xm/?level=9

访问地址:http://127.0.0.1:8000/component/xm/?level=10

3. 限流 Throttling

可以对接口访问的频次进行限制,以减轻数据库的查询压力,或者实现特定的业务。

一般用于付费购买次数,投票等场景使用。

3.1 基本使用

可以在配置文件中使用DEFAULT_THROTTLE_CLASSESDEFAULT_THROTTLE_RATES进行全局配置,针对整个站点所有视图。

"""drf的配置"""
# from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly
REST_FRAMEWORK = {
    # 配置认证方式的选项[全局配置]
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication', # session认证
        'rest_framework.authentication.BasicAuthentication',   # basic认证[基于账号密码]
    ],
    # # 配置权限的选项[全局配置]
    # 'DEFAULT_PERMISSION_CLASSES': [
    #     # 'rest_framework.permissions.IsAuthenticated',  # 如果将来的站点时封闭内部系统,则设置IsAuthenticated
    #     # 'rest_framework.permissions.AllowAny',         # 如果将来的站点时开放外部系统,则设置AllowAny
    #     'component.permissions.VVIPPermission'
    # ],
    # 配置限流[全局配置]
    'DEFAULT_THROTTLE_CLASSES':[ # 限流配置类
        'rest_framework.throttling.AnonRateThrottle',  # 未登录认证的用户
        'rest_framework.throttling.UserRateThrottle',  # 已登录认证的用户
    ],
    'DEFAULT_THROTTLE_RATES': {  # 访问频率的全局配置
        'anon': '2/day',  # 针对游客的访问频率进行限制,实际上,drf只是识别首字母,但是为了提高代码的维护性,建议写完整单词
        'user': '5/day',  # 针对会员的访问频率进行限制,
    }
}

DEFAULT_THROTTLE_RATES限流配置可以支持使用 s(秒), m(分), h(时) 或d(天)来指明限流周期,对应的设置是在rest_framework/throttling.py的SimpleRateThrottle类中的parse_rate方法中设置的。源代码如下:

    def parse_rate(self, rate):
        """
        Given the request rate string, return a two tuple of:
        <allowed number of requests>, <period of time in seconds>
        """
        if rate is None:
            return (None, None)
        num, period = rate.split('/')
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]   # 核心代码
        return (num_requests, duration)

限流配置也可以在具体视图类中通过类属性throttle_classess来局部配置,views.py,代码:

from rest_framework.throttling import UserRateThrottle, AnonRateThrottle
class ThorttlingAPIView(APIView):
    throttle_classes = [UserRateThrottle, AnonRateThrottle]
    def get(self,request):
        return Response("ok")

3.2 可选限流类

限流类
AnonRateThrottle 限制所有未登录用户,使用IP区分用户。 使用配置项DEFAULT_THROTTLE_RATES['anon'] 来设置频次
UserRateThrottle 限制已登录用户,使用User模型的 id主键 来区分。 使用配置项DEFAULT_THROTTLE_RATES['user'] 来设置频次
ScopedRateThrottle 限制用户对于每个视图类的访问频次,使用ip或user id。 使用视图类中的throttle_scope设置限流频次的变量名,假设是a,则可以使用配置项DEFAULT_THROTTLE_RATES['a']来设置频次

settings.py,代码:

# 此处告诉Django,drf所有的配置在 REST_FRAMEWORK 字典中。
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # "component.authentication.CustomAuthentication",
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],
    # # 配置权限的选项[全局配置]
    # 'DEFAULT_PERMISSION_CLASSES': [
    #     'rest_framework.permissions.IsAuthenticated',  # 设置整个站点所有的视图方法都只允许登陆访问
    # ],
    # 限流[全局配置]
    # "DEFAULT_THROTTLE_CLASSES": [
    #     'rest_framework.throttling.AnonRateThrottle',  # 未登录的用户
    #     'rest_framework.throttling.UserRateThrottle',  # 已登录的用户
    # ],
    "DEFAULT_THROTTLE_RATES": {
        'anon': '6/day',  # 针对游客的访问频率进行限制,实际上,drf只是识别首字母,但是为了提高代码的维护性,建议写完整单词
        'user': '5/day',  # 针对会员的访问频率进行限制,
        'member': '2/min',
    },
}

视图代码:

from rest_framework import throttling


class ThrottingAPIView(APIView):
    # 针对当前视图使用throttling.AnonRateThrottle对游客访问限流
    # 针对当前视图使用throttling.UserRateThrottle对用户访问限流
    # throttle_classes = [throttling.AnonRateThrottle, throttling.UserRateThrottle]
    # 给指定视图设置限流命名空间,在配置文件中针对当前视图进行限流
    # throttle_classes = [throttling.ScopedRateThrottle]
    throttle_scope = "member"

    def get(self, request):
        return Response({"msg": "限流功能的视图"})

urls.py,代码:

from django.urls import path
from . import views

urlpatterns = [
    path("home/", views.HomeAPIView.as_view()),
    path("home2/", views.Home2APIView.as_view()),
    path("xm/", views.LevelAPIView.as_view()),
    path("thrott/", views.ThrottingAPIView.as_view()),
]

4. 过滤Filtering

Github:https://github.com/carltongibson/django-filter

使用文档:https://django-filter.readthedocs.io/en/stable/guide/rest_framework.html

对于列表数据可能需要根据字段进行过滤,我们可以通过添加django-fitlter扩展模块来增强支持。

pip install django-filter

settings.py,代码:

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

在配置文件中增加过滤器类的全局设置:

"""drf配置信息必须全部写在REST_FRAMEWORK配置项中"""
REST_FRAMEWORK = {
    # ....代码省略。。。

    # 过滤查询,全局配置
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend'
    ],
}

在具体的视图类中添加类属性filterset_fields,指定可以过滤的字段

from rest_framework.generics import ListAPIView


class FilterAPIView(ListAPIView):
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
    # 设置允许客户端使用的过滤字段[列表可以写1个或多个过滤字段名,字段名来自模型]
    filterset_fields = ["sex", "age"]

# 单个字段过滤
# http://127.0.0.1:8000/component/list/?age=18
# http://127.0.0.1:8000/component/list/?sex=1
# 多个字段过滤
# http://127.0.0.1:8000/component/list/?age=18&sex=1

路由代码:

from django.urls import path
from . import views

urlpatterns = [
    path("home/", views.HomeAPIView.as_view()),
    path("home2/", views.Home2APIView.as_view()),
    path("xm/", views.LevelAPIView.as_view()),
    path("thrott/", views.ThrottingAPIView.as_view()),
    path("filter/", views.FilterAPIView.as_view()),
]

4.1 局部设置

就是直接在视图类中设置类属性filter_backends调用的过滤器类

from rest_framework.generics import ListAPIView
from django_filters.rest_framework import DjangoFilterBackend

class FilterAPIView(ListAPIView):
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
    # 在当前视图中设置过滤器
    filter_backends = [DjangoFilterBackend]
    # 设置允许客户端使用的过滤字段
    filterset_fields = ["sex", "age"]

注意:

开发中针对Filtering组件的使用,更多是使用在视图类中局部设置,而不会全局设置。

5. 排序Ordering

对于列表数据,REST framework提供了OrderingFilter过滤器来帮助我们快速指明数据按照指定字段进行排序。

在类视图中设置filter_backends,使用rest_framework.filters.OrderingFilter过滤器,REST framework会在请求的查询字符串参数中检查是否包含了ordering参数,如果包含了ordering参数,则按照ordering参数指明的排序字段对数据集进行排序。

前端可以传递的ordering参数的可选字段值需要在ordering_fields中指明。

全局配置,settings.py,代码:

"""drf的配置"""
# from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly
REST_FRAMEWORK = {
   # 中间代码省略。。。。。
   # 中间代码省略。。。。。

    # 查询过滤[全局配置]
    'DEFAULT_FILTER_BACKENDS': [
        # 'django_filters.rest_framework.DjangoFilterBackend',  # 过滤
        'rest_framework.filters.OrderingFilter',  # 排序
    ],
}

视图代码:

class OrderAPIView(ListAPIView):
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
    ordering_filters = ["id", "age"]

# http://127.0.0.1:8000/component/order/?ordering=-id
# -id 表示针对id字段进行倒序排序
# id  表示针对id字段进行升序排序

urls.py,代码:

from django.urls import path
from . import views

urlpatterns = [
    path("home/", views.HomeAPIView.as_view()),
    path("home2/", views.Home2APIView.as_view()),
    path("xm/", views.LevelAPIView.as_view()),
    path("thrott/", views.ThrottingAPIView.as_view()),
    path("filter/", views.FilterAPIView.as_view()),
    path("order/", views.OrderAPIView.as_view()),
]

5.1 局部设置

在视图类中使用filter_backends设置当前视图类中使用的排序类,views.py,代码:

from rest_framework.filters import OrderingFilter


class OrderAPIView(ListAPIView):
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
    filter_backends = [OrderingFilter]
    ordering_filters = ["id", "age"]

上面提到,因为过滤和排序共用了一个配置项filterbackends,所以如果排序和过滤要一起使用的话则必须整个项目,要么一起全局过滤排序,要么一起局部过滤排序。绝不能出现一个全局,一个局部的这种情况,局部配置项filterbackends会自动覆盖全局配置的DEFAULTFILTERBACKENDS。

from rest_framework.generics import ListCreateAPIView
from stuapi import models
from sers import serializers
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter


class StuListAPIView(ListAPIView):
    """既要排序又要过滤"""
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
    filter_backends = [DjangoFilterBackend, OrderingFilter]
    filterset_fields = ["sex", "age"]
    ordering_filters = ["id", "age"]

# http://127.0.0.1:8000/component/stu/?ordering=-id&classmate=301

路由,代码:

from django.urls import path
from . import views

urlpatterns = [
    path("home/", views.HomeAPIView.as_view()),
    path("home2/", views.Home2APIView.as_view()),
    path("xm/", views.LevelAPIView.as_view()),
    path("thrott/", views.ThrottingAPIView.as_view()),
    path("filter/", views.FilterAPIView.as_view()),
    path("order/", views.OrderAPIView.as_view()),
    path("stu/", views.StuListAPIView.as_view()),
]

6. 分页Pagination

因为django默认提供的分页器主要使用于前后端不分离的业务场景,所以REST framework也提供了针对接口数据的分页支持。

我们可以在配置文件settings.py中进行全局分页配置,代码:

"""drf的配置"""
# from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly
REST_FRAMEWORK = {
    # 中间代码省略......
    # 中间代码省略......
    # 列表分页[全局配置,对整站所有的列表页视图都会进行分页处理]
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',  # 以page参数作为分页参数
    # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',              # 以limit和offset作为分页参数
    'PAGE_SIZE': 2,  # 每页数目,如果不设置,则没有进行分配
}

上面直接访问任意一个列表页视图的api接口,可以看到如下效果:

{
    "count": 16,   // 本次分页的总数据量
    "next": "http://127.0.0.1:8000/component/page/?page=3",  // 下一页数据所在的地址
    "previous": "http://127.0.0.1:8000/component/page/",         // 上一页数据所在的地址
    "results": [    // 当前页数据的列表项
        {
            "id": 6,
            "name": "xiaoming",
            "sex": true,
            "age": 20,
            "classmate": "303",
            "description": "hello world"
        },
       // .....
    ]
}

如果在settings.py配置文件中设置了全局分页,那么在drf中凡是调用了ListModelMixin的list()都会自动分页。如果项目中出现大量需要分页的数据,只有少数部分的不需要分页,则可以在少部分的视图类中关闭分页功能。可以在视图类中设置如下:

class PageAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    pagination_class = None  # 关闭来自全局配置的分页设置,设置当前列表视图不需要分页

也可通过自定义Pagination类来为视图添加不同分页行为。在视图类中通过pagination_classs属性来指明。

6.1 可选分页器

drf提供提供了2个分页器给开发者实现分页,LimitOffsetPagination在开发中比较少用,常用是PageNumberPagination。

6.1.1 PageNumberPagination

前端访问网址形式:

GET  http://127.0.0.1:8000/component/page/?page=2

可以在子类中定义的属性:

参数名 描述
page_size 每页数据量的数量,默认是没有设置的,我们可以在配置分页时通过PAGE_SIZE设置设置
pagequeryparam url地址栏上当前页码的参数名,默认为"page"
pagesizequery_param url地址栏上代表每一页数据量的参数名,默认是None,也就是不允许地址栏修改每一页数据量。
maxpagesize 限制url地址栏上设置每一页数据量的最大值,前提是已经设置了pagesizequery_param

例如,在当前子应用下的paginations.py创建一个自定义分页器类,代码:

from rest_framework.pagination import PageNumberPagination


class StudentPagination(PageNumberPagination):
    page_size = 10 # 每页数据量
    page_query_param = "page" # 地址栏代表页码的参数
    page_size_query_param = "size"  # 地址栏代表数据量的参数
    max_page_size = 10  # 限制客户端修改的每页数据量的最大值

视图中使用自定义分页类,视图代码:

from . import paginations

class studentAPIView(ListAPIView):
    """数据分页"""
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
    # pagination_class的值只能是一个分页对象,
    pagination_class = paginations.StudentPagination

# 访问地址:http://127.0.0.1:8000/component/student/?page=3
# 访问地址:http://127.0.0.1:8000/component/student/?page=3&size=5

6.1.2 LimitOffsetPagination

前端访问网址形式:

GET http://127.0.0.1:8000/component/page/?limit=5&offset=10

可以在子类中定义的属性:

参数 描述
default_limit 默认每一页展示数据的数量,默认值与PAGE_SIZE一样的
limitqueryparam 默认'limit',default_limit的地址栏参数名
offsetqueryparam 查询数据的开始偏移量,相当于上面的page参数,默认'offset'
max_limit 限制地址栏对每一页数据展示的最大数量。默认None

定义分页类,paginations.py,代码:

from rest_framework.pagination import LimitOffsetPagination
# LimitOffsetPagination,以数据库查询的limit和offset数值作为分页条件
# limit=10&offset=0   第1页
# limit=10&offset=10  第2页


class StudentLimitPagination(LimitOffsetPagination):
    default_limit = 10 # 默认的每页数据量
    offset_query_param = "offset"  # 地址栏代表偏移量的参数,类似上面的page
    limit_query_param = "limit" # 地址栏代表数据量的参数,类似上面的size
    max_limit = 10  # 限制客户端修改的每页数据量的最大值

视图,views.py,代码:

from . import paginations


class Student2APIView(ListAPIView):
    """数据分页: LimitOffsetPagination"""
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
    pagination_class = paginations.StudentLimitPagination

  # 访问地址:http://127.0.0.1:8000/component/student2/?limit=3

7. 异常处理 Exceptions

REST framework本身在APIView提供了异常处理,但是仅针对drf内部现有的接口开发相关的异常进行格式处理,但是开发中我们还会使用到各种的数据或者进行各种网络请求,这些都有可能导致出现异常,这些异常在drf中是没有进行处理的,所以就会冒泡给django框架了,django框架会进行组织错误信息,作为html页面返回给客户端,所在在前后端分离项目中,可能js的ajax无法理解或者无法接收到这种数据,甚至导致js出现错误的情况。因此为了避免出现这种情况,我们可以自定义一个属于自己的异常处理函数,对于drf无法处理的异常,我们自己编写异常处理的代码逻辑。

针对于现有的drf的异常处理进行额外添加属于开发者自己的逻辑代码,一般我们编写的异常处理函数,会写一个公共的目录下或者主应用目录下。这里主应用下直接创建一个exceptions.py,代码:

from rest_framework.views import exception_handler
from rest_framework.response import Response
from rest_framework import status
from django.core.exceptions import ObjectDoesNotExist

"""
执行上下文[context]: 指程序运行到某一行时,解析器内部能提供给开发者所调用的程序相关环境信息
"""


def custom_exception_handler(exc, context):
    """
    自定义异常处理
    :param exc      本次发生的异常实例对象
    :param context  本次发生时的执行上下文[类似字典的对象,记录错误发生时python解析器能提供的关于本次错误的所有信息,例如错误发生的路径,行号,错误提示,发生的事件,视图]
    """
    print(context)  # {'view': <component.views.ExceptionAPIView object at 0x0000016D6664D060>, 'args': (), 'kwargs': {}, 'request': <rest_framework.request.Request: GET '/component/exc/'>}
    print(context['request'])  # <rest_framework.request.Request: GET '/component/exc/'>
    # 先调用drf的异常处理函数,让drf先处理
    response = exception_handler(exc, context)
    # 如果drf无法处理,那么我们自己处理
    if response is None:
        """当drf遇到无法处理的异常,就返回None,此时我们需要自己判断"""
        if isinstance(exc, ZeroDivisionError):
            response = Response({'detail': '0不能作为除数!'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        elif isinstance(exc, ObjectDoesNotExist):
            response = Response({'detail': '当前数据不存在!'}, status=status.HTTP_404_NOT_FOUND)
    # 返回结果
    return response

在配置文件中声明自定义的异常处理,settings.py,代码:

# 此处告诉Django,drf所有的配置在 REST_FRAMEWORK 字典中。
REST_FRAMEWORK = {
    # 。。。中间是其他配置,所以省略
    # 自定义异常
    'EXCEPTION_HANDLER': 'drfdemo.exceptions.custom_exception_handler',
}

如果未声明自定义异常的话,drf会采用默认的方式,使用自己封装的异常处理函数,rest_framework/settings.py,源代码:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',  # from rest_framework.views import exception_handler
}

视图代码:

class ExceptionAPIView(APIView):
    def get(self, request):
        # print(1/0) # 这句代码会抛出python异常 ZeroDivisionError
        models.Student.objects.get(pk=60000)
        return Response({"msg": "ok"})

urls.py,代码:

from django.urls import path
from . import views

urlpatterns = [
    path("home/", views.HomeAPIView.as_view()),
    path("home2/", views.Home2APIView.as_view()),
    path("xm/", views.LevelAPIView.as_view()),
    path("thrott/", views.ThrottingAPIView.as_view()),
    path("filter/", views.FilterAPIView.as_view()),
    path("order/", views.OrderAPIView.as_view()),
    path("stu/", views.StuListAPIView.as_view()),
    path("student/", views.StudentAPIView.as_view()),
    path("student2/", views.Student2APIView.as_view()),
    path("exc/", views.ExceptionAPIView.as_view()),
]

7.1 REST framework内置异常处理类

所谓的内置异常处理类就是针对这部分异常,drf可以进行处理成json格式返回给客户端。

异常类 描述
APIException drf的所有异常的父类
ParseError 解析错误
AuthenticationFailed 认证失败
NotAuthenticated 尚未认证
PermissionDenied 权限拒绝
NotFound 404 未找到
MethodNotAllowed 请求方式不支持
NotAcceptable 要获取的数据格式不支持
UnsupportedMediaType 不支持的媒体格式
Throttled 超过限流次数
ValidationError 校验失败

也就是说很多的没有在上面列出来的异常,就需要我们在自定义异常函数中自己处理了。

8. 自动生成接口文档

REST framework可以自动帮助我们生成接口文档。drf的接口文档多数以网页的方式呈现,自动接口文档能生成的是继承自APIView及其子类的视图。

8.1 coreapi

8.1.1 安装依赖

REST framewrok生成接口文档需要coreapi库的支持。

pip install coreapi

8.1.2 设置接口文档访问路径

在settings.py中配置接口文档的模块到项目中。

INSTALLED_APPS = [

    'coreapi',


]
REST_FRAMEWORK = {
    # 。。。 其他选项
    # 配置自动生成接口文档的模式
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
}

在总路由中添加接口文档路径。

文档路由对应的视图配置为rest_framework.documentation.include_docs_urls

参数title为接口文档网站的标题。总路由urls.py,代码:

from rest_framework.documentation import include_docs_urls

urlpatterns = [
    ...
    path('docs/', include_docs_urls(title='站点页面标题')),
]

文档描述说明的定义位置

1) 单一方法的视图,可直接使用类视图的文档字符串,如

class StudentListView(ListAPIView):
    """
    返回所有学生信息
    """

2)包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如

from rest_framework.generics import ListCreateAPIView
class DocumentAPIView(ListCreateAPIView):
    """
    get: 获取所有学生信息
    post: 添加学生信息
    """
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer

from rest_framework.generics import RetrieveUpdateDestroyAPIView
class Document1APIView(RetrieveUpdateDestroyAPIView):
    """
    get: 查询一个学生信息
    put: 更新一个学生信息
    patch: 更新一个学生信息[部分字段]
    delete: 删除一个学生信息
    """
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer

3)对于视图集ViewSet,在类视图的文档字符串中定义,但是应使用action名称区分,如

from rest_framework.decorators import action
from demo.serializers import StudentLoginModelSerializer

class DocumentAPIView(ModelViewSet):
    """
    list: 获取所有学生信息
    create: 添加学生信息
    read: 查询一个学生信息
    update: 更新一个学生信息
    partial_update: 更新一个学生信息[部分字段]
    delete: 删除一个学生信息
    login: 学生登录
    """
    queryset = Student.objects.all()

    def get_serializer_class(self):
        if self.action == "login":
            return StudentLoginModelSerializer
        return StudentModelSerializer

    @action(methods=["POST"], detail=False)
    def login(self, request):
        return Response("ok")

serializers.py,代码:

from rest_framework import serializers
from stuapi.models import Student
from .models import Course

class StudentModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = "__all__"


class StudentLoginModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = ["name", "age"]

class CourseModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Course
        fields = "__all__"

stuapi/modes.py,代码:

from django.db import models


# Create your models here.
class Student(models.Model):
    """学生信息"""
    # help_text 提供给接口文档中显示当前字段的意义的
    name = models.CharField(max_length=255, verbose_name="姓名", help_text="姓名")
    sex = models.BooleanField(default=True, verbose_name="性别" ,help_text="性别")
    age = models.IntegerField(verbose_name="年龄", help_text="年龄")
    classmate = models.CharField(db_column="class", max_length=5, verbose_name="班级", help_text="班级编号为3个数字组成")
    description = models.TextField(max_length=1000, null=True, blank=True, verbose_name="个性签名", help_text="个性签名")

    class Meta:
        db_table = "tb_student"
        verbose_name = "学生"
        verbose_name_plural = verbose_name

urls.py,代码:

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

urlpatterns = [
   # ..中间代码省略
]

from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register("doc", views.DocumentAPIView, basename="doc")
urlpatterns += router.urls

8.1.3 访问接口文档网页

浏览器访问 127.0.0.1:8000/docs/,即可看到自动生成的接口文档。

接口文档网页

两点说明:

1) 视图集ViewSet中的retrieve名称,在接口文档网站中叫做read

2)参数的Description需要在模型类或序列化器类的字段中以help_text选项定义,如:

class Student(models.Model):
    ...
    age = models.IntegerField(default=0, verbose_name='年龄', help_text='年龄')
    ...

class StudentModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = "__all__"
        extra_kwargs = {
            'classmate': {
                'help_text': '班级'
            }
        }

8.2 yasg

yasg是生成Swagger风格的一个drf模块,Swagger是一个规范和完整的自动化接口开发框架,用于生成、描述、调用和可视化RESTful风格的Web服务。总体目标是使客户端和文件系统源代码同步更新接口。当接口有变动时,对应的接口文档也会自动更新。如:接口测试站点(http://httpbin.org/),也是利用Swagger来生成接口文档。

8.2.1 编写swaggerAPI接口文档

Swagger在线编辑器:https://editor.swagger.io/

演示代码:

openapi: '3.0.0' # swagger版本语法
info: # 站点信息
  title: 自动化运维平台 # 标题
  description: |- # 站点描述
    yasg是生成Swagger风格的一个drf模块,Swagger是一个规范和完整的自动化接口开发框架,用于生成、描述、调用和可视化RESTful风格的Web服务。总体目标是使客户端和文件系统源代码同步更新接口。当接口有变动时,对应的接口文档也会自动更新。如:接口测试站点(http://httpbin.org/),也是利用Swagger来生成接口文档。
  version: 0.0.1 # 站点版本
servers: # 服务端api访问地址
  - url: https://petstore3.swagger.io/api/v3

tags: # 功能模块,相当于子应用
  - name: music
    description: 音乐信息管理接口
  - name: user
    description: 用户信息与用户登录等接口
  - name: menu
    description: 导航菜单的相关接口
paths: # 接口信息
  /music: # url路径
    post: # http请求方法
      tags: # 当前接口属于哪一个功能模块的
        - music
      summary: 添加音乐接口  # 简述
      description: 添加音乐接口 # 详细说明
      operationId: createMusic # 接口别名关键字
      requestBody: # 请求
        content: # 请求体内容
          application/json: # 内容格式
            schema: # 内容结构
              type: object # 内容是一个对象
              properties: # 对象的属性
                title: # 属性名
                  type: integer  # 数据格式
                  example:  "音乐标题" # 例子参数
      responses: # 响应
        '201': # 响应状态码
          description: 创建成功 # 描述信息
          content: # 响应内容
            application/json: # 响应内容的数据格式
              schema: # 响应内容的结构
                $ref: '#/components/schemas/Music' # 如果响应内容是之前定义的模型对象,则可以使用$ref进行引用
        '400':
          description: 添加失败,请求参数有误!
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example:  -1
                  detail:
                    type: string
                    example: "添加失败,请求参数有误!"
    get:
      tags:
        - music
      summary: '查看音乐列表'
      description: '查看音乐列表'
      operationId: GetMusic
      parameters:
        - name: id
          in: query
          description: 音乐ID
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: 获取成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Music'
  /music/{id}:
    delete:
      tags:
        - music
      summary: 删除音乐数据
      description: 删除音乐数据
      operationId: DeleteMusic
      parameters:
        - name: id
          in: path
          description: 音乐主键ID
          required: true
          schema:
            type: string
      responses:
        '204':
          description: 删除成功
        '404':
          description: 找不到当前音乐

components: # 相当于模型
  schemas: # 模型的参数/表结构
    Music: # 模型类名
      type: object # object表示当前是个模型对象
      properties:  # 字段
        id: # 字段名
          type: integer # 数据类型,integer:整型, string:字符串
          format: int64 # 数据子类型,
          example: 10   # 样本参数
        title:
          type: string
          example: "音乐标题"
    User:
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        username:
          type: string
          example: theUser
        firstName:
          type: string
          example: John
        lastName:
          type: string
          example: James
        email:
          type: string
          example: john@email.com
        password:
          type: string
          example: '12345'
        phone:
          type: string
          example: '12345'

当然,有些公司需要提前写接口文档的话,我们可以使用上面的方式,也有的公司是边写代码边同步接口文档的,这种情况下,我们就可以使用drf-yasg模块自动同步接口文档。

8.2.2 drf-yasg自动生成接口文档

官方文档:https://drf-yasg.readthedocs.io/en/stable/

Github:https://github.com/axnsan12/drf-yasg

安装

pip install drf-yasg

配置,settings.py,代码:

INSTALLED_APPS = [

    'drf_yasg',  # 接口文档drf_yasg

]

总路由,

from django.contrib import admin
from django.urls import path, include
from rest_framework.documentation import include_docs_urls


# yasg的视图配置类,用于生成api
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

# 接口文档的视图配置
schema_view = get_schema_view(
    openapi.Info(
        title="drf接口文档",  # 站点标题,必填
        default_version='v1.0,0',  # api版本,必填
        description="描述信息",  # 站点描述
        terms_of_service='htttp://www.moluo.net/',   # 团队博客网址
        contact=openapi.Contact(name="墨落", url="htttp://www.moluo.net/", email="649641514@qq.com"), # 联系邮箱地址
        license=openapi.License(name="开源协议名称", url="开源协议网地") # 协议
    ),
    public=True, # 是否外部站点
    # permission_classes=(rest_framework.permissions.AllowAny)  # 权限类
)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('doc/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger'),
    path('docs/', include_docs_urls(title='公司项目名')),
    path('api/', include("stuapi.urls")),
    path('api/', include("students.urls")),
    path('sers/', include("sers.urls")),
    path("req/", include("req.urls")),
    path("demo/", include("demo.urls")),
    path("component/", include("component.urls")),
]

http://127.0.0.1:8000/doc/,访问效果:

image-20210901182801990

image-20210901182739563

yasg提供给开发者编写视图接口的描述信息方式与前面学习的coreapi一样。

views.py,代码:

# Create your views here.
class UserAPIView(ModelViewSet):
    """
    list: 返回所有学生信息
    create: 创建一个学生信息
    read: 读取一个学生信息
    update: 更新一个学生信息
    partial_update: 更新一个学生信息的单个字段
    delete: 删除一个学生
    """
    queryset = models.UserModel.objects.all()
    serializer_class = serializers.UserModelSerializer

访问效果:

image-20231013152105222