django模型查询操做

一旦建立好了数据模型,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):
        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(to=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):
        return self.headline

一、建立对象

Django使用更直观的系统:模型类表示数据库表,该类的实例表示数据库表中的特定记录也就是数据值正则表达式

要建立对象,请使用模型类的关键字参数对其进行实例化,而后调用save()以将其保存到数据库中,假设模型存在于mysite/app01/models.py文件中数据库

from app01.models import Blog
b = Blog(name='beatles blog',tagline='all the latest beatles news.')
b.save()

它会在后台执行SQL语句INSERT,若是不调用save()方法,Django不会马上将该操做反应到数据库中,save()方法没有返回值,它能够接受一些额外的参数django

#修改对象的值,在后台会执行一条SQL语句的UPDATE
b.name = 'python is django'
b.save()
Blog.objects.all()
<QuerySet [<Blog: python is django>]>

若是想要一行代码完成上面的操做,请使用create()方法,它能够省略save的步骤:编程

Blog.objects.create(name='Blog title',tagline='this is one blog title name')
Blog.objects.filter(name='Blog title')  #查询数据 <QuerySet [<Blog: name:Blog title;tagline:this is one blog title name>]>

二、保存ForeignKey和ManyToManyField字段

保存一个ForeignKey字段和保存普通字段没什么区别,只须要注意值的类型要正确,下面的例子,有一个Entry的实例和一个Blog的实例,而后把cheese_blog做为值赋给了entry的blog属性,最后调用save方法进行保存。缓存

from app01.models import Blog,Entry
entry = Entry.objects.get(pk=1)
cheese_blog = Blog.objects.get(name='Blog title')
entry.blog = cheese_blog
entry.save()

ManyToManyField字段的保存稍微有点区别,须要调用add()方法,而不是直接给属性赋值,但它不须要调用save方法,以下示例:安全

from app01.models import Author
joe = Author.objects.create(name='Joe')
entry.authors.add(joe)

在对对多关系中能够一次性添加多条记录,只需在调用add()时指定多个值便可:app

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)

#如指定了错误的类型对象,将引起异常

三、检索对象

想要从数据库中检索对象,须要基于模型类,经过管理器(Manager)构造一个查询结果集(QuerySet),每一个QuerySet表明一些数据库对象的集合,它能够包含零个、一个或多个过滤器(filters),Filters缩小查询结果的范围,在SQL语法中,一个QuerySet至关于一个SELECT语句,而filter至关于WHERE或者LIMIT一类的子句框架

经过模型的Manager得到QuerySet,每一个模型至少具备一个Manager,默认状况下,它被称做为objects,能够经过模型类直接调用它,但不能经过模型类的实例调用它,以此实现‘表级别’操做和‘记录级别’操做的强制分离,以下所示:

Blog.objects
<django.db.models.manager.Manager object at 0x000001E3583EFDD8>
b = Blog(name='FOO',tagline='Bar')
b.objects
Traceback (most recent call last):
......
AttributeError: Manager isn't accessible via Blog instances

 Managers只能经过模型类访问,不能经过模型实例访问,它强制表级操做和记录操做之间的分离

#检索全部对象,可以使用all()方法,可获取某张表的全部记录
alll_entries = Entry.objects.all()

#过滤对象,有两种方法用来过滤QuerySet的结果:
filter(**kwargs) :返回一个根据指定参数查询出来的QuerySet
exclude(**kwargs) :返回给定查找参数不匹配的QuerySet
#例如:获取2006年的博客条目,使用filter()
Entery.objects.filter(pub_date__year=2006)
#使用默认的manager类,它等同于上
Entery.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.date(2005.1.30))

#它将获取数据库中全部条目中带'what'的首字母的QuerySet,而后排除,添加过滤器而后过滤是2005年1月30日的条目

被过滤的QuerySets都是惟一的,每次过滤,都会得到一个全新的QuerySet,它和以前的QuerySet没有任何关系,能够彻底独立的被保存,使用和重用

