編寫你的第一個 Django app,第二部分(Page 7)轉載請注明鏈接地址
本教程上接前面的教程。我們會配置數據,創建你的第一個 model,並對Django 自動生成的 admin 站點進行快速的介紹。
數據庫設置
現在,打開 mysite/settings.py。它是一個帶有模塊級變量的普通 Python 模塊,也是 Django 的配置文件。
默認情況下,配置中使用的是 SQLite,如果你是一個數據庫新手,或者你只是對 Django 剛興趣兒想嘗試一下,這是最簡單的選擇。Python 默認包含 SQLite,所以我們不需要安裝任何東西來支持數據庫。但是,當你開發你的第一個真正的項目,你希望使用像PostgreSQL一樣有更易於擴展的數據庫,避免為切換數據庫切換頭疼。
如果你希望使用其他數據庫,請安裝合適的數據庫綁定(就是像 pymysql 一樣用於鏈接數據庫的 python 模塊),並更改數據庫“default” 項下匹配你數據庫鏈接的設置:
- ENGINE —— 選擇'django.db.backends.sqlite3', 'django.db.backends.postgresql', 'django.db.backends.mysql', 或者 'django.db.backends.oracle'中的一個,當前,其他的數據庫后端也是可以的,點擊這里查看(這里少一個鏈接)。
- NAME —— 你的數據庫的名字,如果你使用 SQLite,數據庫會是你電腦上的一個文件;在這里,NAME(這里少一個鏈接)應該是一個包含文件名的完整的絕對路徑,默認值是 os.path.join(BASE_DIR, 'db.sqlite3'),會保存在你的項目目錄里。
如果你沒有使用 SQLite 作為你的數據庫,像 USER,PASSWORD 和 HOST 這些額外設置必須被添加。更多信息,請參考數據庫文檔這里少一個鏈接。
除 SQLite 以外的其他數據庫
如果你使用了 SQLite 之外的數據庫,確保你已經創建了一個數據庫,在數據庫的交互式解釋器里用“CREATE DATABASE database_name;”完成。
請確保配置在mysite/settings.py中的數據庫用戶有“create database”的權限,這樣在后面的教程里需要的時候,會自動創建一個測試數據庫(這里少一個鏈接)。
如果你使用的是 SQLite,你不需要提前創建任何東西 —— 數據庫文件會在需要的時候自動創建。
當你編輯 mysite/settings.py 的請將 TIME_ZONE(這里少一個鏈接) 設置為你的時區。
同時,注意注意配置文件頂部的 INSTALLED_APPS(這里少一個鏈接) 設置,它包含了在本項目實例中所有被激活的 django app的名字。app可以被用於多個項目,你給可以打包和分發它給其他人在他們的項目里使用。
默認情況下,INSTALLED_APPS(這里少一個鏈接)包含下面幾個app,他們都是 Django 自帶的:
- django.contrib.admin(這里少一個鏈接) – 提供管理功能,很快你就會用到它。
- django.contrib.auth(這里少一個鏈接) – 一個認證系統。
- django.contrib.contenttypes(這里少一個鏈接) –
- django.contrib.sessions (這里少一個鏈接)– 一個 session 框架
- django.contrib.messages(這里少一個鏈接) – 一個 messaging 框架.
- django.contrib.staticfiles(這里少一個鏈接) – 一個管理靜態文件的框架.
默認包含的這些app為一些常見的情況提供方便。
其中一些app需要使用至少一個數據庫表,因此,在我們使用它們之前,我們需要在數據庫中創建表。我們可以使用下面的命令來做到:
$ python manage.py migrate
migrate(這里少一個鏈接) 命令會依據 mysite/settings.py中的數據庫配置 和 INSTALLED_APPS(這里少一個鏈接) 的設置創建需要的數據庫表,將app的的數據遷移到數據庫(這個外面稍后在討論)。每次app遷移你都可以看到一條消息。如果你感興趣,運行數據庫的命令行客戶端,輸入\dt (PostgreSQL), SHOW TABLES; (MySQL), .schema (SQLite), or SELECT TABLE_NAME FROM USER_TABLES; (Oracle) 來顯示 Django 顯示數據庫表。(文檔中這里的標點似乎有些問題,我理解的是每個圓括號中的內容對應它前面的命令)。
對極簡主義者來說
就像我們上面說到的,app默認包含一些普通實例(就像 auth,user),但不是每個人都需要用到它們。如果你用不到他們,可以在運行migrate命令前在INSTALLED_APPS中注釋或刪除對應的行。migrate命令只會對INSTALLED_APPS中app 進行遷移。
創建模型(models)
現在我們開始定義你的模型 —— 本質上是帶有額外元數據的數據庫布局。
設計哲學
模型是唯一可靠的數據來源。它包含你存儲的數據的必要字段和行為。Django遵循DRY原則(這里少一個鏈接)。 我們的目標是在一個地方定義你的數據模型並從它那獲取數據。
這里包含的遷移 —— 和 Ruby On Rails 不同,例如:遷移完全源自你的 模型文件,本質是它只是一個歷史,Django 可以通過更新的數據庫模式(database schema)來匹配你的當前模型。
在我們簡單的 poll app中,我們創建了兩個模型,Question 和 Choice 。Question中有一個問題字段和一個發布日期字段。Choice有兩個字段:選擇的內容和投票統計,Choice中的每一個記錄都和Question 中的記錄有聯系。
這些概念相當於 簡單的 Python 類,編輯 polls/models.py,內容如下:
# polls/models.py
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
代碼很簡單,每個模型都是 django.db.models.Model(這里少一個鏈接)的一個子類。每個模型都有許多類變量,模型中的每一個變量對應一個數據庫字段。
每個字段都是 Field(這里少一個鏈接) 類的一個實例 —— 例如,CharField(這里少一個鏈接)用於字符字段,DateTimeField(這里少一個鏈接)用於日期時間字段。這可以告訴 Django 每個字段保存了什么類型的數據。
沒一個 Field(這里少一個鏈接) 實例(比如question_text 或 pub_date)的名字就是字段的名字,是對機器友好的格式。你的 Python 代碼中會使用這個值,你的數據庫會將其作為列的名字。
你可以使用 Field(這里少一個鏈接)可選的第一個位置參數來將其命名為一個人們更容易理解的名字。這在Django 的一些內省部分使用過,同時它也可以做為注釋文檔。如果這個字段未指定,Django 會使用一個讓機器易讀的名字。在這個例子中,我們只給 Question.pub_date 定義一個讓人類易讀的名字。這個模型中的其他所有字段,使用讓機器易讀的名字已經足以讓人也易讀。
一些Field(這里少一個鏈接) 需要指定參數,例如:CharField(這里少一個鏈接)需要你指定 max_length(這里少一個鏈接)。這不僅僅只用於數據庫模式(database schema),后面我們也用於驗證中。
一個Field(這里少一個鏈接)有很多可選的參數,這個例子中,我們設置 votes的默認值這里少一個鏈接)為0.
最后,注意使用 ForeignKey 時有一個關系被定義,它告訴 Django Choice中的每一條記錄都對應 Question中的一條記錄。Django 支持所有常用的數據庫關系:一對多,多對多,一對一。(many-to-one, many-to-many, and one-to-one.)
激活模型
一小部分代碼涉及了Django 的很多信息。通過它,Django 可以:
- 在這個app中創建數據庫模式(data schema)(
CREATE TABLE語句) - 創建用於訪問
Question和Choice對象的 Python數據庫鏈接 API
但是我們先告訴我們的項目 polls app已經被安裝。
設計哲學
Django app是可插拔的: 你可以在多個項目中使用同一個app,也可以發布app,因為他們不需要綁定到一個給定的 Django 安裝中。
我們項目中包含的app,我們需要在INSTALLED_APPS(這里少一個鏈接) 設置中添加一個到配置類的參考。PollsConfig 類在polls/apps.py中,所以它的引用路徑是:'polls.apps.PollsConfig'。編輯mysite/settings.py文件,添加app的路徑到INSTALLED_APPS配置中。就像下面一樣:
# mysite/settings.py
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
現在 Django 知道了要包含 polls app。我們來運行另外一個命令:
$ python manage.py makemigrations polls
你可以看到類似下面的內容:
Migrations for 'polls':
polls/migrations/0001_initial.py:
- Create model Choice
- Create model Question
- Add field question to choice
通過運行 makemigrations,你告訴了 django,你對你的模型做了一些更改(這個例子中,你創建了一個新的),並且你希望這個更改可以像遷移(migraion)一樣被存儲。
遷移如何更改你的 Django 存儲模型(以及數據庫模式)—— 它們只是磁盤上的文件,如果你願意,你可以在 polls/migrations/0001_initial.py文件中 閱讀新的模型的遷移。不要擔心,你不用每次 django 生成一個文件就閱讀一個文件,在這里,它們被設計成易於讓人編輯的,讓你可以手動調整 Django更改的內容。
有一個命令會自動為你運行遷移並管理你的數據庫模式 —— 它就是 migrate,我們一會再繼續說;首先,我們先看看遷移會執行哪些 SQL,sqlmigrate 命令后跟遷移的名字並返回他們執行的 SQL;
$ python manage.py sqlmigrate polls 0001
你會看到類似下面的內容(為了便於閱讀,我們對其進行了格式化)
BEGIN;
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
"id" serial NOT NULL PRIMARY KEY,
"choice_text" varchar(200) NOT NULL,
"votes" integer NOT NULL
);
--
-- Create model Question
--
CREATE TABLE "polls_question" (
"id" serial NOT NULL PRIMARY KEY,
"question_text" varchar(200) NOT NULL,
"pub_date" timestamp with time zone NOT NULL
);
--
-- Add field question to choice
--
ALTER TABLE "polls_choice" ADD COLUMN "question_id" integer NOT NULL;
ALTER TABLE "polls_choice" ALTER COLUMN "question_id" DROP DEFAULT;
CREATE INDEX "polls_choice_7aa0f6ee" ON "polls_choice" ("question_id");
ALTER TABLE "polls_choice"
ADD CONSTRAINT "polls_choice_question_id_246c99a640fbbd72_fk_polls_question_id"
FOREIGN KEY ("question_id")
REFERENCES "polls_question" ("id")
DEFERRABLE INITIALLY DEFERRED;
COMMIT;
注意下面的內容:
- 准確的輸出依賴於你正在使用的數據庫。上面生成的例子基於 PostgreSQL.
- 數據庫表名會結合app(polls)中的名字和模型的小寫名字的自動生成,——
question和choice。(你可以覆蓋該行為) - 主鍵(IDs)會被自動添加。(這個行為你也可以覆蓋)
- 按照慣例,Django 會在外鍵字段的名字中追加 “_id”。(這個行為你也可以覆蓋)
- 外鍵關系根據外鍵約束來明確創建。不要擔心
DEFERRABLE的部分,那只是告訴 PostgreSQL 在通信結束前不要強制使用外鍵。 - 它是針對你使用的數據庫定制的,像
auto_increment(MySQL),serial(PostgreSQL), orinteger primary key autoincrement(SQLite) 這些特定的數據庫字段類型會為你自動處理。字段名稱的引用也是如此 —— 例如,使用說引號或者單引號。 - 實際上
sqlmigrate命令並不會在你的數據庫上執行遷移 —— 它只是將你可以看到的 Django 認為需要執行的 SQL 打印到屏幕。它用來檢查 Django 會如何做 或者 你是否需要更改 SQL 腳本的數據庫管理員權限。
如果你感興趣,你可還可以運行 python manage.py check;這可以在不做遷移或鏈接數據庫的情況下檢查問題。
現在再次運行 mingrate 來在你的數據庫中創建這些模型。
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
Rendering model states... DONE
Applying polls.0001_initial... OK
mingrate 命令會執行所有尚未被應用的遷移(Django 在數據庫中使用一個叫 django_migrations 的特殊表來追蹤哪些遷移被應用),並在數據庫中運行他們 —— 本質上,使用數據庫中的模式來同步更改到你的模型中。
遷移非常強大,可以讓你隨着時間推移更改你的模型,當你開發你的項目時,不需要刪除你的數據庫或者表后 再創建新的 —— 他們專門在不丟失數據庫的情況下升級你的數據庫。我們會在后面的教程中深入研究它,而現在,記住創建模型變更的散步操作:
- 更改你的模型 (在 models.py 中)
- 運行 python manage.py makemigrations 為這些變更創建遷移
- 運行 python manage.py migrate 將這些變更應用到數據庫
分離創建遷移和應用遷移命令的原因是你會提交遷移到你的版本控制系統並把它們和你的 app 一起運送;這不僅僅使你的開發更容易,也供其他開發人員和生產人員使用。
閱讀 django-admin documentation(這里少一個鏈接) 了解 manage.py工具可以做什么 的完整信息
玩轉 API
現在,我們進入交互式 Python shell,玩轉 Django 提供的API,使用下面的命令,進入 python shell:
$ python manage.py shell
我們用上面的命令替代簡單的輸入“python”,是因為 manage.py 會設置DJANGO_SETTINGS_MODULE(這里少一個鏈接) 環境變量,這樣 Python 會將 mysite/settings.py文件導入到你的 Django (其實就是進入一個快速有當前項目環境變量的 shell 中)。
不使用 manage.py
如果你不想使用manage.py,沒有問題,只需要在mysite.settings中設置DJANGO_SETTINGS_MODULE(這里少一個鏈接) ,啟動一個簡單的 Python shell 並安裝 Django 即可:
>>> import django
>>> django.setup()
如果這里報告
AttributeError異常,你可能使用了一個不適用當前版本文檔的 django。你需要切換到老版本的文檔或新版本的 Django。
你必須在manage.py的同級目錄中運行python,或者確保目錄在 Python 路徑上(python 的 sys.path 中),這樣import mysite就可以工作了。
更多信息,請查閱django-admin documentation(這里少一個鏈接)
當你進入 shell 后,你就可以研究 database API(這里少一個鏈接) 了:
>>> from polls.models import Question, Choice # Import the model classes we just wrote.
# No questions are in the system yet.
>>> Question.objects.all()
<QuerySet []>
# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
# Save the object into the database. You have to call save() explicitly.
>>> q.save()
# Now it has an ID.
>>> q.id
1
# Access model field values via Python attributes.
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)
# Change values by changing the attributes, then calling save().
>>> q.question_text = "What's up?"
>>> q.save()
# objects.all() displays all the questions in the database.
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>
稍等一下,<Question: Question object (1)> 這個對象不是一個友好的表現形式。我們可以編輯 Question(在polls/models.py文件中) 模型,在 Question 和 Choice 中都加入 __str__()(這里少一個鏈接)方法。
# polls/models.py
from django.db import models
class Question(models.Model):
# ...
def __str__(self):
return self.question_text
class Choice(models.Model):
# ...
def __str__(self):
return self.choice_text
給你的模型增加 __str__()(這里少一個鏈接) 是很重要的,不僅僅是當你使用交互式提示符處理時方便,也因為對象的表現形式貫穿django 自動生成admin的過程。
注意這些普通的 python 方法。現在讓我們添加一個自定義方法,來做一下師范:
# polls/models.py
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
# ...
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
注意添加的 import datetime 和 from django.util import timezone,可以分別參考 Python 標准庫中的 datetime,和 django 里 django.utils.timezone(這里少一個鏈接)中關於時區相關工具。如果你不熟悉 Python 中的時區處理,有可以在 時區支持文檔(這里少一個鏈接) 中了解更多內容。
保存這些更改,再次執行 python manage.py shell 啟動一個新的python 交互式 shell:
>>> from polls.models import Question, Choice
# Make sure our __str__() addition worked.
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>
# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>
# Get the question that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>
# Request an ID that doesn't exist, this will raise an exception.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.
# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
>>> Question.objects.get(pk=1)
<Question: What's up?>
# Make sure our custom method worked.
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True
# Give the Question a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a question's choice) which can be accessed via the API.
>>> q = Question.objects.get(pk=1)
# Display any choices from the related object set -- none so far.
>>> q.choice_set.all()
<QuerySet []>
# Create three choices.
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
# Choice objects have API access to their related Question objects.
>>> c.question
<Question: What's up?>
# And vice versa: Question objects get access to Choice objects.
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3
# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
# Let's delete one of the choices. Use delete() for that.
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
更多關於模型關系的信息,請查看 Accessing related objects(這里少一個鏈接)。更多關於如何通過 API 使用雙下划線查找字段,請查看 字段查找(這里少一個了鏈接)。數據庫 API 的詳細信息可以查看 數據庫 API 參考(這里少一個鏈接)。
介紹 Django admin
設計哲學
為你的員工或客戶生成有增加、更改、刪除內容功能的管理站點的工作很單調,因為這不需要什么創造力。因為這個原因,Django完全為模型自動創建管理接口。
Django是在一個新聞編輯環境被寫出的,在“內容發布”和“公共”站點之間做了非常明確的分離。站點管理者使用這個系統去添加新聞故事、事件、體育分數等,並且這些內容會被顯示在公共站點上。Django 為了解決站點管理員編輯內容的問題創建了一個統計接口。
admin 不是讓站點的游客使用的,它是為站點的管理者准備的。
創建一個 admin 用戶
首先我們需要創建一個可以登陸管理站點的用戶。運行下面的命令:
$ python manage.py createsuperuser
輸入你想使用的用戶名並回車:
Username: admin
然后提示你輸入你想使用的 Email 地址:
Email address: admin@example.com
最后一步是輸入你的密碼。會提示你輸入兩次密碼,兩次輸入的密碼必須一樣。
Password: **********
Password (again): *********
Superuser created successfully.
開始開發服務器
Django admin 站點默認是激活的。現在我們開始開發服務器並瀏覽它。
如果服務器沒有運行,執行下面的命令來運行它:
$ python manage.py runserver
現在打開一個 web 瀏覽器,並打開本地域的 "/admin" —— 例如:http://127.0.0.1:8000/admin/. 你應該可以看到 admin 的登陸界面:

