【问题标题】:Disabling choices on rest_framework's RelatedField禁用 rest_framework 的相关字段上的选择
【发布时间】:2016-06-04 12:53:26
【问题描述】:

我使用的是django rest framework 3.3.2 版。

我们在数百个不同的地方使用HyperlinkedRelatedField,我的问题是它通过RelatedField 继承了choices 方法,它执行以下操作:

class RelatedField(Field):

    ...

    @property
    def choices(self):
        queryset = self.get_queryset()
        if queryset is None:
            # Ensure that field.choices returns something sensible
            # even when accessed with a read-only field.
            return {}

        return OrderedDict([
            (
                six.text_type(self.to_representation(item)),
                self.display_value(item)
            )
            for item in queryset
        ])

该查询集是与另一个表的关系,可以包含数十万行。对 api 的 OPTIONS 请求现在会消耗所有可用内存,因为它会尝试为关系的可用选择生成 json 响应。尽管html_cutoff 选项将此数字截断为 1000,但问题仍然存在,因为查询集在被截断限制之前已经被使用。

我正在寻找一种非侵入式的方式来禁用外键上的选择枚举。我想避免创建自定义字段类,如果可能的话,有没有办法通过其余框架 api 影响这种行为?我根本不需要在选项响应中看到choices

【问题讨论】:

    标签: python django api django-rest-framework http-options-method


    【解决方案1】:

    在当前的 DRF (v3.3.2) 中有 this problem,OPTIONS 响应尝试评估外键的“选择”。这是一个糟糕的想法,如果您的数据库中有大量内容,那么可浏览的 API 将无法使用。

    DRF 维护人员知道这一事实,并there is a PR currently scheduled for the 3.4.0 release 来解决这个问题。

    在上游修复之前,这是我的解决方法。 注意:要覆盖该行为,您需要在settings.pyREST_FRAMEWORK 选项下设置DEFAULT_METADATA_CLASS

    这故意不会禁用ChoiceField 和朋友的选择枚举,仅用于相关字段。

    from copy import copy
    from functools import wraps
    
    from rest_framework.metadata import SimpleMetadata
    from rest_framework.relations import RelatedField
    
    
    class MyMetadata(SimpleMetadata):
    
        def get_field_info(self, field):
    
            if isinstance(field, RelatedField):
                def kill_queryset(f):
                    @wraps(f)
                    def wrapped(*args, **kwargs):
                        qs = f(*args, **kwargs)
                        if qs is not None:
                            qs = qs.none()
                        return qs
                    return wrapped
    
                field = copy(field)
                field.get_queryset = kill_queryset(field.get_queryset)
    
            result = super(MyMetadata, self).get_field_info(field)
    
            if not result.get('choices'):
                result.pop('choices', None)
    

    【讨论】:

      【解决方案2】:

      您可以使用元数据 API 在 Django REST 框架中修改任何 OPTIONS 请求的内容。

      这涉及定义您自己的元数据类 - 请参阅 this documentation page

      您可以将自定义元数据类添加到导致问题的视图中。

      【讨论】:

      • 确实,我认为自定义元数据类可能是攻击此问题的最佳场所。但似乎没有任何合适的方法可以覆盖它,在它进入序列化程序和字段之前的最后机会是在get_field_info(它做了很多其他重要的事情)。
      • @wim 我创建了一个新的元数据类,它覆盖了get_field_info。我从SimpleMetadata.get_field_info 作为模板开始。我知道这不是超级干净,但这是我找到的唯一方法!
      • 如果您使用SimpleMetadata.get_field_info,那么您已经使用了查询集,所以这不好..
      • @wim 这很烦人,我没想到。如果我找到答案,我会告诉你,但我也需要知道!
      • 您好 Kieran,我已经使用元数据 API 实现了一种解决方法。这很 hacky,因为它实际上是导致问题的 RelatedField.choices 属性,因此您必须在字段有机会评估它之前对查询集进行中性处理。有关血腥细节,请参阅我的答案。
      猜你喜欢
      • 2013-01-01
      • 1970-01-01
      • 2014-05-11
      • 1970-01-01
      • 1970-01-01
      • 2016-12-11
      • 1970-01-01
      • 2012-09-26
      • 1970-01-01
      相关资源
      最近更新 更多