Django多对多序列化器Post API报错:AttributeError求助
多对多关联(通过中间表Cast)创建电影接口AttributeError问题排查与解决
问题场景
通过Cast中间表在Movie和Person模型间建立多对多关联,使用CastSerializer实现POST接口创建新电影及演员关联,但调用接口时返回AttributeError。
期望请求体格式
{ ... "title": "string", "genres": [ 0 ], "cast": [ { "charactor": "string", "cast": 0, "order": 2147483647 } ] }
报错信息
AttributeError at /movie/ Got AttributeError when attempting to get a value for field `cast` on serializer `MoviesSerializer`. The serializer field might be named incorrectly and not match any attribute or key on the `Movie` instance. Original exception text was: 'Movie' object has no attribute 'person'.
模型代码
class Movie(models.Model): ... cast = models.ManyToManyField(Person,related_name='cast_movies', through="Cast") def __str__(self): return self.title class Cast(models.Model): id = models.AutoField(primary_key=True,null=False,blank=False) movie = models.ForeignKey(Movie,on_delete=models.CASCADE,related_name='casts') cast = models.ForeignKey(Person,on_delete=models.CASCADE,related_name='person') charactor = models.CharField(max_length=200,null=True,blank=True,default='') order = models.IntegerField(null=True) def __str__(self): return self.cast.name + " ("+ self.movie.title + ")"
序列化器代码
class CastSerializer(serializers.ModelSerializer): class Meta: model = Cast fields = ['charactor','cast','order'] class MoviesSerializer(serializers.ModelSerializer): genres = serializers.SlugRelatedField( many=True, queryset=Genre.objects.all(),slug_field='id' ) cast = CastSerializer(many = True) class Meta: model = Movie fields = [ ...,'cast'] def create(self, validated_data): genres_data = validated_data.pop('genres', []) cast_data = validated_data.pop('cast', []) movie = Movie.objects.create(**validated_data) for cast in cast_data: Cast.objects.create(movie_id = movie.id,**cast) genresInstances = [] for genre in genres_data: genresInstances.append(genre) movie.genres.set(genresInstances) return movie
问题原因
- 序列化器字段映射冲突:MoviesSerializer中定义的
cast = CastSerializer(many=True),与Movie模型的cast多对多字段(关联Person)重名。序列化器尝试读取Movie实例的cast字段时,会获取Person集合,但CastSerializer期望的是Cast中间表实例,导致逻辑混淆。 - 反向关联名称冲突:Cast模型中指向Person的字段
cast的related_name设为person,名称过于通用,引发ORM解析关联时的混淆,直接导致报错中'Movie' object has no attribute 'person'的问题。
修复步骤
1. 修改模型反向关联名称
将Cast模型中指向Person字段的related_name改为更清晰的名称,避免冲突:
class Cast(models.Model): id = models.AutoField(primary_key=True,null=False,blank=False) movie = models.ForeignKey(Movie,on_delete=models.CASCADE,related_name='casts') # 重命名related_name,避免与其他逻辑冲突 cast = models.ForeignKey(Person,on_delete=models.CASCADE,related_name='cast_roles') charactor = models.CharField(max_length=200,null=True,blank=True,default='') order = models.IntegerField(null=True) def __str__(self): return self.cast.name + " ("+ self.movie.title + ")"
2. 调整序列化器字段映射
在MoviesSerializer中,使用Movie模型通过Cast中间表的反向关联casts(对应Cast模型中movie字段的related_name),并通过source参数映射到请求体的cast字段:
class MoviesSerializer(serializers.ModelSerializer): genres = serializers.SlugRelatedField( many=True, queryset=Genre.objects.all(),slug_field='id' ) # 使用source指定从反向关联casts读取数据,保持请求体字段名不变 cast = CastSerializer(source='casts', many=True) class Meta: model = Movie fields = [ ...,'cast'] def create(self, validated_data): genres_data = validated_data.pop('genres', []) cast_data = validated_data.pop('cast', []) movie = Movie.objects.create(**validated_data) # 直接关联movie实例,无需手动传movie_id for cast in cast_data: Cast.objects.create(movie=movie,**cast) # 简化genres关联逻辑 movie.genres.set(genres_data) return movie
3. 可选:增强字段验证
为确保请求中cast对应的Person存在,可在CastSerializer中显式指定字段类型:
class CastSerializer(serializers.ModelSerializer): cast = serializers.PrimaryKeyRelatedField(queryset=Person.objects.all()) class Meta: model = Cast fields = ['charactor','cast','order']
内容的提问来源于stack exchange,提问作者12341413451354134
相关产品推荐
相关产品推荐