QuerySets是懒惰的,一个建立QuerySet的动做不会马上致使任何数据库的行为,你能够不断的进行filter动做,Django不会运行任何实际的数据库查询操做,直到QuerySet被提交(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语句时才执行数据库操做,一般QuerySet的检索不会马上执行实际的数据库操做,知道出现相似print的请求,也就是evaluated

检索单个对象:filter方法始终返回的是QuerySet,哪怕只有一个对象符合顾虑条件,返回的也是包含一个对象的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方法要慎用!

其余QuerySet方法:大多数状况下,须要从数据库中查找对象时,使用all()、get()、filter()和exclude()就行,针对QuerySet的方法还有不少高级用法能够参考官网

QuerySet使用限制:使用相似于python对列表进行切片的方法能够对QuerySet进行范围取值,它至关于SQL语句中的LIMIT和OFFSET子句

Entry.objects.all()[:5]    #返回前5个对象
Entry.objects.all()[5:10]  #返回第6个到第10个对象

注意:QuerySet不支持负索引,如:Entry.objects.all()[-1]是不容许的

一般状况下,切片操做会返回一个新的QuerySet,而且不会被马上执行,可是有一个例外,就是在指定step步长的时候,查询操做会马上在数据库内执行,以下:

Entry.objects.all()[:10:2]

若要获取单一的对象而不是一个列表(SELECT foo FROM bar LIMIT 1),能够简单地使用索引而不是切片,例如,下面的语句返回数据库中根据标题排序后的第一条:

Entry.objects.order_by('headline')[0]
#至关于:
Entry.objects.order_by('headline')[0:1].get()

#注意:若是没有匹配到对象,第一种方法会抛出IndexError异常,而第二种
#方法会抛出DoesNotExist异常,因此在使用get和切片时,须要注意查询结果的元素个数

字段查询:字段查询是指SQL WHERE子句内容的方式,也就是filter()、exclude()和get()等方法的关键字参数,其基本格式是:field__lookuptype=value(注意是双下划线)

#pub_date为字段名连接双下划线再指定查找类型的关键字参数
Entry.objects.filter(pub_date__lte='2006-01-01')
#至关于SQL语句:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

其中的字段必须是模型种定义的字段之一,可是有一个例外,那就是ForeignKey字段,你能够为其添加一个“_id“后缀(单下划线),这种状况下键值是外键模型的主键原始值,如:

#blog为外键模型
Entry.objects.filter(blog_id=4)
#若是你传递了一个非法的键值,查询函数会抛出TypeError异常

Django的数据库API支持20多种查询类型,下面介绍一些经常使用的:

#exact为模型类型,若是不提供查询类型,那查询类型就是这个默认的exact,精确匹配
Entry.objects.get(headline__exact="Cat bites dog")
#SQL语句:
SELECT ... WHERE headline='Cat bites dog';
#下面两个至关:
Blog.objects.get(id__exact=4)
Blog.objects.get(id=4)

#iexact:不区分大小写的彻底匹配
Blog.objects.get(name__iexact='beatles blog')
SELECT ... WHERE name ILIKE 'beatles blog'

#contains:区分大小写
Entry.objects.get(headline__contains='Lennon')
SELECT ... WHERE headline LIKE '%Lennon%';

#icontains:不区分大小写
Entry.objects.get(headline__icontains='Lennon')
SELECT ... WHERE headline ILIKE '%Lennon%';

#in:包含在列表、元组或集合的,也可接受字符串(可迭代)
Entry.objects.filter(id__in=[1,3,4])

#gt:大于
Entry.objects.filter(id__gt=3)

#gte:大于或等于
Entry.objects.filter(id__gte=3)

#lt:小于
Entry.objects.filter(id__lt=3)

#lte:小于或等于
Entry.objects.filter(id__lte=3)

#startswith:区分大小写的开头
Entry.objects.filter(headline__startswith='Lennon')

#istartswith:不区分大小写的开头
Entry.objects.filter(headline__istartswith='Lennon')

#endswith:区分大小写的结尾;iendswith:不区分大小写的结尾
Entry.objects.filter(headline__endswith='Lennon')
Entry.objects.filter(headline__iendswith='Lennon')

#range:范围在内
import datetime
start_date = datetime.date(2005,1,1)
end_date = datetime.date(2005,12,31)
Entry.objects.filter(pub_date__range=(start_date,end_date))

#date:对于datetime字段,将值转换为日期,采用日期值查找
Entry.objects.filter(pub_date__date=datetime.date(2005,1,1))
Entry.objects.filter(pub_date__date__gt=datetime.date(2005,1,1))

#year:对于日期和时间字段,确切的年份匹配,须要整数年
Entry.objects.filter(pub_date__year=2005)
Entry.objects.filter(pub_date__year__gte=2005)

#iso_year:精确的ISO 8601周编年份匹配,须要整数年
Entry.objects.filter(pub_date__iso_year=2005)
Entry.objects.filter(pub_date__iso_year__gte=2005)

#month:日期和日期时间字段,确切的月份匹配
Entry.objects.filter(pub_date__month=1)
Entry.objects.filter(pub_date__month__gte=5)

#day:确切的天匹配
Entry.objects.filter(pub_date__day=3)
Entry.objects.filter(pub_date__day__gte=3)

#week:日期和时间字段,根据ISO-8601返回周数
Entry.objects.filter(pub_date__week=52)
Entry.objects.filter(pub_date__week__gte=32,pub_date__week__lte=38)

#week_day:匹配星期几,从1(星期日)到7(星期六)的整数值
Entry.objects.filter(pub_date__week_day=2)
Entry.objects.filter(pub_date__week_day__gte=2)

#quarter:匹配四季,取1到4之间的整数值
Entry.objects.filter(pub_date__quarter=2)

#time:对于datetime字段,将值转换为时间,取一个datetime.time值
Entry.objects.filter(pub_date__time=datetime.time(14,30))

#hour:对日期时间和时间字段,精确的小时匹配,取值0-23
Entry.objects.filter(timestamp__hour=11)

#minute:对于日期时间和时间字段,精确分钟匹配
Entry.objects.filter(timestamp__minute=29)

#second:对于日期时间和时间字段,确切的妙匹配
Entry.objects.filter(timestamp__second=31)

#isnull:采用任一True或False,其对应于SQL查询IS NULLIS NOT NULL
Entry.objects.filter(pub_date__isnull=True)
SELECT ... WHERE pub_date IS NULL

#regex:区分大小写的正则表达式匹配,语法为python re模块语法
Entry.objects.filter(headline__regex=r'^(an?|The)+')

#iregex:不区分大小写的正则表达式匹配
Entry.objects.filter(headline__iregex=r'^(an?|the)+')

跨越关系的查询:Django提供了强大而且直观的方式解决跨越关联的查询,它在后台自动执行包含JOIN的SQL语句,要跨越某个关联,只需使用关联的模型字段名称,并使用双下划线分隔,直至你想要的字段(能够链式跨越,无限跨越)。

#先指定字段名而后双下划线分隔指定外键字段名
Entry.objects.filter(blog__name='Blog title')

若是要引用一个反向关联只须要使用模型的小写名便可:

#获取全部的blog对象,但前提是所关联的Entry模型的headline字段包含'django models'
Blog.objects.filter(entry__headline__icontains='django models')

若是多级关联种进行过滤并且其中某个模型没有知足过滤条件的值,Django将把它看成一个空(null)可是合法的对象,不会抛出任何异常或错误:

#查询Blog的全部对象,但关联entry模型种authors字段关联名必须时py.qi
Blog.objects.filter(entry__authors__name='py.qi')

若是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='django models',entry__pub_date__year=2019)

