Django 08. django框架模型之增删改查进阶

2019年11月12日 阅读数:93
这篇文章主要向大家介绍Django 08. django框架模型之增删改查进阶,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。
简介 django 框架模型之增改查进阶操做。

建立完Model以后, Django 自动为你提供一套数据库抽象层的API,利用它能够完成建立,提取,更新,删除对象的操做。html

如下面的Model为例:python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class   Blog(models.Model):
     name  =   models.CharField(max_length = 100 )
     tagline  =   models.TextField()
 
     # On Python 3: def __str__(self):
     def   __unicode__( self ):
         return   self .name
 
class   Author(models.Model):
     name  =   models.CharField(max_length = 50 )
     email  =   models.EmailField()
 
     # On Python 3: def __str__(self):
     def   __unicode__( self ):
         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()
 
     # On Python 3: def __str__(self):
     def   __unicode__( self ):
         return   self .headline

建立Creating objects

1
2
>>> b  =   Blog(name = 'Beatles Blog' , tagline = 'All the latest Beatles news.' )
>>> b.save()

Django 用一种很直观的表达方式将Python对象和数据表对应起来:一个model类对应一张数据表,一个model实例对应表中的某一行记录。git

以建立对象为例:只要将关键字参数传递给model类,而后调用save()保存到数据库便可github

这段代码就会在幕后执行一条INSERT SQL语句。除非你显式地调用save()方法,不然Django不会保存到数据库中。数据库

你可使用create()方法一次完成新建并保存对象的操做。django

修改Saving changes to objects

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

Saving ForeignKey and ManyToManyField fields编程

更新ForeignKey字段和保存普通字段没什么差异;只是在给字段分配对象时要注意对象类型必定要正确:api

1
2
3
4
>>> entry  =   Entry.objects.get(pk = 1 )
>>> cheese_blog  =   Blog.objects.get(name = "Cheddar Talk" )
>>> entry.blog  =   cheese_blog
>>> entry.save()

更新ManyToManyField就有些不一样;要在字段上使用add()方法来添加关系:数组

1
2
>>> joe  =   Author.objects.create(name = "Joe" )
>>> entry.authors.add(joe)

一次添加多个:缓存

1
2
3
4
5
>>> 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)

检索对象 (Retrieving objects)

要检索数据库中的对象,就要为你的model 类构造一个查询集QuerySet。一个QuerySet就表明数据库中的一组数据。它能够有一个或不少个,也能够经过filter根据给定的参数对数据集作进一步筛选。在SQL术语中,QuerySet至关于SELECT语句,filter至关于WHERE或LIMIT这样的限定从句。

检索全部的对象

1
all_entries  =   Entry.objects. all ()

使用过滤器filter检索特定的对象

filter(**kwargs)

    返回知足筛选条件的新QuerySet

exclude(**kwargs)

    返回不知足筛选条件的新QuerySet

1
Entry.objects. filter (pub_date__year = 2006 )

链式过滤:

1
2
3
4
5
6
7
>>> Entry.objects. filter (
...     headline__startswith = 'What'
... ).exclude(
...     pub_date__gte = datetime.date.today()
... ). filter (
...     pub_date__gte = datetime( 2005 1 30 )
... )

查询一个单独的对象

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

注意当使用get查询时若对象不存在会产生DoesNotExist异常。

QuerySet是惰性的

QuerySets是惰性的。建立 QuerySet 的动做不涉及任何数据库操做。你能够一直添加过滤器,在这个过程当中,Django不会执行任何数据库查询,除非QuerySet被执行。

切片

能够用python的数组切片语法来限制你的QuerySet以获得一部分结果。它等价于SQL中的LIMIT和OFFSET。

例如,下面的这个例子返回前五个对象:

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

Django 不支持对查询集作负数索引。

跨关系查询

Django 提供了一种直观而高效的方式在查询(lookups)中表示关联关系,它能自动确认 SQL JOIN 联系。要作跨关系查询,就使用双下划线来连接模型(model)间关联字段的名称,直到最终连接到你想要的 model 为止。

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

要注意的是若是跨关系对象有多个符合条件,被查询对象会返回屡次:

1
Blog.objects. filter (entry__headline = 'note' )

上面若是有个Blog连接多个headline为note的Entry对象,此Blog会返回屡次。

跨一对多/多对多关系

对于包含在同一个filter()中的筛选条件,查询集要同时知足全部筛选条件。而对于连续的filter(),查询集的范围是依次限定的。但对于跨一对多/多对多关系查询来讲,在第二种状况下,筛选条件针对的是主model全部的关联对象,而不是被前面的filter()过滤后的关联对象。

