从其他 Q() 对象构建 Django Q() 对象,但具有交叉上下文关系

Building Django Q() objects from other Q() objects, but with relation crossing context

我经常发现自己在我的 Django 应用程序中多次编写相同的条件。我通常会将它封装在一个 return 是 Django Q() 对象的函数中,这样我就可以在一个地方维护标准。

我将在我的代码中做这样的事情:

def CurrentAgentAgreementCriteria(useraccountid):
    '''Returns Q that finds agent agreements that gives the useraccountid account current delegated permissions.'''
    AgentAccountMatch = Q(agent__account__id=useraccountid)
    StartBeforeNow = Q(start__lte=timezone.now())
    EndAfterNow = Q(end__gte=timezone.now())
    NoEnd = Q(end=None)
    # Now put the criteria together
    AgentAgreementCriteria = AgentAccountMatch & StartBeforeNow & (NoEnd | EndAfterNow)
    return AgentAgreementCriteria

这使得我不必多次考虑数据库模型,并且我可以结合这些函数的 return 值来构建更复杂的标准。到目前为止效果很好,并且在数据库模型发生变化时已经为我节省了时间。

当我开始结合这些函数的标准时,我已经意识到,Q() 对象本质上与正在调用的对象 .filter() 的类型相关联。这就是我所期望的。

我偶尔会发现自己想使用我的一个函数中的 Q() 对象来构造另一个 Q 对象 ,该对象旨在过滤不同但相关的模型实例 .

让我们用一个 simple/contrived 例子来说明我的意思。 (这很简单,通常这不值得开销,但请记住,我在这里使用一个简单的示例来说明我的应用程序中更复杂的部分。)

假设我有一个函数 return 是一个 Q() 对象,它可以找到所有 Django 用户,其用户名以 'a':

开头
def UsernameStartsWithAaccount():
    return Q(username__startswith='a')

假设我有一个相关模型,它是一个用户配置文件,其中包含设置,包括他们是否需要我们的电子邮件:

class UserProfile(models.Model):
    account = models.OneToOneField(User, unique=True, related_name='azendalesappprofile')
    emailMe = models.BooleanField(default=False)

假设我想找到所有用户名以 'a' 开头的用户配置文件,并想用它们向他们发送一些电子邮件通讯。我可以轻松地为后者编写一个 Q() 对象:

wantsEmails = Q(emailMe=True)

但发现自己想要为前者做这样的事情:

startsWithA = Q(account=UsernameStartsWithAaccount())
# And then
UserProfile.objects.filter(startsWithA & wantsEmails)

不幸的是,这不起作用(当我尝试时它生成无效的 PSQL 语法)。

换句话说,我正在寻找一种类似于 Q(account=Q(id=9)) 的语法,它 return 与 Q(account__id=9).

的结果相同

所以,由此产生了几个问题:

  1. 是否有 Django Q() 对象的语法允许您向它们添加 "context" 以允许它们跨越您所在的模型的关系边界 运行 .filter() ?
  2. 如果不是,这在逻辑上可能吗? (因为当我想做类似 Q(account=Q(id=9)) 的事情时我可以写 Q(account__id=9) 看起来 就像它会那样)。

也许有人提出了更好的建议,但我最终手动将上下文传递给此类函数。我不认为有一个简单的解决方案,因为您可能需要调用一整条相关的 table 链才能到达您的领域,例如 table1__table2__table3__profile__user__username,您怎么猜?用户 table 也可以链接到 table2,但在这种情况下你不需要它,所以我认为你无法避免手动设置路径。

您还可以将字典传递给 Q(),将列表或字典传递给 filter() 函数,这比使用关键字参数和应用 & 更容易使用。

def UsernameStartsWithAaccount(context=''):
    field = 'username__startswith'
    if context:
        field = context + '__' + field
    return Q(**{field: 'a'})

然后,如果您只需要 AND 您的条件,您可以将它们组合成一个列表并传递给过滤器:

UserProfile.objects.filter(*[startsWithA, wantsEmails])