|
接下来,用这个变更过的模型创建一些示例文档,这些文档均已分配了 Format 实例。
清单 6. 创建具有指定格式的文档
# First create a series of Format objects and save them to the database format_text = Format.objects.create(type='text') format_epub = Format.objects.create(type='epub') format_html = Format.objects.create(type='html') # Create a few documents in various formats for i in range(0, 10): Document.objects.create(name='My text document', format=format_text) Document.objects.create(name='My epub document', format=format_epub) Document.objects.create(name='My HTML document', format=format_html) |
假设这个应用程序提供了一种方法来按格式对文档进行首次过滤,然后再按其他字段(如标题)对 QuerySet 进行过滤。那么一个只返回文本文档的示例查询就可以是:Document.objects.filter(format=format_text)。
在这个示例中,查询的含义很清楚,但在一个成熟的应用程序中,往往还需要对结果集应用更多的限制。比如,只想让结果集中出现标记了 public 的那些文档或是那些 30 天以内的文档。若需要从应用程序中的多个位置调用这个查询,那么要让所有这些过滤子句保持同步将是一件很头疼的事,并且会引发很多的 bug。
这时就需要借助定制管理器。定制管理器提供了定义无限量封装(canned)查询的能力 — 这一点类似于内置管理器方法,例如 latest()(它仅返回给定模型的一个最新实例)或 distinct()(它在所生成的查询中发出一个 SELECT DISTINCT 子句)。这些查询可以减少在应用程序中需要复制的代码量,管理器则提高了可读性。在实际使用中,相信您一定不会愿意阅读如下所示的内容:
Documents.objects.filter(format=format_text,publish_on__week_day=todays_week_day, is_public=True).distinct().order_by(date_added).reverse() |
而会觉得下面的代码对于您或是新的开发人员更好理解:
Documents.home_page.all() |
创建一个定制管理器非常简单。清单 7 给出了 get_by_format 示例。
清单 7. 能为每个格式类型提供方法的定制管理器类
from django.db import models
class DocumentManager(models.Manager):
# The model class for this manager is always available as
# self.model, but in this example we are only relying on the
# filter() method inherited from models.Manager.
def text_format(self):
return self.filter(format__type='text')
def epub_format(self):
return self.filter(format__type='epub')
def html_format(self):
return self.filter(format__type='html')
class Document(models.Model):
name = models.CharField(max_length=255)
format = models.ForeignKey('Format')
# The new model manager
get_by_format = DocumentManager()
# The default model manager now needs to be explicitly defined
objects = models.Manager()
class Comment(models.Model):
document = models.ForeignKey(Document, related_name='comments')
content = models.TextField()
class Format(models.Model):
type = models.CharField(choices=( ('Text file', 'text'),
('ePub ebook', 'epub'),
('HTML file', 'html')),
max_length=10)
def __unicode__(self):
return self.type
|
关于这个代码的一些解释:
- 如果您定义一个定制管理器,那么 Django 将会自动删除默认管理器。但我更倾向于同时保留默认管理器和定制管理器,以便其他开发人员(或我自已)仍可继续使用 objects,并且它仍会严格 地如我们预期的那样工作。然而,由于我的这个新 get_by_format 管理器只是 Django models.Manager 的一个子类,因此,所有的默认方法,比如 all(),对于它来说都是可用的。是否在包括定制管理器的同时还包括默认管理器,这就取决于您的个人喜好了。
- 将新管理器直接指定给 objects 也是可以的。惟一的缺点就是在想要覆盖初始的 QuerySet itself 的时候,新的 objects 就会有一个出乎其他开发人员意料之外的行为。
- 在定义模型类之前,需要在 models.py 内先定义管理器类,否则 Django 将不能用这个类。这与对 ForeignKey 类引用的限制很相似。
- 我本可以简单地用一个能接受参数的方法(如 with_format(format_name))实现 DocumentManager。但通常我更倾向于使用管理器方法,这些方法的名字虽然有些长,但它们均不接受参数。
- 对于可以指定给某个类的定制管理器的数量通常没有技术上的限制,但有一到两个就已经可以满足您的需要了。
使用新的管理器方法非常简单。
In [1]: [d.format for d in Document.get_by_format.text_format()][0] Out[1]: <Format: text> In [2]: [d.format for d in Document.get_by_format.epub_format()][0] Out[2]: <Format: epub> In [3]: [d.format for d in Document.get_by_format.html_format()][0] Out[3]: <Format: html> |
现在,有一个方便的位置可以用来放置与这些查询相关的任何功能,并且还可以在不打乱代码的情况下应用额外的限制。将这种功能放入 models.py 而不是将它胡乱地丢入视图或模板标记也符合 Django model-view-controller(MVC)的一贯精神。
覆盖定制管理器所返回的初始 QuerySet
另一个适用于管理器类的编码模式可以不涉及任何定制方法。例如,您不必去定义一个只返回 HTML 格式文档的新方法,相反,您可以定义一个完全 运行在该限制集之上的定制管理器,如下面的示例所示。
清单 8. 针对 HTML 文档的定制管理器
class HTMLManager(models.Manager):
def get_query_set(self):
return super(HTMLManager, self).get_query_set().filter(format__type='html')
class Document(models.Model):
name = models.CharField(max_length=255)
format = models.ForeignKey('Format')
html = HTMLManager()
get_by_format = DocumentManager()
objects = models.Manager()
|
get_query_set() 方法继承自 models.Manager,并在本示例中被覆盖以接受这个基础查询(与 all() 所生成的相同),并为其应用了一个额外的过滤。添加到这个管理器的所有后续方法都要首先调用 get_query_set() 方法,然后才能在该结果之上再应用其他的查询方法,如下所示。
清单 9. 使用这个定制格式管理器
# Our HTML query returns the same number of results as the manager # which explicitly filters the result set. In [1]: Document.html.all().count() Out[1]: 10 In [2]: Document.get_by_format.html_format().count() Out[2]: 10 # In fact we can prove that they return exactly the same results In [3]: [d.id for d in Document.get_by_format.html_format()] == [d.id for d in Document.html.all()] Out[3]: True # It is not longer possible to operate on the unfiltered # query in HTMLManager() In [4]: Document.html.filter(format__type='epub') Out[4]: [] |