Использование класса 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

Если у вас реализована пагинация, то это не будет работать прямо, и вам придется скорректировать код.

Вернуться на верх