Reputation: 1088
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 bar
s 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
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