Django教程之十二-----执行查询

一旦你创建了你的数据模型,Django自动给你一个数据库抽象API,让你创建,提取,更新和删除对象。这个文档解释如何使用这个API。参考<数据模型指引>获取所有不同模型查找选项的全部细节。


贯穿这个指导(和参考),我们将涉及下列模型,他组成一个网页博客应用:

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)
    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

1. 创建对象

为了用Python对象展示数据表数据,Django使用一个直观的系统:一个模型类展示一个数据表,那个类的一个实例来展示数据表中的一个特殊的记录。


为了创建一个对象,使用关键字参数来实例化模型类,然后调用save()来将它保存到数据库。


假设模型在文件mysite/blog/models.py,有一个例子:

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()

这个执行一个INSERT sql语句在幕后。Django不会接触数据库直到你明确的调用save().


save()方法没有返回值。

另请参见

save()有很多优点没有在这里描述。阅读<save()>文档来获取完整信息。

在一步中创建和保存一个对象,使用create()方法。


2. 保存对象的修改

为了保存对一个已经在数据库中的对象的修改,使用save()。


给定一个blog实例b5,之前已经保存在数据库里的,这个例子修改它的名称并且在数据库里更新他的记录:

>>> b5.name = 'New name'
>>> b5.save()

这执行一个UPDATE sql语句在幕后。Django没有接触数据库直到你明确的调用save()。

2.1 保存ForeignKey和ManyToManyField字段

更新一个ForeignKey字段和保存一个正常字段的工作方式完全一致 -- 简单的将一个正确类型的对象赋值给问题中的字段。这个例子更新一个Entry的实例entry的blog属性,假设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()方法来提哪件这个关系的一个记录。这个例子添加Author的实例joe到entry对象中:

>>> 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将会抱怨。


3. 提取对象

为了从你的数据库中提取对象,通过在你模型类中的一个Manager来组成一个QuerySet。


一个QuerySet展示了从你数据库中的对象的集合。它能有0个,1个或者多个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."

注意

Managers仅仅通过模型类才能访问,而不是从模型实例,来执行一个“表级”操作和“记录级”操作的分离。

Manager是一个模型的QuerySets的主要来源。例如,Blog.objects.all()返回一个QuerySet,它包含所有在数据库中的的Blog对象。

3.1 提取所有的对象

从一个表中提取对象的最简单的方式就是获取它们所有的。为了这样做,在一个Manager上使用all()方法:

>>> all_entries = Entry.objects.all()

all()方法返回一个数据库中所有对象的QuerySet。


3.2 使用filters提取指定对象

QuerySet返回通过all()描述的在数据表中的所有对象。通常,尽管,你将只需要对象全部级的一个子集。


为了创建这样一个子集,你提炼初始的QuerySet,添加过滤条件。2个最常用的提炼一个QuerySet的方法是:

  • filter(**kwargs)                                                                                                                                                                 返回一个新的QuerySet包含给定查找参数的对象
  • exclude(**kwargs)                                                                                                                                                           返回一个新的QuerySet包含不匹配给定查找参数的对象。


查找参数(在上述函数定义的**kwargs)应该是下面的<字段查找>中描述的格式。


例如,为了得到一个从2006年开始的blog条目QuerySet,像这样使用filter():

Entry.objects.filter(pub_date__year=2006)

使用默认的管理器类,它和这个是一样的:

Entry.objects.all().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(2005, 1, 30)
... )

这获取数据库中所有条目的初始QuerySet,添加一个过滤器,然后是一个排除器,然后另外一个过滤器。最终的结果是一个QuerySet,包含What开头的标题的所有条目,发布在2005.1.30和今天之间。


过滤的QuerySet是独一无二的

每次你提炼一个QuerySet,你将得到一个全新的和之前的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())

