MaxenceP
MaxenceP

Reputation: 291

Django: Creating Nested Objects with Reverse Relationship

I have a problem while trying to create Nested object with a reverse relationship.

I'm trying to POST a Work but at the SAME time trying to POST a Price which is a Work child.

Here is my work_model:

from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
from PIL import Image

class Work(models.Model):
    user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
    name = models.CharField(max_length=200)
    length = models.IntegerField(null=True)
    width = models.IntegerField(null=True)

    def save(self, *args, **kwargs):
        super(Work, self).save(*args, **kwargs)

        img = Image.open(self.image.path)

        if img.height > 300 or img.width > 300:
            output_size = (300, 300)
            img.thumbnail(output_size)
            img.save(self.image.path)

    def __str__(self):
        return "{}".format(self.id)

My price_model:

from django.db import models
from .model_work import *
from .model_taxes import *
from djmoney.models.fields import MoneyField

class Price(models.Model):
    work = models.OneToOneField(Work, on_delete=models.CASCADE, related_name='price')
    price = MoneyField(max_digits=19, decimal_places=4, default_currency='USD', null=True)
    taxes = models.ForeignKey(Taxes, null=True, on_delete=models.SET_NULL)
    total = models.IntegerField(null=True)

    def __str__(self):
        return "{}".format(self.price)

And my taxes_model:

from django.db import models

class Taxes(models.Model):
    tax_percentage = models.IntegerField(null=True)
    tax_country = models.CharField(max_length=200, null=True)
    tax_region = models.CharField(max_length=200, null=True)

    def __str__(self):
        return "{}".format(self.tax_country)

Here is my work_serializer:

from rest_framework import serializers
from ..models.model_work import Work
from .serializers_user import *
from .serializers_price import *


class WorkIndexSerializer(serializers.ModelSerializer):
    """
    Serializer listing all Works models from DB
    """
    user = UserIndexSerializer()
    price = PriceDetailsSerializer(many=False)

    class Meta:
        model = Work
        fields = [
            'id',
            'user',
            'price',
            'name',
            'image',
            'length',
            'width'
        ]

class WorkCreateSerializer(serializers.ModelSerializer):
    """
    Serializer to create a new Work model in DB
    """

    price = PriceCreateSerializer(many=False)

    class Meta:
        model = Work
        fields = [
            'user',
            'price',
            'name',
            'length',
            'width'
        ]

    def create(self, validated_data):
        price = Price.objects.create(**validated_data)
        work = Work.objects.create(**validated_data)

        return work


class WorkDetailsSerializer(serializers.ModelSerializer):
    """
    Serializer showing details of an Works model from DB
    """
    user = UserIndexSerializer()

    class Meta:
        model = Work
        fields = fields = [
            'user',
            'name',
            'image',
            'length',
            'width'
        ]

My price_serializer:

from rest_framework import serializers
from ..models.model_price import Price
from .serializers_work import *

class PriceIndexSerializer(serializers.ModelSerializer):
    """
    Serializer showing Price information when called by Work GET serializers.
    Not showing 'work' field to avoid loop.
    """
    taxes = serializers.StringRelatedField(read_only=True)
    class Meta:
        model = Price
        fields = [
            'price',
            'price_currency',
            'taxes',
            'total'
        ]
        depth = 1

class PriceDetailsSerializer(serializers.ModelSerializer):
    """
    Serializer showing Price information when called by Work GET serializers.
    Not showing 'work' field to avoid loop.
    """
    taxes = serializers.StringRelatedField(read_only=True)
    class Meta:
        model = Price
        fields = [
            'price',
            'price_currency',
            'taxes',
            'total'
        ]
        depth = 1

class PriceCreateSerializer(serializers.ModelSerializer):
    """
    Serializer to create a new Price when new Work model is created in DB
    """
    work = serializers.StringRelatedField(read_only=True)
    taxes = serializers.StringRelatedField(read_only=True)
    class Meta:
        model = Price
        fields = [
            'work',
            'price',
            'price_currency',
            'taxes',
            'total'
        ]

    def create(self, validated_data):
        work = Work.objects.create(**validated_data)
        price = Price.objects.create(**validated_data)
        taxes = Taxes.objects.create(**validated_data)
        return price

And when I'm trying to POST a Work with Postman:

{
    "user":"2",
    "name":"work 20",
    "price": 
            {
                "price":20,
                "price_currency":"EUR",
                "taxes":1,
                "total":32
            },
    "length":"50",
    "width":"60"
}

I get the error:

TypeError at /works/
conversion from collections.OrderedDict to Decimal is not supported

And when I try to POST a Price by itself:

{
    "work":20,
    "price":20,
    "price_currency":"EUR",
    "taxes":1,
    "total":32
}

I get the error:

ValueError at /prices/
Cannot assign "<Money: 20.0000 EUR>": "Work.price" must be a "Price" instance.

I can't find any solution working with my case.

What am I doing worng or missing?

Thanks you for your responses!

Upvotes: 1

Views: 767

Answers (1)

Igor Moraru
Igor Moraru

Reputation: 7739

1. TypeError at /works/
conversion from collections.OrderedDict to Decimal is not supported

The solution is to pass the nested price dictionary to create Price model instead of the whole data:

class WorkCreateSerializer(serializers.ModelSerializer):

    price = PriceDetailsSerializer(many=False)

...

 def create(self, validated_data):
        price_data = validated_data.pop('price')
        work = Work.objects.create(**validated_data)
        price = Price.objects.create(**price_data)
        return price

2.

ValueError at /prices/
  Cannot assign "<Money: 20.0000 EUR>": "Work.price" must be a "Price" instance.

First, change work field on PriceCreateSerializer, so you can have work data in validated_data:

work = WorkDetailsSerializer(many=False)

then:

def create(self, validated_data):
        work_data = validated_data.pop('work')
        work = Work.objects.create(**work_data)
        price = Price.objects.create(**validated_data)
        return price

Upvotes: 1

Related Questions