我有两个简单的模型,一个代表电影,另一个代表电影的评级.
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它保留None了Movie实例创建时的值.
.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时,我的数据库中的对象和对象之间也存在差异.我在博客上的解释与上面相同,但措辞有点不同.)
看看Django源代码,答案在于Django用来提供其优秀API的一些神奇功能.
当你实例化一个Rating对象,Django的设置(尽管有一些间接的方式使这个通用)self.movie来the_hobbit.但是,self.movie不是常规财产,而是通过__set__.的__set__方法(上面链接)着眼于值(the_hobbit),并试图设置属性movie_id的代替movie,因为它是一个ForeignKey场.但是,由于the_hobbit.pk是None,它只是设置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"
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)
在您的情况下,这将导致更有用的错误消息.