Search code examples
pythondjangodjango-rest-frameworkdjango-filter

Use filterset class in a nested route in Django REST Framework


We have a REST API created with Django and Django REST Framework. With the package django-filter I've created a FilterSet class which I want to use in a nested route.

For illustration, we have model classes User, Post, Tag. A Post has one author (User) and can have many (or none) Tags.

The following endpoints are present:

  • /users/[id]/
  • /posts/[id]/
  • /users/[id]/posts/

The FilterSet class looks like this:

class PostFilterSet(filters.FilterSet):
    tags = filters.ModelChoiceFilter(queryset=Tag.objects.all())

    class Meta:
        model = Post
        fields = ("tags",)

We use it in the viewset for Posts:

class PostViewSet(viewsets.ModelViewSet):
    serializer_class = PostSerializer
    queryset = Post.objects.all()
    filter_backends = [filters.DjangoFilterBackend]
    filterset_class = PostFilterSet

Now this is working well and the list of posts can be filtered by tag, like this:

/posts/?tags=some_tag

In the UserViewSet we have a nested route created with the decorator action:

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = UserSerializer
    queryset = User.objects.all()
    filter_backends = [filters.DjangoFilterBackend]
    filterset_class = UserFilterSet

    @action(methods=["get"], detail=True)
    def posts(self, request, pk):
        # logic to fetch posts for the given user
        return Response(serializer.data)

We want to filter the list of posts for a given user (author) tagged by some tag:

/users/[id]/posts/?tags=some_tag

I want to use the PostFilterSet class in the nested route above. Is this possible? If yes, how should it be done?


Solution

  • This is how I have solved this problem:

    class UserViewSet(viewsets.ReadOnlyModelViewSet):
        serializer_class = UserSerializer
        queryset = User.objects.all()
        filter_backends = [filters.DjangoFilterBackend]
        filterset_class = UserFilterSet
    
        @action(methods=["get"], detail=True)
        def posts(self, request, pk):
            user = get_object_or_404(User, pk=pk)
            posts = Post.objects.filter(author=user)
            filter_class = PostFilterSet(request.query_params, queryset=posts)
            posts = filter_class.qs
            serializer = PostSerializer(posts, many=True, context={"request": request})
            return Response(serializer.data)
    

    The bold lines are showing how to call the filterset class manually and filter the queryset.

    Now if the client calls the endpoint like this:

    /users/[id]/posts/?tags=some_tag

    then the query parameters (in this case it's only one - tags) will be used to filter the queryset.

    This answer gave me an idea:

    https://stackoverflow.com/a/27161942/3848833

    If you have implemented pagination then this isn't going to work straightforward and you'll have to adjust the code.