I'm trying to create nested objects in Django Rest Framework according to the docs.
Here's my models.py:
import uuid
from django.db import models
from django.utils.http import int_to_base36
from django.core.validators import MaxValueValidator, MinValueValidator
from django.contrib.auth import get_user_model
ID_LENGTH = 12
USER = get_user_model()
def slug_gen():
"""Generates a probably unique string that can be used as a slug when routing
Starts with a uuid, encodes it to base 36 and shortens it
"""
#from base64 import b32encode
#from hashlib import sha1
#from random import random
slug = int_to_base36(uuid.uuid4().int)[:ID_LENGTH]
return slug
class List(models.Model):
"""Models for lists
"""
slug = models.CharField(max_length=ID_LENGTH, default=slug_gen, editable=False)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_by = models.ForeignKey(USER, on_delete=models.CASCADE, related_name='list_created_by')
created_at = models.DateTimeField(auto_now_add=True)
modified_by = models.ForeignKey(USER, on_delete=models.SET_NULL, null=True,
related_name='list_modified_by')
modified_at = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=255)
description = models.CharField(max_length=255, blank=True, default='')
is_public = models.BooleanField(default=False)
def __str__(self):
return self.title
class Item(models.Model):
"""Models for list items
"""
slug = models.CharField(max_length=ID_LENGTH, default=slug_gen, editable=False)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
modified_at = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=255)
description = models.CharField(max_length=255, blank=True, default='')
list = models.ForeignKey(List, on_delete=models.CASCADE, related_name='items')
order = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(10)])
class Meta:
unique_together = ('list', 'order')
ordering = ['order']
def __unicode__(self):
return '%d: %s' % (self.order, self.title)
Here's my serializers.py:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('id', 'title', 'description', 'slug', 'modified_at', 'list', 'order')
class ListSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True)
print('hello one')
# automatically set created_by as the current user
created_by = serializers.PrimaryKeyRelatedField(
read_only=True,
default=serializers.CurrentUserDefault()
)
class Meta:
model = List
fields = ('id', 'title', 'description', 'is_public',
'slug', 'created_by', 'created_at',
'modified_by', 'modified_at', 'items')
def create(self, validated_data):
print('hello two')
items_data = validated_data.pop('items', None)
print(validated_data)
print(items_data)
newlist = List.objects.create(**validated_data)
for item_data in items_data:
Item.objects.create(list=newlist, **item_data)
return list
And here's the cURL that I'm sending with the data:
curl 'http://localhost:3000/api/v1/content/lists/' -H 'Authorization: Token ae367b73efbb7d6849af421d553e9c243b4baf7b' -H 'Origin: http://localhost:3000' -H 'Accept-Encoding: gzip, deflate, br' -H 'dataType: json' -H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36' -H 'Content-Type: application/json' -H 'Accept: */*' -H 'Referer: http://localhost:3000/newlist' -H 'Connection: keep-alive' --data-binary '{"title":"Hats","description":"","items":[{"title":"Fedora","order":1}]}' --compressed
As far as I can see, this matches the example in the docs, and I think the data is correctly formatted with 'items' as an array of objects. However when I try to create a new list with an item, it fails with this error:
{"items":[{"list":["This field is required."]}]}
I read this post: I see the same error if I use the browsable API, so it seems a different problem.
Why is there an error? 'list' is provided in the code as per the example. It's a UUID, does that need special handling?
A second issue that is making it harder for me to debug...why don't the print statements in the 'create' method write anything to the console? "hello one" appears when the server starts, but "hello two" never appears in the console.
Many thanks for any help!
Update: from this post it seems that creating nested objects works the other way around to what I expected - ItemSerializer runs before ListSerializer, even though an item cannot exist without a list. Removing 'list' from the ItemSerializer fields means that the creation now succeeds, hurrah! And my debug text is printed, showing that ListSerializer's create method is now being called.
Here's the working fields definition in ItemSerializer :
fields = ('id', 'title', 'description', 'slug', 'modified_at', 'order')
I also had a typo in ListSerializer. return list
should be return newlist
.