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

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

给定一个Class:

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`.

Given a class:

from django.db import models

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

Is it possible, and if so how, to have a QuerySet that filters based on dynamic arguments? For example:

 # 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`.

回答 0

Python的参数扩展可用于解决此问题:

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

Person.objects.filter(**kwargs)

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

Python’s argument expansion may be used to solve this problem:

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

Person.objects.filter(**kwargs)

This is a very common and useful Python idiom.


回答 1

一个简化的例子:

在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")   

A simplified example:

In a Django survey app, I wanted an HTML select list showing registered users. But because we have 5000 registered users, I needed a way to filter that list based on query criteria (such as just people who completed a certain workshop). In order for the survey element to be re-usable, I needed for the person creating the survey question to be able to attach those criteria to that question (don’t want to hard-code the query into the app).

The solution I came up with isn’t 100% user friendly (requires help from a tech person to create the query) but it does solve the problem. When creating the question, the editor can enter a dictionary into a custom field, e.g.:

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

That string is stored in the database. In the view code, it comes back in as self.question.custom_query . The value of that is a string that looks like a dictionary. We turn it back into a real dictionary with eval() and then stuff it into the queryset with **kwargs:

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

回答 2

Django.db.models.Q正是您想要的Django方式。

Django.db.models.Q is exactly what you want in a Django way.


回答 3

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

您究竟希望如何获得列名和操作的值?您从哪里获得'name'an 的值'startswith'

 filter_by = '%s__%s' % ('name', 'startswith')
  1. “搜索”表格?您要-什么?-从名称列表中选择名称?从操作列表中选择操作?尽管开放式,但大多数人都觉得这令人困惑且难以使用。

    有多少列具有此类过滤器?6吗 12吗 18吗

    • 一些?复杂的选择列表没有任何意义。一些字段和一些if语句是有意义的。
    • 大量?您的模型听起来不正确。听起来“字段”实际上是另一个表中的一行的键,而不是列的键。
  2. 特定的过滤器按钮。等等…这就是Django管理员的工作方式。特定的过滤器变成按钮。与上述分析相同。一些过滤器很有意义。大量的过滤器通常意味着一种违反第一范式的行为。

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

A really complex search forms usually indicates that a simpler model is trying to dig it’s way out.

How, exactly, do you expect to get the values for the column name and operation? Where do you get the values of 'name' an 'startswith'?

 filter_by = '%s__%s' % ('name', 'startswith')
  1. A “search” form? You’re going to — what? — pick the name from a list of names? Pick the operation from a list of operations? While open-ended, most people find this confusing and hard-to-use.

    How many columns have such filters? 6? 12? 18?

    • A few? A complex pick-list doesn’t make sense. A few fields and a few if-statements make sense.
    • A large number? Your model doesn’t sound right. It sounds like the “field” is actually a key to a row in another table, not a column.
  2. Specific filter buttons. Wait… That’s the way the Django admin works. Specific filters are turned into buttons. And the same analysis as above applies. A few filters make sense. A large number of filters usually means a kind of first normal form violation.

A lot of similar fields often means there should have been more rows and fewer fields.