Django 1.10中文文档-执行查询


Django 1.10中文文档: https://github.com/jhao104/django-chinese-doc

只要创建好 关键字参数 。

查询条件中指定的字段必须是模型字段的名称。但有一个例外,对于 OR查询示例 演示了几种 Q 的用法.

对象比较

使用双等号 == 比较两个对象,其实是比较两个模型主键的值。

使用上面的 Entry 示范, 下面两个表达式是等同的:

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

1.9开始删除方法才有返回值。

你还可以批量删除对象。每个 QuerySet 都有 delete() 方法,它作用是删除 QuerySet 中的所有成员。

例如, 下面语句删除所有 pub_date 为2005的 Entry

 

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

 

上面的过程是通过SQL实现的,并不是依次调用每个对象的 delete() 方法。 如果你给模型类提供了一个自定义的 delete() 方法,并且希望删除时方法被调用。 你需要”手动”调用实例的 delete()(例如:迭代 QuerySet 调用每个实例的 delete() 方法,使用 QuerySet 的 delete() 方法)。

当Django 删除一个对象时,它默认使用SQL ON DELETE CASCADE 约束 —— 换句话讲,任何有外键指向要删除对象的对象都将一起删除。 例如:

b = Blog.objects.get(pk=1)
# 这会删除该 Blog 和它所有的Entry对象
b.delete()

这种级联的行为可以通过 ForeignKey 的 on_delete 参数定义。

注意, delete() 是唯一没有在 Manager 上暴露出来的 QuerySet 方法。 这是一个安全机制来防止你意外地请求 Entry.objects.delete() , 而删除所有的条目。 如果你确实想删除所有的对象,你必须明确地请求一个完整的查询集:

Entry.objects.all().delete()

复制对象

没有内建的复制模型实例的方法,但可以通过创建一个新的实例并将它的所有字段都拷贝过来。最简单的方法是,只需要将 pk 设置成 None 。使用blog作为演示:

1 blog = Blog(name='My blog', tagline='Blogging is easy')
2 blog.save() # blog.pk == 1
3 
4 blog.pk = None
5 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设置一个多对多关联关系:

1 entry = Entry.objects.all()[0] # some previous entry
2 old_authors = entry.authors.all()
3 entry.pk = None
4 entry.save()
5 entry.authors.set(old_authors)

如果是 OneToOneField, 您必须复制相关联的对象并将其赋值给新对象的字段,避免出现复制后一对多的情况。 例如,假设 entry 已经是复制后的:

detail = EntryDetail.objects.all()[0]
detail.pk = None
detail.entry = entry
detail.save()

同时更新多个对象

可以对 QuerySet 中的所有对象修改该某个字段的值。这就需要使用 update() 方法,例如:

# 修改所有pub_date为2007的headlines
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

也可以对非关联字段和 ForeignKey 字段使用这个方法。若要更新一个非关联字段,只需提供一个新的常数值。 若要更新 ForeignKey 字段, 需设置新的值是你想指向的新的模型实例,例如:

>>> b = Blog.objects.get(pk=1)
# 修改所有的 Entry,使它们属于这个Blog
>>> Entry.objects.all().update(blog=b)

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

调用update也可以使用 F 表达式 来根据模型中的一个字段更新另外一个字段。 这种在当前值的基础上加另一个值时特别有用。例如Blog中每个Entry的pingback个数:

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

但是, 和filter和exclude子句中的 F() 对象不同,在update中不可以使用 F() 对象引入join – 只可以引用正在更新的模型的字段。如果使用 F() 对象引入了join,将引发一个 FieldError 错误:

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

关联对象

如果在模型中定义了关联关系 (例如, ForeignKeyOneToOneFieldManyToManyField), 那么该模型的实例将带有一些的API来访问关联的对象。

使用上面提到的模型,例如, 一个 Entry 对象 e 可以通过其 blog 属性: e.blog 来获取关联的 Blog对象。

Django 还提供了API 用于访问关联关系的另一头 – 从关联的模型访问定义关联关系的模型。 例如, Blog 对象 b 可以通过 entry_set 属性 b.entry_set.all() 来访问与它关联的所有 Entry 对象。

下面所有的例子都将使用前面定义的 Blog, Author 和 Entry 模型。

一对多关系

前向查询

如果模型具有一个 ForeignKey ,那么该模型的实例将可以通过属性访问关联的(外部)对象。

例如:

>>> e = Entry.objects.get(id=2)
>>> e.blog # 返回关联的Blog对象.

你可以通过外键属性查询和设置。修改外键并不会立即修改数据库,除非调用了 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) # 在数据库获取相关的Blog.
>>> print(e.blog) # 不会链接数据库;使用缓存.

