【问题标题】:Dynamically building complex queries in Django using Q-Objects使用 Q-Objects 在 Django 中动态构建复杂查询
【发布时间】: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 构造?

【问题讨论】:

    标签: python django


    【解决方案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() 的情况。

      【讨论】:

        猜你喜欢
        • 2023-03-17
        • 2021-07-13
        • 2020-04-22
        • 2013-05-24
        • 2021-05-27
        • 2013-12-11
        • 1970-01-01
        • 2016-06-06
        相关资源
        最近更新 更多