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:
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?
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.