【问题标题】:In Django, how does one filter a QuerySet with dynamic field lookups?在 Django 中,如何使用动态字段查找过滤 QuerySet?
【发布时间】:2010-09-23 13:39:01
【问题描述】:

给定一个类:

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

【问题讨论】:

    标签: python django django-models


    【解决方案1】:

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

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

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

    【讨论】:

    • 只是一个快速的提示:确保 kwargs 中的字符串是 str 类型而不是 unicode,否则 filter() 会发牢骚。
    • @santiagobasulto 也称为参数打包/解包及其变体。
    • @DanielNaab 但这仅适用于处理 AND 条件过滤的 kwargs,OR 条件的任何替代方案。
    • @prateek 你总是可以使用 Q 对象:stackoverflow.com/questions/13076822/…
    • @deecodameeko 如何在 kwargs 中 Q 对象?
    【解决方案2】:

    一个简化的例子:

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

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

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

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

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

    【讨论】:

    • 我想知道如何创建一个自定义的 ModelField/FormField/WidgetField 来实现允许用户在 GUI 端基本上“构建”一个查询,而不会看到实际文本,但使用界面来执行此操作。听起来像一个整洁的项目...
    • T. Stone - 我想如果需要查询的模型很简单,那么以简单的方式构建这样一个工具会很容易,但是很难以一种彻底的方式来展示所有可能的选项,尤其是在模型很复杂的情况下。
    • -1 在用户导入时调用eval() 是个坏主意,即使您完全信任您的用户。在这里使用 JSON 字段会更好。
    【解决方案3】:

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

    【讨论】:

    • 您(或某人)能否提供一个示例,说明如何在使用动态字段名称时使用 Q 对象?
    • Daniel Naab's answer 中的相同,唯一的区别是您将参数传递给 Q 对象构造函数。 Q(**filters),如果您想动态构建 Q 对象,可以将它们放在列表中并使用 .filter(*q_objects),或者使用按位运算符组合 Q 对象。
    • 这个答案应该真的包括一个使用Q解决OP问题的例子。
    • 我在这里有一个例子,但它可能会在评论中中断,所以我在这里写了额外的答案。
    【解决方案4】:

    此外,为了扩展先前对进一步代码元素提出一些请求的答案,我正在添加一些我正在使用的工作代码 在我的 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)...
    

    希望这会有所帮助,因为我花了很多时间来挖掘它。

    【讨论】:

      【解决方案5】:

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

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

       filter_by = '%s__%s' % ('name', 'startswith')
      
      1. “搜索”表单?你要——什么? -- 从名字列表中选择名字?从操作列表中选择操作?虽然是开放式的,但大多数人觉得这令人困惑且难以使用。

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

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

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

      【讨论】:

      • 恕我直言,在对设计一无所知的情况下提出建议是冒昧的。为了“简单地实现”这个应用程序将产生天文(> 200 个应用程序 ^21 foos)功能来满足要求。您正在阅读示例的目的和意图;你不应该。 :)
      • 我遇到过很多人,他们认为只要事情 (a) 更通用并且 (b) 按照他们想象的方式工作,他们的问题将很容易解决。那样会带来无尽的挫败感,因为事情并不是他们想象的那样。我见过太多的失败源于“修复框架”。
      • 根据 Daniel 的回复,一切正常。我的问题是关于语法,而不是设计。如果我有时间写出设计,我会这样做的。我相信您的意见会有所帮助,但这不是一个实际的选择。
      • S.Lott,您的回答甚至没有远程回答这个问题。如果您不知道答案,请不要理会这个问题。当您对设计的了解完全为零时,不要主动提供设计建议!
      • @slypete:如果对设计的更改消除了问题,那么问题就解决了。基于糟糕的设计继续沿着这条道路前进比必要的更昂贵和复杂。解决根本原因问题比解决源于错误设计决策的其他问题要好。很抱歉你不喜欢根本原因分析。但是当事情真的很困难时,这通常意味着你一开始就尝试了错误的事情。
      猜你喜欢
      • 2020-02-06
      • 1970-01-01
      • 2014-08-07
      • 2018-03-30
      • 2015-11-11
      • 2018-10-19
      • 1970-01-01
      • 2013-07-30
      相关资源
      最近更新 更多