查询操做是Django的ORM框架中最重要的内容之一。咱们创建模型、保存数据为的就是在须要的时候能够查询获得数据。Django自动为全部的模型提供了一套完善、方便、高效的API,一些重要的,咱们要背下来,一些不经常使用的,要有印象,使用的时候能够快速查找参考手册。python
本节的内容基于以下的一个博客应用模型:web
from django.db import models class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def __str__(self): # __unicode__ on Python 2 return self.name class Author(models.Model): name = models.CharField(max_length=200) email = models.EmailField() def __str__(self): # __unicode__ on Python 2 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) n_comments = models.IntegerField() n_pingbacks = models.IntegerField() rating = models.IntegerField() def __str__(self): # __unicode__ on Python 2 return self.headline
假设模型位于mysite/blog/models.py
文件中,那么建立对象的方式以下:sql
>>> from blog.models import Blog >>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') >>> b.save()
在后台,这会运行一条SQL的INSERT语句。若是你不显式地调用save()方法,Django不会马上将该操做反映到数据库中。save()方法没有返回值,它能够接受一些额外的参数。数据库
若是想要一行代码完成上面的操做,请使用creat()
方法,它能够省略save的步骤:django
b = Blog.objects.create(name='Beatles Blog', tagline='All the latest Beatles news.')
使用save()方法,保存对数据库内已有对象的修改。例如若是已经存在b5对象在数据库内:编程
>>> b5.name = 'New name' >>> b5.save()
在后台,这会运行一条SQL的UPDATE语句。若是你不显式地调用save()方法,Django不会马上将该操做反映到数据库中。缓存
保存一个外键字段和保存普通字段没什么区别,只是要注意值的类型要正确。下面的例子,有一个Entry的实例entry和一个Blog的实例cheese_blog
,而后把cheese_blog
做为值赋给了entry的blog属性,最后调用save方法进行保存。安全
>>> 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()
多对多字段的保存稍微有点区别,须要调用一个add()
方法,而不是直接给属性赋值,但它不须要调用save方法。以下例所示:app
>>> from blog.models import Author >>> joe = Author.objects.create(name="Joe") >>> entry.authors.add(joe)
在一行语句内,能够同时添加多个对象到多对多的字段,以下所示:框架
>>> 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表明一些数据库对象的集合。它能够包含零个、一个或多个过滤器(filters)。Filters缩小查询结果的范围。在SQL语法中,一个QuerySet至关于一个SELECT语句,而filter则至关于WHERE或者LIMIT一类的子句。
经过模型的Manager得到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."
使用all()
方法,能够获取某张表的全部记录。
>>> all_entries = Entry.objects.all()
有两个方法能够用来过滤QuerySet的结果,分别是:
filter(**kwargs)
:返回一个根据指定参数查询出来的QuerySetexclude(**kwargs)
:返回除了根据指定参数查询出来结果的QuerySet其中,**kwargs
参数的格式必须是Django设置的一些字段格式。
例如:
Entry.objects.filter(pub_date__year=2006)
它等同于:
Entry.objects.all().filter(pub_date__year=2006)
链式过滤
filter和exclude的结果依然是个QuerySet,所以它能够继续被filter和exclude,这就造成了链式过滤:
>>> Entry.objects.filter( ... headline__startswith='What' ... ).exclude( ... pub_date__gte=datetime.date.today() ... ).filter( ... pub_date__gte=datetime(2005, 1, 30) ... )
(这里须要注意的是,当在进行跨关系的链式过滤时,结果可能和你想象的不同,参考下面的跨多值关系查询)
被过滤的QuerySets都是惟一的
每一次过滤,你都会得到一个全新的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())
例子中的q2和q3虽然由q1得来,是q1的子集,可是都是独立自主存在的。一样q1也不会受到q2和q3的影响。
QuerySets都是懒惰的
一个建立QuerySets的动做不会马上致使任何的数据库行为。你能够不断地进行filter动做一成天,Django不会运行任何实际的数据库查询动做,直到QuerySets被提交(evaluated)。
简而言之就是,只有碰到某些特定的操做,Django才会将全部的操做体现到数据库内,不然它们只是保存在内存和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)
上面的例子,看起来执行了3次数据库访问,实际上只是在print语句时才执行1次访问。一般状况,QuerySets的检索不会马上执行实际的数据库查询操做,直到出现相似print的请求,也就是所谓的evaluated。
filter方法始终返回的是QuerySets,那怕只有一个对象符合过滤条件,返回的也是包含一个对象的QuerySets,这是一个集合类型对象,你能够简单的理解为Python列表,可迭代可循环可索引。
若是你肯定你的检索只会得到一个对象,那么你可使用Manager的get()方法来直接返回这个对象。
>>> one_entry = Entry.objects.get(pk=1)
在get方法中你可使用任何filter方法中的查询参数,用法也是如出一辙。
注意:使用get()方法和使用filter()方法而后经过[0]的方式分片,有着不一样的地方。看似二者都是获取单一对象。可是,若是在查询时没有匹配到对象,那么get()方法将抛出DoesNotExist异常。这个异常是模型类的一个属性,在上面的例子中,若是不存在主键为1的Entry对象,那么Django将抛出Entry.DoesNotExist
异常。
相似地,在使用get()方法查询时,若是结果超过1个,则会抛出MultipleObjectsReturned异常,这个异常也是模型类的一个属性。
因此:get()方法要慎用!
大多数状况下,须要从数据库中查找对象时,使用all()、 get()、filter() 和exclude()就行。针对QuerySet的方法还有不少,都是一些相对高级的用法。
使用相似Python对列表进行切片的方法能够对QuerySet进行范围取值。它至关于SQL语句中的LIMIT和OFFSET子句。参考下面的例子:
>>> Entry.objects.all()[:5] # 返回前5个对象 >>> Entry.objects.all()[5:10] # 返回第6个到第10个对象
注意:不支持负索引!例如 Entry.objects.all()[-1]是不容许的
一般状况,切片操做会返回一个新的QuerySet,而且不会被马上执行。可是有一个例外,那就是指定步长的时候,查询操做会马上在数据库内执行,以下:
>>> Entry.objects.all()[:10:2]
若要获取单一的对象而不是一个列表(例如,SELECT foo FROM bar LIMIT 1),能够简单地使用索引而不是切片。例如,下面的语句返回数据库中根据标题排序后的第一条Entry:
>>> Entry.objects.order_by('headline')[0]
它至关于:
>>> Entry.objects.order_by('headline')[0:1].get()
注意:若是没有匹配到对象,那么第一种方法会抛出IndexError异常,而第二种方式会抛出DoesNotExist异常。
也就是说在使用get和切片的时候,要注意查询结果的元素个数。
字段查询其实就是filter()、exclude()和get()等方法的关键字参数。
其基本格式是:field__lookuptype=value
,注意其中是双下划线。
例如:
>>> Entry.objects.filter(pub_date__lte='2006-01-01') # 至关于: SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
其中的字段必须是模型中定义的字段之一。可是有一个例外,那就是ForeignKey字段,你能够为其添加一个“_id”后缀(单下划线)。这种状况下键值是外键模型的主键原生值。例如:
>>> Entry.objects.filter(blog_id=4)
若是你传递了一个非法的键值,查询函数会抛出TypeError异常。
Django的数据库API支持20多种查询类型,下面介绍一些经常使用的:
exact:
默认类型。若是你不提供查询类型,或者关键字参数不包含一个双下划线,那么查询类型就是这个默认的exact。
>>> Entry.objects.get(headline__exact="Cat bites dog") # 至关于 # SELECT ... WHERE headline = 'Cat bites dog'; # 下面两个至关 >>> Blog.objects.get(id__exact=14) # Explicit form >>> Blog.objects.get(id=14) # __exact is implied
iexact:
不区分大小写。
>>> Blog.objects.get(name__iexact="beatles blog") # 匹配"Beatles Blog", "beatles blog",甚至"BeAtlES blOG".
contains:
表示包含的意思!大小写敏感!
Entry.objects.get(headline__contains='Lennon') # 至关于 # SELECT ... WHERE headline LIKE '%Lennon%'; # 匹配'Today Lennon honored',但不匹配'today lennon honored'
icontains:
contains的大小写不敏感模式。
startswith和endswith
以什么开头和以什么结尾。大小写敏感!
istartswith和iendswith
是不区分大小写的模式。
Django提供了强大而且直观的方式解决跨越关联的查询,它在后台自动执行包含JOIN的SQL语句。要跨越某个关联,只需使用关联的模型字段名称,并使用双下划线分隔,直至你想要的字段(能够链式跨越,无限跨度)。例如:
# 返回全部Blog的name为'Beatles Blog'的Entry对象 # 必定要注意,返回的是Entry对象,而不是Blog对象。 # objects前面用的是哪一个class,返回的就是哪一个class的对象。 >>> 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')
若是Entry中没有关联任何的author,那么它将看成其没有name,而不会由于没有author 引起一个错误。一般,这是比较符合逻辑的处理方式。惟一可能让你困惑的是当你使用isnull
的时候:
Blog.objects.filter(entry__authors__name__isnull=True)
这将返回Blog对象,它关联的entry对象的author字段的name字段为空,以及Entry对象的author字段为空。若是你不须要后者,你能够这样写:
Blog.objects.filter(entry__authors__isnull=False,entry__authors__name__isnull=True)
跨越多值的关系查询
最基本的filter和exclude的关键字参数只有一个,这种状况很好理解。可是当关键字参数有多个,且是跨越外键或者多对多的状况下,那么就比较复杂,让人迷惑了。咱们看下面的例子:
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
这是一个跨外键、两个过滤参数的查询。此时咱们理解两个参数之间属于-与“and”的关系,也就是说,过滤出来的BLog对象对应的entry对象必须同时知足上面两个条件。这点很好理解。也就是说上面要求至少有一个entry同时知足两个条件。
可是,看下面的用法:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
把两个参数拆开,放在两个filter调用里面,按照咱们前面说过的链式过滤,这个结果应该和上面的例子同样。可实际上,它不同,Django在这种状况下,将两个filter之间的关系设计为-或“or”,这真是让人头疼。
多对多关系下的多值查询和外键foreignkey的状况同样。
可是,更头疼的来了,exclude的策略设计的又和filter不同!
Blog.objects.exclude(entry__headline__contains='Lennon',entry__pub_date__year=2008,)
这会排除headline中包含“Lennon”的Entry和在2008年发布的Entry,中间是一个-和“or”的关系!
那么要排除同时知足上面两个条件的对象,该怎么办呢?看下面:
Blog.objects.exclude( entry=Entry.objects.filter( headline__contains='Lennon', pub_date__year=2008, ), )
(有没有很坑爹的感受?因此,建议在碰到跨关系的多值查询时,尽可能使用Q查询)
到目前为止的例子中,咱们都是将模型字段与常量进行比较。可是,若是你想将模型的一个字段与同一个模型的另一个字段进行比较该怎么办?
使用Django提供的F表达式!
例如,为了查找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()中使用双下划线来进行跨表查询。例如,查询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()
、.bitrightshift()
和.bitleftshift()
4种位操做,例如:
>>> F('somefield').bitand(16)
pk就是primary key
的缩写。一般状况下,一个模型的主键为“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
能够联合其余类型的参数:
# 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)
能够跨表操做:
>>> Entry.objects.filter(blog__id__exact=3) >>> Entry.objects.filter(blog__id=3) >>> Entry.objects.filter(blog__pk=3)
(当主键不是id的时候,请注意了!)
在原生SQL语句中%
符号有特殊的做用。Django帮你自动转义了百分符号和下划线,你能够和普通字符同样使用它们,以下所示:
>>> Entry.objects.filter(headline__contains='%') # 它和下面的同样 # SELECT ... WHERE headline LIKE '%\%%';
每一个QuerySet都包含一个缓存,用于减小对数据库的实际操做。理解这个概念,有助于你提升查询效率。
对于新建立的QuerySet,它的缓存是空的。当QuerySet第一次被提交后,数据库执行实际的查询操做,Django会把查询的结果保存在QuerySet的缓存内,随后的对于该QuerySet的提交将重用这个缓存的数据。
要想高效的利用查询结果,下降数据库负载,你必须善于利用缓存。看下面的例子,这会形成2次实际的数据库操做,加倍数据库的负载,同时因为时间差的问题,可能在两次操做之间数据被删除或修改或添加,致使脏数据的问题:
>>> 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]) # 再次查询数据库
可是,若是已经遍历过整个QuerySet,那么就至关于缓存过,后续的操做则会使用缓存,例如:
>>> queryset = Entry.objects.all() >>> [entry for entry in queryset] # 查询数据库 >>> print(queryset[5]) # 使用缓存 >>> print(queryset[5]) # 使用缓存
下面的这些操做都将遍历QuerySet并创建缓存:
>>> [entry for entry in queryset] >>> bool(queryset) >>> entry in queryset >>> list(queryset)
注意:简单的打印QuerySet并不会创建缓存,由于__repr__()
调用只返回所有查询集的一个切片。
普通filter函数里的条件都是“and”逻辑,若是你想实现“or”逻辑怎么办?用Q查询!
Q来自django.db.models.Q
,用于封装关键字参数的集合,能够做为关键字参数用于filter、exclude和get等函数。
例如:
from django.db.models import Q Q(question__startswith='What')
可使用“&”或者“|”或“~”来组合Q对象,分别表示与或非逻辑。它将返回一个新的Q对象。
Q(question__startswith='Who')|Q(question__startswith='What') # 这至关于: WHERE question LIKE 'Who%' OR question LIKE 'What%'
更多的例子:
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
你也能够这么使用,默认状况下,以逗号分隔的都表示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对象组合使用时,Q对象必须放在前面,以下例子:
Poll.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),question__startswith='Who',)
若是关键字参数放在Q对象的前面,则会报错。
要比较两个模型实例,只须要使用python提供的双等号比较符就能够了。在后台,其实比较的是两个实例的主键的值。下面两种方法是等同的:
>>> 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})
也能够批量删除。每一个QuerySet都有一个delete()方法,它能删除该QuerySet的全部成员。例如:
>>> Entry.objects.filter(pub_date__year=2005).delete() (5, {'webapp.Entry': 5})
须要注意的是,有可能不是每个对象的delete方法都被执行。若是你改写了delete方法,为了确保对象被删除,你必须手动迭代QuerySet进行逐一删除操做。
当Django删除一个对象时,它默认使用SQL的ON DELETE CASCADE约束,也就是说,任何有外键指向要删除对象的对象将一块儿被删除。例如:
b = Blog.objects.get(pk=1) # 下面的动做将删除该条Blog和全部的它关联的Entry对象 b.delete()
这种级联的行为能够经过的ForeignKey的on_delete
参数自定义。
注意,delete()
是惟一没有在管理器上暴露出来的方法。这是刻意设计的一个安全机制,用来防止你意外地请求相似Entry.objects.delete()
的动做,而不慎删除了全部的条目。若是你确实想删除全部的对象,你必须明确地请求一个彻底的查询集,像下面这样:
Entry.objects.all().delete()
虽然没有内置的方法用于复制模型的实例,但仍是很容易建立一个新的实例并将原实例的全部字段都拷贝过来。最简单的方法是将原实例的pk设置为None,这会建立一个新的实例copy。示例以下:
blog = Blog(name='My blog', tagline='Blogging is easy') blog.save() # blog.pk == 1 # blog.pk = None 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.objects.all()[0] # some previous entry old_authors = entry.authors.all() entry.pk = None entry.save() entry.authors.set(old_authors)
对于OneToOneField,还要复制相关对象并将其分配给新对象的字段,以免违反一对一惟一约束。 例如,假设entry已经如上所述重复:
detail = EntryDetail.objects.all()[0] detail.pk = None detail.entry = entry detail.save()
使用update()
方法能够批量为QuerySet中全部的对象进行更新操做。
# 更新全部2007年发布的entry的headline Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
只能够对普通字段和ForeignKey字段使用这个方法。若要更新一个普通字段,只需提供一个新的常数值。若要更新ForeignKey字段,需设置新值为你想指向的新模型实例。例如:
>>> b = Blog.objects.get(pk=1) # 修改全部的Entry,让他们都属于b >>> Entry.objects.all().update(blog=b)
update方法会被马上执行,并返回操做匹配到的行的数目(有可能不等于要更新的行的数量,由于有些行可能已经有这个新值了)。惟一的约束是:只能访问一张数据库表。你能够根据关系字段进行过滤,但你只能更新模型主表的字段。例如:
>>> 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()对象进行跨表操做,你只能够引用正在更新的模型的字段。若是你尝试使用F()对象引入另一张表的字段,将抛出FieldError异常:
# THIS WILL RAISE A FieldError >>> Entry.objects.update(headline=F('blog__name'))
利用本节一开始的模型,一个Entry对象e能够经过blog属性e.blog
获取关联的Blog对象。反过来,Blog对象b能够经过entry_set
属性b.entry_set.all()
访问与它关联的全部Entry对象。
正向查询:
直接经过圆点加属性,访问外键对象:
>>> e = Entry.objects.get(id=2) >>> e.blog # 返回关联的Blog对象
要注意的是,对外键的修改,必须调用save方法进行保存,例如:
>>> e = Entry.objects.get(id=2) >>> e.blog = some_blog >>> e.save()
若是一个外键字段设置有null=True
属性,那么能够经过给该字段赋值为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) # 访问数据库,获取实际数据 >>> print(e.blog) # 不会访问数据库,直接使用缓存的版本
请注意QuerySet的select_related()
方法会递归地预填充全部的一对多关系到缓存中。例如:
>>> e = Entry.objects.select_related().get(id=2) >>> print(e.blog) # 不会访问数据库,直接使用缓存 >>> print(e.blog) # 不会访问数据库,直接使用缓存
反向查询:
若是一个模型有ForeignKey,那么该ForeignKey所指向的外键模型的实例能够经过一个管理器进行反向查询,返回源模型的全部实例。默认状况下,这个管理器的名字为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()
使用自定义的反向管理器:
默认状况下,用于反向关联的RelatedManager是该模型默认管理器的子类。若是你想为一个查询指定一个不一样的管理器,你可使用下面的语法:
from django.db import models class Entry(models.Model): #... objects = models.Manager() # 默认管理器 entries = EntryManager() # 自定义管理器 b = Blog.objects.get(id=1) b.entry_set(manager='entries').all()
固然,指定的自定义反向管理器也能够调用它的自定义方法:
b.entry_set(manager='entries').is_published()
处理关联对象的其它方法:
除了在前面定义的QuerySet方法以外,ForeignKey管理器还有其它方法用于处理关联的对象集合。下面是每一个方法的归纳。
add(obj1, obj2, ...):添加指定的模型对象到关联的对象集中。
create(**kwargs):建立一个新的对象,将它保存并放在关联的对象集中。返回新建立的对象。
remove(obj1, obj2, ...):从关联的对象集中删除指定的模型对象。
clear():清空关联的对象集。
set(objs):重置关联的对象集。
若要一次性给关联的对象集赋值,使用set()方法,并给它赋值一个可迭代的对象集合或者一个主键值的列表。例如:
b = Blog.objects.get(id=1) b.entry_set.set([e1, e2])
在这个例子中,e1和e2能够是完整的Entry实例,也能够是整数的主键值。
若是clear()方法可用,那么在将可迭代对象中的成员添加到集合中以前,将从entry_set
中删除全部已经存在的对象。若是clear()方法不可用,那么将直接添加可迭代对象中的成员而不会删除全部已存在的对象。
这节中的每一个反向操做都将当即在数据库内执行。全部的增长、建立和删除操做也将马上自动地保存到数据库内。
多对多关系的两端都会自动得到访问另外一端的API。这些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.
与外键字段中同样,在多对多的字段中也能够指定related_name
名。
(注:在一个模型中,若是存在多个外键或多对多的关系指向同一个外部模型,必须给他们分别加上不一样的related_name
,用于反向查询)
一对一很是相似多对一关系,能够简单的经过模型的属性访问关联的模型。
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.
不一样之处在于反向查询的时候。一对一关系中的关联模型一样具备一个管理器对象,可是该管理器表示一个单一的对象而不是对象的集合:
e = Entry.objects.get(id=2) e.entrydetail # 返回关联的EntryDetail对象
若是没有对象赋值给这个关系,Django将抛出一个DoesNotExist异常。
能够给反向关联进行赋值,方法和正向的关联同样:
e.entrydetail = ed
一些ORM框架须要你在关系的两端都进行定义。Django的开发者认为这违反了DRY (Don’t Repeat Yourself)原则,因此在Django中你只须要在一端进行定义。
那么这是怎么实现的呢?由于在关联的模型类没有被加载以前,一个模型类根本不知道有哪些类和它关联。
答案在app registry
!在Django启动的时候,它会导入全部INSTALLED_APPS
中的应用和每一个应用中的模型模块。每建立一个新的模型时,Django会自动添加反向的关系到全部关联的模型。若是关联的模型尚未导入,Django将保存关联的记录并在关联的模型导入时添加这些关系。
因为这个缘由,将模型所在的应用都定义在INSTALLED_APPS
的应用列表中就显得特别重要。不然,反向关联将不能正确工做。
涉及关联对象的查询与正常值的字段查询遵循一样的规则。当你指定查询须要匹配的值时,你可使用一个对象实例或者对象的主键值。
例如,若是你有一个id=5的Blog对象b,下面的三个查询将是彻底同样的:
Entry.objects.filter(blog=b) # 使用对象实例 Entry.objects.filter(blog=b.id) # 使用实例的id Entry.objects.filter(blog=5) # 直接使用id
若是你发现须要编写的Django查询语句太复杂,你能够回归到手工编写SQL语句。Django对于编写原生的SQL查询有许多选项。
最后,须要注意的是Django的数据库层只是一个数据库接口。你能够利用其它的工具、编程语言或数据库框架来访问数据库,Django没有强制指定你非要使用它的某个功能或模块。