这是一个跨外键、两个过滤参数的查询,此时咱们理解两个参数之间属于‘and’的关系,也就是说,过滤出来的Blog对象对用的entry对象必须知足上面两个条件,可是,咱们看下面的用法:

Blog.objects.filter(entry__headline__contains='django models').filter(entry__pub_date__year=2019)

把两个参数拆分,放在两个filer调用里面,安装咱们前面说过的链式过滤,这里的结果和上面的例子应该同样,可实际上,它不同,Djang在这种状况下,将两个filter之间的关系设计为'or',多对对关系下的多值查询和外键foreignkey的状况同样。

可是,exclude的策略设计又和filter不同:

Blog.objects.exclude(entry__headline__contains='django models',entry__pub_date__year=2019)

它会排除headline中包含‘django models'的entry或在2019年发布的Entry,中间是一个'or'的关系

那要排除同时知足上面两个条件的对象,应该这么作:

Blog.objects.exclude(entry__in=Entry.objects.filter(headline__contains='django models',pub_date__year=2019,),)

使用F表达式引用模型的字段:

到目前为止的例子中,咱们都是将模型字段于常量进行比较,可是,若是你想将模型的一个字段与同一个模型的另一个字段进行比较该怎么办呢?

使用Django提供的F表达式;例如:为了查找commnets数目多于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比pinggback和comments数目综合还要小的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和datetime字段,还能够加或减去一个timedelta对象,下面的例子将返回发布时间超过3天后别修改的全部Entry:

Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

F()对象还支持:.bitand()、.bitor()、.bitrightshift()和.bitleftshift()4种位操做:

F('somefield').bitand(16)

主键的快捷查询方式:pk

pk就是primary key的缩写,一般状况下,一个模型的主键位'id',因此下面三个语句的效果同样:

Blog.objects.get(id__exact=1)
Blog.objects.get(id=1)
Blog.objects.get(pk=1)

能够联合其余类型的参数:

Blog.objects.filter(pk__in=[1,3,4])
Blog.objects.filter(pk__gt=10)

能够跨表操做:

Entry.objects.filter(blog__id__exact=3)
Entry.objects.filter(blog__id=3)
Entry.objects.filter(blog__pk=3)

在LIKE语句中转义百分符号和下划线:在原生SQL语句中%符号有特殊的做用,Django帮你自动转义了百分符号和下划线,你能够和普通字符同样使用它们,以下:

