Django过滤器:按模型属性过滤

时间:2019-04-02 12:38:04

标签: django properties django-queryset django-filter

我在several places上读到,无法使用属性过滤Django查询集,因为Django ORM不知道如何将其转换为SQL。

但是,一旦将数据提取并加载到内存中,就可以 使用这些属性在Python中对其进行过滤。

我的问题是:是否有任何库允许查询集按内存中的属性过滤?如果不是,那么如何精确地篡改查询集才能使之成为可能?以及如何将django-filter包含在其中?

4 个答案:

答案 0 :(得分:1)

django-filter想要并假设您正在使用查询集。一旦您获取了一个查询集并将其更改为list,那么下游的所有内容都将只能处理list或仅遍历列表,而不再是查询集。

如果您有django_filters.FilterSet,例如:

class FooFilterset(django_filters.FilterSet):
    bar = django_filters.Filter('updated', lookup_expr='exact')
    my_property_filter = MyPropertyFilter('property')
    class Meta:
        model = Foo
        fields = ('bar',  'my_property_filter')

然后您可以像这样写MyPropertyFilter

class MyPropertyFilter(django_filters.Filter):
    def filter(self, qs, value):
        return [row for row in qs if row.baz == value]

这时,MyProperteyFilter下游的所有内容都会有一个列表。

注意:我相信fields的顺序应该有您的自定义过滤器,MyPropertyFilter最后,因为这样,它将始终在常规查询集过滤器之后进行处理。


因此,对于某些损坏的值,您刚刚破坏了“ queryset” API。在这一点上,您必须解决所有下游错误。如果FilterSet之后需要.count成员,则可以像这样更改MyPropertyFilter

class MyPropertyFilter(django_filters.Filter):
    def filter(self, qs, value):
        result = [row for row in qs if row.baz == value]
        result.count = len(result)
        return result

您处在未知的领域,您将不得不破解自己的方式。

无论如何,我之前已经做过,这并不可怕。只需解决错误,就可以解决。

答案 1 :(得分:1)

您有没有困难的财产? 如果没有,您可以像这样将其重写为queryset:

from django.db import models

class UserQueryset(models.Manager):

    def get_queryset(self):

        return super().get_queryset().annotate(
            has_profile=models.Exists(Profile.objects.filter(user_id=models.OuterRef('id')))
        )

class User(models.Model):
    objects = UserQueryset


class Profile(models.Model):
    user = models.OneToOneField(User, related_name='profile')


# When you want to filter by has profile just use it like has field has profile

user_with_profiles = User.objects.filter(has_profile=True)

也许不是您想要的,但是在某些情况下它可以为您提供帮助

答案 2 :(得分:1)

由于通过非字段属性(例如property进行过滤不可避免地将QuerySet转换为list(或类似的结果),因此我希望将其推迟并在{{1}上进行过滤} object_list方法中。为了将过滤逻辑保留在get_context_data类中,我使用了一个简单的技巧。我定义了一个filterset

decorator

,用于def attr_filter(func): def wrapper(self, queryset, name, value, force=False, *args, **kwargs): if force: return func(self, queryset, name, value, *args, **kwargs) else: return queryset return wrapper 非字段过滤方法。多亏了这个装饰器,该过滤基本上不执行(或跳过)非字段过滤方法(由于默认为django-filter)。

接下来,我定义了一个force=False类中要使用的Mixin

view

基本上,它只是返回到您的 class FilterByAttrsMixin: def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) filtered_list = self.filter_qs_by_attributes(self.object_list, self.filterset) context.update({ 'object_list': filtered_list, }) return context def filter_qs_by_attributes(self, queryset, filterset_instance): if hasattr(filterset_instance.form, 'cleaned_data'): for field_name in filter_instance.filters: method_name = f'attr_filter_{field_name}' if hasattr(filterset_instance, method_name): value = filterset_instance.form.cleaned_data[field_name] if value: queryset = getattr(filterset_instance, filter_method_name)(queryset, field_name, value, force=True) return queryset 并运行所有名为filterset的方法,这次是attr_filter_<field_name>

总而言之,您需要:

  • 继承force=True类中的FilterByAttrsMixin
  • 调用您的过滤方法view
  • 在过滤方法上使用attr_filter_<field_name>装饰器

简单示例(假设我有attr_filter称为modelMyModel名为property的过滤条件:

型号:

is_static

视图:

class MyModel(models.Model):
    ...

@property
def is_static(self):
    ...

过滤器:

class MyFilterView(FilterByAttrsMixin, django_filters.views.FilterView):
    ...
    filterset_class = MyFiltersetClass
    ...

答案 3 :(得分:0)

看看 django-property-filter 包。这是 django-filter 的扩展,提供按类属性过滤查询集的功能。

文档中的简短示例:

from django_property_filter import PropertyNumberFilter, PropertyFilterSet

class BookFilterSet(PropertyFilterSet):
    prop_number = PropertyNumberFilter(field_name='discounted_price', lookup_expr='gte')

    class Meta:
        model = NumberClass
        fields = ['prop_number']