0

how can I create a nested serializer field without using (many=True)? The following code works fine:

from music.models import Track, Album
from rest_framework import serializers

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Track
        fields = ['order', 'title', 'duration']

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

    def create(self, validated_data):
        tracks_data = validated_data.pop('tracks')
        album = Album.objects.create(**validated_data)
        for track_data in tracks_data:
            Track.objects.create(album=album, **track_data)
        return album

This json works fine:

{
    "album_name": "Black Album",
    "artist": "Metallica",
    "tracks": [
        {
            "order": 1,
            "title": "Enter Sandman",
            "duration": 245
        },
        {
            "order": 2,
            "title": "Sad but True",
            "duration": 264
        },
        {
            "order": 3,
            "title": "The Unforgiven",
            "duration": 159
        }
    ]
}

but I need to get this json working, one object, without the square brackets []:

    {
        "album_name": "Black Album",
        "artist": "Metallica",
        "tracks": 
            {
                "order": 1,
                "title": "Enter Sandman",
                "duration": 245
            }
        }

I've tried to remove the (many=True) but I receive either the following error:

create() argument after ** must be a mapping, not str

models:

from django.db import models

class Album(models.Model):
    album_name = models.CharField(max_length=100)
    artist = models.CharField(max_length=100)

class Track(models.Model):
    album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
    order = models.IntegerField()
    title = models.CharField(max_length=100)
    duration = models.IntegerField()

    class Meta:
        unique_together = ['album', 'order']
        ordering = ['order']

    def __str__(self):
        return '%d: %s' % (self.order, self.title)

views.py

from rest_framework import viewsets
from music.serializers import AlbumSerializer
from music.models import Album


class STMusic(viewsets.ModelViewSet):

    serializer_class = AlbumSerializer
    queryset = Album.objects.all()

How to fix it?

2
  • I haven't tested this, but i think the problem is when you specify many=True, django expect that the data as a list and he return the data as a list for u, but when you remove the many=True, he expect the data as an object, so he don't return the data as list and when you loop through the object you will get error, because the ojbect is string not a list. Commented Mar 23, 2022 at 16:33
  • I think you are still using for loop in create method after change many=False. Please check. Commented Mar 23, 2022 at 19:48

2 Answers 2

1
def create(self, validated_data):
    track_data = validated_data.pop('tracks')
    album = Album.objects.create(**validated_data)
    Track.objects.create(album=album, **track_data)
    return album
Sign up to request clarification or add additional context in comments.

Comments

1

Ok I found the solution based on the comments from tsantor on this other post: Django Rest Framework: AttributeError when Serializer many=False, but not when many=True

It seems if you are using a ForeignKey relationship on your model you need to add (many=True) to your serializer as DRF creates a list based on the OneToMany relationship. If you need to POST only one object, you need to use a OneToOne relationship in your model (which makes sense) so that DRF expects only one object and not a list.

So the working code is:

models.py

from django.db import models

class Album(models.Model):
    album_name = models.CharField(max_length=100)
    artist = models.CharField(max_length=100)

class Track(models.Model):
    album = models.OneToOneField(Album, related_name='track', on_delete=models.CASCADE)
    order = models.IntegerField()
    title = models.CharField(max_length=100)
    duration = models.IntegerField()

    class Meta:
        unique_together = ['album', 'order']
        ordering = ['order']

    def __str__(self):
        return '%d: %s' % (self.order, self.title)

serializer.py

class AlbumSerializer(serializers.ModelSerializer):
    track = TrackSerializer()

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'track']

    def create(self, validated_data):
        track_data = validated_data.pop('tracks')
        album = Album.objects.create(**validated_data)
        Track.objects.create(album=album, **track_data)
        return album

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.