Search code examples
pythondjangodjango-rest-frameworkdjango-serializer

Multiple Nested Serializers from Different Models with Django Rest Framework


My end goal is a json response similar to:

        {
            "card_id": "72e81de94dfc0373b006ca75e9c851a1",
            "item_id": "A.J.Burnett11610311269941028099991216231498183825508164480371614",
            "name": "A.J. Burnett",
            "playerattribute": {
                "team": "Marlins",
                "contact_l": 16,
                "power_l": 14
            },
            "playermarketlisting": {
                "buy": 420,
                "sell": 69,
            }
        },

Here is my model.py (simplified for the sake of this question):

class PlayerProfile(models.Model):
    card_id = models.CharField(max_length=120, unique=True, primary_key=True)
    item_id = models.CharField(max_length=120) # used for matching with attributes table
    name = models.CharField(max_length=120)


class PlayerAttribute(models.Model):
    player_profile = models.OneToOneField(
        PlayerProfile,
        on_delete=models.CASCADE,
    )
    team = models.CharField(max_length=120)
    contact_l = models.IntegerField()
    power_l = models.IntegerField()


class PlayerMarketListing(models.Model):
    player_profile = models.OneToOneField(
        PlayerProfile,
        on_delete=models.CASCADE,
    )
    buy = models.IntegerField()
    sell = models.IntegerField()

Here are the serializers.

class PlayerMarketListingForProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = PlayerMarketListing
        fields = (
            'buy', 
            'sell', 
        )

class PlayerAttributeForProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = PlayerAttribute
        fields = (
            'team', 
            'contact_l', 
            'power_l'
        )

class PlayerProfileSerializer(serializers.ModelSerializer):
    playermarketlisting = PlayerMarketListingForProfileSerializer()
    playerattribute = PlayerAttributeForProfileSerializer()
    class Meta:
        model = PlayerProfile
        fields = (
            'card_id',
            'item_id',
            'name',
            'playermarketlisting'
            'playerattribute'
        )

Relevant view:

class PlayerProfileView(viewsets.ModelViewSet):
    serializer_class = PlayerProfileSerializer
    queryset = PlayerProfile.objects.all()  
    filter_backends = (filters.DjangoFilterBackend,)
    filterset_class = PlayerProfileFilter

    def get_queryset(self):
        queryset = super(PlayerProfileView, self).get_queryset()

        order_by = self.request.query_params.get('order_by', '')
        if order_by:
            order_by_name = order_by.split(' ')[1]
            order_by_sign = order_by.split(' ')[0]
            order_by_sign = '' if order_by_sign == 'asc' else '-'
            queryset = queryset.order_by(order_by_sign + order_by_name)

        return queryset

However when I try to view the API, I get the following error:

The field 'playermarketlisting' was declared on serializer PlayerProfileSerializer, but has not been included in the 'fields' option.

If I only use one of playermarketlisting or playerattribute, it works fine. But it fails when using both. How can I use multiple nested serializers to achieve the desired JSON output?

Edit

Full traceback:

Environment:


Request Method: GET
Request URL: http://127.0.0.1:8000/api/player-profiles/

Django Version: 3.0.8
Python Version: 3.8.1
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django_filters',
 'corsheaders',
 'rest_framework',
 'api']
Installed Middleware:
['corsheaders.middleware.CorsMiddleware',
 'django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback (most recent call last):
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/viewsets.py", line 114, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/views.py", line 505, in dispatch
    response = self.handle_exception(exc)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/views.py", line 465, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
    raise exc
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/views.py", line 502, in dispatch
    response = handler(request, *args, **kwargs)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/mixins.py", line 43, in list
    return self.get_paginated_response(serializer.data)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 760, in data
    ret = super().data
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 260, in data
    self._data = self.to_representation(self.instance)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 677, in to_representation
    return [
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 678, in <listcomp>
    self.child.to_representation(item) for item in iterable
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 514, in to_representation
    for field in fields:
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 375, in _readable_fields
    for field in self.fields.values():
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/django/utils/functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 363, in fields
    for key, value in self.get_fields().items():
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 1038, in get_fields
    field_names = self.get_field_names(declared_fields, info)
  File "/Users/jeremyenglert/Documents/Sites/showzone/.venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 1134, in get_field_names
    assert field_name in fields, (

Exception Type: AssertionError at /api/player-profiles/
Exception Value: The field 'playermarketlisting' was declared on serializer PlayerProfileSerializer, but has not been included in the 'fields' option.


Solution

  • You are missing a coma:

    class PlayerProfileSerializer(serializers.ModelSerializer):
        playermarketlisting = PlayerMarketListingForProfileSerializer()
        playerattribute = PlayerAttributeForProfileSerializer()
        class Meta:
            model = PlayerProfile
            fields = (
                'card_id',
                'item_id',
                'name',
                'playermarketlisting', #### here
                'playerattribute'
            )