当前位置:   article > 正文

django-rest-framework框架总结之认证、权限、限流、过滤、分页及异常处理_django restframework 用户权限

django restframework 用户权限

认证 Authentication

REST 框架提供了几种即用的身份验证方案,并且支持实现自定义认证。

我们需要在 setting.py 文件中设置 DEFAULT_AUTHENTICATION_CLASSES 全局默认身份验证方案。例如。


REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
'
运行

还可以基于每个视图或每个视图集设置 permission_classes ,且视图中定义的认证方案会高于 DEFAULT_AUTHENTICATION_CLASSES 全局配置 。

from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

class ExampleView(APIView):
    authentication_classes = [SessionAuthentication, BasicAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):
        content = {
            'user': str(request.user),  # `django.contrib.auth.User` instance.
            'auth': str(request.auth),  # None
        }
        return Response(content)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

或者我们也可以使用装饰器 @api_view 结合函数视图一起使用。

@api_view(['GET'])
@authentication_classes([SessionAuthentication, BasicAuthentication])
@permission_classes([IsAuthenticated])
def example_view(request, format=None):
    content = {
        'user': str(request.user),  # `django.contrib.auth.User` instance.
        'auth': str(request.auth),  # None
    }
    return Response(content)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

认证失败通常会有两种响应:

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

权限 Permissions

权限控制可以限制用于对于视图的访问和对于具体数据对象的访问。

  • 在 执行视图的 dispatch() 方法前,会先进行视图访问权限的判断。
  • 在通过 get_object() 获取具体对象时,会进行对象访问权限的判断。

设置权限策略

可以使用该设置全局设置默认 DEFAULT_PERMISSION_CLASSES 权限策略。例如

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
'
运行

如果配置文件中未指明,则设置默认为允许无限制访问

'DEFAULT_PERMISSION_CLASSES': [
   'rest_framework.permissions.AllowAny',
]
  • 1
  • 2
  • 3

DRF中提供的权限策略

  • AllowAny 允许所有用户
  • IsAuthenticated 仅通过认证的用户
  • IsAdminUser 仅管理员用户
  • IsAuthenticatedOrReadOnly 认证的用户可以完全操作,否则只能 Get 读取

还可以基于每个视图或者每个视图集设置身份验证策略,使用基于 APIView 类的视图。

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

class ExampleView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):
        content = {
            'status': 'request was permitted'
        }
        return Response(content)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

或者 可以使用 api_view 装饰器与基于函数的视图一起使用。


from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def example_view(request, format=None):
    content = {
        'status': 'request was permitted'
    }
    return Response(content)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注意:类属性或装饰器设置新的权限类时,他的优先级会高于 setting.py 文件中的配置。

如果他们继承自 rest_framework.permissions.BasePermissionIsAuthenticatedOrReadOnly,则可以使用标准Python 按位运算符组合权限。

from rest_framework.permissions import BasePermission, IsAuthenticated, SAFE_METHODS
from rest_framework.response import Response
from rest_framework.views import APIView

class ReadOnly(BasePermission):
    def has_permission(self, request, view):
        return request.method in SAFE_METHODS

class ExampleView(APIView):
    permission_classes = [IsAuthenticated|ReadOnly]

    def get(self, request, format=None):
        content = {
            'status': 'request was permitted'
        }
        return Response(content)
        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

注意: 它支持&(和),|(或)和~(不是)。

自定义权限策略

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

  • .has_permission(self, request, view): 是否可以访问视图,view 表示当前视图对象
  • .has_object_permission(self, request, view, obj): 是否可以访问数据对象,view 表示当前视图,ojb 为数据对象

如果应向请求授予访问权限,则应返回 True ,否则应返回 False.

如果测试失败,自定义权限将引发异常。若要更改与异常关联的错误消息,请直接在自定义权限上实现属性。

from rest_framework import permissions

class CustomerAccessPermission(permissions.BasePermission):
    message = 'Adding customers not allowed.'

    def has_permission(self, request, view):
         ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

示例

下面是一个权限类示例,该权限类根据阻止列表检查传入请求的 IP 地址,并在 IP 被阻止时拒绝请求。