这3个QuerySet都是独立的。第一个是基类的QuerySet包含所有标题以What开头的条目。第二个是第一个的一个子集,有一个额外的条件就是排除pub_date是今天或者未来的记录。第三个是第一个的子集,有一个额外的条件是只选择pub_date是今天或在未来的记录。初始的QuerySet(q1)没有被提取的过程所影响。


QuerySet是懒的

QuerySets很懒 -- 创建一个QuerySet的动作没有涉及任何数据库活动。你可以整天把过滤器堆在一起,Django将不会实际执行查询知道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)
尽管这看起来像3个数据库操作,事实上它只操作了数据库一次,在最后一行的(print(q))。一般的,QuerySet的结果不会从数据库取得除非你要求它们。当你这样做时,QuerySet通过访问数据库被评估。想要更多细节,阅读<当QuerySets被评估时>。


3.3 使用get()来提取一个对象

过滤器总是会给你一个QuerySet,即使只有一个对象符合查询 -- 在这种情况下,他将是一个包含一个元素的QuerySet。


如果你知道只有一个对象符合你的查询,你可以在一个管理器上使用get()方法,他将直接返回对象:

>>> one_entry = Entry.objects.get(pk=1)

你可以在get()中使用任何查询表达式,就像在filter()中一样 -- 再次,阅读下面的<字段查找>

注意在使用get(),和使用有[0]的一个切片的fitler()的不同。如果没有结果匹配查询,get()将会抛出一个DoesNotExist异常。这个异常是查询被执行在的模型类的一个属性 -- 所以在上面的代码中,如果没有主键为1的Entry对象,Django将会抛出一个Entry.DoesNotExist。


相似的,如果超过一个项匹配get()查询,Django将会抱怨。在这种情况下,它将会抛出一个MultipleObjectsReturned,它也是它本身模型类的一个属性。


3.4 其他QuerySet方法

当你需要从数据库查找对象是大部分时间你将使用all(),get(),filter()和exclude()。然而,那远远不是全部;阅读<QuerySet API 指引>来查看一个完整的QuerySet方法的列表。


3.5 限制QuerySets

使用Python的数组切片语法的一个子集来限制你的QuerySet为一个确定数量的记过。这等价于SQL的LIMIT和OFFSET子句。


例如,这将返回前5个对象(LIMIT 5):

>>> Entry.objects.all()[:5]
这将返回6-10的对象(OFFSET 5 LIMIT 5):

>>> Entry.objects.all()[5:10]

负索引(例如,Entry.objects.all()[-1])是不支持的。


通常,将一个QuerySet切片返回一个新的QuerySet -- 它不会评估查询。一个异常是,如果你使用Python切片语法“step”参数。例如,这将明确的执行查询为了返回一个列表包含前10个中间隔2个的对象:

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

为了提取一个单独的对象而不是一个列表(e.g.SELECT foo FROM bar LIMIT 1),使用一个简单的索引来代替切片。例如,这将返回数据库中第一个Entry,在将标题按字母顺序排序后的:

>>> Entry.objects.order_by('headline')[0]

这等价于:

>>> Entry.objects.order_by('headline')[0:1].get()

注意,然而,如果没有对象匹配个定的条件第一个将会抛出IndexError而第二个会抛出DoesNotExist。阅读<get()>获得更多细节。


3.6 字段查找

字段查找是如何指定一个SQL WHERE子句的方式。他们被作为关键字参数指定给QuerySet的方法filter(),exclude()和get()。


基本的查找关键字参数的形式是field__lookputype=value。(那是一个双下划线)。

例如:

>>> Entry.objects.filter(pub_date__lte='2006-01-01')
可以解释成(大致的)下面的SQL:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

这怎么可能

Python可以定义接受任意 名称-值 的函数,这些参数的名称和值可以在运行时评估。了解更多信息,阅读Python官方教程<关键字参数>。

在一个查找中指定的字段必须是模型字段的名称。尽管有一个例外,在一个ForeignKey的情况下,你可以用后缀_id来指定字段名。在这种情况下,值参数期望包含外模型主键的原始值。例如:

