Django和数据库打交道:数据建模

前面我们讲述了用 Django建造网站的基本途径:建立视图和 URLConf。正如我们所阐述的,视图负责处理一些任意逻辑,然后返回响应结果。在范例中,我们的任意逻辑就是计算当前的日期和时间。

在当代 Web应用中,任意逻辑经常牵涉到与数据库的交互。数据库驱动网站在后台连接数据库服务器,从中取出一些数据,然后在 Web页面用漂亮的格式展示这些数据。或者,站点也提供让访问者自行填充数据库的功能。

许多复杂的网站都提供了以上两个功能的某种结合。例如 Amazon.com就是一个数据库驱动站点的良好范例。本质上,每个产品页都是从数据库中取出的数据被格式化为HTML,而当你发表客户评论时,该评论被插入评论数据库中。

由于先天具备Python 简单而强大的数据库查询执行方法,Django非常适合开发数据库驱动网站。本章深入介绍了该功能:Django数据库层。

(注意:尽管对 Django数据库层的使用中并不特别强调,我们还是强烈建议掌握一些数据库和 SQL原理。对这些概念的介绍超越了本书的范围,但就算你是数据库方面的菜鸟,我们也建议你继续阅读。你也许能够跟上进度,并在上下文学习过程中掌握一些概念。)

在视图中进行数据库查询的笨方法

正如第三章详细介绍的那个在视图中输出 HTML的笨方法(通过在视图里对文本直接硬编码HTML),在视图中也有笨方法可以从数据库中获取数据。很简单:用现有的任何 Python类库执行一条 SQL 查询并对结果进行一些处理。

