John R Perry
John R Perry

Reputation: 4192

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]

Upvotes: 0

Views: 74

Answers (2)

mtzd
mtzd

Reputation: 799

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

Upvotes: 1

Daniel
Daniel

Reputation: 3527

As mentioned in the comments, a SerializerMethodfield is a good way to go:

serializers.py

class LogSerializer(...):

    ...
    changeInfo = serializers.SerializerMethodField()

    def get_changeInfo(self, obj): return {
        "leadId" : obj.lead_id,
        "updateTime": obj.update_time
    }

    class Meta:
        fields = ["changeInfo", ...]
        ...

Upvotes: 0

Related Questions