0

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.

  1. Why is there an error? 'list' is provided in the code as per the example. It's a UUID, does that need special handling?

  2. 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.

Little Brain
  • 2,647
  • 1
  • 30
  • 54
  • 1
    You have this in your create method `return list`. Can't actually see where you declared the `list` you're returning so it is most likely just returning the native Python list. And you shouldn't be using attributes like this that overshadow the Python keywords in the first place as you'll be shooting yourself on the foot most times. In many languages, it won't even run. – Ken4scholars Jan 20 '19 at 22:08
  • Thanks! I changed 'list' to 'newList' when I realised 'list' was a reserved word, but I missed the return value. Changing it to 'newList' fixes the problem. – Little Brain Jan 21 '19 at 12:08

1 Answers1

1

Issue is with your ItemSerializer, in fields it's containing the list field, and you are not passing that value, it will create inside create method of ListSerializer. so at validation time it check for value, that why it's return the validation error.

class ItemSerializer(serializers.ModelSerializer):
   class Meta:
      model = Item
      fields = ('id', 'title', 'description', 'slug', 'modified_at', 'order')
aman kumar
  • 3,086
  • 1
  • 17
  • 24