該系列教程系個人原創,並完整發布在個人官網劉江的博客和教程
所有轉載本文者,需在頂部顯著位置注明原作者及www.liujiangblog.com官網地址。
接着第一部分,本節將講述如何安裝數據庫,編寫第一個模型以及簡要的介紹下Django自動生成的后台管理admin站點。
一、數據庫安裝
打開mysite/settings.py
配置文件,這是整個Django項目的設置中心。Django默認使用SQLite數據庫,因為Python源生支持SQLite數據庫,所以你無須安裝任何程序,就可以直接使用它。當然,如果你是在創建一個實際的項目,可以使用類似PostgreSQL的數據庫,避免以后數據庫遷移的相關問題。
# mysite/settings.py
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
如果你想使用其他的數據庫,請先安裝相應的數據庫操作模塊,並將settings文件中DATABASES位置的 ’default’
的鍵值進行相應的修改,用於連接你的數據庫。其中:
-
ENGINE(引擎):可以是
django.db.backends.sqlite3
、django.db.backends.postgresql
、django.db.backends.mysql
、django.db.backends.oracle
,當然其它的也行。 -
NAME(名稱):類似Mysql數據庫管理系統中用於保存項目內容的數據庫的名字。如果你使用的是默認的SQLite,那么數據庫將作為一個文件將存放在你的本地機器內,此時的NAME應該是這個文件的完整絕對路徑包括文件名,默認值
os.path.join(BASE_DIR, ’db.sqlite3’)
,將把該文件儲存在你的項目目錄下。
如果你不是使用默認的SQLite數據庫,那么一些諸如USER,PASSWORD和HOST的參數必須手動指定!下面給出一個基於pymysql操作Mysql數據庫的例子,更多細節參考后續的數據庫章節。
# mysite/settings.py
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
import pymysql # 一定要添加這兩行!通過pip install pymysql!
pymysql.install_as_MySQLdb()
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mysite',
'HOST': '192.168.1.1',
'USER': 'root',
'PASSWORD': 'pwd',
'PORT': '3306',
}
}
注意:
- 在使用非SQLite的數據庫時,請務必預先在數據庫管理系統的提示符交互模式下創建數據庫,你可以使用命令:“CREATE DATABASE database_name;”。Django不會自動幫你做這一步工作。
- 確保你在settings文件中提供的數據庫用戶具有創建數據庫表的權限,因為在接下來的教程中,我們需要自動創建一個test數據表。(在實際項目中也需要確認這一條要求。)
- 如果你使用的是SQLite,那么你無需做任何預先配置,直接使用就可以了。
在修改settings文件時,請順便將TIME_ZONE
設置為國內所在的時區Asia/Shanghai
。
同時,請注意settings文件中頂部的INSTALLED_APPS
設置項。它列出了所有的項目中被激活的Django應用(app)。你必須將你自定義的app注冊在這里。每個應用可以被多個項目使用,並且可以打包和分發給其他人在他們的項目中使用。
默認情況,INSTALLED_APPS
中會自動包含下列條目,它們都是Django自動生成的:
- django.contrib.admin:admin管理后台站點
- django.contrib.auth:身份認證系統
- django.contrib.contenttypes:內容類型框架
- django.contrib.sessions:會話框架
- django.contrib.messages:消息框架
- django.contrib.staticfiles:靜態文件管理框架
上面的一些應用也需要建立一些數據庫表,所以在使用它們之前我們要在數據庫中創建這些表。使用下面的命令創建數據表:
$ python manage.py migrate。
migrate命令將遍歷INSTALLED_APPS
設置中的所有項目,在數據庫中創建對應的表,並打印出每一條動作信息。如果你感興趣,可以在你的數據庫命令行下輸入:\dt
(PostgreSQL)、 SHOW TABLES;
(MySQL)或 .schema
(SQLite) 來列出 Django 所創建的表。
提示:對於極簡主義者,你完全可以在INSTALLED_APPS內注釋掉任何或者全部的Django提供的通用應用。這樣,migrate也不會再創建對應的數據表。
二、創建模型
現在,我們來定義模型model,模型本質上就是數據庫表的布局,再附加一些元數據。
Django通過自定義Python類的形式來定義具體的模型,每個模型的物理存在方式就是一個Python的類Class,每個模型代表數據庫中的一張表,每個類的實例代表數據表中的一行數據,類中的每個變量代表數據表中的一列字段。Django通過模型,將Python代碼和數據庫操作結合起來,實現對SQL查詢語言的封裝。也就是說,你可以不會管理數據庫,可以不會SQL語言,你同樣能通過Python的代碼進行數據庫的操作。Django通過ORM對數據庫進行操作,奉行代碼優先的理念,將Python程序員和數據庫管理員進行分工解耦。
在這個簡單的投票應用中,我們將創建兩個模型:Question
和Choice
。Question包含一個問題和一個發布日期。Choice包含兩個字段:該選項的文本描述和該選項的投票數。每一條Choice都關聯到一個Question。這些都是由Python的類來體現,編寫的全是Python的代碼,不接觸任何SQL語句。現在,編輯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中使用一個可選的第一位置參數用於提供一個人類可讀的字段名,讓你的模型更友好,更易讀,並且將被作為文檔的一部分來增強代碼的可讀性。
一些Field類必須提供某些特定的參數。例如CharField需要你指定max_length。這不僅是數據庫結構的需要,同樣也用於數據驗證功能。
有必填參數,當然就會有可選參數,比如在votes里我們將其默認值設為0.
最后請注意,我們使用ForeignKey
定義了一個外鍵關系。它告訴Django,每一個Choice關聯到一個對應的Question(注意要將外鍵寫在‘多’的一方)。Django支持通用的數據關系:一對一,多對一和多對多。
三、啟用模型
上面的代碼看着有點少,其實包含了大量的信息,據此,Django會做下面兩件事:
- 創建該app對應的數據庫表結構
- 為Question和Choice對象創建基於Python的數據庫訪問API
但是,首先我們得先告訴Django項目,我們要使用投票app。
要將應用添加到項目中,需要在INSTALLED_APPS
設置中增加指向該應用的配置文件的鏈接。對於本例的投票應用,它的配置類文件是polls/apps.py
,路徑格式為polls.apps.PollsConfig
。我們需要在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',
]
實際上,在多數情況下,我們簡寫成‘polls’就可以了:
# mysite/settings.py
INSTALLED_APPS = [
'polls',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
現在Django已經知道你的投票應用的存在了,並把它加入了項目大家庭。
我們需要再運行下一個命令:
$ 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你對模型有改動,並且你想把這些改動保存為一個“遷移(migration)”。
migrations
是Django保存模型修改記錄的文件,這些文件保存在磁盤上。在例子中,它就是polls/migrations/0001_initial.py
,你可以打開它看看,里面保存的都是人類可讀並且可編輯的內容,方便你隨時手動修改。
接下來有一個叫做migrate
的命令將對數據庫執行真正的遷移動作。但是在此之前,讓我們先看看在migration的時候實際執行的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的輸出。
- 表名是自動生成的,通過組合應用名 (polls) 和小寫的模型名
question
和choice
。 ( 你可以重寫此行為。) - 主鍵 (IDs) 是自動添加的。( 你也可以重寫此行為。)
- 按照慣例,Django 會在外鍵字段名上附加 "_id" 。 (你仍然可以重寫此行為。)
- 生成SQL語句時針對你所使用的數據庫,會為你自動處理特定於數據庫的字段,例如 auto_increment (MySQL), serial (PostgreSQL), 或integer primary key (SQLite) 。 在引用字段名時也是如此 – 比如使用雙引號或單引號。
- 這些SQL命令並沒有在你的數據庫中實際運行,它只是在屏幕上顯示出來,以便讓你了解Django真正執行的是什么。
如果你感興趣,也可以運行python manage.py check
命令,它將檢查項目中的錯誤,並不實際進行遷移或者鏈接數據庫的操作。
現在,我們可以運行migrate命令,在數據庫中進行真正的表操作了。
$ 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
migrate命令對所有還未實施的遷移記錄進行操作,本質上就是將你對模型的修改體現到數據庫中具體的表上面。Django通過一張叫做django_migrations的表,記錄並跟蹤已經實施的migrate動作,通過對比獲得哪些migrations尚未提交。
migrations的功能非常強大,允許你隨時修改你的模型,而不需要刪除或者新建你的數據庫或數據表,在不丟失數據的同時,實時動態更新數據庫。我們將在后面的章節對此進行深入的闡述,但是現在,只需要記住修改模型時的操作分三步:
- 在models.py中修改模型;
- 運行
python manage.py makemigrations
為改動創建遷移記錄; - 運行
python manage.py migrate
,將操作同步到數據庫。
之所以要將創建和實施遷移的動作分成兩個命令兩步走是因為你也許要通過版本控制系統(例如github,svn)提交你的項目代碼,如果沒有一個中間過程的保存文件(migrations),那么github如何知道以及記錄、同步、實施你所進行過的模型修改動作呢?畢竟,github不和數據庫直接打交道,也沒法和你本地的數據庫通信。但是分開之后,你只需要將你的migration文件(例如上面的0001)上傳到github,它就會知道一切。
四、使用模型的API
下面,讓我們進入Python交互環境,嘗試使用Django提供的數據庫訪問API。要進入Python的shell,請輸入命令:
$ python manage.py shell
相比較直接輸入“python”命令的方式進入Python環境,調用manage.py
參數能將DJANGO_SETTINGS_MODULE
環境變量導入,它將自動按照mysite/settings.py
中的設置,配置好你的python shell環境,這樣,你就可以導入和調用任何你項目內的模塊了。
或者你也可以這樣,先進入一個純凈的python shell環境,然后啟動Django,具體如下:
>>> import django
>>> django.setup()
當你進入shell后,嘗試一下下面的API吧:
>>> from polls.models import Question, Choice # 導入我們寫的模型類
# 現在系統內還沒有questions對象
>>> Question.objects.all()
<QuerySet []>
# 創建一個新的question對象
# Django推薦使用timezone.now()代替python內置的datetime.datetime.now()
# 這個timezone就來自於Django唯一的依賴庫pytz
from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
# 你必須顯式的調用save()方法,才能將對象保存到數據庫內
>>> q.save()
# 默認情況,你會自動獲得一個自增的名為id的主鍵
>>> q.id
1
# 通過python的屬性調用方式,訪問模型字段的值
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)
# 通過修改屬性來修改字段的值,然后顯式的調用save方法進行保存。
>>> q.question_text = "What's up?"
>>> q.save()
# objects.all() 用於查詢數據庫內的所有questions
>>> Question.objects.all()
<QuerySet [<Question: Question object>]>
這里等一下:上面的<Question: Question object>
是一個不可讀的內容展示,你無法從中獲得任何直觀的信息,為此我們需要一點小技巧,讓Django在打印對象時顯示一些我們指定的信息。
返回polls/models.py
文件,修改一下question和Choice這兩個類,代碼如下:
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible # 當你想支持python2版本的時候才需要這個裝飾器
class Question(models.Model):
# ...
def __str__(self): # 在python2版本中使用的是__unique__
return self.question_text
@python_2_unicode_compatible
class Choice(models.Model):
# ...
def __str__(self):
return self.choice_text
這個技巧不但對你打印對象時很有幫助,在你使用Django的admin站點時也同樣有幫助。
另外,這里我們自定義一個模型的方法,用於判斷問卷是否最近時間段內發布度的:
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)
請注意上面分別導入了兩個關於時間的模塊,一個是python內置的datetime一個是Django工具包提供的timezone。
保存修改后,我們重新啟動一個新的python shell,再來看看其他的API:
>>> from polls.models import Question, Choice
# 先看看__str__()的效果,直觀多了吧?
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>
# Django提供了大量的關鍵字參數查詢API
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>
# 獲取今年發布的問卷
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>
# 查詢一個不存在的ID,會彈出異常
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.
# Django為主鍵查詢提供了一個縮寫:pk。下面的語句和Question.objects.get(id=1)效果一樣.
>>> Question.objects.get(pk=1)
<Question: What's up?>
# 看看我們自定義的方法用起來怎么樣
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True
# 讓我們試試主鍵查詢
>>> q = Question.objects.get(pk=1)
# 顯示所有與q對象有關系的choice集合,目前是空的,還沒有任何關聯對象。
>>> q.choice_set.all()
<QuerySet []>
# 創建3個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對象可通過API訪問和他們關聯的Question對象
>>> c.question
<Question: What's up?>
# 同樣的,Question對象也可通過API訪問關聯的Choice對象
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3
# API會自動進行連表操作,通過雙下划線分割關系對象。連表操作可以無限多級,一層一層的連接。
# 下面是查詢所有的Choices,它所對應的Question的發布日期是今年。(重用了上面的current_year結果)
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
# 使用delete方法刪除對象
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
關於模型的使用就暫時先介紹這么多。這部分內容是Django項目的核心,也是動態網站與數據庫交互的核心,對於初學者,再難理解也要理解。
五、admin后台管理站點
很多時候,我們不光要開發針對客戶使用的前端頁面,還要給后台管理人員提供相應的管理界面。但是大多數時候為你的團隊或客戶編寫用於增加、修改和刪除內容的后台管理站點是一件非常乏味的工作並且沒有多少創造性,而且也需要花不少的時間和精力。Django最大的優點之一,就是體貼的為你提供了一個基於項目model創建的一個后台管理站點admin。這個界面只給站點管理員使用,並不對大眾開放。雖然admin的界面可能不是那么美觀,功能不是那么強大,內容不一定符合你的要求,但是它是免費的、現成的,並且還是可定制的,有完善的幫助文檔,那么,你還要什么自行車?
1. 創建管理員用戶
首先,我們需要通過下面的命令,創建一個可以登錄admin站點的用戶:
$ python manage.py createsuperuser
輸入用戶名:
Username: admin
輸入郵箱地址:
Email address: xxx@xxx.xxx
輸入密碼:
Password: **********
Password (again): *********
Superuser created successfully.
注意:Django1.10版本后,超級用戶的密碼強制要求具備一定的復雜性,不能再偷懶了。
2. 啟動開發服務器
服務器啟動后,在瀏覽器訪問http://127.0.0.1:8000/admin/
。你就能看到admin的登陸界面了:
在實際環境中,為了站點的安全性,我們不能將管理后台的url隨便暴露給他人,不能用/admin/
這么簡單的路徑。
打開根url路由文件mysite/urls.py
,修改其中admin.site.urls對應的正則表達式,換成你想要的,比如:
from django.conf.urls import url
from django.contrib import admin
urlpatterns = [
url(r'^my/set/', admin.site.urls),
]
這樣,我們必須訪問http://127.0.0.1:8000/my/set/
才能進入admin界面。
3. 進入admin站點
利用剛才建立的admin賬戶,登陸admin,你將看到如下的界面:
當前只有兩個可編輯的內容:groups和users。它們是django.contrib.auth
模塊提供的身份認證框架。
4. 在admin中注冊投票應用
現在還無法看到投票應用,必須先在admin中進行注冊,告訴admin站點,請將polls的模型加入站點內,接受站點的管理。
打開polls/admin.py
文件,加入下面的內容:
from django.contrib import admin
from .models import Question
admin.site.register(Question)
4. admin站點的主要功能
注冊question模型后,刷新admin頁面就能看到Question欄目了。
點擊“Questions”,進入questions的修改列表頁面。這個頁面會顯示所有的數據庫內的questions對象,你可以在這里對它們進行修改。看到下面的“What’s up?”
了么?它就是我們先前創建的一個question對象,並且通過__str__
方法的幫助,顯示了較為直觀的信息,而不是一個冷冰冰的對象類型名稱。
下面,點擊What’s up?
進入編輯界面:
這里需要注意的是:
- 頁面中的表單是由Question模型自動生成的。
- 不同的模型字段類型(DateTimeField, CharField)會表現為不同的
HTML input
框類型。 - 每一個
DateTimeField
都會自動生成一個可點擊鏈接。日期是Today,並有一個日歷彈出框;時間是Now,並有一個通用的時間輸入列表框。
在頁面的底部,則是一些可選項按鈕:
delete
:彈出一個刪除確認頁面save and add another
:保存當前修改,並加載一個新的空白的當前類型對象的表單。save and continue editing
:保存當前修改,並重新加載該對象的編輯頁面。save
:保存修改,返回當前對象類型的列表頁面。
如果Date published
字段的值和你在前面教程創建它的時候不一致,可能是你沒有正確的配置TIME_ZONE
,在國內,通常是8個小時的時間差別。修改TIME_ZONE配置並重新加載頁面,就能顯示正確的時間了。
在頁面的右上角,點擊History
按鈕,你會看到你對當前對象的所有修改操作都在這里有記錄,包括修改時間和操作人員,如下圖所示: