【问题标题】:Django REST Framework - Serializing optional fieldsDjango REST Framework - 序列化可选字段
【发布时间】:2013-07-07 07:19:06
【问题描述】:

我有一个包含可选字段的对象。我以这种方式定义了我的序列化程序:

class ProductSerializer(serializers.Serializer):
    code = serializers.Field(source="Code")
    classification = serializers.CharField(source="Classification", required=False)

如果该字段不存在,我 thought required=False 将完成绕过该字段的工作。但是,文档中提到这会影响反序列化而不是序列化。

我收到以下错误:

'Product' object has no attribute 'Classification'

当我尝试访问序列化实例的.data 时会发生这种情况。 (这不是说是反序列化引起的吗?)

这发生在没有Classification 的实例上。如果我从序列化程序类中省略Classification,它就可以正常工作。

我该如何正确地做到这一点?序列化带有可选字段的对象,即。

【问题讨论】:

  • 是否可以将这些字段序列化为None,或者根本不存在密钥?
  • 它们根本不存在,我正在调用一个 SOAP Web 服务,它具有使用 suds 的可选字段,响应对象表示返回的 XML,在某些情况下不包括可选字段。
  • Tom 我刚刚明白你的意思;理想情况下,我希望他们根本不在场,但我可以暂时与None 一起生活。

标签: python django serialization django-rest-framework


【解决方案1】:

Django REST 框架 3.0+
现在支持动态字段,请参阅 http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields - 这种方法定义了序列化程序中的所有字段,然后允许您有选择地删除不需要的字段。

或者你也可以为模型序列化器做这样的事情,你在序列化器初始化中搞乱 Meta.fields:

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ('code',)

    def __init__(self, *args, **kwargs):
        if SHOW_CLASSIFICATION: # add logic here for optional viewing
            self.Meta.fields = list(self.Meta.fields)
            self.Meta.fields.append('classification')
        super(ProductSerializer, self).__init__(*args, **kwargs)

如果这是否是“正确的方法”,您必须询问 Tom,因为它可能不符合长期计划。

Django REST 框架
试试这样的:

class ProductSerializer(serializers.Serializer):
    ...
    classification = serializers.SerializerMethodField('get_classification')

    def get_classification(self, obj):
        return getattr(obj, 'classification', None)

多个序列化器

另一种方法是创建具有不同字段集的多个序列化程序。一个序列化器继承自另一个并添加额外的字段。然后您可以使用get_serializer_class 方法在视图中选择合适的序列化程序。这是一个实际示例,说明如果用户对象与请求用户相同,我如何使用这种方法调用不同的序列化程序来呈现不同的用户数据。

def get_serializer_class(self):
    """ An authenticated user looking at their own user object gets more data """
    if self.get_object() == self.request.user:
        return SelfUserSerializer
    return UserSerializer

从表示中删除字段

我在安全上下文中使用的另一种方法是删除to_representation 方法中的字段。定义一个方法,如

def remove_fields_from_representation(self, representation, remove_fields):
    """ Removes fields from representation of instance.  Call from
    .to_representation() to apply field-level security.
    * remove_fields: a list of fields to remove
    """
    for remove_field in remove_fields:
        try:
            representation.pop(remove_field)
        except KeyError:
            # Ignore missing key -- a child serializer could inherit a "to_representation" method
            # from its parent serializer that applies security to a field not present on
            # the child serializer.
            pass

然后在您的序列化程序中,调用该方法,如

def to_representation(self, instance):
    """ Apply field level security by removing fields for unauthorized users"""
    representation = super(ProductSerializer, self).to_representation(instance)
    if not permission_granted: # REPLACE WITH PERMISSION LOGIC
        remove_fields = ('classification', ) 
        self.remove_fields_from_representation(representation, remove_fields)
    return representation

这种方法简单而灵活,但它的代价是序列化有时不显示的字段。但这可能没关系。

【讨论】:

  • 这通常是根据权限有条件地显示数据的任务,我想知道没有舒适的方法来处理它。看起来您的第一种方法可行,但它有点像猴子补丁,每次创建新实例时您的 ProductSerializer.Meta.fields 都会增加,您将失去它的第一个状态
  • @meteor 我在最近如何解决这个问题的末尾添加了另一个示例以用于安全用途。我不建议再使用您提到的示例。
  • 第一个示例不是非常接近文档中的建议吗?我是一个菜鸟,对为什么这不是一个好方法感到困惑?来源:django-rest-framework.org/api-guide/serializers/…
  • 也许——这个答案最初是在很久以前写的,然后才有标准的方法:-)。如果我是你,我会按照文档中的示例开始。不过,我曾经使用过这些技术中的每一种,因此在某些特殊情况下,它们可能仍然与某些技术相关。
【解决方案2】:

序列化程序被特意设计为使用一组固定的字段,因此您不会轻易地选择删除其中一个键。

您可以使用SerializerMethodField 来返回字段值或None(如果该字段不存在),或者您根本无法使用序列化程序而只需编写一个直接返回响应的视图。

REST framework 3.0 更新 serializer.fields 可以在实例化的序列化程序上进行修改。当需要动态序列化程序类时,我可能会建议更改自定义 Serializer.__init__() 方法中的字段。

【讨论】:

  • 考虑到到目前为止框架对我的项目有多友好,最好为不存在的密钥提供一个开箱即用的选项,默认为None
  • @TomChristie 嘿,汤姆,现在 DRF 3 已经发布,您能否更新您对使用 DRF 3 的推荐方法的答案?我之所以这么问,是因为有几个人建议使用 DRF 3 的不同方式,而我不确定最好的选择是什么(请参阅下面的 Mark 的回答或在此处查看 David 的回答:stackoverflow.com/questions/27015931/… 以获取使用 DRF 完成此任务的方法示例3. 您认为使用 DRF 3 的最佳方式是什么?)
【解决方案3】:

下面描述的方法对我有用。 非常简单,容易,对我有用。

使用的 DRF 版本 = djangorestframework (3.1.0)

class test(serializers.Serializer):
  id= serializers.IntegerField()
  name=serializers.CharField(required=False,default='some_default_value')

【讨论】:

    【解决方案4】:

    DynamicSerializer 用于 DRF 3,它允许动态指定哪些字段将在序列化程序中使用,哪些字段将被排除,以及哪些字段将成为必需!

    1. 创建 Mixin
        class DynamicSerializerMixin:
            """
            A Serializer that takes an additional `fields` argument that
            controls which fields should be used.
            """
    
            def __init__(self, *args, **kwargs):
                # Don't pass the 'fields' arg up to the superclass
                fields = kwargs.pop("fields", None)
                excluded_fields = kwargs.pop("excluded_fields", None)
                required_fields = kwargs.pop("required_fields", None)
    
                # Instantiate the superclass normally
                super().__init__(*args, **kwargs)
    
                if fields is not None:
                    # Drop any fields that are not specified in the `fields` argument.
                    allowed = set(fields)
                    existing = set(self.fields)
                    for field_name in existing - allowed:
                        self.fields.pop(field_name)
    
                    if isinstance(fields, dict):
                        for field, config in fields.items():
                            set_attrs(self.fields[field], config)
    
                if excluded_fields is not None:
                    # Drop any fields that are not specified in the `fields` argument.
                    for field_name in excluded_fields:
                        self.fields.pop(field_name)
    
                if required_fields is not None:
                    for field_name in required_fields:
                        self.fields[field_name].required = True
    
    1. 通过将 DynamicSerializerMixin 添加到继承中来初始化/调整您的序列化程序
    
    class UserProfileSerializer(DynamicSerializerMixin, serializers.ModelSerializer):
    
        class Meta:
            model = User
            fields = (
                "id",
                'first_name', 'last_name'
                "email",
                "is_staff",
            )
    
    1. 使用它 :)
    class RoleInvitationSerializer(serializers.ModelSerializer):
        invited_by = UserProfileSerializer(fields=['id', 'first_name', 'last_name'])
    

    动作API

        @action(detail=True, serializer_class=YourSerialzierClass)
        def teams_roles(self, request, pk=None):
            user = self.get_object()
            queryset = user.roles.all()
            serializer = self.get_serializer(queryset, many=True, excluded_fields=['user'])
            return Response(data=serializer.data)
    

    【讨论】:

      【解决方案5】:

      为此,序列化程序具有partial 参数。如果在初始化序列化程序时可以传递partial=True。如果您使用泛型或混合,您可以覆盖get_serializer 函数,如下所示:

      def get_serializer(self, *args, **kwargs):
          kwargs['partial'] = True
          return super(YOUR_CLASS, self).get_serializer(*args, **kwargs)
      

      这样就行了。

      注意:这允许所有字段都是可选的,而不仅仅是一个特定的。如果您只想要细节,您可以覆盖该方法(即更新)并为各个字段添加存在验证。

      【讨论】:

        【解决方案6】:

        序列化器Charfield方法有一个属性allow_blank

        默认情况下,它设置为 False。 将其设置为True 将允许您在“序列化”期间将该字段标记为可选。

        这是你应该写的代码

        classification = serializers.CharField(source="Classification", allow_blank=True)
        

        注意:required 属性用于反序列化。

        【讨论】:

          【解决方案7】:

          从“依赖于 DRF 和 Django 的特定实现细节的可怕的 hack,但它可以工作(至少现在)”文件中,这是我用来在响应中包含一些额外调试数据的方法“ create" 序列化器上的方法实现:

          def create(self, validated_data)
              # Actual model instance creation happens here...
              self.fields["debug_info"] = serializers.DictField(read_only=True)
              my_model.debug_info = extra_data
              return my_model
          

          这是一种临时方法,让我可以使用可浏览的 API 来显示在创建过程中从特定远程服务接收到的一些原始响应数据。将来,我倾向于保留此功能,但将其隐藏在创建请求中的“报告调试信息”标志后面,而不是默认返回较低级别的信息。

          【讨论】:

            【解决方案8】:

            对我来说行之有效的方法是像这样设置序列化程序:

            classification = serializers.CharField(max_length=20, allow_blank=True, default=None)

            【讨论】:

              猜你喜欢
              • 2014-11-09
              • 1970-01-01
              • 2020-06-09
              • 2013-11-15
              • 1970-01-01
              • 2015-12-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多