Django-rest-framework: 支持按 OrderingFilter 中嵌套的相关字段排序

创建于 2013-07-25  ·  28评论  ·  资料来源: encode/django-rest-framework

使用示例是按嵌套的相关对象进行排序。

嵌套表示:

{
    'username': 'george',
    'email': '[email protected]'
    'stats': {
        'facebook_friends': 560,
        'twitter_followers': 4043,
        ...
    },
},
{
    'username': 'michael',
    'email': '[email protected]'
    'stats': {
        'facebook_friends': 256,
        'twitter_followers': 120,
        ...
    },
},
...

一种选择是支持相关模型的 django orm 双下划线符号 __。

前任。 ?ordering=stats__facebook_friends将按 facebook_friends 排序。

当前排序仅适用于查询集中指定的特定模型的字段。

https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/filters.py#L125

最有用的评论

感谢您的提示:+1:。 链接让我们修复。 这有效

from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields.related import ForeignObjectRel
from rest_framework.filters import OrderingFilter


class RelatedOrderingFilter(OrderingFilter):
    """
    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:
            field, parent_model, direct, m2m = \
                model._meta.get_field_by_name(components[0])

            # reverse relation
            if isinstance(field, ForeignObjectRel):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, ordering, view):
        return [term for term in ordering
                if self.is_valid_field(queryset.model, term.lstrip('-'))]

所有28条评论

我会考虑为此提出拉取请求,但这不是我自己有时间做的事情。

任何希望实现此功能的人都需要确保在过滤中使用不正确的字段名称时采取合理的行为。 至少,结果应该仍然可以正常返回。 (理想情况下,应忽略任何不正确的字段名称,但应保留其他字段。)

考虑到我将关闭它。 如果有人发出拉取请求并测试处理它,那么我可能会重新考虑,但是每个基本过滤类的更完整的实现确实有人可以在第三方包中很好地解决,然后可以单独维护,并从主要文档链接到。

没有拉取请求,因为我没有时间编写测试或文档,但如果这有助于其他人发现这个问题,我正在使用:

from django.db.models.fields import FieldDoesNotExist 
from rest_framework.filters import OrderingFilter

class RelatedOrderingFilter(OrderingFilter):
    """ 
    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related 
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:
            field, parent_model, direct, m2m = model._meta.get_field_by_name(components[0])
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, ordering):
        return [term for term in ordering if self.is_valid_field(queryset.model, term.lstrip('-'))]

@rhunwicks你的版本不允许反向关系排序,这里只有直接 fk 是补丁版本

class RelatedOrderingFilter(OrderingFilter):
    """
    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:
            field, parent_model, direct, m2m = \
                model._meta.get_field_by_name(components[0])

            # reverse relation
            if isinstance(field, RelatedObject):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, ordering, view):
        return [term for term in ordering
                if self.is_valid_field(queryset.model, term.lstrip('-'))]

pySilver 的片段对我来说效果很好,我们可以把它放到 DRF 中吗?

你从哪里导入RelatedObject ? 我似乎无法使用

Python 3.4.3
Django==1.8.5
djangorestframework==3.2.3

@eldamir django.db.models.related.RelatedObject

ImportError: No module named 'django.db.models.related'

啊,好像已经从 1.8 中删除了,参见https://code.djangoproject.com/ticket/21414

感谢您的提示:+1:。 链接让我们修复。 这有效

from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields.related import ForeignObjectRel
from rest_framework.filters import OrderingFilter


class RelatedOrderingFilter(OrderingFilter):
    """
    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:
            field, parent_model, direct, m2m = \
                model._meta.get_field_by_name(components[0])

            # reverse relation
            if isinstance(field, ForeignObjectRel):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, ordering, view):
        return [term for term in ordering
                if self.is_valid_field(queryset.model, term.lstrip('-'))]

最新版本的 django rest 框架没有问题:)

不要忘记正确指定您的ordering_fields

