Django REST 序列化程序方法可写字段

Posted

技术标签:

【中文标题】Django REST 序列化程序方法可写字段【英文标题】:Django REST Serializer Method Writable Field 【发布时间】:2017-03-26 03:01:55 【问题描述】:

我正在阅读 Django REST Framework,我有一个使用 SerializerMethodField() 的 getter 序列化模型。

但是,当我 POST 到此端点时,我也希望能够设置此字段,但这不起作用,因为如上面的文档所示,您无法写入 SerializerMethodField。 Django REST 中是否有任何方法可以让您定义一个自定义 getter 方法和一个自定义 setter 方法的序列化器字段?

编辑:这是我正在尝试做的事情的来源。客户与用户是一对一的关系。

class ClientSerializer(serializers.ModelSerializer):
    email = serializers.SerializerMethodField()

    def create(self, validated_data):
        email = validated_data.get("email", None) # This doesn't work because email isn't passed into validated_data because it's a readonly field
        # create the client and associated user here


    def get_email(self, obj):
        return obj.user.email

    class Meta:
        model = Client
        fields = (
            "id",
            "email",
        )

【问题讨论】:

***.com/questions/18396547/… 是的,这就是我要问的。它说您不能在其中一个 cmets 中真正接受 POST 上的 serializermethodfield 的数据,但我问是否有办法在 Django REST 中做到这一点,可能不使用 serializermethodfield 您应该发布源代码以供您使用 django rest 序列化程序方法。为什么不只使用普通的序列化器字段?您不能将方法序列化程序用于发布请求,因为它是只读字段 好的,我添加了我的代码。如何修改它以接受 POST 请求中的电子邮件? 【参考方案1】:

这是一个读/写序列化方法字段:

class ReadWriteSerializerMethodField(serializers.SerializerMethodField):
    def __init__(self, method_name=None, **kwargs):
        self.method_name = method_name
        kwargs['source'] = '*'
        super(serializers.SerializerMethodField, self).__init__(**kwargs)

    def to_internal_value(self, data):
        return self.field_name: data

【讨论】:

【参考方案2】:

您需要使用另一种类型的字段:

class ClientSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(source='user.email')

    def create(self, validated_data):
        # DRF will create object "user": "email": "inputed_value" in validated_date
        email = validated_data.get("user", ).get('email')

    class Meta:
        model = Client
        fields = (
            "id",
            "email",
        )

【讨论】:

【参考方案3】:

我尝试使用Guilherme Nakayama da Silva 和Julio Marins 的答案来解决我写入SerializerMethodField 的问题。它适用于阅读和更新,但不适用于创建。

所以我根据他们的回答创建了自己的 WritableSerializerMethodField,它非常适合阅读、创建和写作。

class WritableSerializerMethodField(serializers.SerializerMethodField):
    def __init__(self, **kwargs):
        self.setter_method_name = kwargs.pop('setter_method_name', None)
        self.deserializer_field = kwargs.pop('deserializer_field')

        super().__init__(**kwargs)

        self.read_only = False

    def bind(self, field_name, parent):
        retval = super().bind(field_name, parent)
        if not self.setter_method_name:
            self.setter_method_name = f'set_field_name'

        return retval

    def get_default(self):
        default = super().get_default()

        return 
            self.field_name: default
        

    def to_internal_value(self, data):
        value = self.deserializer_field.to_internal_value(data)
        method = getattr(self.parent, self.setter_method_name)
        return self.field_name: self.deserializer_field.to_internal_value(method(value))

然后我在我的序列化器中使用了它

class ProjectSerializer(serializers.ModelSerializer):
    contract_price = WritableSerializerMethodField(deserializer_field=serializers.DecimalField(max_digits=12, decimal_places=2))

    def get_contract_price(self, project):
        return project.contract_price

    def set_contract_price(self, value):
        return value

【讨论】:

您能否发布一个示例,说明如何将其与实际数据一起使用?例如在 django shell 中【参考方案4】:

就我而言,我需要 get_* 方法中的逻辑,但无法使用 source 属性获取值。所以我想出了这个领域。

class WritableSerializerMethodField(serializers.SerializerMethodField):
    def __init__(self, method_name=None, **kwargs):
        super().__init__(**kwargs)

        self.read_only = False

    def get_default(self):
        default = super().get_default()

        return 
            self.field_name: default
        

    def to_internal_value(self, data):
        return self.field_name: data