from rest_framework import permissions

class BlocklistPermission(permissions.BasePermission):
    """
    Global permission check for blocked IPs.
    """

    def has_permission(self, request, view):
        ip_addr = request.META['REMOTE_ADDR']
        blocked = Blocklist.objects.filter(ip_addr=ip_addr).exists()
        return not blocked
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

除了针对所有传入请求运行的全局权限外,您还可以创建对象级权限,这些权限仅针对影响特定对象实例的操作运行。例如:

class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Object-level permission to only allow owners of an object to edit it.
    Assumes the model instance has an `owner` attribute.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Instance must have an attribute named `owner`.
        return obj.owner == request.user
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

限流 Throttling

限流 可以对接口的访问频次进行限制,以减轻服务器压力。

设置限流策略

可以在配置文件中,使用 DEFAULT_THROTTLE_RATES 中进行全局配置。

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        #  适用于任何用户对接口访问的限制
        'rest_framework.throttling.AnonRateThrottle',
        # 登录用户对接口访问的限制
        'rest_framework.throttling.UserRateThrottle'
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day'
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
'
运行
  • AnonRateThrottle: 限制所有匿名未认证的用户,使用 IP 区分用户。
  • UserRateThrottle:限制认证用户,使用 user_id 来区分
  • ScopedRateThrottle: 限制用户对于每个视图访问频次,使用 IP 或者 user_id

DEFAULT_THROTTLE_RATES 可以使用 second , minute,hour, 或者 day 来指定周期。
同样还可以基于每个视图或者每个视图集设置限流策略,使用基于类视图。

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

class ExampleView(APIView):
    throttle_classes = [UserRateThrottle]

    def get(self, request, format=None):
        content = {
            'status': 'request was permitted'
        }
        return Response(content)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果将装饰器 @api_view 与基于函数的视图一起使用,则可以使用以下装饰器。

@api_view(['GET'])
@throttle_classes([UserRateThrottle])
def example_view(request, format=None):
    content = {
        'status': 'request was permitted'
    }
    return Response(content)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

还可以为使用 @action 装饰器创建的路由设置限制类。 以这种方式设置的限制类将覆盖任何视图集级别类设置。

@action(detail=True, methods=["post"], throttle_classes=[UserRateThrottle])
def example_adhoc_method(request, pk=None):
    content = {
        'status': 'request was permitted'
    }
    return Response(content)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

ScopedRateThrottle 自定义限流

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.ScopedRateThrottle'
    ],
    'DEFAULT_THROTTLE_RATES': {
        'uploads': '100/day',
        'contacts': '1000/day'
    }
}

class ExampleView(APIView):
    throttle_scope = "contacts"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

过滤 Filtering

Filtering 可以针对列表数据根据字段进行过滤

针对当前用户进行筛选

您可能希望筛选查询集,以确保仅返回与发出请求的当前经过身份验证的用户相关的结果。我们可以 使用 request.user 来获取当前用户。

from myapp.models import Purchase
from myapp.serializers import PurchaseSerializer
from rest_framework import generics

class PurchaseList(generics.ListAPIView):
    serializer_class = PurchaseSerializer

    def get_queryset(self):
        """
        This view should return a list of all the purchases
        for the currently authenticated user.
        """
        user = self.request.user
        return Purchase.objects.filter(purchaser=user)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

针对网址进行筛选

另一种筛选方式可能涉及根据 URL 的某些部分限制查询集。例如

