【问题标题】:Django REST Serializer Method Writable FieldDjango REST 序列化程序方法可写字段
【发布时间】: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",
        )

【问题讨论】:

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

标签: django rest django-rest-framework serialization


【解决方案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 SilvaJulio 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。
            • 借助一些元类魔法和肘部润滑脂,您可以让它发挥作用。
            猜你喜欢
            • 2018-10-18
            • 2014-08-05
            • 1970-01-01
            • 2013-07-07
            • 1970-01-01
            • 1970-01-01
            • 2013-11-15
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多