Arghya Saha
Arghya Saha

Reputation: 5733

Django Rest Framework not working with simple nested serializer

I was converting my existing app to api based. The structure of my file is as follow:

models.py

class BookDetail(models.Model):
    title= models.CharField(max_length=10, default='title')
    author= models.CharField(max_length=10)
    series= models.CharField(max_length=10)
    edition= models.CharField(max_length=10)
    description= models.CharField(max_length=10)
    keywords= models.CharField(max_length=10)
    reading_age= models.CharField(max_length=10)
    genre= models.CharField(max_length=10)
    publishing_rights= models.CharField(max_length=10)

    def __str__(self):
        return self.title


class Addon(models.Model):
    header= models.CharField(max_length=10)
    footer= models.CharField(max_length=10, default='1')
    additional_features = models.ForeignKey(BookDetail, related_name='additional_features',
                                            on_delete=models.CASCADE, null=True)

    def __str__(self):
        return self.header

views.py

class BookDetailsList(APIView):

    def get(self, request):
        stocks = BookDetail.objects.all()
        serializers = BookDetailsSerializer(stocks, many=True)
        return Response(serializers.data)

    def post(self, request):
        serializer = BookDetailsSerializer(data=request.data)
        print(serializer.is_valid())
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

serializer.py

class AddonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Addon
        fields = ('header', 'footer')


class BookDetailsSerializer(serializers.ModelSerializer):
    additional_features = AddonSerializer(many=True)
    class Meta:
        model = BookDetail
        fields = ('title', 'author','series', 'edition',
                  'description', 'keywords', 'reading_age',
                  'genre', 'publishing_rights','additional_features')
        # fields = '__all__'

    def create(self, validated_data):
        additional_feature = validated_data.pop('additional_features')
        book_detail = BookDetail.objects.create(**validated_data)
        for a in additional_feature:
            Addon.objects.create(additional_features=book_detail, **a)
        # Addon.objects.create(additional_features=book_detail, **additional_feature)
        return book_detail

My input data is in JSON format

{
    "title": "lolwa",
    "author": "asd",
    "series": "string",
    "edition": "a",
    "description": "as",
    "keywords": "sd",
    "reading_age": "aasd",
    "genre": "adasda",
    "publishing_rights": "aadasd",
    "additional_features": [{"header":"head",
                             "footer":"foot"}]
  }

This is working fine. But what I really want is not passing my additional_features as a list which I'm currently doing, I want to pass it like this.

    "additional_features": {"header":"head",
                             "footer":"foot"}

But my code throws error when I'm trying to pass it like this. I made the following changes in my serializer.py

class BookDetailsSerializer(serializers.ModelSerializer):
    additional_features = AddonSerializer(many=False)
    # additional_features = AddonSerializer(many=True)
    class Meta:
        model = BookDetail
        fields = ('title', 'author','series', 'edition',
                  'description', 'keywords', 'reading_age',
                  'genre', 'publishing_rights','additional_features')
        # fields = '__all__'

    def create(self, validated_data):
        additional_feature = validated_data.pop('additional_features')
        book_detail = BookDetail.objects.create(**validated_data)
        # for a in additional_feature:
        #     Addon.objects.create(additional_features=book_detail, **a)
        Addon.objects.create(additional_features=book_detail, **additional_feature)
        return book_detail

Made many=False and removed the for loop. Since both of them, **a and **additional_feature are

OrderedDict([('header', 'head'), ('footer', 'foot')])

I don't see a reason why it is failing.

This is the stack trace of the error

Internal Server Error: /bookdetails/
Traceback (most recent call last):
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/rest_framework/fields.py", line 444, in get_attribute
    return get_attribute(instance, self.source_attrs)
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/rest_framework/fields.py", line 103, in get_attribute
    instance = getattr(instance, attr)
AttributeError: 'RelatedManager' object has no attribute 'header'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/django/core/handlers/base.py", line 149, in get_response
    response = self.process_exception_by_middleware(e, request)
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/django/core/handlers/base.py", line 147, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/rest_framework/views.py", line 483, in dispatch
    response = self.handle_exception(exc)
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/rest_framework/views.py", line 443, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/rest_framework/views.py", line 480, in dispatch
    response = handler(request, *args, **kwargs)
  File "/Users/argo/Django/pagination-backend/backend/publishbook/views.py", line 21, in post
    return Response(serializer.data, status=status.HTTP_201_CREATED)
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/rest_framework/serializers.py", line 531, in data
    ret = super(Serializer, self).data
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/rest_framework/serializers.py", line 262, in data
    self._data = self.to_representation(self.instance)
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/rest_framework/serializers.py", line 500, in to_representation
    ret[field.field_name] = field.to_representation(attribute)
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/rest_framework/serializers.py", line 487, in to_representation
    attribute = field.get_attribute(instance)
  File "/Users/argo/Django/pagination-backend/env/lib/python3.5/site-packages/rest_framework/fields.py", line 463, in get_attribute
    raise type(exc)(msg)
AttributeError: Got AttributeError when attempting to get a value for field `header` on serializer `AddonSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `RelatedManager` instance.
Original exception text was: 'RelatedManager' object has no attribute 'header'.

Upvotes: 0

Views: 490

Answers (1)

zaidfazil
zaidfazil

Reputation: 9245

From the structure of your models, a BookDetail object can have more than one Addon instances, since, Addon model have a foreign key to BookDetail.

If you want to have more than one Addons for a particular BookDetail, you cant update the Addon instance without using a list.

But, if its not the case, then I would recommend using a OneToOne relation instead of Foreign Key. Actually, its pretty ugly using a OneToOne field, rather you can just add the fields into your parent model(BookDetail), which can be defaulted to null, if there is None.

Upvotes: 1

Related Questions