Django过滤器:按模型属性过滤
我读到,不可能使用属性过滤Django查询集,因为Django ORM不知道如何将它们转换为SQL 但是,一旦数据被提取并加载到内存中,就可以使用这些属性在Python中对其进行过滤Django过滤器:按模型属性过滤,django,properties,django-queryset,django-filter,Django,Properties,Django Queryset,Django Filter,我读到,不可能使用属性过滤Django查询集,因为Django ORM不知道如何将它们转换为SQL 但是,一旦数据被提取并加载到内存中,就可以使用这些属性在Python中对其进行过滤 我的问题是:是否有任何库允许查询集按内存中的属性进行过滤?如果没有,查询集必须如何被篡改才能实现?以及如何将django过滤器包括在其中?django过滤器希望并假设您正在使用queryset。一旦您获取一个查询集并将其更改为一个列表,那么下游的任何东西都需要能够只处理一个列表,或者只遍历列表,而列表不再是查询集
我的问题是:是否有任何库允许查询集按内存中的属性进行过滤?如果没有,查询集必须如何被篡改才能实现?以及如何将
django过滤器
包括在其中?django过滤器
希望并假设您正在使用queryset。一旦您获取一个查询集并将其更改为一个列表
,那么下游的任何东西都需要能够只处理一个列表
,或者只遍历列表,而列表不再是查询集
如果您有一个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')
class MyPropertyFilter(django_filters.Filter):
def filter(self, qs, value):
return [row for row in qs if row.baz == value]
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
然后您可以编写MyPropertyFilter
如下所示:
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')
class MyPropertyFilter(django_filters.Filter):
def filter(self, qs, value):
return [row for row in qs if row.baz == value]
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
此时,MyPropertyFilter
下游的任何内容都将有一个列表
注意:我认为字段
的顺序应该有您的自定义筛选器,MyPropertyFilter
最后一个,因为这样它将始终在常规queryset筛选器之后处理
因此,您刚刚破坏了“queryset”API,因为某些值是破坏的。在这一点上,你必须克服下游的错误。如果
FilterSet
之后的内容需要.count
成员,您可以更改MyPropertyFilter
如下:
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')
class MyPropertyFilter(django_filters.Filter):
def filter(self, qs, value):
return [row for row in qs if row.baz == value]
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,如下所示:
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)
可能这不是您想要的,但在某些情况下它可以帮助您,因为通过非字段属性(如
属性
进行过滤不可避免地将查询集
转换为列表
(或类似),我喜欢推迟它,并在获取上下文数据
方法中对对象列表
进行过滤。为了将过滤逻辑保留在filterset
类中,我使用了一个简单的技巧。我定义了一个装饰器
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过滤器
非字段过滤方法。由于这个修饰符,过滤基本上不做(或跳过)非字段过滤方法(因为force=False
默认值)
接下来,我定义了一个要在视图
类中使用的Mixin
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\u filter\uu
的方法,这次使用force=True
总之,您需要:
- 在
视图
类中继承过滤器YattrSmixin
- 调用您的筛选方法
attr\u filter\uu
- 在筛选方法上使用
attr\u filter
decorator
简单示例(假设我有模型
名为MyModel
,具有属性
名为是静态的
,我想通过它进行过滤:
型号:
class MyModel(models.Model):
...
@property
def is_static(self):
...
视图:
过滤器:
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]
看看这个包。这是一个扩展,提供了按类属性过滤查询集的功能
文档中的简短示例:
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']
返回QuerySet而不是列表不是更好吗?当然返回QuerySet更好。但是你是说你不能。如果你不能对数据库进行筛选,那么你必须在之后进行筛选。QuerySet是绝对可以在数据库上执行的东西。如果你可以将属性更改为以某种方式执行在数据库中,您可以完全避免这种情况。但是,如果您必须在实际的python中进行过滤,那么您违反了queryset的本质,它不再是queryset。对不起,也许我没有很好地表达自己。我可以做一些类似于返回queryset([row for row in qs If row.baz==value]的事情吗
这样它就有了所有的方法,比如.count
,.filter
等等?当然,它们都只在内存中工作,根本不会访问数据库,或者至少这是我的想法。我可以让它以某种方式工作吗?没有默认方法将列表
更改回查询集
.count
我在列表中很容易,但是.filter()呢
?Django ORM不知道如何在列表上进行过滤,它只在数据库上进行过滤。无论如何,下游代码正在使用的QuerySet
API的每个部分都需要实现。如果您正确订购了过滤器,只需将QuerySet
列表放入即可代码>过滤器位于字段的末尾。我想为所有类型的属性提供一个通用API,所以是的,我可能会有一些困难的属性。@karloss,我建议通用API是Django ORM本身。几乎每次通过Django ORM将过滤器推送到数据库时,它都会比加载对数据进行筛选,然后用python自己对其进行筛选。这不是因为python太慢,而是因为您正在从数据库中提取更多数据,而不是在数据库中对其进行筛选。编写自定义的django_过滤器。对奇怪的推断属性进行筛选也没那么糟糕。@karloss,您还可以动态生成注释和过滤器。创建新的筛选器,然后使用filterset。我使用Django Rest Framework做了这项工作,效果非常好,目的是让客户端减少从服务器后端返回到自身的字段数。