在本例的视图中,我们使用了MySQLdb类库(可以从http://www.djangoproject.com/r/python-mysql/获得)来连接 MySQL 数据库,取回一些记录,将它们提供给模板以显示一个网页:


from django.shortcuts import render_to_response

import MySQLdb

def book_list(request):

db = MySQLdb.connect(user='me', db='mydb', passwd='secret', host='localhost')

cursor = db.cursor()

cursor.execute('SELECT name FROM books ORDER BY name')

names = [row[0] for row in cursor.fetchall()]

db.close()

return render_to_response('book_list.html', {'names': names})


这个方法可用,但很快一些问题将出现在你面前:

§ 我们将数据库连接参数硬行编码于代码之中。理想情况下,这些参数应当保存在 Django配置中。

§ 我们不得不重复同样的代码:创建数据库连接、创建数据库游标、执行某个语句、然后关闭数据库。理想情况下,我们所需要应该只是指定所需的结果。

§ 它把我们栓死在 MySQL之上。如果过段时间,我们要从 MySQL 换到 PostgreSQL,就不得不使用不同的数据库适配器(例如psycopg而不是MySQLdb),改变连接参数,根据 SQL 语句的类型可能还要修改SQL。理想情况下,应对所使用的数据库服务器进行抽象,这样一来只在一处修改即可变换数据库服务器。

正如你所期待的,Django数据库层正是致力于解决这些问题。以下提前揭示了如何使用 Django数据库 API 重写之前那个视图。

from django.shortcuts import render_to_response

from mysite.books.models import Book

def book_list(request):

books = Book.objects.order_by('name')

return render_to_response('book_list.html', {'books': books})

我们将在本章稍后的地方解释这段代码。目前而言,仅需对它有个大致的认识。

MTV 开发模式

在钻研更多代码之前,让我们先花点时间考虑下 Django数据驱动 Web 应用的总体设计。

我们在前面章节提到过,Django的设计鼓励松耦合及对应用程序中不同部分的严格分割。遵循这个理念的话,要想修改应用的某部分而不影响其它部分就比较容易了。在视图函数中,我们已经讨论了通过模板系统把业务逻辑和表现逻辑分隔开的重要性。在数据库层中,我们对数据访问逻辑也应用了同样的理念。

把数据存取逻辑、业务逻辑和表现逻辑组合在一起的概念有时被称为软件架构的Model-View-Controller (MVC)模式。在这个模式中, Model代表数据存取层,View 代表的是系统中选择显示什么和怎么显示的部分,Controller指的是系统中根据用户输入并视需要访问模型,以决定使用哪个视图的那部分。

为什么用缩写?

MVC 这样的明确定义模式的主要用于改善开发人员之间的沟通。与其告诉同事:让我们对数据存取进行抽象,用单独一层负责数据显示,然后在中间放置一层来进行控制,还不如利用通用的词汇告诉他们:让我们在这里使用 MVC 模式吧

Django 紧紧地遵循这种 MVC模式,可以称得上是一种 MVC框架。以下是 Django MV C 各自的含义:

§ M,数据存取部分,由django数据库层处理,本章要讲述的内容。

§ V,选择显示哪些数据要及怎样显示的部分,由视图和模板处理。

§ C,根据用户输入委派视图的部分,由 Django框架通过按照 URLconf 设置,对给定 URL 调用合适的 python函数来自行处理。

由于 C 由框架自行处理,而 Django 里更关注的是模型(Model)、模板(Template)和视图(Views),Django也被称为MTV 框架。在 MTV开发模式中:

§ M代表模型(Model),即数据存取层。该层处理与数据相关的所有事务:如何存取、如何确认有效性、包含哪些行为以及数据之间的关系等。

§ T代表模板(Template),即表现层。该层处理与表现相关的决定:如何在页面或其他类型文档中进行显示。

§ V代表视图(View),即业务逻辑层。该层包含存取模型及调取恰当模板的相关逻辑。你可以把它看作模型与模板之间的桥梁。

如果你熟悉其它的 MVC Web开发框架,比方说 Ruby on Rails,你可能会认为 Django视图是控制器,而 Django模板是视图。很不幸,这是对 MVC不同诠释所引起的错误认识。在 Django MVC 的诠释中,视图用来描述要展现给用户的数据;不是数据看起来怎么样,而是要呈现哪些数据。相比之下,Ruby on Rails 及一些同类框架提倡控制器负责决定向用户展现哪些数据,而视图则仅决定如何展现数据,而不是展现哪些数据。

两种诠释中没有哪个更加正确一些。重要的是要理解底层概念。

数据库配置

记住这些理念之后,让我们来开始 Django数据库层的探索。首先,我们需要搞定一些初始化设置:我们必须告诉 Django要用哪个数据库服务器及如何连接上它。

我们将假定你已经完成了数据库服务器的安装和激活,并且已经在其中创建了数据库(例如,用CREATE DATABASE语句)。SQLite数据库有点特别,用它的话不需要创建数据库,因为 SQLite用使用文件系统中的单个文件来保存数据。

象前面章节提到的TEMPLATE_DIRS一样,数据库配置也是在Django的配置文件里,缺省settings.py。编辑打开这个文件并查找数据库配置:

DATABASE_ENGINE = ''

DATABASE_NAME = ''

DATABASE_USER = ''

DATABASE_PASSWORD = ''

DATABASE_HOST = ''

DATABASE_PORT = ''

配置纲要如下。

DATABASE_ENGINE告诉Django使用哪个数据库引擎。如果你在 Django中使用数据库,DATABASE_ENGINE必须是 Table 5-1中所列出的值。

表 5-1. 数据库引擎设置

设置

数据库

适配器

postgresql

PostgreSQL

psycopg版本 1.x,http://www.djangoproject.com/r/python-pgsql/1/.

postgresql_psycopg2

PostgreSQL

psycopg版本 2.x,http://www.djangoproject.com/r/python-pgsql/.

mysql

MySQL

MySQLdb ,http://www.djangoproject.com/r/python-mysql/.

sqlite3

SQLite

Python 2.5+ 内建。 其他,pysqlite ,http://www.djangoproject.com/r/python-sqlite/.

ado_mssql

Microsoft SQL Server

adodbapi版本 2.0.1+,http://www.djangoproject.com/r/python-ado/.

oracle

Oracle

cx_Oracle ,http://www.djangoproject.com/r/python-oracle/.

要注意的是无论选择使用哪个数据库服务器,都必须下载和安装对应的数据库适配器。访问表 5-1所需适配器一栏中的链接,可通过互联网免费获取这些适配器。

DATABASE_NAME将数据库名称告知 Django。如果使用 SQLite,请对数据库文件指定完整的文件系统路径。(例如'/home/django/mydata.db')。

DATABASE_USER告诉 Django用哪个用户连接数据库。如果用SQLite,空白即可。

DATABASE_PASSWORD告诉Django连接用户的密码。SQLite用空密码即可。

DATABASE_HOST告诉 Django连接哪一台主机的数据库服务器。如果数据库与 Django安装于同一台计算机(即本机),可将此项保留空白。使用 SQLite,也可保留空白。

此处的 MySQL是一个特例。如果使用的是 MySQL且该项设置值由斜杠('/')开头,MySQL将通过 Unix socket来连接指定的套接字,例如:


DATABASE_HOST = '/var/run/mysql'

如果用 MySQL而该项设置的值不是以正斜线开始的,系统将假定该项值是主机名。

DATABASE_PORT告诉 Django连接数据库时使用哪个端口。如果用SQLite,空白即可。其他情况下,如果将该项设置保留空白,底层数据库适配器将会连接所给定数据库服务器的缺省端口。在多数情况下,使用缺省端口就可以了,因此你可以将该项设置保留空白。

输入完设置后,测试一下配置情况。首先,转到在第二章创建的mysite项目目录,运行python manage.py shell命令。

你会看到该命令启动了一个 Python交互界面。运行命令python manage.py shell启动的交互界面和标准的python交互界面有很大的区别。看起来都是基本的python外壳(shell),但是前者告诉Django使用哪个配置文件启动。这对数据库操作来说很关键:Django需要知道使用哪个配置文件来获得数据库连接信息。

python manage.py shell假定你的配置文件就在和manage.py一样的目录中。以后将会讲到使用其他的方式来告诉Django使用其他的配置文件。

输入下面这些命令来测试你的数据库配置:

>>> from django.db import connection

>>> cursor = connection.cursor()

如果没有显示什么错误信息,那么你的数据库配置是正确的。否则,你就得查看错误信息来纠正错误。表 5-2是一些常见错误。

表 5-2. 数据库配置错误信息

错误信息

解决方案

You havent set the DATABASE_ENGINE setting yet.

设置正确的 DATABASE_ENGINE配置

Environment variable DJANGO_SETTINGS_MODULE is undefined.

运行命令行 python manage.py shell而不是python .

Error loading _____ module: No module named _____.

你没有安装相关的数据库适配器 (例如, psycopgMySQLdb ).

_____ isnt an available database backend.

设置正确的 DATABASE_ENGINE配置 也许是拼写错误?

database _____ does not exist

设置 DATABASE_NAME配置到一个已有的数据库, 或者使用CREATE DATABASE语句创建数据库。

role _____ does not exist

修改 DATABASE_USER配置到一个有效用户

could not connect to server

确认 DATABASE_HOSTDATABASE_PORT设置是正确的,并 确认服务器是在运行的。

你的第一个应用程序

你现在已经确认数据库连接正常工作了,让我们来创建一个Django app ,开始编码模型和视图。这些文件放置在同一个包中并且形成为一个完整的Django应用程序。

在这里要先解释一些术语,初学者可能会混淆它们。在第二章我们已经创建了project , 那么project app 之间到底有什么不同呢?它们的区别就是一个是配置另一个是代码:

一个project包含很多个Django app以及对它们的配置。

技术上,project的作用是提供配置文件,比方说哪里定义数据库连接信息,安装的app列表,TEMPLATE_DIRS,等等。

一个app是一套Django功能的集合,通常包括模型和视图,按Python的包结构的方式存在。

例如,Django本身内建有一些app,例如注释系统和自动管理界面。 app的一个关键点是它们是很容易移植到其他project和被多个project重用。

如果你只是建造一个简单的web站点,那么可能你只需要一个app就可以了。如果是复杂的象电子商务之类的Web站点,你可能需要把这些功能划分成不同的app,以便以后重用。

确实,你还可以不用创建app,例如以前写的视图,只是简单的放在views.py,不需要app

当然,系统对app有一个约定:如果你使用了Django的数据库层(模型),你必须创建一个django app。模型必须在这个app中存在。因此,为了开始建造我们的模型,我们必须创建一个新的app

转到mysite项目目录,执行下面的命令来创建一个新app叫做books

python manage.py startapp books

这个命令没有输出什么,它在mysite的目录里创建了一个books目录。让我们来看看这个目录的内容:

books/

__init__.py

models.py

views.py

这些文件里面就包含了这个app的模型和视图。

看一下models.pyviews.py文件。它们都是空的,除了models.py里有一个 import

Python代码里定义模型

我们早些时候谈到。MTV里的M代表模型。Django模型是用Python代码形式表述的数据在数据库中的定义。对数据层来说它等同于CREATE TABLE语句,只不过执行的是Python代码而不是 SQL,而且还包含了比数据库字段定义更多的含义。Django用模型在后台执行SQL代码并把结果Python的数据结构来描述,这样你可以很方便的使用这些数据。Django还用模型来描述SQL不能处理的高级概念。

如果你对数据库很熟悉,你可能马上就会想到,用PythonSQL来定义数据模型是不是有点多余? Django这样做是有下面几个原因的:

自省(运行时自动识别数据库)会导致过载和有数据完整性问题。为了提供方便的数据访问API Django需要以某种方式知道数据库层内部信息,有两种实现方式。第一种方式是用Python明确的定义数据模型,第二种方式是通过运行时扫描数据库来自动侦测识别数据模型。

第二种方式看起来更清晰,因为数据表信息只存放在一个地方-数据库里,但是会带来一些问题。首先,运行时扫描数据库会带来严重的系统过载。如果每个请求都要扫描数据库的表结构,或者即便是服务启动时做一次都是会带来不能接受的系统过载。(Django尽力避免过载,而且成功做到了这一点)其次,有些数据库,例如老版本的MySQL,没有提供足够的元数据来完整地重构数据表。

编写Python代码是非常有趣的,保持用Python的方式思考会避免你的大脑在不同领域来回切换。这可以帮助你提高生产率。不得不去重复写SQL,再写Python代码,再写SQL,会让你头都要裂了。

把数据模型用代码的方式表述来让你可以容易对它们进行版本控制。这样,你可以很容易了解数据层的变动情况。

SQL只能描述特定类型的数据字段。例如,大多数数据库都没有数据字段类型描述Email地址、URL而用Django的模型可以做到这一点。好处就是高级的数据类型带来高生产力和更好的代码重用。

SQL还有在不同数据库平台的兼容性问题。你必须为不同的数据库编写不同的SQL脚本,Python的模块就不会有这个问题。

当然,这个方法也有一个缺点,就是Python代码和数据库表的同步问题。如果你修改了一个Django模型,你要自己做工作来保证数据库和模型同步。我们将在稍后讲解解决这个问题的几种策略。

最后,我们要提醒你Django提供了实用工具来从现有的数据库表中自动扫描生成模型。这对已有的数据库来说是非常快捷有用的。

你的第一个模型

在本章和后续章节里,我们将集中到一个基本的书籍/作者/出版商数据层上。我们这样做是因为这是一个众所周知的例子,很多SQL有关的书籍也常用这个举例。你现在看的这本书也是由作者创作再由出版商出版的哦!

我们来假定下面的这些概念、字段和关系:

§ 作者有尊称(例如,先生或者女士),姓,名,还有Email地址,头像。

§ 出版商有名称,地址,所在城市、省,国家,网站。

§ 书籍有书名和出版日期。它有一个或多个作者(和作者是多对多的关联关系[many-to-many]),只有一个出版商(和出版商是一对多的关联关系[one-to-many],也被称作外键[foreign key]

第一步是用Python代码来描述它们。打开models.py并输入下面的内容:


from django.db import models

class Publisher(models.Model):

name = models.CharField(maxlength=30)

address = models.CharField(maxlength=50)

city = models.CharField(maxlength=60)

state_province = models.CharField(maxlength=30)

country = models.CharField(maxlength=50)

website = models.URLField()

class Author(models.Model):

salutation = models.CharField(maxlength=10)

first_name = models.CharField(maxlength=30)

last_name = models.CharField(maxlength=40)

email = models.EmailField()

headshot = models.ImageField(upload_to='/tmp')

class Book(models.Model):

title = models.CharField(maxlength=100)

authors = models.ManyToManyField(Author)

publisher = models.ForeignKey(Publisher)

publication_date = models.DateField()


让我们来快速讲解一下这些代码的含义。首先要注意的事是每个数据模型都是django.db.models.Model的子类。它的父类Model包含了所有和数据库打交道的方法,并提供了一个简洁漂亮的定义语法。不管你相信还是不相信,这就是我们用Django写的数据基本存取功能的全部代码。

每个模型相当于单个数据库表,每个属性也是这个表中的一个字段。属性名就是字段名,它的类型(例如CharField)相当于数据库的字段类型(例如varchar)。例如,Publisher模块等同于下面这张表(用PostgresqlCREATE TABLE语法描述):

CREATE TABLE "books_publisher" (

"id" serial NOT NULL PRIMARY KEY,

"name" varchar(30) NOT NULL,

"address" varchar(50) NOT NULL,

"city" varchar(60) NOT NULL,

"state_province" varchar(30) NOT NULL,

"country" varchar(50) NOT NULL,

"website" varchar(200) NOT NULL

);

事实上,正如过一会儿我们所要展示的,Django可以自动生成这些CREATE TABLE语句。

每个数据库表对应一个类这条规则的例外情况是多对多关系。在我们的范例模型中,Book有一个多对多字段叫做authors该字段表明一本书籍有一个或多个作者,但Book数据库表却并没有authors字段。相反,Django创建了一个额外的表(多对多连接表)来处理书籍和作者之间的映射关系。

请查看附录 B了解所有的字段类型和模型语法选项。

最后需要注意的是:我们并没有显式地为这些模型定义任何主键。除非你指定,否则 Django会自动为每个模型创建一个叫做id的主键。每个 Django模型必须要有一个单列主键。

模型安装

完成这些代码之后,现在让我们来在数据库中创建这些表。要完成该向工作,第一步是在 Django项目中激活这些模型。将booksapp添加到配置文件的已 installed apps列表中即可完成此步骤。

再次编辑settings.py文件,找到INSTALLED_APPS设置。INSTALLED_APPS告诉 Django 项目哪些 app处于激活状态。缺省情况下如下所示:

INSTALLED_APPS = (

'django.contrib.auth',

'django.contrib.contenttypes',

'django.contrib.sessions',

'django.contrib.sites',

)

把这四个设置前面加#临时注释起来。(它们是一些缺省的公用设置,现在先不管它们,以后再来讨论)同样的,修改缺省的MIDDLEWARE_CLASSESTEMPLATE_CONTEXT_PROCESSORS设置,都注释起来。然后添加'mysite.books'INSTALLED_APPS列表,现在看起来是这样:

MIDDLEWARE_CLASSES = (

# 'django.middleware.common.CommonMiddleware',

# 'django.contrib.sessions.middleware.SessionMiddleware',

# 'django.contrib.auth.middleware.AuthenticationMiddleware',

# 'django.middleware.doc.XViewMiddleware',

)

TEMPLATE_CONTEXT_PROCESSORS = ()

#...

INSTALLED_APPS = (

#'django.contrib.auth',

#'django.contrib.contenttypes',

#'django.contrib.sessions',

#'django.contrib.sites',

'mysite.books',

)

(尽管这是单个tuple元素,我们也不要忘了结尾的逗号[,]另外,本书的作者喜欢在每一个tuple元素后面加一个逗号,不管它是不是只有一个元素。这是为了避免忘了加逗号)

'mysite.books'标识booksappINSTALLED_APPS中的每个app都用 Python的路径描述,包的路径,用小数点(.)区分。

现在我们可以创建数据库表了。首先,用下面的命令对校验模型的有效性:

python manage.py validate

validate命令检查你的模型的语法和逻辑是否正确。如果一切正常,你会看到0 errors found消息。如果有问题,它会给出非常有用的错误信息来帮助你修正你的模型。


一旦你觉得你的模型可能有问题,运行python manage.py validate它可以帮助你捕获一些常见的模型定义错误。

模型确认没问题了,运行下面的命令来生成CREATE TABLE语句:

python manage.py sqlall books

在这个命令行中,booksapp的名称。和你运行manage.py startapp中的一样。运行命令的结果是这样的:

BEGIN;

CREATE TABLE "books_publisher" (

"id" serial NOT NULL PRIMARY KEY,

"name" varchar(30) NOT NULL,

"address" varchar(50) NOT NULL,

"city" varchar(60) NOT NULL,

"state_province" varchar(30) NOT NULL,

"country" varchar(50) NOT NULL,

"website" varchar(200) NOT NULL

);

CREATE TABLE "books_book" (

"id" serial NOT NULL PRIMARY KEY,

"title" varchar(100) NOT NULL,

"publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id"),

"publication_date" date NOT NULL

);

CREATE TABLE "books_author" (

"id" serial NOT NULL PRIMARY KEY,

"salutation" varchar(10) NOT NULL,

"first_name" varchar(30) NOT NULL,

"last_name" varchar(40) NOT NULL,

"email" varchar(75) NOT NULL,

"headshot" varchar(100) NOT NULL

);

CREATE TABLE "books_book_authors" (

"id" serial NOT NULL PRIMARY KEY,

"book_id" integer NOT NULL REFERENCES "books_book" ("id"),

"author_id" integer NOT NULL REFERENCES "books_author" ("id"),

UNIQUE ("book_id", "author_id")

);

CREATE INDEX books_book_publisher_id ON "books_book" ("publisher_id");

COMMIT;

注意:

§ 自动生成的表名是app名称(books)和模型的小写名称publisher,book,author)的组合。你可以指定不同的表名,详情请看附录 B

