How to apply a filter backend to all fields of all resources in Django Rest Framework?
Asked Answered
T

5

16

I have a lot of resources and i want to apply the DjangoFilterBackend to all of them.

Tried setting in settings.py

'DEFAULT_FILTER_BACKENDS': [
            'rest_framework.filters.DjangoFilterBackend',
        ]

But it didn't work

I tried adding only filter_backends = (filters.DjangoFilterBackend,) to one of my resources and it still didn't work.

Only after I explicitly added filter_fields = ('col1','col2',) it started working with those fields only.

Is there any way I can apply the filter backend to all resources and all fields (same way i do with permissions for example ... ) ?

Thanks.

Typewrite answered 30/11, 2014 at 16:33 Comment(2)
Have you installed django-filter app?Awry
yep. As I wrote - It's working when I explicitly set filterbackend and fields.Typewrite
M
14

Right now you are telling Django REST Framework to use the DjangoFilterBackend for all views, but you are not telling it how the FilterSet should be generated.


django-filter will automatically generate a FilterSet for all of the fields on a model if the fields are set to None. Django REST Framework will automatically generate a FilterSet if filter_fields are not set to None, which means you won't be able to use the default DjangoFilterBackend.

You can create a custom DjangoFilterBackend though, which will automatically generate the FilterSet for all fields on the model.

from rest_framework.filters import DjangoFilterBackend

class AllDjangoFilterBackend(DjangoFilterBackend):
    """
    A filter backend that uses django-filter.
    """

    def get_filter_class(self, view, queryset=None):
        """
        Return the django-filters `FilterSet` used to filter the queryset.
        """
        filter_class = getattr(view, 'filter_class', None)
        filter_fields = getattr(view, 'filter_fields', None)

        if filter_class or filter_fields:
            return super(AllDjangoFilterBackend, self).get_filter_class(self, view, queryset)

        class AutoFilterSet(self.default_filter_set):
            class Meta:
                model = queryset.model
                fields = None

        return AutoFilterSet

This will still use the original filter backend for situations where the view defines a custom filter_class or filter_fields, but it will generate a custom FilterSet for all other situations. Keep in mind that you shouldn't allow fields which aren't returned through the API to be filtered, as you are opening yourself up to future security issues (like people filtering a user list by passwords).

Malebranche answered 30/11, 2014 at 23:25 Comment(4)
Hey @Kevin Brows, This looks like a great solution but it still requires me to set the filter_backend for each resource (I changed the default backend in the settings to app.filters.AllDjangoFilterBackend and it's still not workinging. Only after setting the backend explicitly it worked ... ) any way around that as well ?Typewrite
If you manually override filter_backend on the view, you will have to repeat it again. If you can, remove that from the classMalebranche
@KevinBrown great answer! I have updated it a bit below for use with django-filter==2.1.0Photomural
Should self be a member of super(AllDjangoFilterBackend, self).get_filter_class(self, view, queryset) I think self should not be part of arguments to get_filter_class...Consideration
B
6

Ok I know it was a long time ago, but I was just faced with this question today (2019/11), so I decided to share this way that I think it is a little better:

Just use '__all__' for filter fields

filter_fields = '__all__'
Bowdlerize answered 13/9, 2019 at 0:8 Comment(0)
P
2

Kevin Browns answer is fantastic, however may be slightly out of date now.

Running the AllDjangoFilterBackend filter backend with django-filter==2.1.0 throws the following:

Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' has been deprecated since 0.15.0 and is now disallowed. Add an explicit 'Meta.fields' or 'Meta.exclude' to the AutoFilterSet class.

It seems that simply replacing fields = None with exclude = '' is sufficient to use all fields. Full code below:

from django_filters.rest_framework import DjangoFilterBackend


class AllDjangoFilterBackend(DjangoFilterBackend):
    '''
    Filters DRF views by any of the objects properties.
    '''

    def get_filter_class(self, view, queryset=None):
        '''
        Return the django-filters `FilterSet` used to filter the queryset.
        '''
        filter_class = getattr(view, 'filter_class', None)
        filter_fields = getattr(view, 'filter_fields', None)

        if filter_class or filter_fields:
            return super().get_filter_class(self, view, queryset)

        class AutoFilterSet(self.default_filter_set):
            class Meta:
                exclude = ''
                model = queryset.model

        return AutoFilterSet

Save this to your_project/your_app/filters.py (or similar) and then ensure your settings file contains:

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': (
        'your_project.your_app.filters.AllDjangoFilterBackend'
    ),
}
Photomural answered 1/7, 2019 at 16:16 Comment(0)
W
1

I found this to work instead for drf 3.14.0 and django-filter 23.3

Updated from Stuart Buckingham's answer

from django_filters.rest_framework import DjangoFilterBackend

class AllDjangoFilterBackend(DjangoFilterBackend):
    """
    Filters DRF views by any of the objects properties.
    """

    def get_filterset_class(self, view, queryset=None):
        """
        Return the `FilterSet` class used to filter the queryset.
        """
        filterset_class = getattr(view, "filterset_class", None)
        filterset_fields = getattr(view, "filterset_fields", None)

        if filterset_fields or filterset_class:
            return super().get_filterset_class(view, queryset)

        class AutoFilterSet(self.filterset_base):
            class Meta:
                model = queryset.model
                fields = [field.name for field in queryset.model._meta.get_fields()]

        return AutoFilterSet

   
Wills answered 1/12, 2023 at 10:19 Comment(0)
T
0

Updated version of Stuart Buckingham comment for django-rest-framework 3.13.1:

from django_filters.rest_framework import DjangoFilterBackend


class AllDjangoFilterBackend(DjangoFilterBackend):
    """
    A filter backend that uses django-filter.
    """

    def get_filterset_class(self, view, queryset=None):
        '''
        Return the django-filters `FilterSet` used to filter the queryset.
        '''
        filter_class = getattr(view, 'filter_class', None)
        filter_fields = getattr(view, 'filter_fields', None)

        if filter_class or filter_fields:
            return super().get_filter_class(self, view, queryset)

        class AutoFilterSet(self.filterset_base):
            class Meta:
                fields = "__all__"
                model = queryset.model

        return AutoFilterSet
Truckload answered 24/8, 2022 at 9:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.