默認情況下 翻譯(這里少一個鏈接) 是打開的,登陸界面可能會顯示成你使用的語言,這取決於瀏覽器的設置 和 Django 是否有這種語言的翻譯。
進入 admin 站點
現在,嘗試使用超級用戶登陸前面步驟中你創建的站點,你會看到 Django admin 的索引頁:

你會看到幾種可編輯的內容:groups 和 users。它們由 django.contrib.auth(這里少一個鏈接)提供,默認情況,認證框架會被 Django 加載。
使 poll app可以在 admin 中被修改
我們的 pollapp 在哪里呢?它並沒有顯示在 admin的索引頁面。
我們只需要做一件事情:我們需要告訴 admin, Question 對象有一個 admin 接口。要做到這個,需要打開 polls/admin.py 文件,像下面一樣編輯它:
# polls/admin.py
from django.contrib import admin
from .models import Question
admin.site.register(Question)
研究自由的 admin 功能
現在我們注冊了 Question,django 知道了它應該被顯示在 admin 索引頁面上:

點擊“Questions”。現在你就進入“change list”頁面編輯問題了。這個頁面顯示數據庫中的所有的問題,讓你可以選擇並修改。有一個我們前面創建的“what's up?”問題:

點擊“what's up?”問題來編輯它:

這里有一些需要注意的內容:
-
表單是
Question模型自動生成的。 -
不同的模型字段類型(
DateTimeField,CharField)對應相應的 HTML 輸入控件。每一個字段類型都知道如何在 Django admin 中顯示自己。 -
每一個
DateTimeField可以獲取 JavaScript 快捷方式(這里的快捷方式感覺翻譯成按鈕更能好一些,原詞是shortcut)。Dates 獲取“Today”的快捷方式和日歷彈出框,times 獲取“Now”的快捷方式並彈出一個列出常用輸入時間的彈出框。
頁面的底部給了你幾個選項: -
Save – 保存更改並返回此類型對象的更改列表頁面
-
Save and continue editing – 保存更改並為這個對象重載admin 頁面
-
Save and add another – 保存更改並加載一個新的,空表單的該類型的對象。
-
Delete – 顯示一個刪除確認頁面。
如果 “Date published” 的值和你在 Tutorial 1中創建問題的時間不匹配,可能是你忘記了給 TIME_ZONE(這里少一個鏈接) 設置正確的值。修改它,重載頁面后再檢查,就可以看到正確的值了。
點擊 “Today” 和 “Now”的快捷方式 可以修改“Date published”,然后點擊 “Save and continue editing.” 在點擊右上角”History“。你就可以通過Django admin看到一個列出所有對這個對象做的修改的頁面,還帶有做出這個修改該時間戳和用戶名:

當你熟悉了模型 API,了解了 admin ,就可以學習 教程的第三部分(Page 8) ,學習如何在我們的 polls app中添加更多視圖。