§ 我们前面已经提到,Django为自动加了一个id主键,你一样可以修改它。

§ 按约定,Django添加"_id"后缀到外键字段名。这个同样也是可自定义的。

§ 外键是用REFERENCES语句明确定义的。

§ 这些CREATE TABLE语句会根据你的数据库而作调整,这样象数据库特定的一些字段例如:auto_increment(MySQL),serial(PostgreSQL),integer primary key(SQLite) 可以自动处理。同样的,字段名称也是自动处理(例如单引号还好是双引号)。这个给出的例子是Postgresql的语法。

sqlall命令并没有在数据库中真正创建数据表,只是把SQL语句段打印出来。你可以把这些语句段拷贝到你的SQL客户端去执行它。当然,Django提供了更简单的方法来执行这些SQL语句。运行syncdb命令:

python manage.py syncdb

你将会看到这样的内容:

Creating table books_publisher

Creating table books_book

Creating table books_author

Installing index for books.Book model

syncdb命令是同步你的模型到数据库的一个简单方法。它会根据INSTALLED_APPS里设置的app来检查数据库,如果表不存在,它就会创建它。需要注意的是,syncdb不能同步模型的修改到数据库。如果你修改了模型,然后你想更新数据库,syncdb是帮不了你的。(稍后我们再讲这些。)

