我是这样的模特:
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吗?
答案 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'))