點我查看本文集的說明及目錄。
本文是《 django by example 》第十三章 上線運行 的內(nèi)容颅围。
CH13 上線運行
上一章,我們?yōu)轫椖縿?chuàng)建了 RESTful API 恨搓。本章院促,我們將學習如何實現(xiàn)以下功能:
配置生產(chǎn)環(huán)境
創(chuàng)建自定義中間件
實現(xiàn)自定義管理命令
生產(chǎn)環(huán)境上線運行
現(xiàn)在是時候?qū)?Django 項目部署到生產(chǎn)環(huán)境了。我們將根據(jù)下面的步驟實現(xiàn)項目上線:
- 為生產(chǎn)環(huán)境配置項目斧抱;
- 使用 PostgreSQL 數(shù)據(jù)庫常拓;
- 提供靜態(tài)資源;
- 使用 SSL 加密辉浦。
配置多種環(huán)境
實際項目需要處理多種環(huán)境弄抬。我們至少需要本地和生產(chǎn)環(huán)境,還可能包括其它環(huán)境宪郊。項目的大部分設(shè)置在多種環(huán)境中通用掂恕,但有些設(shè)置需要根據(jù)環(huán)境進行更改拖陆。下面我們來為多種環(huán)境配置項目,使一切有條不紊懊亡。
在 educa 項目目錄下創(chuàng)建 settings 目錄依啰。將項目的 settings.py 文件重命名為 settings/base.py,并創(chuàng)建下圖中的其它文件斋配,settings 目錄結(jié)構(gòu)為:
這些文件包括:
base.py : 包含通用設(shè)置和缺省設(shè)置的基礎(chǔ)設(shè)置文件孔飒;
local.py :本地環(huán)境使用的自定義設(shè)置;
pro.py : 生產(chǎn)環(huán)境使用的自定義設(shè)置艰争。
編輯 settings/base.py 文件坏瞄,找到下面一行:
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
替換為:
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(os.path.join(__file__,os.pardir))))
這里將設(shè)置文件移動到下一級,所以需要 BASE_DIR 通過 os.pardir 指向父路徑甩卓。
編輯 settings/local.py 文件鸠匀,并添加下面的代碼:
from .base import *
DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
這是本地環(huán)境的設(shè)置文件。文件導入 base.py 定義的所有設(shè)置并為這個環(huán)境配置特定設(shè)置逾柿。由于 DEBUG 和 DATABASE 在每種環(huán)境中的值不同缀棍,這里從 base.py 文件中復制 DEBUG 和 DATABASE 設(shè)置,我們可以從 base.py 中移除這兩項設(shè)置机错。
編輯 settings/pro.py 文件并讓其看起來是這樣的:
from .base import *
DEBUG = False
ADMINS = (
('Antonio M', 'email@mydomain.com'),
)
ALLOWED_HOSTS = ['educaproject.com', 'www.educaproject.com']
DATABASES = {
'default': {
}
}
這是生產(chǎn)環(huán)境的設(shè)置爬范。我們來看一下這些選項:
- DEBUG : 生產(chǎn)環(huán)境必須將 DEBUG 設(shè)為 False 。不這樣做可能導致每個人都能看到調(diào)試信息及敏感配置弱匪。
- ADMINS:DEBUG 設(shè)置為 False 并且視圖出現(xiàn)異常時青瀑,異常信息會通過郵件上報給 ADMINS 設(shè)置的用戶。這里需要將 name/e-mail 對改為自己的信息萧诫。
- ALLOWED_HOSTS :由于 DEBUG 設(shè)置為 False 斥难,Django 只允許這個列表中的主機提供服務(wù)。這是一個安全機制帘饶,這里已經(jīng)包含了網(wǎng)站使用的域名 educaproject.com 和 www.educaproject.com哑诊。
- DATABASES : 保留了空的設(shè)置,下一步將實現(xiàn)生產(chǎn)環(huán)境的數(shù)據(jù)庫設(shè)置及刻。
注意:
處理多種環(huán)境時镀裤,創(chuàng)建一個基礎(chǔ)設(shè)置文件,并為每種環(huán)境配置一個設(shè)置文件缴饭。環(huán)境設(shè)置文件應(yīng)該繼承通用設(shè)置并重寫環(huán)境特定配置暑劝。
我們已經(jīng)將配置文件從默認的 settings.py 文件更改到另一個文件。在指定要使用的配置模塊之前茴扁,manage.py 工具不能執(zhí)行任何命令铃岔。在 shell 中運行管理命令時汪疮,需要添加一個 —settings 設(shè)置或設(shè)置 DJANGO_SETTINGS_MODULE 環(huán)境變量峭火。打開 shell并運行下面的命令:
export DJANGO_SETTINGS_MODULE=educa.settings.pro
這將為當前 shell 會話設(shè)置 DJANGO_SETTINGS_MODULE 環(huán)境變量毁习。如果你不想每次啟動 shell 執(zhí)行上面的命令,請將這個命令配置到 .bashrc 或者 .bash_profile 文件中卖丸。如果不想在 .bashrc 或 .bash_profile 中設(shè)置這個變量纺且,運行命令是需要包含 —settings ,比如:
python manage.py migrate –-settings=educa.settings.pro
現(xiàn)在稍浆,我們已經(jīng)為多種環(huán)境組織好設(shè)置了载碌。
安裝 PostgreSQL
我們整本書使用的都是 SQLite 數(shù)據(jù)庫。這樣做很簡單并且容易啟動衅枫,但是生產(chǎn)環(huán)境需要像 PostgreSQL嫁艇、MySQL、Oricle 等更加強大的數(shù)據(jù)庫弦撩。我們將為我們的項目使用 PostGreSQL 數(shù)據(jù)庫步咪。Django 推薦使用 PostGreSQL 數(shù)據(jù)庫。Django 內(nèi)置 django.contrib.postgres 模塊便于我們使用特定的 PostGreSQL 功能益楼。https://docs.djangoproject.com/en/1.11/ref/contrib/postgres/ 有這個模塊的詳細介紹猾漫。
如果使用 Linux,這樣安裝 Python 操作 PostGreSQL 的依賴關(guān)系:
sudo apt-get install libpq-dev python-dev
然后使用以下命令安裝 PostgreSQL:
sudo apt-get install postgresql postgresql-contrib
如果使用 mac OS X 或者 Windows 感凤,可以從 https://www.postgresql.org/download/ 下載 PostgreSQL 悯周。
我們來創(chuàng)建 PostGreSQL 用戶,打開 shell 并運行以下命令:
su postgres
createuser -dP educa
將需要輸入密碼及這個用戶的權(quán)限陪竿。輸入密碼和權(quán)限后使用下面的命令創(chuàng)建一個新的數(shù)據(jù)庫:
createdb -E urf8 -U educa educa
然后禽翼,編輯 settings/pro.py 文件并修改 DATABASES 設(shè)置:
DATABASES = {'default': {'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'educa',
'USER': 'educa',
'PASSWORD': '******',
},
}
將上述數(shù)據(jù)替換為自己創(chuàng)建的用戶的數(shù)據(jù)庫名稱和憑據(jù)。 新的數(shù)據(jù)庫是空的萨惑, 運行以下命令來實現(xiàn)所有數(shù)據(jù)庫遷移:
python manage.py migrate –-settings=educa.settings.pro
最后捐康,使用以下命令創(chuàng)建 superuser:
python manage.py createsuperuser –-settings=educa.settings.pro
檢查你的項目
Django 內(nèi)置可以在任何時刻檢查項目的 check 管理命令。這個命令檢查 Django 項目中的應(yīng)用程序并輸出錯誤或警告庸蔼。如何添加 —deploy 選項解总,則僅僅進行生產(chǎn)相關(guān)的檢查。打開 shell 運行以下命令來執(zhí)行檢查:
python manage.py check --deploy –-settings=educa.settings.pro
你將會看到輸出中沒有錯誤但是又幾個警告姐仅。這意味著檢查是成功地花枫,但是應(yīng)該逐條檢查警告使項目在生產(chǎn)環(huán)境中更加安全的運行。這里我們不再深入討論這個問題掏膏,但是一定要記住在上線之前檢查項目來識別任何相關(guān)問題劳翰。
使用 WSGI 提供 Django 服務(wù)
Django 的主要部署平臺是 WSGI 。WSGI 是 Web Server Gateway Interface 的縮寫馒疹,它是 Python 語言中 Web服務(wù)器和 Web應(yīng)用程序之間或框架之間的通用接口標準佳簸。
我們使用 startproject 命令創(chuàng)建新項目時, Django 在項目目錄中生成一個 wsgi.py 文件。這個文件包括可調(diào)用 WSGI 配置(可以訪問應(yīng)用)生均。 使用 Django 開發(fā)服務(wù)器運行項目和在生產(chǎn)環(huán)境運行項目都需要使用 WSGI 听想。
我們可以從http://wsgi.readthedocs.io/en/latest/ 了解到更多 WSGI 相關(guān)細節(jié)。
安裝 uWSGI
整本書我們都在使用 Django 開發(fā)服務(wù)器在本地運行項目马胧。然而汉买,生產(chǎn)環(huán)境需要一個真正的 web 服務(wù)器來部署我們的項目。
uWSGI 是一個非撑寮梗快的 Python 應(yīng)用服務(wù)器蛙粘。它使用 WSGI 配置與我們的 Python 應(yīng)用通信。uWSGI 將 web 請求轉(zhuǎn)化為 Django 可以處理的格式威彰。
使用以下命令安裝 uWSGI 服務(wù)器:
pip install uwsgi==2.0.11.1
如果使用 Mac OS X出牧,我們可以通過 brew install uwsgi 命令使用 Homebrew 安裝 uWSGI 。如果希望在 Windows 中安裝 uWSGI歇盼,則需要使用 Cygwin 崔列。然而,我們推薦在 UNIX 環(huán)境中使用 uWSGI旺遮。
配置 uWSGI
我們可以從命令行運行 uWSGI 赵讯。打開 shell 并從 educa 項目目錄運行以下命令:
uwsgi --module=educa.wsgi:application \
--env=DJANGO_SETTINGS_MODULE=educa.settings.pro \
--http=127.0.0.1:80 \
--uid=1000 \
--virtualenv=/home/zenx/env/educa/
如果使用的賬戶沒有足夠權(quán)限,這些命令需要添加 sudo 前綴耿眉。
我們使用這個命令在本地運行 uWSGI 边翼,并設(shè)置了以下參數(shù):
- 使用 educa.wsgi:application WSGI;
- 從生產(chǎn)環(huán)境加載設(shè)置鸣剪;
- 使用虛擬環(huán)境组底,將 virtualenv 選項設(shè)置為你的虛擬環(huán)境目錄。如果沒有使用虛擬環(huán)境筐骇,則可以跳過這一步债鸡。
如果沒有在項目目錄運行這個命令,使用項目的路徑設(shè)置 —chdir=/path/to/educa/ 铛纬。
在瀏覽器中打開 http://127.0.0.1/厌均,你將看到生成的 HTML , 但是并沒有加載任何 CSS 或者圖片。由于我們沒有配置 uWSGI 提供靜態(tài)文件告唆,所以這很好理解棺弊。
我們可以使用 .ini. 文件定義 uWSGI 自定義配置。這比向命令行添加選項更加方便擒悬。在 educa 目錄下創(chuàng)建如下文件結(jié)構(gòu):
編輯 uwsgi.ini 文件并添加以下代碼:
# variables
projectname = educa
base = /home/zenx/educa
# configuration
master = true
virtualenv = /home/zenx/env/%(projectname)
pythonpath = %(base)
chdir = %(base)
env = DJANGO_SETTINGS_MODULE=%(projectname).settings.pro
module = educa.wsgi:application
socket = /tmp/%(projectname).sock
我們定義了如下變量:
- projectname : Django 項目的名稱模她,這里使用 educa 。
- base : educa 項目的絕對路徑懂牧。使用你的絕對路徑進行設(shè)置侈净。
下面是可能會使用的 uWSGI 選項。我們可以定義任意其它與 uWSGI 選項不同的變量,這里設(shè)置了這些選項:
- master :啟動 master 處理畜侦;
- virtualenv : 虛擬環(huán)境路徑运怖,設(shè)置為你的虛擬環(huán)境目錄。
- pythonpath :添加你的 Python 路徑的路徑夏伊。
- chdir : 項目目錄的路徑,這樣 uWSGI 在加載應(yīng)用之前可以更改路徑吻氧。
- env : 環(huán)境變量溺忧。這里包含指向生產(chǎn)環(huán)境配置的 DJANGO_SETTINGS_MODULE 變量。
- module :使用的 WSGI 模塊盯孙。我們將其設(shè)置為項目 wsgi 模塊中包括的可調(diào)用 application 鲁森。
- socket : 綁定到服務(wù)器的 UNIX/TCP socket 。
socket 選項是為了便于與第三方 router (比如 Nginx ) 通信振惰,http 選項用于 uWSGI 接收 HTTP請求并自己實現(xiàn)路由歌溉。我們將使用 socket 運行 uWSGI 。由于我們將使用 Nginx 作為我們的 web服務(wù)器骑晶,所以使用 socket 與 uWSGI 進行通信痛垛。
我們可以從 http://uwsgi-docs.readthedocs.io/en/latest/Options.html 了解所有 uWSGI 選項。
現(xiàn)在桶蛔,我們可以使用以下命令運行自定義配置的 uWSGI :
uwsgi --ini config/uwsgi.ini
由于通過 socket 運行匙头,現(xiàn)在還不能通過瀏覽器訪問 uWSGI 實例。下一步仔雷,我們來完成生產(chǎn)環(huán)境蹂析。
安裝 Nginx
網(wǎng)站上線時,我們需要提供動態(tài)內(nèi)容碟婆,還需要提供靜態(tài)文件(比如 CSS电抚、JavaScript 文件和圖像)。 盡管 uWSGI 能夠提供靜態(tài)文件竖共,但這將為 HTTP請求增加不必要的開銷蝙叛。 因此,建議在 uWSGI 之前設(shè)置一個類似 Nginx 的 Web服務(wù)器來提供靜態(tài)文件公给。
Nginx 是一款專注于高并發(fā)性甥温、高性能和低內(nèi)存使用率的 Web服務(wù)器。 Nginx 還充當反向代理妓布,接收 HTTP請求并將它們路由到不同的后端姻蚓。 一般而言,需要在前面使用諸如 Nginx 之類的Web服務(wù)器匣沼,以便快速有效的提供靜態(tài)文件狰挡,并將動態(tài)請求轉(zhuǎn)發(fā)到 uWSGI 。 通過使用 Nginx ,我們還可以使用規(guī)則及反向代理功能加叁。
使用以下命令安裝 Nginx :
sudo apt-get install nginx
Mac OS X 系統(tǒng)可以使用 brew install nginx 安裝 Nginx 倦沧。 http://nginx.org/en/download.html 網(wǎng)站有 Windows 系統(tǒng)安裝 Nginx 使用的二進制文件。
生產(chǎn)環(huán)境
下圖表示生產(chǎn)環(huán)境最終的流程:
Client browser (客戶端瀏覽器) 發(fā)出 HTTP 請求后將進行以下工作:
- Nginx 接收 HTTP 請求它匕;
- 如果請求靜態(tài)文件展融, Nginx 直接提供靜態(tài)文件。如果請求動態(tài)頁面豫柬, Nginx 通過 socket 將請求發(fā)送到 uWSGI 告希。
- uWSGI 將請求發(fā)送到 Django ,Django 的 HTTP 響應(yīng)傳回 Nginx 烧给,Nginx 將其發(fā)送到 Client browser 燕偶。
配置 Nginx
在 config/ 目錄下新建一個 nginx.conf 文件,添加以下代碼:
# the upstream component nginx needs to connect to
upstream educa {
server unix:///tmp/educa.sock;
}
server {
listen 80;
server_name www.educaproject.com educaproject.com;
location / {
include /etc/nginx/uwsgi_params;
uwsgi_pass educa;
} }
這是 Nginx 基礎(chǔ)配置础嫡,我們設(shè)置了一個名為 educa 的 upstream 來指向 uWSGI 創(chuàng)建的 socket 指么。我們使用服務(wù)器指令并添加以下配置:
- 告訴 Nginx 監(jiān)聽 80 端口;
- 將服務(wù)器名稱設(shè)置為 www.educaproject.com 和 educaproject.com 榴鼎。Nginx將為這兩個域的請求提供服務(wù)伯诬。
- 最后,指定所有 / 路徑的內(nèi)容必須路由到 educa socket( uWSGI )巫财。并包含 Nginx 內(nèi)置的默認 uWSGI 配置姑廉。
我們可以從 http://nginx.org/en/docs/ 找到 Nginx 文檔。 Nginx 主配置文件位于 /etc/nginx/nginx.conf 翁涤,它包含 /etc/nginx/sites-enabled/ 中找到的所有配置文件桥言。為了讓 Nginx 加載自定義配置文件,這樣創(chuàng)建一個鏈接:
sudo ln -s /home/zenx/educa/config/nginx.conf /etc/nginx/sites-enabled/
educa.conf
將 /home/zenx/educa/ 替換為自己的絕對路徑葵礼。 如果 uWSGI 還沒有運行号阿,打開一個 shell 并運行 uWSGI :
uwsgi --ini config/uwsgi.ini
打開第二個 shell 使用以下命令運行 Nginx :
server nginx start
由于這里使用的是示例域名,因此需要將其重定向到本地主機鸳粉。 編輯 /etc/hosts 文件扔涧,并添加下列內(nèi)容:
127.0.0.1 educaproject.com
127.0.0.1 www.educaproject.com
這樣,兩個名稱都路由到了我們的本地服務(wù)器届谈。在生產(chǎn)服務(wù)器中枯夜,我們不需要這樣做。在生產(chǎn)服務(wù)器中艰山,我們將在 域名 DNS 配置中主機指向我們的服務(wù)器湖雹。
在瀏覽器中打開 http://educaproject.com/ 。應(yīng)該可以能夠看到?jīng)]有加載任何靜態(tài)資源的網(wǎng)站了曙搬。我們的生產(chǎn)環(huán)境快要部署成功了摔吏。
提供靜態(tài)和文件資源
為了實現(xiàn)最佳性能鸽嫂,我們將使用 Nginx 直接提供靜態(tài)資源。
編輯 settings/base.py 文件并添加下面的代碼:
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR,'static/')
這里需要使用 Django 輸出靜態(tài)資源征讲。 collect static 命令可以將所有應(yīng)用的靜態(tài)文件拷貝到 STATIC_ROOT 目錄据某。
打開 shell 并運行下面的命令:
python manage.py collectstatic
你將看到下面的輸出:
You have requested to collect static files at the destination
location as specified in your settings.
This will overwrite existing files!
Are you sure you want to do this?
輸入 yes 以便 Django 拷貝所有文件。你將看到下面的輸出诗箍。
96 static files copied to '/Users/apple/profile/django_by_example/educa/educa3/static'.
現(xiàn)在癣籽,編輯 config/nginx.conf 文件并在 server 中添加下面的代碼:
location /static/ {
alias /home/zenx/educa/static/;
}
location /media/ {
alias /home/zenx/educa/media/;
}
記得使用項目路徑替換 /home/zenx/educa/ 路徑。Nginx 可以提供 /static/ 或 /media/ 路徑下的靜態(tài)文件了滤祖。 這些命令告訴 Nginx 直接提供 /static/ 或 /media/ 路徑下的靜態(tài)資源筷狼。
使用以下命令重新加載 Nginx 配置文件:
service nginx reload
在瀏覽器中打開 http://educaproject.com/ 。現(xiàn)在應(yīng)該可以看到靜態(tài)文件了氨距。我們已經(jīng)成功配置 Nginx 來提供靜態(tài)資源了。
使用 SSL 進行安全連接
SSL 協(xié)議( Secure Sockets Layer ) 正在成為為網(wǎng)站提供安全連接服務(wù)的標準棘劣。 強烈建議您的網(wǎng)站使用 HTTPS 提供服務(wù)俏让。 我們將在 Nginx 中配置 SSL 證書來安全地為網(wǎng)站提供服務(wù)。
創(chuàng)建 SSL 證書
在 educa 項目中新建一個名為 ssl 的目錄茬暇。然后在命令行使用以下命令生成一個 SSL 證書:
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl/
educa.key -out ssl/educa.crt
你正在生成一個私鑰和一個有效期為一年的 2048-bit 的 SSL 證書首昔。你將需要輸入以下信息:
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []: Madrid
Organization Name (eg, company) [Internet Widgits Pty Ltd]: Zenx IT
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []: educaproject.com
Email Address []: email@domain.com
你可以使用自己的數(shù)據(jù)填寫需要的內(nèi)容。其中最重要的字段為 Common Name 糙俗,這個字段指定需要驗證的域名勒奇,我們這里使用 educaproject.com。
通常會在 ssl 目錄下生成一個 educa.key 的私鑰文件和一個 educa.crt 證書巧骚。
使用 SSL 驗證 Nginx
編輯 nginx.conf 文件并修改服務(wù)器指令以包含以下SSL指令:
server { listen listen
80;
443 ssl;
ssl_certificate
ssl_certificate_key /home/zenx/educa/ssl/educa.key; server_name www.educaproject.com educaproject.com; # ...
}
現(xiàn)在赊颠,服務(wù)器通過 80 端口監(jiān)聽 HTTP ,并通過 443 端口監(jiān)聽 HTTPS 劈彪。我們使用 ssl_certificate 指定 SSL 證書的路徑竣蹦,并使用 ssl_certificate_key 指定證書秘鑰。
使用以下密鑰重啟 Nginx :
sudo service nginx restart
Nginx 將加載新的配置沧奴。在瀏覽器中打開 https://educaproject.com 痘括,你應(yīng)該可以看到下圖所在的警告信息:
由于瀏覽器的不同,上面的信息可能會有所不同滔吠。信息提醒我們網(wǎng)站沒有使用可信任的證書纲菌。瀏覽器無法驗證網(wǎng)站的身份。這是因為我們自己簽署了證書疮绷,而沒有從可信任的證書頒發(fā)機構(gòu)獲得證書翰舌。當你擁有一個真實的域名時,可以申請一個可信的 CA 來處理 SSL 證書冬骚,以便瀏覽器驗證其身份灶芝。
如果要為真實域名獲取一個可信的證書郑原,可以參考 Linux Foundation 創(chuàng)建的 Let's Encrypt 項目。 這是一個旨在簡化獲取和更新可信 SSL 證書的協(xié)作項目夜涕。 https://letsencrypt.org/ 網(wǎng)站有更多相關(guān)信息犯犁。
點擊 Add Exception 按鈕告訴瀏覽器我們信任這個證書。 您將看到瀏覽器的 URL 旁邊顯示一個鎖形圖標女器,如以下屏幕截圖所示:
如果點擊鎖形圖標酸役,將會顯示 SSL 證書詳情。
為項目配置 SSL
Django 內(nèi)置一些 SSL 配置驾胆。編輯 settings/pro.py 設(shè)置文件并添加下面的代碼:
SECURE_SSL_REDIRECT = True
CSRF_COOKIE_SECURE = True
這些設(shè)置為:
- SECURE_SSL_REDIRECT : HTTP 請求是否需要重定向到 HTTPS 請求涣澡。
- CSRF_COOKIE_SECURE : 為跨網(wǎng)站請求偽造防御建立安全 cookie 。
太棒了丧诺,我們已經(jīng)配置了一個生產(chǎn)環(huán)境入桂,可以為項目提供卓越的性能。
創(chuàng)建自定義中間件
我們已經(jīng)了解了包含項目中間件的 MIDDLEWARE_CLASSES 設(shè)置驳阎。中間件是一個類抗愁,它內(nèi)置可以全局執(zhí)行的特定方法,我們可以把它想象成低級別的插件系統(tǒng)呵晚,在請求或響應(yīng)過程中執(zhí)行的 Hook 蜘腌。 每個中間件負責為所有請求或響應(yīng)執(zhí)行特定操作。
注意:
由于中間件為每個請求執(zhí)行特定方法饵隙,請避免為中間件添加昂貴的過程撮珠。
接收到 HTTP 請求后,中間件按照 MIDDLEWARE_CLASSES 設(shè)置中設(shè)定的順序執(zhí)行金矛。Django 生成一個 HTTP 響應(yīng)后芯急,中間件方法將按照反向順序執(zhí)行。
下圖展示了在請求階段和響應(yīng)階段中間件方法的執(zhí)行順序驶俊。 它還顯示了(可能)調(diào)用的中間件方法:
請求階段將執(zhí)行這些中間件方法:
- process_request(request) : 在 Django 決定請求執(zhí)行哪個視圖之前調(diào)用志于。request 是一個 HttpRequest 實例;
- process_view (request, view_func, view_args, view_kwargs ) :在 Django 調(diào)用視圖之前調(diào)用废睦。它可以訪問視圖函數(shù)及接收的參數(shù)伺绽。
響應(yīng)階段將執(zhí)行這些中間件方法:
- process_exception( request, exception ) :視圖函數(shù)引發(fā) Exception 異常時調(diào)用。
- process_template_response(request, response) :視圖執(zhí)行完后返回的響應(yīng)對象有一個 render() 方法時調(diào)用(如果一個 TemplateResponse 或者等效的對象)嗜湃。
- process_response(request, response) : 所有響應(yīng)返回瀏覽器之前都要調(diào)用奈应。
由于中間件可能需要使用前面中間件方法設(shè)置到請求中的數(shù)據(jù)集,MIDDLEWARE_CLASSES 設(shè)置中中間件的順序非常重要购披。注意杖挣,即使由于前面的中間件返回了 HTTP 響應(yīng)而跳過了 process_request() 或者 process_view() ,也將調(diào)用中間件的 process_response() 方法刚陡。這意味著惩妇,process_response() 不能依賴請求階段的數(shù)據(jù)集株汉。如果中間件處理異常并返回響應(yīng),則不會執(zhí)行前面的中間件類歌殃。
注意:
向 MIDDLEWARE_CLASSES 添加新的中間件時乔妈,確保把它放在正確的位置。中間件方法在請求階段按照設(shè)置中的順序執(zhí)行氓皱,在響應(yīng)階段按照反向順序執(zhí)行路召。
我們可以從 https://docs.djangoproject.com/en/1.11/topics/http/middleware/ 了解更多中間件的信息。
我們將創(chuàng)建自定義中間件來實現(xiàn)通過自定義子域訪問課程波材。每個課程細節(jié)視圖的 URL (看起來像 https://educaproject.com/course/django/)也可以通過使用課程內(nèi)容的子域訪問股淡,例如 https://django.educaproject.com/ 。
創(chuàng)建子域中間件
中間件可以位于項目的任何位置廷区。然而唯灵,推薦在應(yīng)用目錄下創(chuàng)建一個 middleware.py 文件。
在 courses 應(yīng)用目錄下新建一個 middleware.py 文件并添加以下代碼:
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404, redirect
from .models import Course
class SubdomainCourseMiddleware(object):
"""
Provides subdomains for courses
"""
def process_request(self, request):
host_parts = request.get_host().split('.')
if len(host_parts) > 2 and host_parts[0] != 'www':
# get course for the given subdomain
course = get_object_or_404(Course, slug=host_parts[0])
course_url = reverse('course_detail', args=[course.slug])
# redirect current request to the course_detail view
url = '{}://{}{}'.format(request.scheme, '.'.join(host_parts[1:]),
course_url)
return redirect(url)
我們創(chuàng)建了一個執(zhí)行 process_request() 的中間件隙轻。接收到 HTTP 請求后埠帕,我們完成以下任務(wù):
獲取請求使用的主機名并對其進行拆分。例如大脉,如果用戶訪問 mycourse.educaproject.com搞监,這里將生成 ['mycourse', 'educaproject', 'com'] 水孩。
通過檢查主機名拆分是否生成2個以下元素確定主機名是否含有子域镰矿。如果主機名包含子域,并且子域不是 'www'俘种,則嘗試使用子域提供的 slug 查找對應(yīng)課程秤标。
-
如果沒有找到課程,引發(fā)一個 Http404 異常宙刘。使用主域?qū)g覽器重定向到課程詳細信息 URL 苍姜。
?
編輯項目的 settings/base.py 文件并將 'courses.middleware.SubdomainCourseMiddleware' 添加到 MIDDLEWARE_CLASSES 列表的底部。如下所示:
MIDDLEWARE = ['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
# 'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.moddleware.cache.FetchFromCacheMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'course.middleware.SubdomainCourseMiddleware' ]
現(xiàn)在悬包,我們剛剛創(chuàng)建的中間件將對每個請求進行操作衙猪。
使用 Nginx 提供多個子域
Nginx 需要為任何可能的子域提供網(wǎng)站服務(wù)。編輯 config/nginx.conf 文件并找到以下行:
server_name www.educaproject.com educaproject.com;
將其替換為:
server_name *.educaproject.com educaproject.com;
通過使用星號布近,規(guī)則將適用于 educaproject.com 的所有子域垫释。 為了在本地測試中間件,我們將任何需要測試的子域添加到 /etc/hosts 撑瞧。 要使用slug 為 django 的 Course 對象測試中間件棵譬,請將下面一行添加到 /etc/hosts 文件中:
127.0.0.1 django.educaproject.com
然后,在瀏覽器中打開 https://django.educaproject.com/ 预伺。 中間件將通過子域找到課程并將瀏覽器重定向到https://educaproject.com/course/django/订咸。
執(zhí)行自定義管理命令
Django 允許應(yīng)用注冊用于 manage.py 的自定義管理命令曼尊。例如,我們在第九章使用了管理命令 makemessages 和 compilemessages 來創(chuàng)建和編譯翻譯文件脏嚷。
管理命令由繼承 django.core.management.BaseCommand 的 Command 類組成的 Python 模塊實現(xiàn)骆撇。我們可以創(chuàng)建簡單命令,或者接收位置和可選參數(shù)作為輸入然眼。
Django 從 INSTALLED_APPS 設(shè)置中每個激活的應(yīng)用的 management/commands/ 目錄中查找管理命令艾船。找到的每個模塊都將注冊為管理命令(命令名稱為模塊名稱)。
https://docs.djangoproject.com/en/1.11/howto/custom-management-commands/ 包含更多自定義管理命令的介紹高每。
我們將創(chuàng)建一個自定義管理命令來提醒學生至少注冊一門課程屿岂。命令將向注冊了一段時間但是沒有報讀任何課程的用戶發(fā)送郵件提醒。
在 students 應(yīng)用目錄下創(chuàng)建以下文件結(jié)構(gòu):
編輯 enroll_reminder.py 文件并添加以下代碼:
import datetime
from django.conf import settings
from django.contrib.auth.models import User
from django.core.mail import send_mass_mail
from django.core.management.base import BaseCommand
from django.db.models import Count
class Command(BaseCommand):
help = 'Sends an e-mail reminder to users registered more \
than N days that are not enrolled into any courses yet'
def add_arguments(self, parser):
parser.add_argument('--days', dest='days', type=int)
def handle(self, *args, **options):
emails = []
subject = 'Enroll in a course'
date_joined = datetime.date.today() - datetime.timedelta(
days=options['days'])
users = User.objects.annotate(
course_count=Count('courses_enrolled')).filter(course_count=0,
date_joined__lte=date_joined)
for user in users:
message = 'Dear {},\n\nWe noticed that you didn\'t enroll in any courses yet.What are you waiting for ?'.format(
user.first_name)
emails.append(
(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email]))
send_mass_mail(emails)
self.stdout.write('Sent {} reminders'.format(len(emails)))
這就是我們的 enroll_reminder 命令鲸匿,上面的代碼如下所述:
Command 命令繼承 BaseCommand爷怀。
添加 help 屬性。這個屬性提供運行 python manage.py help enroll_reminder 時打印的簡單描述带欢。
使用 add_arguments() 方法添加 —days 名稱參數(shù)运授,這個參數(shù)用于指定用戶注冊但是沒有報讀任何課程的最小日期以便于提供提醒。
handle() 方法包含實際命令乔煞。獲取命令行解析到的 days 屬性吁朦,然后查詢注冊超過特定日期但是沒有報讀任何課程的用戶,查詢通過聚合計算每個用戶報讀的課程總數(shù)得到渡贾;為每個符合條件的用戶生成提醒郵件并放入 email 列表中逗宜。最后,使用 send_mass_mail() 函數(shù)發(fā)送郵件空骚,send_mass_mail() 函數(shù)可以打開一次 SMTP 鏈接發(fā)送多封郵件纺讲。
我們已經(jīng)創(chuàng)建了第一個管理命令,打開 shell 并運行命令:
python manage.py enroll_reminder --days=20
如果您沒有本地 STMP 服務(wù)器囤屹,查看第二章中關(guān)于為 Django 項目配置 STMP 設(shè)置的內(nèi)容熬甚。我們還可以將以下內(nèi)容添加到 settings/base.py 文件中以便在開發(fā)過程中將郵件打印到標準輸出:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
我們來安排運行管理命令,以便服務(wù)器每天早上8點運行該命令肋坚。如果您使用的是基于 UNIX 的系統(tǒng)乡括,例如 Linux 或Mac OS X 袜硫,請打開 shell 并運行 crontab -e 編輯 cron 握牧,添加以下行:
0 8 * * * python /path/to/educa/manage.py enroll_reminder --days=20
--settings=educa.settings.pro
如果你不熟悉 cron ,可以在 https://en/wikipedia.org/wiki/Cron 找到更多資料。
如果您使用 Windows妖胀,可以使用任務(wù)管理器安全任務(wù)峦剔。 http://windows.microsoft.com/en-au/windows/schedule-task#1TC=windows-7 包含更多相關(guān)內(nèi)容档礁。
定期執(zhí)行任務(wù)的另一個選擇是使用 Celery 創(chuàng)建任何和日程。我們在第七章使用過 Celery 來執(zhí)行異步任務(wù)吝沫。這種情況不需要創(chuàng)建管理命令以及使用 Cron 創(chuàng)建日程呻澜,我們可以創(chuàng)建異步任務(wù)并使用 Celery 心跳日程來執(zhí)行任務(wù)即可递礼。使用 Celery 調(diào)度定期任務(wù)的更多信息見 http://celery.readthedocs.org/en/latest/userguide/periodic-tasks.html 。
注意:
使用 Cron 或 Windows 計劃任務(wù)控制面板時羹幸,使用獨立腳本實現(xiàn)管理命令脊髓。
Django 內(nèi)置 Python 調(diào)用管理命令的方法。我們可以使用代碼運行管理命令:
from django.core import management
management.call_command('enroll_reminder',days=20)
恭喜你栅受!現(xiàn)在可以為應(yīng)用創(chuàng)建自定義管理命令并定期執(zhí)行了将硝。
總結(jié)
本章,我們使用 uWSGI 和 Nginx 配置了生成環(huán)境屏镊,并且學習了如何創(chuàng)建自定義中間件以及如何創(chuàng)建自定義管理命令依疼。