在Django模型上执行不相关的SQL JOIN?

3 投票
3 回答
2380 浏览
提问于 2025-04-15 19:41

我有两个模型,一个是用户(django.contrib.auth.models.User),另一个是日志(Log)。这两个模型都有一个“email”字段。日志模型没有指向用户模型的外键。我想知道如何通过这两个表的email字段来进行连接。

其实我想执行两个基本的查询。第一个是简单的连接查询,用来过滤数据。

#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类中添加一些东西来“激活”这种关系?

谢谢!

3 个回答

0

Log.email的值是否总是对应一个用户?如果是的话,直接在Log对象中添加一个外键(ForeignKey)指向用户(User)不就可以了吗?

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

有了指向用户的外键,找到你想要的内容就变得简单多了:

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

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

log.user

如果Log.email的值不一定要属于某个用户,你可以尝试在一个模型管理器中添加一个方法。

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)
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) 这一部分,使用 extra 的写法是这样的(未经测试):

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)
3

你可以在用户类上添加一个额外的方法,这种做法叫做猴子补丁(Monkey Patching)或鸭子打击(Duck Punching):

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中似乎不太受欢迎。

(我前几天遇到了“鸭子打击”这个词。它是基于鸭子类型的概念,我们不在乎某个东西属于哪个类:只要它听起来像只鸭子,那它就是鸭子。如果你打它的时候它不叫,那就继续打,直到它叫出来为止。)

撰写回答