【发布时间】:2012-11-17 20:19:44
【问题描述】:
我有两个简单的模型,一个代表电影,另一个代表电影的评分。
class Movie(models.Model):
id = models.AutoField(primary_key=True)
title = models.TextField()
class Rating(models.Model):
id = models.AutoField(primary_key=True)
movie = models.ForeignKey(Movie)
rating = models.FloatField()
我的期望是我能够首先创建一个引用该电影的Movie 和一个Review,然后将它们都提交到数据库中,只要我首先提交Movie 以便它被赋予一个Review 要引用的主键。
the_hobbit = Movie(title="The Hobbit")
my_rating = Rating(movie=the_hobbit, rating=8.5)
the_hobbit.save()
my_rating.save()
令我惊讶的是,它仍然引发了 IntegrityError 抱怨我试图指定一个空外键,即使 Movie 已经提交并且现在有一个主键。
IntegrityError: null value in column "movie_id" violates not-null constraint
我通过添加一些print 声明确认了这一点:
print "the_hobbit.id =", the_hobbit.id # None
print "my_rating.movie.id =", my_rating.movie.id # None
print "my_rating.movie_id =", my_rating.movie_id # None
the_hobbit.save()
print "the_hobbit.id =", the_hobbit.id # 3
print "my_rating.movie.id =", my_rating.movie.id # 3
print "my_rating.movie_id =", my_rating.movie_id # None
my_rating.save() # raises IntegrityError
.movie 属性指的是一个Movie 实例,它确实有一个非None .id,但.movie_id 保留了None 的值,它在Movie 实例时具有被装箱了。
当我尝试提交 Review 时,我希望 Django 会查找 .movie.id,但显然它不是这样做的。
一边
在我的例子中,我通过在某些模型上覆盖 .save() 方法来处理这种行为,以便它们在保存之前再次查找外键的主键。
def save(self, *a, **kw):
for field in self._meta.fields:
if isinstance(field, ForeignKey):
id_attname = field.attname
instance_attname = id_attname.rpartition("_id")[0]
instance = getattr(self, instance_attname)
instance_id = instance.pk
setattr(self, id_attname, instance_id)
return Model.save(self, *a, **kw)
这很 hacky,但它对我有用,所以 我并不是真的在寻找解决这个特定问题的方法。
我正在寻找对 Django 行为的解释。 Django 在什么时候查找外键的主键?请具体;最好参考 Django 源代码。
【问题讨论】:
-
我不在我的电脑上,但是您是否尝试过通过管理器创建您的序列?
Movie.objects.create(title="The Hobbit")等 -
@Hedde 感谢您的建议!这确实有效。查看文档,似乎使用管理器会导致立即保存对象,因此这与在创建
Review之前手动调用Movie上的.save()相同。 Django 绝对似乎只在创建实例时检查 ID。我希望有人能找到这种行为的准确参考。 -
我尝试编写一个类装饰器,用检索
.field.id的装饰器替换模型上的所有.field_id属性。不幸的是,事实证明.field是一个描述符,它依赖于.field_id的值才能正常运行。鉴于我对 Django 内部的了解有限,这对我来说太复杂了,无法轻松处理。 -
ReverseSingleRelatedObjectDescriptor 类型检查相关对象并使其管理器可用作实例上的属性。如果您需要猴子补丁,IMO 退后一步,重新考虑您的目标。很有可能有一条比侵入 Django 内部更明显的途径。但我喜欢你的问题,希望对 Django 哲学有更多经验的人能够阐明这个 +1
-
不是严格相关但有趣的相关花絮:如果你
.save()一个实例,以便 Django 从数据库中获取一个.id,即使事务是@987654355,该实例也会保留那个.id@发生被回滚。 (对我来说,他们将如何做其他事情并不明显;我不是在批评这一点。)下次您尝试.save()它时,Django 将使用相同的主键INSERT它 - 数据库不会生成一个新的。 (我认为即使在这样的回滚情况下,大多数/所有数据库后端也永远不会生成相同的主键,所以这应该不是问题。)
标签: python django django-orm