更好的 Django 模型 在 Django V1.1 内使用模型管理器(2)

来源:developerWorks 中国 作者:Liza Daly
  
数据库更新的最佳实践

任何时候在向 models.py 中添加表或列时,都需要重新同步相关数据库。以下是数据库更新的几个最佳实践:

  • 在开发的早期,只使用内存数据库,如 sqlite3,并采用数据库的固有功能来自动加载示例内容。内存数据库对单一用户速度很快,并能大量减少在传统的 RDBMS(如 MySQL)中丢弃或重建表时的等待时间。
  • 采用测试驱动的开发方式。Django 的测试框架每次都会从头重建数据库,所以表总是最新的。将这个功能与 sqlite3 内存数据库相结合就可以使测试更快。
  • 试试众多管理数据库同步的 Django 附加软件。虽然我已经有很多 django-evolution 程序包的经验,但除此之外还有很多其他的程序包值得一试。关于 django-evolution 的更多信息,请参见 参考资料。

若在开发或测试中选择了使用 sqlite3,请确保对产品数据库执行最终的集成测试。在大多数情况下,Django 的 ORM 可以帮助消除 RDBMS 引擎间的差异,但我们并不保证所有的行为都是一样的。

接下来,用这个变更过的模型创建一些示例文档,这些文档均已分配了 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]: []

时间:2009-06-19 11:08 来源:developerWorks 中国 作者:Liza Daly 原文链接

好文,顶一下
(4)
80%
文章真差,踩一下
(1)
20%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量