Django Rest Framework - Read nested data, write integer

前端 未结 4 837
轻奢々
轻奢々 2021-01-01 14: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 wa

相关标签:
4条回答
  • 2021-01-01 15:09

    I create a Field type that tries to solve the problem of the Data Save requests with its ForeignKey in Integer, and the requests to read data with nested data

    This is the class:

    class NestedRelatedField(serializers.PrimaryKeyRelatedField):
    """
        Model identical to PrimaryKeyRelatedField but its
        representation will be nested and its input will
        be a primary key.
    """
    
    def __init__(self, **kwargs):
        self.pk_field = kwargs.pop('pk_field', None)
        self.model = kwargs.pop('model', None)
        self.serializer_class = kwargs.pop('serializer_class', None)
        super().__init__(**kwargs)
    
    def to_representation(self, data):
        pk = super(NestedRelatedField, self).to_representation(data)
        try:
            return self.serializer_class(self.model.objects.get(pk=pk)).data
        except self.model.DoesNotExist:
            return None
    
    def to_internal_value(self, data):
        return serializers.PrimaryKeyRelatedField.to_internal_value(self, data)
    

    And so it would be used:

    class PostModelSerializer(serializers.ModelSerializer):
    
        message = NestedRelatedField(
             queryset=MessagePrefix.objects.all(),
             model=MessagePrefix,
             serializer_class=MessagePrefixModelSerializer
       )
    

    I hope this helps you.

    0 讨论(0)
  • 2021-01-01 15:16

    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.

    0 讨论(0)
  • 2021-01-01 15:33

    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?

    0 讨论(0)
  • 2021-01-01 15:36

    Django lets you access the Item on your Pin with the item attribute, but actually stores the relationship as item_id. You can use this strategy in your serializer 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).

    The best way to do this is to use a PrimaryKeyRelatedField with a source argument. This will ensure proper validation gets done, converting "item_id": <id> to "item": <instance> during field validation (immediately before the serializer's validate call). This allows you to manipulate the full object during validate, create, and update methods. Your final code would be:

    class PinSerializer(serializers.ModelSerializer):
        item = ItemSerializer(read_only=True)
        item_id = serializers.PrimaryKeyRelatedField(write_only=True,
                                                     source='item',
                                                     queryset=Item.objects.all())
    
        class Meta:
            model = Pin
            fields = ('id', 'item', 'item_id',)
    

    Note 1: I also removed source='item' on the read-field as that was redundant.

    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.

    This method can even be used with forward and reverse "Many" relationships. For example, you can use an array of pin_ids to set all the Pins on an Item with the following code:

    class ItemSerializer(serializers.ModelSerializer):
        pins = PinSerializer(many=True, read_only=True)
        pin_ids = serializers.PrimaryKeyRelatedField(many=True,
                                                     write_only=True,
                                                     source='pins',
                                                     queryset=Pin.objects.all())
    
        class Meta:
            model = Item
            fields = ('id', 'pins', 'pin_ids',)
    

    Another strategy that I previously recommended is to use an IntegerField to directly set the item_id. Assuming you are using a OneToOneField or ForeignKey to relate your Pin to your Item, you can set item_id to an integer without using the item field at all. This weakens the validation and can result in DB-level errors from constraints being violated. If you want to skip the validation DB call, have a specific need for the ID instead of the object in your validate/create/update code, or need simultaneously writable fields with the same source, this may be better, but I wouldn't recommend anymore. The full line would be:

    item_id = serializers.IntegerField(write_only=True)
    
    0 讨论(0)
提交回复
热议问题