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

scyqe7ek  于 2022-12-20  发布在  Go
关注(0)|答案(7)|浏览(129)

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/ www.example.com上的未实现错误StringRelatedField.to_内部值()。

zpf6vheq

zpf6vheq1#

假设您在MovieSerializer中使用StringRelatedField,如下所示:

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定义了对象类型是如何显示的,例如在表单中,这里它只返回对象类型,即__str__的输出。
方法to_representation定义了对象Genre在输出中的显示方式(JSON或XML)。它与前面的方法非常相似,但这里我们必须显式地将Genre转换为字符串。当然,您可以根据自己的要求创建更复杂的输出。
方法to_internal_value通过获取给定值的对象Genre来解决实际问题,如果这里有更复杂的方法to_representation,则需要扩展逻辑来获取适当的对象。
使用这种方法,您可以按照所需的格式发布JSON,指定流派名称而不是它们的id。
我希望这个例子对其他人也有帮助。

rwqw0loc

rwqw0loc2#

重写序列化程序的**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
rkkpypqq

rkkpypqq3#

一个解决方案是重写序列化器中的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,并按名称获取其中的对象,然后在父序列化程序中将其用作输入。

mrfwxfqh

mrfwxfqh4#

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

class Genre(models.Model):
    name = models.CharField(max_length=30, primary_key=True)

这在这里并不重要,但这也将在数据库中保存一列,因为自动生成的id列将消失:)

    • 更新**

在对这个答案的评论中进行了一些讨论之后,我发现提到使用ANY类型作为主键是很重要的,您还应该避免在以后更改该字段。
这是因为对主键的更改也需要更新指向该主键的所有外键,并且(从这个问题的Angular 来说)即使包含流派的表相对较小,也可能有大量的电影指向每个流派。

raogr8fs

raogr8fs5#

如果你总是把name传递给你的序列化器,你可以在Model defination中添加外键字段。

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)
blmhpbnm

blmhpbnm6#

使用SerializerMethodField,则无需修改您的模型。

xzabzqsa

xzabzqsa7#

您可以使用序列化程序字段方法。这将保留模型上的PK声明,并且您可以排除ForeignKey的字段。
示例:

class ModelSerializer(serializers.ModelSerializer):
    """
    Example Serializer.
    """

    foreignkey_name: Union[SerializerMethodField, str] = SerializerMethodField()

    @staticmethod
    def get_foreignkey_name(instance: Product) -> str:
        """
        Get the foreignkey.name value.

        Parameters
        ----------
        instance: Model

        Returns
        -------
        str
        """
        try:
            return instance.product_type.name
   
        except AttributeError:
            return "Product Type field name has no value"

    class Meta:
        """
        Set the foreignkey model and fields.
        """

        model: Type[ForeignKeyModel] = ForeignKeyModel
        exclude: tuple[str, str] = foreignkey_field # this will exclude the default foreignkey field in the serializer that contains the FK pk.

相关问题