2010-02-24 41 views
2

我有2个模型,用户(django.contrib.auth.models.User)和一个名为Log的模型。两者都包含一个“电子邮件”字段。日志没有指向用户模型的ForeignKey。我试图找出如何使用电子邮件字段作为通用性在这两个表上执行JOIN。在不相关的Django模型上执行SQL JOIN?

基本上有2个我想要执行的查询。一个基本的过滤连接

#Get all the User objects that have related Log objects with the level parameter set to 3. 
User.objects.filter(log__level=3) 

我也想做一些聚合。

User.objects.all().anotate(Count('log')) 

当然,能够做相反的做法也是很好的。

log = Log.objects.get(pk=3) 
log.user... 

有没有办法用ORM做到这一点?也许我可以添加到模型的Meta类来“激活”关系?

谢谢!

回答

2

您可以添加一个额外的方法到User类,使用的Monkeypatching/DuckPunching:

def logs(user): 
    return Log.objects.filter(email=user.email) 

from django.contrib.auth.models import User 
User.logs = property(logs) 

现在,你可以查询用户,并要求附上日志(例如,在视图中):

user = request.user 
logs = user.logs 

这种类型的过程在Ruby世界中很常见,但似乎在Python中被忽视。

(我整个DuckPunching项附带的其他日子正是基于鸭子类型,在这里我们不关心什么类的东西是:如果它叫起来像鸭子,那么它是一只鸭子,只要我们如果它在冲击时不嘎嘎,那么继续冲刺直到它嘎嘎)。

+0

这与注释相结合,应该涵盖你的两个用例,我想。 – 2010-02-25 09:27:13

+0

+1仅供DuckPunching使用 – Wogan 2010-02-26 02:13:20

+0

因此,要“使用level参数设置为3来获取所有具有相关Log对象的User对象”,您应该为每个用户调用日志功能。似乎很多疑问。 Extra允许在一个查询中执行此操作。 – sunprophit 2014-01-29 22:11:17

2

为什么不使用extra()

例子(未测试):

User.objects.extra(
    select={ 
     'log_count': 'SELECT COUNT(*) FROM myapp_log WHERE myapp_log.email = auth_user.email' 
    }, 
) 

用于这里的User.objects.filter(log__level=3)部分是具有额外(未测试)的当量:

User.objects.extra(
    select={ 
     'log_level_3_count': 'SELECT COUNT(*) FROM myapp_log WHERE (myapp_log.email = auth_user.email) AND (myapp_log.level=3)' 
    }, 
).filter(log_level_3_count__gt=0) 
+0

这适用于anotate用例。但不是我提到的其他用户案例。我试图找到一种方法来使用ORM中的单个查询(如果可能,我宁愿不要使用原始SQL)。 – erikcw 2010-02-25 01:16:17

+0

为什么只限于一个查询?看看用单独的查询做到现在是否足够快,什么时候不用再查看优化。 – 2010-02-25 09:28:15

+0

你可以根据你添加的东西进行过滤() – Jiaaro 2010-02-26 01:49:22

0

不要的Log.email值总是对应于一个用户?如果是这样,那么只需将一个ForeignKey(用户)添加到日志对象?

class Log(models.Model): 
    # ... 
    user = models.ForeignKey(User) 

随着FK到用户,就成了相当直截了当地找到你想要的东西:

User.objects.filter(log__level=3) 
User.objects.all().anotate(Count('log')) 

user.log_set.all() 
user.log_set.count() 

log.user 

如果Log.email值并不一定属于你可以尝试添加一个用户方法到model manager

class LogManager(models.Manager): 
    def for_user(self, user): 
     return super(LobManager, self).get_query_set().filter(email=user.email) 

class Log(models.Model): 
    # ... 
    objects = LogManager() 

,然后用它是这样的:

user = User.objects.get(pk=1) 
logs_for_user = Log.objects.for_user(user) 
+0

日志实际上是第三方应用程序的一部分。因此添加一个新的领域并不理想。 – erikcw 2010-02-25 01:16:57