如何获取模型相关对象及其子对象的总数?

3 投票
3 回答
1568 浏览
提问于 2025-04-15 18:34

在Django中,我有一个Checkout模型,它代表了一个人借用设备的票据。同时,我还有一个OrganizationalUnit模型,Checkout模型和它是有关联的(通过外键),因为借用设备的人属于我们校园的一个组织单位。

这个OrganizationalUnit模型有自我关联的特性,也就是说,一个组织单位可以有多个子单位,这些子单位还可以有自己的子单位,依此类推。下面是这些模型的简化版本。

class OrganizationalUnit(models.Model):
    name = models.CharField(max_length=100)
    parent = models.ForeignKey(
        'self',
        blank=True, null=True,
        related_name='children',
)

class Checkout(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    department = models.ForeignKey(
        OrganizationalUnit,
        null=True,
        blank=True,
        related_name='checkouts',
)

我想要统计与某个特定的组织单位及其所有子单位相关的借用记录数量。我知道如何统计与一个组织单位相关的所有借用记录。

ou = OrganizationalUnit.objects.get(pk=1)
count = ou.checkouts.all().count()

但是,我该如何让这个统计结果包含这个组织单位的子单位以及它们的子单位的借用记录呢?我需要使用某种循环吗?


编辑:我觉得我还是没能完全理解如何用while命令来实现这个功能。组织单位的层级可以根据需要嵌套得很深,但目前数据库中最多只有5层。我写了这个……

for kid in ou.children.all():
    child_checkout_count += kid.checkouts.all().count()
    for kid2 in kid.children.all():
        child_checkout_count += kid2.checkouts.all().count()
        for kid3 in kid2.children.all():
            child_checkout_count += kid3.checkouts.all().count()
            for kid4 in kid3.children.all():
                child_checkout_count += kid4.checkouts.all().count()
                for kid5 in kid4.children.all():
                    child_checkout_count += kid5.checkouts.all().count()

……结果很糟糕。而且运行起来很慢,因为它几乎遍历了数据库的大部分内容。求助!(我今天的思路似乎不太清晰。)

3 个回答

0

我不太确定SQL在这个问题上的表现,但你想做的正是你所描述的。

你需要用循环来获取所有的OU(组织单位)及其父级,然后统计借出的数量并把它们加起来。

ORM(对象关系映射)可以让你对SQL进行动态操作,但会影响性能哦 :)

3

你需要的是一个递归函数,这个函数可以遍历组织单位的关系树,并计算每个组织单位相关的借阅数量。你的代码大概会是这样的:

def count_checkouts(ou):
   checkout_count = ou.checkouts.count()
   for kid in ou.children.all():
       checkout_count += count_checkouts(kid)
   return checkout_count

另外,要获取相关借阅的数量,我使用的是:

checkout_count = ou.checkouts.count()

而不是:

count = ou.checkouts.all().count()

我的方法更高效(可以参考 http://docs.djangoproject.com/en/1.1/ref/models/querysets/#count)。

3

我觉得计算这个问题最有效的方法是在写入的时候。你应该这样修改组织单位(OrganizationalUnit):

class OrganizationalUnit(models.Model):
    name = models.CharField(max_length=100)
    parent = models.ForeignKey(
        'self',
        blank=True, null=True,
        related_name='children',
    )
    checkout_number = models.IntegerField(default=0)

创建一些函数,用来在写入的时候更新组织单位及其父级单位:

def pre_save_checkout(sender, instance, **kwargs):
    if isinstance(instance,Checkout) and instance.id and instance.department:
         substract_checkout(instance.department)

def post_save_checkout(sender, instance, **kwargs):
    if isinstance(instance,Checkout) and instance.department:
         add_checkout(instance.department)

def  substract_checkout(organizational_unit):
    organizational_unit.checkout_number-=1
    organizational_unit.save()
    if organizational_unit.parent:
        substract_checkout(organizational_unit.parent)

def  add_checkout(organizational_unit):
    organizational_unit.checkout_number+=1
    organizational_unit.save()
    if organizational_unit.parent:
        add_checkout(organizational_unit.parent)

现在你只需要把这些函数连接到预保存(pre_save)、后保存(post_save)和预删除(pre_delete)的信号上:

from django.db.models.signals import post_save, pre_save, pre_delete

pre_save.connect(pre_save_checkout, Checkout)
pre_delete.connect(pre_save_checkout, Checkout)
post_save.connect(post_save_checkout, Checkout)

这样就可以了……

撰写回答