django的使用3-数据库抽象API

[TOC]

数据库抽象API

一旦创建数据模型后,Django 自动给予你一套数据库抽象 API,允许你创建,检索,更新和删除对象。下面介绍这些API。

先创建三个模型:博客,作者,记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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

创建新数据

一个模型类代表一张数据表,一个模型类的实例代表数据库表中的一行记录。要创建一个对象,用关键字参数初始化它,然后调用 save() 将其存入数据库。

1
2
3
>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()

注意: 必须执行了save() 方法之后,才会把数据更新到数据库。

更新数据

更新数据有两种情况:

  • ForeignKey
  • ManyToManyField

ForeignKey

更新 ForeignKey 字段的方式与保存普通字段的方式相同——只需将正确类型的实例分配给相关字段。如下entry

1
2
3
4
5
>>> 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

更新 ManyToManyField 字段有点不同——在字段上使用 add() 方法为关联关系添加一条记录。如下entry.authors

1
2
3
>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)

查询数据

要从数据库检索对象,要通过模型类的 Manager 构建一个 QuerySet。一个 QuerySet 代表来自数据库中对象的一个集合。它可以有 0 个,1 个或者多个 filters. Filters,可以根据给定参数缩小查询结果量。

你能通过模型的 Manager 获取 QuerySet。每个模型至少有一个 Manager,默认名称是 objects。像这样直接通过模型类使用它:

1
2
3
4
5
6
7
>>> 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."

检索全部对象数据

调用 all() 方法即可。

1
all_entries = Entry.objects.all()

过滤器检索

两种最常见的方式:

  • filter(**kwargs)
  • exclude(**kwargs)

filter(**kwargs) 返回一个新的 QuerySet,包含的对象满足给定查询参数。
exclude(**kwargs) 返回一个新的 QuerySet,包含的对象 不 满足给定查询参数。

例子如下:

1
2
Entry.objects.filter(pub_date__year=2006)
Entry.objects.all().filter(pub_date__year=2006)

链式过滤器

结果本身还是一个 QuerySet,所以能串联精炼过程。例子:

1
2
3
4
5
6
7
>>> Entry.objects.filter(
... headline__startswith='What'
... ).exclude(
... pub_date__gte=datetime.date.today()
... ).filter(
... pub_date__gte=datetime.date(2005, 1, 30)
... )

这个先获取包含数据库所有条目(entry)的 QuerySet,然后排除一些,再进入另一个过滤器。最终的 QuerySet 包含标题以 “What” 开头的,发布日期介于 2005 年 1 月 30 日与今天之间的所有条目。

注意:QuerySet 是惰性的

1
2
3
4
>>> 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)) 做了一次。

检索单个对象

若你知道只会有一个对象满足查询条件,你可以在 Manager 上使用 get() 方法,它会直接返回这个对象:

1
>>> one_entry = Entry.objects.get(pk=1)

限制 QuerySet 条目数

利用 Python 的数组切片语法将 QuerySet 切成指定长度。这等价于 SQL 的 LIMIT 和 OFFSET 子句。

1
>>> Entry.objects.all()[:5]

字段查询

基本的查询关键字参数遵照 field__lookuptype=value。(有个双下划线)。例如:

1
>>> Entry.objects.filter(pub_date__lte='2006-01-01')

转换为 SQL 语句大致如下:

1
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

常见的查询

exact(默认):

1
2
>>> Entry.objects.get(headline__exact="Cat bites dog")
SQL: SELECT ... WHERE headline = 'Cat bites dog';

若关键字参数未包含双下划线 —— 查询类型会被指定为 exact。因为 exact 查询是最常见的。

iexact:不分大小写的匹配,查询语句:

1
>>> Blog.objects.get(name__iexact="beatles blog")

contains: 大小写敏感的包含。例子:

1
2
Entry.objects.get(headline__contains='Lennon')
SQL: SELECT ... WHERE headline LIKE '%Lennon%';

icontains: 大小写不敏感的包含查找。

startswith: 以……开头的查找

endswith: 以……结尾的查找

istartswith: 大小写不敏感的, 以……开头的查找

iendswith: 大小写不敏感的, 以……结尾的查找

跨关系查询

Django 提供了一种强大而直观的方式来“追踪”查询中的关系,在幕后自动为你处理 SQL JOIN 关系。为了跨越关系,跨模型使用关联字段名,字段名由双下划线分割,直到拿到想要的字段。

1
>>> Entry.objects.filter(blog__name='Beatles Blog')

本例检索出所有的 Entry 对象,其 Blogname'Beatles Blog' .

