跳转到主要内容

为Django REST Framework提供更好的过滤功能

项目描述

https://travis-ci.org/philipn/django-rest-framework-filters.png?branch=master https://codecov.io/gh/philipn/django-rest-framework-filters/branch/master/graph/badge.svg https://img.shields.io/pypi/v/djangorestframework-filters.svg

django-rest-framework-filters 是Django REST framework和Django filter的扩展,它使得跨关系过滤变得容易。历史上,此扩展还提供了一些额外的功能和修复,但它们已被合并回 django-filter,因此功能数量有所减少。

使用 django-rest-framework-filters,我们可以轻松完成如下操作

/api/article?author__first_name__icontains=john
/api/article?is_published!=true

功能

  • 跨关系的简单过滤

  • 支持跨关系的方法过滤

  • 使用简单的param!=value语法自动否定过滤

  • 后端缓存以提高性能

要求

  • Python: 2.7或3.3+

  • Django: 1.8, 1.9, 1.10, 1.11

  • DRF: 3.5, 3.6

安装

$ pip install djangorestframework-filters

用法

django-filter升级到django-rest-framework-filters很简单

  • rest_framework_filters导入而不是从django_filters导入

  • 使用rest_framework_filters后端而不是django_filter提供的一个后端。

# django-filter
from django_filters.rest_framework import FilterSet, filters

class ProductFilter(FilterSet):
    manufacturer = filters.ModelChoiceFilter(queryset=Manufacturer.objects.all())
    ...


# django-rest-framework-filters
import rest_framework_filters as filters

class ProductFilter(filters.FilterSet):
    manufacturer = filters.ModelChoiceFilter(queryset=Manufacturer.objects.all())
    ...

要使用django-rest-framework-filters后端,请在您的设置中添加以下内容

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': (
        'rest_framework_filters.backends.DjangoFilterBackend', ...
    ),
    ...

配置完成后,您可以使用在django-filter中找到的所有过滤器。

跨关系过滤

在过滤时,您可以使用RelatedFilter轻松遍历多个关系

from rest_framework import viewsets
import rest_framework_filters as filters


class ManagerFilter(filters.FilterSet):
    class Meta:
        model = Manager
        fields = {'name': ['exact', 'in', 'startswith']}


class DepartmentFilter(filters.FilterSet):
    manager = filters.RelatedFilter(ManagerFilter, name='manager', queryset=Manager.objects.all())

    class Meta:
        model = Department
        fields = {'name': ['exact', 'in', 'startswith']}


class CompanyFilter(filters.FilterSet):
    department = filters.RelatedFilter(DepartmentFilter, name='department', queryset=Department.objects.all())

    class Meta:
        model = Company
        fields = {'name': ['exact', 'in', 'startswith']}


# company viewset
class CompanyView(viewsets.ModelViewSet):
    filter_class = CompanyFilter
    ...

示例过滤调用

/api/companies?department__name=Accounting
/api/companies?department__manager__name__startswith=Bob

queryset可调用对象

由于RelatedFilterModelChoiceFilter的子类,因此queryset参数支持可调用行为。在以下示例中,部门集合被限制为用户的公司的那些部门。

def departments(request):
    company = request.user.company
    return company.department_set.all()

class EmployeeFilter(filters.FilterSet):
    department = filters.RelatedFilter(filterset=DepartmentFilter, queryset=departments)
    ...

递归关系

也支持递归关系。可能需要指定完整的模块路径。

class PersonFilter(filters.FilterSet):
    name = filters.AllLookupsFilter(name='name')
    best_friend = filters.RelatedFilter('people.views.PersonFilter', name='best_friend', queryset=Person.objects.all())

    class Meta:
        model = Person

支持 Filter.method

django_filters.MethodFilter已被弃用,并作为所有过滤器类的method参数重新实现。它包含了旧rest_framework_filters.MethodFilter的一些实现细节,但需要更少的样板代码,并且更容易编写。

  • 不再需要执行空/空值检查。

  • 您可以使用任何过滤器类(CharFilterBooleanFilter等),它将为您验证输入值。

  • 参数签名已从(name, qs, value)更改为(qs, name, value)

