Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writable nested serializer in django-rest-framework?

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.

like image 771
Thomas Liu Avatar asked Jan 14 '16 08:01

Thomas Liu


People also ask

What is the difference between ModelSerializer and HyperlinkedModelSerializer?

The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.

Do we need Serializers in Django REST Framework?

Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

How do you pass extra context data to Serializers in Django REST Framework?

In function-based views, we can pass extra context to serializer with “context” parameter with a dictionary. To access the extra context data inside the serializer we can simply access it with “self. context”. From example, to get “exclude_email_list” we just used code 'exclude_email_list = self.

How do I pass Queryset to serializer?

To serialize a queryset or list of objects instead of a single object instance, you should pass the many=True flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.


2 Answers

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
like image 125
bakkal Avatar answered Oct 10 '22 09:10

bakkal


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)
like image 35
Ahmed Kamal Avatar answered Oct 10 '22 09:10

Ahmed Kamal