Home > Software design >  Django RestFramework nested serializer field many=false
Django RestFramework nested serializer field many=false

Time:03-24

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?

CodePudding user response:

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

CodePudding user response:

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
  • Related