跳至主要內容

Rest-framework专栏讲解(十九):Filtering

Mr.暴走の海鸽约 3575 字大约 12 分钟

Rest-framework专栏讲解(十九):Filtering

目录


官方原文链接open in new window

过滤open in new window

REST framework 的通用列表视图的默认行为是从模型管理器返回整个查询集。通常你会希望 API 限制查询集返回的条目。

筛选 GenericAPIView 子类的查询集的最简单方法是重写 .get_queryset() 方法。

重写此方法允许你以多种不同方式自定义视图返回的查询集。

根据当前用户进行过滤open in new window

你可能需要过滤查询集,以确保只返回与当前通过身份验证的用户发出的请求相关的结果。

你可以基于 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)

根据 URL 进行过滤open in new window

另一种过滤方式可能涉及基于 URL 的某个部分限制查询集。

例如,如果你的 URL 配置包含这样的条目:

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

然后,你可以编写一个视图,返回由 URL 的用户名部分过滤的 purchase 查询集:

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)

根据查询参数进行过滤open in new window

过滤初始查询集的最后一个例子是根据 url 中的查询参数确定初始查询集。

我们可以覆盖 .get_queryset() 来处理诸如 http://example.com/api/purchases?username=denvercoder9 的URL,并且只有在 URL 中包含 username 参数时才过滤查询集:

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', None)
        if username is not None:
            queryset = queryset.filter(purchaser__username=username)
        return queryset

通用过滤器open in new window

除了能够覆盖默认的查询集外,REST framework 还包括对通用过滤后端的支持,使你可以轻松构建复杂的搜索和过滤器。

通用过滤器也可以在可浏览的 API 和管理 API 中将自己渲染为 HTML 控件。

设置过滤器后端open in new window

可以使用 DEFAULT_FILTER_BACKENDS setting 全局设置默认的过滤器后端。例如。

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

你还可以使用基于 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]

过滤和对象查找open in new window

请注意,如果为一个视图配置了一个过滤器后端,那么除了用于筛选列表视图之外,它还将用于筛选返回单个对象的查询集。

例如,根据前面的示例以及 ID 为 4675 的产品,以下 URL 将返回相应的对象,或返回 404 响应,具体取决于给定产品实例是否满足过滤条件:

http://example.com/api/products/4675/?category=clothing&max_price=10.00

覆盖初始查询集open in new window

请注意,你可以同时重写的 .get_queryset() 和通用过滤,并且所有内容都将按预期工作。例如,如果产品与用户具有多对多关系,则可能需要编写一个如下所示的视图:

class PurchasedProductsList(generics.ListAPIView):
    """
    Return a list of all the products that the authenticated
    user has ever purchased, with optional filtering.
    """
    model = Product
    serializer_class = ProductSerializer
    filterset_class = ProductFilter

    def get_queryset(self):
        user = self.request.user
        return user.purchase_set.all()

过滤器API 参考open in new window

DjangoFilterBackend

django-filter 库包含一个 DjangoFilterBackend 类,它支持 REST framework 对字段过滤进行高度定制。

要使用 DjangoFilterBackend,首先安装 django-filter。然后将 django_filters 添加到 Django 的 INSTALLED_APPS

python -m pip install django-filter

然后添加 'django_filters' 到 Django 的 INSTALLED_APPS

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

你现在应该将过滤器后端添加到设置中:

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

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

from django_filters.rest_framework import DjangoFilterBackend

class UserListView(generics.ListAPIView):
    ...
    filter_backends = [DjangoFilterBackend]

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

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

这将自动为给定字段创建一个 FilterSet 类,并允许你发出如下请求:

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

对于更高级的过滤要求,你应该在视图上在指定 FilterSet 类。你可以在 django-filter 文档open in new window中阅读有关 FilterSet 的更多信息。还建议你阅读 DRF integrationopen in new window

SearchFilter

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

在使用时,可浏览的 API 将包含一个 SearchFilter 控件:

SearchFilter 类将仅在视图具有 search_fields 属性集的情况下应用。search_fields 属性应该是模型上文本类型字段的名称列表,例如 CharFieldTextField

from rest_framework import filters

class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = [filters.SearchFilter]
    search_fields = ['username', 'email']

这将允许客户端通过查询来过滤列表中的项目,例如:

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

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

search_fields = ['username', 'email', 'profile__profession']

对于 JSONFieldHStoreField 字段, 您可以使用相同的双下划线符号根据数据结构内的嵌套值进行过滤(就是高级):

search_fields = ['data__breed', 'data__owner__other_pets__0__name']

默认情况下,搜索将使用不区分大小写的部分匹配。搜索参数可能包含多个搜索词,它们应该是空格和(或)逗号分隔的。如果使用多个搜索条件,则只有在所有提供的条件匹配的情况下,对象才会返回到列表中。

搜索行为可以通过将各种字符预先添加到 search_fields 来限制。

  • ^:匹配起始部分。
  • =:完全匹配。
  • @:全文搜索。(目前只支持 Django 的 MySQL 后端。)
  • $:正则匹配。

例如:

search_fields = ['=username', '=email']

🐍默认情况下,搜索参数被命名为 search ,但这可能会被 SEARCH_PARAM 配置覆盖。

要根据请求内容动态更改搜索字段, 可以对进行 SearchFilter 子类化并覆盖该 get_search_fields() 函数, 例如以下子类仅在查询参数 title_only 在请求中时才搜索 title

from rest_framework import filters

class CustomSearchFilter(filters.SearchFilter):
    def get_search_fields(self, view, request):
        if request.query_params.get('title_only'):
            return ['title']
        return super(CustomSearchFilter, self).get_search_fields(view, request)

