pythonjsondjangodjango-rest-frameworkdjango-serializer

How to allow null value on serializer when the field in the model is required?


Given the following payload, models.py, and serializers.py in Django (DRF):

payload

{
  "created_by": 6,
  "brand": 1,
  "foo_details": [
    {
      "quantity": 123,
      "price_estimation": 456
    },
    {
      "quantity": 789,
      "price_estimation": 1011
    }
  ]
}

models.py

class Foo(models.Model):
    created_by = models.ForeignKey(CustomUser, on_delete=models.PROTECT)
    brand = models.ForeignKey(Brand, on_delete=models.SET_NULL, null=True)
    # OTHER FIELDS HERE

class FooChild(models.Model):
    foo = models.ForeignKey(Foo, on_delete=models.CASCADE, related_name="foo_details")
    quantity = models.PositiveIntegerField(default=0)
    price_estimation = models.PositiveIntegerField(default=0)
    # OTHER FIELDS HERE

serializers.py

class FooChildSerializer(serializers.ModelSerializer):
    # foo = serializers.PrimaryKeyRelatedField(read_only=True, required=False) -> LINE 2

    class Meta:
        model = FooChild
        fields = ["id", "foo", "quantity", "price_estimation", ...]

class FooSerializer(serializers.ModelSerializer):
    # foo_details = FooChildSerializer(many=True) -> LINE 9
    # foo_details = serializers.DictField(child=serializers.CharField(), many=True) -> LINE 10

    class Meta:
        model = Foo
        fields = ["id", "created_by", "brand", "foo_details", ...]

    def create(self, validated_data):
        # I want to save the payload to `Foo` and `FooChild` inside this function at the same time, below is my unsuccessful attempt

        # print(validated_data)
        # validated_data.pop("foo_details")
        # foo = Foo.objects.create(**validated_data) -> LINE 21
        # foo_child = FooChild.objects.create(foo=foo)
        
        # return foo

The problem I'm having right now is, when I tried to POST the payload, DRF complained that foo field in FooChild is required, which is understandable, the problem is, the id of the Foo exists only after I created the foo instance (on line 21). I tried to tell DRF to ignore the requirement to provide foo when creating FooChild to no avail (see line 2, 9, 10 above). How do I solve this problem?

Actually I have an idea to set null=True in the FooChild model, but the FooChild can't exist without Foo, so I figured this doesn't make any sense. Thx for the help


Solution

  • To allow a null value on the foo field in the FooChild serializer while keeping the field as required in the model, you can modify your code as follows:

    1. In FooChildSerializer, remove the foo field declaration.
    class FooChildSerializer(serializers.ModelSerializer):
        class Meta:
            model = FooChild
            fields = ["id", "quantity", "price_estimation", ...]
    
    1. In FooSerializer, update the foo_details field to handle the creation of FooChild instances after saving the Foo instance.
    class FooSerializer(serializers.ModelSerializer):
        foo_details = FooChildSerializer(many=True, required=False)
    
        class Meta:
            model = Foo
            fields = ["id", "created_by", "brand", "foo_details", ...]
    
        def create(self, validated_data):
            foo_details_data = validated_data.pop("foo_details", None)
    
            foo = Foo.objects.create(**validated_data)
    
            if foo_details_data:
                for foo_detail_data in foo_details_data:
                    FooChild.objects.create(foo=foo, **foo_detail_data)
    
            return foo
    

    By setting required=False for the foo_details field in the serializer, it allows the field to be optional in the payload. In the create method, we extract the foo_details data from validated_data using pop, keeping it None if not present.

    After creating the foo instance, we check if foo_details_data exists. If it does, we iterate over the data and create FooChild instances, associating them with the foo instance.

    This way, you can create a Foo instance with or without providing foo_details in the payload. If foo_details are provided, they will be associated with the Foo instance after it is created.