【讨论】:

【参考方案5】:

您可以覆盖序列化程序上的save() 方法并使用self.initial_data。然后,您需要自己对该字段进行验证。

class MySerializer(serializers.ModelSerializer):

    magic_field = serializers.SerializerMethodField()

    def get_magic_field(self, instance):
        return instance.get_magic_value()

    def save(self, **kwargs):

        super().save(**kwargs)  # This creates/updates `self.instance`

        if 'magic_field' in self.initial_data:
            self.instance.update_magic_value(self.initial_data['magic_field'])

        return self.instance

【讨论】:

【参考方案6】:

为什么不直接在视图中创建客户端呢?

def post(self, request, *args, **kwargs):
    data = 
        'email': request.data.get('email'),
    

    serializer = ClientSerializer(data=data)
    if serializer.is_valid():
        email = serializer.data.get('email')
        client = Client.objects.create(email=email)
        # do other stuff

【讨论】:

我绝对可以做到这一点,但我希望已经有一种方法可以在 Django REST 序列化程序中处理这个用例。将序列化和字段的逻辑与序列化器本身分开似乎很奇怪 是否也可以覆盖 ViewSet 的 post 方法?【参考方案7】:

我遇到了同样的问题,并想出了下面的解决方案。

请注意,我确实需要在我的序列化程序中使用 SerializerMethodField,因为我需要根据 request.user 和某些权限填充字段,这对于 SerializerField 或其他建议的其他解决方案来说太复杂了答案。

解决方案是“劫持”API 视图的perform_update,并在此时执行特定的写入(在我的情况下,在正常的序列化器之上使用另一个序列化器)。我只需要对更新执行此操作,但如果这是您的用例,您可能需要使用 perform_create 执行此操作。

是这样的:

    def perform_update(self, serializer):
        if 'myField' in self.request.data and isinstance(self.request.data['myField'], bool):
        if self.request.user == serializer.instance.owner:
            serializer.instance.myField = self.request.data['myField']
        else:
            # we toggle myField in OtherClass
            try:
                other = models.OtherClass.objects.get(...)
            except models. OtherClass.DoesNotExist:
                return Response("You don't sufficient permissions to run this action.", status=status.HTTP_401_UNAUTHORIZED)
            except models.OtherClass.MultipleObjectsReturned:  # should never happen...
                return Response("Internal Error: too many instances.", status=status.HTTP_500_INTERNAL_SERVER_ERROR)
            else:
                data = 
                    'myField': self.request.data['myField']
                    ... # filled up with OtherClass params
                
                otherSerializer = serializers.OtherClassSerializer(other, data=data)
                if otherSerializer.is_valid():
                    otherSerializer.save()
    serializer.save()  # takes care of all the non-read-only fields 

我不得不承认,按照 MVC 模式,它并不理想,但它确实有效。

【讨论】:

【参考方案8】:

您可以重复email字段,它可以工作,但它可能会混淆

class ClientSerializer(serializers.ModelSerializer):
    email = serializers.SerializerMethodField()
    email = serializers.EmailField(required=False)

    def create(self, validated_data):
        email = validated_data.get("email", None) # This doesn't work because email isn't passed into validated_data because it's a readonly field
        # create the client and associated user here


    def get_email(self, obj):
        return obj.user.email

    class Meta:
        model = Client
        fields = (
            "id",
            "email",
        )

【讨论】:

不,那 not 工作。在 python 中的一个类上不能有两个同名的属性。在这种情况下,第一个被覆盖,你只得到 EmailField。 借助一些元类魔法和肘部润滑脂,您可以让它发挥作用。

以上是关于Django REST 序列化程序方法可写字段的主要内容,如果未能解决你的问题,请参考以下文章

使用 Django Rest Framework 3.2.2 具有现有对象的可写嵌套序列化程序

Django Rest框架:序列化程序上的共享字段

django rest 框架:从序列化器 validate() 方法设置字段级错误

该字段已在序列化程序上声明,但在 django rest 中未包含错误

Django rest框架 - 模型序列化程序不会序列化所有字段

django-rest-framework 序列化器在多个视图中的不同字段