Использование класса filterset во вложенном маршруте в Django REST Framework
У нас есть REST API, созданный с помощью Django и Django REST Framework. С помощью пакета django-filter
я создал класс FilterSet, который я хочу использовать во вложенном маршруте.
Для иллюстрации, у нас есть модельные классы User, Post, Tag. У Post один автор (User) и может быть много (или ни одного) Tags.
Присутствуют следующие конечные точки:
- /пользователи/[id]/
- /посты/[id]/
- /пользователи/[id]/посты/
Класс FilterSet выглядит следующим образом:
class PostFilterSet(filters.FilterSet):
tags = filters.ModelChoiceFilter(queryset=Tag.objects.all())
class Meta:
model = Post
fields = ("tags",)
Мы используем его в наборе представлений для Posts:
class PostViewSet(viewsets.ModelViewSet):
serializer_class = PostSerializer
queryset = Post.objects.all()
filter_backends = [filters.DjangoFilterBackend]
filterset_class = PostFilterSet
Теперь это работает хорошо, и список постов может быть отфильтрован по тегам, вот так:
/posts/?tags=some_tag
В UserViewSet
у нас есть вложенный маршрут, созданный с помощью декоратора action:
class UserViewSet(viewsets.ReadOnlyModelViewSet):
@action(methods=["get"], detail=True)
def posts(self, request, pk):
# logic to fetch posts for the given user
return Response(serializer.data)
Мы хотим отфильтровать список постов для данного пользователя (автора), помеченных некоторым тегом:
/users/[id]/posts/?tags=some_tag
Я хочу использовать класс PostFilterSet
во вложенном маршруте выше. Возможно ли это? Если да, то как это сделать?
Вот как я решил эту проблему:
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 = PostSerializerPostSerializer(posts, many=True, context={"request": request})
return Response(serializer.data)
Жирные линии показывают, как вызвать класс filterset вручную и отфильтровать набор запросов.
Теперь, если клиент вызывает конечную точку следующим образом:
/users/[id]/posts/?tags=some_tag
тогда параметры запроса (в данном случае только один - tags) будут использованы для фильтрации набора запросов.
Этот ответ натолкнул меня на мысль:
https://stackoverflow.com/a/27161942/3848833
Если у вас реализована пагинация, то это не будет работать прямо, и вам придется скорректировать код.