Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework 3.0: Saving Nested, Many-To-One Relationship

I'm attempting to build a nested relationship using Django Rest Framework 3.0. I've created my serializers and have attempted to override the create() function. My models are defined as follows:

class Item(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    name = models.CharField(max_length=200)
    description = models.CharField(max_length=1000)
    categories = models.ManyToManyField(Category, null=True, blank=True)

class Price(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    item = models.ForeignKey(Item, related_name='prices')
    name = models.CharField(max_length=100)
    cost = models.FloatField()

As you'll note, I can have multiple prices for my items. My serializers are defined as follows:

class PriceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Price
        owner = serializers.Field(source='owner.username')
        exclude = ('user',)

class ItemSerializer(serializers.ModelSerializer):
    prices = PriceSerializer(many=True, required=False)
    categories = CategorySerializer(many=True, required=False)

    class Meta:
        model = Item
        owner = serializers.Field(source='owner.username')
        fields = ('id', 'name', 'description', 'prices', 'categories')

    def create(self, validated_data):
        user = validated_data.get('user')

        # Get our categories
        category_data = validated_data.pop('categories')

        # Create our item
        item = Item.objects.create(**validated_data)

        # Process the categories. We create any new categories, or return the ID of existing
        # categories.
        for category in category_data:
            category['name'] = category['name'].title()
            category, created = Category.objects.get_or_create(user=user, **category)
            item.categories.add(category.id)

        item.save()

        return item

When I try and POST a new item:

{
    "name": "Testing",
    "description": "This is a test",
    "categories": [
        {
            "name": "foo"
        },
        {
            "name": "bar"
        }
    ],
    "prices": [
        {
            "name": "Red",
            "cost": 10
        }
    ]
}

I get the following error:

{
    "prices": [
        {
            "item": [
                "This field is required."
            ]
        }
    ]
}

Presumably because the Price serializer has no idea what the ID of the new item is. I've tried overriding this functionality in the create() function of my serializer, but it appears as though the serializer's validation is being hit before I have the opportunity to create the item and associate it with the price.

So - How do I create a new item, get the item ID, and then create each of the new prices?

like image 331
Julio Avatar asked Dec 31 '14 05:12

Julio


1 Answers

The problem is that your PriceSerializer is looking for the item key because it is specified on the Price model. This isn't immediately obvious because you are using Meta.exclude instead of Meta.fields.

class PriceSerializer(serializers.ModelSerializer):

    class Meta:
        model = Price
        exclude = ('user',)

Is the same as writing

class PriceSerializer(serializers.ModelSerializer):

    class Meta:
        model = Price
        fields = ('id', 'item', 'name', 'cost', )

Which makes it very clear what your issue is. Because your item field on the model does not have empty=True (or null=True) set, Django REST Framework automatically generates it as a PrimaryKeyRelatedField with required=True. This is why you are getting the This field is required is required error, because Django REST Framework cannot automatically detect that it is coming from a parent serializer which already has that field.

You can get around this by removing the field from the serializer, as it doesn't appear to ever be needed.

class PriceSerializer(serializers.ModelSerializer):

    class Meta:
        model = Price
        fields = ('id', 'name', 'cost', )

This will no longer display the item field though, but I suspect this isn't actually an issue for you.

like image 151
Kevin Brown-Silva Avatar answered Oct 05 '22 16:10

Kevin Brown-Silva