Writable nested serializer in django-rest-framework?

11,441

Solution 1

Nested Serializer

You can do something like this, define a serializer for Dataitem that can reuse a serializer of the AssetModel model

class AssetModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = AssetModel

    # Fields org and name of AssetModel will be inlcuded by default

class DataitemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Dataitem

    mod = AssetModelSerializer()
        # This is the Dataitem.mod field
        # which is a FK to AssetModel,
        # Now it'll be serilized using the AssetModelSerializer
        # and include the org and name fields of AssetModelSerializer

I prefer this approach because of the control I get. If you serialize using the above you get a structure like this:

data_item = {'name': ..., 'mod': {'org': ..., 'name': ...}}
                          ^
                          |___ AssetModel fields

Alternatively you can also use depth = n

You can also use depth = 1 in Dataitem

class DataitemSerializer(serializers.ModelSerializer):
        class Meta:
            model = Dataitem
            depth = 1 # Will include fields from related models
                      # e.g. the mod FK to AssetModel

Writable Nested Serializer

Because the behavior of nested creates and updates can be ambiguous, and may require complex dependencies between related models, REST framework 3 requires you to always write these methods explicitly.

We have to implement create/update to make this writable as per DRF's documentation

class DataitemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Dataitem

    # Nested serializer
    mod = AssetModelSerializer()

    # Custom create()
    def create(self, validated_data):
        # First we create 'mod' data for the AssetModel
        mod_data = validated_data.pop('mod')
        asset_model = AssetModel.objects.create(**mod_data)

        # Now we create the Dataitem and set the Dataitem.mod FK
        dataitem = Dataitem.objects.create(mod=asset_model, **validated_data)

        # Return a Dataitem instance
        return dataitem

Solution 2

There seems to be a library that does this drf-writable-nested

it handles the creation and serialisation of these types

  • OneToOne (direct/reverse)
  • ForeignKey (direct/reverse)
  • ManyToMany (direct/reverse excluding m2m relations with through model)
  • GenericRelation (this is always only reverse)
Share:
11,441
Thomas Liu
Author by

Thomas Liu

Updated on June 05, 2022

Comments

  • Thomas Liu
    Thomas Liu almost 2 years

    My design is as following about Django ModelSerializer. There are model A and model B. Model B has a foreign key field of Model A. For some reasons, I can not use the primary key directly to serialize Model B. As my thought, what I need is to serialize two other fields(unique together in Model A).

    And I see the SlugRelatedField must be used for one slug field. I searched there is a NaturalKeyField can support NaturalKeyField. But it looks like it is superseeded by django-rest-framework. But I checked the django-rest-framework, there is no such field at all. Can anyone help?? What should I do?

    The code is as following. Model A

    class AssetModel(models.Model):
        org = models.ForeignKey(Org, related_name='models')
        name = models.CharField(max_length=128)
        model_type = models.SmallIntegerField(default = 3,choices = MODEL_TYPE )
        directory = models.CharField(max_length = 128)
        ...
        class Meta:
            unique_together = ('org', 'name',)
    

    Model B

    class Dataitem(models.Model):
        mod = models.ForeignKey(AssetModel, related_name='dataitems')
        name = models.CharField(max_length=128)
        data_type = models.SmallIntegerField(default =0,choices = DATAITEM_DATATYPE)
        ...
    

    Serializer of model A

    class AssetModelSerializer(serializers.ModelSerializer):
        org =  serializers.SlugRelatedField(queryset=Org.objects.all(), slug_field='name')
        class Meta:
            model = AssetModel
            fields = ('org', 'name', 'model_type',..
    

    Serializer of model B

    class DataitemSerializer(serializers.ModelSerializer):
        class Meta:
            model = Dataitem
            fields = ('mod', 'name','data_type'...)
    

    The primary key of Model A is just a id Django auto added. When serialize the model B, I need to get the org and name of model A. Both read and write are needed.