1
>>> Blog.objects.filter(entry__headline__contains='Lennon')

本例检索的所有 Blog 对象均拥有少一个 标题 含有 'Lennon' 的条目.

要筛选出所有关联条目同时满足标题含有 “Lennon” 发布于 2008 (同一个条目,同时满足两个条件)年的博客,这样写:

1
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

要筛选所有条目标题包含 “Lennon” 条目发布于 2008 年的博客,这样写:

1
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

过滤器指定字段

Django 提供了 F 表达式, 实现将模型字段值与同一模型中的另一字段做比较.

例如,要查出所有评论数大于 pingbacks 的博客条目.

1
2
>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))

F 表达式特点:

  • Django 支持对 F() 对象进行加、减、乘、除、求余和次方.

  • 用双下划线在 F() 对象中通过关联关系查询。

  • F() 对象通过 .bitand(), .bitor(), .bitxor(),.bitrightshift() 和 .bitleftshift() 支持位操作。

  • 对于 date 和 date/time 字段,你可以加上或减去一个 timedelta 对象。

主键 (pk) 查询快捷方式

出于方便的目的,Django 提供了一种 pk 查询快捷方式, pk 表示主键 “primary key”。

1
2
3
4
5
6
7
8
>>> 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

# 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 查找也支持跨连接。

1
2
3
>>> Entry.objects.filter(blog__id__exact=3) # Explicit form
>>> Entry.objects.filter(blog__id=3) # __exact is implied
>>> Entry.objects.filter(blog__pk=3) # __pk implies __id__exact

缓存和 QuerySet

每个 QuerySet 都带有缓存,尽量减少数据库访问。

例如,以下会创建两个 QuerySet,计算它们,丢掉它们:

1
2
>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])

两个结果:

  • 同样的数据库查询会被执行两次,实际加倍了数据库负载。
  • 有可能这两个列表不包含同样的记录,因为在两次请求间,可能有 Entry 被添加或删除了。

要避免此问题,保存 QuerySet 并复用它:

1
2
3
>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.

什么时候不会缓存?

当仅计算查询结果集的 部分 时,会校验缓存,若没有填充缓存,则后续查询返回的项目不会被缓存。使用数组切片或索引的 限制查询结果集 不会填充缓存。

例如:

1
2
3
>>> queryset = Entry.objects.all()
>>> print(queryset[5]) # Queries the database
>>> print(queryset[5]) # Queries the database again

若全部查询结果集已被检出,就会去检查缓存:

1
2
3
4
>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print(queryset[5]) # Uses cache
>>> print(queryset[5]) # Uses cache

触发计算结果并缓存的操作包括如下几个方法:

1
2
3
4
>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)

JSONField

JSONField 里的查找实现是不一样的,主要因为存在key转换。为了演示,我们将使用下面这个例子:

1
2
3
4
5
6
7
8
from django.db import models

class Dog(models.Model):
name = models.CharField(max_length=200)
data = models.JSONField(null=True)

def __str__(self):
return self.name

保存和查询 None

如果 None 被保存在列表或字典中,它将始终被解释为JSON的 null 值。当查询时,None 值将一直被解释为JSON的null。要查询SQL的NULL,需要使用 isnull:

1
2
3
4
5
6
7
8
9
10
11
12
>>> Dog.objects.create(name='Max', data=None)  # SQL NULL.
<Dog: Max>
>>> Dog.objects.create(name='Archie', data=Value('null')) # JSON null.
<Dog: Archie>
>>> Dog.objects.filter(data=None)
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data=Value('null'))
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data__isnull=True)
<QuerySet [<Dog: Max>]>
>>> Dog.objects.filter(data__isnull=False)
<QuerySet [<Dog: Archie>]>

Key, index, 和路径转换

为了查询给定的字典键,可以将该键作为查询名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> Dog.objects.create(name='Rufus', data={
... 'breed': 'labrador',
... 'owner': {
... 'name': 'Bob',
... 'other_pets': [{
... 'name': 'Fishy',
... }],
... },
... })
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': None})
<Dog: Meg>
>>> Dog.objects.filter(data__breed='collie')
<QuerySet [<Dog: Meg>]>


>>> Dog.objects.filter(data__owner__name='Bob')
<QuerySet [<Dog: Rufus>]>

# 如果键是个整型,那么它将在数组中被解释成一个索引:
>>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy')
<QuerySet [<Dog: Rufus>]>

如果查询时缺少键名,请使用 isnull 查询:

1
2
3
4
>>> Dog.objects.create(name='Shep', data={'breed': 'collie'})
<Dog: Shep>
>>> Dog.objects.filter(data__owner__isnull=True)
<QuerySet [<Dog: Shep>]>

