11
votes

I have an ItemCollection and Items in my Django models and I want to be able to remove Items from the collection through the UI. In a REST PUT request I add an extra boolean field deleted for each Item to signal that an Item should be deleted.

The correct way to handle this seems to be in the update method of the Serializer. My problem is that this non-model deleted field gets removed during validation, so it is not available anymore. Adding deleted as a SerializerMethodField did not help. For now I get my deleted information from the initial_data attribute of the Serializer, but that does not feel right.

My current example code is below. Does anybody know a better approach?

Models:

    class ItemCollection(models.Model):
        description = models.CharField(max_length=256)


    class Item(models.Model):
        collection = models.ForeignKey(ItemCollection, related_name="items")

Serializers:

    from django.shortcuts import get_object_or_404
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework import serializers
    from models import Item, ItemCollection


    class ItemSerializer(serializers.ModelSerializer):

        class Meta:
            model = Item


    class ItemCollectionSerializer(serializers.ModelSerializer):

        items = ItemSerializer(many=True, read_only=False)

        class Meta:
            model = ItemCollection

        def update(self, instance, validated_data):
            instance.description = validated_data['description']
            for item, item_obj in zip(
                   self.initial_data['items'], validated_data['items']):
                if item['delete']:
                    instance.items.filter(id=item['id']).delete()
            return instance


    class ItemCollectionView(APIView):

        def get(self, request, ic_id):
            item_collection = get_object_or_404(ItemCollection, pk=ic_id)
            serialized = ItemCollectionSerializer(item_collection).data
            return Response(serialized)

        def put(self, request, ic_id):
            item_collection = get_object_or_404(ItemCollection, pk=ic_id)
            serializer = ItemCollectionSerializer(
               item_collection, data=request.data)
            if serializer.is_valid(raise_exception=True):
                serializer.save()
            return Response(serializer.data)

And an example of the json in the PUT request:

    {
        "id": 2,
        "items": [
            {
                "id": 3,
                "collection": 2,
                "delete": true
            }
        ],
        "description": "mycoll"
    }
2
Would it be acceptable to just do a separate HTTP DELETE for each of the nested modules that need deleting? If you were to use serializers.HyperlinkedModelSerializer as your serializers' base class, each sub-item would have a URL that you could easily DELETE.Ross Rogers
That's an interesting angle to explore. Although, the main problem is that information about which Items need to be deleted is missing in validated_data.jjmurre
What I mean is that on the client side, instead of setting flag delete on the sub-item, just do an HTTP DELETE to that object and remove it from the container. You don't do the DELETE from inside Django ( If I may presume you were insinuating that approach.)Ross Rogers
Ah, I misunderstood. Nice approach, tx!jjmurre

2 Answers

28
votes

You can add non-model fields back by overwriting the to_internal_value fn:

def to_internal_value(self, data):
    internal_value = super(MySerializer, self).to_internal_value(data)
    my_non_model_field_raw_value = data.get("my_non_model_field")
    my_non_model_field_value = ConvertRawValueInSomeCleverWay(my_non_model_field_raw_value)
    internal_value.update({
        "my_non_model_field": my_non_model_field_value
    })
    return internal_value

Then you can process it however you want in create or update.

0
votes

If you're doing a PUT request, your view is probably calling self.perform_update(serializer). Change it for

serializer.save(<my_non_model_field>=request.data.get('<my_non_model_field>', <default_value>)

All kwargs are passed down to validated_data to your serializer. Make sure to properly transform incoming value (to boolean, to int, etc.)