Package: djangorestframework-filters
django-rest-framework-filtersis an extension toDjango REST frameworkandDjango filterthat makes it easy to filter across relationships. Historically, this extension also provided a
number of additional features and fixes, however the number of features has shrunk as they are
merged back intodjango-filter.Usingdjango-rest-framework-filters, we can easily do stuff like:/api/article?author__first_name__icontains=john
/api/article?is_published!=trueTable of ContentsFeaturesRequirementsInstallationUsageFiltering across relationshipsSupportingFilter.methodAutomatic Filter Negation/ExclusionAllowing any lookup type on a fieldCan I mix and matchdjango-filteranddjango-rest-framework-filters?Caveats & LimitationsMigrating to 1.0RelatedFilter.querysetnow requiredget_filters()renamed toexpand_filters()PublishingLicenseFeaturesEasy filtering across relationshipsSupport for method filtering across relationshipsAutomatic filter negation with a simpleparam!=valuesyntaxBackend caching to increase performanceRequirementsPython: 2.7 or 3.3+Django: 1.8, 1.9, 1.10, 1.11DRF: 3.5, 3.6Installation$pipinstalldjangorestframework-filtersUsageUpgrading fromdjango-filtertodjango-rest-framework-filtersis straightforward:Import fromrest_framework_filtersinstead of fromdjango_filtersUse therest_framework_filtersbackend instead of the one provided bydjango_filter.# django-filterfromdjango_filters.rest_frameworkimportFilterSet,filtersclassProductFilter(FilterSet):manufacturer=filters.ModelChoiceFilter(queryset=Manufacturer.objects.all())...# django-rest-framework-filtersimportrest_framework_filtersasfiltersclassProductFilter(filters.FilterSet):manufacturer=filters.ModelChoiceFilter(queryset=Manufacturer.objects.all())...To use the django-rest-framework-filters backend, add the following to your settings:REST_FRAMEWORK={'DEFAULT_FILTER_BACKENDS':('rest_framework_filters.backends.DjangoFilterBackend',...),...Once configured, you can continue to use all of the filters found indjango-filter.Filtering across relationshipsYou can easily traverse multiple relationships when filtering by usingRelatedFilter:fromrest_frameworkimportviewsetsimportrest_framework_filtersasfiltersclassManagerFilter(filters.FilterSet):classMeta:model=Managerfields={'name':['exact','in','startswith']}classDepartmentFilter(filters.FilterSet):manager=filters.RelatedFilter(ManagerFilter,name='manager',queryset=Manager.objects.all())classMeta:model=Departmentfields={'name':['exact','in','startswith']}classCompanyFilter(filters.FilterSet):department=filters.RelatedFilter(DepartmentFilter,name='department',queryset=Department.objects.all())classMeta:model=Companyfields={'name':['exact','in','startswith']}# company viewsetclassCompanyView(viewsets.ModelViewSet):filter_class=CompanyFilter...Example filter calls:/api/companies?department__name=Accounting
/api/companies?department__manager__name__startswith=BobquerysetcallablesSinceRelatedFilteris a subclass ofModelChoiceFilter, thequerysetargument supports callable behavior.
In the following example, the set of departments is restricted to those in the user’s company.defdepartments(request):company=request.user.companyreturncompany.department_set.all()classEmployeeFilter(filters.FilterSet):department=filters.RelatedFilter(filterset=DepartmentFilter,queryset=departments)...Recursive relationshipsRecursive relations are also supported. It may be necessary to specify the full module path.classPersonFilter(filters.FilterSet):name=filters.AllLookupsFilter(name='name')best_friend=filters.RelatedFilter('people.views.PersonFilter',name='best_friend',queryset=Person.objects.all())classMeta:model=PersonSupportingFilter.methoddjango_filters.MethodFilterhas been deprecated and reimplemented as themethodargument
to all filter classes. It incorporates some of the implementation details of the oldrest_framework_filters.MethodFilter, but requires less boilerplate and is simpler to write.It is no longer necessary to perform empty/null value checking.You may use any filter class (CharFilter,BooleanFilter, etc…) which will
validate input values for you.The argument signature has changed from(name, qs, value)to(qs, name, value).classPostFilter(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')classMeta:model=Postfields=['title','content']deffilter_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 BooleanFilterisnull=notvaluelookup_expr=LOOKUP_SEP.join([name,'isnull'])returnqs.filter(**{lookup_expr:isnull})classAuthorFilter(filters.FilterSet):posts=filters.RelatedFilter('PostFilter',queryset=Post.objects.all())classMeta:model=Authorfields=['name']The above would enable the following filter calls:/api/posts?is_published=true
/api/authors?posts__is_published=trueIn the first API call, the filter method receives a queryset of posts. In the second,
it receives a queryset of users. The filter method in the example modifies the lookup
name to work across the relationship, allowing you to find published posts, or authors
who have published posts.Automatic Filter Negation/ExclusionFilterSets support automatic exclusion using a simpleparam!=valuesyntax. This syntax
internally sets theexcludeproperty on the filter./api/page?title!=The%20ParkThis syntax supports regular filtering combined with exclusion filtering. For example, the
following would search for all articles containing “Hello” in the title, while excluding
those containing “World”./api/articles?title__contains=Hello&title__contains!=WorldNote that most filters only accept a single query parameter. In the above,title__containsandtitle__contains!are interpreted as two separate query parameters. The following would
probably be invalid, although it depends on the specifics of the individual filter class:/api/articles?title__contains=Hello&title__contains!=World&title_contains!=FriendAllowing any lookup type on a fieldIf you need to enable several lookups for a field, django-filter provides the dict-syntax forMeta.fields.classProductFilter(filters.FilterSet):classMeta:model=Productfields={'price':['exact','lt','gt',...],}django-rest-framework-filtersalso allows you to enable all possible lookups for any field.
This can be achieved through the use ofAllLookupsFilteror using the'__all__'value in
theMeta.fieldsdict-style syntax. Generated filters (Meta.fields,AllLookupsFilter)
will never override your declared filters.Note that using all lookups comes with the same admonitions as enabling'__all__'fields in
django forms (docs). Exposing all lookups may allow users to construct queries that
inadvertently leak data. Use this feature responsibly.classProductFilter(filters.FilterSet):# Not overridden by `__all__`price__gt=filters.NumberFilter(name='price',lookup_expr='gt',label='Minimum price')classMeta:model=Productfields={'price':'__all__',}# orclassProductFilter(filters.FilterSet):price=filters.AllLookupsFilter()# Not overridden by `AllLookupsFilter`price__gt=filters.NumberFilter(name='price',lookup_expr='gt',label='Minimum price')classMeta:model=ProductYou cannot combineAllLookupsFilterwithRelatedFilteras the filter names would clash.classProductFilter(filters.FilterSet):manufacturer=filters.RelatedFilter('ManufacturerFilter',queryset=Manufacturer.objects.all())manufacturer=filters.AllLookupsFilter()To work around this, you have the following options:classProductFilter(filters.FilterSet):manufacturer=filters.RelatedFilter('ManufacturerFilter',queryset=Manufacturer.objects.all())classMeta:model=Productfields={'manufacturer':'__all__',}# orclassProductFilter(filters.FilterSet):manufacturer=filters.RelatedFilter('ManufacturerFilter',queryset=Manufacturer.objects.all(),lookups='__all__')# `lookups` also accepts a listclassMeta:model=ProductCan I mix and matchdjango-filteranddjango-rest-framework-filters?Yes you can.django-rest-framework-filtersis simply an extension ofdjango-filter. Note
thatRelatedFilterand otherdjango-rest-framework-filtersfeatures are designed to work
withrest_framework_filters.FilterSetand will not function on adjango_filters.FilterSet.
However, the targetRelatedFilter.filtersetmay point to aFilterSetfrom either package,
and bothFilterSetimplementations are compatible with the other’s DRF backend.# validclassVanillaFilter(django_filters.FilterSet):...classDRFFilter(rest_framework_filters.FilterSet):vanilla=rest_framework_filters.RelatedFilter(filterset=VanillaFilter,queryset=...)# invalidclassDRFFilter(rest_framework_filters.FilterSet):...classVanillaFilter(django_filters.FilterSet):drf=rest_framework_filters.RelatedFilter(filterset=DRFFilter,queryset=...)Caveats & LimitationsMultiWidgetis incompatibledjangorestframework-filters is not compatible with form widgets that parse query names that differ from the filter’s
attribute name. Although this only practically applies toMultiWidget, it is a general limitation that affects
custom widgets that also have this behavior. Affected filters includeRangeFilter,DateTimeFromToRangeFilter,DateFromToRangeFilter,TimeRangeFilter, andNumericRangeFilter.To demonstrate the incompatiblity, take the following filterset:classPostFilter(FilterSet):publish_date=filters.DateFromToRangeFilter()The above filter allows users to perform arangequery on the publication date. The filter class internally usesMultiWidgetto separately parse the upper and lower bound values. The incompatibility lies in thatMultiWidgetappends an index to its inner widget names. Instead of parsingpublish_date, it expectspublish_date_0andpublish_date_1. It is possible to fix this by including the attribute name in the querystring, although this is
not recommended.?publish_date_0=2016-01-01&publish_date_1=2016-02-01&publish_date=MultiWidgetis also discouraged since:core-apifield introspection fails for similar reasons_0and_1are less API-friendly than_minand_maxThe recommended solutions are to either:Create separate filters for each of the sub-widgets (such aspublish_date_minandpublish_date_max).Use a CSV-based filter such as those derived fromBaseCSVFilter/BaseInFilter/BaseRangeFilter. eg,?publish_date__range=2016-01-01,2016-02-01Migrating to 1.0RelatedFilter.querysetnow requiredThe related filterset’s model is no longer used to provide the default value forRelatedFilter.queryset. This
change reduces the chance of unintentionally exposing data in the rendered filter forms. You must now explicitly
provide thequerysetargument, or override theget_queryset()method (seequeryset callables).get_filters()renamed toexpand_filters()django-filter has add aget_filters()classmethod to it’s API, so this method has been renamed.Publishing$pipinstall-Utwinesetuptoolswheel$rm-rfdist/build/$pythonsetup.pysdistbdist_wheel$twineuploaddist/*LicenseCopyright (c) 2013-2015 Philip Neustrom <philipn@gmail.com>,
2016-2017 Ryan P Kilby <rpkilby@ncsu.edu>Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.