How to auto create foreingKey object (python with django)

Question:

I’m creating a simple rest api

This is my actual result from devices, i create first the group and options, and then associate, but i want to create the options at the same time i create the device.

My result from devices right now

[
   
    {
        "id_device": "a099d2ce-812b-4d85-8c8b-cfe71057fbe7",
        "group": {
            "id_group": "39407ef6-1a3e-4965-9b61-96f6c40b76b3",
            "name": "Cozinha"
        },
        "options": [
            {
                "id_option": "1b383a37-5229-4ae0-9649-f76aa32eeda0",
                "name": "1",
                "identifier": "POWER1"
            },
            {
                "id_option": "4ff5406a-8c7b-4517-9ec0-14fd4f68f30b",
                "name": "2",
                "identifier": "POWER2"
            },
            {
                "id_option": "a541216d-f509-4461-85ca-444491ac9217",
                "name": "3",
                "identifier": "POWER3"
            },
            {
                "id_option": "3debe828-edd6-4d83-bfd8-2776a1594380",
                "name": "4",
                "identifier": "POWER4"
            }
        ],
        "name": "Interruptor 4",
        "identifier": "identifiertest"
    },
]

my models.py

from uuid import uuid4
from django.db import models

class Options(models.Model):
    id_option = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    name = models.CharField(max_length=255)
    identifier = models.CharField(max_length=255)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name


class Groups(models.Model):
    id_group = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    name = models.CharField(max_length=255)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name

class Devices(models.Model):
    id_device = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    name = models.CharField(max_length=255)
    identifier = models.CharField(max_length=255)
    options = models.ManyToManyField(Options)
    group = models.ForeignKey(Groups, on_delete=models.CASCADE, default="")

viewsets

from rest_framework import viewsets
from devices.api import serializers
from devices import models

class DevicesViewsets(viewsets.ModelViewSet):
    serializer_class = serializers.DevicesSerializer
    queryset = models.Devices.objects.all()
    
class OptionsViewsets(viewsets.ModelViewSet):
    serializer_class = serializers.OptionsSerializer
    queryset = models.Options.objects.all()

class GroupsViewsets(viewsets.ModelViewSet):
    serializer_class = serializers.GroupsSerializer
    queryset = models.Groups.objects.all()

serializers

from rest_framework import serializers
from devices import models


class OptionsSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Options
        fields = '__all__'

class GroupsSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Groups
        fields = '__all__'

class DevicesSerializer(serializers.ModelSerializer):
    group = GroupsSerializer()
    options = OptionsSerializer(many=True)

    class Meta:
        model = models.Devices
        fields = '__all__'


I send this to create the device

{
    "group": {
        "name": "newgroup"
    },
    "options": [
        {    
           "name": "teste1",
           "identifier": "teste1" 
        }
     ],
    "name": "teste1",
    "identifier": "teste1"
}

but get this error:

AssertionError at /devices/
The .create() method does not support writable nested fields by default.
Write an explicit .create() method for serializer devices.api.serializers.DevicesSerializer, or set read_only=True on nested serializer fields.
Request Method: POST
Request URL: http://127.0.0.1:8000/devices/
Django Version: 4.1.7
Exception Type: AssertionError
Exception Value:
The .create() method does not support writable nested fields by default.
Write an explicit .create() method for serializer devices.api.serializers.DevicesSerializer, or set read_only=True on nested serializer fields.

I know the response is here, but i dont know how to do it.
I try some solutions from internet but none with success, so can someone help me?

Asked By: Ricardo

||

Answers:

You are right, the answer is in there. Basically you have to provide a def create(self, validated_data) method to your DevicesSerializer class.

Here is something that could help:

class DevicesSerializer(serializers.ModelSerializer):
    group = GroupsSerializer()
    options = OptionsSerializer(many=True)

    class Meta:
        model = models.Devices
        fields = '__all__'

    def create(self, validated_data):
        options_data = validated_data.pop("options")
        device = models.Devices.objects.create(**validated_data)  # or you can manually pass the fields/data as kwargs

        for option_data in options_data:
            models.Options.objects.create(device=device, **option_data)

        return device

Basically, you need to manually save the base object (Device) and then manually iterate over the options data and create each of them pointing to the newly created device.

Answered By: foobarna

serializer.py

class DevicesSerializer(serializers.ModelSerializer):
    group = GroupsSerializer()
    options = OptionsSerializer(many=True)

    class Meta:
        model = models.Devices
        fields = '__all__'

    def create(self, validated_data):
        group_data = validated_data.pop('group')
        options_data = validated_data.pop('options')
        group, _ = models.Groups.objects.get_or_create(**group_data)
        options = [models.Options.objects.create(**option_data) for option_data in options_data]
        device = models.Devices.objects.create(group=group, **validated_data)
        device.options.set(options)
        return device

viewsets.py:

from rest_framework import viewsets
from devices.api import serializers
from devices import models

class DevicesViewsets(viewsets.ModelViewSet):
    serializer_class = serializers.DevicesSerializer
    queryset = models.Devices.objects.all()

    def perform_create(self, serializer):
        serializer.save()

    
class OptionsViewsets(viewsets.ModelViewSet):
    serializer_class = serializers.OptionsSerializer
    queryset = models.Options.objects.all()

class GroupsViewsets(viewsets.ModelViewSet):
    serializer_class = serializers.GroupsSerializer
    queryset = models.Groups.objects.all()
Answered By: Iqbal Hussain