Search code examples
pythondjangodjango-rest-frameworkdjango-serializer

Readable nested serializer with many2many in django REST


I am running a django app using REST to send data to my API.

I have a network that contains many projects and a project can belong to many networks.

I want to send this dataset via requests:

payload =  {
            "name": "my great testnetwork",
            "type_network": "xyz",
            "projects" : [
                {
                    "project_name": "Brasil",
                },
                {
                    "project_name": "Sweden",
                },
            ],

            "creation_date": "2020-05-15T15:57:27.399455Z"
    } 

I do not want to create the project_names, they are already in the database. I only want this to work like a normal FK, where I specify the project_name and it shows in my API. It should only relate them.

What happens now is that the project list remains empty when I send it like this

This is what I get:

{
    "id": 6,
    "name": "my great test",
    "type_network": "xyz",
    "projects": [],
    "creation_date": "2020-05-15T19:00:05.947542Z"
}

But I need the projects in the list.

My serializer looks like this:


class NetworkSerializer(serializers.ModelSerializer):
    """Serializer for Network.

    Add extra field buildings from NestedProjectSerializer.
    """

    projects = NestedProjectSerializer(
        many=True,
        read_only=True,
        help_text="Many to many relation. Project instances expected."
    )

    class Meta:
        model = Network
        fields = (
            'id',
            'name',
            'projects',
            'creation_date',
        )

Ok so I tried to overwrite the create method for writable nested serializers, but I think I don't need this, right? Because I don't want to create new objects. Then I also tried to use the PrimaryKeyRelatedField, but i get either the error that pk is not valid (when passing a queryset) or it doens't show the projects (when using read-only=True).

I am a bit stuck here. Can someone lend me a hand? Thanks so much for your time and help. Appreciated very much!

I am happy to provide more code or clarifications if needed.

My models:


class Project(models.Model):
    project_name = models.CharField(max_length=120, primary_key=True, unique=True)
    location = models.CharField(max_length=120, null=True, blank=True)


class Network(models.Model):

    name = models.CharField(
        max_length=120,
        null=True,
        blank=True,
        help_text="Name of network. String expected."
    )

    projects = models.ManyToManyField(
        Project,
        default=None,
        blank=True,
        help_text="Many to many relation. Project instances expected."
    )

    creation_date = models.DateTimeField(
        auto_now=True
    )

I'm getting the feeling that I have to overwrite the create method... the project_name is the pk btw.

EDIT: Sending data like this:


payload =  {
            "name": "my great testnetwork",
            # "projects" : [
            #     {
            #         "project_name": "frankfurt",

            #     }
            # ],
            "projects_data" : [
                {
                    "project_name": "Brasil",

                }
            ],

            "creation_date": "2020-05-15T15:57:27.399455Z"
    }   

r = requests.request('post', 'http://localhost:8000/myendpoint', json=payload, headers=headers)
print(r.text)
print(r.status_code)


Solution

  • You will need to update your code to as below:

    from rest_framework import serializers
    
    class NetworkSerializer(serializers.ModelSerializer):
        """Serializer for Network.
    
        Add extra field buildings from NestedProjectSerializer.
        """
        projects_data = serializers.SerializerMethodField()
    
        class Meta:
            model = Network
            fields = (
                'id',
                'name',
                'projects',
                'projects_data',
                'creation_date',
            )
    
        def get_projects_data(self, obj):
            projects = obj.projects.all()
            return NestedProjectSerializer(projects, many=True).data
    

    I hope this helps..!!

    If you define a nested serializer, you will always be stuck while creating the records. Since create method needs a Serialized data from your project serializer

    Ref: https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

    Edit: New Serializer

    from rest_framework import serializers
    
    class NetworkSerializer(serializers.ModelSerializer):
        """Serializer for Network.
    
        Add extra field buildings from NestedProjectSerializer.
        """
        projects = NestedProjectSerializer(many=True, required=False)
    
        class Meta:
            model = Network
            fields = (
                'id',
                'name',
                'projects',
                'projects_data',
                'creation_date',
            )
    
        def create(self, validated_data):
            projects = validated_data.pop("projects")
            network_obj = Network.objects.create(**validated_data)
            for project in projects:
                network_obj.projects.add(Project.objects.create(**project))
            network_obj.save()
            return network_obj
    

    I have overrides the create method as per your requirements