有关更多详细信息,请参阅 Django 文档open in new window

OrderingFilteropen in new window

OrderingFilter 类支持简单查询参数控制结果的排序。

默认情况下,查询参数被命名为 ordering,但这可能会被 ORDERING_PARAM 配置覆盖。

例如,要通过 username 对用户排序:

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

客户端也可以通过在字段名称前添加 - 来指定相反的顺序, 如下所示:

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

也可以指定多个排序:

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

指定可以根据哪些字段进行排序open in new window

建议你明确指定 API 应该允许在排序过滤器中使用哪些字段。你可以通过在视图上设置一个 ordering_fields 属性来完成此操作,如下所示:

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

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

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

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

class BookingsListView(generics.ListAPIView):
    queryset = Booking.objects.all()
    serializer_class = BookingSerializer
    filter_backends = [filters.OrderingFilter]
    ordering_fields = '__all__'

指定默认顺序open in new window

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

通常情况下,你应该通过在初始查询集上设置 order_by 来控制此操作,但是通过在视图上使用 ordering 参数,你可以指定排序方式,然后可以将其作为上下文自动传递到渲染的模板。这可以自动渲染列标题,如果它们用于排序结果。

class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = [filters.OrderingFilter]
    ordering_fields = ['username', 'email']
    ordering = ['username']

ordering 属性可以是一个字符串或者字符串列表(元组)。

DjangoObjectPermissionsFilteropen in new window

DjangoObjectPermissionsFilter 旨在与 django-guardianopen in new window 软件包一起使用,添加了自定义 'view' 的权限。过滤器将确保查询集仅返回用户具有适当查看权限的对象。

如果你使用的是 DjangoObjectPermissionsFilter,那么你可能还需要添加适当的对象权限类,以确保用户只有在具有适当对象权限的情况下才能对实例进行操作。做到这一点的最简单方法是继承 DjangoObjectPermissions 并为 perms_map 属性添加 'view' 权限。

使用 DjangoObjectPermissionsFilterDjangoObjectPermissions 的完整示例可能如下所示。

permissions.pyopen in new window:

class CustomObjectPermissions(permissions.DjangoObjectPermissions):
    """
    Similar to `DjangoObjectPermissions`, but adding 'view' permissions.
    """
    perms_map = {
        'GET': ['%(app_label)s.view_%(model_name)s'],
        'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
        'HEAD': ['%(app_label)s.view_%(model_name)s'],
        'POST': ['%(app_label)s.add_%(model_name)s'],
        'PUT': ['%(app_label)s.change_%(model_name)s'],
        'PATCH': ['%(app_label)s.change_%(model_name)s'],
        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
    }

views.pyopen in new window:

class EventViewSet(viewsets.ModelViewSet):
    """
    Viewset that only lists events if user has 'view' permissions, and only
    allows operations on individual events if user has appropriate 'view', 'add',
    'change' or 'delete' permissions.
    """
    queryset = Event.objects.all()
    serializer_class = EventSerializer
    filter_backends = (filters.DjangoObjectPermissionsFilter,)
    permission_classes = (myapp.permissions.CustomObjectPermissions,)

自定义通用过滤器open in new window

你还可以提供自己的通用过滤器后端,或者编写一个可供其他开发人员使用的可安装应用程序。

为此,请继承 BaseFilterBackend,并覆盖 .filter_queryset(self, request, queryset, view) 方法。该方法应该返回一个新的,过滤的查询集。

除了允许客户端执行搜索和过滤外,通用过滤器后端可用于限制哪些对象应该对给定的请求或用户可见。

举个栗子open in new window

你可能需要限制用户只能看到他们创建的对象。

class IsOwnerFilterBackend(filters.BaseFilterBackend):
    """
    Filter that only allows users to see their own objects.
    """
    def filter_queryset(self, request, queryset, view):
        return queryset.filter(owner=request.user)

自定义接口open in new window

通用过滤器也可以在可浏览的 API 中渲染接口。为此,你应该实现一个 to_html() 方法,该方法返回过滤器的渲染 HTML 表示。此方法应具有以下签名:

to_html(self, request, queryset, view)

该方法应该返回一个渲染的 HTML 字符串。

Pagination & schemasopen in new window

通过实现 get_schema_fields() 方法,你还可以使过滤器控件可用于 REST framework 提供的模式自动生成。此方法应具有以下签名:

get_schema_fields(self, view)

该方法应该返回一个 coreapi.Field 实例列表。

第三方库

Django REST框架过滤器软件包

Django 框架过滤器open in new window封装与 DjangoFilterBackend 类一起工作, 并允许您轻松地在跨关系的创建过滤器, 或在指定字段创建多个过滤器查找类型。

Django REST框架全字搜索过滤器

djangorestframework-word-filteropen in new window作为 filter.SearchFilter 替代品, 它将在文本中搜索完整的单词或完全匹配。

Django URL过滤器

django-url-filteropen in new window 提供了一种通过友好的 url 过滤数据的安全方法, 它的工作原理与 DRF 序列化程序和字段非常相似, 在某种意义上它们可以嵌套, 但它们被称为 filtersetsfilters, 这提供了过滤相关数据的简单方法, 而且这个库也是通用的, 所以它可以用来过滤其他数据源, 而不仅仅是 Django QuerySet

drf-url-filters

DRF-URL-Filtersopen in new window 是一个简单的 Django 应用程序, 它以干净、简单和可配置的方式在 drfmodelviewsetQueryset 上应用过滤器, 它还支持对传入查询参数的验证, 一个漂亮的 python 包 Voluptuous 用于对传入的查询参数进行验证, 关于 Voluptuous 的最好的部分是您可以根据查询参数要求定义自己的验证。