re_path('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),
  • 1

然后,可以编写一个视图,该视图返回按 URL 的用户名部分筛选的查询集:

class PurchaseList(generics.ListAPIView):
    serializer_class = PurchaseSerializer

    def get_queryset(self):
        """
        This view should return a list of all the purchases for
        the user as determined by the username portion of the URL.
        """
        username = self.kwargs['username']
        return Purchase.objects.filter(purchaser__username=username)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

根据查询参数进行筛选

筛选初始查询集的最后一个示例是根据 url 中的查询参数确定初始查询集。

我们可以覆盖以处理诸如 ,并且仅当参数包含在 URL 中时才过滤查询集:.get_queryset()

http://example.com/api/purchases?username=denvercoder9username
  • 1
class PurchaseList(generics.ListAPIView):
    serializer_class = PurchaseSerializer

    def get_queryset(self):
        """
        Optionally restricts the returned purchases to a given user,
        by filtering against a `username` query parameter in the URL.
        """
        queryset = Purchase.objects.all()
        username = self.request.query_params.get('username')
        if username is not None:
            queryset = queryset.filter(purchaser__username=username)
        return queryset
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Generic Filtering 通用筛选

可以使用 DEFAULT_FILTER_BACKENDS 设置默认过滤器

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
  • 1
  • 2
  • 3
'
运行

还可以按视图或视图集设置筛选器后端, 使用基于 GenericAPIView 类的视图。

import django_filters.rest_framework
from django.contrib.auth.models import User
from myapp.serializers import UserSerializer
from rest_framework import generics

class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

DjangoFilterBackend

django-filter 库包含一个类 支持 REST 框架的高度可自定义 DjangoFilterBackend 字段筛选。
要使用 DjangoFilterBackend ,请先安装 django-filter

pip install django-filter
  • 1

然后添加到 Django 的 INSTALLED_APPS

INSTALLED_APPS = [
    ...
    'django_filters',
    ...
]
  • 1
  • 2
  • 3
  • 4
  • 5

将筛选器后端添加到设置中:

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
  • 1
  • 2
  • 3
'
运行

或者将过滤器后端添加到单个视图或视图集

from django_filters.rest_framework import DjangoFilterBackend

class UserListView(generics.ListAPIView):
    ...
    filter_backends = [DjangoFilterBackend]
  • 1
  • 2
  • 3
  • 4
  • 5

如果您只需要简单的基于相等的过滤,则可以在视图或视图集上设置 filterset_fields 属性,列出要过滤的字段集。

class ProductList(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['category', 'in_stock']
  • 1
  • 2
  • 3
  • 4
  • 5

设置了 filterset_fields ,则可以通过如下方式发送请求

http://example.com/api/products?category=clothing&in_stock=True
  • 1

SearchFilter 搜索过滤器

SearchFilter 类支持简单的基于单个查询参数的搜索,并且基于 Django 管理员的搜索功能。

from rest_framework import filters

class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = [filters.SearchFilter]
    search_fields = ['username', 'email']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

客户端通过进行查询来筛选列表中的项目,例如:

默认搜索参数名为 search

http://example.com/api/users?search=russell
  • 1

还可以使用查找 API 双下划线表示法对外键或 ManyToManyField 执行相关查找:

search_fields = ['username', 'email', 'profile__profession']
  • 1
'
运行

对于 JSONFieldHStoreField 字段,您可以使用相同的双下划线表示法根据数据结构中的嵌套值进行筛选:

search_fields = ['data__breed', 'data__owner__other_pets__0__name']
  • 1
'
运行

默认情况下,搜索将使用不区分大小写的部分匹配。搜索参数可以包含多个搜索词,这些搜索词应以空格和/或逗号分隔。如果使用多个搜索词,则仅当提供的所有词都匹配时,才会在列表中返回对象。

可以通过在 search_fields 前面加上各种字符来限制搜索行为。

  • “^” 从搜索开始。
  • “=”完全匹配。
  • “@”全文搜索。(目前仅支持 Django 的 PostgreSQL 后端。)
  • ‘$’ 正则表达式搜索。
search_fields = ['=username', '=email']
  • 1
'
运行

OrderingFilter 排序过滤器

OrderingFilter 类支持简单的查询参数控制的结果排序。默认情况下,查询参数名为 ordering
例如,要按用户名对用户进行排序:

http://example.com/api/users?ordering=username
  • 1

客户端还可以通过在字段名称前面加上“-”来指定反向排序,如下所示:

http://example.com/api/users?ordering=-username
  • 1

还可以指定多个排序:

http://example.com/api/users?ordering=account,username
  • 1

建议显式指定 API 应在排序筛选器中允许哪些字段。您可以通过在视图上设置属性来执行此操作,如下所示:

class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = [filters.OrderingFilter]
    ordering_fields = ['username', 'email']
  • 1
  • 2
  • 3
  • 4
  • 5

这有助于防止意外的数据泄露,例如允许用户根据密码哈希字段或其他敏感数据进行排序。

如果未在视图上指定 ordering_fields 属性,则筛选器类将默认允许用户筛选属性指定的serializer_class 序列化程序上的任何可读字段。

如果确信视图使用的查询集不包含任何敏感数据,则还可以通过使用特殊值 __all__ 显式指定视图应允许对任何模型字段或查询集聚合进行排序。

class BookingsListView(generics.ListAPIView):
    queryset = Booking.objects.all()
    serializer_class = BookingSerializer
    filter_backends = [filters.OrderingFilter]
    ordering_fields = '__all__'
  • 1
  • 2
  • 3
  • 4
  • 5

指定默认排序

如果在视图上设置了 ordering 属性,则将将其用作默认排序。

class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = [filters.OrderingFilter]
    ordering_fields = ['username', 'email']
    ordering = ['username']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

分页 Pagination

DRF 中也给我们提供了分页支持。

分页仅仅使用通用视图或视图集时,才会自动执行分页。注意如果使用的是常规 APIView ,则需要自行调用分页 API,以确保返回分页响应。
有关示例,可以参考 mixins.ListModelMixingenerics.GenericAPIView 类的源代码。
可以通过将分页类设置为 None 来关闭分页。

可以使用 DEFAULT_PAGINATION_CLASS PAGE_SIZE 设置键全局设置分页样式。例如,要使用内置的限制/偏移分页,您可以执行以下操作:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 100
}
  • 1
  • 2
  • 3
  • 4
'
运行

如果要修改分页样式的特定方面,则需要覆盖其中一个分页类,并设置要更改的属性。

class LargeResultsSetPagination(PageNumberPagination):
    page_size = 1000
    page_size_query_param = 'page_size'
    max_page_size = 10000

class StandardResultsSetPagination(PageNumberPagination):
    page_size = 100
    page_size_query_param = 'page_size'
    max_page_size = 1000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以使用pagination_class 属性将新样式应用于视图:

class BillingRecordsView(generics.ListAPIView):
    queryset = Billing.objects.all()
    serializer_class = BillingRecordsSerializer
    pagination_class = LargeResultsSetPagination
  • 1
  • 2
  • 3
  • 4

或者使用 DEFAULT_PAGINATION_CLASS 设置键全局应用样式。例如:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'apps.core.pagination.StandardResultsSetPagination'
}
  • 1
  • 2
  • 3
