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 框架:从序列化器 validate() 方法设置字段级错误
该字段已在序列化程序上声明,但在 django rest 中未包含错误