从外键计算的字段,需要保存两次?

时间:2014-07-07 13:13:16

标签: python django

我是这样的模特:

class IPv4Pool(models.Model):
    name = models.CharField(max_length=50)
    available_ips = models.IntegerField()

    def save(self, *args, **kwargs):
        super(IPv4Pool, self).save(*args, **kwargs)
        self.available_ips = 0
        for ip_range in self.ip_range_set.all():
            print str(ip_range)
            self.available_ips += len(ip_range)

因为self.available_ips是根据ForeignKeys计算的,并且在保存模型之前不会分配它们(至少这是我认为的),available_ips的值仅在第二次保存后计算。

优雅的pythonic方式是什么?我应该在计算值后第二次调用save吗?

1 个答案:

答案 0 :(得分:0)

当前方法存在问题

你不应该真正拥有像这样计算的数据库字段。它可能导致数据库中的数据不一致问题。

以此方案为例:

>>> pool = IPv4Pool.objects.get(pk=1)
>>> pool.available_ips
2
>>> range = IPRange()
>>> range.pool = poll
>>> range.save()
>>> pool.available_ips
2

没有人更新available_ips,所以现在数据库中的数据不一致。有3 IPRange行指向pool,但pool只认为有2行。

更好的方法

您应该将模型更新为以下内容:

class IPv4Pool(models.Model):
    name = models.CharField(max_length=255)

class IPRange(models.Model):
    pool = models.ForeignKey(IPv4Pool, related_name='ip_ranges')
    ...

class Subscriber(models.Model):
    pool = models.ForeignKey(IPv4Pool, related_name='subscribers')
    ...

使用Django ORM的功能查询此数据:

pool.ip_ranges.count()

如果您真的想在available_ips课程中使用IPv4Pool属性,请使用此字段:

class IPv4Pool(models.Model):
    name = models.CharField(max_length=50)

    @property
    def available_ips(self):
        return self.ip_ranges.count()

但对我来说,这似乎并没有带来太多好处。

处理复杂查询

根据您对此答案的评论,您希望能够根据相关对象的计数过滤IPv4Pool个对象。您还希望能够在这些查询中使用F个对象,这是明智的。这可以使用Django aggregation API实现。

举个例子,假设您想要这个查询:

  • 向我提供订阅者数量少于范围数量的所有池

你需要这个特殊的咒语:

IPv4Pool.objects.annotate(available_ips=Count('ip_ranges'),
                          num_subscribers=Count('subscribers'),
                 ).filter(num_subscribers__lt=F('available_ips'))

如果每次输入看起来过于冗长,您可以添加custom model manager,自动将这些注释添加到每个查询中:

class AnnotatedIPv4PoolManager(models.Manager):
    def get_queryset(self):
        query = super(AnnotatedIPv4PoolManager, self).get_queryset()
        return query.annotate(available_ips=Count('ip_ranges'),
                              num_subscribers=Count('subscribers'),
                              )

class IPv4Pool(models.Model):
    name = models.CharField(max_length=255)
    annotated = AnnotatedIPv4PoolManager()

然后你会像这样使用与上面相同的效果:

IPv4Pool.annotated.filter(num_subscribers__lt=F('available_ips'))
相关问题