禁用 rest_framework 的 RelatedField 上的选择

Disabling choices on rest_framework's RelatedField

我正在使用 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
        ])

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

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

您可以使用元数据 API.

在 Django REST 框架中修改任何 OPTIONS 请求的内容

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

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

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

DRF 维护者知道这个事实,并且 there is a PR currently scheduled for the 3.4.0 release 会解决这个问题。

在上游修复之前,这是我的解决方法。 注意:要覆盖该行为,您需要在 settings.py 中的 REST_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)