Django-filter:按模型属性过滤
Posted
技术标签:
【中文标题】Django-filter:按模型属性过滤【英文标题】:Django-filter: filtering by model property 【发布时间】:2019-08-23 19:01:07 【问题描述】:我在severalplaces 上读到,无法使用属性过滤 Django 查询集,因为 Django ORM 不知道如何将它们转换为 SQL。
但是,一旦数据被提取并加载到内存中,应该可以使用这些属性在 Python 中过滤它们。
我的问题是:是否有任何库允许通过内存中的属性过滤查询集?如果不是,那么查询集究竟必须如何被篡改才能使这成为可能?以及如何将django-filter
包含在其中?
【问题讨论】:
【参考方案1】:你有困难的财产吗? 如果没有,您可以像这样将其重写为查询集:
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)
可能不是你想要的,但在某些情况下它可以帮助你
【讨论】:
我想要一个通用的 API 来处理各种属性,所以是的,我可能会遇到困难的。 @karloss,我建议通用 API 是 Django ORM 本身。几乎每次您通过 Django ORM 将过滤器推送到数据库时,它都会比加载该数据然后自己在 python 中过滤它要快。不是因为 python 太慢,而是因为您从数据库中提取了更多数据,而不是在数据库中对其进行过滤。为奇怪的推断属性编写自定义django_filters.Filter
还不错。
@karloss,您还可以即时生成注释和过滤器。创建新过滤器,然后使用 Metaclasses 创建过滤器集。为了让客户端减少从服务器后端返回到自身的字段数量,我已经使用 Django Rest Framework 完成了这项工作。【参考方案2】:
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
like:
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
你处于未知领域,你必须破解自己的方式。
不管怎样,我以前做过,并不可怕。及时处理错误。
【讨论】:
返回一个QuerySet而不是一个列表不是更好吗? 当然最好返回一个查询集。但你说你不能。如果您不能对数据库进行过滤,那么您必须在之后进行过滤。查询集是绝对可以在数据库上执行的东西。如果您可以更改您的属性以某种方式在数据库上执行,那么您可以完全避免这种情况。但是,如果你必须在实际的 python 中进行过滤,那么你就违反了查询集的本质,它不再是查询集。 对不起,可能我表达得不好。我可以做一些像return QuerySet([row for row in qs if row.baz ==value])
这样的事情,这样它就拥有.count
、.filter
等所有方法吗?当然,它们都只会在内存中工作,根本不会访问数据库,或者至少这是我的想法。我可以让这个以某种方式工作吗?
没有将list
改回QuerySet
的默认方法。 .count
在列表情况下很容易,但是 .filter()
呢? Django ORM 不知道如何对list
进行过滤,它只对数据库进行过滤。无论如何,QuerySet
API 的每个部分,您的下游代码正在使用,都需要实现。如果您正确订购过滤器,您只需将QuerySet
-> list
过滤器放在fields
顺序的末尾即可。【参考方案3】:
由于像property
这样的非字段属性过滤不可避免地会将QuerySet
转换为list
(或类似的),所以我喜欢推迟它并在get_context_data
方法中对object_list
进行过滤。为了将过滤逻辑保留在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
。
总之,您需要:
在view
类中继承FilterByAttrsMixin
调用你的过滤方法attr_filter_<field_name>
在过滤方法上使用attr_filter
装饰器
简单示例(假设我有 model
称为 MyModel
和 property
称为 is_static
我想过滤:
型号:
class MyModel(models.Model):
...
@property
def is_static(self):
...
查看:
class MyFilterView(FilterByAttrsMixin, django_filters.views.FilterView):
...
filterset_class = MyFiltersetClass
...
过滤器:
class MyFiltersetClass(django_filters.FilterSet):
is_static = django_filters.BooleanFilter(
method='attr_filter_is_static',
)
class Meta:
model = MyModel
fields = [...]
@attr_filter
def attr_filter_is_static(self, queryset, name, value):
return [instance for instance in queryset if instance.is_static]
【讨论】:
【参考方案4】:看看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']
【讨论】:
以上是关于Django-filter:按模型属性过滤的主要内容,如果未能解决你的问题,请参考以下文章
python测试开发django-169.过滤器django-filter 入门使用