首页 > Python基础教程 >
-
建立一个更高级别的查询 API:正确使用Django ORM 的方式(2)
注意:这里可以写成request.user.todo_set.filter(is_done=False, priority=1)。但是这里只是一个实验。
为什么这样写不好呢?
首先,代码冗长。七行代码才能完成,正式的项目中,将会更加复杂。
其次,泄露实现细节。比如代码中的is_done是BooleanField,如果改变了他的类型,代码就不能用了。
然后就是,意图不清晰,很难理解。
最后,使用中会有重复。例:你需要写一行命令,通过cron,每周发送给所有用户一个todo list,这时候你就需要复制-粘贴着七行代码。这不符合DRY(do not repeat yourself)
让我们大胆的猜测一下:直接使用低等级的ORM代码是反模式的。
如何改进呢?
使用 Managers 和 QuerySets
首先,让我们先了解一下概念。
Django 有两个关系密切的与表级别操作相关的构图:managers 和 querysets
manager(django.db.models.manager.Manager的一个实例)被描述成 “通过查询数据库提供给Django的插件”。Manager是表级别功能的通往ORM大门。每一个model都有一个默认的manager,叫做objects。
Quesyset (django.db.models.query.QuerySet) 是“数据库中objects的集合”。本质上是一个SELECT查询,也可以使用过滤,排序等(filtered,ordered),来限制或者修改查询到的数据。用来 创建或操纵 django.db.models.sql.query.Query实例,然后通过数据库后端在真正的SQL中查询。
啊?你还不明白?
随着你慢慢深入的了解ORM,你就会明白Manager和QuerySet之间的区别了。
人们会被所熟知的Manager接口搞糊涂,因为他并不是看上去那样。
Manager接口就是个谎言。
QuerySet方法是可链接的。每一次调用QuerySet的方法(如:filter)都会返回一个复制的queryset等待下一次的调用。这也是Django ORM 流畅之美的一部分。
但是当Model.objects 是一个 Manager时,就出现问题了。我们需要调用objects作为开始,然后链接到结果的QuerySet上去。
那么Django又是如何解决呢?
接口的谎言由此暴露,所有的QuerySet 方法基于Manager。在这个方法中,通过
让我们立刻回到todo list ,解决query接口的问题。Django推荐的方法是自定义Manager子类,并加在models中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
self .get_query_set()的代理,重新创建一个QuerySet。 class Manager( object ): # SNIP some housekeeping stuff.. def get_query_set( self ): return QuerySet( self .model, using = self ._db) def all ( self ): return self .get_query_set() def count( self ): return self .get_query_set().count() def filter ( self , * args, * * kwargs): return self .get_query_set(). filter ( * args, * * kwargs) # and so on for 100+ lines... |
你也可以在model中增加多个managers,或者重新定义objects,也可以维持单个的manager,增加自定义方法。
下面让我们实验一下这几种方法:
方法1:多managers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class IncompleteTodoManager(models.Manager): def get_query_set( self ): return super (TodoManager, self ).get_query_set(). filter (is_done = False ) class HighPriorityTodoManager(models.Manager): def get_query_set( self ): return super (TodoManager, self ).get_query_set(). filter (priority = 1 ) class Todo(models.Model): content = models.CharField(max_length = 100 ) # other fields go here.. objects = models.Manager() # the default manager # attach our custom managers: incomplete = models.IncompleteTodoManager() high_priority = models.HighPriorityTodoManager() |