如果你再次运行python manage.py syncdb,什么也没发生,因为你没有添加新的模型或者添加新的app。所以,运行python manage.py syncdb总是安全的,它不会把事情搞砸。

如果你有兴趣,花点时间用你的SQL客户端登录进数据库服务器看看刚才Django创建的数据表。 Django带有一个命令行工具,python manage.py dbshell

基本数据访问

一旦你创建了模型,Django自动为这些模型提供了高级的Pyhton API运行python manage.py shell并输入下面的内容试试看:

>>> from books.models import Publisher

>>> p1 = Publisher(name='Addison-Wesley', address='75 Arlington Street',

... city='Boston', state_province='MA', country='U.S.A.',

... website='http://www.apress.com/')

>>> p1.save()

>>> p2 = Publisher(name="O'Reilly", address='10 Fawcett St.',

... city='Cambridge', state_province='MA', country='U.S.A.',

... website='http://www.oreilly.com/')

>>> p2.save()

>>> publisher_list = Publisher.objects.all()

>>> publisher_list

[<Publisher: Publisher object>, <Publisher: Publisher object>]


这短短几行代码干了不少的事。这里简单的说一下:

§ 要创建对象,只需 import相应模型类,并传入每个字段值将其实例化。

§ 调用该对象的save()方法,将对象保存到数据库中。Django会在后台执行一条INSERT语句。

