在Django REST框架中发送不同的字段,而不是主键

在Django REST框架中发送不同的字段,而不是主键,django,django-rest-framework,Django,Django Rest Framework,序列化程序.py views.py 我像这样发送数据,它工作正常 { "popularity": 83.0, "director": "Victor Fleming", "genre": [ 1, 2, 3, 4 ], "imdb_score": 8.3, "name": "The Wizard of Oz" } 但我想发送的数据有点像这样: { "popularity": 83.0, "direc

序列化程序.py

views.py

我像这样发送数据,它工作正常

{
   "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"
}
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
class MovieSerializer(serializers.ModelSerializer):
    genre = serializers.StringRelatedField(many=True)

    class Meta:
        model = Movie
        fields = [
            'popularity',
            'director',
            'genre',
            'imdb_score',
            'name',
        ]
查看流派列表-我发送的不是主键而是
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/ 必须实现StringRelatedField.to_internal_value()


解决方案之一是覆盖序列化程序中的
类型
字段,以接受如下字符串列表:

{
   "popularity": 83.0,
   "director": "Victor Fleming",
   "genre": [
      "Adventure",
      "Family",
      "Fantasy",
      "Musical"
   ],
   "imdb_score": 8.3,
   "name": "The Wizard of Oz"
}
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
class MovieSerializer(serializers.ModelSerializer):
    genre = serializers.StringRelatedField(many=True)

    class Meta:
        model = Movie
        fields = [
            'popularity',
            'director',
            'genre',
            'imdb_score',
            'name',
        ]
在validate方法中,尝试根据每个对象的名称获取它们,并将它们放入一个新列表中,然后用新的对象列表更新
数据
结果。(我知道这不是最干净的解决方案,但效果很好)


您还可以尝试使用
MethodSerializer
或定义一个
GenreSerializer
,通过名称获取其中的对象,并在父序列化程序中将其用作输入。

如果您始终将名称传递给序列化程序,则可以在模型定义中添加外键字段。你的情况

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)

重写序列化程序的
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
类MovieSerializer(serializers.ModelSerializer):
genre=serializers.ListSerializer(child=serializers.CharField())
类元:
模型=电影
字段=[
"人气",,
“董事”,
“体裁”,
“imdb_分数”,
“姓名”,
]
def创建(自我验证的_数据):
流派=已验证的_data.pop('流派',[])
movie=super().创建(已验证的\u数据)
genre\u qs=genre.objects.filter(name\u in=genre)
电影。流派。添加(*流派)

返回电影
假设您在
MovieSerializer
中使用了
StringRelatedField
,如下所示:

{
   "popularity": 83.0,
   "director": "Victor Fleming",
   "genre": [
      "Adventure",
      "Family",
      "Fantasy",
      "Musical"
   ],
   "imdb_score": 8.3,
   "name": "The Wizard of Oz"
}
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
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
是只读的

但是,您可以创建自定义相关字段

这是完整的序列化程序.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\u value
定义对象类型的显示方式,例如在表单中。在这里,它只返回对象类型,即
\uuu str\uuu
的输出

方法
to_representation
定义了对象类型在输出中的显示方式(JSON或XML)。它与前面的方法非常相似,但这里我们必须显式地将流派转换为字符串。当然,您可以根据需要创建更复杂的输出

方法
to_internal_value
通过获取给定值的对象类型来解决实际问题。如果您有一个更复杂的方法
来表示
,那么您需要扩展逻辑来获得适当的对象

使用这种方法,您可以以所需的形式发布JSON,指定流派名称而不是它们的ID


我希望这个例子也能帮助其他人。

简单的解决方案是将
流派
模型更改为使用
名称
作为主键,如下所示:

类类型(models.Model):
name=models.CharField(最大长度=30,主键=True)
这并不重要,但这也将在数据库中保存一列,因为自动生成的
id
列将消失:)


更新

在对这个答案的评论中进行了一些讨论之后,我发现重要的一点是要提到,使用任何类型作为主键,之后也应该避免更改该字段


这是因为对主键的更改还需要更新指向该主键的所有外键,并且(就这个问题而言)即使您的带有类型的表可能相对较小,你可能有大量的电影指向每种类型。

请展示你的
GenreSerializer
@cezar没有`GenreSerializer``你的genre
name
是唯一的吗?是的,它是唯一的@shakil你在
MovieSerializer
中使用过
StringRelatedField
吗:
name=serializers.StringRelatedField(many=True)
类元之前
?它在/api/movie/'ManyRelatedManager'对象处给出错误-
TypeError不可编辑
@Huzaifsayed尝试
[color.objects.get(name=name)获取流派中的名称。all()
不应
验证()
在某个时候验证其他字段?您的回答几乎是正确的。请参阅已接受的答案。我已解决问题。问题是我现在添加到答案中的
genre.all()
。我想这实际上是一个有效的答案。相对解释。这是非常令人失望的,有人试图把一个价值,即使这是不相关或不适当的,你认为你应该总是把评论,然后你可以投反对票。不加评论的否决票非常令人失望。我支持你@Shakil@HuzaifSayyed你不应该投这个票。如果你认为我的答案是增值,那就这么做吧。谢谢你的支持。我觉得你的回答很有帮助,这就是为什么我投你的票。可能不是答案是的,这会起作用,但使用字符串作为主键是一种非常糟糕的做法。是的,我同意@cezar。但我不想我的结构被破坏destroy@cezar为什么这是一种“非常糟糕的做法”?有时,方便性和易用性胜过其他一切。在这种情况下,我觉得更重要的是:你打算增加多少种风格?当然不是几百万,甚至不是几千,而且你已经将字段限制为30个字符,这比