Bary12
Bary12

Reputation: 1088

Related resources on a nested URL in Django Rest Framework

I have two models, Foo and Bar. Bar has a foreign key to Foo. I have a ModelViewSet for Foo and I want the route /api/foos/<pk>/bars/ to return all the bars related to the given foo.

I know this can be done using actions. For example

class FooViewSet(viewsets.ModelViewSet):
    serializer_class = FooSerializer
    queryset = Foo.objects.all()

    @action(detail=True, methods=['GET'])
    def bars(self, request, pk):
        queryset = Bar.objects.filter(foo=pk)
        serializer = BarSerializer(queryset, many=True)
        return Response(serializer.data)

    @bars.mapping.post
    def create_bar(self, request, pk):
        request.data['foo'] = pk
        serializer = BarSerializer(request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)

However, this feels like a lot of unnecessary boilerplate code.

Using bars = PrimaryKeyRelatedField(many=True) unfortunately doesn't fit my case because each foo can have hundreds of bars, which I don't want to send in every request.

Is this possible to do in viewsets in a more convenient way? If not, what is the normal DRF way of doing it? A solution with a different URL is also acceptable for me as long as it minimizes boilerplate code.

Upvotes: 5

Views: 1357

Answers (1)

wencakisa
wencakisa

Reputation: 5958

The straightforward solution for me is to use a simple generics.ListAPIView for this specific endpoint:

views.py

from rest_framework import generics


class FooBarsListAPIView(generics.ListAPIView):
    serializer_class = BarSerializer

    def get_queryset(self):
        return Bar.objects.filter(foo=self.kwargs.get('pk'))

And then you just register this view in your urls.py instead of the viewset.


This is all you need to do in order to achieve the result that you want. You just specify how your queryset filter looks and everything else is done by the ListAPIView implementation.

Viewsets are doing the work in most cases, but if you want something specific like this, the viewset can become an overkill. Yes, now there is one additional class defined for this specific endpoint, but the boilerplate code is reduced to zero, which is what we want at the end of the day.

Upvotes: 3

Related Questions