>>> Entry.objects.filter(blog_id=4)

如果你传递了一个无效的关键字参数,一个查找函数将会抛出TypeError。


数据库API支持大约2打查找类型;你可以在<字段查找指引>中找到完整指引。下面是一些最常用的查找:

exact

一个“精确”匹配。例如:

>>> Entry.objects.get(headline__exact="Cat bites dog")

将会产生这样的SQL:

SELECT ... WHERE headline = 'Cat bites dog';

如果你不想提供一个查找类型 -- 那就是说,如果你的关键字参数不包含一个双下划线 --查找类型假定是exact。


例如,下面2个语句是等价的:

>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)         # __exact is implied
这很方便,一位内exact查找是通用情况。

iexact

不区分大小写的匹配。所以,查询:

>>> Blog.objects.get(name__iexact="beatles blog")

将会匹配一个Blog标题"Beatles Blog","beatles blog"或者甚至"BeAtlES blOG"。

contains

大小写敏感的包含测试。例如:

Entry.objects.get(headline__contains='Lennon')

大致上翻译为SQL:

SELECT ... WHERE headline LIKE '%Lennon%';

注意这将会匹配标题“Today Lennon honored”,而不是"today lennon honored"。

同样也有个无视大小写的版本,icontains

startswith,endswith

Starts-with和ends-with搜索,各自的。同样也有不区分大小写版本istartswith和iendswith。


再次,这只是一点皮毛。一个完整的指引可以在<字段查找指引>中找到。


3.7 查找跨度关系

Django提供一个强有力的,直觉的方式来“跟踪”关系,为你自动处理SQL JOIN,在幕后。为了跨越一个关系,

只要使用相关跨模型字段的字段名,被双下划线分割的,直到你获得你想要的字段。


这个例子提取所有带有name是“Beatles blog”的Blog的所有Entry:

>>> Entry.objects.filter(blog__name='Beatles Blog')
这个跨越可以如你想象的深。


它也可以向后。为了指定一个“反向”的关系,只要使用模型的小写名称。


这个例子提取所有的Blog对象,它有至少一个Entry,Entry的标题包含‘Lennon’:

>>> Blog.objects.filter(entry__headline__contains='Lennon')

如果你在多个关系之间进行过滤并且其中一个中间关系没有符合过滤条件的值,Django将像空值(所有的值都是NULL)

一样处理,但是有效的,对象。所有这些意味着将不会有错误抛出。例如,在这个过滤器中:

Blog.objects.filter(entry__authors__name='Lennon')

(如果有一个相关的Author模型),如果没有关联到一个entry的author,它将会像没有name属性附加上那样处理,而不

是抛出一个错误因为没有author。通常你想要发生的事是很明确的。它可能引起困惑的唯一情况是如果你使用了

isnull。因此:

Blog.objects.filter(entry__authors__name__isnull=True)
将会返回Blog对象,它有在author上有一个空的name和那些有在entry上有空的author。如果你不想要这些后来的对象

,你可以编写:

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

跨越多值关系

当你在过滤一个基于一个ManyToManyField或者一个反向ForeignKey的对象时,有种中不同的过滤排序你可能感兴趣

。考虑Blog/Entry的关系(Blog对Entry是一对一关系)。我们可能会感兴趣的是找到一个有“Lennon”标题的博客,并且

还有一个在2008年发布。因为有多个条目关联到了一个blog上,这2个查询都是可能的,并且在某种情况下会有意义。


同样的情况发生在ManyToManyField。例如,如果一个Entry有一个ManyToManyField叫做tags,我们也许想要找到

链接到tags的entries,称为“music”和“bands”或者我们也许想要一个entry包含带有一个“music”名字和“public”状态的

标签。


为了处理这2种情况,Django有一种一致的处理filter()调用的方法。在一个filter()调用内部的所有东西都同时被应用来

