pythondjangodjango-rest-frameworkdjango-serializer

Django Rest Framework Many To Many serializer problem


I'm creating an API in Django Rest Framework and I have a problem with a ManyToMany relation.

I'm trying to create an movie and assign genre to it (ManyToMany relationship between movie and genre).

Here are my models:

class Genre(models.Model):
    id = models.IntegerField(primary_key=True)
    name = models.CharField(null=False,blank=False,max_length=50)
    
    def __str__(self):
        return self.name

class Movie(models.Model):
    id = models.IntegerField(primary_key=True,null=False,blank=False)
    adult = models.BooleanField(null=False,blank=False)
    backdrop_path = models.CharField(null=True,blank=True,max_length=200)
    budget = models.BigIntegerField (default=0)
    homepage = models.CharField(null=True,blank=True,max_length=200)
    original_language = models.CharField(max_length=10)
    original_title = models.CharField(max_length=200)
    overview = models.CharField(max_length=1000)
    poster_path = models.CharField(null=True,blank=True,max_length=200)
    release_date = models.DateField(null=True,blank=True,)
    revenue = models.BigIntegerField (default=0)
    runtime = models.IntegerField(null=True,blank=True)
    status = models.CharField(max_length=50)
    tagline = models.CharField(null=True,blank=True,max_length=100)
    title = models.CharField(max_length=200)
    genres = models.ManyToManyField(Genre,blank=True)
    
    def __str__(self):
        return self.title

here are my serializers:

class GenresSerializers(serializers.ModelSerializer):
    class Meta:
        model = Genre
        fields = ['id','name']
        
    def create(self,validated_data):
        
        instance, _ = Genre.objects.get_or_create(**validated_data)
        return instance
    
class MovieGenreSerializer(serializers.ModelSerializer):
    class Meta:
        model = Genre
        fields = ['id']
        
    def create(self, validated_data):
        instance, _ = Genre.objects.get(**validated_data)
        return instance
    
class MoviesSerializer(serializers.ModelSerializer):
    genres = GenresSerializers(many= True)
    
    class Meta:
        model = Movie
        fields = [ 'id', 'adult', 'backdrop_path', 'budget', 'homepage', 'original_language', 'original_title', 'overview', 'poster_path', 'release_date', 'revenue', 'runtime', 'status', 'tagline', 'title', 'genres'
            ]
        # extra_kwargs = {'id': {'read_only': True}}
        
        
    def create(self, validated_data):
        genres_data = validated_data.pop('genres', []) 
        
        genresInstances = []
        for genre in genres_data:
            genresInstances.append(Genre.objects.get(pk = genre['id']))
        movie = Movie.objects.create(**validated_data)
        movie.genre.set(genresInstances)
        return movie

and my viewsets:

class MovieViewSet(viewsets.ModelViewSet):
    queryset = Movie.objects.all()
    serializer_class = MoviesSerializer

When I try to create a new movie with this request body (I already have created genres):

{
  "id": 425,
  "adult": false,
  "backdrop_path": "/kK5OeulwVDniPgjNOGHvzcORzdG.jpg",
  "budget": 59000000,
  "homepage": "http://www.iceagemovies.com/films/ice-age",
  "original_language": "en",
  "original_title": "Ice Age",
  "overview": "With the impending ice age almost upon them, a mismatched trio of prehistoric critters – Manny the woolly mammoth, Diego the saber-toothed tiger and Sid the giant sloth – find an orphaned infant and decide to return it to its human parents. Along the way, the unlikely allies become friends but, when enemies attack, their quest takes on far nobler aims.",
  "poster_path": "/stQoHvo0q6ZcEkji2Z1wlAxKnDq.jpg",
  "release_date": "2002-03-10",
  "revenue": 383257136,
  "runtime": 81,
  "status": "Released",
  "tagline": "They came. They thawed. They conquered.",
  "title": "Ice Age",
  "genres": [
    {
      "id": 16
    },
    {
      "id": 35
    },
    {
      "id": 10751
    },
    {
      "id": 12
    }

  ]
}

it return like this:

{
  "genres": [
    {
      "id": [
        "genre with this id already exists."
      ]
    },
    {
      "id": [
        "genre with this id already exists."
      ]
    },
    {
      "id": [
        "genre with this id already exists."
      ]
    },
    {
      "id": [
        "genre with this id already exists."
      ]
    }
  ]
}

EDIT:

edit in admin site

i have edit in admin site, add, delete genre to exist movie and save but it not save and the database not change


Solution

  • Don't use a subserializer, use for example a SlugRelatedFieldSerializer [drf-doc]:

    class MoviesSerializer(serializers.ModelSerializer):
        genres = serializers.SlugRelatedField(
            many=True, queryset=Genre.objects.all(), slug_field='name'
        )
    
        class Meta:
            model = Movie
            fields = [
                'id',
                'adult',
                'backdrop_path',
                'budget',
                'homepage',
                'original_language',
                'original_title',
                'overview',
                'poster_path',
                'release_date',
                'revenue',
                'runtime',
                'status',
                'tagline',
                'title',
                'genres',
            ]
            # extra_kwargs = {'id': {'read_only': True}}
    
        # no create

    You create the items then with:

    {
        # …,
        "genres": ["horror", "music", "musical"]
    }