从其他Q()对象构建Django Q()对象,但关系跨越上下文

时间:2016-04-03 00:06:08

标签: python django django-q

我经常发现自己在我的Django应用程序中不止一次地编写相同的标准。我通常将它封装在一个返回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

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

当我开始结合这些函数的标准时,我已经意识到这一点,即Q()对象本身与对象的类型相关联.filter()被调用。这就是我所期待的。

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

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

假设我有一个函数返回一个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)

假设我想查找所有UserProfiles,其用户名以'a'开头,并希望用它们向他们发送一些电子邮件简报。我可以轻松地为后者编写一个Q()对象:

wantsEmails = Q(emailMe=True)

但发现自己想为前者做点什么事情:

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

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

换句话说,我正在寻找与Q(account=Q(id=9))一致的语法,它会返回与Q(account__id=9)相同的结果。

因此,由此产生了一些问题:

  1. 是否有一个Django Q()对象的语法,允许您向它们添加“上下文”,以允许它们从您正在运行.filter()的模型中跨越关系边界?
  2. 如果没有,这在逻辑上是否可行? (因为当我想做Q(account__id=9)这样的事情时,我可以写Q(account=Q(id=9))似乎就像它一样。

1 个答案:

答案 0 :(得分:1)

也许有人建议更好的东西,但我最终手动将上下文传递给这些功能。我不认为有一个简单的解决方案,因为你可能需要调用一整套相关的表来到你的领域,比如table1__table2__table3__profile__user__username,你会怎么猜?用户表也可以链接到table2,但在这种情况下你不需要它,所以我认为你不能手动设置路径。

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

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

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

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