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

时间:2018-06-17 14:42:42

标签: django django-rest-framework

我一直在为看板式项目委员会开发自己的API。我附上了一个UML图,以显示“板”应用程序是如何组织的。

My Application's Model UML Diagram

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

{
    "title": "Test Card",
    "description": "This is a Test Card!",
    "created_by": 1,
    "labels": [1,2]
}

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

{
    "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": []
}

我将假设要实现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."
            ]
        }
    ]
}

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

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

禁止直接分配到多对多集合的前端。请改用labels.set()。

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

models.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')

views.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)

serializers.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')

2 个答案:

答案 0 :(得分:2)

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

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

这里是 MyModelListSerializerMyModelCreateSerializer 的示例,

# 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__"

答案 1 :(得分:0)

我设法找到了解决方案,但这可能不符合最佳做法。如果有人能澄清这将是伟大的。但是,现在:

我将CardSerializer中的create函数更改为以下内容:

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

card.labels.set(labels_data)行意味着我绕过了以下错误消息:

Direct assignment to the forward side of a many-to-many set is prohibited.

这就是为什么我不确定它是否是"正确"要做的事情,但现在似乎有效。