




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、在DjangoV1.1内使用模型管理器developerWorks文档选项将打印机的版面设置成横向打印模式打印本页将此页作为电子邮件发送将此页作为电子邮件发送英文原文英文原文级别:中级LizaDaly,软件工程师和所有人,ThreepressConsultingInc.2009年6月18日花5分钟学习wiki,然后进入实际的应用程序编程,Django程序员很容易创建让人迷惑、难于维护或低效的模型类。在本文中,了解如何避免一些常见的查询错误、如何使用模型管理器来封装复杂查询以及如何充分利用DjangoV1.1强大的聚集特性。在Django内,与数据库的大多数交互都通过对象关系映射器(ORM),这
2、个特性是Django与其他最新的Web框架(比如Rails)所共有的。ORM越来越受开发人员欢迎,因为ORM能够自动化与数据库的很多常见交互,而且会使用为人熟知的面向对象方式,而不是SQL语句。Django程序员可能会选择绕过原生ORM,而选择流行的SQLAlchemy包,虽然SQLAlchemy十分强大,但是却较难使用,而且需要更多的代码行。虽然有些Django应用程序是使用SQLAlchemy而非原生ORM开发的,但是Django最吸引人的一些特性,比如其自动生成的管理界面,都要求使用ORM。常用缩写词* API:应用程序编程接口(Applicationprogramminginterfa
3、ce)* HTML:超文本标记语言(HypertextMarkupLanguage)* RDBMS:关系数据库管理系统(Relationaldatabasemanagementsystemi)* SQL:结构化查询语言(StructuredQueryLanguage)本文着重阐释了DjangoORM的一些不为人熟知的特性,此外,本文还为SQLAlchemy的用户提供了一些有关低效查询生成的告诫,这对其编码很有帮助。本文中使用的软件版本包括:* DjangoV1.0.2(第1和第2部分)* DjangoV1.1alpha(第3部分)* sqlite3* PythonV2.4-2.6(Django
4、尚不支持PythonV3)* IPython(针对示例输出)DjangoORM支持很多数据库后端,但sqlite3最易于安装,并且常常与操作系统捆绑。本文中的例子应该能与任何后端协作。要想获得Django支持的数据库的完整列表,请参见参考资料。避免ORM查询生成中的常见陷阱Django的设计支持敏捷开发的风格,因此能快速进行原型化和实验。在开始阶段,最好不要过于担心性能,而是要关注可读性和实现的简便性。有时,发现性能问题并不需要太长时间。通常在初次用实际数据试用应用程序时,很容易发现性能问题。有时,若只包含几个测试的测试套件的执行时间超过了5分钟的界限,这就表明存在性能问题。有时,应用程序运行
5、过慢,也表示性能问题的存在。所幸的是,现在已经有了一些很容易识别的模式,这些模式亦很容易修复。清单1(应用程序的models.py文件)和清单2给出了一个很常见的例子。清单1.示例应用程序的基本模型:models.pyfromdjango.dbimportmodels# Somekindofdocument,likeablogpostorawikipageclassDocument(models.Model):name=models.CharField(max_length=255)# Auser-generatedcomment,suchasfoundonasitelike# DiggorR
6、edditclassComment(models.Model):document=models.ForeignKey(Document,related_name='comments')content=models.TextField()有关代码示例pythonDjango提供了一种很方便的捷径来在独立的代码内设置一个工作环境:运行manage.pyshell。本文中所有的代码示例都预先假设工作环境均是以这种方式调用的。在Djangolingo内,本文中作了如下假设:* Django项目的名称是better_models。* better_models项目包含一个应用程序,名为e
7、xamples。示例应用程序所模拟的是一个类似blog的基本文档系统,这些文档可能没有任何注释,也可能有注释。清单2显示了如何以一种低效的方式访问清单1中所设置的那些模型。清单2.非常慢地访问那些模型fromexamples.modelimport*importuuid# Firstcreatealotofdocumentsandassignthemrandomnamesforiinrange(0,10000):Document.objects.create(name=str(uuid.uuid4()# Getaselectionofnamesbacktobelookeduplatername
8、s=Document.objects.values_list('name',flat=True)0:5000# ThereallyslowwaytogetbackalistofDocumentsthat# matchthesenamesdocuments=fornameinnames:documents.append(Document.objects.get(name=name)这虽然是一个人为的示例,却展示了一种非常常见的用例:给定一列标识符,从数据库获得对应于这些标识符的所有项目。当使用内存中的sqlite3时,上述示例代码的运行时间为65秒。如果是一个独立于文件系统的数据
9、库,运行所花时间可能更长。不过,清单3中也有针对这个运行缓慢的查询的一个补丁。与针对每个名称值发出多个数据库查询相反,使用fieldname_in操作符来生成一个SQL查询,如下所示:SELECT*FROMmodelWHEREfieldnameIN('1','2',.)(所生成的实际查询语法将会随数据库引擎而变化。)清单3.用来获得条目列表的快速查询fromexamplesimportmodelsimportuuidforiinrange(0,10000):Document.objects.create(name=str(uuid.uuid4()names=Do
10、cument.objects.values_list('name',flat=True)0:5000documents=list(Document.objects.filter(name_in=names)上述代码在3秒内即可执行。请注意此代码会将查询结果强制转型为一个列表,以强制对此查询求值。由于Django查询会被延迟求值,因此,简单的分配查询结果并不会引起对数据库的任何访问,亦使对比无效。习惯于编写原始SQL的数据库大师们会觉得本例十分直白,但是很多Python程序员并不具有数据库背景。有时,程序员的开发习惯往往有悖于效率。清单4给出了改进清单2中的代码的一种可能方式,这
11、种方式是程序员很有可能选择采用的,因为他们没有意识到这是个陷阱。清单4.一个会降低数据库使用效率的常见模型fornameinnames:documents.append(get_document_by_name(name)defget_document_by_name(name):returnDocument.objects.get(name=name)表面上看,创建一个用来从数据库检索文档的单独方法似乎是个不错的主意。但是这里还有其他一些工作要做,例如在返回前向模型中添加数据。请注意,对于这个模型,进行重构形成独立的方法看起来像是对代码的改进。在开发之初就编写单元测试并包括进一些针对大型数据
12、集的测试可以帮助我们识别重构所导致的性能骤降。回页首用管理器模型封装常见查询所有Django的开发人员都使用内置Manager类:表单Model.objects.*的所有方法,者B会调用此类。这个基础Manager类自动可用,并且提供常用的一些能够返回QuerySets的方法(例如,all()、返回值的方法(例如,count()及返回Model实例的方法(例如,get_or_create()。我们鼓励Django的开发人员覆盖这个基础Manager类。为了说明此特性的用处,我们对这个示例应用程序进行了扩展,为它添加了一个新模型Format,这个模型描述了此系统内文档的格式。下面是一个示例。清单
13、5.为示例添加一个模型fromdjango.dbimportmodelsclassDocument(models.Model):name=models.CharField(max_length=255)format=models.ForeignKey('Format')classComment(models.Model):document=models.ForeignKey(Document,related_name='comments')content=models.TextField()classFormat(models.Model):type=model
14、s.CharField(choices=('Textfile','text'),('ePubebook','epub'),('HTMLfile','html'),max_length=10)数据库更新的最佳实践任何时候在向models.py中添加表或列时,都需要重新同步相关数据库。以下是数据库更新的几个最佳实践:* 在开发的早期,只使用内存数据库,如sqlite3,并采用数据库的固有功能来自动加载示例内容。内存数据库对单一用户速度很快,并能大量减少在传统的RDBMS(如MySQL)中丢弃或重建表时的
15、等待时间。* 采用测试驱动的开发方式。Django的测试框架每次都会从头重建数据库,所以表总是最新的。将这个功能与sqlite3内存数据库相结合就可以使测试更快。* 试试众多管理数据库同步的Django附加软件。虽然我已经有很多django-evolution程序包的经验,但除此之外还有很多其他的程序包值得一试。关于django-evolution的更多信息,请参见参考资料。若在开发或测试中选择了使用sqlite3,请确保对产品数据库执行最终的集成测试。在大多数情况下,Django的ORM可以帮助消除RDBMS引擎间的差异,但我们并不保证所有的行为都是一样的。接下来,用这个变更过的模型创建一些
16、示例文档,这些文档均已分配了Format实例。清单6.创建具有指定格式的文档# FirstcreateaseriesofFormatobjectsandsavethemtothedatabaseformat_text=Format.objects.create(type='text')format_epub=Format.objects.create(type='epub')format_html=Format.objects.create(type='html')# Createafewdocumentsinvariousformatsfori
17、inrange(0,10):Document.objects.create(name='Mytextdocument',format=format_text)Document.objects.create(name='Myepubdocument',format=format_epub)Document.objects.create(name='MyHTMLdocument',format=format_html)假设这个应用程序提供了一种方法来按格式对文档进行首次过滤,然后再按其他字段(如标题)对QuerySet进行过滤。那么一个只返回文本文档
18、的示例查询就可以是:Document.objects.filter(format=format_text)。在这个示例中,查询的含义很清楚,但在一个成熟的应用程序中,往往还需要对结果集应用更多的限制。比如,只想让结果集中出现标记了public的那些文档或是那些30天以内的文档。若需要从应用程序中的多个位置调用这个查询,那么要让所有这些过滤子句保持同步将是一件很头疼的事,并且会引发很多的bug。这时就需要借助定制管理器。定制管理器提供了定义无限量封装(canned)查询的能力一这一点类似于内置管理器方法,例如latest()(它仅返回给定模型的一个最新实例)或distinct()(它在所生成的查
19、询中发出一个SELECTDISTINCT子句)。这些查询可以减少在应用程序中需要复制的代码量,管理器则提高了可读性。在实际使用中,相信您一定不会愿意阅读如下所示的内容: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_fo
20、rmat示例。清单7.能为每个格式类型提供方法的定制管理器类fromdjango.dbimportmodelsclassDocumentManager(models.Manager):# Themodelclassforthismanagerisalwaysavailableas# self.model,butinthisexampleweareonlyrelyingonthe# filter()methodinheritedfrommodels.Manager.deftext_format(self):returnself.filter(format_type='text')
21、defepub_format(self):returnself.filter(format_type='epub')defhtml_format(self):returnself.filter(format_type='html')classDocument(models.Model):name=models.CharField(max_length=255)format=models.ForeignKey('Format')# Thenewmodelmanagerget_by_format=DocumentManager()# Thedefau
22、ltmodelmanagernowneedstobeexplicitlydefinedobjects=models.Manager()classComment(models.Model):document=models.ForeignKey(Document,related_name='comments')content=models.TextField()classFormat(models.Model):type=models.CharField(choices=('Textfile','text'),('ePubebook'
23、,'epub'),('HTMLfile','html'),max_length=10)def_unicode_(self):returnself.type关于这个代码的一些解释:# 如果您定义一个定制管理器,那么Django将会自动删除默认管理器。但我更倾向于同时保留默认管理器和定制管理器,以便其他开发人员(或我自已)仍可继续使用objects,并且它仍会严格地如我们预期的那样工作。然而,由于我的这个新get_by_format管理器只是Djangomodels.Manager的一个子类,因此,所有的默认方法,比如all(),对于它来说都是可用的
24、。是否在包括定制管理器的同时还包括默认管理器,这就取决于您的个人喜好了。# 将新管理器直接指定给objects也是可以的。惟一的缺点就是在想要覆盖初始的QuerySetitself的时候,新的objects就会有一个出乎其他开发人员意料之外的行为。# 在定义模型类之前,需要在models.py内先定义管理器类,否则Django将不能用这个类。这与对ForeignKey类引用的限制很相似。# 我本可以简单地用一个能接受参数的方法(如with_format(format_name)实现DocumentManager。但通常我更倾向于使用管理器方法,这些方法的名字虽然有些长,但它们均不接受参数。#
25、对于可以指定给某个类的定制管理器的数量通常没有技术上的限制,但有一到两个就已经可以满足您的需要了。使用新的管理器方法非常简单。In1:d.formatfordinDocument.get_by_format.text_format()0Out1:<Format:text>In2:d.formatfordinDocument.get_by_format.epub_format()0Out2:<Format:epub>In3:d.formatfordinDocument.get_by_format.html_format()0Out3:Format:html>现在,有
26、一个方便的位置可以用来放置与这些查询相关的任何功能,并且还可以在不打乱代码的情况下应用额外的限制。将这种功能放入models.py而不是将它胡乱地丢入视图或模板标记也符合Djangomodel-view-controller(MVC)的一贯精神。覆盖定制管理器所返回的初始QuerySet另一个适用于管理器类的编码模式可以不涉及任何定制方法。例如,您不必去定义一个只返回HTML格式文档的新方法,相反,您可以定义一个完全运行在该限制集之上的定制管理器,如下面的示例所示。清单8.针对HTML文档的定制管理器classHTMLManager(models.Manager):defget_query_s
27、et(self):returnsuper(HTMLManager,self).get_query_set().filter(format_type='html')classDocument(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.Mana
28、ger,并在本示例中被覆盖以接受这个基础查询(all()所生成的相同),并为其应用了一个额外的过滤。添加到这个管理器的所有后续方法都要首先调用get_query_set()方法,然后才能在该结果之上再应用其他的查询方法,如下所示。清单9.使用这个定制格式管理器# OurHTMLqueryreturnsthesamenumberofresultsasthemanager# whichexplicitlyfilterstheresultset.In1:Document.html.all().count()Out1:10In2:Document.get_by_format.html_format()
29、.count()Out2:10# InfactwecanprovethattheyreturnexactlythesameresultsIn3:d.idfordinDocument.get_by_format.html_format()=d.idfordinDocument.html.all()Out3:True# Itisnotlongerpossibletooperateontheunfiltered# queryinHTMLManager()In4:Document.html.filter(format_type='epub')Out4:若在您数据的子集上需要进行很多操作
30、同时还希望减少代码量及需要生成的查询的复杂性时,可以考虑使用这个基于类的方法来过滤查询。为模型使用类和静态方法QuerySet,也QuerySets。为管理器所能添加的方法的类型是没有限制的。如前面所示,方法可以返回可以返回相关模型类的实例(比如self.model)。在有些情况下,您可能希望执行一些与模型相关的操作,但又不能返回实例或Django文档指出所有非模型类实例上的方法都应在管理器中,但还有一个可能性就是使用Python类和静态方法。如下所示是一个实用方法的简单示例,这个方法与Format类有关,与具体某个实例无关。# Returnthecanonicalnameforaformat
31、extensionbasedonsome# commonvaluesthatmightbeseen"inthewild"defcheck_extension(extension):ifextension='text'orextension='txt'orextension='.csv':return'text'ifextension.lower()='epub'orextension='zip':return'epub'if'htm'inexten
32、sion:return'html'raiseException('Didnotgetknownextension')上述代码并不接受或返回Format类的实例,所以把它作为实例方法并不恰当。也可以把它添加给FormatManager,但是由于它根本不能访问数据库,所以把它放在那里也不太合适。一个解决办法就是把它添加给Format类并用staticmethod修饰符把它声明为一个静态方法,如下所示。清单10.作为模型类上的静态方法添加一个实用函数classFormat(models.Model):type=models.CharField(choices=(
33、9;Textfile','text'),('ePubebook','epub'),('HTMLfile','html'),max_length=10)staticmethoddefcheck_extension(extension):ifextension='text'orextension='txt'orextension='.csv':return'text'ifextension.lower()='epub'orexten
34、sion='zip':return'epub'if'htm'inextension:return'html'raiseException('Didnotgetknownextension')def_unicode_(self):returnself.type这个方法可被称为Format.check_extension(extension),它既不需要Format实例,也不需要创建一个管理器。Python还提供了classmethod修饰符,它能基于类生成方法,并且第一个参数就是类本身。如果想要在不实例化的情况下执行
35、类对象本身上的某种自查(introspection),这一点会很有用。回页首DjangoV1.1中的聚集查询在2009年4月发布的V1.1中,Django的ORM包括了很多功能强大的查询方法,这些方法所提供的功能以前只有通过原始的SQL才可用。对于对SQL心存戒心的Python开发人员一以及任何希望他/她的Django应用程序能跨多个数据库引擎可用的人而言,这的确是个福音。在当今根据需求而不断调整而成的应用程序中,通常不仅需要能依常规的字段,如字母顺序或创建日期,来对项目进行排序,还需要按其他某种动态数据对项目进行排序。例如,在示例应用程序中,您可能需要按受欢迎程度对文档进行排序,也就是基于每
36、个文档的注释的数量进行排列。在DjangoV1.1发布之前,往往需要编写一些定制SQL代码,才能实现这个功能,结果,所创建的存储过程不可移植,或一最糟的一编写的面向对象的查询十分低效。另一种方法就是定义一个dummy数据库字段,其中包含用来计数的理想值(例如,注释行的数量)并通过覆盖文档的save()方法手动更新它。Django聚合排除了所有上述需求。现在仅用一个QuerySet方法(annotate。)就可以实现对文档按注释的数量进行排序。清单11提供了一个示例。清单11.使用聚合对结果按注释的数量进行排序fromdjango.db.modelsimportCount# Createsome
37、sampleDocumentsunpopular=Document.objects.create(name='Unpopulardocument',format=format_html)popular=Document.objects.create(name='Populardocument',format=format_html)# Assignmorecommentsto"popular"thanto"unpopular"foriinrange(0,10):Comment.objects.create(document
38、=popular)foriinrange(0,5):Comment.objects.create(document=unpopular)# Ifwereturnresultsintheordertheywerecreated(idorder,bydefault),weget#the"unpopular"documentfirst.In1:Document.objects.all()Out1:Document:Unpopulardocument,Document:Populardocument# Ifweinsteadannotatetheresultsetwiththeto
39、talnumberof# commentsoneachDocumentandthenorderbythatcomputedvalue,we#getthe"popular"documentfirst.In2:Document.objects.annotate(Count('comments').order_by('-comments_count')Out2:<Document:Populardocument>,<Document:Unpopulardocument>annotate()QuerySet方法自身并不执行任何聚合。相反,它可以指示Django将所传递的表达式的值指定给结果集中的一个伪列。默认情况下
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 步行街个人店铺租赁合同书
- 区商贸城商铺租赁合同
- 健身场地租赁合同
- 农副产品购销合同
- 土地租赁建房合同
- 借款抵押担保合同
- 停车位代理销售合同
- 知识产权专项法律服务合同
- 焦作师范高等专科学校《高尔夫球具维护》2023-2024学年第二学期期末试卷
- 长沙航空职业技术学院《广播电视技术实务》2023-2024学年第二学期期末试卷
- Nikon尼康D3100中文说明书
- DL-T5002-2021地区电网调度自动化设计规程
- 人教版四年级上册数学期末试卷(6套)
- FZ∕T 73037-2019 针织运动袜行业标准
- 春节的那些事作文6篇
- (完整版)机房安全检查表
- 山西省太原市2023-2024学年七年级下学期期中数学试题
- XF-T 3004-2020 汽车加油加气站消防安全管理
- 子宫内膜癌保留生育治疗
- (正式版)JBT 14660-2024 额定电压6kV到30kV地下掘进设备用橡皮绝缘软电缆
- 2.2算法的概念及其描述课件人教中图版高中信息技术必修1
评论
0/150
提交评论