Django Rest Framework POST 和 GET 嵌套序列化器

ev3*_*350 5 django django-rest-framework

我一直在为看板式项目板开发自己的 API。我附上了一个 UML 图来展示“boards”应用程序是如何组织的。

我的应用程序的模型 UML 图

我的问题是,当我想创建一张新卡时,我希望能够创建带有在 POST 参数中传递的主键标签列表的卡,如下所示:

{
    "title": "Test Card",
    "description": "This is a Test Card!",
    "created_by": 1,
    "labels": [1,2]
}
Run Code Online (Sandbox Code Playgroud)

我的另一个要求是我想检索序列化标签作为卡片对象的一部分,如下所示:

{
    "id": 1,
    "board": 1,
    "title": "Some Card",
    "description": "The description of Some Card.",
    "created_by": 1,
    "assignees": [
        {
            "id": 1,
            "username": "test1",
            "email": "test1_user@hotmail.co.uk"
        }
    ],
    "labels": [
        {
            "id": 1,
            "board": 1,
            "title": "Pink Label",
            "color": "#f442cb"
        }
    ],
    "comment_set": []
}
Run Code Online (Sandbox Code Playgroud)

我假设要实现 POST 和 GET 功能的这种差异,我必须有 2 个不同的序列化器?

然而,这篇文章的主要问题与上面提到的 POST 数据的创建逻辑有关。我不断收到这样的错误:

{
    "labels": [
        {
            "non_field_errors": [
                "Invalid data. Expected a dictionary, but got int."
            ]
        },
        {
            "non_field_errors": [
                "Invalid data. Expected a dictionary, but got int."
            ]
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

我在 CardSerializer 中尝试了许多不同的 DRF 序列化器组合,但最终总是出现与上面格式相同的错误消息:“预期但得到”。任何帮助或指示,甚至有人告诉我这是糟糕的 REST 设计,将不胜感激!:)

编辑:我应该补充一点,如果我将 CardSerializer 标签字段从 LabelSerializer 更改为 PrimaryKeyRelatedField (如代码中的注释所示),我会收到以下错误:

禁止直接分配到多对多集的前端。使用 labels.set() 代替。

以下是我的源代码的相关部分:

模型.py

class Card(models.Model):
"""Represents a card."""

    # Parent
    board = models.ForeignKey(Board, on_delete=models.CASCADE)
    column = models.ForeignKey(Column, on_delete=models.CASCADE, null=True)

    # Fields
    title = models.CharField(max_length=255, null=False)
    description = models.TextField()

    assignees = models.ManyToManyField(User, blank=True, related_name='card_assignees')
    labels = models.ManyToManyField(Label, blank=True, related_name='card_labels')

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(blank=True, null=True)  # Blank for django-admin

    created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='card_created_by')
Run Code Online (Sandbox Code Playgroud)

视图.py

class CardList(generics.ListCreateAPIView):
    queryset = Card.objects.all()
    serializer_class = CardSerializer

    def get_queryset(self):
        columns = Column.objects.filter(board_id=self.kwargs['board_pk'])
        queryset = Card.objects.filter(column__in=columns)
        return queryset

    def post(self, request, *args, **kwargs):
        board = Board.objects.get(pk=kwargs['board_pk'])

        post_data = {
            'title': request.data.get('title'),
            'description': request.data.get('description'),
            'created_by': request.data.get('created_by'),
            'assignees': request.data.get('assignees'),
            'labels': request.data.get('labels'),
        }
        serializer = CardSerializer(data=post_data, context={'board': board})

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status.HTTP_201_CREATED)
        return Response(serializer.errors, status.HTTP_400_BAD_REQUEST)
Run Code Online (Sandbox Code Playgroud)

序列化器.py

class UserSerializer(serializers.ModelSerializer):
    """Serializer to map the User instance to JSON."""

    class Meta:
        model = User
        fields = ('id', 'username', 'email')


class CommentSerializer(serializers.ModelSerializer):
    """Serializer to map the Comment instance to JSON."""

    class Meta:
        model = Comment
        fields = '__all__'


class LabelSerializer(serializers.ModelSerializer):
    """Serializer to map the Label instance to JSON."""

    class Meta:
        model = Label
        fields = ('id', 'board', 'title', 'color')


class CardSerializer(serializers.ModelSerializer):
    """Serializer to map the Card instance to JSON."""
    assignees = UserSerializer(many=True, read_only=True)
    labels = LabelSerializer(many=True)
    comment_set = CommentSerializer(many=True, read_only=True)

    # assignees = PrimaryKeyRelatedField(many=True, read_only=True)
    # labels = PrimaryKeyRelatedField(many=True, queryset=Label.objects.all())

    def create(self, validated_data):
        board = self.context['board']
        card = Card.objects.create(
            board=board,
            **validated_data
        )
        return card

    class Meta:
        model = Card
        fields = ('id', 'board', 'title', 'description', 'created_by', 'assignees', 'labels', 'comment_set')
        read_only_fields = ('id', 'board')
Run Code Online (Sandbox Code Playgroud)

gis*_*gis 2

如果有人遇到同样的问题,那么这就是解决方案。我认为您需要创建两个序列化器类,一个用于请求get,另一个用于请求postviewset并从下面调用所需的序列化器,

class MyModelViewSet(viewsets.MyModelViewSet):

    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer # default serializer, you can change this to MyModelListSerializer as well

    action_serializers = {
        'list': MyModelListSerializer, # get request serializer
        'create': MyModelCreateSerializer # post request serializer
    }

    def get_serializer_class(self):

        if hasattr(self, 'action_serializers'):
            return self.action_serializers.get(self.action, self.serializer_class)

        return super(MyModelViewSet, self).get_serializer_class()
Run Code Online (Sandbox Code Playgroud)

MyModelListSerializer这是和的示例MyModelCreateSerializer

# Used for the get request
class MyModelListSerializer(serializers.ModelSerializer):
    assignees = AssigneesSerializer(read_only=True, many=True)
    labels = LabelsSerializer(read_only=True, many=True)

    class Meta:
        model = MyModel
        fields = '__all__'

# Used for the post request
class MyModelCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = "__all__"
Run Code Online (Sandbox Code Playgroud)