§ 使用属性Publisher.objects从数据库中获取对象。调用Publisher.objects.all()获取数据库中所有的Publisher对象。此时,Django在后台执行一条SELECTSQL语句。

自然,你肯定想执行更多的Django数据库API试试看,不过,还是让我们先解决一点烦人的小问题。

添加模块的字符串表现

当我们打印整个publisher列表时,我们没有得到想要的有用的信息:

[<Publisher: Publisher object>, <Publisher: Publisher object>]

我们可以简单解决这个问题,只需要添加一个方法__str__()Publisher对象。__str__()方法告诉Python要怎样把对象当作字符串来使用。请看下面:

from django.db import models

class Publisher(models.Model):

name = models.CharField(maxlength=30)

address = models.CharField(maxlength=50)

city = models.CharField(maxlength=60)

state_province = models.CharField(maxlength=30)

country = models.CharField(maxlength=50)

website = models.URLField()

**def __str__(self):**

**return self.name**

class Author(models.Model):

salutation = models.CharField(maxlength=10)

first_name = models.CharField(maxlength=30)

last_name = models.CharField(maxlength=40)

email = models.EmailField()

headshot = models.ImageField(upload_to='/tmp')

**def __str__(self):**

**return '%s %s' % (self.first_name, self.last_name)**

class Book(models.Model):

