django学习之Model(五)MakingQuery

接着上篇。

10-一次更新多个对象

有时想要对QuerySet中的所有对象的某一个field来设定一个值,这时候可以像下边这样用update():

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

这样使用的update()只能是没有关联关系的model或者有ForeignKey的model。如果是有ForeignKey的话,像下边这样用:

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

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

update()会立即生效并返回被query匹配的行的个数。但是如果有的行已经有了新的值,则可能与update()返回的个数不相等。QuerySet的update()只有一个限制条件,就是只能作用于一个数据表(database table),也就是model的主table。可以根据相关联的field来过滤一个query,但是只能update model的主表中的columns。例如:

>>> 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语句,也可以做批量的update。不会进行任何save()操作的,也不会发出pre_save()或者post_save()的信号(这个信号是调用save()方法的顺序),也不会用auto_now这个field选项的。如果想要对QuerySet的每一个实例都使用save()方法的话,只要遍历这个QuerySet,用for循环就行:

for item in my_queryset:
    item.save()

用update()方法时,也可以用F()对象来进行更新,根据此model中的另一个field值。尤其是当增加技术的时候很好用,下例就是一个增加pingback计数的用法:

>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

然而,与在filter中的F()对象不同的是,不能对一个在update中的F()使用joins功能,只能引用要更新的model中的field,如果师徒对一个F()对象使用jions,会有FieldError错误:

# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))

11-相关的对象

在一个model中定义了一个关系时(例如,ForeignKey, OneToOneField, ManyToManyField),这个model的实例会有很方便的API来接触相关对象。像前边讲到的例子,一个Entry的对象e,可以e.blog来连接Blog的对象blog。背后其实用到的是python的语法。

对于这些关联关系的另一面来说,django也会有一些其他的API。例如,Blog的对象blog可以连接到一系列的Entry的对象,通过方法entry_set,像这样:b.entry_set.all()。

下边的所有例子都是用到了前文提到的Blog, Author, Entry。

1)-一对多关系

如果一个model有一个ForeignKey, 这个model的实例就会有连接他的foreign关系的对象的接口:

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

上边代码,Entry中有一个ForeignKey指向Blog,所以,blog可以看作是Entry的一个成员,类似于对象的成员这样调用。

当然,只有save()的时候才能保存,这个是一直在强调的:

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

如果ForeignKey的field中有一个参数null=True,那么可以分配个None值来解除这个Foreign关系:

>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"

当第一次接触到一个对象的database的时候,取到的值会被缓存下来,接下来如果还用到的话,就不用去接触database了,而是直接从缓存中取数据,这是在one-to-many这样的关系时的情况,记得前边讲到过,其他情况下,这样是不会产生缓存的,还是会第二次hit the database:

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

2)-把这种关系反过来

Entry中有一个ForeignKey关系到Blog,上边的例子是e.blog,那如果已知b是Blog的对象,对应的b.entry会是什么呢?实际上,是会返回所有的entry的对象的一个QuerySet,这个方法市一中Manager,但要明确并不是b.entry这样写,而是下面例子中的F00_set,其中F00是含有ForeignKey的model的实例(例如entry),注意是小写的entry:

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

当然这个entry_set是可以重写的,如果定义了

blog = ForeignKey(Blog, 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()

除了在前文中提到的对象检索中的QuerySet的方法,ForeignKeyManager还有额外的方法来处理相关联的对象。完备的资料参考related objects reference

add(obj1, obj2, ...)

把指定的model的对象加到相关联的model的对象中。(应该是可以把entry加到blog中)

create(**kwargs)

创建一个新的对象,保存并把放进相关联的对象中。返回新创建的对象。

clear()

把相关联的对象中的所有对象都移除。

对相关联的对象一次添加多个对象,可以像下面这样写:

b = Blog.objects.get(id=1)
b.entry_set = [e1, e2]

e1和e2可以使完整的Entry的实例,也可以是整数型的pirmary key的值。

如果有clear()方法,那么在entry_set添加新的对象之前,之前的那些对象都会被移除,如果没有clear()方法,那么添加entry_set不回移除之前的对象。

这一段讨论的检索方法,都会立即生效于database,每个例子中的添加和删除都会自动save,所以不用再调用save方法了。

3)-Many-to-Many关联

这种关联的两头的对象都有自动的API,来连接另一端,也就是可以理解为关联的两头是地位相等的,对称的。这个API就像上面提到的one-to-many时反过来的那个  ”多到一“  的那样操作。唯一的区别是参数的名字上:定义了ManyToManyField的model用自己的field的名字,关联另一端的那个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.

当然,Entry中定义了ManyToManyField,Author是此关联的另一端,所以a.entry_set.all()。

就像ForeignKey, ManyToManyField可以定义一个related_name,上边的例子中也可以定义Entry中的related_name='entries',则entry_set就可以用entries代替了。

4)-One-to-one关联

这种关联与many-to-many关联类似,如果在一个model中定义了OneToOneField,这个model就可以用简单的方法来连接到相关联的那个model:

class EntryDetail(models.Model):
    entry = models.OneToOneField(Entry)
    details = models.TextField()

ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.

不同的“反过来”的query,one-to-one关联中的被关联的model也有一种Manager,只不过这个Manager返回的只是一个对象,而不是一些对象,很好理解,因为one-to-one关系嘛:

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

如果关联的model没有对象的话,会有错误DoesNotExist。

这种“反过来”也可以给对象赋值为一个对象,就像顺序关联的时候那样:

e.entrydetail = ed

5)-为什么“反过来”的关联是可能的呢?

别的对象关联图要求必须在关联的两端都要定义这种关联关系。django开发者认为i这是一种对DRY(Don't Repeat Yourself)准则的践踏(- -!),所以django只要求在一端来定义关联关系就可以了。

想像一下,凭感觉来讲,在的django中,貌似是直到别的model class被加载的时候,这个定义了关联关系的model class才知道自己关联到了哪里,也就是说,其实之前定义了关联的model class是不知道自己要被关联到哪里的,按道理说这怎么可能实现呢?

其实答案就在settings.py文件中INSTALLED_APPS中,当每个model被第一次加载的时候,django会在INSTALLED_APPS中列出来这些model,然后在内存中记录下“反过来”的关联,来备用。基本的,INSTALLED_APPS的一个作用就是告诉django整个model的控制器。

6)-相关联的对象的查询

包含相关联的对象的查询与普通的查询是一样的,有可以用对象的实例来作为filter条件,也可以用primary key。例如,Blog的对象是blog,其id=5,则如下:

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

 

最后-不妨看看原生的SQL

如果发现写一个SQL的query来给django来用太麻烦,不妨自己手写一个SQL代码段,django也提供了一些选项可以写原生的SQL语句,可以参考 Performing raw SQL queries.

特别要注意的是,django的databse的表层(layer)只不过仅仅是对于你的database的一个接口,一个界面(interface),除了django当然也可以用其他的方法来处理database。

MakingQuery到此结束,感觉囫囵吞枣的看来一遍,还有很多地方只能是猜到大概的意思,理解不深,对于SQL需要更多的学习,对于实战经验也需要更多的积累。

posted @ 2014-08-20 23:54 Runbbit 阅读(...) 评论(...) 编辑 收藏