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

22 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()
Run Code Online (Sandbox Code Playgroud)

我的期望是,我能够首先创建一个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()
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是,它仍然IntegrityError抱怨我正在尝试指定一个空外键,即使Movie已经提交并且现在有一个主键.

IntegrityError: null value in column "movie_id" violates not-null constraint
Run Code Online (Sandbox Code Playgroud)

我通过添加一些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
Run Code Online (Sandbox Code Playgroud)

.movie属性指的是一个Movie具有非实例的实例None .id,但.movie_id它保留NoneMovie实例创建时的值.

.movie.id当我试图提交时,我预计Django会抬头Review,但显然这不是它正在做的事情.


在旁边

在我的情况下,我通过覆盖.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)
Run Code Online (Sandbox Code Playgroud)

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


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

Hed*_*ide 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")
Run Code Online (Sandbox Code Playgroud)

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

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")
Run Code Online (Sandbox Code Playgroud)

来源: 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()
Run Code Online (Sandbox Code Playgroud)


Rei*_*ees 10

主要问题与需要或不需要的副作用有关.并且变量确实是Python中对象的指针.

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

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

从数据库重新加载对象为您提供了设置所有内容的完美对象,但它也使您的变量指向不同的对象.因此,如果您已经为"旧"Movie对象指定了一个指针,那么这对您的示例没有帮助.

Movie.objects.create(title="The Hobbit")通过Hedde提的是这里的伎俩.它从数据库返回一个电影对象,因此它已经有一个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()
Run Code Online (Sandbox Code Playgroud)

(当我新创建的对象没有输出unicode时,我的数据库中的对象和对象之间也存在差异.我在博客上的解释与上面相同,但措辞有点不同.)


sec*_*ond 9

看看Django源代码,答案在于Django用来提供其优秀API的一些神奇功能.

当你实例化一个Rating对象,Django的设置(尽管有一些间接的方式使这个通用)self.moviethe_hobbit.但是,self.movie不是常规财产,而是通过__set__.的__set__方法(上面链接)着眼于值(the_hobbit),并试图设置属性movie_id的代替movie,因为它是一个ForeignKey场.但是,由于the_hobbit.pk是None,它只是设置moviethe_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"
Run Code Online (Sandbox Code Playgroud)

现在呢

    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))
Run Code Online (Sandbox Code Playgroud)

在您的情况下,这将导致更有用的错误消息.