在第三章,我們講述了用 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 這樣的明確定義模式的主要用于改善開發人員之間的溝通。 比起告訴同事,“讓我們采用抽象的數據存取方式,然后單獨劃分一層來顯示數據,并且在中間加上一個控制它的層”,一個通用的說法會讓你收益,你只需要說:“我們在這里使用MVC模式吧。”。
Django 緊緊地遵循這種 MVC 模式,可以稱得上是一種 MVC 框架。 以下是 Django 中 M、V 和 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/。 |
| 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 manage.py shell`` 來進行測試。 (我們上一章提到過在,`` manager.py shell`` 命令是以正確Django配置啟用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?)。Django并不自帶適配器,所以你得自己下載安裝。 |
| _____ 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_-一個包含模型,視圖和Django代碼,并且形式為獨立Python包的完整Django應用。
在這里要先解釋一些術語,初學者可能會混淆它們。 在第二章我們已經創建了?_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的文件,編寫了一些函數并在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。這就是你Django app的基礎。
## 在Python代碼里定義模型
我們早些時候談到。MTV里的M代表模型。 Django模型是用Python代碼形式表述的數據在數據庫中的定義。 對數據層來說它等同于 CREATE TABLE 語句,只不過執行的是Python代碼而不是 SQL,而且還包含了比數據庫字段定義更多的含義。 Django用模型在后臺執行SQL代碼并把結果用Python的數據結構來描述。 Django也使用模型來呈現SQL無法處理的高級概念。
如果你對數據庫很熟悉,你可能馬上就會想到,用Python?_和_?SQL來定義數據模型是不是有點多余? Django這樣做是有下面幾個原因的:
自省(運行時自動識別數據庫)會導致過載和有數據完整性問題。 為了提供方便的數據訪問API, Django需要以?_某種方式_?知道數據庫層內部信息,有兩種實現方式。 第一種方式是用Python明確地定義數據模型,第二種方式是通過自省來自動偵測識別數據模型。
第二種方式看起來更清晰,因為數據表信息只存放在一個地方-數據庫里,但是會帶來一些問題。 首先,運行時掃描數據庫會帶來嚴重的系統過載。 如果每個請求都要掃描數據庫的表結構,或者即便是 服務啟動時做一次都是會帶來不能接受的系統過載。 (有人認為這個程度的系統過載是可以接受的,而Django開發者的目標是盡可能地降低框架的系統過載)。第二,某些數據庫,尤其是老版本的MySQL,并未完整存儲那些精確的自省元數據。
編寫Python代碼是非常有趣的,保持用Python的方式思考會避免你的大腦在不同領域來回切換。 盡可能的保持在單一的編程環境/思想狀態下可以幫助你提高生產率。 不得不去重復寫SQL,再寫Python代碼,再寫SQL,…,會讓你頭都要裂了。
把數據模型用代碼的方式表述來讓你可以容易對它們進行版本控制。 這樣,你可以很容易了解數據層 的變動情況。
SQL只能描述特定類型的數據字段。 例如,大多數數據庫都沒有專用的字段類型來描述Email地址、URL。 而用Django的模型可以做到這一點。 好處就是高級的數據類型帶來更高的效率和更好的代碼復用。
SQL還有在不同數據庫平臺的兼容性問題。 發布Web應用的時候,使用Python模塊描述數據庫結構信息可以避免為MySQL, PostgreSQL, and SQLite編寫不同的CREATE?TABLE。
當然,這個方法也有一個缺點,就是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 包含了所有必要的和數據庫交互的方法,并提供了一個簡潔漂亮的定義數據庫字段的語法。 信不信由你,這些就是我們需要編寫的通過Django存取基本數據的所有代碼。
每個模型相當于單個數據庫表,每個屬性也是這個表中的一個字段。 屬性名就是字段名,它的類型(例如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會自動為每個模型生成一個自增長的整數主鍵字段每個Django模型都要求有單獨的主鍵。id
## 模型安裝
完成這些代碼之后,現在讓我們來在數據庫中創建這些表。 要完成該項工作,第一步是在 Django 項目中?_激活_這些模型。 將?books?app 添加到配置文件的已安裝應用列表中即可完成此步驟。
再次編輯?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?命令檢查你的模型的語法和邏輯是否正確。 如果一切正常,你會看到?0?errors?found?消息。如果出錯,請檢查你輸入的模型代碼。 錯誤輸出會給出非常有用的錯誤信息來幫助你修正你的模型。
一旦你覺得你的模型可能有問題,運行?python?manage.py?validate?。 它可以幫助你捕獲一些常見的模型定義錯誤。
模型確認沒問題了,運行下面的命令來生成?CREATE?TABLE?語句(如果你使用的是Unix,那么可以啟用語法高亮):
~~~
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?)的組合。你可以參考附錄B重寫這個規則。
* 我們前面已經提到,Django為每個表格自動添加加了一個?id?主鍵, 你可以重新設置它。
* 按約定,Django添加?"_id"?后綴到外鍵字段名。 你猜對了,這個同樣是可以自定義的。
* 外鍵是用?REFERENCES?語句明確定義的。
* 這些?CREATE?TABLE?語句會根據你的數據庫而作調整,這樣象數據庫特定的一些字段例如:(MySQL),auto_increment(PostgreSQL),serial(SQLite),都會自動生成。integer?primary?key同樣的,字段名稱也是自動處理(例如單引號還好是雙引號)。 例子中的輸出是基于PostgreSQL語法的。
sqlall?命令并沒有在數據庫中真正創建數據表,只是把SQL語句段打印出來,這樣你可以看到Django究竟會做些什么。 如果你想這么做的話,你可以把那些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?并?_不能_將模型的修改或刪除同步到數據庫;如果你修改或刪除了一個模型,并想把它提交到數據庫,syncdb并不會做出任何處理。 (更多內容請查看本章最后的“修改數據庫的架構”一段。)
如果你再次運行?python?manage.py?syncdb?,什么也沒發生,因為你沒有添加新的模型或者 添加新的app。因此,運行python?manage.py?syncdb總是安全的,因為它不會重復執行SQL語句。
如果你有興趣,花點時間用你的SQL客戶端登錄進數據庫服務器看看剛才Django創建的數據表。 你可以手動啟動命令行客戶端(例如,執行PostgreSQL的`` psql`` 命令),也可以執行 `` python manage.py dbshell`` ,這個命令將依據`` DATABASE_SERVER`` 的里設置自動檢測使用哪種命令行客戶端。 常言說,后來者居上。
## 基本數據訪問
一旦你創建了模型,Django自動為這些模型提供了高級的Python API。 運行?python?manage.py?shell?并輸入下面的內容試試看:
~~~
>>> from books.models import Publisher
>>> p1 = Publisher(name='Apress', address='2855 Telegraph Avenue',
... city='Berkeley', state_province='CA', 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`` 類的實例并設置了字段`` name, address`` 等的值。
* 調用該對象的?save()?方法,將對象保存到數據庫中。 Django 會在后臺執行一條?INSERT?語句。
* 最后,使用`` Publisher.objects`` 屬性從數據庫取出出版商的信息,這個屬性可以認為是包含出版商的記錄集。 這個屬性有許多方法, 這里先介紹調用`` Publisher.objects.all()`` 方法獲取數據庫中`` Publisher`` 類的所有對象。這個操作的幕后,Django執行了一條SQL `` SELECT`` 語句。
這里有一個值得注意的地方,在這個例子可能并未清晰地展示。 當你使用Django modle API創建對象時Django并未將對象保存至數據庫內,除非你調用`` save()`` 方法:
~~~
p1 = Publisher(...)
# At this point, p1 is not saved to the database yet!
p1.save()
# Now it is.
~~~
如果需要一步完成對象的創建與存儲至數據庫,就使用`` objects.create()`` 方法。 下面的例子與之前的例子等價:
~~~
>>> p1 = Publisher.objects.create(name='Apress',
... address='2855 Telegraph Avenue',
... city='Berkeley', state_province='CA', country='U.S.A.',
... website='http://www.apress.com/')
>>> p2 = Publisher.objects.create(name="O'Reilly",
... address='10 Fawcett St.', city='Cambridge',
... state_province='MA', country='U.S.A.',
... website='http://www.oreilly.com/')
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
~~~
當然,你肯定想執行更多的Django數據庫API試試看,不過,還是讓我們先解決一點煩人的小問題。
## 添加模塊的字符串表現
當我們打印整個publisher列表時,我們沒有得到想要的有用信息,無法把[``](http://docs.30c.org/djangobook2/chapter05/#id9)[``](http://docs.30c.org/djangobook2/chapter05/#id11)對象區分開來:
~~~
[<Publisher: Publisher object>, <Publisher: Publisher object>]
~~~
我們可以簡單解決這個問題,只需要為Publisher?對象添加一個方法?__unicode__()?。?__unicode__()?方法告訴Python如何將對象以unicode的方式顯示出來。 為以上三個模型添加__unicode__()方法后,就可以看到效果了:
~~~
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()
def __unicode__(self):
return self.name
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
def __unicode__(self):
return u'%s %s' % (self.first_name, self.last_name)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
def __unicode__(self):
return self.title
~~~
就象你看到的一樣,?__unicode__()?方法可以進行任何處理來返回對一個對象的字符串表示。?Publisher和Book對象的__unicode__()方法簡單地返回各自的名稱和標題,Author對象的__unicode__()方法則稍微復雜一些,它將first_name和last_name字段值以空格連接后再返回。
對__unicode__()的唯一要求就是它要返回一個unicode對象 如果`` __unicode__()`` 方法未返回一個Unicode對象,而返回比如說一個整型數字,那么Python將拋出一個`` TypeError`` 錯誤,并提示:”coercing to Unicode: need string or buffer, int found” 。
> Unicode對象
> 什么是Unicode對象呢?
> 你可以認為unicode對象就是一個Python字符串,它可以處理上百萬不同類別的字符——從古老版本的Latin字符到非Latin字符,再到曲折的引用和艱澀的符號。
> 普通的python字符串是經過_編碼_的,意思就是它們使用了某種編碼方式(如ASCII,ISO-8859-1或者UTF-8)來編碼。 如果你把奇特的字符(其它任何超出標準128個如0-9和A-Z之類的ASCII字符)保存在一個普通的Python字符串里,你一定要跟蹤你的字符串是用什么編碼的,否則這些奇特的字符可能會在顯示或者打印的時候出現亂碼。 當你嘗試要將用某種編碼保存的數據結合到另外一種編碼的數據中,或者你想要把它顯示在已經假定了某種編碼的程序中的時候,問題就會發生。 我們都已經見到過網頁和郵件被???弄得亂七八糟。 ?????? 或者其它出現在奇怪位置的字符:這一般來說就是存在編碼問題了。
> 但是Unicode對象并沒有編碼。它們使用Unicode,一個一致的,通用的字符編碼集。 當你在Python中處理Unicode對象的時候,你可以直接將它們混合使用和互相匹配而不必去考慮編碼細節。
> Django 在其內部的各個方面都使用到了 Unicode 對象。 模型 對象中,檢索匹配方面的操作使用的是 Unicode 對象,視圖 函數之間的交互使用的是 Unicode 對象,模板的渲染也是用的 Unicode 對象。 通常,我們不必擔心編碼是否正確,后臺會處理的很好。
注意,我們這里只是對Unicode對象進行非常淺顯的概述,若要深入了解你可能需要查閱相關的資料。 這是一個很好的起點:http://www.joelonsoftware.com/articles/Unicode.html。
為了讓我們的修改生效,先退出Python Shell,然后再次運行?python?manage.py?shell?進入。(這是保證代碼修改生效的最簡單方法。)現在`` Publisher``對象列表容易理解多了。
~~~
>>> from books.models import Publisher
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
[<Publisher: Apress>, <Publisher: O'Reilly>]
~~~
請確保你的每一個模型里都包含?__unicode__()?方法,這不只是為了交互時方便,也是因為 Django會在其他一些地方用?__unicode__()?來顯示對象。
最后,?__unicode__()?也是一個很好的例子來演示我們怎么添加?_行為_?到模型里。 Django的模型不只是為對象定義了數據庫表的結構,還定義了對象的行為。?__unicode__()?就是一個例子來演示模型知道怎么顯示它們自己。
## 插入和更新數據
你已經知道怎么做了: 先使用一些關鍵參數創建對象實例,如下:
~~~
>>> p = Publisher(name='Apress',
... address='2855 Telegraph Ave.',
... city='Berkeley',
... state_province='CA',
... country='U.S.A.',
... website='http://www.apress.com/')
~~~
這個對象實例并?_沒有_?對數據庫做修改。 在調用`` save()`` 方法之前,記錄并沒有保存至數據庫,像這樣:
~~~
>>> p.save()
~~~
在SQL里,這大致可以轉換成這樣:
~~~
INSERT INTO books_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()?將不會創建新的記錄,而只是修改記錄內容(也就是 執行?UPDATE?SQL語句,而不是INSERT?語句):
~~~
>>> p.name = 'Apress Publishing'
>>> p.save()
~~~
前面執行的?save()?相當于下面的SQL語句:
~~~
UPDATE books_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;
~~~
注意,并不是只更新修改過的那個字段,所有的字段都會被更新。 這個操作有可能引起競態條件,這取決于你的應用程序。 請參閱后面的“更新多個對象”小節以了解如何實現這種輕量的修改(只修改對象的部分字段)。
~~~
UPDATE books_publisher SET
name = 'Apress Publishing'
WHERE id=52;
~~~
## 選擇對象
當然,創建新的數據庫,并更新之中的數據是必要的,但是,對于 Web 應用程序來說,更多的時候是在檢索查詢數據庫。 我們已經知道如何從一個給定的模型中取出所有記錄:
~~~
>>> Publisher.objects.all()
[<Publisher: Apress>, <Publisher: O'Reilly>]
~~~
這相當于這個SQL語句:
~~~
SELECT id, name, address, city, state_province, country, website
FROM books_publisher;
~~~
> 注意
> 注意到Django在選擇所有數據時并沒有使用?SELECT*?,而是顯式列出了所有字段。 設計的時候就是這樣:SELECT*?會更慢,而且最重要的是列出所有字段遵循了Python 界的一個信條: 明言勝于暗示。
有關Python之禪(戒律) :-),在Python提示行輸入?import?this?試試看。
讓我們來仔細看看?Publisher.objects.all()?這行的每個部分:
> 首先,我們有一個已定義的模型?Publisher?。沒什么好奇怪的: 你想要查找數據, 你就用模型來獲得數據。
>
> 然后,是objects屬性。 它被稱為管理器,我們將在第10章中詳細討論它。 目前,我們只需了解管理器管理著所有針對數據包含、還有最重要的數據查詢的表格級操作。
>
> 所有的模型都自動擁有一個?objects?管理器;你可以在想要查找數據時使用它。
>
> 最后,還有?all()?方法。這個方法返回返回數據庫中所有的記錄。 盡管這個對象 看起來 象一個列表(list),它實際是一個 QuerySet 對象, 這個對象是數據庫中一些記錄的集合。 附錄C將詳細描述QuerySet。 現在,我們就先當它是一個仿真列表對象好了。
所有的數據庫查找都遵循一個通用模式:
### 數據過濾
我們很少會一次性從數據庫中取出所有的數據;通常都只針對一部分數據進行操作。 在Django API中,我們可以使用`` filter()`` 方法對數據進行過濾:
~~~
>>> Publisher.objects.filter(name='Apress')
[<Publisher: Apress>]
~~~
filter()?根據關鍵字參數來轉換成?WHERE?SQL語句。 前面這個例子 相當于這樣:
~~~
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name = 'Apress';
~~~
你可以傳遞多個參數到?filter()?來縮小選取范圍:
~~~
>>> Publisher.objects.filter(country="U.S.A.", state_province="CA")
[<Publisher: Apress>]
~~~
多個參數會被轉換成?AND?SQL從句, 因此上面的代碼可以轉化成這樣:
~~~
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = 'U.S.A.'
AND state_province = 'CA';
~~~
注意,SQL缺省的?=?操作符是精確匹配的, 其他類型的查找也可以使用:
~~~
>>> Publisher.objects.filter(name__contains="press")
[<Publisher: Apress>]
~~~
在?_name_?和?contains?之間有雙下劃線。和Python一樣,Django也使用雙下劃線來表明會進行一些魔術般的操作。這里,contains部分會被Django翻譯成LIKE語句:
~~~
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name LIKE '%press%';
~~~
其他的一些查找類型有:icontains(大小寫無關的LIKE),startswith和endswith, 還有range(SQLBETWEEN查詢)。 附錄C詳細描述了所有的查找類型。
### 獲取單個對象
上面的例子中`` filter()`` 函數返回一個記錄集,這個記錄集是一個列表。 相對列表來說,有些時候我們更需要獲取單個的對象, `` get()`` 方法就是在此時使用的:
~~~
>>> Publisher.objects.get(name="Apress")
<Publisher: Apress>
~~~
這樣,就返回了單個對象,而不是列表(更準確的說,QuerySet)。 所以,如果結果是多個對象,會導致拋出異常:
>>> Publisher.objects.get(country="U.S.A.")
Traceback (most recent call last):
...
MultipleObjectsReturned: get() returned more than one Publisher --
it returned 2! Lookup parameters were {'country': 'U.S.A.'}
如果查詢沒有返回結果也會拋出異常:
~~~
>>> Publisher.objects.get(name="Penguin")
Traceback (most recent call last):
...
DoesNotExist: Publisher matching query does not exist.
~~~
這個?DoesNotExist?異常 是 Publisher 這個 model 類的一個屬性,即?Publisher.DoesNotExist。在你的應用中,你可以捕獲并處理這個異常,像這樣:
~~~
try:
p = Publisher.objects.get(name='Apress')
except Publisher.DoesNotExist:
print "Apress isn't in the database yet."
else:
print "Apress is in the database."
~~~
### 數據排序
在運行前面的例子中,你可能已經注意到返回的結果是無序的。 我們還沒有告訴數據庫 怎樣對結果進行排序,所以我們返回的結果是無序的。
在你的 Django 應用中,你或許希望根據某字段的值對檢索結果排序,比如說,按字母順序。 那么,使用order_by()?這個方法就可以搞定了。
~~~
>>> Publisher.objects.order_by("name")
[<Publisher: Apress>, <Publisher: O'Reilly>]
~~~
跟以前的?all()?例子差不多,SQL語句里多了指定排序的部分:
~~~
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name;
~~~
我們可以對任意字段進行排序:
~~~
>>> Publisher.objects.order_by("address")
[<Publisher: O'Reilly>, <Publisher: Apress>]
>>> Publisher.objects.order_by("state_province")
[<Publisher: Apress>, <Publisher: O'Reilly>]
~~~
如果需要以多個字段為標準進行排序(第二個字段會在第一個字段的值相同的情況下被使用到),使用多個參數就可以了,如下:
~~~
>>> Publisher.objects.order_by("state_province", "address")
[<Publisher: Apress>, <Publisher: O'Reilly>]
~~~
我們還可以指定逆向排序,在前面加一個減號?-?前綴:
~~~
>>> Publisher.objects.order_by("-name")
[<Publisher: O'Reilly>, <Publisher: Apress>]
~~~
盡管很靈活,但是每次都要用?order_by()?顯得有點啰嗦。 大多數時間你通常只會對某些 字段進行排序。 在這種情況下,Django讓你可以指定模型的缺省排序方式:
~~~
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()
def __unicode__(self):
return self.name
class Meta:
ordering = ['name']
~~~
現在,讓我們來接觸一個新的概念。?class?Meta,內嵌于?Publisher?這個類的定義中(如果?class?Publisher是頂格的,那么?class?Meta?在它之下要縮進4個空格--按 Python 的傳統 )。你可以在任意一個 模型 類中使用?Meta?類,來設置一些與特定模型相關的選項。 在 附錄B 中有?Meta?中所有可選項的完整參考,現在,我們關注?ordering?這個選項就夠了。 如果你設置了這個選項,那么除非你檢索時特意額外地使用了?order_by(),否則,當你使用 Django 的數據庫 API 去檢索時,Publisher對象的相關返回值默認地都會按?name?字段排序。
### 連鎖查詢
我們已經知道如何對數據進行過濾和排序。 當然,通常我們需要同時進行過濾和排序查詢的操作。 因此,你可以簡單地寫成這種“鏈式”的形式:
~~~
>>> Publisher.objects.filter(country="U.S.A.").order_by("-name")
[<Publisher: O'Reilly>, <Publisher: Apress>]
~~~
你應該沒猜錯,轉換成SQL查詢就是?WHERE?和?ORDER?BY?的組合:
~~~
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = 'U.S.A'
ORDER BY name DESC;
~~~
### 限制返回的數據
另一個常用的需求就是取出固定數目的記錄。 想象一下你有成千上萬的出版商在你的數據庫里, 但是你只想顯示第一個。 你可以使用標準的Python列表裁剪語句:
~~~
>>> Publisher.objects.order_by('name')[0]
<Publisher: Apress>
~~~
這相當于:
~~~
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
LIMIT 1;
~~~
類似的,你可以用Python的range-slicing語法來取出數據的特定子集:
~~~
>>> Publisher.objects.order_by('name')[0:2]
~~~
這個例子返回兩個對象,等同于以下的SQL語句:
~~~
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
OFFSET 0 LIMIT 2;
~~~
注意,不支持Python的負索引(negative slicing):
~~~
>>> Publisher.objects.order_by('name')[-1]
Traceback (most recent call last):
...
AssertionError: Negative indexing is not supported.
~~~
雖然不支持負索引,但是我們可以使用其他的方法。 比如,稍微修改 order_by() 語句來實現:
~~~
>>> Publisher.objects.order_by('-name')[0]
~~~
### 更新多個對象
在“插入和更新數據”小節中,我們有提到模型的save()方法,這個方法會更新一行里的所有列。 而某些情況下,我們只需要更新行里的某幾列。
例如說我們現在想要將Apress Publisher的名稱由原來的”Apress”更改為”Apress Publishing”。若使用save()方法,如:
~~~
>>> p = Publisher.objects.get(name='Apress')
>>> p.name = 'Apress Publishing'
>>> p.save()
~~~
這等同于如下SQL語句:
~~~
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name = 'Apress';
UPDATE books_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;
~~~
(注意在這里我們假設Apress的ID為52)
在這個例子里我們可以看到Django的save()方法更新了不僅僅是name列的值,還有更新了所有的列。 若name以外的列有可能會被其他的進程所改動的情況下,只更改name列顯然是更加明智的。 更改某一指定的列,我們可以調用結果集(QuerySet)對象的update()方法: 示例如下:
~~~
>>> Publisher.objects.filter(id=52).update(name='Apress Publishing')
~~~
與之等同的SQL語句變得更高效,并且不會引起競態條件。
~~~
UPDATE books_publisher
SET name = 'Apress Publishing'
WHERE id = 52;
~~~
update()方法對于任何結果集(QuerySet)均有效,這意味著你可以同時更新多條記錄。 以下示例演示如何將所有Publisher的country字段值由’U.S.A’更改為’USA’:
~~~
>>> Publisher.objects.all().update(country='USA')
2
~~~
update()方法會返回一個整型數值,表示受影響的記錄條數。 在上面的例子中,這個值是2。
## 刪除對象
刪除數據庫中的對象只需調用該對象的delete()方法即可:
~~~
>>> p = Publisher.objects.get(name="O'Reilly")
>>> p.delete()
>>> Publisher.objects.all()
[<Publisher: Apress Publishing>]
~~~
同樣我們可以在結果集上調用delete()方法同時刪除多條記錄。這一點與我們上一小節提到的update()方法相似:
~~~
>>> Publisher.objects.filter(country='USA').delete()
>>> Publisher.objects.all().delete()
>>> Publisher.objects.all()
[]
~~~
刪除數據時要謹慎! 為了預防誤刪除掉某一個表內的所有數據,Django要求在刪除表內所有數據時顯示使用all()。 比如,下面的操作將會出錯:
~~~
>>> Publisher.objects.delete()
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Manager' object has no attribute 'delete'
~~~
而一旦使用all()方法,所有數據將會被刪除:
~~~
>>> Publisher.objects.all().delete()
~~~
如果只需要刪除部分的數據,就不需要調用all()方法。再看一下之前的例子:
~~~
>>> Publisher.objects.filter(country='USA').delete()
~~~
## 下一章
通過本章的學習,你應該可以熟練地使用Django模型來編寫一些簡單的數據庫應用程序。 在第十章我們將討論Django數據庫層的高級應用。
一旦你定義了你的模型,接下來就是要把數據導入數據庫里了。 你可能已經有現成的數據了,請看第十八章以獲得有關如何集成現有數據庫的建議。 也可能數據是用戶提供的,第七章中還會教你怎么處理用戶提交的數據。
有時候,你和你的團隊成員也需要手工輸入數據,這時候如果有一個基于Web的數據輸入和管理的界面就會很有幫助。 下一章將介紹解決手工錄入問題的方法——Django管理界面。