在Django框架中运行Python应用全攻略
我们来假定下面的这些概念、字段和关系:
- 一个作者有姓,有名及email地址。
- 出版商有名称,地址,所在城市、省,国家,网站。
- 书籍有书名和出版日期。它有一个或多个作者(和作者是多对多的关联关系[many-to-many]),只有一个出版商(和出版商是一对多的关联关系[one-to-many],也被称作外键[foreignkey])
第一步是用Python代码来描述它们。打开由``startapp``命令创建的models.py并输入下面的内容:
fromdjango.dbimportmodels classPublisher(models.Model): name=models.CharField(max_length=30) address=models.CharField(max_length=50) city=models.CharField(max_length=60) state_province=models.CharField(max_length=30) country=models.CharField(max_length=50) website=models.URLField() classAuthor(models.Model): first_name=models.CharField(max_length=30) last_name=models.CharField(max_length=40) email=models.EmailField() classBook(models.Model): title=models.CharField(max_length=100) authors=models.ManyToManyField(Author) publisher=models.ForeignKey(Publisher) publication_date=models.DateField()
让我们来快速讲解一下这些代码的含义。首先要注意的事是每个数据模型都是django.db.models.Model的子类。它的父类Model包含了所有必要的和数据库交互的方法,并提供了一个简洁漂亮的定义数据库字段的语法。信不信由你,这些就是我们需要编写的通过Django存取基本数据的所有代码。
每个模型相当于单个数据库表,每个属性也是这个表中的一个字段。属性名就是字段名,它的类型(例如CharField)相当于数据库的字段类型(例如varchar)。例如,Publisher模块等同于下面这张表(用PostgreSQL的CREATETABLE语法描述):
CREATETABLE"books_publisher"( "id"serialNOTNULLPRIMARYKEY, "name"varchar(30)NOTNULL, "address"varchar(50)NOTNULL, "city"varchar(60)NOTNULL, "state_province"varchar(30)NOTNULL, "country"varchar(50)NOTNULL, "website"varchar(200)NOTNULL );
事实上,正如过一会儿我们所要展示的,Django可以自动生成这些CREATETABLE语句。
“每个数据库表对应一个类”这条规则的例外情况是多对多关系。在我们的范例模型中,Book有一个多对多字段叫做authors。该字段表明一本书籍有一个或多个作者,但Book数据库表却并没有authors字段。相反,Django创建了一个额外的表(多对多连接表)来处理书籍和作者之间的映射关系。
请查看附录B了解所有的字段类型和模型语法选项。
最后需要注意的是,我们并没有显式地为这些模型定义任何主键。除非你单独指明,否则Django会自动为每个模型生成一个自增长的整数主键字段每个Django模型都要求有单独的主键。
模型安装
完成这些代码之后,现在让我们来在数据库中创建这些表。要完成该项工作,第一步是在Django项目中激活这些模型。将booksapp添加到配置文件的已安装应用列表中即可完成此步骤。
再次编辑settings.py文件,找到INSTALLED_APPS设置。INSTALLED_APPS告诉Django项目哪些app处于激活状态。缺省情况下如下所示:
INSTALLED_APPS=( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', )
把这四个设置前面加#临时注释起来。(这四个app是经常使用到的,我们将在后续章节里讨论如何使用它们)。同时,注释掉MIDDLEWARE_CLASSES的默认设置条目,因为这些条目是依赖于刚才我们刚在INSTALLED_APPS注释掉的apps。然后,添加``‘mysite.books'``到``INSTALLED_APPS``的末尾,此时设置的内容看起来应该是这样的:
MIDDLEWARE_CLASSES=( #'django.middleware.common.CommonMiddleware', #'django.contrib.sessions.middleware.SessionMiddleware', #'django.contrib.auth.middleware.AuthenticationMiddleware', ) INSTALLED_APPS=( #'django.contrib.auth', #'django.contrib.contenttypes', #'django.contrib.sessions', #'django.contrib.sites', 'mysite.books', )
(就像我们在上一章设置TEMPLATE_DIRS所提到的逗号,同样在INSTALLED_APPS的末尾也需添加一个逗号,因为这是个单元素的元组。另外,本书的作者喜欢在每一个tuple元素后面加一个逗号,不管它是不是只有一个元素。这是为了避免忘了加逗号,而且也没什么坏处。)
'mysite.books'指示我们正在编写的booksapp。INSTALLED_APPS中的每个app都使用Python的路径描述,包的路径,用小数点“.”间隔。
现在我们可以创建数据库表了。首先,用下面的命令验证模型的有效性:
pythonmanage.pyvalidate
validate命令检查你的模型的语法和逻辑是否正确。如果一切正常,你会看到0errorsfound消息。如果出错,请检查你输入的模型代码。错误输出会给出非常有用的错误信息来帮助你修正你的模型。
一旦你觉得你的模型可能有问题,运行pythonmanage.pyvalidate。它可以帮助你捕获一些常见的模型定义错误。
模型确认没问题了,运行下面的命令来生成CREATETABLE语句(如果你使用的是Unix,那么可以启用语法高亮):
pythonmanage.pysqlallbooks
在这个命令行中,books是app的名称。和你运行manage.pystartapp中的一样。执行之后,输出如下:
BEGIN; CREATETABLE"books_publisher"( "id"serialNOTNULLPRIMARYKEY, "name"varchar(30)NOTNULL, "address"varchar(50)NOTNULL, "city"varchar(60)NOTNULL, "state_province"varchar(30)NOTNULL, "country"varchar(50)NOTNULL, "website"varchar(200)NOTNULL ) ; CREATETABLE"books_author"( "id"serialNOTNULLPRIMARYKEY, "first_name"varchar(30)NOTNULL, "last_name"varchar(40)NOTNULL, "email"varchar(75)NOTNULL ) ; CREATETABLE"books_book"( "id"serialNOTNULLPRIMARYKEY, "title"varchar(100)NOTNULL, "publisher_id"integerNOTNULLREFERENCES"books_publisher"("id")DEFERRABLEINITIALLYDEFERRED, "publication_date"dateNOTNULL ) ; CREATETABLE"books_book_authors"( "id"serialNOTNULLPRIMARYKEY, "book_id"integerNOTNULLREFERENCES"books_book"("id")DEFERRABLEINITIALLYDEFERRED, "author_id"integerNOTNULLREFERENCES"books_author"("id")DEFERRABLEINITIALLYDEFERRED, UNIQUE("book_id","author_id") ) ; CREATEINDEX"books_book_publisher_id"ON"books_book"("publisher_id"); COMMIT;
注意:
- 自动生成的表名是app名称(books)和模型的小写名称(publisher,book,author)的组合。你可以参考附录B重写这个规则。
- 我们前面已经提到,Django为每个表格自动添加加了一个id主键,你可以重新设置它。
- 按约定,Django添加"_id"后缀到外键字段名。你猜对了,这个同样是可以自定义的。
- 外键是用REFERENCES语句明确定义的。
- 这些CREATETABLE语句会根据你的数据库而作调整,这样象数据库特定的一些字段例如:(MySQL),auto_increment(PostgreSQL),serial(SQLite),都会自动生成。integerprimarykey同样的,字段名称也是自动处理(例如单引号还好是双引号)。例子中的输出是基于PostgreSQL语法的。
sqlall命令并没有在数据库中真正创建数据表,只是把SQL语句段打印出来,这样你可以看到Django究竟会做些什么。如果你想这么做的话,你可以把那些SQL语句复制到你的数据库客户端执行,或者通过Unix管道直接进行操作(例如,``pythonmanager.pysqlallbooks|psqlmydb``)。不过,Django提供了一种更为简易的提交SQL语句至数据库的方法:``syncdb``命令
pythonmanage.pysyncdb
执行这个命令后,将看到类似以下的内容:
Creatingtablebooks_publisher Creatingtablebooks_author Creatingtablebooks_book Installingindexforbooks.Bookmodel
syncdb命令是同步你的模型到数据库的一个简单方法。它会根据INSTALLED_APPS里设置的app来检查数据库,如果表不存在,它就会创建它。需要注意的是,syncdb并不能将模型的修改或删除同步到数据库;如果你修改或删除了一个模型,并想把它提交到数据库,syncdb并不会做出任何处理。(更多内容请查看本章最后的“修改数据库的架构”一段。)
如果你再次运行pythonmanage.pysyncdb,什么也没发生,因为你没有添加新的模型或者添加新的app。因此,运行pythonmanage.pysyncdb总是安全的,因为它不会重复执行SQL语句。
如果你有兴趣,花点时间用你的SQL客户端登录进数据库服务器看看刚才Django创建的数据表。你可以手动启动命令行客户端(例如,执行PostgreSQL的``psql``命令),也可以执行``pythonmanage.pydbshell``,这个命令将依据``DATABASE_SERVER``的里设置自动检测使用哪种命令行客户端。常言说,后来者居上。
基本数据访问
一旦你创建了模型,Django自动为这些模型提供了高级的PythonAPI。运行pythonmanage.pyshell并输入下面的内容试试看:
>>>frombooks.modelsimportPublisher >>>p1=Publisher(name='Apress',address='2855TelegraphAvenue', ...city='Berkeley',state_province='CA',country='U.S.A.', ...website='http://www.apress.com/') >>>p1.save() >>>p2=Publisher(name="O'Reilly",address='10FawcettSt.', ...city='Cambridge',state_province='MA',country='U.S.A.', ...website='http://www.oreilly.com/') >>>p2.save() >>>publisher_list=Publisher.objects.all() >>>publisher_list [<Publisher:Publisherobject>,<Publisher:Publisherobject>]
这短短几行代码干了不少的事。这里简单的说一下:
- 首先,导入Publisher模型类,通过这个类我们可以与包含出版社的数据表进行交互。
- 接着,创建一个``Publisher``类的实例并设置了字段``name,address``等的值。
- 调用该对象的save()方法,将对象保存到数据库中。Django会在后台执行一条INSERT语句。
- 最后,使用``Publisher.objects``属性从数据库取出出版商的信息,这个属性可以认为是包含出版商的记录集。这个属性有许多方法,这里先介绍调用``Publisher.objects.all()``方法获取数据库中``Publisher``类的所有对象。这个操作的幕后,Django执行了一条SQL``SELECT``语句。
这里有一个值得注意的地方,在这个例子可能并未清晰地展示。当你使用DjangomodleAPI创建对象时Django并未将对象保存至数据库内,除非你调用``save()``方法:
p1=Publisher(...) #Atthispoint,p1isnotsavedtothedatabaseyet! p1.save() #Nowitis.
如果需要一步完成对象的创建与存储至数据库,就使用``objects.create()``方法。下面的例子与之前的例子等价:
>>>p1=Publisher.objects.create(name='Apress', ...address='2855TelegraphAvenue', ...city='Berkeley',state_province='CA',country='U.S.A.', ...website='http://www.apress.com/') >>>p2=Publisher.objects.create(name="O'Reilly", ...address='10FawcettSt.',city='Cambridge', ...state_province='MA',country='U.S.A.', ...website='http://www.oreilly.com/') >>>publisher_list=Publisher.objects.all() >>>publisher_list
当然,你肯定想执行更多的Django数据库API试试看,不过,还是让我们先解决一点烦人的小问题。
添加模块的字符串表现
当我们打印整个publisher列表时,我们没有得到想要的有用信息,无法把````对象区分开来:
SystemMessage:WARNING/2(<string>,line872);backlink Inlineliteralstart-stringwithoutend-string. SystemMessage:WARNING/2(<string>,line872);backlink Inlineliteralstart-stringwithoutend-string. [<Publisher:Publisherobject>,<Publisher:Publisherobject>]
我们可以简单解决这个问题,只需要为Publisher对象添加一个方法__unicode__()。__unicode__()方法告诉Python如何将对象以unicode的方式显示出来。为以上三个模型添加__unicode__()方法后,就可以看到效果了:
fromdjango.dbimportmodels classPublisher(models.Model): name=models.CharField(max_length=30) address=models.CharField(max_length=50) city=models.CharField(max_length=60) state_province=models.CharField(max_length=30) country=models.CharField(max_length=50) website=models.URLField() **def__unicode__(self):** **returnself.name** classAuthor(models.Model): first_name=models.CharField(max_length=30) last_name=models.CharField(max_length=40) email=models.EmailField() **def__unicode__(self):** **returnu'%s%s'%(self.first_name,self.last_name)** classBook(models.Model): title=models.CharField(max_length=100) authors=models.ManyToManyField(Author) publisher=models.ForeignKey(Publisher) publication_date=models.DateField() **def__unicode__(self):** **returnself.title**
就象你看到的一样,__unicode__()方法可以进行任何处理来返回对一个对象的字符串表示。Publisher和Book对象的__unicode__()方法简单地返回各自的名称和标题,Author对象的__unicode__()方法则稍微复杂一些,它将first_name和last_name字段值以空格连接后再返回。
对__unicode__()的唯一要求就是它要返回一个unicode对象如果``__unicode__()``方法未返回一个Unicode对象,而返回比如说一个整型数字,那么Python将抛出一个``TypeError``错误,并提示:”coercingtoUnicode:needstringorbuffer,intfound”。