要注意的是: delete() 方法是QuerySet上的方法,但并不适用于 Manager 本身。这是一种保护机制,是为了避免意外地调用Entry.objects.delete() 方法导致 所有的 记录被误删除。如果你确认要删除所有的对象,那么你必须显式地调用:
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
blog.pk = None
blog.save() # blog.pk == 2
如果使用继承的话,情况会复杂一点:
class ThemeBlog(Blog):
theme = models.CharField(max_length=200)
django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3
需要将pk和id都设为None:
django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4
这样写是不会复制关系对象的,要复制关系对象,还需要一点代码:
entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors = old_authors # saves new many2many relations
一次更新多个对象 (Updating multiple objects at once)
有时你想对QuerySet中的所有对象,一次更新某个字段的值。这个要求可以用 update() 方法完成。例如:
# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
这种方法仅适用于非关系字段和ForeignKey外键字段。更新非关系字段时,传入的值应该是一个常量。更新ForeignKey字段时,传入的值应该是你想关联的那个类的某个实例。例如:
>>> b = Blog.objects.get(pk=1)
# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)
update() 方法也是即时生效,返回与查询匹配的行数(可能不等于被更新的行数,因为有些行已经是新值,不需要被更新)。 在QuerySet进行更新时,唯一的限制就是一次只能更新一个数据表,就是当前 model 的主表。你可以尝试更新关联表和与此类似的操作,但是只有主表的条目被更新:
>>> b = Blog.objects.get(pk=1)
# Update all the headlines belonging to this Blog.
>>> Entry.objects.select_related().filter(blog=b).update(headline='Everything is the same')
要小心的是:update() 方法是直接翻译成一条SQL语句的。因此它是直接地一次完成所有更新。它不会调用你的 model 中的save() 方法,也不会发出pre_save 和post_save信号(这些信号在调用 save() 方法时产生)。如果你想保存QuerySet中的每个对象,并且调用每个对象各自的save() 方法,那么你不必另外多写一个函式。只要遍历这些对象,依次调用 save() 方法即可:
for item in my_queryset:
item.save()
在调用update时可以使用 F() 对象来把某个字段的值更新为另一个字段的值。这对于自增记数器是非常有用的。例如,给所有的博文 (entry) 的广播数 (pingback) 加一:
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
但是,与 F() 对象在查询时所不同的是,在filter 和 exclude子句中,你不能在 F() 对象中引入关联关系(NO-Join),你只能引用当前 model 中要更新的字段。如果你在 F() 对象引入了Join 关系object,就会抛出 FieldError 异常:
# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))
对象关联(Related objects)
当你定义在 model 定义关系时 (例如,ForeignKey, OneToOneField, 或 ManyToManyField),model 的实例自带一套很方便的API以获取关联的对象。
以最上面的 models 为例,一个 Entry 对象e能通过blog属性获得相关联的Blog对象:e.blog。
Django也提供反向获取关联对象的API,就是由从被关联的对象得到其定义关系的主对象。例如,一个Blog类的实例b对象通过entry_set属性得到所有相关联的Entry对象列表:
b.entry_set.all()
一对多关系(One-to-many relationships)
正向(Forward)
如果一个model有一个ForeignKey字段,我们只要通过使用关联model的名称就可以得到相关联的外键对象。
>>> e = Entry.objects.get(id=2)
>>> e.blog # Returns the related Blog object.
你可以设置和获得外键属性。正如你所期望的,改变外键的行为并不引发数据库操作,直到你调用 save()方法时,才会保存到数据库。例如:
>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()
如果外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。例如:
>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ..
在一对多关系中,第一次正向获取关联对象时,关联对象会被缓存。其后根据外键访问时这个实例,就会从缓存中获得它。例如:
>>> e = Entry.objects.get(id=2)
>>> print(e.blog) # 访问数据库
>>> print(e.blog) # 使用缓存
要注意的是,QuerySet 的select_related() 方法提前将所有的一对多关系放入缓存中。例如:
>>> e = Entry.objects.select_related().get(id=2)
>>> print(e.blog) # 使用缓存
>>> print(e.blog) # 使用缓存
逆向关联(Following relationships "backward")
如果model有一个ForeignKey外键字段,那么外联model的实例可以通过访问Manager来得到所有相关联的源model的实例。默认情况下,这个Manager被命名为 FOO_set, 这里面的 FOO 就是源 model 的小写名称。这个Manager返回QuerySets,它是可过滤和可操作的,在上面 "对象获取(Retrieving objects)" 有提及。
>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # Returns all Entry objects related to Blog.
# b.entry_set is a Manager that returns QuerySets.
>>> b.entry_set.filter(headline__contains='Lennon')
>>> b.entry_set.count()
你可以通过在ForeignKey() 的定义中设置related_name的值来覆写FOO_set 的名称。例如,如果 Entry model 中做一下更改: blog = ForeignKey(Blog, related_name='entries'),那么接下来就会如我们看到这般:
>>> b = Blog.objects.get(id=1)
>>> b.entries.all() # Returns all Entry objects related to Blog.
# b.entries is a Manager that returns QuerySets.
>>> b.entries.filter(headline__contains='Lennon')
>>> b.entries.count()
ForeignKey Manager 还有如下一些方法。下面仅仅对它们做一个简短介绍:
add(obj1, obj2, ...)
将某个特定的 model 对象添加到被关联对象集合中。
create(**kwargs)
创建并保存一个新对象,然后将这个对象加被关联对象的集合中,然后返回这个新对象。
remove(obj1, obj2, ...)
将某个特定的对象从被关联对象集合中去除。
clear()
清空被关联对象集合。
想一次指定关联集合的成员,那么只要给关联集合分配一个可迭代的对象即可。它可以包含对象的实例,也可以只包含主键的值。例如:
b = Blog.objects.get(id=1)
b.entry_set = [e1, e2]
在这个例子中,e1 和 e2 可以是完整的 Entry 实例,也可以是整型的主键值。
如果 clear() 方法是可用的,在迭代器(上例中就是一个列表)中的对象加入到 entry_set 之前,已存在于关联集合中的所有对象将被清空。如果 clear() 方法 不可用,原有的关联集合中的对象就不受影响,继续存在。
这一节提到的每一个 "reverse" 操作都是实时操作数据库的,每一个添加,创建,删除操作都会及时保存将结果保存到数据库中。
多对多关系(Many-to-many relationships)
在多对多关系的任何一方都可以使用 API 访问相关联的另一方。多对多的 API 用起来和上面提到的 "逆向" 一对多关系关系非常相象。
唯一的差虽就在于属性的命名: ManyToManyField 所在的 model (为了方便,我称之为源model A) 使用字段本身的名称来访问关联对象;而被关联的另一方则使用 A 的小写名称加上 '_set' 后缀(这与逆向的一对多关系非常相象)。
下面这个例子会让人更容易理解:
e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')
a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.
与 ForeignKey 一样, ManyToManyField 也可以指定 related_name。在上面的例子中,如果 Entry 中的 ManyToManyField 指定 related_name='entries',那么接下来每个 Author 实例的 entry_set 属性都被 entries 所代替。
一对一关系(One-to-one relationships)
相对于多对一关系而言,一对一关系是非常简单的。如果你在 model 中定义了一个 OneToOneField 关系,那么你就可以用这个字段的名称做为属性来访问其所关联的对象。
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
与 "reverse" 查询不同的是,一对一关系的关联对象也可以访问Manager对象,但是这个Manager表现一个单独的对象,而不是一个列表:
e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object
如果一个空对象被赋予关联关系,Django就会抛出一个DoesNotExist 异常。
和你定义正向关联所用的方式一样,类的实例也可以赋予逆向关联关系:
e.entrydetail = ed
关系中的反向连接是如何做到的?
其他对象关系的映射(ORM)需要你在关联双方都定义关系。而 Django 的开发者则认为这违背了 DRY 原则 (Don't Repeat Yourself),所以Django只需要你在一方定义关系即可。
但仅由一个model类并不能知道其他model 类是如何与它关联的,除非是其他model也被载入,那么这是如何办到的?
答案就在于 INSTALLED_APPS 设置中。任何一个 model 在第一次调用时,Django 就会遍历所有的 INSTALLED_APPS 的所有models,并且在内存中创建中必要的反向连接。本质上来说,INSTALLED_APPS 的作用之一就是确认 Django 完整的model范围。
在关联对象上的查询(Queries over related objects)
包含关联对象的查询与包含普通字段值的查询都遵循相同的规则。为某个查询指定某个值的时候,你可以使用一个类实例,也可以使用对象的主键值。
例如,如果你有一个 Blog 对象 b ,它的 id=5, 下面三个查询是一样的:
Entry.objects.filter(blog=b) # Query using object instance
Entry.objects.filter(blog=b.id) # Query using id from instance
Entry.objects.filter(blog=5) # Query using id directly
直接使用SQL(Falling back to raw SQL)
如果你发现某个 SQL 查询用 Django 的数据库映射来处理会非常复杂的话,你可以使用直接写 SQL 来完成。
建议的方式是在你的model自定义方法或是自定义model的manager方法来运行查询。虽然Django不要求数据操作必须在model层中执行。但是把你的商业逻辑代码放在一个地方,从代码组织的角度来看,也是十分明智的。
最后,要注意的是,Django的数据操作层仅仅是访问数据库的一个接口。你可以用其他的工具,编程语言,数据库框架来访问数据库。对你的数据库而言,没什么是非用 Django 不可的。