Python 根据外键计算的字段,需要保存两次吗?
我有一个这样的模型:Python 根据外键计算的字段,需要保存两次吗?,python,django,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
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是从foreignkey计算出来的,在保存模型之前不会分配它们,至少我认为可用_ips的值是在第二次保存后才计算出来的
什么是优雅的蟒蛇式的方法?我应该在计算完值后再次调用save吗 当前方法的问题
您不应该有这样计算的数据库字段。这可能会导致数据库中的数据不一致问题
以这个场景为例:
>>> pool = IPv4Pool.objects.get(pk=1)
>>> pool.available_ips
2
>>> range = IPRange()
>>> range.pool = poll
>>> range.save()
>>> pool.available_ips
2
没有人更新可用的IP,因此现在数据库中的数据不一致。有3个IPRange行指向该池,但池认为只有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()
如果您确实希望IPv4Pool类上有可用的\u ips属性,请使用以下命令:
class IPv4Pool(models.Model):
name = models.CharField(max_length=50)
@property
def available_ips(self):
return self.ip_ranges.count()
但对我来说,这似乎并没有什么好处
处理复杂查询
根据您对此答案的评论,您希望能够根据相关对象的计数筛选IPv4Pool对象。您还希望能够在这些查询中使用对象,这是明智的。这可以使用djangoapi实现
例如,假设您需要此查询:
给我所有订户数小于范围数的池
你需要这个特别的咒语:
IPv4Pool.objects.annotate(available_ips=Count('ip_ranges'),
num_subscribers=Count('subscribers'),
).filter(num_subscribers__lt=F('available_ips'))
如果每次键入都过于冗长,您可以添加一个,它会自动将这些注释添加到每个查询中:
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'))
无论如何,我看不出在第一次保存时,如何让任何ip_范围对象指向这个。必须有人通过将FK指向此对象来添加它们,当然,这只能在保存对象后才能很好地进行。谢谢,使用@property效果很好。因为我不会有很多FK,所以实时计算它们而不是存储在DB上就足够了。不管你有多少FK,存储这种信息永远都是不正确的。数据库的工作就是为您完成这项工作。解决性能问题的方法是为该键创建一个数据库索引,这样数据库就不必实际执行所有数据检查,只需在索引中查找该值。这与您执行相同操作的方法之间的区别在于,数据库将永远不会不一致。我发现采用这种方法存在的一个问题是,在F表达式上不允许定义为@property的字段:num\u subscribers\uu lt=F'available\u networks'。有解决方法吗?您是否也有一个订阅者模型,具有FK到IPv4池?是的,有一个订阅者,具有FK到IPv4和IPv6池,并且IPv4池具有FK到IP_范围。