使用 Q-Objects 在 Django 中动态构建复杂查询

Posted

技术标签:

【中文标题】使用 Q-Objects 在 Django 中动态构建复杂查询【英文标题】:Dynamically building complex queries in Django using Q-Objects 【发布时间】:2021-01-17 01:30:59 【问题描述】:

我有一些项目的数据库查询 (containers)。

有一个相关表可以定义一些限制(仅限***org中的那些,只有dept中的那些(客户org,或者只是 team 中的那些(课程)在 dept

这是获取对象列表的(不工作的)代码:

def get_containers(customer_id: None, course_id: None):
    q_list = (Q(is_private=False), Q(is_active=True))

    if customer_id:
        try:
            customers = Customer.objects
            customer = customers.get(id=customer_id)
        except KeyError:
            return None

        # Second level restriction: may have a customer-id
        customer_q = (
            Q(restrictions__customer__isnull=True)
            | Q(restrictions__customer=customer.id)
        )

        # Third level restriction: may be restricted to a course-code
        course_q = Q(restrictions__course_code__isnull=True)
        if course_id:
            course_q |= Q(restrictions__course_code=course_id)

        # Top level restriction: restricted to org
        restrictions_q = (
            Q(restrictions__organisation=customer.org.id)
            & customer_q 
            & course_q
        )

        q_list = (Q(q_list) | Q(restrictions_q))

    print(f"q_list: q_list")
    return Container.objects.filter(q_list)

在此期间,我一直使用https://docs.djangoproject.com/en/3.0/topics/db/queries/#complex-lookups-with-q(和引用的https://github.com/django/django/blob/master/tests/or_lookups/tests.py)和之前询问的django dynamically filtering with q objects 作为引用。

我尝试了很多变体来让if customer_id: 块末尾的OR 工作 - 它们都给了我错误:

q_list = q_list | restrictions_q\nTypeError: unsupported operand type(s) for |: 'tuple' and 'Q' q_list = Q(q_list | restrictions_q)\nTypeError: unsupported operand type(s) for |: 'tuple' and 'Q' q_list |= restrictions_q\nTypeError: unsupported operand type(s) for |=: 'tuple' and 'Q' q_list.add(restrictions_q, Q.OR)\nAttributeError: 'tuple' object has no attribute 'add'

问题:如何创建q_list = q_list OR restrictions_q 构造?

【问题讨论】:

【参考方案1】:

嗯,主要问题似乎是您有元组和列表而不是 Q 对象。例如,行

q_list = (Q(is_private=False), Q(is_active=True))

会像这些选项中的任何一个一样更正确:

q_obj = Q(Q(is_private=False), Q(is_active=True))
q_obj = Q(is_private=False, is_active=True)

但也许你的整个方法可以稍微修改一下。

def get_containers(customer_id=None, course_id=None):
    customer_q = Q()
    org_q = Q()
    if customer_id:
        # this may raise Customer.DoesNotExist error
        customer = Customer.objects.get(id=customer_id)

        # Top level restriction: restricted to org
        org_q = Q(restrictions__organisation=customer.org.id)

        # Second level restriction: may have a customer-id
        customer_q = Q(
            Q(restrictions__customer__isnull=True)
            | Q(restrictions__customer=customer.id))

    course_q = Q()
    if course_id:
        # Third level restriction: may be restricted to a course-code
        course_q = Q(
            Q(restrictions__course_code__isnull=True)
            | Q(restrictions__course_code=course_id))

    # apply Q to queryset
    return Container.objects.filter(
        Q(is_private=False, is_active=True) & org_q & customer_q & course_q)

我建议你创建空的Q 对象并仅在满足条件时替换它们(例如,仅当course_id 为真时);如果条件不满足,那么它只是保持一个空的Q 对象,这意味着一个空的过滤子句。这样.filter() 调用的代码就更简单了。

【讨论】:

一个小问题 - 我需要Q(restrictions__course_code__isnull=True) 来处理所有请求,所以我们不会为这个customer 挑选任何东西,但仅限于不同 @987654331 @ .... 不过我会玩这个.... 看起来不错【参考方案2】:

根据@Ralf 的解决方案,这就是我们所使用的:

def get_containers(customer_id = None, course_id = None):
    q_list = Q(is_active=True, restrictions__organisation__isnull=True)

    if customer_id:
        try:
            customers = Customer.objects
            customer = customers.get(id=customer_id)
        except KeyError:
            return None

        q_restrict_org = Q(
            Q(restrictions__organisation__isnull=True) | 
            Q(restrictions__organisation=customer.org.id)
        )
        q_restrict_cust = Q(
            Q(restrictions__customer__isnull=True) |
            Q(restrictions__customer=customer.id)
        )
        q_restrict_course = Q(
            Q(restrictions__course_code__isnull=True) |
            Q(restrictions__course_code=course_id)
        )

        q_list = (
            Q(is_active=True) & Q(
                    q_restrict_org
                    & q_restrict_cust
                    & q_restrict_course
                )
            )

    return Container.objects.filter(q_list)

【讨论】:

【参考方案3】:

您发布的错误表明。

'tuple' 表示您从表中获得了一行。 “Q”表示查询,如查询集(多行) 并且您正在尝试合并 2.

很简单,你的问题是q_list查询执行不正确。q_list = restrictions_q.filter(q_list)

这可能是在您声明 q_list 本身时的开头,或者如果您不是指 restrictions,则必须声明与 q_list 关联的表/查询。 顺便说一句,为什么您在查询中进行了 4 次不同的计算?,您可以简单地过滤所有包含 Q() 的情况。

【讨论】:

以上是关于使用 Q-Objects 在 Django 中动态构建复杂查询的主要内容,如果未能解决你的问题,请参考以下文章

无法在 Django 中使用动态图像 URL

如何在 Django 中使用动态外键?

在 django 中使用 mailsnake 使用模板 id 动态创建活动

如何在 django 的插入查询中动态使用列名

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

如何使用 django 动态访问会话中的值?