执行查询所需要的条件
from demo1.models import *
from django.utils import timezone
b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
b.save()
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.objects.create(blog=b, headline='this is headline', body_text='body..', pub_date=timezone.now(), mod_date=timezone.now(),)
entry = Entry.objects.get(pk=1)
entry
<Entry: this is headline>
entry.authors.add(john, paul, george, ringo)
b2 = Blog(name='SUN Blog', tagline='All the latest SUN news.') b2.save() Entry.objects.create(blog=b2, headline='this is Lennon headline', body_text='body..', pub_date=timezone.now(), mod_date=timezone.now(),)
跨关联关系的查询
author
Entry:
Blog:
下面这个例子获取所有Blog 的name 为'Beatles Blog' 的Entry 对象
Entry.objects.filter(blog__name='Beatles Blog')
反向查询
Blog.objects.filter(entry__authors__name='le') # <QuerySet []>
Blog.objects.filter(entry__authors__name='john') # <QuerySet [<Blog: Beatles Blog>]>
查找Entry下没有作者名字的文章,entry和blog没有关联
Blog.objects.filter(entry__authors__name__isnull=True) # <QuerySet [<Blog: Beatles Blog>, <Blog: Beatles Blog>]>
跨越多值的关联关系
当基于基于ManyToManyField 或反向的ForeignKey 来过滤一个对象时,有两种不同类的过滤器。
Django 有种一致的方法来处理filter() 调用。一个filter() 调用中的所有参数会同时应用以过滤出满足所有要求的记录。接下来的filter() 调用进一步限制对象集,但是对于多值关系,它们应用到与主模型关联的对象,而不是应用到前一个filter() 调用选择出来的对象
当同一个Blog下有两个entry:
- 一个entry的pub_date是2008而headline字段包含 "Lennon"
- 另外一个的pub_date是2017而headline字段不包含 "Lennon"
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
<QuerySet [<Blog: Beatles Blog>]>
满足于其中的一个条件, 即headline字段包含 "Lennon" 或者 pub_date=2008
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
<QuerySet []>
使用这种筛选,既要headline字段包含 "Lennon" 且 pub_date=2008
排除headline字段包含 "Lennon"和pub_date是2008的Blog
Blog.objects.exclude(
entry__headline__contains='Lennon',
entry__pub_date__year=2008,
)
<QuerySet [<Blog: SUN Blog>]>
exclude 不是排除同时满足两个条件的Entry. 择的Blog中不包含在2008年发布且healine 中带有“Lennon” 的Entry
<QuerySet [<Blog: SUN Blog>]>
Blog.objects.exclude(
entry=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
<QuerySet [<Blog: Beatles Blog>, <Blog: SUN Blog>]>
Filter 可以引用模型的字段
Django 提供F 表达式 来允许模型字段间的比较。F() 返回的实例用作查询内部对模型字段的引用。这些引用可以用于查询的filter 中来比较相同模型实例上不同字段之间值的比
from django.db.models import F
Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
<QuerySet [<Entry: this is headline>]>
Django 支持对F() 对象使用加法、减法、乘法、除法、取模以及幂计算等算术操作,两个操作数可以都是常数和其它F()对象。
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 操作以访问关联的对象
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))
() 对象支持.bitand() 和.bitor() 两种位操作,例如:
>>> F('somefield').bitand(16)
转义LIKE 语句中的百分号和下划线
与LIKE SQL 语句等同的字段查询(iexact、 contains、icontains、startswith、 istartswith、endswith和iendswith)将自动转义在LIKE 语句中使用的两个特殊的字符 —— 百分号和下划线。(在LIKE 语句中,百分号通配符表示多个字符,下划线通配符表示单个字符)。
Entry.objects.filter(headline__contains='%')
sql语句:
SELECT ... WHERE headline LIKE '%\%%';
缓存和查询集
每个查询集都包含一个缓存来最小化对数据库的访问。理解它是如何工作的将让你编写最高效的代码。
在一个新创建的查询集中,缓存为空。首次对查询集进行求值 —— 同时发生数据库查询 ——Django 将保存查询的结果到查询集的缓存中并返回明确请求的结果(例如,如果正在迭代查询集,则返回下一个结果)。接下来对该查询集 的求值将重用缓存的结果。
请牢记这个缓存行为,因为对查询集使用不当的话,它会坑你的。例如,下面的语句创建两个查询集,对它们求值,然后扔掉它们:
>>> print([e.headline for e in Entry.objects.all()]) >>> print([e.pub_date for e in Entry.objects.all()])
这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载。同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Entry被添加进来或删除掉。
为了避免这个问题,只需保存查询集并重新使用它:
>>> 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 = Entry.objects.all() >>> print queryset[5] # Queries the database >>> print queryset[5] # Queries the database again
然而,如果已经对全部查询集求值过,则将检查缓存:
>>> queryset = Entry.objects.all() >>> [entry for entry in queryset] # Queries the database >>> print queryset[5] # Uses cache >>> print queryset[5] # Uses cache
下面是一些其它例子,它们会使得全部的查询集被求值并填充到缓存中:
>>> [entry for entry in queryset] >>> bool(queryset) >>> entry in queryset >>> list(queryset)
注
简单地打印查询集不会填充缓存。因为__repr__() 调用只返回全部查询集的一个切片
使用Q 对象进行复杂的查询
组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
传递多个q对象作为位置参数。参数关系是 AND
Poll.objects.get( Q(question__startswith='Who'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) )
翻译的sql语句:
SELECT * from polls WHERE question LIKE 'Who%' AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
当既有q对象和关键字参数时,q对象必须位于关键字参数之前,所有参数关系都是 AND
Entry.objects.get( Q(pub_date__year=2017), headline__contains='is')
<Entry: this is Lennon headline>
比较对象
为了比较两个模型实例,只需要使用标准的Python 比较操作符,即双等于符号:==。在后台,它会比较两个模型主键的值。
利用上面的Entry 示例,下面两个语句是等同的:
>>> some_entry == other_entry >>> some_entry.id == other_entry.id
删除对象
删除方法,为了方便,就取名为delete()。这个方法将立即删除对象且没有返回值。例如:
e.delete()
你还可以批量删除对象。每个查询集 都有一个delete() 方法,它将删除该查询集中的所有成员。
例如,下面的语句删除pub_date 为2005 的所有Entry 对象:
Entry.objects.filter(pub_date__year=2005).delete()
记住,这将尽可能地使用纯SQL 执行,所以这个过程中不需要调用每个对象实例的delete()方法。如果你给模型类提供了一个自定义的delete() 方法并希望确保它被调用,你需要手工删除该模型的实例(例如,迭代查询集并调用每个对象的delete())而不能使用查询集的批量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() 是唯一没有在管理器 上暴露出来的查询集方法。这是一个安全机制来防止你意外地请求Entry.objects.delete(),而删除所有 的条目。如果你确实想删除所有的对象,你必须明确地请求一个完全的查询集:
Entry.objects.all().delete()
拷贝模型实例¶
虽然没有内建的方法用于拷贝模型实例,但还是很容易创建一个新的实例并让它的所有字段都拷贝过来。最简单的方法是,只需要将pk 设置为None。利用我们的Blog 示例:
blog = Blog(name='My blog', tagline='Blogging is easy') blog.save() # blog.pk == 1 blog.pk = None blog.save() # blog.pk == 2
表blog 会多出两条记录
如果你用继承,那么会复杂一些。考虑下面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 有一个到Author 的多对多字段:
entry = Entry.objects.all()[0] # some previous entry old_authors = entry.authors.all() entry.pk = None entry.save() entry.authors = old_authors # saves new many2many relations
一次更新多个对象
为一个查询集中所有对象的某个字段都设置一个特定的值
Entry.objects.filter(pub_date__year=2017).update(headline='everything is the same')
1
若要更新ForeignKey 字段,需设置新的值为你想指向的新的模型实例。例如:
# Change every Entry so that it belongs to this Blog. >>> Entry.objects.all().update(blog=b)
要注意update() 方法会直接转换成一个SQL 语句。它是一个批量的直接更新操作。它不会运行模型的save() 方法,或者发出pre_save 或 post_save信号(调用save()方法产生)或者查看auto_now 字段选项。如果你想保存查询集中的每个条目并确保每个实例的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'))
关联的对象
一对多关系
前向查询
如果一个模型具有ForeignKey,那么该模型的实例将可以通过属性访问关联的(外部)对象
Entry.objects.get(id=3).blog
如果ForeignKey 字段有null=True 设置(即它允许NULL 值),你可以分配None 来删除对应的关联性
e = Entry.objects.get(id=2)
e.blog
<Blog: Beatles Blog>
e.blog = None
e.save()
一对多关联关系的前向访问在第一次访问关联的对象时被缓存。以后对同一个对象的外键的访问都使用缓存。例如:
>>> 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.
注意select_related() 查询集方法递归地预填充所有的一对多关系到缓存中。例如:
>>> e = Entry.objects.select_related().get(id=2) >>> print(e.blog) # Doesn't hit the database; uses cached version. >>> print(e.blog) # Doesn't hit the database; uses cached version.
反向查询
如果模型I有一个ForeignKey,那么该ForeignKey 所指的模型II实例可以通过一个管理器返回前面有ForeignKey的模型I的所有实例。这个管理器的名字为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')
你可以在ForeignKey 定义时设置related_name 参数来覆盖foo_set 的名称。例如,如果Entry 模型改成blog =ForeignKey(Blog, related_name='entries')
>>> b = Blog.objects.get(id=1) >>> b.entries.all() # Returns all Entry objects related to Blog.
使用自定义的反向管理器
默认情况下,用于反向关联关系的RelatedManager 是该模型默认管理器 的子类。如果你想为一个查询指定一个不同的管理器,你可以使用下面的语法:
from django.db import models class Entry(models.Model): #... objects = models.Manager() # Default Manager entries = EntryManager() # Custom Manager b = Blog.objects.get(id=1) b.entry_set(manager='entries').all()
如果EntryManager 在它的get_queryset() 方法中使用默认的过滤,那么该过滤将适用于all() 调用。
当然,指定一个自定义的管理器还可以让你调用自定义的方法:
b.entry_set(manager='entries').is_published()
关联对象的其它方法
add(obj1, obj2, ...)
添加一指定的模型对象到关联的对象集中。
e = Entry.objects.get(id=2)
e.authors.add(a1, a3)
create(**kwargs)
创建一个新的对象,将它保存并放在关联的对象集中。返回新创建的对象。
remove(obj1, obj2, ...)
从关联的对象集中删除指定的模型对象。
clear()
从关联的对象集中删除所有的对象。
若要一次性给关联的对象集赋值,只需要给它赋值一个可迭代的对象。这个可迭代的对象可以包含对象的实例,或者一个主键值的列表。例如:
b = Blog.objects.get(id=1) b.entry_set = [e1, e2]
如果有clear() 方法,那么在将可迭代对象中的成员添加到集合中之前,将从entry_set 中删除所有已经存在的对象。如果没有clear() 方法,那么将直接添加可迭代对象中的成员而不会删除所有已存在的对象。
这一节中提到的每个”反向“操作都会立即对数据库产生作用。每个添加、创建和删除操作都会立即并自动保存到数据库中。
多对多关系
定义 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() # Retur
一对一关系¶
一对一关系与多对一关系非常相似。如果你在模型中定义一个OneToOneField,该模型的实例将可以通过该模型的一个简单属性访问关联的模型。
例如:
class EntryDetail(models.Model): entry = models.OneToOneField(Entry) details = models.TextField() ed = EntryDetail.objects.get(id=2) ed.entry # Returns the related Entry object.
在“反向”查询中有所不同。一对一关系中的关联模型同样具有一个管理器对象,但是该管理器表示一个单一的对象而不是对象的集合:
e = Entry.objects.get(id=2) e.entrydetail # returns the related EntryDetail object
如果没有对象赋值给这个关联关系,Django 将引发一个DoesNotExist 异常。
实例可以赋值给反向的关联关系,方法和正向的关联关系一样:
e.entrydetail = ed
反向的关联关系实现
答案在app registry 中。当Django 启动时,它导入INSTALLED_APPS 中列出的每个应用,然后导入每个应用中的models 模块。每创建一个新的模型时,Django 添加反向的关系到所有关联的模型。如果关联的模型还没有导入,Django 将保存关联关系的记录并在最终关联的模型导入时添加这些关联关系。