在 Django 中,如何使用动态字段查找过滤 QuerySet?

给一个班级:

from django.db import models


class Person(models.Model):
name = models.CharField(max_length=20)

有没有可能(如果有的话)有一个基于动态参数过滤的 QuerySet? 例如:

 # Instead of:
Person.objects.filter(name__startswith='B')
# ... and:
Person.objects.filter(name__endswith='B')


# ... is there some way, given:
filter_by = '{0}__{1}'.format('name', 'startswith')
filter_value = 'B'


# ... that you can run the equivalent of this?
Person.objects.filter(filter_by=filter_value)
# ... which will throw an exception, since `filter_by` is not
# an attribute of `Person`.
72045 次浏览

一个非常复杂的搜索表单通常表明一个更简单的模型正在尝试挖掘它的出路。

确切地说,您希望如何获得列名和操作的值? 你从哪里得到 'name''startswith'的值?

 filter_by = '%s__%s' % ('name', 'startswith')
  1. “搜索”表格?你要... 什么?从名单上选个名字?从操作列表中选择操作?虽然是开放式的,但是大多数人都会感到困惑和难以使用。

    有多少列有这样的过滤器? 6列? 12列? 18列?

    • 一些? 一个复杂的选择列表是没有意义的。一些字段和一些 if 语句是有意义的。
    • 很多吗?你的模型听起来不太对。听起来“字段”实际上是另一个表中某行的键,而不是列。
  2. 特定的过滤按钮。等等,姜戈管理员就是这么做的。特定的过滤器被转换成按钮。同样的分析也适用于上述情况。一些过滤器是有意义的。大量的滤波器通常意味着一种第一正规形违背。

很多类似的字段通常意味着应该有更多的行和更少的字段。

Python 的参数扩展可以用来解决这个问题:

kwargs = {
'{0}__{1}'.format('name', 'startswith'): 'A',
'{0}__{1}'.format('name', 'endswith'): 'Z'
}


Person.objects.filter(**kwargs)

这是一个非常常见和有用的 Python 习惯用法。

一个简单的例子:

在 Django 调查应用程序中,我需要一个 HTML 选择列表来显示注册用户。但是因为我们有5000个注册用户,所以我需要一种基于查询条件(比如仅仅是完成某个研讨会的人)过滤该列表的方法。为了使调查元素可重用,我需要创建调查问题的人能够将这些标准附加到该问题上(不想将查询硬编码到应用程序中)。

我想到的解决方案并不是100% 用户友好(需要技术人员的帮助才能创建查询) ,但它确实解决了问题。创建问题时,编辑器可以在自定义字段中输入字典,例如:

{'is_staff':True,'last_name__startswith':'A',}

该字符串存储在数据库中。在视图代码中,它返回为 self.question.custom_query。它的值是一个字符串,看起来就像一个字典。我们使用 eval ()将其返回到 真的字典中,然后使用 * * kwargs 将其填充到查询集中:

kwargs = eval(self.question.custom_query)
user_list = User.objects.filter(**kwargs).order_by("last_name")

Q 正是您在 Django 方式中想要的。

此外,为了扩展以前的答案,提出了一些进一步的代码元素的请求,我添加了一些工作代码,我正在使用 在我的 Q 代码中,让我们假设在我的请求中,可能有或没有过滤这样的字段:

publisher_id
date_from
date_until

这些字段可以出现在查询中,但也可能丢失。

这就是我如何基于聚合查询上的字段构建过滤器的,这些字段在初始查询集执行后不能进一步过滤:

# prepare filters to apply to queryset
filters = {}
if publisher_id:
filters['publisher_id'] = publisher_id
if date_from:
filters['metric_date__gte'] = date_from
if date_until:
filters['metric_date__lte'] = date_until


filter_q = Q(**filters)


queryset = Something.objects.filter(filter_q)...

希望这能帮上忙,因为我花了不少时间来挖这个。

编辑:

作为一个额外的好处,您也可以使用列表。对于前面的例子,如果您使用一个名为 Publiserid 的列表来代替 Publiser _ id,那么您可以使用下面这段代码:

if publisher_ids:
filters['publisher_id__in'] = publisher_ids

在我看来,这更容易理解:

kwargs = {
'name__startswith': 'A',
'name__endswith': 'Z',
***(Add more filters here)***


}
Person.objects.filter(**kwargs)