'
运行

PageNumberPagination 页码分页

首先需要在 DEFAULT_PAGINATION_CLASS PAGE_SIZE 设置键全局设置分页样式。

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 100
}
  • 1
  • 2
  • 3
  • 4
'
运行

请求:

https://api.example.org/accounts/?page=4
  • 1

响应:

HTTP 200 OK
{
    "count": 1023,
    "next": "https://api.example.org/accounts/?page=5",
    "previous": "https://api.example.org/accounts/?page=3",
    "results": []
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

**注意 :如果我们在视图中需要关闭分页功能,可我们只需要在视图中 设置pagination_classNone **

pagination_class = None
  • 1
'
运行

PageNumberPagination 类包含许多属性,可以重写这些属性以修改 PageNumberPagination 分页样式。

若要设置这些属性,应重写 PageNumberPagination 类,然后启用自定义分页类,如上所示

  • django_paginator_class: 要使用的 Django Paginator类。默认值为 django.core.paginator.Paginator ,这对于大多数用例来说应该没问题。
  • page_size:指示页面大小的数值。如果设置 PAGE_SIZE,这将覆盖该设置。默认为 PAGE_SIZE 与设置键相同的值。
  • page_query_param: 一个字符串值,指示要用于分页控件的查询参数的名称。
  • page_size_query_param: 如果设置,则这是一个字符串值,指示允许客户端基于每个请求设置页面大小的查询参数的名称。默认值为 None ,表示客户端可能无法控制请求的页面大小。
  • max_page_size: 如果设置,则这是一个数值,指示允许的最大请求页面大小。仅当还设置了 page_size_query_param此属性时,此属性才有效。
  • last_page_strings:字符串值的列表或元组,指示可与 一起使用以请求集合中的最后一页的值。默认值 page_query_param

LimitOffsetPagination 偏移分页

要全局启用 LimitOffsetPagination 样式,请使用以下配置:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination'
}
  • 1
  • 2
  • 3
