创建数据模型后,Django会有一套数据库抽象API,允许创建,检索,更新和删除对象。
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
创建对象
在Django中有一套直观的系统:一个模型类代表一张数据表,一个模型类的实例代表数据库表中的一行记录。
要创建一个对象,用关键字初始化,然后调用save()将其存入数据库。
示例
>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
相当于执行了INSERT SQL语句
注:save()方法没有返回值
将修改保存至对对象
要将修改保存至数据库中已有的某个对象,使用save()。
示例
- 一个已被存入数据库中的Blog实例b5,本例将其改名,并在数据库中更新记录
>>> b5.name = 'New name'
>>> b5.save()
相当于执行了UPDATE SQL语句
保存ForeignKey和ManyToManyField字段
更新ForeignKey字段的方式与保存普通字段的方式相同,只需将正确类型的实例分配给相关字段。
示例
- 为Entry类的实例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()方法,为关联关系增加一条记录。
>>> 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会在添加或指定错误类型的对象时报错。
检索对象
要从数据库检索对象,要通过模型类的Manager构建一个QuerySet。
一个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."
注:Managers只能通过模型类访问,而不是通过模型实例,目的时强制分离“表级”操作和“行级“操作。
检索全部对象
从数据库中检索对象最简单的方式就是检索全部。
>>> all_entries = Entry.objects.all()
方法all()返回了一个包含数据库中所有对象的QuerySet对象
通过过滤器检索指定对象
- filter(**kwargs)
返回一个新的QuerySet,包含的对象满足给定查询参数
- exclude(**kwargs)
返回一个新的QuerySet,包含的对象不满足给定查询参数。
示例
- 包含获取2006年的博客条目的QuerySet
Entry.objects.filter(pub_date__year=2006)
链式过滤器
精炼QuerySet的结果本身还是一个QuerySet
>>> Entry.objects.filter(
... headline__startswith='What'
... ).exclude(
... pub_date__gte=datetime.date.today()
... ).filter(
... pub_date__gte=datetime.date(2005, 1, 30)
... )
每个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是独立的,第一个是基础QuerySet。第二个是第一个的子集,带有筛选条件。第三个是第一个的自己,带有额外条件。
QuerySet是惰性的
QuerySet是惰性的,创建QuerySet并不会引发任何数据库活动。
>>> 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的结果直到“要使用”时才会从数据库中拿出。
用get()检索单个对象
filter()总是返回一个QuerySet,即便只有一个对象满足查询条件。
若知道只会有一个对象满足查询条件,可以在Manager上使用get()方法
>>> one_entry = Entry.objects.get(pk=1)
注:使用切片[0]时的get()和filter()有点不同。如果没有满足条件查询的结果,get()会抛出一个DoesNotExist异常。
另外,如果Django会在有不止一个记录满足get()查询条件时发出警告。
限制QuerySet条目数
利用Python的数组切片语法将QuerySet切成指定长度。等价于SQL的LIMIT和OFFSET。
示例
- 将返回前5个对象(LIMIT 5)
>>> Entry.objects.all()[:5]
- 返回第6至第10个对象(OFFSET 5 LIMIT 5)
>>> Entry.objects.all()[5:10]
注:不支持负索引
- 检索单个对象而不是一个列表时(SELECT foo FROM bar LIMIT 1)
>>> Entry.objects.order_by('headline')[0]
大致等价于
>>> Entry.objects.order_by('headline')[0:1].get()
若没有对象满足给定条件,前者会抛出IndexError,而后则会抛出DoesNotExits。
字段查询
基本的查询关键字参数遵照field__lookuptype=value。(有个双下划线)
示例
>>> Entry.objects.filter(pub_date__lte='2006-01-01')
转换SQL语句
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
查询子句中指定的字段必须是模型的一个字段名。
不过在ForeignKey中国,指定以_id为后缀的字段名算是例外。
>>> Entry.objects.filter(blog_id=4)
传入了无效关键词参数,查询函数会抛出TypeError。
- exact
示例
>>> Entry.objects.get(headline__exact="Cat bites dog")
等同SQL语句
SELECT ... WHERE headline = 'Cat bites dog';
- iexact
不分大小写的匹配
>>> Blog.objects.get(name__iexact="beatles blog")
- contains
大小写敏感的包含测试
Entry.objects.get(headline__contains='Lennon')
粗略转为SQL
SELECT ... WHERE headline LIKE '%Lennon%';
注:会匹配标题"Today Lennon honnored",而不是"today lennon honored"
大小写不敏感使用icontains
- startswith、endswith
以…开头和以…结尾的查找
跨关系查询
Django提供了一种强大而直观的方式来“追踪”查询中的关系,在幕后自动处理SQL JOIN关系。
为了跨越关系,只需跨模型使用关联字段名,字段名由双下划线分割,直到拿到想要的字段
示例
- 检索出所有Entry对象,其Blog的name为Beatles Blog
>>> Entry.objects.filter(blog__name='Beatles Blog')
反向操作也是可以的,指向一个“反向的”关联关系,只需使用模型名的小写
示例
>>> Blog.objects.filter(entry__headline__contains='Lennon')
如果在跨多个关系中进行筛选,在某个中间模型没有满足筛选条件的值,Django会将它当作一个空的但是有效的对象。所以不会抛出错误。
跨多值连线
示例
- 筛选出所有关联条目同时满足标题含有"Lennon"且发布于2008(同一个条目,同时满足两个条件)年的博客
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
- 筛选所有条目标题包含”Lennon"或发布于2008年的博客
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
过滤器可以为模型指定字段
Django提供F表达式实现将模型字段值与同一模型中另一个字段作比较。
示例
- 要查出所有评论大于pingbacks的博客条目
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))
Django支持对F()对象进行加减乘除、求余和次方。
示例
- 找到那些评论数两倍于pingbacks的博客条目
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)
- 找出所有评论分低于pingback和评论总数之和的条目
>>> Entry.objects.filter(rating__lt=F('number_of_comments') + F('number_of_pingbacks'))
- 检索出所有作者名于博客名相同的博客
>>> Entry.objects.filter(authors__name=F('blog__name'))
主键(pk)查询快捷方式
Django提供了一种pk查询快捷方式,pk表示主键
示例
- 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查询
# 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查找支持跨链接
>>> 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缓存是空的,一旦要计算QuerySet的值,就会执行数据查询,随后,Django就会将查询结果保存在QuerySet的缓存中,并返回这些显式请求的缓存。
>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])
这意味着同样的数据库查询会被执行两次。因为在两次请求间,可能有Entry被添加或删除了
为了避免此问题,简单地保存QuerySet并复用它
>>> 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.
当QuerySet未被缓存时
查询结果集并不总是缓存结果。当仅计算查询结果集的部分时,会校验缓存,若没有填充缓存,则后续查询返回的项目不会被缓存。
示例
- 重复的从某个查询结果集对象中去指定索引的对象会每次都查询数据库
>>> queryset = Entry.objects.all()
>>> print(queryset[5]) # Queries the database
>>> print(queryset[5]) # Queries the database again
注:只是打印查询结果集并不会填充缓存。因为调用__repr__()仅返回了完整结果集的一个切片。
通过Q对象完成复杂查询
类似filter()中,查询使用的关键字参数通过"AND"连接起来,如果执行更复杂的查询(例:OR语句),可以使用Q对象。
Q对象能通过&
和|
操作符连接起来,当操作符被用于两个Q之间时会生成一个新的Q对象。
示例
生成一个Q对象,表示两个"question_startswith"查询语句之间的OR关系
Q(question__startswith='Who') | Q(question__startswith='What')
等价于SQL WHER子句
WHERE question LIKE 'Who%' OR question LIKE 'What%'
Q对象也可以通过~
操作符反转,允许在组合查询中组合普通查询或反向(NOT)查询
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
每个接受关键字参数的查询函数(例filter,get)同时接受一个或多个Q对象作为位置参数。
若提供多个Q对象参数,这些参数会通过AND连接
示例
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
粗略等同于
SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
若提供了Q对象,那么它必须位于所有关键字参数之前。
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who',
)
比较对象
通常,删除方法被命名为delete()。该方法立刻删除对象,并返回被删除的对象数量和一个包含了每个被删除对象类型的数量的字典。
>>> e.delete()
(1, {'weblog.Entry': 1})
也可以批量删除对象,所有QuerySet都有个delete()方法,会删除QuerySet中的所有成员
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})
关联对象
当在模型中定义了关联关系(如ForeignKey),该模型的实例会自动获取一套API,能快捷地访问关联对象。
一对多关联
- 正向访问
若模型有个ForeignKey,该模型的实例能通过其属性方便的访问关联(外部的)对象。
示例
>>> e = Entry.objects.get(id=2)
>>> e.blog # Returns the related Blog object.
可以通过foreign-key属性获取和设置值。
>>> 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) # Hits the database to retrieve the associated Blog.
>>> print(e.blog) # Doesn't hit the database; uses cached version.
- "反向"关联
若模型有ForeignKey,外键关联的模型实例将能访问Manager,后者会返回第一个模型的所有实例。
默认情况下,该Manager名为FOO_set,FOO即原模型名的小写形式。
示例
>>> 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模型为blog = ForeignKey(Blog, on_delete=models.CASCADE, 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()
多对多关联
多对多关联的两端均自动获取访问另一端的API。
定义了ManyToManyField的模型使用字段名作为属性名,而“反向”模型使用原模型名的小写形式,加上_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.
一对一关联
一对一关联与多对一关联非常类似。
如果在模型中定义OneToOneField,该模型的实例只需通过其属性就能访问关联对象。
示例
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
反向关联是如何实现的
Django启动时,它会导入INSTALLED_APPS列出的每个应用,和每个应用中的model模块,无论何时创建了一个新模型类,Django为每个关联模型添加反向关联。
若被关联的模型未被导入,Django会持续追踪这些关联,并在关联模型被导入时添加关联关系。