在Django REST Framework中代替主键发送不同的字段

时间:2019-03-14 11:13:19

标签: django django-rest-framework

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()。

5 个答案:

答案 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)