'
运行

请求:

GET https://api.example.org/accounts/?limit=100&offset=400
  • 1

响应:

HTTP 200 OK
{
    "count": 1023,
    "next": "https://api.example.org/accounts/?limit=100&offset=500",
    "previous": "https://api.example.org/accounts/?limit=100&offset=300",
    "results": []
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

自定义分页样式

若要创建自定义分页序列化程序类,应继承 pagination.BasePagination 子类 ,重写 paginate_queryset(self, queryset, request, view=None)get_paginated_response(self, data)方法:

class CustomPagination(pagination.PageNumberPagination):
    def get_paginated_response(self, data):
        return Response({
            'links': {
                'next': self.get_next_link(),
                'previous': self.get_previous_link()
            },
            'count': self.page.paginator.count,
            'results': data
        })
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

然后,我们需要在配置中设置自定义类:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.CustomPagination',
    'PAGE_SIZE': 100
}
  • 1
  • 2
  • 3
  • 4
'
运行

异常处理 Exception

REST 框架的视图处理各种异常,并处理返回适当的错误响应。

处理的异常包括:

  • 在 REST 框架中引发的 APIException 异常
  • Django的 Http404异常
  • Django的 PermissionDenied 异常

在每种情况下,REST 框架都将返回具有相应状态代码和内容类型的响应。响应正文将包含有关错误性质的任何其他详细信息。

DELETE http://api.example.com/foo/bar HTTP/1.1
Accept: application/json
  • 1
  • 2

可能会收到错误响应,指示 DELETE 方法不允许用于该资源:

HTTP/1.1 405 Method Not Allowed
Content-Type: application/json
Content-Length: 42

{"detail": "Method 'DELETE' not allowed."}
  • 1
  • 2
  • 3
  • 4
  • 5

验证错误的处理方式略有不同,并将 NON_FIELD_ERRORS_KEY 字段名称作为响应中的键包含在内。如果验证错误不是特定于特定字段的,则它将使用“non_field_errors”键或为设置设置的任何字符串值。
示例验证错误可能如下所示:

HTTP/1.1 400 Bad Request
Content-Type: application/json
Content-Length: 94

{"amount": ["A valid integer is required."], "description": ["This field may not be blank."]}
  • 1
  • 2
  • 3
  • 4
  • 5

自定义异常处理

例如,您可能希望确保所有错误响应在响应正文中包含 HTTP 状态代码,如下所示:

HTTP/1.1 405 Method Not Allowed
Content-Type: application/json
Content-Length: 62

{"status_code": 405, "detail": "Method 'DELETE' not allowed."}
  • 1
  • 2
  • 3
  • 4
  • 5

为了更改响应的样式,您可以编写以下自定义异常处理程序:

from rest_framework.views import exception_handler

def custom_exception_handler(exc, context):
    # Call REST framework's default exception handler first,
    # to get the standard error response.
    response = exception_handler(exc, context)

    # Now add the HTTP status code to the response.
    if response is not None:
        response.data['status_code'] = response.status_code

    return response
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

还必须使用设置键在设置中 EXCEPTION_HANDLER 配置异常处理程序。例如:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
  • 1
  • 2
  • 3
'
运行

如果未指定 'EXCEPTION_HANDLER',则设置默认为 REST 框架提供的标准异常处理程序:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}
  • 1
  • 2
  • 3
'
运行

APIException

APIView 类或 @api_view 装饰器内引发的所有异常的基类。

如果要自定义 APIException 异常,需要在 APIException 类中设置 .status_code.default_detaildefault_code 属性。

from rest_framework.exceptions import APIException