title = models.CharField(maxlength=100)

authors = models.ManyToManyField(Author)

publisher = models.ForeignKey(Publisher)

publication_date = models.DateField()

**def __str__(self):**

**return self.title**


就象你看到的一样,__str__()方法返回一个字符串。__str__()必须返回字符串,如果是其他类型,Python将会抛出TypeError错误消息"__str__ returned non-string"出来。

为了让我们的修改生效,先退出Python Shell,然后再次运行python manage.py shell进入。现在列出Publisher对象就很容易理解了:

>>> from books.models import Publisher

>>> publisher_list = Publisher.objects.all()

>>> publisher_list

[<Publisher: Addison-Wesley>, <Publisher: O'Reilly>]

请确保你的每一个模型里都包含__str__()方法,这不只是为了交互时方便,也是因为 Django会在其他一些地方用__str__()来显示对象。

最后,__str()__也是一个很好的例子来演示我们怎么添加行为到模型里。 Django的模型不只是为对象定义了数据库表的结构,还定义了对象的行为。__str__()就是一个例子来演示模型知道怎么显示它们自己。

插入和更新数据

你已经知道怎么做了:先使用一些关键参数创建对象实例,如下:

>>> p = Publisher(name='Apress',

... address='2855 Telegraph Ave.',

... city='Berkeley',

... state_province='CA',

... country='U.S.A.',

... website='http://www.apress.com/')

这个对象实例并没有对数据库做修改。

要保存这个记录到数据库里(也就是执行INSERTSQL语句),调用对象的save()方法:

>>> p.save()

SQL里,这大致可以转换成这样:

INSERT INTO book_publisher

(name, address, city, state_province, country, website)

VALUES

('Apress', '2855 Telegraph Ave.', 'Berkeley', 'CA',

'U.S.A.', 'http://www.apress.com/');

因为Publisher模型有一个自动增加的主键id,所以第一次调用save()还多做了一件事:计算这个主键的值并把它赋值给这个对象实例:

>>> p.id

52 # this will differ based on your own data

接下来再调用save()将不会创建新的记录,而只是修改记录内容(也就是执行UPDATESQL语句,而不是INSERT语句):

>>> p.name = 'Apress Publishing'

>>> p.save()

前面执行的save()相当于下面的SQL语句:

UPDATE book_publisher SET

name = 'Apress Publishing',

address = '2855 Telegraph Ave.',

city = 'Berkeley',

state_province = 'CA',

country = 'U.S.A.',

website = 'http://www.apress.com'

WHERE id = 52;

选择对象

我们已经知道查找所有数据的方法了:

>>> Publisher.objects.all()

[<Publisher: Addison-Wesley>, <Publisher: O'Reilly>, <Publisher: Apress Publishing>]

这相当于这个SQL语句:

SELECT

id, name, address, city, state_province, country, website

FROM book_publisher;

注意

注意到Django在选择所有数据时并没有使用SELECT*,而是显式列出了所有字段。就是这样设计的:SELECT*会更慢,而且最重要的是列出所有字段遵循了Python界的一个信条:明确比不明确好。

有关Python之禅(戒律) :-),在Python提示行输入import this试试看。


让我们来仔细看看Publisher.objects.all()这行的每个部分:

首先,我们有一个已定义的模型Publisher。没什么好奇怪的:你想要查找数据,你就用模型来获得数据。

其次,objects是干什么的?技术上,它是一个管理器(manager管理器将在附录B详细描述,在这里你只要知道它处理有关数据表的操作,特别是数据查找。

所有的模型都自动拥有一个objects管理器;你可以在想要查找数据时是使用它。

最后,还有all()方法。这是objects管理器返回所有记录的一个方法。尽管这个对象看起来象一个列表(list),它实际是一个QuerySet 对象,这个对象是数据库中一些记录的集合。附录C将详细描述QuerySet,现在,我们就先当它是一个仿真列表对象好了。

所有的数据库查找都遵循一个通用模式:调用模型的管理器来查找数据。

数据过滤

如果想要获得数据的一个子集,我们可以使用filter()方法:

>>> Publisher.objects.filter(name="Apress Publishing")

[<Publisher: Apress Publishing>]

filter()根据关键字参数来转换成WHERESQL语句。前面这个例子相当于这样:

SELECT

id, name, address, city, state_province, country, website

FROM book_publisher

WHERE name = 'Apress Publishing';

你可以传递多个参数到filter()来缩小选取范围:

>>> Publisher.objects.filter(country="U.S.A.", state_province="CA")

[<Publisher: Apress Publishing>]

多个参数会被转换成ANDSQL语句,例如象下面这样:

SELECT

id, name, address, city, state_province, country, website

FROM book_publisher

WHERE country = 'U.S.A.' AND state_province = 'CA';

注意,SQL缺省的=操作符是精确匹配的,其他的查找类型如下:

>>> Publisher.objects.filter(name__contains="press")

[<Publisher: Apress Publishing>]

namecontains之间有双下划线。象Python自己一样,Django也使用双下划线来做一些小魔法,这个__contains部分会被Django转换成LIKESQL语句:

SELECT

id, name, address, city, state_province, country, website

FROM book_publisher

WHERE name LIKE '%press%';

其他的一些查找类型有:icontains(大小写无关的LIKE),startswithendswith,还有range(SQLBETWEEN查询)。附录C详细列出了这些类型的详细资料。

获取单个对象

有时你只想获取单个对象,这个时候使用get()方法:

>>> Publisher.objects.get(name="Apress Publishing")

<Publisher: Apress Publishing>

这样,就返回了单个对象,而不是列表(更准确的说,QuerySet)所以,如果结果是多个对象,会导致抛出异常:

>>> Publisher.objects.get(country="U.S.A.")

Traceback (most recent call last):

...

AssertionError: get() returned more than one Publisher -- it returned 2!

如果查询没有返回结果也会抛出异常:

>>> Publisher.objects.get(name="Penguin")

Traceback (most recent call last):

...

DoesNotExist: Publisher matching query does not exist.

数据排序

在运行前面的例子中,你可能已经注意到返回的结果是无序的。我们还没有告诉数据库怎样对结果进行排序,所以我们返回的结果是无序的。

当然,我们不希望在页面上列出的出版商的列表是杂乱无章的。我们用order_by()排列返回的数据:

>>> Publisher.objects.order_by("name")

[<Publisher: Apress Publishing>, <Publisher: Addison-Wesley>, <Publisher: O'Reilly>]


跟以前的all()例子差不多,SQL语句里多了指定排序的部分:

SELECT

id, name, address, city, state_province, country, website

FROM book_publisher

ORDER BY name;

我们可以对任意字段进行排序:

>>> Publisher.objects.order_by("address")

[<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher: Addison-Wesley>]

>>> Publisher.objects.order_by("state_province")

[<Publisher: Apress Publishing>, <Publisher: Addison-Wesley>, <Publisher: O'Reilly>]

多个字段也没问题:

>>> Publisher.objects.order_by("state_provice", "address")

[<Publisher: Apress Publishing>, <Publisher: O'Reilly>, <Publisher: Addison-Wesley>]

我们还可以指定逆向排序,在前面加一个减号-前缀:

>>> Publisher.objects.order_by("-name")

[<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher: Addison-Wesley>]

每次都要用order_by()显得有点啰嗦。大多数时间你通常只会对某些字段进行排序。在这种情况下,Django让你可以指定模型的缺省排序方式:

class Publisher(models.Model):

name = models.CharField(maxlength=30)

address = models.CharField(maxlength=50)

city = models.CharField(maxlength=60)

state_province = models.CharField(maxlength=30)

country = models.CharField(maxlength=50)

website = models.URLField()

def __str__(self):

return self.name

**class Meta:**

**ordering = ["name"]**


这个ordering = ["name"]告诉Django如果没有显示提供order_by(),就缺省按名称排序。

Meta是什么?

Django使用内部类Meta存放用于附加描述该模型的元数据。这个类完全可以不实现,不过他能做很多非常有用的事情。查看附录B,在Meta项下面,获得更多选项信息,

排序

你已经知道怎么过滤数据了,现在让我们来排序它们。你可以同时做这过滤和排序,很简单,就象这样:

>>> Publisher.objects.filter(country="U.S.A.").order_by("-name")

[<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher: Addison-Wesley>]

你应该没猜错,转换成SQL查询就是WHEREORDER BY的组合:

SELECT

id, name, address, city, state_province, country, website

FROM book_publisher

WHERE country = 'U.S.A'

ORDER BY name DESC;


你可以任意把它们串起来,多长都可以,这里没有限制。

限制返回的数据

另一个常用的需求就是取出固定数目的记录。想象一下你有成千上万的出版商在你的数据库里,但是你只想显示第一个。你可以这样做:

>>> Publisher.objects.all()[0]

<Publisher: Addison-Wesley>

这相当于:

SELECT

id, name, address, city, state_province, country, website

FROM book_publisher

ORDER BY name

LIMIT 1;

还有更多

我们只是刚接确到模型的皮毛,你还必须了解更多的内容以便理解以后的范例。具体请看附录C

删除对象

要删除对象,只需简单的调用对象的delete()方法:

>>> p = Publisher.objects.get(name="Addison-Wesley")

>>> p.delete()

>>> Publisher.objects.all()

[<Publisher: Apress Publishing>, <Publisher: O'Reilly>]

你还可以批量删除对象,通过对查询的结果调用delete()方法:

>>> publishers = Publisher.objects.all()

>>> publishers.delete()

>>> Publisher.objects.all()

[]

注意

删除是不可恢复的,所以要小心操作!事实上,应该尽量避免删除对象,除非你确实需要删除它。数据库的数据恢复的功能通常不太好,而从备份数据恢复是很痛苦的。

通常更好的方法是给你的数据模型添加激活标志。你可以只在激活的对象中查找,对于不需要的对象,将激活字段值设为False,而不是删除对象。这样,如果一旦你认为做错了的话,只需把标志重设回来就可以了。

修改数据库表结构

当我们在这一章的前面介绍syncdb命令的时候,我们强调syncdb仅仅创建数据库中不存在的表,而不会同步模型的修改或者删除到数据库。如果你添加或者修改了模型的一个字段,或者删除一个模型,你必须手动改变你的数据库。下面我们看看怎么来做。

当我们处理表结构的修改时,要时刻想着 Django的数据库层是如何工作的:

§ 如果模型中包含一个在数据库中并不存在的字段,Django会大声抱怨的。这样当你第一次调用Django的数据库API来查询给定的表时就会出错(也就是说,它会在执行的时候出错,而不是编译的时候)

§ Django并不关心数据库表中是否存在没有在模型中定义的列

§ Django并不关心数据库中是否包含没有模型描述的表

修改表结构也就是按照正确的顺序修改各种Python代码和数据库本身

添加字段

当按照产品需求向一个表/模型添加字段时,Django不关心一个表的列是否在模型中定义,我们可以利用这个小技巧,先在数据库中添加列,然后再改变模型中对应的字段。

然而,这里总是存在先有鸡还是先有蛋的问题,为了弄清新的数据列怎么用SQL描述,你需要查看manage.py sqlall的执行结果,它列出了模型中已经存在的字段。(注意:你不需要像Django中的SQL一模一样的创建你的列,但是这确实是一个好主意,从而保证所有都是同步的)

解决鸡和蛋的问题的方法就是先在开发环境而不是服务器上的产品上修改。(你现在用的就是测试/开发环境,不是吗?)下面是详细的步骤。

首先,在开发环境中执行下面的步骤(也就是说,不是在发布服务器上):

1. 把这个字段添加到你的模型中.

2. 运行manage.py sqlall [yourapp]会看到模型的新的CREATE TABLE语句。注意新的字段的列定义。

3. 启动您的数据库交互shell(也就是psqlmysql或者您也可以使用manage.py dbshell)执行一个ALTER TABLE语句,添加您的新列。

4. (可选)manage.py shell启动Python交互式shell,并通过引入模型并选择表验证新的字段已被正确添加(比如,MyModel.objects.all()[:5])

然后在发布服务器上执行下面的步骤:

1. 启动你的数据库的交互式命令行;

2. 执行ALTER TABLE语句,也就是在开发环境中第3步执行的语句;

3. 添加字段到你的模型中。如果你在开发时使用了版本控制系统并checkin了你的修改,现在可以更新代码到发布服务器上了(例如,使用Subverison的话就是svn update)

4. 重启Web服务器以使代码修改生效。

例如,让我们通过给Book模型添加一个num_pages字段来演示一下。首先,我们在开发环境中这样修改模型:

class Book(models.Model):

title = models.CharField(maxlength=100)

authors = models.ManyToManyField(Author)

publisher = models.ForeignKey(Publisher)

publication_date = models.DateField()

**num_pages = models.IntegerField(blank=True, null=True)**

def __str__(self):

return self.title


(注意:我们这里为什么写blank=Truenull=True呢?阅读题为添加非空字段的侧边栏获取更多信息。)

然后我们运行命令manage.py sqlall books来得到CREATE TABLE语句。它们看起来是这样的:

CREATE TABLE "books_book" (

"id" serial NOT NULL PRIMARY KEY,

"title" varchar(100) NOT NULL,

"publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id"),

"publication_date" date NOT NULL,

"num_pages" integer NULL

);

新加的字段SQL描述是这样的:

"num_pages" integer NULL

接下来,我们启动数据库交互命令界面,例如Postgresql是执行psql,并执行下面的语句:

ALTER TABLE books_book ADD COLUMN num_pages integer;

添加非空字段(NOT NULL

这里有一个要注意的地方。在添加num_pages字段时我们使用了blank=Truenull=True可选项。我们之所以这么做是因为在数据库创建时我们想允许字段值为NULL

当然,也可以在添加字段是设置值不能为NULL。要实现这个,你不得不先创建一个NULL字段,使用缺省值,再修改字段到NOT NULL。例如:

BEGIN;

ALTER TABLE books_book ADD COLUMN num_pages integer;

UPDATE books_book SET num_pages=0;

ALTER TABLE books_book ALTER COLUMN num_pages SET NOT NULL;

COMMIT;

如果你这样做了,记得要把blank=Truenull=True从你的模型中拿掉。

执行完ALTER TABLE语句,我们确认一下修改是否正确,启动Python交互界面并执行下面语句:

>>> from mysite.books.models import Book

>>> Book.objects.all()[:5]

如果没有错误,我们就可以转到发布服务器来在数据库上执行ALTER TABLE语句了。然后,再更新模型并重启WEB服务器。

删除字段

从模型里删除一个字段可要比增加它简单多了。删除一个字段仅需要做如下操作:

从你的模型里删除这个字段,并重启Web服务器。

使用如下面所示的命令,从你的数据库中删掉该列:

ALTER TABLE books_book DROP COLUMN num_pages;

删除 Many-to-Many字段

因为many-to-many字段同普通字段有些不同,它的删除过程也不一样:

删除掉你的模型里的ManyToManyField,并且重启Web服务器。

使用如下面所示的命令,删除掉你数据库里的many-to-many表:

DROP TABLE books_books_publishers;

删除模型

完全删除一个模型就像删除一个字段一样简单。删除模型仅需要做如下步骤:

将此模型从你的models.py文件里删除,并且重启Web服务器。

使用如下的命令,将此表从你的数据库中删除:

DROP TABLE books_book;

转载请注明文章出处:http://blog.csdn.net/wolaiye320/article/details/51840854

你可能感兴趣的:(python,django)