Django什么时候查找外键的主键?

时间:2012-11-29 17:19:46

标签: python django django-orm

我有两个简单的模型,一个代表电影,另一个代表电影的评级。

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()

我的期望是,我可以先创建MovieReview引用该电影,然后将它们都提交到数据库,只要我先提交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保留了值{{ 1}} None实例被装箱时的情况。

我希望当我尝试提交Movie时,Django会查找.movie.id,但显然这不是它正在做的事情。


除了

在我的情况下,我通过在某些模型上覆盖Review方法来处理这种行为,以便在保存之前再次查找外键的主键。

.save()

这很hacky,但它对我有用我并不是在寻找这个特定问题的解决方案


我正在寻找Django行为的解释。 Django在什么时候查找外键的主键?请具体说明;对Django源代码的引用是最好的。

5 个答案:

答案 0 :(得分:10)

如文件所述:

  

关键字参数只是您所在字段的名称   在您的模型上定义。请注意,绝不会实例化模型   触摸你的数据库;为此,你需要保存()。

在模型类上添加classmethod:

class Book(models.Model):
    title = models.CharField(max_length=100)

    @classmethod
    def create(cls, title):
        book = cls(title=title)
        # do something with the book
        return book

book = Book.create("Pride and Prejudice")

在自定义管理器上添加方法(通常首选):

class BookManager(models.Manager):
    def create_book(self, title):
        book = self.create(title=title)
        # do something with the book
        return book

class Book(models.Model):
    title = models.CharField(max_length=100)

    objects = BookManager()

book = Book.objects.create_book("Pride and Prejudice")

<强>来源: https://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs#creating-objects

分配the_hobbit时,您将分配一个Movie实例,因此不会访问数据库。一旦调用'save',数据库就会填满,但是你的变量仍然指向内存中的对象,而不知道数据库的突然变化。

也就是说,改变序列的顺序也应该有效地创建对象:

the_hobbit = Movie(title="The Hobbit")
the_hobbit.save()
my_rating = Rating(movie=the_hobbit, rating=8.5)
my_rating.save()

答案 1 :(得分:10)

主要问题与想要与否的副作用有关。并且变量确实是Python中对象的指针。

当您从模型中创建对象时,它还没有主键,因为您还没有保存它。但是,在保存时,Django是否必须确保更新已存在对象的属性?主键是合乎逻辑的,但它也会导致您期望其他属性被更新。

一个例子是Django的unicode处理。无论你给什么字符集你放入数据库的文本:一旦你再次出来,Django会给你unicode。但是如果你创建一个对象(带有一些非unicode属性)并保存它,Django应该修改你现有对象的那个文本属性吗?这已经听起来有点危险了。这可能是(可能)为什么Django不会对您要求它存储在数据库中的对象进行任何动态更新。

从数据库重新加载对象会为您提供一个完美的对象,其中包含所有设置,但它也会使您的变量指向不同的对象。所以,如果你已经在你的“旧”Movie对象上给了一个指针,那么这对你的例子没有帮助。

Hedde提到的Movie.objects.create(title="The Hobbit")就是诀窍。它从数据库中返回一个电影对象 ,因此它已经有了一个id。

the_hobbit = Movie.objects.create(title="The Hobbit")
my_rating = Rating(movie=the_hobbit, rating=8.5)
# No need to save the_hobbit, btw, it is already saved.
my_rating.save()

(当我新创建的对象没有输出unicode时,我的数据库中的对象和对象之间的区别也出现了问题。我放在我的博客上的explanation与上面相同,但是措辞有点不同。)

答案 2 :(得分:9)

查看Django source,答案在于Django用于提供其优秀API的一些神奇功能。

当你实例化一个Rating对象时,Django会设置(虽然有更多的间接使这个通用)self.moviethe_hobbit。但是,self.movie不是常规属性,而是通过__set__设置。 __set__方法(上面链接)查看值(the_hobbit)并尝试设置属性movie_id而不是movie,因为它是ForeignKey字段。但是,由于the_hobbit.pk为无,因此只需将movie设置为the_hobbit。在您尝试保存评分后,它会再次查找movie_id,但会失败(它甚至不会尝试查看movie。)

有趣的是,似乎这种行为在Django 1.5中发生了变化。

而不是

setattr(value, self.related.field.attname, getattr(
    instance, self.related.field.rel.get_related_field().attname))
# "self.movie_id = movie.pk"

它现在

    related_pk = getattr(instance, self.related.field.rel.get_related_field().attname)
    if related_pk is None:
        raise ValueError('Cannot assign "%r": "%s" instance isn\'t saved in the database.' %
                            (value, instance._meta.object_name))

在您的情况下会产生更有用的错误消息。

答案 3 :(得分:0)

刚刚完成,因为我无法发表评论......

您也可以(但不是在这种情况下)愿意更改数据库端的行为。这可能对于运行某些可能导致类似问题的测试非常有用(因为它们是在提交和回滚中完成的)。有时候使用这个hacky命令可能会更好,以使测试尽可能接近应用程序的真实行为,而不是将它们打包在TransactionalTestCase中:

它与约束的属性有关...执行以下SQL命令也将解决问题(仅限PostgreSQL):

SET CONSTRAINTS [ALL / NAME] DEFERRABLE INITIALLY IMMEDIATE;

答案 4 :(得分:0)

我的意见是,在您的hobbit对象上调用 save()方法后,该对象将被保存。但是 my_rating 对象中存在的本地引用并不真正知道它必须使用数据库中存在的值更新自身。

因此,当您调用 my_rating.movi​​e.id 时,django无法再次识别电影对象上的数据库查询,因此您获得,这是值该对象的本地实例包含。

但是 my_rating.movi​​e_id 并不依赖于本地实例上存在哪些数据 - 这是一种明确的方式,要求django查看数据库并通过外键关系查看哪些信息