只是一个说明 - 在 Django > 1.10 中,一些方法发生了变化

具体来说,您必须更改以下内容:

field, parent_model, direct, m2m = model._meta.get_field_by_name(components[0])

field = model._meta.get_field(components[0])

remove_invalid_fields的签名已更改为:

def remove_invalid_fields(self, queryset, ordering, view, request):

这导致 Django > 1.10 的最终工作版本:

class RelatedOrderingFilter(OrderingFilter):
    """
    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:

            field = model._meta.get_field(components[0])

            # reverse relation
            if isinstance(field, ForeignObjectRel):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, fields, view, request):
        return [term for term in fields if self.is_valid_field(queryset.model, term.lstrip('-'))]

上面的代码将有 OneToOneField 的问题,为此添加了一个修复程序:

from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields.reverse_related import ForeignObjectRel, OneToOneRel

from rest_framework.filters import OrderingFilter


class RelatedOrderingFilter(OrderingFilter):
    """
    Extends OrderingFilter to support ordering by fields in related models.
    """    

    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:

            field = model._meta.get_field(components[0])

            if isinstance(field, OneToOneRel):
                return self.is_valid_field(field.related_model, components[1])

            # reverse relation
            if isinstance(field, ForeignObjectRel):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.rel and len(components) == 2:
                return self.is_valid_field(field.rel.to, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, fields, view):
        return [term for term in fields
                if self.is_valid_field(queryset.model, term.lstrip('-'))]        

field.rel 和 field.rel.to 将在 Django >= 1.10 上引发弃用警告。 他们现在分别是:

  • field.remote_field
  • 场模型

@tomchristie考虑到这个补丁已经在这个问题中徘徊了 4 年(并且不断更新)(我们从去年开始就在生产中使用它),它是否适合 PR 并合并到 DRF(是的,由一组适当的单元测试)?

只是我的 0.2 美分

这个补丁对我有用,我们可以将它合并到发布版本吗?

如前所述,要么使其成为第 3 方(首选),要么需要带有测试和文档的适当 PR。

@filipernaldi我认为在 Django >= 1.10 field.rel.to变成field.related_model 。 这是通过我们的单元测试的补丁的最新版本。

nb 我们使用的是 Django 1.11 所以 ymmw

class RelatedOrderingFilter(filters.OrderingFilter):
    """

    See: https://github.com/tomchristie/django-rest-framework/issues/1005

    Extends OrderingFilter to support ordering by fields in related models
    using the Django ORM __ notation
    """
    def is_valid_field(self, model, field):
        """
        Return true if the field exists within the model (or in the related
        model specified using the Django ORM __ notation)
        """
        components = field.split('__', 1)
        try:
            field = model._meta.get_field(components[0])

            if isinstance(field, OneToOneRel):
                return self.is_valid_field(field.related_model, components[1])

            # reverse relation
            if isinstance(field, ForeignObjectRel):
                return self.is_valid_field(field.model, components[1])

            # foreign key
            if field.remote_field and len(components) == 2:
                return self.is_valid_field(field.related_model, components[1])
            return True
        except FieldDoesNotExist:
            return False

    def remove_invalid_fields(self, queryset, fields, ordering, view):
        return [term for term in fields
                if self.is_valid_field(queryset.model, term.lstrip('-'))]

好吧,在这个问题的 4 岁生日之际,我决定尝试将一个外部软件包与 DRF 一起安装,以支持这个有用的排序:

https://github.com/apiraino/djangorestframework_custom_filters_ordering

我将在接下来的几天内完成这项工作(即我需要正确打包和分解代码,改进测试并确保支持积极支持的 Django 版本)。

当然,欢迎投稿!

干杯

参考 #5533。

我很迷茫。 这似乎默认有效?

我来到这个问题,阅读所有评论,并继续通过@apiraino实施解决方案,但后来我发现我只是输入了错误的相关字段的名称。

但是,我现在使用?ordering=job__customer__company作为双嵌套关系来对 API 结果进行排序,并且它工作正常。

@halfnibble - 我相信这已在 #5533 中修复。

抄送@carltongibson

@halfnibble ,你用的是什么版本? 我在 3.8.2 上,它对我不起作用。 这些是我的版本:

    "install_requires": [
        "django==2.0.3",
        "coreapi==2.3.3",
        "django-filter==1.1.0",
        "djangorestframework-filters==0.10.2.post0",
        "djangorestframework-queryfields==1.0.0",
        "djangorestframework==3.8.2",
        "django-bulk-update==2.2.0",
        "django-cors-headers==2.4.0",
        "django-rest-auth[with_social]==0.9.2",
        "drf-yasg==1.6.0",
        "django-taggit==0.22.2",
        "google-api-python-client==1.6.2",
        "markdown==2.6.11",
        "pygments==2.2.0",
        "xlrd==1.1.0",
        "xlsxwriter==0.9.8",
        "factory-boy==2.10.0",
        "psycopg2-binary==2.7.4",
        "django-admin-tools==0.8.1"
    ]

对不起,现在我明白了。 默认情况下,它仅在相关排序参数包含在ordering_fields时才有效。 但是对于更通用的解决方案,补丁仍然是必需的。

你好,我对 DRF 比较陌生,正在寻找一种方法来向我的订单字段添加排序,而不会暴露我们数据库的结构。 例如,如果我想查找 z,则需要编写 x__y__z,并将其作为参数传递到端点以暴露结构。 如果我只想说 z 并让函数检查它是否是 ORM 中的相关字段,这就是我要找的吗?

您可以尝试覆盖OrderingFilter.get_ordering 。 就像是...

class CustomOrderingFilter(OrderingFilter):
    def get_ordering(self, request, queryset, view):
        ordering = super().get_ordering(request, queryset, view)
        field_map = {
            'z': 'x__y__z',
        }
        return [field_map.get(o, o) for o in ordering]

@rpkilby 非常感谢! 这正是我正在寻找的。 我很感激你的帮助。

这是我整理的一个解决方案:

class RelatedOrderingFilter(filters.OrderingFilter):
    _max_related_depth = 3

    <strong i="6">@staticmethod</strong>
    def _get_verbose_name(field: models.Field, non_verbose_name: str) -> str:
        return field.verbose_name if hasattr(field, 'verbose_name') else non_verbose_name.replace('_', ' ')

    def _retrieve_all_related_fields(
            self,
            fields: Tuple[models.Field],
            model: models.Model,
            depth: int = 0
    ) -> List[tuple]:
        valid_fields = []
        if depth > self._max_related_depth:
            return valid_fields
        for field in fields:
            if field.related_model and field.related_model != model:
                rel_fields = self._retrieve_all_related_fields(
                    field.related_model._meta.get_fields(),
                    field.related_model,
                    depth + 1
                )
                for rel_field in rel_fields:
                    valid_fields.append((
                        f'{field.name}__{rel_field[0]}',
                        self._get_verbose_name(field, rel_field[1])
                    ))
            else:
                valid_fields.append((
                    field.name,
                    self._get_verbose_name(field, field.name),
                ))
        return valid_fields

    def get_valid_fields(self, queryset: models.QuerySet, view, context: dict = None) -> List[tuple]:
        valid_fields = getattr(view, 'ordering_fields', self.ordering_fields)
        if not valid_fields == '__all_related__':
            if not context:
                context = {}
            valid_fields = super().get_valid_fields(queryset, view, context)
        else:
            valid_fields = [
                *self._retrieve_all_related_fields(queryset.model._meta.get_fields(), queryset.model),
                *[(key, key.title().split('__')) for key in queryset.query.annotations]
            ]
        return valid_fields
````

Then I add this to wherever I want to be able to order by all related fields:
```python
filter_backends = (RelatedOrderingFilter,)
ordering_fields = '__all_related__'
此页面是否有帮助?
0 / 5 - 0 等级