这听起来会让人迷糊,举个例子会讲得更清楚。要检索这样的 blog:它要关系一个大标题中含有 "Lennon" 而且在2008年出版的entry(这个entry同时知足这两个条件),能够这样写:

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

要检索另一种 blog:它关联一个大标题含有"Lennon"的 entry ,又关联一个在2008年出版的 entry (一个entry的大标题含有Lennon,同一个或另外一个entry是在2008年出版的)。能够这样写:

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

上述原则一样适用于exclude():一个单独exclude()中的全部筛选条件都是做用于同一个实例 (若是这些条件都是针对同一个一对多/多对多的关系)。连续的filter()或exclude()却根据一样的筛选条件,做用于不一样的关联对象。

Filters can reference fields on the model

class F

在上面全部的例子中,咱们构造的过滤器都只是将字段值与某个常量作比较。若是咱们要对两个字段的值作比较,那该怎么作呢?

Django 提供F()来作这样的比较。F()的实例能够在查询中引用字段,来比较同一个model实例中两个不一样字段的值。

例如:要查询回复数(comments)大于广播数(pingbacks)的博文(blog entries),能够构造一个 F() 对象在查询中引用评论数量:

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

Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操做。例如,要找到广播数等于评论数两倍的博文,能够这样修改查询语句:

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

要查找阅读数量小于评论数与广播数之和的博文,查询以下:

