2016-04-03 53 views
0

我经常发现自己不止一次在我的Django应用程序中编写了相同的条件。我通常会将它封装在一个返回Django Q()对象的函数中,这样我就可以在一个地方维护标准。从其他Q()对象构建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 

这使得它,这样我就不用想通过DB模式不止一次,我可以从这些组合的返回值功能建立更复杂的标准。到目前为止效果很好,并且在数据库模型更改时节省了我的时间。

当我开始将这些函数的标准结合起来时,我已经意识到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) 

说我想查找所有具有au的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

也许有人建议更好的东西,但我最终手动将上下文传递给这些函数。我不认为有一个简单的解决方案,因为您可能需要调用整个相关表链来到您的领域,如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]) 
+0

我其实更喜欢'&'和'|'经营者表达标准。如果您使用描述性名称命名较小的Q()变量,则几乎可以大声朗读代码并使其在英语中有意义。但它看起来像你必须使用字典'**'语法来创建字段名称,就像你建议的那样,我将采用你的解决方案的那部分。 (谢谢!) – Azendale