过滤匹配所有这些需求的项。连续的filter()调用进一步限制了对象的集,但对于多值关系,它们应用到任何链接到主

要模型的对象上,不一定是由哪些被早期filter()调用选择的对象。


那听起来有一点困惑,希望一个例子可以说明白。为了选择所有blogs,它包含即在标题上有“Lennon”,又是在2008

年发布的(同样的entry满足2个条件),我们将会写:

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

为了选择所有的blogs,它包含在标题上带有“Lennon”的entry,也包含发布在2008年的entry,我们将这样写:

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
假设只有一个blog拥有包含“Lennon”的entry和来自2008的entry,但是来自2008的entry都不包含“Lennon”。第一个

查询将不会返回任何blogs,但是第二个查询将会返回那个博客。


在第二个例子中,第一个过滤限制了查询集为链接到所有在标题中有“Lennon”的entry的blogs。第二个过滤限制了

blogs的集进一步到那些发布于2008的entry。我们在使用每个过滤语句来过滤Blog的项,而不是Entry项。

注意

对于跨越多值关系的查询,filter()的行为,正如上描述一样,和exclude()是不等的。相反,在一个exclude()调用的条件将不需

要引用相同的项。

例如,下列查询将会排除blogs包含既有“Lennon”在标题上的entry,也有发布在2008年的Entry:

Blog.objects.exclude(
    entry__headline__contains='Lennon',
    entry__pub_date__year=2008,
)
然而,不想当使用filter()的行为,这将不会限制基于 满足2个条件的entry的blog。为了做到那个,例如为了选择所有既不包含

“Lennon”标题也不是在2008发布的,你需要做2个查询:

Blog.objects.exclude(
    entry__in=Entry.objects.filter(
        headline__contains='Lennon',
        pub_date__year=2008,
    ),
)


3.8 能引用模型字段的过滤器

在目前所给的例子中,我们已经写过用一个常量和模型字段的值比较的过滤器。但是如果你想要将模型字段与同模型

的另一个字段比较呢?


Django提供了F 表达式来允许这样的比较。F()的 实例作为带有一个查询的模型的引用。这些引用能被用于查询过滤器

来比较在同一个模型实例的2个不同的字段。


例如,为了找到一个比pingbacks有更多评论的所有的blog entries列表,我们构建了一个F()对象来引用pingback数量

,并在那个查询里使用F()对象:

>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

Django支持在F()对象中使用加法,减法,乘法,除法,模和幂运算,包括常数和其他F()对象。为了找到比pingbacks

多2倍评论的blog entry,我们修改查询:

>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

你也可以在F()对象中使用双下划线来跨越关系。一个带有一个双下划线的F()对象将会引入任何需要访问相关对象的链

接。例如,为了提取所有author名称和blog名称相同的entries,我们可以这样查询:

>>> Entry.objects.filter(authors__name=F('blog__name'))

对于date和date/time字段,你可以添加或者减去一个timedelta对象。下面将会返回在发布后被修改超过3天的所有的

entries:

>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
F()对象支持位操作通过:bitand(),bitor(),bitrightshift(),和bitleftshift()。

例如:

>>> F('somefield').bitand(16)

在Django1.11中的改变:

.bitrightshift()和.bitleftshift()被添加和支持。


3.9 pk查找缩写

为了方便,Django提供了一个pk查找缩写,它代表“主键”。


在blog模型的例子中,主键是id字段,所以这3个语句是等价的:

>>> 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查询 -- 任何查询项都可以和pk组合来在一个模型的主键上执行查询:

# 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查找同样可以连接。例如,这3个语句是等价的:

>>> 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


3.10 在LIKE语句中转义百分符号和下划线

等同于LIKE SQL语句的字段查找(iexact,contains,icontains,startswith,istartswith,endswith和iendswith)当用在LIKE语