1
>>> Entry.objects. filter (rating__lt = F( 'n_comments' +   F( 'n_pingbacks' ))

你也能够在 F() 对象中使用双下划线作跨关系查询。F() 对象使用双下划线引入必要的关联对象。例如,要查询博客(blog)名称与做者(author)名称相同的博文(entry),查询就能够这样写:

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

对于Date和datetime域,能够加减timedelta对象。

返回出版3天之后修改的博文:

1
2
>>>  from   datetime  import   timedelta
>>> Entry.objects. filter (mod_date__gt = F( 'pub_date' +   timedelta(days = 3 ))

F对象如今也支持位运算(bitand(),bitor()):

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

主键查询的简捷方式 (The pk lookup shortcut)

为使用方便考虑,Django 用pk表明主键"primary key"。

以Blog为例, 主键是id字段,因此下面三个语句都是等价的:

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

在LIKE语句中转义百分号%和下划线_ (Escaping percent signs and underscores in LIKE statements)

字段筛选条件至关于LIKE SQL语句 (iexactcontainsicontainsstartswithistartswithendswith 和 iendswith) ,它会自动转义两个特殊符号,百分号(%)和下划线(_)。(在LIKE语句中,百分号%表示多字符匹配,而下划线_表示单字符匹配。)

这就意味着咱们能够直接使用这两个字符,而不用考虑他们的SQL语义。例如,要查询大标题中含有一个百分号%的entry:

1
>>> Entry.objects. filter (headline__contains = '%' )

Django会处理转义,下划线_和百分号%的处理方式相同,Django都会自动转义。

缓存和查询

每一个QuerySet都包含一个缓存,以减小对数据库的访问。要编写高效代码,就要理解缓存是如何工做的。

一个QuerySet时刚刚建立的时候,缓存是空的。 QuerySet 第一次运行时,会执行数据库查询,接下Django就在QuerySet的缓存中保存查询的结果,并根据请求返回这些结果(好比,后面再次调用这个 QuerySet 的时候)。再次运行 QuerySet 时就会重用这些缓存结果。

要牢住上面所说的缓存行为,不然在使用 QuerySet 时可能会给你形成不小的麻烦。例如,建立下面两个 QuerySet ,并对它们求值,而后释放:

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

这就意味着相同的数据库查询将执行两次,事实上读取了两次数据库。并且,这两次读出来的列表可能并不彻底相同,由于存在这种可能:在两次读取之间,某个 Entry 被添加到数据库中,或是被删除了。

要避免这个问题,只要简单地保存QuerySet而后重用便可:

1
2
3
>>> queryset  =   Entry.objects. all ()
>>>  print ([p.headline  for   in   queryset])  # 对查询集求值.
>>>  print ([p.pub_date  for   in   queryset])  # 使用缓存.

When querysets are not cached

查询集并不老是缓存结果,当查询集执行部分查询时,会先检查缓存,若是它没有被填充,部分查询返回的结果不会被缓存。这意味着,使用切片查询不会填充缓存。

例如,重复的切片查询每次都会访问数据库:

1
2
3
>>> queryset  =   Entry.objects. all ()
>>>  print   queryset[ 5 # 访问数据库
>>>  print   queryset[ 5 # 再次访问数据库

可是,若是整个查询集已经被求值,切片查询会使用缓存:

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

下面是一些会填充缓存的操做:

1
2
3
4
>>> [entry  for   entry  in   queryset]
>>>  bool (queryset)
>>> entry  in   queryset
>>>  list (queryset) 

用Q对象实现复杂查找 (Complex lookups with Q objects)

在filter() 等函式中关键字参数彼此之间都是 "AND" 关系。若是你要执行更复杂的查询(好比,实现筛选条件的OR关系),可使用Q对象。

Q对象(django.db.models.Q)是用来封装一组查询关键字的对象。

例如,下面这个Q对象封装了一个单独的 LIKE 查询:

1
2
from   django.db.models  import   Q
Q(question__startswith = 'What' )

Q 对象能够用 & 和 | 运算符进行链接。当某个操做链接两个 Q 对象时,就会产生一个新的等价的 Q 对象。

例如,下面这段语句就产生了一个Q,这是用 "OR" 关系链接的两个"question__startswith" 查询:

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

上面的例子等价于下面的 SQL WHERE 从句:

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

你能够用 & 和 | 链接任意多的Q对象,并且能够用括号分组。Q 对象也能够用 ~ 操做取反,并且普通查询和取反查询(NOT)能够链接在一块儿使用:

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

每种查询函式(好比 filter(), exclude(), get()) 除了能接收关键字参数之外,也能以位置参数的形式接受一个或多个 Q 对象。若是你给查询函式传递了多个 Q 对象,那么它们彼此间都是 "AND" 关系。例如:

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

... 大致能够翻译为下面的 SQL:

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

查找函式能够混用 Q 对象和关键字参数。查询函式的全部参数(Q 关系和关键字参数) 都是 "AND" 关系。可是,若是参数中有 Q 对象,它必须排在全部的关键字参数以前。例如:

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

是一个有效的查询。但下面这个查询虽然看上去和前者等价:

1
2
3
4
# 无效查询
Poll.objects.get(
     question__startswith = 'Who' ,
     Q(pub_date = date( 2005 5 2 )) | Q(pub_date = date( 2005 5 6 )))

但这个查询倒是无效的。

对象比较 (Comparing objects)

要比较两个对象,就和Python 同样,使用双等号运算符:==。实际上比较的是两个model的主键值。

以上面的Entry为例,下面两个语句是等价的:

1
2
>>> some_entry  = =   other_entry
>>> some_entry. id   = =   other_entry. id

若是model的主键名称不是id,也不要紧。Django会自动比较主键的值,而无论他们的名称是什么。例如,若是一个model的主键字段名称是name,那么下面两个语句是等价的:

1
2
>>> some_obj  = =   other_obj
>>> some_obj.name  = =   other_obj.name

对象删除(Deleting objects)

删除方法就是delete()。它运行时当即删除对象而不返回任何值。例如:

1
e.delete()

你也能够一次性删除多个对象。每一个QuerySet 都有一个delete() 方法,它一次性删除 QuerySet 中全部的对象。

例如,下面的代码将删除 pub_date 是2005年的 Entry 对象:

1
Entry.objects. filter (pub_date__year = 2005 ).delete()

要牢记这一点:不管在什么状况下,QuerySet 中的 delete() 方法都只使用一条 SQL 语句一次性删除全部对象,而并非分别删除每一个对象。若是你想使用在model中自定义的delete() 方法,就要自行调用每一个对象的delete方法。(例如,遍历 QuerySet,在每一个对象上调用 delete()方法),而不是使用QuerySe 中的 delete()方法。

在Django删除对象时,会模仿SQL约束ON DELETE CASCADE的行为,换句话说,删除一个对象时也会删除与它相关联的外键对象。例如:

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

要注意的是: delete() 方法是QuerySet上的方法,但并不适用于 Manager 自己。这是一种保护机制,是为了不意外地调用Entry.objects.delete() 方法致使 全部的 记录被误删除。若是你确认要删除全部的对象,那么你必须显式地调用:

1
Entry.objects. all ().delete()

复制Copying model instances

复制对象并无内置的函数,最简单的状况,将pk设为None。

1
2
3
4
5
blog  =   Blog(name = 'My blog' , tagline = 'Blogging is easy' )
blog.save()  # blog.pk == 1
 
blog.pk  =   None
blog.save()  # blog.pk == 2

若是使用继承的话,状况会复杂一点:

1
2
3
4
5
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:

1
2
3
django_blog.pk  =   None
django_blog. id   =   None
django_blog.save()  # django_blog.pk == 4

这样写是不会复制关系对象的,要复制关系对象,还须要一点代码:

1
2
3
4
5
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

一次更新多个对象 (Updating multiple objects at once)

有时你想对QuerySet中的全部对象,一次更新某个字段的值。这个要求能够用 update() 方法完成。例如:

1
2