跨越关系查询
Django提供了强大并且直观的方式解决跨越关联的查询. 它在后台自动执行包含JOIN的SQL语句.
要跨越某个关联, 只需使用关联的模型字段名, 并使用双下划线分割
# 返回所有Blog的name为'Beatles Blog'的Entry对象
>>> Entry.objects.filter(blog__name='Beatles Blog')
<QuerySet [<Entry: laowang blog>]>
F表达式
将模型的一个字段与同一个模型的另外一个字段进行比较, 使用Django提供的F表达式
# 查找comments数目多于pingbacks数目的Entry
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))
<QuerySet [<Entry: laowang blog>]>
Django支持对F()对象进行加、减、乘、除、求余以及幂运算等算术操作
例如查找comments数目比pingbacks两倍还要多的Entry
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)
还可以在F()中使用双下划线来进行跨表查询
例如查询author的名字与blog名字相同的Entry
>>> Entry.objects.filter(authors__name=F('blog__name'))
pk
pk就是primary key的缩写, 通常一个模型的主键为’id’.
>>> Blog.objects.get(pk=1)
<Blog: Beatles Blog>
可以跨表操作
>>> Entry.objects.filter(blog__pk=1)
转义百分符号和下划线
在原生SQL语句中%符号有特殊的作用。Django自动转义了百分符号和下划线,可以和普通字符一样使用它们
>>> Entry.objects.filter(headline__contains='%')
缓存与查询集
- 对于新创建的QuerySet, 它的缓存是空的. 当QuerySet第一次被提交后, 数据库执行实际的查询操作, Django会把查询的结果保存在QuerySet的缓存内. 随后的对于该QuerySet的提交将重用这缓存的数据
>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])
上面的例子会加倍数据库的负载.
为了避免上面的问题, 可以使用以下方式
>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # 提交查询
>>> print([p.pub_date for p in queryset]) # 重用查询缓存
什么时候不会被缓存
有一些操作不会缓存QuerySet , 例如切片和索引.
例如 :
>>> queryset = Entry.objects.all()
>>> print(queryset[5])
>>> print(queryset[5])
上面的两个print都会执行实际的数据库查询.
遍历过整个QuerySet,后续的操作则会使用缓存
>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # 查询数据库
>>> print(queryset[5]) # 使用缓存
>>> print(queryset[5]) # 使用缓存
Q对象
用于封装关键字参数的集合
from django.db.models import Q
Q(question__startswith='What')
可以使用&
或者|
或~
来组合Q对象
Q(question__startswith='who') | Q(question_startswith='What')
比较对象
比较两个模型实例, 只需要使用python提供的双等号比较符就可以, 在后台, 其实比较的是两个实例的主键的值
some_entry.id == other_entry.id
判断基于下面的逻辑 :
- 同一模型下, 主键相等则实例相等
- 实例等于自己本身
- 代理模型的实例等于相同主键的父类实例
复制模型实例
将原实例的pk设置为None, 这会创建一个新的实例copy
>>> blog = Blog(name='My blog', tagline='Blogging is easy')
>>> blog.save()
>>> blog.pk = None
>>> blog.save()
在继承父类的时候, 必须同时将pk和id设为None
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 = None
django_blog.id = None
django_blog.save()
批量更新对象
只可以对普通字段和ForeignKey字段使用这个方法.
使用update() 方法可以批量为QuerySet中所有的对象进行更新操作
>>> Entry.objects.filter(pub_date__year=2021).update(headline='xiaoming blog')
如果要更新ForeignKey字段, 需设置新值为你想指向的新模型实例
>>> b = Blog.objects.get(pk=4)
>>> Entry.objects.all().update(blog=b)
关联对象的查询
一对多 (外键)
- 正向查询 : 直接通过圆点加属性, 访问外键对象
>>> e = Entry.objects.get(id=1)
>>> e.blog
<Blog: xiaoming>
- 如果一个外键字段设置有null=True属性, 可以通过给该字段赋值为None的方法移除外键值.
>>> e = Entry.objects.get(id=1)
>>> e.blog=None
>>> e.save()
正常来说保存就相当于执行SQL语句 : update blog_entry set blog_id = null;
, 但是这里的id不允许为空, 所以保存后会报错
反向查询
如果一个模型有ForeignKey, 那么该ForeignKey所指向的外键模型的实例可以通过一个管理器 FOO_set 返回原模型的所有实例
>>> b = Blog.objects.get(id=4)
>>> b.entry_set.all()
可以在ForeignKey字段的定义中,通过设置related_name来重写FOO_set的名字
多对多
多对多关系的两端都会自动获得访问另一端的API
>>> e = Entry.objects.get(id=1)
>>> e.authors.all()
<QuerySet [<Author: joe>, <Author: John>, <Author: Paul>, <Author: George>
一对一
一对一关系可以通过模型的属性访问关联的模型
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry
可以给反向关联进行赋值
e.entrydetail = ed
关联对象的增删改
-
add(obj1, obj2, …):添加指定的模型对象到关联的对象集中。
-
create(**kwargs):创建一个新的对象,将它保存并放在关联的对象集中。返回新创建的对象。
-
remove(obj1, obj2, …):从关联的对象集中删除指定的模型对象。
-
clear():清空关联的对象集。
-
set([obj1,obj2…]):重置关联的对象集。
# 增加
>>> e = Entry.objects.get(id=1)
>>> b = Blog.objects.get(id=2)
>>> b.entry_set.add(e)
# 创建
>>> e = b.entry_set.create(
... headline='Hello',
... body_text='Hi',
... number_of_comments=1,
... number_of_pingbacks=1,
... rating=1)
# 删除
>>> b = Blog.objects.get(id=1)
>>> e = Entry.objects.get(id=2)
>>> b.entry_set.remove(e)