class PostFilter(filters.FilterSet):
    # Note the use of BooleanFilter, the original model field's name, and the method argument.
    is_published = filters.BooleanFilter(name='date_published', method='filter_is_published')

    class Meta:
        model = Post
        fields = ['title', 'content']

    def filter_is_published(self, qs, name, value):
        """
        `is_published` is based on the `date_published` model field.
        If the publishing date is null, then the post is not published.
        """
        # incoming value is normalized as a boolean by BooleanFilter
        isnull = not value
        lookup_expr = LOOKUP_SEP.join([name, 'isnull'])

        return qs.filter(**{lookup_expr: isnull})

class AuthorFilter(filters.FilterSet):
    posts = filters.RelatedFilter('PostFilter', queryset=Post.objects.all())

    class Meta:
        model = Author
        fields = ['name']

以上将启用以下过滤调用

/api/posts?is_published=true
/api/authors?posts__is_published=true

在第一个API调用中,过滤方法接收帖子查询集。在第二个中,它接收用户查询集。示例中的过滤方法修改了查找名称以跨关系工作,允许您查找已发布的帖子或已发布帖子的作者。

自动过滤否定/排除

FilterSets支持使用简单的param!=value语法进行自动排除。此语法内部将过滤器上的exclude属性设置为。

/api/page?title!=The%20Park

此语法支持常规过滤与排除过滤的组合。例如,以下将搜索标题中包含“Hello”的所有文章,同时排除包含“World”的文章。

/api/articles?title__contains=Hello&title__contains!=World

请注意,大多数过滤器只接受单个查询参数。在上面的例子中,title__containstitle__contains!被解释为两个不同的查询参数。以下可能无效,尽管这取决于单个过滤器类的具体细节

/api/articles?title__contains=Hello&title__contains!=World&title_contains!=Friend

允许字段上的任何查找类型

如果您需要为字段启用多个查找,django-filter为Meta.fields提供dict语法。

class ProductFilter(filters.FilterSet):
    class Meta:
        model = Product
        fields = {
            'price': ['exact', 'lt', 'gt', ...],
        }

django-rest-framework-filters 允许您为任何字段启用所有可能的查找。这可以通过使用 AllLookupsFilter 或在 Meta.fields 字典式语法中使用 '__all__' 值来实现。生成的过滤器(Meta.fieldsAllLookupsFilter)永远不会覆盖您声明的过滤器。

请注意,使用所有查找与在 django 表单中启用 '__all__' 字段具有相同的警告(文档)。暴露所有查找可能会允许用户构建意外泄露数据的查询。请负责任地使用此功能。

class ProductFilter(filters.FilterSet):
    # Not overridden by `__all__`
    price__gt = filters.NumberFilter(name='price', lookup_expr='gt', label='Minimum price')

    class Meta:
        model = Product
        fields = {
            'price': '__all__',
        }

# or

class ProductFilter(filters.FilterSet):
    price = filters.AllLookupsFilter()

    # Not overridden by `AllLookupsFilter`
    price__gt = filters.NumberFilter(name='price', lookup_expr='gt', label='Minimum price')

    class Meta:
        model = Product

您不能将 AllLookupsFilterRelatedFilter 结合使用,因为过滤器名称将冲突。

class ProductFilter(filters.FilterSet):
    manufacturer = filters.RelatedFilter('ManufacturerFilter', queryset=Manufacturer.objects.all())
    manufacturer = filters.AllLookupsFilter()

为了解决这个问题,您有以下选项

class ProductFilter(filters.FilterSet):
    manufacturer = filters.RelatedFilter('ManufacturerFilter', queryset=Manufacturer.objects.all())

    class Meta:
        model = Product
        fields = {
            'manufacturer': '__all__',
        }

# or

class ProductFilter(filters.FilterSet):
    manufacturer = filters.RelatedFilter('ManufacturerFilter', queryset=Manufacturer.objects.all(), lookups='__all__')  # `lookups` also accepts a list

    class Meta:
        model = Product

我可以混合使用 django-filterdjango-rest-framework-filters 吗?

是的,您可以。 django-rest-framework-filters 仅仅是 django-filter 的扩展。请注意,RelatedFilter 和其他 django-rest-framework-filters 功能是为与 rest_framework_filters.FilterSet 一起使用而设计的,不会在 django_filters.FilterSet 上运行。然而,目标 RelatedFilter.filterset 可能指向来自任一包的 FilterSet,并且这两个 FilterSet 实现都与对方的 DRF 后端兼容。

# valid
class VanillaFilter(django_filters.FilterSet):
    ...

class DRFFilter(rest_framework_filters.FilterSet):
    vanilla = rest_framework_filters.RelatedFilter(filterset=VanillaFilter, queryset=...)


# invalid
class DRFFilter(rest_framework_filters.FilterSet):
    ...

class VanillaFilter(django_filters.FilterSet):
    drf = rest_framework_filters.RelatedFilter(filterset=DRFFilter, queryset=...)

注意事项 & 限制

MultiWidget 不兼容

djangorestframework-filters 与解析查询名称与过滤器属性名称不同的表单小部件不兼容。尽管这仅实际适用于 MultiWidget,但它是一个影响具有此行为的自定义小部件的一般限制。受影响的过滤器包括 RangeFilterDateTimeFromToRangeFilterDateFromToRangeFilterTimeRangeFilterNumericRangeFilter

为了展示这种不兼容性,考虑以下过滤器集

class PostFilter(FilterSet):
    publish_date = filters.DateFromToRangeFilter()

上述过滤器允许用户对出版日期执行 range 查询。过滤器类内部使用 MultiWidget 来分别解析上界和下界值。不兼容之处在于 MultiWidget 将索引追加到其内部小部件名称。它期望解析 publish_date 而不是 publish_date_0publish_date_1。可以通过在查询字符串中包含属性名称来修复此问题,尽管不推荐这样做。

?publish_date_0=2016-01-01&publish_date_1=2016-02-01&publish_date=

MultiWidget 也被不推荐使用,因为

  • core-api 字段自省失败出于类似原因

  • _0_1 比不友好的 _min_max

建议的解决方案是

  • 为每个子小部件创建单独的过滤器(例如 publish_date_minpublish_date_max)。

  • 使用基于 CSV 的过滤器,例如从 BaseCSVFilter/BaseInFilter/BaseRangeFilter 衍生出的过滤器。例如,

?publish_date__range=2016-01-01,2016-02-01

迁移到1.0版本

RelatedFilter.queryset现在必须使用

相关过滤器集的模型不再用于为 RelatedFilter.queryset 提供默认值。此更改减少了在渲染的过滤器表单中意外暴露数据的机会。您现在必须显式提供 queryset 参数,或重写 get_queryset() 方法(见 查询集调用)。

get_filters()已重命名为expand_filters()

django-filter 为其 API 添加了一个 get_filters() 类方法,因此该方法已被重命名。

发布

$ pip install -U twine setuptools wheel
$ rm -rf dist/ build/
$ python setup.py sdist bdist_wheel
$ twine upload dist/*

许可

版权所有(c)2013-2015菲利普·纽斯特罗姆 <philipn@gmail.com>,2016-2017瑞安·P·基尔比 <rpkilby@ncsu.edu>

特此授予任何人免费获得本软件及其相关文档文件(以下简称“软件”)的副本的权利,可以在不受限制的情况下处理软件,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件副本,并允许向软件提供副本的个人这样做,但必须遵守以下条件

上述版权声明和本许可声明应包含在软件的所有副本或实质性部分中。

软件按“原样”提供,不提供任何形式的保证,无论是明示的还是隐含的,包括但不限于适销性、适用于特定目的和未经授权的保证。在任何情况下,作者或版权所有者不对任何索赔、损害或其他责任承担责任,无论是因为合同、侵权或其他原因而引起的,无论是在软件或软件的使用或其他情况下。

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源代码发行版

djangorestframework-filters-0.11.1.tar.gz (13.7 kB 查看哈希值)

上传时间 源代码

构建发行版

djangorestframework_filters-0.11.1-py2.py3-none-any.whl (12.7 kB 查看哈希值)

上传时间 Python 2 Python 3

由以下机构支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面