Django 1.10中文文档-执行查询
Django 1.10中文文档: https://github.com/jhao104/django-chinese-doc
只要创建好 数据模型, Django 会自动为生成一套数据库抽象的API, 可以让你创建、检索、更新和删除对象。这篇文档阐述如何使用这些API。 关于模型查询所有选项的完整细节,请见 数据模型参考 。
在整个文档(以及参考)中,都将引用下面的模型,它是一个博客应用:
1 from django.db import models 2 3 class Blog(models.Model): 4 name = models.CharField(max_length=100) 5 tagline = models.TextField() 6 7 def __str__(self): # __unicode__ on Python 2 8 return self.name 9 10 class Author(models.Model): 11 name = models.CharField(max_length=200) 12 email = models.EmailField() 13 14 def __str__(self): # __unicode__ on Python 2 15 return self.name 16 17 class Entry(models.Model): 18 blog = models.ForeignKey(Blog) 19 headline = models.CharField(max_length=255) 20 body_text = models.TextField() 21 pub_date = models.DateField() 22 mod_date = models.DateField() 23 authors = models.ManyToManyField(Author) 24 n_comments = models.IntegerField() 25 n_pingbacks = models.IntegerField() 26 rating = models.IntegerField() 27 28 def __str__(self): # __unicode__ on Python 2 29 return self.headline
创建对象
Django 使用一种直观的方式把数据库表中的数据表示成Python对象: 一个模型类代表数据库中的一个表,一个模型类的实例代表这个数据库表中的一条记录。
使用关键字参数实例化模型实例来创建一个对象,然后调用 save()
把它保存到数据库中。
假设模型位于文件 mysite/blog/models.py
中,下面是一个例子:
1 >>> from blog.models import Blog 2 >>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') 3 >>> b.save()
上面的代码其实是执行了SQL 的 INSERT
语句。在你调用 save()
之前,Django 不会访问数据库。
save()
方法没有返回值。
修改对象
要保存一个数据库已存在对象的修改也是使用 save()
方法。
假设 Blog
的一个实例 b5
已经被保存在数据库中,下面这个例子将更改它的 name 并且更新数据库中的记录:
>>> b5.name = 'New name'
>>> b5.save()
上面的代码其实是执行了SQL 的 UPDATE
语句。在你调用 save()
之前,Django 不会访问数据库。
保存 ForeignKey
和 ManyToManyField
字段
更新 ForeignKey 字段的方式和保存普通字段相同 —— 只要把一个正确类型的对象赋值给该字段即可。 下面的例子更新了 ``Entry`
类的实例 entry
的 blog
属性,假设 Entry
和 Blog
分别已经有一个实例保存在数据库中 (所以我们才能像下面这样获取它们):
>>> from blog.models import Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()
更新 ManyToManyField
的方式有一些不同 —— 需要使用字段的 add()
方法来增加关联关系的一条记录。 下面这个例子向 entry
对象添加 Author
类的实例 joe
>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)
可以在调用 add()
方法时传入多参数,一次性向 ManyToManyField
添加多条记录:
>>> 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将会在你赋值或添加错误类型的对象时报错。
检索对象
通过使用模型中的 Manager
构造一个 QuerySet
来从数据库中获取对象。
QuerySet
表示从数据库中取出来的对象的集合。 它可以包含零个、一个或者多个 过滤器 。 过滤器的功能是基于所给的参数过滤查询的结果。 从SQL角度看, QuerySet
等价于 SELECT
语句, 过滤器就相当于 WHERE
或者 LIMIT
这样的子句。
QuerySet
通过模型的 Manager
获取。每个模型至少包含一个 Manager
, 它们默认叫做 objects
。 可以通过模型类直接访问, 例如:
1 >>> Blog.objects 2 <django.db.models.manager.Manager object at ...> 3 >>> b = Blog(name='Foo', tagline='Bar') 4 >>> b.objects 5 Traceback: 6 ... 7 AttributeError: "Manager isn't accessible via Blog instances."
注解
Managers
只能通过模型类访问,而不是模型实例。 目的是为了强制区分“表级别”的操作和“记录级别”的操作。
Manager
是 QuerySets
的主要来源。 比如, Blog.objects.all()
返回一个数据库中所有 Blog
对象的 QuerySet
。
使用过滤器检索
all()
方法返回包含数据库所有记录的 QuerySet
。 但是往往只需要获取其中的一个子集。
要创建这样一个子集,你需要在原始的的 QuerySet
上增加一些过滤条件。 有两种方式可以实现:
查询参数 (上面函数中的 **kwargs
) 需要满足特定的格式,下面 字段查询 一节会提到。
例如, 使用 filter()
方法获取年份为2006的所有文章的 QuerySet
Entry.objects.filter(pub_date__year=2006)
利用默认的管理器,它相当于:
Entry.objects.all().filter(pub_date__year=2006)
链式过滤
QuerySet
的筛选结果本身还是 QuerySet
, 所以可以将筛选语句链接在一起:
1 >>> Entry.objects.filter( 2 ... headline__startswith='What' 3 ... ).exclude( 4 ... pub_date__gte=datetime.date.today() 5 ... ).filter( 6 ... pub_date__gte=datetime(2005, 1, 30) 7 ... )
这个例子最开始获取数据库中所有对象的一个 QuerySet
, 之后增加一个过滤器,然后是一个排除器,再之后又是一个过滤器。 但是最终结果还是 QuerySet
。它包含标题以”What“开头、发布日期在2005年1月30日至当天之间的所有记录。
过滤后的 QuerySet
是独立的
每次你筛选一个 QuerySet
, 得到的都是全新的另一个 QuerySet
, 它和之前的 QuerySet
之间没有任何绑定关系。每次筛选都会创建一个独立的 QuerySet
,它可以被存储及反复使用。
例如:
>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())
这三个 QuerySets
都是独立的。第一个是包含所有标题以“What”开头的 QuerySet
。第二个是第一个的子集, 增加了限制条件,排除了 pub_date
大于等于今天的记录。 第三个也是第一个的子集,限制条件是:只要 pub_date
大于等于今天的记录。 而原来的 QuerySet
(q1
) 不会受到筛选的影响。
QuerySet
是惰性的
QuerySets
是惰性执行的 —— 创建 QuerySet
不会立即执行任何数据库的访问。 你可以将过滤器保持一整天,直到 QuerySet
被 求值 时,Django 才会真正运行这个查询。看下这个例子:
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")>>> print(q)
虽然它看上去有三次数据库访问, 但事实上只有在最后一行 (print(q)
) 时才访问一次数据库。 一般来说,只有在“请求” QuerySet
的结果时才会到数据库中去获取它们。 当你确实需要结果时,QuerySet
通过访问数据库来求值。 关于求值发生的准确时间,参见 When QuerySets are evaluated.
使用 get()
获取单个对象
filter()
始终返回一个 QuerySet
,即使只有一个对象满足查询条件。 —— 这种情况下, QuerySet
将只包含一个元素。
如果你知道只有一个对象满足你的查询,你可以使用 Manager
的 get()
方法,它直接返回该对象:
>>> one_entry = Entry.objects.get(pk=1)
你可以对 get()
使用任何查询表达式, 就和 filter()
一样, 参考 字段查询 。
但是 get()
和 filter()
有一点区别,如果没有符合条件的查询结果 get()
会抛出一个 DoesNotExist
异常。 这个异常是正在查询的模型类的一个属性,所以在上面的代码中,如果没有主键为 1 的 Entry, Django 将抛出一个 Entry.DoesNotExist
。
同样,如果 get()
满足条件的结果超过1个,Django会抛出一个 MultipleObjectsReturned
异常。
其他 QuerySet
方法
查询数据库时,大多数会使用方法 all()
, get()
, filter()
和 exclude()
。 但是这只是其中一小部分方法,有关 QuerySet
完整的方法列表,请参见 QuerySet API Reference 。
QuerySet
的Limit
可以使用Python 的切片语法来限制 QuerySet
记录的数目。它等同于SQL 的 LIMIT
和 OFFSET
子句。
例如,下面的语句返回前面5个对象 (LIMIT 5
):
>>> Entry.objects.all()[:5]
下面这条语句返回第6至第10个对像 (OFFSET 5 LIMIT 5
):
>>> Entry.objects.all()[5:10]
不支持负数索引 (i.e. Entry.objects.all()[-1]
) 。
通常, QuerySet
的切片返回一个新的 QuerySet
– 它不会立即执行查询。但是,如果你使用了Python切片语法中的“步长”参数, 比如下面的语句将在前10个对象中每隔2个对象返回,这样会立即执行数据库查询:
>>> Entry.objects.all()[:10:2]
若不想获取列表,要一个单一的对象(e.g. SELECT foo FROM bar LIMIT 1
),可以直接使用位置索引而不是切片。 。例如,下面的语句返回数据库中根据标题排序后的第一条 Entry
>>> Entry.objects.order_by('headline')[0]
它等同于:
>>> Entry.objects.order_by('headline')[0:1].get()
不同的是,如果没有满足条件的结果,第一种方法将引发 IndexError
异常,第二种方法会引发 DoesNotExist
异常。 更多细节参见 get()
。
字段查询
字段查询是指如何指定SQL WHERE
子句的内容, 它们通过 QuerySet
的 filter()
, exclude()
和get()
方法的关键字参数指定。
查询的关键字参数的基本形式是 field__lookuptype=value
. (中间是两个下划线)。 例如:
>>> Entry.objects.filter(pub_date__lte='2006-01-01')
翻译成SQL就是:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
如何实现
Python 定义的函数可以接收任意的键/值对参数,这些名称和参数可以在运行时求值。更多信息, 参见Python 官方文档中的 关键字参数 。
查询条件中指定的字段必须是模型字段的名称。但有一个例外,对于 ForeignKey
你可以使用字段名加上 _id
后缀。在这种情况下,该参数的值应该是外键的原始值。例如:
>>> Entry.objects.filter(blog_id=4)
如果传入的是一个不合法的参数,查询函数将引发 TypeError
。
这些数据库API 支持大约二十多种查询的类型; 完整的参考请参见 字段查询 。 下面是一些可能用到的常见查询:
exact
-
“精确”匹配。例如:
>>> Entry.objects.get(headline__exact="Cat bites dog")
将生成下面的SQL:
SELECT ... WHERE headline = 'Cat bites dog';
如果没有提供查询类型 – 即如果关键字参数不包含双下划线 – 默认查询类型就是
exact
。因此,下面的两条语句相等:
>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied这是为了方便,因为
exact
查询是最常见的查询。 iexact
-
大小写不敏感的匹配。所以这个查询:
>>> Blog.objects.get(name__iexact="beatles blog")
将匹配到标题为
"Beatles Blog"
和"beatles blog"
甚至"BeAtlES blOG"
的Blog
。 contains
-
大小写敏感的包含关系。 例如:
Entry.objects.get(headline__contains='Lennon')
可以翻译成下面的SQL:
SELECT ... WHERE headline LIKE '%Lennon%';
注意,这种可以匹配到
'Today Lennon honored'
但匹配不到'today lennon honored'
。同样也有个大小写不敏感的版本
icontains
。 startswith
和endswith
- 分别是查找以目标字符串开头和结尾的记录,同样的,它们都有一个不区分大小写的方法
istartswith
和iendswith
。
上面罗列的仅仅是部分查询方法,完整的参考: 字段查询参考.
夸关联关系查询
Django 提供了强大而又直观的方式来“处理”查询中的关联关系,它在后台自动帮你处理 JOIN
。 若要使用关联关系的字段查询,只需使用关联的模型字段的名称,并使用双下划线分隔。
比如要获取所有 Entry
中所有 Blog
的 name
为 'Beatles Blog'
的对象:
>>> Entry.objects.filter(blog__name='Beatles Blog')
而且这种查询可以是任意深度的。 反过来也是可行的。若要引用一个“反向”的关系,使用该模型的小写的名称即可。
比如,获取所有 Blog
中 Entry
的 headline
包含 'Lennon'
的对象:
>>> Blog.objects.filter(entry__headline__contains='Lennon')
如果多个关联关系直接过滤而且其中某个中间模型没有满足过滤条件的值, Django 会把它当做一个空的(所有的值都为NULL)合法对象。这意味着不会引发任何错误。例如,在下面的过滤器中:
Blog.objects.filter(entry__authors__name='Lennon')
(假设存在 Author
的关联模型), 如果没有找到符合条件的 author
, 那么都会返回空, 而不是引发缺失 author
的异常, 这是一种比较好的处理方式,但是当使用 isnull
就会有二义性。 例如:
Blog.objects.filter(entry__authors__name__isnull=True)
这将会返回 author
中 name
为空的 Blog
对象,以及 entry
中 author
为空的``Blog`` 对象。 如果你不需要后者,你可以修改成:
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)
跨关联关系多值查询
当你使用 ManyToManyField
或者 ForeignKey
来过滤一个对象时,有两种不同的过滤方式。 对于 Blog
/Entry
的关联关系 (Blog
和 Entry
是一对多关系)。既可以查找headline为 “Lennon” 并且pub_date是2008的Entry, 也可以查找 headline为 “Lennon” 或者 pub_date为 2008的Entry。这两种查询都是有可能并且有意义的。
ManyToManyField
也有类似的情况。比如,如果 Entry
有一个 ManyToManyField
tags
,这样可能想找到tag为 “music” and “bands” 的Entry, 或者我们想找一个tag名为 “music” 且状态为“public”的Entry。
这些情况都可以使用 filter()
来处理。 在单个 filter()
中的条件都会被同时应用到匹配。
这种描述可能不好理解,用一个例子来说明。比如要选择所有的entry包含 “Lennon” 标题并于2008年发表的博客(即这个Blog的entry要同时包含这两个条件), 查询代码是这样的:
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
要选择Blog的entry包含 “Lennon” 标题,或者是2008年出版的,查询代码是这样的:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
假设有一个Blog拥有一个标题包含 “Lennon” 的entry和一个来自2008年的entry。 第一个查询将匹配不到Blog, 第二个查询才会匹配上这个Blog。
第二个例子中,第一个filter过滤出的查询集是所有关联有标题包含 “Lennon” 的entry的Blog, 第二个filter是在第一个的查询集中过滤出关联有发布时间是2008的entry的Blog。 第二个filter过滤出来的entry与第一个filter过滤出来的entry可能相同也可能不同。 每个filter语句过滤的是 Blog
,而不是 Entry
。
注解
夸关联关系的多值 filter()
查询和 exclude()
不同。 单个 exclude()
方法的条件不必引用同一个记录。
例如,要排除标题中包含 “Lennon” 的entry和 发布在2008的entry:
Blog.objects.exclude(entry__headline__contains='Lennon',entry__pub_date__year=2008,)
但是,这个和 filter()
不一样,它并不是排除同时满足这两个条件的Blog。 如果要排除Blog中entry的标题包含 “Lennon” 且发布时间为2008的,需要改成这样:
Blog.objects.exclude(entry__in=Entry.objects.filter(headline__contains='Lennon',pub_date__year=2008,),)
Filters 引用模型字段
在上面例子中,最多是将模型字段和常量进行比较。那么如何将模型的一个字段与模型的另外一个字段进行比较?
Django 提供了 F 表达式
来完成这种操作。 F()
的实例作为查询中模型字段的引用。可以在查询filter中使用这些引用来比较相同模型不同instance上两个不同字段的值。
比如, 如果要查找comments数目多于pingbacks的Entry,可以构造一个 F()
对象来引用pingback数目, 并在查询中使用该 F()
对象:
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django 支持对 F()
对象使用加法、减法、乘法、除法、取模以及幂计算等算术操作, 操作符两边可以都是常数或 F()
对象。例如,查找comments 数目比pingbacks 两倍还要多的Entry,可以将查询修改为:
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
查询rating 比pingback 和comment 数目总和要小的Entry,可以这样查询:
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
F()
还支持在对象中使用双下划线标记来跨关联关系查询。带有双下划线的 F()
对象将引入任何需要的join 操作以访问关联的对象。例如,如要获取author的名字与blog名字相同的Entry,可以这样查询:
>>> Entry.objects.filter(authors__name=F('blog__name'))
对于date 和date/time 字段,支持给它们加上或减去一个 timedelta
对象。 下面的例子将返回修改时间位于发布3天后的Entry:
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
F()
对象支持 .bitand()
和 .bitor()
两种位操作,例如:
>>> F('somefield').bitand(16)
pk
快捷查询
为了方便,Django 提供一个查询快捷方式 pk
,它表示“primary key” 的意思。
在 Blog
模型示例中,主键是 id
字段,所以下面三条语句是等同的:
>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # pk implies id__exact
pk
的使用不仅限于 __exact
查询 —— 任何查询类型都可以与 pk
结合来完成一个模型上对主键的查询:
# Get blogs entries with id 1, 4 and 7
>>> Blog.objects.filter(pk__in=[1,4,7])
# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)
pk
查询在 join 中适用。例如,下面三个语句是等同的:
1 >>> Entry.objects.filter(blog__id__exact=3) # Explicit form 2 >>> Entry.objects.filter(blog__id=3) # __exact is implied 3 >>> Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
LIKE
中的%和_转义
与 LIKE
SQL 语句等同的字段查询( iexact
、 contains
、 icontains
、 startswith
、 istartswith
、 endswith
和 iendswith
)将自动转义在 LIKE
语句中 使用的两个特殊的字符 —— 百分号和下划线。(在 LIKE
语句中,百分号通配符表示多个字符,下划线通配符表示单个字符)
这样语句将很直观,不会显得太抽象。例如,要获取包含一个百分号的所有的 Entry
,只需要像其它任何字符一样使用百分号:
>>> Entry.objects.filter(headline__contains='%')
Django 会帮你转义;生成的SQL 看上去会是这样s:
SELECT ... WHERE headline LIKE '%\%%';
对于下划线是同样的道理。百分号和下划线都会自动地帮你处理。
QuerySet
缓存
每个 QuerySet
都会缓存一个最小化的数据库访问。编写高效的代码前你需要理解它是如何工作的。
在一个新创建的 QuerySet
中,缓存为空。 首次对查询集进行求值——即产生数据库查询,Django将保存查询的结果到 QuerySet
的缓存中,并明确返回请求的结果( 例如,如果正在迭代 QuerySet
,则返回下一个结果) 接下来对该 QuerySet
的求值将重用缓存的结果。
请牢记这个缓存行为,因为对 QuerySet
使用不当的话,它会坑你的。 例如,下面的语句创建两个 QuerySet
,对它们求值,然后扔掉它们:
>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])
这意味着相同的数据库查询将执行两次,显然增加了你的数据库负载。 同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有 Entry
被添加进来或删除掉。
为了避免这个问题,只需保存 QuerySet
并重新使用它:
1 >>> queryset = Entry.objects.all() 2 >>> print([p.headline for p in queryset]) # Evaluate the query set. 3 >>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
何时查询集不会被缓存
查询集不会永远缓存它们的结果。当只对查询集的部分进行求值时会检查缓存, 但是如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。 这意味着使用切片或 限制查询集 将不会填充缓存。
例如,重复获取查询集对象中一个特定的索引将每次都查询数据库:
1 >>> queryset = Entry.objects.all() 2 >>> print(queryset[5]) # Queries the database 3 >>> print(queryset[5]) # Queries the database again
然而,如果已经对全部查询集求值过,则将检查缓存:
1 >>> queryset = Entry.objects.all() 2 >>> [entry for entry in queryset] # Queries the database 3 >>> print(queryset[5]) # Uses cache 4 >>> print(queryset[5]) # Uses cache
下面是一些其它例子,它们都会使得全部的查询集被求值并填充到缓存中:
1 >>> [entry for entry in queryset] 2 >>> bool(queryset) 3 >>> entry in queryset 4 >>> list(queryset)
注解
简单地打印查询集不会填充缓存。因为 __repr__()
调用只返回全部查询集的一个切片。
Q
复杂查询
filter()
等方法中的关键字参数查询都是一起进行 “AND” 操作, 如果你需要执行更复杂的查询(例如 OR
语句), 你可以使用 Q 查询对象
.
Q 对象
(django.db.models.Q
) 对象用于封装一组关键字参数。 这些关键字参数就是上文“字段查询” 中所提及的那些。
例如,下面的 Q ``对象封装一个 ``LIKE
查询:
from django.db.models import Q Q(question__startswith='What')
Q
对象可以使用 &
和 |
操作符组合。 当使用操作符将两个对象组合是,将生成一个新的 Q
对象。
例如,下面的语句产生一个 Q
对象,表示两个 "question__startswith"
查询的“OR”:
Q(question__startswith='Who') | Q(question__startswith='What')
它等同于下面的SQL WHERE
句子:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
你可以组合 &
和 |
操作符以及使用括号进行分组来编写任意复杂的 Q
对象。 同时,Q
对象可以使用 ~
操作符取反,这允许组合正常的查询和取反( NOT
) 查询:
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
每个接受关键字参数的查询函数 (e.g. filter()
, exclude()
, get()
) 都可以传递一个或多个 Q
对象作为位置参数。 如果一个查询函数有多个 Q
对象参数,这些参数的逻辑关系为“AND”。例如:
1 Poll.objects.get( 2 Q(question__startswith='Who'), 3 Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) 4 )
大体上可以翻译成这个SQL:
1 SELECT * from polls WHERE question LIKE 'Who%' 2 AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
查询函数可以混合使用 Q
对象和关键字参数。 所有提供查询函数的参数(关键字参数或 Q
对象)都将”AND”在一起。 但是,如果出现 Q
对象,它必须位于所有关键字参数的前面。例如:
1 Poll.objects.get( 2 Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), 3 question__startswith='Who', 4 )
这是一个合法的查询,等同于前面的例子; 但是:
1 # INVALID QUERY 2 Poll.objects.get( 3 question__startswith='Who', 4 Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) 5 )
这个是不合法的。
参见
Django 单元测试中的 OR查询示例 演示了几种 Q
的用法.
对象比较
使用双等号 ==
比较两个对象,其实是比较两个模型主键的值。
使用上面的 Entry
示范, 下面两个表达式是等同的:
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id
即是模型的主键名称不是 id
也没关系,这种比较总是会使用主键不叫,不论叫什么名字。 例如,如果模型的主键字段叫 name
,下面的两条语句是等同的:
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name
删除对象
删除方法叫做 delete()
。这个方法将立即删除对象, 返回被删除的对象的总数和每个对象类型的删除数量的字典。举例:
>>> e.delete() (1, {'weblog.Entry': 1})
1.9开始删除方法才有返回值。
你还可以批量删除对象。每个 QuerySet
都有 delete()
方法,它作用是删除 QuerySet
中的所有成员。
例如, 下面语句删除所有 pub_date
为2005的 Entry
>>> Entry.objects.filter(pub_date__year=2005).delete() (5, {'webapp.Entry': 5})
上面的过程是通过SQL实现的,并不是依次调用每个对象的 delete()
方法。 如果你给模型类提供了一个自定义的 delete()
方法,并且希望删除时方法被调用。 你需要”手动”调用实例的 delete()
(例如:迭代 QuerySet
调用每个实例的 delete()
方法,使用 QuerySet
的 delete()
方法)。
当Django 删除一个对象时,它默认使用SQL ON DELETE CASCADE
约束 —— 换句话讲,任何有外键指向要删除对象的对象都将一起删除。 例如:
b = Blog.objects.get(pk=1) # 这会删除该 Blog 和它所有的Entry对象 b.delete()
这种级联的行为可以通过 ForeignKey
的 on_delete
参数定义。
注意, delete()
是唯一没有在 Manager
上暴露出来的 QuerySet
方法。 这是一个安全机制来防止你意外地请求 Entry.objects.delete()
, 而删除所有的条目。 如果你确实想删除所有的对象,你必须明确地请求一个完整的查询集:
Entry.objects.all().delete()
复制对象
没有内建的复制模型实例的方法,但可以通过创建一个新的实例并将它的所有字段都拷贝过来。最简单的方法是,只需要将 pk
设置成 None
。使用blog作为演示:
1 blog = Blog(name='My blog', tagline='Blogging is easy') 2 blog.save() # blog.pk == 1 3 4 blog.pk = None 5 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:
django_blog.pk = None django_blog.id = None django_blog.save() # django_blog.pk == 4
这样就不会复制到其关联对象。 比如, Entry
有一个 ManyToManyField
的 Author
。在复制了一个entry后, 必须为这个新的entry设置一个多对多关联关系:
1 entry = Entry.objects.all()[0] # some previous entry 2 old_authors = entry.authors.all() 3 entry.pk = None 4 entry.save() 5 entry.authors.set(old_authors)
如果是 OneToOneField
, 您必须复制相关联的对象并将其赋值给新对象的字段,避免出现复制后一对多的情况。 例如,假设 entry
已经是复制后的:
detail = EntryDetail.objects.all()[0] detail.pk = None detail.entry = entry detail.save()
同时更新多个对象
可以对 QuerySet
中的所有对象修改该某个字段的值。这就需要使用 update()
方法,例如:
# 修改所有pub_date为2007的headlines
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
也可以对非关联字段和 ForeignKey
字段使用这个方法。若要更新一个非关联字段,只需提供一个新的常数值。 若要更新 ForeignKey
字段, 需设置新的值是你想指向的新的模型实例,例如:
>>> b = Blog.objects.get(pk=1)
# 修改所有的 Entry,使它们属于这个Blog
>>> Entry.objects.all().update(blog=b)
The update()
方法会立即执行并返回匹配的行数 (如果有些行的值和新值相同,返回的行数可能和被更新的行数不相等)。 更新 QuerySet
唯一的限制是它只能访问一个数据库表,也就是模型的主表。 你可以根据关联的字段过滤,但是你只能更新模型主表中的列,例如:
>>> 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语句。它是一个批量的更新操作,而且不会调用模型的save()
方法, 或者触发 pre_save
和 post_save
信号(调用 save()
方法产生), 或者遵从 auto_now
选项。 如果想保存 QuerySet
中的每个条目并确保每个实例的 save()
方法都被调用, 不需要使用任何特殊的函数来处理。只需要迭代调用它们 的 save()
方法:
for item in my_queryset:
item.save()
调用update也可以使用 F 表达式
来根据模型中的一个字段更新另外一个字段。 这种在当前值的基础上加另一个值时特别有用。例如Blog中每个Entry的pingback个数:
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
但是, 和filter和exclude子句中的 F()
对象不同,在update中不可以使用 F()
对象引入join – 只可以引用正在更新的模型的字段。如果使用 F()
对象引入了join,将引发一个 FieldError
错误:
# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))
使用原始 SQL
如果你发现自己需要编写一个对非常复杂的SQL查询(Django API不好实现),那么你就可以手动写SQL了。 Django有几个选择来编写原始的SQL查询;参见 Performing raw SQL queries 。
最后,值得注意的是Django 的数据库层只是数据库的一个接口。你可以利用其它工具、编程语言或数据库框架来访问数据库; 你的数据库并不需要迎合django的任何东西。