目录
0x00 – 执行查询(管理器)
我们需要知道 QuerySet 是基于管理器(Manager)的。
Manager 是一种接口,它赋予了 Django 模型操作数据库的能力。Django 应用中每个模型拥有至少一个 Manager。
一旦创建 数据模型 后,Django 自动给予你一套数据库抽象 API,允许你创建,检索,更新和删除对象。
默认情况下,Django 为每个模型类添加了一个名为 objects 的 Manager。
这里将引用以下模型,这些模型包含了一个 Webblog 应用:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField()
number_of_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __str__(self):
return self.headline
0x01 – 创建对象
为了用 Python 对象展示数据表对象,Django 使用了一套直观的系统:一个模型类代表一张数据表,一个模型类的实例代表数据库表中的一行记录。
要创建一个对象:
- 用关键字参数初始化它;
- 调用
save()
将其存入数据库。
假设模型都位于文件 mysite/blog/models.py 中,这是一个例子:
>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
这在幕后执行了 INSERT SQL 语句。Django 在你显式调用 save() 才操作数据库。
save() 方法没有返回值。
如果想要一步创建并保存对象,需要使用 create() 方法。
0x02 – 修改对象
要将修改保存至数据库中已有的某个对象,使用 save()。
有一个已被存入数据库中的 Blog 实例 b5,本例将其改名,并在数据库中更新其记录:
>>> b5.name = 'New name'
>>> b5.save()
这在幕后执行了 UPDATE
SQL 语句。Django 在你显示调用 save()
后才操作数据库。
0x02 – 1 – 保存 ForeignKey 和 ManyToManyField 字段
更新 ForeignKey 字段的方式与保存普通字段的方式相同——只需将正确类型的实例分配给相关字段。
如:为 Entry 类的实例 entry 更新了 blog 属性,假设 Entry 和 Blog 的实例均已保存在数据库中(因此能在下面检索它们):
>>> from blog.models import Blog, Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()
更新 ManyToManyField
字段有点不同——在字段上使用 add()
方法为关联关系添加一条记录。
如:将 Author 实例 joe 添加至 entry 对象:
>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)
要一次添加多行记录至 ManyToManyField
字段,在一次调用 add()
时传入多个参数。
如:
>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)
Django 会在添加或指定错误类型的对象时报错。
0x03 – 检索对象(待续)
要从数据库检索对象,要通过模型类的 Manager 构建一个 QuerySet。
一个 QuerySet 代表来自数据库中对象的一个集合。它可以有 0 个,1 个或者多个 filters. Filters
,可以根据给定参数缩小查询结果量。
在 SQL 的层面上, QuerySet 对应 SELECT 语句,而filters对应类似 WHERE 或 LIMIT 的限制子句。
你能通过模型的 Manager 获取 QuerySet。每个模型至少有一个 Manager,默认名称是 objects。像这样直接通过模型类使用它:
>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Blog instances."
Manager 是模型的 QuerySets 主要来源。例如 Blog.objects.all() 返回了一个 QuerySet,后者包含了数据库中所有的 Blog 对象。
0x04 – 查询 JSONField(待续)
JSONField 里的查找实现是不一样的,主要因为存在key转换。
0x05 – 通过Q对象完成复杂查询(待续)
Q()
对象表示一个 SQL 条件,它可以用于数据库相关的操作。它类似于 F()
对象如何表示一个模型字段或注解的值。它们使得定义和重用条件成为可能,并使用诸如 |
(OR)和 &
(AND)等运算符将它们组合起来。
0x06 – 比较对象
要比较两个模型实例,使用标准的 Python 比较操作符,两个等号:==
。
实际上它比较了两个模型实例的主键值。
使用前文的 Entry,以下的两个语句是等效的:
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id
若模型主键名不是 id,没问题。比较时总会使用主键,不管它叫啥。例如,若模型的主键字段名为 name,以下两个语句是等效的:
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name
0x07 – 删除对象
通常,删除方法被命名为 delete()
。
该方法立刻删除对象,并返回被删除的对象数量和一个包含了每个被删除对象类型的数量的字典。
例如:
>>> e.delete()
(1, {'weblog.Entry': 1})
批量删除对象。
所有 QuerySet 都有个 delete()
方法,它会删除 QuerySet 中的所有成员。
例如,这会删除 2005 发布的所有 Entry 对象:
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})
请记住,只要有机会的话,这会通过纯 SQL 语句执行,所以就无需在过程中调用每个对象的删除方法了。
若你为模型类提供了自定义的 delete()
方法,且希望确保调用了该方法,你需要 “手动” 删除该模型的实例(例如,如,遍历 QuerySet,在每个对象上分别调用 delete() 方法),而不是使用 QuerySet 的批量删除方法 delete()。
当 Django 删除某个对象时,默认会模仿 SQL 约束 ON DELETE CASCADE
的行为——换而言之,某个对象被删除时,关联对象也会被删除。例子:
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
这种约束行为由 ForeignKey 的 on_delete 参数指定。
注意 delete() 是唯一未在 Manager 上暴漏的 QuerySet 方法。这是一种安全机制,避免你不小心调用了 Entry.objects.delete(),删除了 所有的 条目。若你 确实 想要删除所有对象,你必须显示请求完整结果集合:
Entry.objects.all().delete()
0x08 – 复制模型实例
尽管没有用于复制模型实例的内置方法,但可以轻松地创建新实例并复制所有字段的值。
在最简单的情况下,您可以将 pk
设置为 None
并将 _state.adding
设置为 True
。
使用我们的博客示例:
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
blog.pk = None
blog._state.adding = True
blog.save() # blog.pk == 2
若你使用了集成,事情会更复杂。考虑下 Blog 的一个子类:
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
,并将 _state.adding
设置为 True
:
django_blog.pk = None
django_blog.id = None
django_blog._state.adding = True
django_blog.save() # django_blog.pk == 4
该方法不会拷贝不是模型数据表中的关联关系。例如, Entry 有一个对 Author 的 ManyToManyField 关联关系。在复制条目后,你必须为新条目设置多对多关联关系。
entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry._state.adding = True
entry.save()
entry.authors.set(old_authors)
对于 OneToOneField 关联,你必须拷贝关联对象,并将其指定给新对象的关联字段,避免违反一对一唯一性约束。例如,指定前文复制的 entry:
detail = EntryDetail.objects.all()[0]
detail.pk = None
detail._state.adding = True
detail.entry = entry
detail.save()
0x09 – 一次修改多个对象
有时候,你想统一设置 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 的唯一限制即它只能操作一个数据表:该模型的主表。你可以基于关联字段进行筛选,但你只能更新模型主表中的列。例子:
>>> b = Blog.objects.get(pk=1)
# Update all the headlines belonging to this Blog.
>>> Entry.objects.filter(blog=b).update(headline='Everything is the same')
要认识到 update() 方法是直接转为 SQL 语句的。这是一种用于直接更新的批量操作。它并不会调用模型的 save() 方法,或发射 pre_save 或 post_save 信号(调用 save() 会触发信号),或使用 auto_now 字段选项。若想保存 QuerySet 中的每项,并确保调用了每个实例的 save() 方法,你并不需要任何特殊的函数来处理此问题。迭代它们,并调用它们的 save() 方法
for item in my_queryset:
item.save()
调用更新方法时也能使用 F 表达式 基于同一模型另一个字段的值更新某个字段。这在基于计数器的当前值增加其值时特别有用。例如,要增加针对博客中每项条目的 pingback 技术:
>>> Entry.objects.all().update(number_of_pingbacks=F('number_of_pingbacks') + 1)
然而,与过滤器中的 F() 对象和排除字句不同,你不能在更新方法中使用 F() 对象的同时使用 join——你只能引用被更新模型的内部字段。若你试着在使用 join 字句时使用 F() 对象,会抛出一个 FieldError():
# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))
0x0a – 关联对象(待续)
当你在模型中定义了关联关系(如 ForeignKey, OneToOneField, 或 ManyToManyField),该模型的实例将会自动获取一套 API,能快捷地访问关联对象。
拿本文开始的模型做例子,一个 Entry 对象 e 通过 blog 属性获取其关联的 Blog 对象: e.blog。
(在幕后,这个函数是由 Python descriptors 实现的。这玩意一般不会麻烦你,但是我们为你指出了注意点。)
Django 也提供了从关联关系 另一边 访问的 API —— 从被关联模型到定义关联关系的模型的连接。例如,一个 Blog 对象 b 能通过 entry_set 属性 b.entry_set.all() 访问包含所有关联 Entry 对象的列表。
本章节中的所有例子都是用了本页开头定义的 Blog, Author 和 Entry 模型。
0x0b – 回归原生SQL
若你发现需要编写的 SQL 查询语句太过复杂,以至于 Django 的数据库映射无法处理,你可以回归手动编写 SQL。Django 针对编写原生 SQL 有几个选项;参考 执行原生 SQL 查询。
最后,Django 数据库层只是一种访问数据库的接口,理解这点非常重要。你也可以通过其它工具,编程语言或数据库框架访问数据库;Django 并没有对数据库数据库做啥独有的操作。
2021年9月24日