django

Django 1.6 Models (III) — QuerySet

这个是一个相当实用也十分必须的部分;

从之前裸 sql 阶段过渡到 Models orm 的阶段必经之路,如何使用 orm 来实现各种查询,本节内容就是这里的重点。

1. Manager 与 QuerySet

每个模型都有至少一个 Manager,QuerySet 由 Manager 产生;一个模型至少有一个 Manager。通过模型类(而不是对象)来调用 Manager。

2. QuerySet 的特性

独立性:每个 queryset 对象都是唯一的,在 QuerySet 上面进行操作不会影响原来的 queryset;

惰性:queryset 在其估值 (evaluate) 之前都不会执行数据库查询;

可以理解为每个 queryset 对象本质上就是一个 sql 语句,通过一些筛选条件,可以变更这个 sql 脚本,但是只有在对 queryset 估值的时候才会触发数据库操作;

queryset 会在以下场合被“估值”:

  • 进行迭代
  • 切片(使用下标运算符)
  • pickle 或者 cache 一个 queryset
  • 调用 repr(), list(), len(), bool()函数

3. filter 和 exclude

filter 相当于在 sql 查询里面添加 where 条件子句;

exclude 相当于加一个 not(…) 的 where 条件;

4. get() 获取对象

如果使用 get 在 queryset 上获取对象,必须保证 queryset 有且仅有一个对象,如果没有对象,get() 会抛出 Entry.DoesNotExist 异常;如果有多个对象,则抛出 MultipleObjectsReturned 对象。

也可以使用 get_or_create 来自动创建或者获取一个对象:

obj, created = Person.objects.get_or_create(first_name='John', last_name='Lennon', defaults={'birthday': date(1940, 10, 9)})

5. 切片操作

使用 [begin:end:step] 格式的下标运算符在 queryset 上面,会触发一个切片操作;

如果是一参数切片 [begin],则等同于 get 一个对象;

如果是两参数切片[begin:end],则等同于在 sql 加上 limit 语句,返回一个 queryset,这时还不会对 queryset 求值。

如果还指定了 step,就会触发 queryset 的求值。

6. 使用 F() 类引用其他字段取值

from django.db.models import F

调入 F 类,然后通过 F 类传入一个字段名构造可以生成一个对其他字段的引用。

7. pk 字段

由于在 models 主键是可以修改的(即不用原来的自动生成 id),然后使用 filter(pk=…) 这种方式可以直接隐式指定实际的主键列,也可以跟其他条件串联,譬如 pk__in, pk__gt 等等。

8. 下划线(_) 和百分号(%) 自动转义

使用 __contains 或者 __startswith 或者 __endswith 函数时,输入的字符串中,如果含有下划线或者百分号,则会被系统自动转义。即代表实际的符号,儿不具备通配符功能。

9. queryset 对象的缓存

对于同一个 queryset,如果它先被求值一次,然后再次被求值的时候假如使用的内容可以通用(意思是没有加入类似切片或者排序、筛选等的功能,例如是多次遍历),则该 queryset 内容会被缓存,即沿用上一次数据库查询的结果,而不是重新执行一次查询。

10. 追溯关系

可以使用双下划线连接符来将查询字段转移到与当前模型关联的其他模型,然后用其他模型的筛选条件来进行查询。

这种关系的追溯可以应用在各种对应关系(一对一,多对一,多对多)中。

在一对多和多对多关系的另一端会生成一个反向引用字段,默认名称是 model_rel,也可以通过对应的外键字段的 related_name 来指定 反向引用字段的名称。

如果一个引用是一个集合,则其上可以通过 add, create, remove, clear 方法来添加或者删除引用对象,而且其对应的外键会被自动填充,不需要显式指定。

https://docs.djangoproject.com/en/1.6/ref/models/relations/

也可以直接向这个引用集合赋值,给出一个列表或者元组,每个元素包含了直接引用的对象或者仅仅是对象的主键即可。

对关联对象字段的查询,可以直接给出对象,也可以用其主键代替。

11. 使用 Q() 对象实现组合查询

此前查询的条件,如果查询的条件需要使用到 or 或者各种 not 等等组合,则可以使用 Q() 对象来灵活组合各个子句。

from django.db.models import Q

然后 Q(cond1=…, cond2=…) 这样的形式可以构造一个查询条件语句。不同的 Q 之间可以通过位运算符“|”或者“~”等等进行组合,然后一个条件参数表可以在位置参数里面传入任意个 Q 对象,他们之间是 and 的关系。

12. 使用 delete() 和 update()

注意如果在 queryset 上面执行 delete,整个删除动作会用一个 sql 语句批量执行,而每个对象的 delete() 函数动作不会被触发;

同理,如果对结果集调用 update(),也是批量处理,不会触发 save() 函数。

如果需要触发每个对象的 delete 或者 save 函数,应该显式遍历这些对象然后调用对象的 delete 和 update 方法。

13. 对象的比较

如果执行 model1 == model2,返回的会仅仅是比较两个 model 对象的 pk 主键值。

14. 对象的复制

可以生成一个对象,然后将其 pk 改为 null(如果是派生对象,其 id 也要改成 null),然后调用 save() 即可完成复制。

15. 使用 select_related() 函数缓存查询

如果在现有的 queryset 上面调用 select_related(),可以返回另一个已经缓存所有级联的外键的 queryset,在这个 queryset 上面调用关联的对象不需要重新查询数据库。这样可以优化查询性能。

在 select_related 方法里面可以传入若干个字符串作为定位参数,用于指定缓存关联到的模型,在此列表之外的关联模型则不会被缓存。

16. 如何为 query_set 上面的批量操作注册事件

参考第 12 条:

假设一个场景:

class A(models.Model): pass

class B(models.Model): a = models.OneToOneField(A)

def save(self, *args, **kwargs):
    if self.pk is None:
        self.a = A.objects.create()
    super(B, self).save(*args, **kwargs)</code>

好了,在这个时候,我们在 B 里面加了一个 OneToOneField 指向 A,并且可以在 B 创建的时候自动创建一个 A 对象;

这时候如果删除 A 的对象,那么关联的 B 对象也会被级联删除;但是如果我们想要删除 B,A 要自动删除的时候恐怕有问题;

如果我们简单重载 B.delete 方法,那么在批量处理的时候就会有问题,这里我们要用到一个比较高级的方法来处理:Django 信号。

参考 stackoverflow 里面的这个解答,这里面只给出代码,不做解释了:

# 在 B 定义之后加上 from django.db import models from django.dispatch import receiver

@receiver(models.signals.post_delete, sender=B) def action_after_deletion_of_b(sender, instance, *arg, **kwargs): instance.a.delete()


【转载请附】愿以此功德,回向 >>

原文链接:https://www.huangwenchao.com.cn/2014/03/django-models-3.html【Django 1.6 Models (III) — QuerySet】

发表评论

电子邮件地址不会被公开。 必填项已用*标注