Pass additional attribute to django-filter

946 Views Asked by At

I'm using django-filter together with DRF. I have a favourite-model, which is linked to several other models through a GenericRelation. To filter for entries which have a favourite-flag, I've created a custom FavouriteFilter, which I add to the respective model. I would like to query for the content_type_id of the respective model in order to limit the results from Favourite. However, I don't know how I can pass down the model to the filter-method in the FavouriteFilter.

Here's a code snippet to illustrate the issue:

class ProjectFilter(BaseFilter):

    favourite_only = FavouriteFilter()


class FavouriteFilter(django_filters.BooleanFilter):
    """
    A custom filter which returns a users favourites of an element
    """

    def __init__(self, *args, **kwargs):
        # gettext_lazy breaks the OpenAPI generation => use gettext instead
        kwargs['label'] = gettext("My favourites")
        super(FavouriteFilter, self).__init__(*args, **kwargs)

    def filter(self, qs, value):
        if value == True:
            user = get_current_user()
            content_type = ContentType.objects.get_for_model(<model>)
            return qs.filter(pk__in=Favourite.objects
                             .filter(owner_id=user)
                             .filter(content_type_id=content_type)
                             .values_list('object_id', flat=True)
                             )
        else:
            return qs

In this example, the <model>-attribute is missing. How can I pass down this information from Project to the filter?

1

There are 1 best solutions below

0
On BEST ANSWER

Keyword arguments can be passed down to the filter, but they need to be removed from the kwarg-dict before the super()-method is called. Otherwise they get passed on to the superclass, the superclass's __init__()-method doesn't know the keyword and a TypeError is thrown:

TypeError: __init__() got an unexpected keyword argument 'model'

In the example above, the superclass is django_filters.BooleanFilter respectively django_filters.Filter.

Using the dict.pop()-method, the keyword is removed from the kwargs-dictionary and at the same time we can save it for further use. Since content_type never changes after initialization, it can already be set in __init__().

Here's a working example of the code above, where Project is the django-model I want to pass down to the filter:

class ProjectFilter(BaseFilter):

    favourite_only = FavouriteFilter(model=Project)


class FavouriteFilter(django_filters.BooleanFilter):
    """
    A custom filter which returns a users favourites of an element
    """

    def __init__(self, *args, **kwargs):
        # gettext_lazy breaks the OpenAPI generation => use gettext instead
        kwargs['label'] = gettext("My favourites")
        model = kwargs.pop('model')
        self.content_type = ContentType.objects.get_for_model(model)
        super(FavouriteFilter, self).__init__(*args, **kwargs)

    def filter(self, qs, value):
        if value == True:
            user = get_current_user()
            return qs.filter(pk__in=Favourite.objects
                             .filter(owner_id=user)
                             .filter(content_type_id=self.content_type)
                             .values_list('object_id', flat=True)
                             )
        else:
            return qs

For my specific use-case, where I'm looking for the model that is using the filter, the model is available through the queryset as qs.model. The code-snippet looks like this:

class ProjectFilter(BaseFilter):

    favourite_only = FavouriteFilter()


class FavouriteFilter(django_filters.BooleanFilter):
    """
    A custom filter which returns a users favourites of an element
    """

    def __init__(self, *args, **kwargs):
        # gettext_lazy breaks the OpenAPI generation => use gettext instead
        kwargs['label'] = gettext("My favourites")
        super(FavouriteFilter, self).__init__(*args, **kwargs)

    def filter(self, qs, value):
        if value == True:
            user = get_current_user()
            content_type = ContentType.objects.get_for_model(qs.model)
            return qs.filter(pk__in=Favourite.objects
                             .filter(owner_id=user)
                             .filter(content_type_id=content_type)
                             .values_list('object_id', flat=True)
                             )
        else:
            return qs