Django模型(model)详细介绍1

第5章 模型

在第三章,我们讲述了用 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数据库层正是致力于解决这些问题。 以下提前揭示了如何使用 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 这样的明确定义模式的主要用于改善开发人员之间的沟通。 与其告诉同事:

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

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

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

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

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

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

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

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

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

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

数据库配置

记住这些理念之后,让我们来开始 Django 数据库层的探索。 首先,我们需要做些初期配置;我们需要告诉Django使用什么数据库以及如何连接数据库。

我们将假定你已经完成了数据库服务器的安装和激活,并且已经在其中创建了数据库(例如,用 CREATEDATABASE 语句)。 如果你使用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/。
oracle Oracle cx_Oracle ,http://www.djangoproject.com/r/python-oracle/ .

要注意的是无论选择使用哪个数据库服务器,都必须下载和安装对应的数据库适配器。 访问表 5-1 中“所需适配器”一栏中的链接,可通过互联网免费获取这些适配器。 如果你使用Linux,你的发布包管理系统会提供方便包。 比如说查找`` python-postgresql`` 或者`` python-psycopg`` 的软件包。

配置示例:

  DATABASE_ENGINE = 'postgresql_psycopg2'
  

DATABASE_NAME 将数据库名称告知 Django 。 例如:

  DATABASE_NAME = 'mydb'
  

如果使用 SQLite,请对数据库文件指定完整的文件系统路径。 例如:

  DATABASE_NAME = '/home/django/mydata.db'
  

对于SQLite数据库的存放,这个例子将其存放在/home/django目录下,你可以使用任意对你来说最便捷的目录。

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

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

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

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

  DATABASE_HOST = '/var/run/mysql'
  

一旦在输入了那些设置并保存之后应当测试一下你的配置。 我们可以在`` mysite`` 项目目录下执行上章所提到的`` python manager.py shell`` 来进行测试。 (我们上一章有提到在正确的Django配置前提下,`` manager.py shell`` 命令是启用Python交互解释器的一种方法。) 这个方法在这里是很有必要的,因为Django可以通过它知晓数据库连接信息的配置文件

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

  >>> from django.db import connection
  
>>> cursor = connection.cursor()

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

表 5-2. 数据库配置错误信息
错误信息 解决方法
You haven’t set the DATABASE_ENGINE setting yet. 未正确配置`` DATABASE_ENGINE`` 的值。 表格 5-1 列出可用的值。
Environment variable DJANGO_SETTINGS_MODULE is undefined. 使用`` python manager.py shell`` 命令启动的交互解释器,而非`` python`` 命令启动的交互解释器。
Error loading _____ module: No module named _____. 未安装合适的数据库适配器 (例如, psycopg 或 MySQLdb ).
_____ isn’t 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_HOST和DATABASE_PORT是否已正确配置,并确认数据库服务器是否已正常运行。

第一个应用程序

你现在已经确认数据库连接正常工作了,让我们来创建一个 Django app ,开始编码模型和视图。

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

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

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

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

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

对于如何架构Django代码并没有快速成套的规则。 如果你只是建造一个简单的web站点,那么可能你只需要一个app就可以了; 但如果是一个包含许多不相关的模块的复杂的网站,例如电子商务和社区之类的站点,那么你可能需要把这些模块划分成不同的app,以便以后重用。

确实,你还可以不用创建app,例如以前写的视图,只是简单的放在 views.py 。 在之前那些例子中,我们只是简单的创建了一个称为views.py的文件,编写了一些函数并将在URLconf中设置各个函数的映射。 这些情况都不需要使用apps。

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

在`` mysite`` 项目文件下输入下面的命令来创建`` books`` app

  python manage.py startapp books
  

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

  books/
  
__init__.py
models.py
tests.py
views.py

这个目录包含了这个app的模型和视图。

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

在Python代码里定义模型

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

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

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

第二种方式看起来更清晰,因为数据表信息只存放在一个地方-数据库里,但是会带来一些问题。 首先,运行时扫描数据库会带来严重的系统过载。 如果每个请求都要扫描数据库的表结构,或者即便是 服务启动时做一次都是会带来不能接受的系统过载。 (有人认为这个程度的系统过载是可以接受的,而Django开发者的目标是尽可能地降低框架的系统过载)。第二,某些数据库,尤其是老版本的MySQL,并未完整存储那些精确的自省元数据。

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

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

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

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

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

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

第一个模型

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

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

  • 一个作者有姓,名字和email地址。

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

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

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

  from django.db import models
  

class Publisher(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()

class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()

class Book(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 包含了所有和数据库 打交道的方法,并提供了一个简洁漂亮的定义语法。

每个模型相当于单个数据库表,每个属性也是这个表中的一个字段。 属性名就是字段名,它的类型(例如CharField )相当于数据库的字段类型 (例如 varchar )。例如, Publisher 模块等同于下面这张表(用Postgresql 的 CREATE 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 项目中 激活 这些模型。 将 books app 添加到配置文件的已 installed apps 列表中即可完成此步骤。

再次编辑 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' 标识 books app。 INSTALLED_APPS 中的每个app都用 Python的路径描述,包的路径,用小数点(.)区分。

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

  python manage.py validate
  

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

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

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

  python manage.py sqlall books
  

在这个命令行中, books 是app的名称。 和你运行 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_author" (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(40) NOT NULL,
"email" varchar(75) 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") DEFERRABLE INITIALLY DEFERRED,
"publication_date" date NOT NULL
)
;
CREATE TABLE "books_book_authors" (
"id" serial NOT NULL PRIMARY KEY,
"book_id" integer NOT NULL REFERENCES "books_book" ("id") DEFERRABLE INITIALLY DEFERRED,
"author_id" integer NOT NULL REFERENCES "books_author" ("id") DEFERRABLE INITIALLY DEFERRED,
UNIQUE ("book_id", "author_id")
)
;
CREATE INDEX "books_book_publisher_id" ON "books_book" ("publisher_id");
COMMIT;

注意:

  • 自动生成的表名是app名称( books )和模型的小写名称 ( publisher book author )的组合。

  • 我们前面已经提到,Django为每个表格自动添加加了一个 id 主键, 你可以重新设置它。

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

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

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

sqlall 命令并没有在数据库中真正创建数据表,只是把SQL语句段打印出来。 如果要创建数据表,你可以复制那些SQL语句到你使用数据库客户端后执行,或者通过Unix管道直接进行操作(例如,`` python manager.py sqlall books | psql mydb`` )。不过,Django提供了一种更为简易的提交SQL语句至数据库的方法: `` syncdb`` 命令

  python manage.py syncdb
  

执行这个命令后,将看到类似以下的内容:

  Creating table books_publisher
  
Creating table books_author
Creating table books_book
Installing index for books.Book model

syncdb 命令是同步你的模型到数据库的一个简单方法。 它会根据 INSTALLED_APPS 里设置的app来检查数据库, 如果表不存在,它就会创建它。 需要注意的是, syncdb 并 不能 同步模型的修改到数据库。 (本章的最后将详细讨论修改数据库的架构)

如果你再次运行 python manage.py syncdb ,什么也没发生,因为你没有添加新的模型或者 添加新的app。

如果你有兴趣,花点时间用你的SQL客户端登录进数据库服务器看看刚才Django创建的数据表。 你可以手动启动命令行客户端(例如,执行PostgreSQL的`` psql`` 命令),也可以执行 `` python manage.py dbshell`` ,这个命令将依据`` DATABASE_SERVER`` 的里设置自动检测使用何种命令行客户端。 常言说,后来者居上。

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