QuerySet 的 select_related() 方法预先递归填充所有的一对多关系到缓存中。例如:

>>> e = Entry.objects.select_related().get(id=2)
>>> print(e.blog) # 不会链接数据库;使用缓存.
>>> print(e.blog) # 不会链接数据库;使用缓存.

反向查询

如果模型具有外键 ForeignKey ,那么该外键所指向的模型实例可以通过 Manager 返回包含某个特定外键的源模型的所有实例。 模型情况下,这个 Manager 叫做 FOO_setFOO 是源模型的小写名称。 该 Manager 返回的 QuerySets 同样可以使用过滤等操作。

例子:

>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # 返回所有关联b的Blog.
# b.entry_set is a Manager that returns QuerySets.
>>> b.entry_set.filter(headline__contains='Lennon')
>>> b.entry_set.count()

你可以在定义 ForeignKey 时 设置 related_name 参数来重写 FOO_set 名字。比如,如果 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()

自定义反向管理器

默认情况下,用于反向关联关系的 RelatedManager 是该模型 默认管理器 的子类。 如果您想为查询指定不同的管理器,那么可以使用以下语法:

1 from django.db import models
2 
3 class Entry(models.Model):
4     #...
5     objects = models.Manager()  # Default Manager
6     entries = EntryManager()    # Custom Manager
7 
8 b = Blog.objects.get(id=1)
9 b.entry_set(manager='entries').all()

如果 EntryManager 在 get_queryset() 方法中执行了默认的过滤,那么该过滤将应用于 all() 调用。

当然,指定一个自定义的管理器还可以让你调用自定义的方法:

b.entry_set(manager='entries').is_published()

处理关联对象的其它方法

除了在上面的“检索对象”中定义的 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() 方法不可用,那么将直接添加可迭代对象中的成员而不会删除所有已存在的对象。

这一节中提到的每个”反向“操作都会立即对数据库产生作用。每个添加、创建和删除操作都会立即并自动保存到数据库中。

多对多关系

多对多关系的两端都带有访问另一端的API。这些API的工作方式与上面提到的一对多关系一样。

唯一的区别在于属性的名称:定义ManyToManyField`的模型使用本字段的属性名称,而“反向”模型使用源模型的小写名称加上 `’_set’`` (和一对多关系一样)。

比如:

1 e = Entry.objects.get(id=3)
2 e.authors.all() # 返回 Entry e中的所有Author.
3 e.authors.count()
4 e.authors.filter(name__contains='John')
5 
6 a = Author.objects.get(id=5)
7 a.entry_set.all() # 返回Author a所有的Entry.

类似 ForeignKeyManyToManyField 可以指定 related_name 。在上面的例子中, 如果 Entry 中的 ManyToManyField 指定 related_name='entries', 那么 Author 实例将使用 entries 属性而不是 entry_set 。

一对一关系

一对一关系与多对一关系非常相似。如果你在模型中定义一个 OneToOneField , 该模型的实例将可以通过该模型的一个简单属性访问关联的模型。

例如:

1 class EntryDetail(models.Model):
2     entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
3     details = models.TextField()
4 
5 ed = EntryDetail.objects.get(id=2)
6 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

反向的关联关系如何实现?

其它对象关系映射要求你在关联关系的两端都要定义。Django 的开发人员相信这是对DRY(不要重复你自己的代码)原则的违背, 所以Django 只要求你在一端定义关联关系。

但是,考虑到直到其他模型类被加载,模型类不知道其他模型类与之相关,这是怎么实现的呢?

答案在 app registry 中。当Django 启动时,它导入 INSTALLED_APPS 中列出的每个应用,然后导入每个应用中的 models 模块。 每创建一个新的模型时,Django 添加反向的关系到所有关联的模型。 如果关联的模型还没有导入,Django 将保存关联关系的记录并在最终关联的模型导入时添加这些关联关系。

由于这个原因,你使用的所有模型都定义在 INSTALLED_APPS 列出的应用中就显得特别重要。 否则,反向的关联关系将不能正常工作。

查询相关对象

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

例如,如果你有一个 id=5 的Blog 对象 b ,下面的三个查询将是完全一样的l:

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

使用原始 SQL

如果你发现自己需要编写一个对非常复杂的SQL查询(Django API不好实现),那么你就可以手动写SQL了。 Django有几个选择来编写原始的SQL查询;参见 Performing raw SQL queries 。

最后,值得注意的是Django 的数据库层只是数据库的一个接口。你可以利用其它工具、编程语言或数据库框架来访问数据库; 你的数据库并不需要迎合django的任何东西。

相关