class ServiceUnavailable(APIException):
    status_code = 503
    default_detail = 'Service temporarily unavailable, try again later.'
    default_code = 'service_unavailable'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

检查 API 异常

重写异常类之后,如果需要验证自定义的异常是否生效,可以使用如下方法

  • .detail - 返回错误的文本说明。
  • .get_codes() - 返回错误的代码标识符。
  • .get_full_details() - 返回文本描述和代码标识符。
>>> print(exc.detail)
You do not have permission to perform this action.
>>> print(exc.get_codes())
permission_denied
>>> print(exc.get_full_details())
{'message':'You do not have permission to perform this action.','code':'permission_denied'}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在验证错误的情况下,错误详细信息将是一个列表或 词典:

>>> print(exc.detail)
{"name":"This field is required.","age":"A valid integer is required."}
>>> print(exc.get_codes())
{"name":"required","age":"invalid"}
>>> print(exc.get_full_details())
{"name":{"message":"This field is required.","code":"required"},"age":{"message":"A valid integer is required.","code":"invalid"}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

ParseError 解析异常

如果访问 request.data 时请求包含格式不正确的数据,则引发该异常。默认情况下,该异常会导致响应 HTTP 状态码 为 400

底层源码:

class ParseError(APIException):
    status_code = status.HTTP_400_BAD_REQUEST
    default_detail = _('Malformed request.')
    default_code = 'parse_error'
  • 1
  • 2
  • 3
  • 4

AuthenticationFailed 认证失败异常

当传入请求包含不正确的身份验证时,如token鉴权失败,则引发的异常。默认情况下,认证失败响应状态码会返回 401,也有可能导致 403 禁止访问


class AuthenticationFailed(APIException):
    status_code = status.HTTP_401_UNAUTHORIZED
    default_detail = _('Incorrect authentication credentials.')
    default_code = 'authentication_failed'
  • 1
  • 2
  • 3
  • 4
  • 5

NotAuthenticated 未认证

当未经身份验证的请求未通过权限检查时引发。默认情况下,认证失败响应状态码会返回 401,也有可能导致 403 禁止访问

class NotAuthenticated(APIException):
    status_code = status.HTTP_401_UNAUTHORIZED
    default_detail = _('Authentication credentials were not provided.')
    default_code = 'not_authenticated'
  • 1
  • 2
  • 3
  • 4

PermissionDenied 权限拒绝

当经过身份验证的请求未通过权限检查时引发。默认情况下,此异常会导致 HTTP 状态代码为“403 禁止访问”的响应。

class PermissionDenied(APIException):
    status_code = status.HTTP_403_FORBIDDEN
    default_detail = _('You do not have permission to perform this action.')
    default_code = 'permission_denied'

  • 1
  • 2
  • 3
  • 4
  • 5

NotFound

当给定 URL 中不存在资源时引发。此异常等效于标准的 Django Http404 异常。

class MethodNotAllowed(APIException):
    status_code = status.HTTP_405_METHOD_NOT_ALLOWED
    default_detail = _('Method "{method}" not allowed.')
    default_code = 'method_not_allowed'

    def __init__(self, method, detail=None, code=None):
        if detail is None:
            detail = force_str(self.default_detail).format(method=method)
        super().__init__(detail, code)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

MethodNotAllowed

当发生未映射到视图上的处理程序方法的传入请求时引发。此异常会导致 HTTP 状态代码为“405 方法不允许”的响应。

class MethodNotAllowed(APIException):
    status_code = status.HTTP_405_METHOD_NOT_ALLOWED
    default_detail = _('Method "{method}" not allowed.')
    default_code = 'method_not_allowed'

    def __init__(self, method, detail=None, code=None):
        if detail is None:
            detail = force_str(self.default_detail).format(method=method)
        super().__init__(detail, code)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

NotAcceptable

当传入请求发生且标头 Accept 无法由任何可用呈现器满足时引发。
默认情况下,此异常会导致 HTTP 状态代码为“406 不可接受”的响应。

class NotAcceptable(APIException):
    status_code = status.HTTP_406_NOT_ACCEPTABLE
    default_detail = _('Could not satisfy the request Accept header.')
    default_code = 'not_acceptable'

    def __init__(self, detail=None, code=None, available_renderers=None):
        self.available_renderers = available_renderers
        super().__init__(detail, code)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

UnsupportedMediaType

如果没有解析器可以在访问 request.data 时处理请求数据的内容类型,则引发该异常。

默认情况下,此异常会导致响应 HTTP 状态代码“415 不支持的媒体类型”。


class UnsupportedMediaType(APIException):
    status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
    default_detail = _('Unsupported media type "{media_type}" in request.')
    default_code = 'unsupported_media_type'

    def __init__(self, media_type, detail=None, code=None):
        if detail is None:
            detail = force_str(self.default_detail).format(media_type=media_type)
        super().__init__(detail, code)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Throttled

当传入请求未通过限制检查时引发,默认情况下,此异常会导致响应 HTTP 状态代码为“429 请求过多”。

class Throttled(APIException):
    status_code = status.HTTP_429_TOO_MANY_REQUESTS
    default_detail = _('Request was throttled.')
    extra_detail_singular = _('Expected available in {wait} second.')
    extra_detail_plural = _('Expected available in {wait} seconds.')
    default_code = 'throttled'

    def __init__(self, wait=None, detail=None, code=None):
        if detail is None:
            detail = force_str(self.default_detail)
        if wait is not None:
            wait = math.ceil(wait)
            detail = ' '.join((
                detail,
                force_str(ngettext(self.extra_detail_singular.format(wait=wait),
                                   self.extra_detail_plural.format(wait=wait),
                                   wait))))
        self.wait = wait
        super().__init__(detail, code)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

ValidationError

ValidationError 异常与 APIException 其他类略有不同。

  • detail 参数是必传的,并非选填
  • detail 参数可以是错误详细信息的列表或字典,也可以是嵌套的数据结构。通过使用字典,可以在序列化程序的方法中执行对象级 validate() 验证时指定字段级错误。如 raise serializers.ValidationError({'name': 'Please enter a valid name.'})

ValidationError 应用于序列化程序和字段验证,以及 serializer.is_valid 验证程序类。使用关键字 raise_exception` 参数调用时也会引发它:

serializer.is_valid(raise_exception=True)
  • 1

通用视图使用 raise_exception=True 标志,这意味着您可以在 API 中全局覆盖验证错误响应的样式。为此,请使用自定义异常处理程序,如上所述。

默认情况下,此异常会导致响应 HTTP 状态代码为“400 错误请求”。


class ValidationError(APIException):
    status_code = status.HTTP_400_BAD_REQUEST
    default_detail = _('Invalid input.')
    default_code = 'invalid'

    def __init__(self, detail=None, code=None):
        if detail is None:
            detail = self.default_detail
        if code is None:
            code = self.default_code

        # For validation failures, we may collect many errors together,
        # so the details should always be coerced to a list if not already.
        if isinstance(detail, tuple):
            detail = list(detail)
        elif not isinstance(detail, dict) and not isinstance(detail, list):
            detail = [detail]

        self.detail = _get_error_details(detail, code)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

Generic Error Views

Django REST 框架提供了两个错误视图,适用于提供通用的 JSON 服务器错误和错误请求响应。(Django 的默认错误视图提供了 HTML 响应,这可能不适合 仅限 API 的应用程序。)

Django 默认视图文档: Django 的自定义错误视图文档

返回具有状态代码 500application/jsonapplication/json响应。

def server_error(request, *args, **kwargs):
    """
    Generic 500 error handler.
    """
    data = {
        'error': 'Server Error (500)'
    }
    return JsonResponse(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
'
运行

返回具有状态代码 400application/jsonapplication/json响应。

def bad_request(request, exception, *args, **kwargs):
    """
    Generic 400 error handler.
    """
    data = {
        'error': 'Bad Request (400)'
    }
    return JsonResponse(data, status=status.HTTP_400_BAD_REQUEST)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
'
运行
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/845582
推荐阅读
相关标签
  

闽ICP备14008679号