Entry.objects.filter(headline__contains='%')
#SQL语句
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[2]) #再次查询数据库

可是,若是已经遍历过整个QuerySet,那么就至关于缓存过,后续的操做则会使用缓存,如:

queryset = Entry.objects.all()
[entry for entry in queryset]   #查询数据库
print(queryset[3])   #使用缓存
print(queryset[1])   #使用缓存

下面的操做都将遍历QuerySet并创建缓存:

[entry for entry in queryset]
bool(queryset)
entry in queryset
list(queryset)

注意:简单的打印QuerySet并不会创建缓存,由于__repr__()调用只返回所有查询集的一个切片

五、使用Q对象进行复杂查询

普通的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关系:

from app01.models import Entry
from django.db.models import Q
from datetime import date
Entry.objects.get(Q(headline__startswith='a'),Q(pub_date=date(2005,1,1)) | Q(pub_date=date(2019,1,1)))

#它至关于SQL的
SELECT * FROM Entry WHERE headline LIKE 'a%' AND (pub_date =  '2005,1,1' OR pub_date = '2019,1,1)

当关键字参数和Q对象组合使用时,Q对象必须放在前面,不然会报错

Entry.objects.filter(Q(pub_date=date(2005,4,2)) | Q(pub_date=date(2019,4,2)),headline__startswith='a')

#若是将headline__startswith='a'放在前面将会报错

六、比较对象

要比较两个模型实例,只须要使用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')

只能够对普通字段和ForignKey字段使用这个方法,若要更新一个普通字段,只需提供一个新的常数值,若要更新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对象

1)一对多外键

正向查询:

直接经过圆点加属性,访问外键对象:

e = Entry.objects.get(id=2)
e.blog

要注意的是,对外键的修改,必须调用save方法进行保存

e = Entry.objects.get(id=2)
other_blog = Blog.objects.get(name='django')
e.blog = other_blog
e.save()

若是一个外键字段设置有Null=True属性,那么能够经过给该字段赋值为None的方法移除外键值:

e = Entry.objects.get(id=2)
e.blog = None
e.save()

在第一次对一个外键关系进行正向访问的时候,关系对象会被缓存,随后对一样外键关系对象的访问会使用这个缓存,如:

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=7)  
b.entry_set.all()  #Returns all Entry objects related to Blog
b.entry_set.filter(headline__contains='Every')  #b.entries is a Manager that returns QuerySets
b.entry_set.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()方法不可用,那么将直接添加可迭代对象中的成员而不会删除全部已存在的对象

以上的每一个反向操做都将马上在数据库内执行,全部的增长、建立和删除操做也将马上自动地保存到数据库内。

2)多对多

多对多关系的两端都会自动获取访问另外一端的API,这些API的工做方式与前面提到的‘反向’一对多关系的用法同样,惟一的区别在于属性的名称,定义ManyToMangField的模型使用该字段的属性名称,而‘反向’模型使用源模型的小写名称加上‘_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,用于反向查询

3)一对一

一对一很是相似多对一关系,能够简单的经过模型的属性访问关联的模型

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

4)反向关联是如何实现的:

一些ORM框架须要你在关系的两端都进行定义,Django的开发者认为这违反了DRY(Don't Repeat Yourself)原则,因此在django中你只需在一端进行定义;那么这是怎么实现的呢?由于在关联的模型类没有被加载以前,一个模型类根本不知道有哪些类与它关联

答案在app registry;在django启动的时候,它会导入全部INSTALLED_APPS中的应用和每一个应用中的模型模块,没建立一个新的模型时,django会自动添加反向的关系到全部关联的模型,若是关联的模型尚未导入,django将保存关联的记录并在关联的模型导入时添加这些关系

因为这个缘由,将模型所在的应用都定义在INSTALLED_APPS的应用列表就显得特定重要,不然,反向关联将不能正确工做。

5)经过关联对象进行查询

涉及关联对象的查询与正常值的字段查询遵循一样的规则,当你指定查询须要匹配的值时,你可使用一个对象实例或者对象的主键值。

例如,若是你有一个id=5的Blog对象b,下面的三个查询将是彻底同样的:

Entry.objects.filter(blog=b) # 使用对象实例
Entry.objects.filter(blog=b.id) # 使用实例的id
Entry.objects.filter(blog=5) # 直接使用id

十一、使用原生SQL语句

若是你发现须要编写的django查询语句太复杂,你能够回归到手工编写SQL语句,django对于编写原生的SQL查询有许多选项。

最后须要注意的是Django的数据库层只是一个数据库接口,你能够利用其它的工具,编程语言或数据库框架来访问数据库,django没有强制指定你非要使用它的某个功能或模块。

相关文章
相关标签/搜索