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

时间:2017-08-02 14:13:30

标签: python django django-rest-framework

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

serializers.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
        # https://stackoverflow.com/a/36334825/3790954
        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

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

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

我从中推测,我仍然坚持使用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

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

def test_method(self):
    print("Successful test method")

我也不能这样称呼!

>>> serializer.test_method()
AttributeError: 'ListSerializer' object has no attribute 'test_method'

3 个答案:

答案 0 :(得分:2)

您的BulkWidgetSerializerListSerializer包裹,这是DRF的默认行为。这就是为什么你的新方法不见了。

如果使用kwarg BaseSerializer the library wraps it实例化many=True的任何子类,并将新ListSerializer child设置为Serializer类。< / p>

因此,您无法覆盖save()方法以获得所需的效果。 尝试覆盖序列化程序的many_init类方法,以提供实现所需行为的自定义ListSerializer {。{3}}。

其次,最好覆盖create()update()方法,而不是调用其中一个方法的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)

然后在BulkWidgetSerializer

@classmethod
def many_init(cls, *args, **kwargs):
    kwargs['child'] = cls()
    return CustomListSerializer(*args, **kwargs)

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

答案 1 :(得分:1)

您一直在阅读文档的错误部分,而且您的方法不正确。

  

多个对象创建的默认实现是简单的   为列表中的每个项调用.create()。如果你想自定义   这种行为,您需要自定义.create()方法   传递many = True时使用的ListSerializer类。

这可以确保您在列表中传递的每个项目都会发生您想要发生的more_business_logic。

根据文档 - http://www.django-rest-framework.org/api-guide/serializers/#customizing-multiple-create

答案 2 :(得分:1)

您似乎正在使用runas /netonly实例化序列化程序。在这种情况下,many=True在内部实例化(您可以在类方法rest_framework.serializers.BaseSerializer.many_init中找到此代码)。

因此调用了ListSerializer的{​​{1}}方法。如果必须覆盖save方法,请首先创建自定义列表序列化程序:

save()

然后通过指定ListSerializer

将此自定义列表序列化程序添加到class CustomListSerializer(serializers.ListSerializer): def save(self): ...
BulkWidgetSerializer

根据他人的指定,最好覆盖list_serializer_classclass Meta: list_serializer_class = CustomListSerializer 方法,而不是create