句中时将自动转义这2个特殊字符 -- 百分号和下划线。(在LIKE语句中,百分号表示一个多字符通配符,下划线表示

一个单字符通配符)。


这意味着事情应该直观的做,所以抽象才不会泄漏。例如,为了提取所有包含一个百分号的entries,只要使用百分号

作为任何其他字符:

>>> Entry.objects.filter(headline__contains='%')
Django为你处理引号;结果SQL类似下面:
SELECT ... WHERE headline LIKE '%\%%';


3.11 缓存和查询集

每一个QuerySet包含一个缓存来减少数据库的访问。理解它如何工作将有助于你编写更高效的代码。


在一个新创建的QuerySet中,缓存是空的。一个QuerySet第一个被评估时 -- 并且,因此, 发生了一个数据库查询

-- Django保存查询结果到QuerySet的缓存中并且返回明确请求的结果(e.g. 如果QuerySet被迭代,就是下一个元素)。

后面的评估复用QuerySet的缓存结果。


记住这个缓存行为,因为如果你没有正确使用你的QuerySet,它可能反咬你一口。例如,里面将会创建2个QuerySets

,评估它们,并且打印他们:

>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])

那就意味着同样的数据库查询会被执行2次,有效的加重了你数据库的负担。同样,还有一种可能是这2个列表也许不

包含同样的数据库记录,因为一个Entry也许会在2个请求间隔的几秒之内被添加或者删除。


为了避免这个问题,,简单的保存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.


当QuerySets没有被缓存

QuerySets并不总是会缓存他们的结果。当只评估queryset的部分时,缓存被检查,如果它没有被填充,由随后的查询

返回的项将不会被缓存。特别的,这意味着使用一个数组切片或者一个索引的限制queryset将不会填充缓存。


例如,在一个queryset对象中重复获取一个确定的索引将每次都查询数据库:

>>> queryset = Entry.objects.all()
>>> print(queryset[5]) # Queries the database
>>> print(queryset[5]) # Queries the database again

然而,如果entries 的queryset已经被评估了,缓存检查将会代替:

>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print(queryset[5]) # Uses cache
>>> print(queryset[5]) # Uses cache

这里有一些其他的行为导致整个queryset被评估因此填充缓存:

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

注意

简单打印queryset将不会填充缓存。这是因为__repr__()调用只返回真个queryset的一个切片


4. 使用Q对象的复杂查找

关键参数查询 -- 在filter(),等 -- 是”and“在一起的。如果你需要执行更多的复杂查询(例如,使用or语句的查询),你

可以使用Q对象。


例如,这个语句缠上一个代表2个”question_startswith“的"or"查询的Q对象:

Q(question__startswith='Who') | Q(question__startswith='What')

这等价于下列SQL WHERE 子句:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

你可以通过组合Q对象和&以及|操作符,并使用括号的分组来构成任意复杂的语句。同样,一个Q对象能被使用~

操作符否定,允许组合包含一个正常查询和一个否定(not)查询的查找:

Q(question__startswith='Who') | ~Q(pub_date__year=2005)

每个需要关键字参数(例如,exclude(),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))
)

大致翻译成SQL:

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

查找函数可以混合Q对象和关键字参数的使用。所有提供给一个查找函数的参数(关键字参数或者Q对象)是”AND“在

一起的。然而,如果提供了一个Q对象,Q对象必须领先任何关键字参数的定义。例如:

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith='Who',
)

将会是一个有效的查询,等价于先前的例子;但是:

