Search code examples
pythondjangodjango-rest-framework

How to arbitrarily nest some data in a django rest framework serializer


An existing client is already sending data in a structure like…

{
    "hive_metadata": {"name": "hive name"},
    "bees": [{"name": "bee 1", "name": "bee 2", ...}]
}

For models like:

class Hive(models.Model):
    name = models.CharField(max_length=32, help_text="name")


class Bee(models.Model):
    name = models.CharField(max_length=32, help_text="name")
    hive = models.ForeignKey(
        Hive, help_text="The Hive associated with this Bee", on_delete=models.CASCADE
    )

The code that makes this possible manually iterates over the incoming data. I would like to rewrite it using a django rest framework serializer; however, the fact that hive_metadata is nested itself has stumped me so far.

If I write

class BeesSerializer(ModelSerializer):
    class Meta:
        model = models.Bee
        fields = ("name",)

class PopulatedHiveSerializer(ModelSerializer):
    bees = BeesSerializer(many=True, source="bee_set")
    class Meta:
        model = models.Hive
        fields = ("name","bees",)

would produce

{
    "name": "hive name",
    "bees": [{"name": "bee 1", "name": "bee 2", ...}]
}

readily enough. I had hoped I could solve it with a reference to a sub-serializer, something like

class HiveMetaDataSerializer(ModelSerializer):
    class Meta:
        model = models.Hive
        fields = ("name",)

class PopulatedHiveSerializer(ModelSerializer):
    bees = BeesSerializer(many=True, source="bee_set")
    hive_metadata = HiveMetaDataSerializer(source=???)
    class Meta:
        model = models.Hive
        fields = ("hive_metadata","bees",)

but I can't seem to figure out what to put in the "source" so that the same object is passed through the outer serializer into the inner.

So, is there a way to do this using a django rest framework serializer?


Solution

  • You can use a string literal with an asterisk ('*'), as specified in the documentation [drf-doc]:

    The value source='*' has a special meaning, and is used to indicate that the entire object should be passed through to the field. This can be useful for creating nested representations, or for fields which require access to the complete object in order to determine the output representation.

    so we can use:

    class PopulatedHiveSerializer(ModelSerializer):
        bees = BeesSerializer(many=True, source='bee_set')
        hive_metadata = HiveMetaDataSerializer(source='*')
    
        class Meta:
            model = models.Hive
            fields = (
                'hive_metadata',
                'bees',
            )