django-rest-framework-bulk BulkUpdateView outuput customization

时间:2016-07-08 11:21:57

标签: python django django-rest-framework

我有一个名为Item的模型,其中FSMFielddjango-fsm)名为status

class Item(models.Model):
    ...
    status = FSMField(choices=StatusChoices.choices, default=StatusChoices.INITIAL)

    @transition(field=status, source=StatusChoices.INITIAL, target=StatusChoices.ARRIVED)
    def mark_arrived(self):  # there are more such transitions
        pass

我有一个批量更新的API端点,我将转换名称作为输入,并在实例上调用那些转换方法。

API输入格式:

[
    {
        'id': 23,
        'transition': 'mark_arrived',
    },
    {
        'id': 25,
        'transition': 'some_non_existent_transition_name',
    }
]

我遇到了一个名为djangorestframework-bulk的软件包,它似乎适合这项任务。现在这是我的观点:

from rest_framework_bulk import BulkUpdateAPIView
class ItemStatusChangeView(BulkUpdateAPIView):
    queryset = Item.objects.all()
    serializer_class = ItemStatusSerializer

这是我的序列化程序类:

from rest_framework_bulk import BulkSerializerMixin, BulkListSerializer
class ItemStatusSerializer(BulkSerializerMixin, serializers.ModelSerializer):
    transition = serializers.CharField(required=True, max_length=20, write_only=True)

    def update(self, instance, validated_data):
        try:
            instance.__getattribute__(validated_data['transition'])()
        except AttributeError:
            raise serializers.ValidationError({'transition': 'No such Transition'})

        instance.save()
        return instance

class Meta:
    model = Item
    fields = ('id', 'transition')
    list_serializer_class = BulkListSerializer

现在按照上面的API输入,我希望API输出格式如下:

[
    {
        'id': 23,
        'status_changed': True,
    },
    {
        'id': 25,
        'status_changed': False,
        'transition': 'No such Transition'
    }
]

但看起来像是:

{
    'transition': 'No such Transition'
}

有优雅的方法吗?

(就输出中的status_changed字段而言,我知道我需要在read_only中添加ItemStatusSerializer字段,但我对如何分配它感到困惑输出期间的值。)

1 个答案:

答案 0 :(得分:1)

这是我实现目标的方式,(虽然不是那么优雅)。

正如@Kevin Brown在评论中所建议的那样,我将ItemStatusSerializer更改为:

class ItemStatusSerializer(BulkSerializerMixin, serializers.ModelSerializer):
    transition = serializers.CharField(required=True, max_length=50, write_only=True)

    def update(self, instance, validated_data):
        return instance

    def validate(self, attrs):
        item = Item.objects.get(item_id=attrs['id'])

        try:
            item.__getattribute__(attrs['transition'])()
        except AttributeError:
            raise serializers.ValidationError({
                'id': item.id,
                'transition': 'No such Transition',
                'is_status_changed': False
            })

        item.save()
        return attrs

    class Meta:
        model = Item
        fields = ('id', 'transition')
        list_serializer_class = BulkListSerializer

但是,我还没有完全达到我想要的输出:

[
    {},  # this one's transition ran successfully
    {
        'id': [25],
        'status_changed': [False],
        'transition': ['No such Transition']
    }
]

为什么item transition成功运行的输出为空?深入研究代码后,我想出了:

如果你看一下to_internal_value serializers.ListSerializerBulkListSerializer类的超级类的方法,你会看到:

def to_internal_value(self, data):
    ...
    ...
    for item in data:
        try:
            validated = self.child.run_validation(item)
        except ValidationError as exc:
            errors.append(exc.detail)
        else:
            ret.append(validated)
            errors.append({})  # this was the culprit 

    if any(errors):
        raise ValidationError(errors)
    return ret

所以我将BulkListSerializer子类化并覆盖了to_internal_value方法,有点像这样:

class ItemStatusChangeListSerializer(BulkListSerializer):

    def to_internal_value(self, data):
        ...
        ...
        all_valid = True
        for item in data:
            try:
                validated = self.child.run_validation(item)
            except serializers.ValidationError as exc:
                all_valid = False
                exc.detail['id'] = exc.detail['id'][0]
                exc.detail['is_status_changed'] = exc.detail['is_status_changed'][0]  # this is just to avoid enclosing it's value in list
                errors.append(exc.detail)
            else:
                validated['is_status_changed'] = True
                del validated['transition']
                ret.append(validated)
                errors.append(validated)  # note this

        if not all_valid:
            raise serializers.ValidationError(errors)

        return ret 

然后在ItemStatusSerializer的元类中,我将list_serializer_class变量设置为上面显示的ItemStatusChangeListSerializer