# INVALID QUERY
Poll.objects.get(
    question__startswith='Who',
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
将不会有效

另请参见

<OR查找例子>在Django单元测试中有更多Q的使用。


5. 比较对象

为了比较2个模型实例,只用使用标准Python比较操作符,双等符号:==。在幕后,那比较2个模型的主键。


使用上面的Entry例子,下面2个语句将是等价的:

>>> some_entry == other_entry
>>> some_entry.id == other_entry.id

如果一个模型的主键不是id,没问题。比较将总是使用主键,不管它的名称是什么。例如,如果一个模型的主键字段

是name,这2个语句是等价的:

>>> some_obj == other_obj
>>> some_obj.name == other_obj.name


6. 删除对象

删除方法,方便的,名称是delete()。这个方法立即删除对象并且返回删除对象的编号和一个带有每个删除对象类型的

编号的字典。

例子:

>>> e.delete()
(1, {'weblog.Entry': 1})

你也可以批量删除对象。每个Queryset都有一个delete()方法,它删除QuerySet的所有成员。


例如,这删除所有带有pub_date是2005的Entry对象:

>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

记住这将会,无论什么时候都可能,在SQL中纯粹的执行,并且所以单个对象实例的delete()方法将不必在处理过程中

调用。如果你在一个模型类型提供了一个定制的delete()函数并且想要确保它被调用,你将需要”手工“删除那个模型的

实例(例如,通过迭代一个QuerySet并且在每个对象上单独的调用delete()方法)而不是使用一个QuerySet的批量删除

delete()方法。


当Django删除一个对象时,默认它效仿SQL级联删除的约束 -- 换句话说,任何有外键指向对象的对象都将跟随删除

被删除。例如:

b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

这个级联行为可以通过对ForeignKey的on_delete参数来自定义。


注意,delete()是唯一没有在管理器上公开的QuerySet方法。这是一个安全的原理用于防止你意外的请求

Entry.objects.delete(),并且删除所有的Entries。如果你项删除所有的对象,你必须明确的请求一个完整的查询集:

Entry.objects.all().delete()

7. 复制模型实例

尽管没有提供复制模型实例的内置函数,轻松创建带有所有字段值复制的新实例也是可以的。在这个简单的情况下,

你可以将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的子类:

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的ManyToManyField。当复制了一个entry

,你必须为新的entry设置多对多关系:

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()

8. 一次更新多个对象

有时你想给在QuerySet中的所有对象的字段设置一个特殊的值。你可以使用update()方法来这样做。例如:

# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

你可以使用这个方法只设置非关系字段和ForeignKey字段。为了更新一个非关系字段,提供新值作为常量。为了更新

ForeignKey字段,将新值设置为你想指定的新的模型实例。例如:

>>> b = Blog.objects.get(pk=1)

# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)

update()方法立即应用并且返回匹配查询(可能不等于更新的行的数量如果某些行有了新值)的行的数量。在被更新

QuerySet上唯一的约束是它只能访问一个数据表:模型的主表。你可以基于相关字段过滤,但是你只能在模型的主表

上更新。例子:

>>> 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()

用于更新的调用仍然可以使用F表达式来更新一个基于模型另一个字段的字段。这对于基于当前值的计数器来说特别

有用。例如,增长在blog中每个entry的pingback数量:

>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
然而,不想在过滤和排除子句中的F()对象,当你使用F()对象在更新中时,你不能引入joins -- 你只能引用正在更新

的模型本地字段。如果你尝试使用F()对象来引进一个join,一个FieldError将会抛出:

# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))

9. 相关对象

当你在模型中定义一个关系时(例如,一个ForeignKey,OneToOneField,或ManyToManyField),那个模型的实例

将会有一个方便的API来访问相关对象。


使用这页面顶部的模型,例如,一个Entry对象能通过访问blog属性来获得它相关的Blog对象。


(在幕后,这个功能由Python描述符实现,这对你不重要,但我们在这里说明只是为了满足好奇)


Django同样为关系的另一边创建API访问器 -- 从被关联的模型到定义这个关系的模型的链接。例如,一个Blog对象b可

以通过entry_set属性:b.entry_set.all()访问所有相关Entry对象的列表。


在这个玄阶的所有的例子都是用在这个页面顶部定义的样本Blog,Author和Entry模型。


9.1 一对多关系

向前

如果一个模型有一个ForeignKey,那个模型的实例将可以通过模型的简单属性来访问相关(外)对象。

例子:

>>> e = Entry.objects.get(id=2)
>>> e.blog # Returns the related Blog object.

你可以通过外键属性来获得和设置。正如你所期望的,对外键的改变不会保存到数据库除非你调用save(),例子:

>>> 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.

注意,select_related() QuerySet预先填充一对多关系的缓存。例子:

>>> 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.

遵循关系”向后“

如果一个模型有一个ForeignKey,外键模型的实例会访问返回第一个模型实例的管理器。默认的,这个管理器别称为

F00_set,其中F00是源模型名称,小写。这个管理器返回QuerySets,它能被过滤和操作正如上面”提取对象“选节描述

的一样。


例子:

>>> 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()

你可以覆写F00_set名称通过设置在ForeignKey定义中的related_name参数。例如,如果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()

使用自定义反向管理器

默认的反向关系的反向管理器是那个模型默认管理器的子类。如果你想要为一个给定的查询制定一个不同的管理器你

可以使用下面的语法:

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()

处理相关对象的额外方法

额外的对于定义在上述”提取对象“的QuerySet方法,ForeignKey Manager有额外的方法用于处理相关对象集。下面

每个的纲要,全部细节请阅读<相关对象指引>。

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()方法不可用,所有在迭代器中的对象将会被添加并且没有任何存在的元素会被移除


9.2 多对多关系

多对多关系的2端都能自动使用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.

就像ForeignKey,ManyToManyField能指定related_name。在上面的例子中,如果在Entry中的ManyToManyField已

经指定了related_name = ‘entries’,那么每个Author实例都将有一个entries属性而不是entry_set


9.3 一对一关系

一对一关系和多对一关系非常相似。如果你在你的模型上定义了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.

反向查询将会不同。一对一关系的相关模型能访问一个Manager对象,但是那个Manager代表一个单独对象,而不是

对象集:

e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object

如果咩有对象被赋值给这个关系,Django将会抛出DoesNotExist异常。


实例能以同样的方式赋值到反向关系正如你复制到正向关系一样:

e.entrydetail = ed

9.4 反向关系如何可能?

其他对象关系映射需要你定义两边的关系。Django开发者相信这是对DRY(不要重复你自己)原则的阻碍,所以

Django只需要你在一边定义关系即可。


但那怎么可能,给定一个模型类,它并不知道其他那个模型类是关联到它的知道这个其他的模型类加载的时候?


答案在app registry。当Django启动的时候,它导入INSTALLED_APPS中的每个应用,和每个应用中的模型。无论

什么时候一个新的模型类被创建,Django添加一个反向关系到任何相关的模型。如果被关联的模型还没有导入,

Django保持跟踪这个关系并且当被关联的模型最终被导入时添加他们。


因为这个原因,你正在使用的所有模型被定义在列在INSTALLED_APPS中的应用里是很重要的。否则,反向关系

将不能正常工作。


9.5 相关对象的查询

涉及相关对象的查询和涉及正常值字段的查询遵循相同的规则。当为一个查询指定值去匹配时,你既可以使用一

个对象实例本身,也可以使用对象的主键。


例如,如果你有一个Blog对象b,它的id=5,下面3个查询将是相同的:

Entry.objects.filter(blog=b) # Query using object instance
Entry.objects.filter(blog=b.id) # Query using id from instance
Entry.objects.filter(blog=5) # Query using id directly

10. 回到原始SQL

如果你发现自己需要写一个Django数据映射难以处理的复杂SQL查询时,你可以手工写原始SQL。Django有写原始

SQL查询的很多选项;请阅读<执行原始SQL查询>。

最后,注意到Django的数据层只是你数据库的一个接口是很重要的。你可以使用其他工具,编程语言或者数据库框架

访问你的数据库;关于你的数据库,没有什么Django专用的东西。
















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值