I have a model Collection which has a many to many relation to a model Item.
I want to be able to add or remove items to this collection using Django Rest Framework.
Option1 - make an action:
class CollectionViewSet(viewsets.ModelViewSet):
queryset = Collection.objects.all()
serializer_class = CollectionSerializer
@action()
def update_items(self, request, **kwargs):
collection = self.get_object()
add_items_id = request.DATA.pop('add_items', [])
remove_items_id = request.DATA.pop('remove_items', [])
items_add = Item.objects.filter(id__in=add_items_id).all()
collection.items.add(*items_add)
items_remove = Item.objects.filter(id__in=remove_items_id).all()
collection.items.remove(*items_remove)
return Response()
I see two downsides with this:
- I cannot make one single request to update the collection with items and other fields (not without also modifying these.
- I do not get the "API documentation" for free.
Option2 - override the update method and use two different serializers
class CollectionSerializer(serializers.HyperlinkedModelSerializer):
items = ItemSerializer(many=True, read_only=True)
class Meta:
model = Collection
fields = ('id', 'title', 'items')
class CollectionUpdateSerializer(serializers.HyperlinkedModelSerializer):
add_items = serializers.PrimaryKeyRelatedField(many=True, source='items', queryset=Item.objects.all())
remove_items = serializers.PrimaryKeyRelatedField(many=True, source='items', queryset=Item.objects.all())
class Meta:
model = Collection
fields = ('id', 'title', 'add_items', 'remove_items')
class CollectionViewSet(viewsets.ModelViewSet):
queryset = Collection.objects.all()
def update(self, request, *args, **kwargs):
collection = self.get_object()
add_items_id = request.DATA.pop('add_items', [])
remove_items_id = request.DATA.pop('remove_items', [])
items_add = Item.objects.filter(id__in=add_items_id).all()
collection.items.add(*items_add)
items_remove = Item.objects.filter(id__in=remove_items_id).all()
collection.items.remove(*items_remove)
# fool DRF to set items to the new list of items (add_items/remove_items has source 'items')
request.DATA['add_items'] = collection.items.values_list('id', flat=True)
request.DATA['remove_items'] = request.DATA['add_items']
return super().update(request, *args, **kwargs)
def get_serializer_class(self):
if self.request.method == "PUT":
return CollectionUpdateSerializer
return CollectionSerializer
Unfortunately, option2 has this horrible and inefficient hack. Here is why:
Super is called to handle the other properties (in this case only 'title'), it will see that add_items/remove_items is on the source 'items', (and at this point add_items/remove_items params would be empty) so it would remove all items.
What would be the canonical way to handle add/remove of items in a list via django-rest-framework, such that we still get: - 1 request to update the object (i.e. not via a separate action request) - re-using DRF patterns / auto-generated API