Search code examples
djangodjango-rest-frameworkdjango-serializer

Passing nested data to Django ModelSerializer


I'm wanting to know how you would pass nested data to a ModelSerializer if the child of the nested data is not a model on its own.

The data that I'm working with looks like this:

{
  'leadId': 12345,
  'updateTime': 1651250096821,
  'changeInfo': {
    'oldstage': 'New Leads',
    'newstage': 'Attempting Contact'
  }
}

From previous experience, I know that if I was only working with the leadId and the updateTime, my serializer would look like this:

class LogSerializer(serializers.ModelSerializer):
    leadId = serializers.IntegerField(source="lead_id")
    updateTime = serializers.IntegerField(source="update_time")

    class Meta:
        model = Log
        fields = ["leadId", "updateTime"]

Which would then make it possible to do this:

data = {
    'leadId': 12345,
    'updateTime': 1651250096821
}
serializer = LogSerializer(data=data)
serializer.is_valid()
serializer.save()

If I'm not wanting to turn changeInfo into its own model, is it possible to map the fields to the nested data? Something that might look like this (but this obviously doesn't work):

class LogSerializer(serializers.ModelSerializer):
    leadId = serializers.IntegerField(source="lead_id")
    updateTime = serializers.IntegerField(source="update_time")
    oldstage = serializers.IntegerField(source="oldstage")
    newstage = serializers.IntegerField(source="newstage")


    class Meta:
        model = Log
        fields = ["leadId", "updateTime", "oldstage", "newstage]

Solution

  • You can use a custom serializer for your changeInfo field (you don't need to create a model for that):

    class ChangeInfoSerializer(serializers.Serializer):
        oldstage = serializers.CharField(max_length=100, source="old_stage") # Set max_length to a value that suits your needs
        newstage = serializers.CharField(max_length=100, source="new_stage")
    
        def create(self, validated_data):
            pass
    
        def update(self, instance, validated_data):
            pass
    
    
    class LogSerializer(serializers.ModelSerializer):
        leadId = serializers.IntegerField(source="lead_id")
        updateTime = serializers.IntegerField(source="update_time")
        changeInfo = ChangeInfoSerializer(required=False) # Change to required=True if you want this field to be mandatory
        
    
        class Meta:
            model = Log
            fields = ["leadId", "updateTime", "changeInfo"]
    
        def create(self, validated_data):
            change_info = validated_data.pop('changeInfo')
            for key, value in change_info.items():
                if key == "old_stage":
                    validated_data['old_stage'] = value
                elif key == "new_stage":
                    validated_data['new_stage'] = value
            log = Log.objects.create(**validated_data)
            return log
    
        def update(self, instance, validated_data):
            change_info = validated_data.pop('changeInfo')
            
            instance.lead_id = validated_data.get('leadId', instance.lead_id)
            instance.update_time = validated_data.get('updateTime', instance.update_time)
            
            # Here you can use change_info['oldstage'] and change_info['newstage'] if 'changeInfo' is sent (otherwise you'll get a KeyError)
            
            instance.save()
            
            return instance