serializers.py
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = [
'popularity',
'director',
'genre',
'imdb_score',
'name',
]
views.py
class MovieList(generics.ListCreateAPIView):
queryset = Movie.objects.all().order_by('-id')[:10]
serializer_class = MovieSerializer
permission_classes = (IsAuthenticated,)
def list(self, request):
queryset = self.get_queryset()
serializer = MovieSerializer(queryset, many=True)
return Response(serializer.data)
def post(self, request, format=None):
data = request.data
if isinstance(data, list):
serializer = self.get_serializer(data=request.data, many=True)
else:
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
我这样发送数据,它可以正常工作。
{
"popularity": 83.0,
"director": "Victor Fleming",
"genre": [
1,
2,
3,
4
],
"imdb_score": 8.3,
"name": "The Wizard of Oz"
}
但是我想像这样发送数据:
{
"popularity": 83.0,
"director": "Victor Fleming",
"genre": [
"Adventure",
"Family",
"Fantasy",
"Musical"
],
"imdb_score": 8.3,
"name": "The Wizard of Oz"
}
请参阅流派列表-我发送的是name
,而不是主键。
这些是我的模特:
class Genre(models.Model):
name = models.CharField(max_length=30, unique=True) # make unique
def __str__(self):
return self.name
class Movie(models.Model):
popularity = models.FloatField(max_length=10)
director = models.CharField(max_length=30)
genre = models.ManyToManyField(Genre)
imdb_score = models.FloatField(max_length=10)
name = models.CharField(max_length=30)
即使我列出数据或获取数据,它也应该发送流派名称而不是ID。我尝试使用StringRelatedField
,但它给了我错误。
/ api / movie /中的NotImplementedError 必须实现StringRelatedField.to_internal_value()。
答案 0 :(得分:3)
覆盖如下序列化程序的 create()
方法,
class MovieSerializer(serializers.ModelSerializer):
genre = serializers.ListSerializer(child=serializers.CharField())
class Meta:
model = Movie
fields = [
'popularity',
'director',
'genre',
'imdb_score',
'name',
]
def create(self, validated_data):
genre = validated_data.pop('genre',[])
movie = super().create(validated_data)
genre_qs = Genre.objects.filter(name__in=genre)
movie.genre.add(*genre_qs)
return movie
答案 1 :(得分:2)
一种解决方案是覆盖序列化程序中的genre
字段,以接受如下所示的字符串列表:
class MovieSerializer(serializers.ModelSerializer):
genre = serializer.ListField(child=serializers.CharField())
class Meta:
model = Movie
fields = [
'popularity',
'director',
'genre',
'imdb_score',
'name',
]
def validate(self, data):
genre = data.get('genre', [])
genre_obj_list = [Genre.objects.get(name=name) for name in genre.all()]
data.update({'genre': genre_obj_list})
return data
然后在validate方法上,尝试按名称获取每个对象并放入新列表中,并使用新的对象列表更新data
结果。 (我知道这不是最干净的解决方案,但是效果很好)
您还可以尝试使用MethodSerializer
或定义GenreSerializer
并通过对象名称获取对象,然后在父级序列化程序中将其用作输入。
答案 2 :(得分:1)
假设您在StringRelatedField
中使用MovieSerializer
是这样的:
class MovieSerializer(serializers.ModelSerializer):
genre = serializers.StringRelatedField(many=True)
class Meta:
model = Movie
fields = [
'popularity',
'director',
'genre',
'imdb_score',
'name',
]
检索电影列表时,结果如下所示:
[
{
"popularity": 83.0,
"director": "Victor Fleming",
"genre": [
"Adventure",
"Family",
"Fantasy",
"Musical"
],
"imdb_score": 8.3,
"name": "The Wizard of Oz"
}
]
但是,如果您要创建新电影,则将无法正常工作,因为StringRelatedField
是只读的。
不过,您可以创建与自定义相关的字段。
这是完整的 serializers.py
:
from rest_framework import serializers
from .models import Genre, Movie
class GenreRelatedField(serializers.RelatedField):
def display_value(self, instance):
return instance
def to_representation(self, value):
return str(value)
def to_internal_value(self, data):
return Genre.objects.get(name=data)
class MovieSerializer(serializers.ModelSerializer):
genre = GenreRelatedField(
queryset=Genre.objects.all(),
many=True
)
class Meta:
model = Movie
fields = (
'popularity',
'director',
'genre',
'imdb_score',
'name',
)
这是一个简单的示例,可以通过多种方式进行高度自定义。
方法display_value
定义对象Genre的显示方式,例如以表格形式。在这里,它仅返回对象流派,即__str__
的输出。
方法to_representation
定义对象Genre在输出(JSON或XML)中的显示方式。它与先前的方法非常相似,但是在这里我们必须明确地将Genre转换为字符串。当然,您可以根据需要创建更复杂的输出。
方法to_internal_value
通过获取给定值的对象Genre解决了您的实际问题。如果您有更复杂的方法to_representation
,则需要扩展逻辑以获取适当的对象。
使用这种方法,您可以按期望的形式发布JSON,指定类型名称而不是其ID。
我希望这个例子也能帮助其他人。
答案 3 :(得分:1)
简单的解决方案是将Genre
模型更改为使用name
作为主键,如下所示:
class Genre(models.Model):
name = models.CharField(max_length=30, primary_key=True)
并不是很重要,但这也会在数据库中保存一列,因为自动生成的id
列会消失:)
更新
在对该答案的评论中进行了一些讨论之后,我发现有必要提及使用ANY类型作为主键,您也应该避免在此后更改该字段。
这是因为更改主键也需要更新所有指向该主键的外键,并且(就这个问题而言)即使您的流派表可能相对较小,您可能有大量指向每种类型的电影。
答案 4 :(得分:0)
如果始终将名称传递给序列化程序,则可以在“模型定义”中添加外键字段。 link您的情况
class Movie(models.Model):
popularity = models.FloatField(max_length=10)
director = models.CharField(max_length=30)
genre = models.ManyToManyField(Genre, db_column='name')
imdb_score = models.FloatField(max_length=10)
name = models.CharField(max_length=30)