今天学习的是Django的Model Query,前一篇已经学习过Model了,讲述的主要是Django中是如何处理关系型数据的模型的,一对一,多对一,多对多等,这篇则主要是描述的查询,能够将数据存进去,还得取出来,Django给每一个Model自动提供了丰富的查询接口,而且能够进行关联查询,基本上,能够满足绝大多数的查询需求。
在Django的文档中,有一句话说的非常好:
A model class represents a database table, and an instance of that class represents a particular record in the database table.
Model类的实例代表的是数据库中的记录,我们在进行查询的时候,得到结果一般都是一个数据库记录集合,这对应到Django里,就是QuerySet,Django提供的查询接口全都封装在QuerySet中,比如filter(), order_by()等,这对应到SQL语句,就是SELECT语句。这里我们只对经常用到的QuerySet方法进行简单描述,起到一个勾起回忆的作用,至于更多的内容还请参考QuerySet详细的API:QuerySet API。
先构造几个有关联关系的Model:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __unicode__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
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()
def __unicode__(self):
return self.headline
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry)
details = models.TextField()
这几个Model中,Blog和Entry是一对多的关系,Entry和Author是多对多的关系,Entry和EntryDetail是一对一的关系,分别通过ForeignKey, ManyToManyField, OneToOneField进行关联。现在我们来看看怎么对这些进行查询。
1. 首先来看最简单的,all(),一次获取所有的数据库记录:
>>> all_entries = Entry.objects.all()
objects是什么呢?每一个Model都有一个Manager对象,Manager也是Model的查询接口,那它和QuerySet之间是什么关系呢?其实Manager可以看成是QuerySet的代理类,最开始的QuerySet对象就是通过Manager类来得到的,objects就是Manager对象在Model类中的属性名。至于为什么有Manager类,为什么QuerySet需要代理,我现在还不是很清楚,这些需要看Django的源码才能知道,现在我们先知道怎么用就可以了。
调用objects.all()方法,得到就是一个QuerySet对象,如:
>>> type(Entry.objects.all())
<class 'django.db.models.query.QuerySet'>
>>> Entry.objects.all()
[<Entry: headline1>, <Entry: headline2>]
2. filters,过滤结果
即给Select语句加上Where查询条件,选出符合条件的子集,QuerySet中提供了两个方法:
- filter(**kwargs)
Returns a new QuerySet containing objects that match the given lookup parameters. - exclude(**kwargs)
Returns a new QuerySet containing objects that do not match the given lookup parameters.
field__lookuptype=value
这样的约束,field是Model的属性名,然后是两个下划线,然后是由Django定义的查询条件,比如exact, contains, startwith, gte, lt等,更多详细的内容,可以查看QuerySet API。下面给出两个例子:
>>> Blog.objects.filter(tagline__startswith="All")
[<Blog: Beatles>]
>>> Entry.objects.filter(pub_date__gte="2014-04-20")
[<Entry: headline1>, <Entry: headline2>]
需要注意的是,filter()和exclude()返回的结果仍然是一个QuerySet对象,所以,可以在一个语句中串联的使用filter,如:
>>> Entry.objects.filter(
... headline__startswith='What'
... ).exclude(
... pub_date__gte=datetime.date.today()
... ).filter(
... pub_date__gte=datetime(2005, 1, 30)
... )
3. 接下来,来说一说QuerySet的一些特性
- 首先,QuerySet是各自独立的,上面例子中,使用了3个串联的filter方法,每一个filter都会得到一个QuerySet,这3个QuerySet之间并没有什么直接的关系,相互之间也没有影响。
- 其次,是QuerySet有一个很重要的特性就是延迟加载,并不是每一次调用filter()方法就会去查询一次数据库,只有在真正需要数据的时候才会去查询数据库,比如:
>>> q = Entry.objects.filter(pub_date__gte="2014-04-20")
>>> q = q.filter(mod_date__gte="2014-05-20")
>>> q
[<Entry: headline1>, <Entry: headline2>]
- 然后,就是QuerySet的缓存特性了。每一个QuerySet都有缓存,它第一次访问数据库时,会将结果缓存起来,这样当再次用到这个QuerySet时,就不用再次访问数据库了。比如:
>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])
- 这样就是访问了两次数据库,因为创建了两个不同的QuerySet对象,但是:
>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset])
>>> print([p.pub_date for p in queryset])
4. 直接得到一个Model对象:get()
filter()的返回结果总是得到一个QuerySet对象,QuerySet是Model对象集合,当你明确知道Model对象的集合只有一个时,可能就没必要用filter这么“麻烦”了,可以使用get()方法直接得到Model对象。如:
>>> one_entry = Entry.objects.get(pk=1)
>>> type(one_entry)
<class 'query_test.models.Entry'>
需要注意的是,当get()查询的结果没有,会抛出 DoesNotExist 异常,或者是有多个时,会抛出MultipleObjectsReturned 异常。
5. limit and offset
当我们在做分页时,会用到SQL的limit和offset特性,从哪开始读取多少个记录。那这个在Django里是怎么用的呢?很简单,就是直接使用python中array-slicing的语法就行,如:
>>> Blog.objects.all()
[<Blog: Beatles>, <Blog: blog2>, <Blog: blog3>]
>>> Blog.objects.all()[0]
<Blog: Beatles>
>>> Blog.objects.all()[1:3]
[<Blog: blog2>, <Blog: blog3>]
第2个例子,通过[0]得到的就直接是一个Model对象,而第三个例子,通过[1:3]得到的是一个QuerySet对象,也就是说是一个Model对象的集合。第三个例子中,可以理解为limit等于2,offset等于1。
上面简单地介绍了一下Django提供的Model查询接口,虽然简单,但是基本上也就是我们平常经常用到的方法。下面我们再从一对一,多对一,多对多这三个方面,来看看怎么进行稍微复杂点的联合查询。
1. One-to-many relationships
一对多,从“多”的这一方,也就是ForeignKey所在的Model,向“一”的这一方查,比较容易,只需要指定属性名称就可以了,比如:
>>> e = Entry.objects.get(id=2)
>>> e.blog
如果从“一”的这一方向“多”的那一方查,那么“多”的一方是“一”的一方的一个集合,Django会自动为“一”的这一方创建一个叫做"Foo_set"的Manager,它就是“多”的这一方的集合,可以直接通过这个Manager对关联的Model进行增删查改操作。比如:
>>> 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()
2. Many-to-many relationships
多对多,其实和一对多非常的像,定义ManyToManyField的Model访问和它关联的Model时,直接访问该Model的名字就可以了,反过来,就需要加上“_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.
是不是和一对多非常的像?
3. One-to-one relationships
一对一,和前两个也是非常像的,唯一不同的地方,还是在“反过来”,也就是由没有定义OneToOneField的那一方向定义了OneToOneField的那一方进行的查询,如:
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
好,最后说一点,在没有定义xxxToxxxField的那一方,是怎么知道都是有谁和它关联呢?也就是是在什么时候,它的Model里面多了一个xxx_set属性呢?答案就是任何一个Model呗加载的时候,Django会遍历INSTALLED_APPS中的所有APP的所有Model,根据xxxToxxxField来对应的给关联类对象创建xxx_set这样的Manager属性。
这样虽然有些暴力,但是使得Django程序看上去更简洁,没有无用的重复的代码了。
好,Model Query就介绍到这里,这里只是一个入门的流水而已,更加丰富的内容,还需要移步到Django的文档中去:
- https://docs.djangoproject.com/en/1.6/topics/db/queries/
- https://docs.djangoproject.com/en/1.6/ref/models/querysets/