Django Rest Framework - Read nested data, write in

2019-01-25 08:39发布

问题:

So far I'm extremely happy with Django Rest Framework, which is why I alsmost can't believe there's such a large omission in the codebase. Hopefully someone knows of a way how to support this:

class PinSerializer(serializers.ModelSerializer):
    item = ItemSerializer(read_only=True, source='item')
    item = serializers.IntegerSerializer(write_only=True)

    class Meta:
        model = Pin

with the goal

The goal here is to read:
{pin: item: {name: 'a', url: 'b'}}
but to write using an id
{pin: item: 10}

An alternative would be to use two serializers, but that looks like a really ugly solution: django rest framework model serializers - read nested, write flat

回答1:

Assuming you are using a OneToOneField or ForeignKey to relate your Pin to your Item, Django stores the relation as item_id, but often abstracts the Item as item. You can take advantage of this to get around the fact that a Python object cannot have two attributes with the same name (a problem you would encounter in your code).

Simply add _id to the name of your write attribute and any writes will set the underlying relation, while any reads will use the abstracted object. Your final code will be:

class PinSerializer(serializers.ModelSerializer):
    item = ItemSerializer(read_only=True)
    item_id = serializers.IntegerField(write_only=True)

    class Meta:
        model = Pin

Note 1: I also removed source='item' as that was redundant and changed serializers.IntegerSerializer to serializers.IntegerField, since I think that must have been a typo.

Note 2: I actually find it rather unintuitive that Django Rest is set up such that a Pin serializer without an Item serializer specified returns the item_id as "item": <id> and not "item_id": <id>, but that is beside the point.



回答2:

If you are using DRF 3.0 you can implement the new to_internal_value method to override the item field to change it to a PrimaryKeyRelatedField to allow the flat writes. The to_internal_value takes unvalidated incoming data as input and should return the validated data that will be made available as serializer.validated_data. See the docs: http://www.django-rest-framework.org/api-guide/serializers/#to_internal_valueself-data

So in your case it would be:

class ItemSerializer(ModelSerializer):
    class Meta:
        model = Item

class PinSerializer(ModelSerializer):
    item = ItemSerializer() 

    # override the nested item field to PrimareKeyRelatedField on writes
    def to_internal_value(self, data):
         self.fields['item'] = serializers.PrimaryKeyRelatedField(queryset=Item.objects.all())
         return super(PinSerializer, self).to_internal_value(data)

    class Meta:
        model = Pin

Two things to note: The browsable web api will still think that writes will be nested. I'm not sure how to fix that but I only using the web interface for debug so not a big deal. Also, after you write the item returned will have flat item instead of the nested one. To fix that you can add this code to force the reads to use the Item serializer always.

def to_representation(self, obj):
    self.fields['item'] = ItemSerializer()
    return super(PinSerializer, self).to_representation(obj)

I got the idea from this from Anton Dmitrievsky's answer here: DRF: Simple foreign key assignment with nested serializers?



回答3:

You can create a Customized Serializer Field (http://www.django-rest-framework.org/api-guide/fields)

The example took from the link:

class ColourField(serializers.WritableField):
    """
    Color objects are serialized into "rgb(#, #, #)" notation.
    """
    def to_native(self, obj):
        return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue)

    def from_native(self, data):
        data = data.strip('rgb(').rstrip(')')
        red, green, blue = [int(col) for col in data.split(',')]
        return Color(red, green, blue)

Then use this field in your serializer class.