包含与键查找

contains

给定的键值对都包含在顶级字段中的对象

1
2
3
4
5
6
7
8
9
10
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.create(name='Fred', data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contains={'owner': 'Bob'})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={'breed': 'collie'})
<QuerySet [<Dog: Meg>]>

contained_by

这是 contains 查找逆过程——返回的对象将是那些传递的值中的子集在对象上的键值对。例如:

1
2
3
4
5
6
7
8
9
10
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.create(name='Fred', data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contained_by={'breed': 'collie', 'owner': 'Bob'})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={'breed': 'collie'})
<QuerySet [<Dog: Fred>]>

has_key

返回给定的键位于数据顶层的对象。例如:

1
2
3
4
5
6
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.filter(data__has_key='owner')
<QuerySet [<Dog: Meg>]>

has_keys

返回所有给定的键位于数据顶层的对象。例如:

1
2
3
4
5
6
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.filter(data__has_keys=['breed', 'owner'])
<QuerySet [<Dog: Meg>]>

has_any_keys

返回任何给定的键位于数据顶层的对象。例如

1
2
3
4
5
6
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.filter(data__has_any_keys=['owner', 'breed'])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

通过 Q 对象完成复杂查询

在类似 filter() 中,查询使用的关键字参数是通过 “AND” 连接起来的。如果你要执行更复杂的查询(例如,由 OR 语句连接的查询),你可以使用 Q 对象。

一个 Q 对象 (django.db.models.Q) 用于压缩关键字参数集合。这些关键字参数由前文 “Field lookups” 指定。

例如,该 Q 对象压缩了一个 LIKE 查询:

1
2
from django.db.models import Q
Q(question__startswith='What')

Q 对象能通过 &| 操作符连接起来。当操作符被用于两个 Q 对象之间时会生成一个新的 Q 对象。

例如,该语句生成一个 Q 对象,表示两个 "question_startswith" 查询语句之间的 “OR” 关系:

1
Q(question__startswith='Who') | Q(question__startswith='What')

这等价于以下 SQL WHERE 字句:

1
WHERE question LIKE 'Who%' OR question LIKE 'What%'

你能通过 &| 操作符和括号分组,组合任意复杂度的语句。当然, Q 对象也可通过 ~ 操作符反转,允许在组合查询中组合普通查询或反向 (NOT) 查询:

1
Q(question__startswith='Who') | ~Q(pub_date__year=2005)

每个接受关键字参数的查询函数 (例如 filter(), exclude(), get()) 也同时接受一个或多个 Q 对象作为位置(未命名的)参数。若你为查询函数提供了多个 Q 对象参数,这些参数会通过 “AND” 连接。例子:

1
2
3
4
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

…粗略地转为 SQL:

1
2
SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

查询函数能混合使用 Q 对象和关键字参数。所有提供给查询函数的参数(即关键字参数或 Q 对象)均通过 “AND” 连接。然而,若提供了 Q 对象,那么它必须位于所有关键字参数之前。例子:

1
2
3
4
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who',
)

……会是一个有效的查询,等效于前文的例子;

比较对象

要比较两个模型实例,使用标准的 Python 比较操作符,两个等号: ==。实际上,这比较了两个模型实例的主键值。比较时总会使用主键,不管它叫啥。

删除对象

通常,删除方法被命名为 delete()。该方法立刻删除对象,并返回被删除的对象数量和一个包含了每个被删除对象类型的数量的字典。例子:

1
2
3
4
5
6
>>> e.delete()
(1, {'weblog.Entry': 1})

# 批量删除对象。
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

某个对象被删除时,关联对象也会被删除。例子:

1
2
3
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

复制模型实例

pk 设为 None

1
2
3
4
5
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1

blog.pk = None
blog.save() # blog.pk == 2

一次修改多个对象

1
2
3
4
5
6
7
# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
>>> b = Blog.objects.get(pk=1)

# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)

执行原生查询

若管理器方法 raw() 能用于执行原生 SQL 查询,就会返回模型实例:

Manager.raw(raw_query, params=None, translations=None)
该方法接受一个原生 SQL 查询语句,执行它,并返回一个 django.db.models.query.RawQuerySet 实例。这个 RawQuerySet 能像普通的 QuerySet 一样被迭代获取对象实例。

最好用例子来解释。假设你有以下模型:

1
2
3
4
5
6
7
8
9
class Person(models.Model):
first_name = models.CharField(...)
last_name = models.CharField(...)
birth_date = models.DateField(...)

>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
... print(p)
John Smith
Jane Jones

参考: