Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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:

  1. I cannot make one single request to update the collection with items and other fields (not without also modifying these.
  2. 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

share|improve this question
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.