无法重载(覆盖)DRF 序列化程序的 save() 方法(或其他方法)

Esc*_*her 1 python django django-rest-framework

我正在尝试覆盖我的序列化程序的save()方法(根据文档)以支持批量实例创建。目前,我有一些看起来像这样的东西(如果你愿意,可以跳过代码,这只是为了上下文。真正的问题是我不能制作任何我自己的序列化方法)。

序列化程序.py

class BulkWidgetSerializer(serializers.ModelSerializer):
    """ Serialize the Widget data """

    #http://stackoverflow.com/questions/28200485/
    some_foreign_key = serializers.CharField(source='fk_fizzbuzz.name', read_only=False)

    class Meta:
        model = Widget
        fields = (
            'some_foreign_key',
            'uuid',
            'foobar',
        )
        # Normally we would set uuid to read_only, but then it won't be available in the self.validate()
        # method. We also need to take the validator off this field to remove the UNIQUE constraint, and
        # perform the validation ourselves.
        # See https://github.com/encode/django-rest-framework/issues/2996 and
        # /sf/answers/2543437781/
        extra_kwargs = {
            'uuid': {'read_only': False, 'validators': []},
        }

    def validate(self, data):
        return super(WidgetSerializer, self).validate(self.business_logic(data))

    def save(self):
        print("---------Calling save-----------")
        more_business_logic()
        instances = []
        for widget in self.validated_data:
            instances.append(Widget(**self.validated_data))
        Widget.objects.bulk_create(instances)
        return instances
Run Code Online (Sandbox Code Playgroud)

视图集.py

class WidgetViewSet(viewsets.ModelViewSet):
    permission_classes = (permissions.IsAuthenticated,)
    serializer_class = BulkWidgetSerializer
    pagination_class = WidgetViewSetPagination
    lookup_field = 'uuid'

    def partial_update(self, request):
        serializer = self.get_serializer(data=request.data,
                    many=isinstance(request.data, list),
                    partial=True)
        if not serializer.is_valid():
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        pdb.set_trace()
        serializer.save()
        return Response(serializer.data, status=status.HTTP_200_OK)
Run Code Online (Sandbox Code Playgroud)

然后我在数据库中获得了新的 Wiget 实例,但它们的属性表明没有发生more_business_logic()调用save()。但是,我确实收到反馈,表明发生business_logicvalidate()通话。

我由此推测,我不知何故仍然被super班级的save()? 如何覆盖此方法?

编辑:

当我在两个文件中重命名save()newsave(),并尝试在 ViewSet 中调用它时,我得到:

AttributeError: 'ListSerializer' 对象没有属性 'newsave'

这是怎么回事?pdb在断点处检查显示它确实是一个BulkWidgetSerializer. 在 shell 中检查显示newsave绝对是该类的方法:

>>>'newsave' in [func for func in dir(BulkWidgetSerializer) if callable(getattr(BulkWidgetSerializer, func))]
True
Run Code Online (Sandbox Code Playgroud)

此外,如果我在序列化程序类中创建自己的测试方法:

def test_method(self):
    print("Successful test method")
Run Code Online (Sandbox Code Playgroud)

我也不能这么叫!

>>> serializer.test_method()
AttributeError: 'ListSerializer' object has no attribute 'test_method'
Run Code Online (Sandbox Code Playgroud)

buo*_*oto 5

BulkWidgetSerializer的包装ListSerializer是 DRF 的默认行为。这就是为什么你的新方法不见了。

如果您实例化BaseSerializerwith kwargmany=True 任何子类,会将其包装为 new ListSerializerwith childset 到您的Serializer类。

因此,您无法覆盖save()方法以获得所需的效果。尝试覆盖many_init序列化程序的 classmethod 以提供ListSerializer实现所需行为的自定义,如 DRF 文档中所示

其次,最好重写create()orupdate()方法,而不是save()调用其中之一。

您的实现可能如下所示:

class CustomListSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        more_business_logic()
        instances = [
            Widget(**attrs) for attrs in validated_data
        ]
        return Widget.objects.bulk_create(instances)
Run Code Online (Sandbox Code Playgroud)

然后在BulkWidgetSerializer

@classmethod
def many_init(cls, *args, **kwargs):
    kwargs['child'] = cls()
    return CustomListSerializer(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

一个问题:不要忘记将正确的 kwargs 从父级传递给子级,例如,kwargs['child'] = cls( partial=kwargs.get('partial') )如果在覆盖方法期间依赖子类中的任何一个kwargs来支持批量部分更新(例如validate())。


ari*_*eet 5

看来您正在使用 实例化您的序列化器many=True。在这种情况下,ListSerializer是在内部实例化的(您可以在类方法中找到它的代码rest_framework.serializers.BaseSerializer.many_init)。

因此称为save()的方法。ListSerializer如果必须重写 save 方法,请首先创建一个自定义列表序列化器:

class CustomListSerializer(serializers.ListSerializer):
    def save(self):
         ...
Run Code Online (Sandbox Code Playgroud)

BulkWidgetSerializer然后通过指定以下内容将此自定义列表序列化器添加到您的列表中list_serializer_class

class Meta:
    list_serializer_class = CustomListSerializer
Run Code Online (Sandbox Code Playgroud)

正如其他人所指定的,最好重写 或create方法update而不是save