第十三章 上線

13 上線

上一章中椅棺,你為你的項目創(chuàng)建了RESTful API攘残。在本章中讨惩,你會學習以下知識點:

  • 配置一個生產環(huán)境
  • 創(chuàng)建一個自定義的中間件
  • 實現(xiàn)自定義的管理命令

13.1 在生產環(huán)境上線

是時候把你的Django項目部署到生產環(huán)境了滥玷。我們將按以下步驟上線我們的項目:

  1. 為生產環(huán)境配置項目設置。
  2. 使用PostgreSQL數(shù)據庫。
  3. 使用uWSGINgnix設置一個web服務器。
  4. 為靜態(tài)資源提供服務行剂。
  5. 用SSL保護我們的網站。

13.1.1 為多個環(huán)境管理設置

在實際項目中如贷,你可能需要處理多個環(huán)境。你最少會有一個本地環(huán)境和一個生產環(huán)境到踏,但是很可能還有別的環(huán)境倒得。有些項目設置是所有環(huán)境通用的,有些可能需要被每個環(huán)境覆蓋夭禽。讓我們?yōu)槎鄠€環(huán)境配置項目設置,同時保持項目的良好組織谊路。

educa項目目錄中創(chuàng)建settings/目錄讹躯。把項目的settings.py文件移動到settings/目錄中,并重命名為base.py缠劝,然后在新目錄中創(chuàng)建以下文件結構:

settings/
    __init__.py
    base.py
    local.py
    pro.py

這些文件分別是:

  • base.py:包括通用和默認設置的基礎設置文件
  • local.py:你本地環(huán)境的自定義設置
  • pro.py:生產環(huán)境的自定義設置

編輯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))))

我們已經把設置文件移動到了低一級的目錄中,所以我們需要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)境的設置文件脱羡。我們導入base.py文件中定義的所有設置萝究,只為這個生產環(huán)境定義特定設置。我們從base.py文件中拷貝了DEBUGDATABASES設置锉罐,因為每個環(huán)境會設置這些選項帆竹。你可以從base.py文件中移除DEBUGDATABASES設置。

編輯settings/pro.py文件脓规,并添加以下代碼:

from .base import *

DEBUG = False

ADMINS = {
    ('Antonio M', 'email@mydomain.com'),
}

ALLOWED_HOSTS = ['educaproject.com', 'www.educaproject.com']

DATABASES = {
    'default': {
        
    }
}

這些是生產環(huán)境的設置栽连。它們分別是:

  • DEBUG:設置DEBUGFalse對任何生產環(huán)境都是強制的。不這么做會導致追蹤信息和敏感的配置數(shù)據暴露給每一個人侨舆。
  • ADMINS:當DEBUGFalse秒紧,并且一個視圖拋出異常時,所有信息會通過郵件發(fā)送給ADMINS設置中列出的所有人挨下。確保用你自己的信息替換上面的name/e-mail元組熔恢。
  • ALLOWED_HOST:因為DEBUGFalse,Django只允許這個列表中列出的主機為應用提供服務臭笆。這是一個安全措施绩聘。我們包括了educaproject.comwww.educaproject.com域名沥割,我們的網站會使用這兩個域名。
  • DATABASES:我們保留這個設置為空凿菩。我們將在下面討論生產環(huán)境的數(shù)據庫設置机杜。

處理多個環(huán)境時,創(chuàng)建一個基礎的設置文件衅谷,并為每個環(huán)境創(chuàng)建一個設置文件椒拗。環(huán)境設置文件應用從通用設置繼承,并覆寫環(huán)境特定設置获黔。

我們已經把項目設置從默認的settings.py文件放到了不同位置蚀苛。除非你指定使用的設置模塊,否則不能用manage.py工具執(zhí)行任何命令玷氏。在終端執(zhí)行管理命令時堵未,你需要添加--settings標記,或者設置DJANGO_SETTINGS_MODULE環(huán)境變量盏触。打開終端執(zhí)行以下命令:

export DJANGO_SETTINGS_MODULE=educa.settings.pro

這會為當前終端會話是設置DJANGO_SETTINGS_MODULE環(huán)境變量渗蟹。如果你不想為每個新終端都執(zhí)行這個命令,可以在.bashrc.bash_profile文件中赞辩,把這個命令添加到你的終端配置雌芽。如果你不想設置這個變量,則必須使用--settings標記運行管理命令辨嗽,比如:

python manage.py migrate --settings=educa.settings.pro

現(xiàn)在你已經成功的為多個環(huán)境組織好了設置世落。

13.1.2 安裝PostgreSQL

在本書中,我們一直使用SQLite數(shù)據庫糟需。它的設置簡單快捷屉佳,但對于生產環(huán)境,你需要一個更強大的數(shù)據庫洲押,比如PostgreSQL忘古,MySQL或者Oracle。我們將在生產環(huán)境使用PostgreSQL诅诱。因為PostgreSQL提供的特性和性能髓堪,所以它是Django的推薦數(shù)據庫。Django還自帶django.contrib.postgres包娘荡,允許你利用PostgreSQL的特定特性干旁。你可以在這里閱讀更多關于這個模塊的信息。

如果你正在使用Linux炮沐,使用以下命令安裝PostgreSQL的依賴:

sudo apt-get install libpq-dev python-dev

然后使用以下命令安裝PostgreSQL:

sudo apt-get install postgresql postgresql-contrib

如果你正在使用Mac OS X或者Windows争群,你可以在這里下載PostgreSQL。

讓我們創(chuàng)建一個PostgreSQL用戶大年。打開終端换薄,并執(zhí)行以下命令:

su postgres
createuser -dP educa

會提示你輸入密碼和給予這個用戶的權限玉雾。輸入密碼和權限,然后使用以下命令創(chuàng)建一個新的數(shù)據庫:

createdb -E utf8 -U educa educa

然后編輯settings/pro.py文件轻要,并修改DATABASES設置:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'educa',
        'USER': 'educa',
        'PASSWORD': '****',
    }
}

用你創(chuàng)建的數(shù)據庫名和用戶憑證替換上面的數(shù)據「囱現(xiàn)在新數(shù)據庫是空的。執(zhí)行以下命令應用所有數(shù)據庫遷移:

python manage.py migrate

最后使用以下命令創(chuàng)建一個超級用戶:

python manage.py createsuperuser

13.1.3 檢查你的項目

Django包括check命令冲泥,可以在任何時候檢查你的項目驹碍。這個命令檢查Django項目中安裝的應用,并輸出所有錯誤或警告凡恍。如果你包括了--deploy選項志秃,則只會觸發(fā)生成環(huán)境相關的額外檢查。打開終端嚼酝,運行以下命令執(zhí)行一次檢查:

python manage.py check --deploy

你會看到沒有錯誤浮还,但是有幾個警告的輸出。這意味著檢查成功了闽巩,但你應該查看警告钧舌,看看是否可以做一些工作,讓你的項目在生產環(huán)境上是安全的又官。我們不會深入其中,你需要記住漫试,在生產環(huán)境中使用之前六敬,你應該檢查項目所有相關的問題。

13.2 通過WSGI為Django提供服務

Django的主要部署平臺是WSGI驾荣。WSGI是Web Server Gateway Interface的縮寫外构,它是在網絡上為Python應用提供服務的標準。

當你使用startproject命令創(chuàng)建一個新項目時播掷,Django會在項目目錄中創(chuàng)建一個wsgi.py文件审编。這個文件包含一個WSGI應用的可調用對象,它是你應用的訪問點歧匈。使用Django開發(fā)服務器運行你的項目垒酬,以及在生產環(huán)境中用你選擇的服務器部署應用都會使用WSGI。

你可以在這里進一步學習WSGI件炉。

13.2.1 安裝uWSGI

在這本書中勘究,你一直使用Django開發(fā)服務器在本地環(huán)境運行項目。但是你需要一個實際的Web服務器在生產環(huán)境部署你的應用斟冕。

uWSGI是一個非晨诟猓快速的Python應用服務器。它使用WSGI規(guī)范與你的Python應用通信磕蛇。uWSGI把Web請求轉換為Django項目可用處理的格式景描。

使用以下命令安裝uWSGI

pip install uwsgi

如果你正在使用Mac OS X十办,你可以使用brew install uwsgi命令安裝uWSGI。如果你想在Windows上安裝uWSGI超棺,則需要Cygwin向族。但是推薦你在基于Unix的環(huán)境中使用uWSGI

13.2.2 配置uWSGI

你可以從命令行中啟動uWSGI说搅。打開終端炸枣,在educa項目目錄中執(zhí)行以下命令:

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/

如果你沒有權限,則需要在命令前加上sudo弄唧。

通過這個命令适肠,我們用以下選項在本地運行uWSGI

  • 我們使用educa.wsgi:application作為WSGI的可調用對象。
  • 我們?yōu)樯a環(huán)境加載設置候引。
  • 我們使用我們的虛擬環(huán)境侯养。用你的實際虛擬環(huán)境目錄替換virtualenv選項的路徑。如果你沒有使用虛擬環(huán)境澄干,則跳過這個選項逛揩。

如果你沒有在項目目錄下運行命令,則需要用你的項目目錄包括--chdir=/path/to/educa/選項麸俘。

在瀏覽器中打開http://127.0.0.1:80/辩稽。你會看到沒有加載CSS或者圖片的HTML。這是有道理的从媚,因為我們還沒有配置uWSGI為靜態(tài)文件提供服務逞泄。

uWSGI允許你在.ini文件中定義自定義配置。它比在命令行中傳遞選項更方便拜效。在主educa/目錄下創(chuàng)建以下文件結構:

config/
    uwsgi.ini

編輯uwsgi.ini文件喷众,添加以下代碼:

[uwsgi]
# 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紧憾。
  • baseeduca項目的絕對路徑到千。用你的絕對路徑替換它。

還有一些會在uWSGI選項中使用的自定義變量赴穗。你可以定義任意變量憔四,只要它跟uWSGI選項名不同就行。我們設置了以下選項:

  • master:啟用主進程般眉。
  • virtualenv:你的虛擬環(huán)境路徑加矛。用響應的路徑替換它。
  • pythonpath:添加到Python路徑的路徑煤篙。
  • chdir:項目目錄的路徑斟览,加載應用之前,uWSGI改變到這個目錄辑奈。
  • env:環(huán)境變量苛茂。我們包括了DJANGO_SETTINGS_MODULE變量已烤,指向生產環(huán)境的設置。
  • module:使用的WSGI模塊妓羊。我們把它設置為application可調用對象胯究,它包含在項目的wsgi模塊中。
  • socket:綁定到服務器的UNIX/TCP套接字躁绸。

socket選項用于與第三方路由(比如Nginx)通信裕循,而http選項用于uWGSI接收傳入的HTTP請求,并自己進行路由净刮。因為我們將配置Nginx作為Web服務器剥哑,并通過套接字與uWSGI通信,所以我們將使用套接字運行uWSGI淹父。

你可以在這里找到所有可用的uWSGI選項列表株婴。

現(xiàn)在你可以使用自定義配置運行uWSGI

uwsgi --ini config/uwsgi.ini

因為uWSGI通過套接字運行,所以你現(xiàn)在不能在瀏覽器中訪問uWSGI實例暑认。讓我們完成生產環(huán)境困介。

13.2.3 安裝Nginx

當你為一個網站提供服務時,你必須為動態(tài)內容提供服務蘸际,同時還需要為靜態(tài)文件座哩,比如CSS,JavaScript文件和圖片提供服務粮彤。雖然uWSGI可以為靜態(tài)文件提供服務根穷,但它會在HTTP請求上增加不必要的開銷。因此驾诈,推薦在uWSGI之前設置一個Web服務器(比如Nginx)缠诅,為靜態(tài)文件提供服務溶浴。

Nginx是一個專注于高并發(fā)乍迄,高性能和低內存使用的Web服務器。Nginx還可以充當反向代理士败,接收HTTP請求闯两,并把它們路由到不同的后臺。通常情況下谅将,你會在前端使用一個Web服務器(比如Nginx)漾狼,高效快速的為靜態(tài)文件提供服務,并且把動態(tài)請求轉發(fā)到uWSGI的工作線程饥臂。通過使用Nginx逊躁,你還可以應用規(guī)則,并從它的反向代理功能中獲益隅熙。

使用以下命令安裝Nginx:

sudo apt-get install nginx

如果你正在使用Mac OS X稽煤,你可以使用brew install nginx命令安裝Nginx核芽。你可以在這里找到Windows的二進制版本。

13.2.4 生產環(huán)境

下圖展示了我們最終的生產環(huán)境:

當客戶端瀏覽器發(fā)起一個HTTP請求時酵熙,會發(fā)生以下事情:

  1. Nginx接收HTTP請求轧简。
  2. 如果請求的是靜態(tài)文件,則Nginx直接為靜態(tài)文件提供服務匾二。如果請求的是動態(tài)頁面哮独,則Nginx通過套接字把請求轉發(fā)給uWSGI
  3. uWSGI把請求傳遞給Django處理察藐。返回的HTTP響應傳遞回Nginx皮璧,然后再返回到客戶端瀏覽器。

13.2.5 配置Nginx

config/目錄中創(chuàng)建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的基礎配置恶导。我們設置了一個名為educa的上游(upstream),指向uWSGI創(chuàng)建的套接字浸须。我們使用server指令惨寿,并添加以下配置:

  1. 我們告訴Nginx監(jiān)聽80端口。
  2. 我們設置服務名為www.educaproject.comeducaproject.com删窒。Nginx會為來自這兩個域名的請求服務裂垦。
  3. 最后,我們指定/路徑下的所有請求都路由到educa套接字(uWSGI)肌索。我們還包括了Nginx自帶的默認uWSGI配置參數(shù)蕉拢。

你可以在這里閱讀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

uwsgi --ini config/uwsgi.ini

打開第二個終端闸准,用以下命令啟動Nginx:

service nginx start

因為我們正在使用簡單的主機名,所以需要把它重定向到本機梢灭。編輯你的/etc/hosts文件夷家,添加下面兩行:

127.0.0.1 educaproject.com
127.0.0.1 www.educaproject.com

這樣,我們把兩個主機名路由到我們的本地服務器敏释。在生產環(huán)境中你不需要這么用库快,因為你會在域名的DNS配置中把主機名指向你的服務器。

在瀏覽器中打開http://educaproject.com钥顽。你可以看到你的網站义屏,仍然沒有加載任何靜態(tài)資源。我們的生產環(huán)境馬上就好了。

13.2.6 為靜態(tài)資源和多媒體資源提供服務

為了最好的性能闽铐,我們將直接使用Nginx為靜態(tài)資源提供服務膀曾。

編輯settings/base.py文件,并添加以下代碼:

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

我們需要用Django導出靜態(tài)資源阳啥。collectstatic命令從所有應用中拷貝靜態(tài)文件添谊,并把它們存儲到STATIC_ROOT目錄中。打開終端執(zhí)行以下命令:

python manage.py collectstatic

你會看到以下輸出:

You have requested to collect static files at the destination location as specified in your settings:

    /educa/static
    
This will overwrite existing files!
Are you sure you want to do this?

輸入yes讓Django拷貝這些文件察迟。你會看到以下輸出:

78 static files copied to /educa/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的配置:

server nginx reload

在瀏覽器中打開http://educaproject.com/。現(xiàn)在你可以看到靜態(tài)文件了概荷。我們成功的配置了Nginx來提供靜態(tài)文件秕岛。

13.3 使用SSL保護鏈接

SSL協(xié)議(Secure Sockets Layer)通過安全連接成為了為網站提供服務的標準。強烈鼓勵你在HTTPS下為網站提供服務误证。我們將在Nginx中配置一個SSL證書继薛,安全的為我們網站提供服務。

13.3.1 創(chuàng)建SSL證書

educa項目目錄中創(chuàng)建ssl目錄愈捅。然后使用以下命令生成一個SSL證書:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl/educa.key -out ssl/educa.crt

我們生成一個私有key和一個有效期是1年的2048個字節(jié)的證書遏考。你將被要求輸入以下信息:

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ù)據。最重要的字段是Common Name蓝谨。你必須制定證書的域名灌具。我們將使用educaproject.com

這會在ssl/目錄中生成educa.key的私有key和實際證書educa.crt文件譬巫。

13.3.2 配置Nginx使用SSL

編輯nginx.conf文件咖楣,并修改server指令,讓它包括以下SSL指令:

server {
    listen 80;
    listen 443 ssl;
    ssl_certificate /home/zenx/educa/ssl/educa.crt;
    ssl_certificate_key /home/zenx/educa/ssl/educa.key;
    server_name www.educaproject.com educaproject.com;
    # ...

現(xiàn)在我們的服務器在80端口監(jiān)聽HTTP芦昔,在443端口監(jiān)聽HTTPS诱贿。我們用ssl_certificate制定SSL證書,用ssl_certificate_key制定證書key烟零。

使用以下命令重啟Nginx:

sudo service nginx restart

Nginx將會加載新的配置瘪松。在瀏覽器中打開http://educaproject.com咸作。你會看到一個類似這樣的靜態(tài)消息:

不同的瀏覽器锨阿,這個消息可能會不同。它警告你记罚,你的網站沒有使用受信任的證書墅诡;瀏覽器不能驗證網站的身份。這是因為我們簽署了自己的證書,而不是從受信任的認證機構獲得證書末早。當你擁有真正的域名時烟馅,你可以申請受信任的CA為其頒發(fā)SSL證書,以便瀏覽器可以驗證其身份然磷。

如果你想為真正域名獲得受信任的證書郑趁,你可以參數(shù)Linux Foundation創(chuàng)建的Let's Encrypt項目。這是一個協(xié)作項目姿搜,目的是免費的簡化獲取和更新受信任的SSL證書寡润。你可以在這里閱讀更多信息。

點擊Add Exception按鈕舅柜,讓瀏覽器知道你信任這個證書梭纹。你會看到瀏覽器在URL旁顯示一個鎖的圖標,如下圖所示:

如果你點擊鎖圖標致份,則會顯示SSL證書的詳情变抽。

13.3.3 為SSL配置我們的項目

Django包括一些SSL的特定設置。編輯settings/pro.py設置文件氮块,添加以下代碼:

SECURE_SSL_REDIRECT = True
CSRF_COOKIE_SECURE = True

這些設置分別是:

  • SECURE_SSL_REDIRECT:HTTP請求是否必須重定義到HTTPS請求
  • CSRF_COOKIE_SECURE:這必須為跨站點請求保護設置為建立一個安全的cookie

非常棒绍载!你已經配置了一個生產環(huán)境,它會為你的項目提供高性能的服務滔蝉。

13.4 創(chuàng)建自定義的中間件

你已經了解了MIDDLEWARE設置逛钻,其中包括項目的中間件。一個中間件是一個類锰提,其中包括一些在全局執(zhí)行的特定方法曙痘。你可以把它看成一個低級別的插件系統(tǒng),允許你實現(xiàn)在請求或響應過程中執(zhí)行的鉤子立肘。每個中間件負責會在所有請求或響應中執(zhí)行的一些特定操作边坤。

避免在中間件中添加開銷昂貴的處理,因為它們?yōu)樵诿總€請求中執(zhí)行谅年。

當收到一個HTTP請求時茧痒,中間件會以MIDDLEWARE設置中的出現(xiàn)順序執(zhí)行。當一個HTTP響應由Django生成時融蹂,中間件的方法會逆序執(zhí)行旺订。

下圖展示了請求和響應階段時,中間件方法的執(zhí)行順序超燃。它還展示了可能被調用的中間件方法:

在請求階段区拳,會執(zhí)行中間件的以下方法:

  1. process_request(request):在Django決定執(zhí)行哪個視圖之前,在每個請求上調用意乓。request是一個HttpRequest實例樱调。
  2. process_view(request, view_func, view_args, view_kwargs):在Django調用視圖之前調用。它可以訪問視圖函數(shù)及其收到的參數(shù)

在響應階段,會執(zhí)行中間件的以下方法:

  1. process_exception(request, exception):只有視圖函數(shù)拋出Exception異常時才會調用笆凌。
  2. process_template_response(request, response):視圖執(zhí)行完成后調用圣猎,只有當response對象有render()方法時才調用(比如它是TemplateResponse或者等價對象)
  3. process_response(request, response):響應返回到瀏覽器之前,在所有響應上調用乞而。

因為中間件可以依賴之前已經執(zhí)行的其它中間件方法在請求中設置的數(shù)據送悔,所以MIDDLEWARE設置中的順序很重要。請注意爪模,即使因為前一個中間件返回了HTTP響應放祟,導致process_request()process_view()被跳過,中間件的process_response()方法也會被調用呻右。這意味著process_response()不能依賴于請求階段設置的數(shù)據跪妥。如果一個異常被某個中間件處理,并返回了一個響應声滥,則之前的中間件類不會被調用眉撵。

當添加新的中間件到MIDDLEWARE設置中時,確保把它放在了正確的位置落塑。在請求階段纽疟,中間件方法按設置中的出現(xiàn)順序執(zhí)行,響應階段則是逆序執(zhí)行憾赁。

你可以在這里查看更多關于中間件的信息污朽。

我們將創(chuàng)建一個自定義的中間件,允許通過自定義子域名訪問課程龙考。每個課程詳情視圖的URL(比如http://educaproject.com/courses/django/)也可以通過子域名(用課程的slug字段構建)訪問蟆肆,比如http://django.educaproject.com/

13.4.1 創(chuàng)建子域名中間件

中間件可以位于項目的任何地方晦款。但是炎功,推薦的方式是在應用目錄中創(chuàng)建一個middleware.py文件。

courses應用目錄中創(chuàng)建middleware.py文件缓溅,并添加以下代碼:

from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404, redirect
from .models import Course

class SubdomainCourseMiddleware:
    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)建了一個實現(xiàn)了process_request()的中間件蛇损。當收到HTTP請求時,我們執(zhí)行以下任務:

  1. 我們獲得請求中使用的主機名坛怪,并把它拆分為多個部分淤齐。比如,如果用戶訪問的是mycourse.educaproject.com袜匿,則會生成['mycourse', 'educaproject', 'com']列表更啄。
  2. 通過檢查拆分后是否生成兩個以上的元素,我們核實包括子域名的主機名沉帮。如果主機名包括子域名锈死,并且它不是www,則嘗試使用子域名提供的slug獲得課程穆壕。
  3. 如果沒有找到課程待牵,我們拋出Http 404異常。否則喇勋,我們使用主域名重定向到課程詳情的URL缨该。

編輯項目的settings/base.py文件,在MIDDLEWARE設置底部添加courses.middleware.SubdomainCourseMiddleware

MIDDLEWARE = [
    # ...
    'courses.middleware.SubdomainCourseMiddleware',
]

現(xiàn)在我們的中間件會在每個請求上執(zhí)行川背。

13.4.2 使用Nginx為多個子域名服務

我們需要Nginx為帶任意可能子域名的我們的網站提供服務贰拿。編輯config/nginx.conf文件,找到這一行代碼:

server_name www.educaproject.com educaproject.com;

替換為下面這一行代碼:

server_name *.educaproject.com educaproject.com;

通過使用星號熄云,這條規(guī)則會應用與educaproject.com的所有子域名膨更。為了在本地測試我們的中間件,我們需要在/etc/hosts中添加想要測試的子域名缴允。要用別名為djangoCourse對象測試中間件荚守,需要在/etc/hosts文件添加這一行:

127.0.0.1 django.educaproject.com

然后在瀏覽器中打開https://django.educaproject.com/。中間件會通過子域名找到課程练般,并重定向到https://educaproject.com/course/django/矗漾。

13.5 實現(xiàn)自定義管理命令

Django允許你的應用為manage.py工具注冊自定義管理命令。例如薄料,我們在第九章使用makemessagescompilemessages管理命令來創(chuàng)建和編譯轉換文件敞贡。

一個管理命令由一個Python模塊組成,其中Python模塊包括一個從django.core.management.BaseCommand繼承的Command類摄职。你可以創(chuàng)建簡單命令誊役,或者讓它們接收位置和可選參數(shù)作為輸入。

Django在INSTALLED_APPS設置中激活的每個應用的management/commands/目錄中查找管理命令谷市。發(fā)現(xiàn)的每個模塊注冊為以其命名的管理命令势木。

你可以在這里進一步學習自定義管理命令。

我們將注冊一個自定義管理命令歌懒,提供學生至少報名一個課程啦桌。該命令會給注冊時間長于指定時間,但尚未報名任何課程的用戶發(fā)送一封提醒郵件及皂。

students應用目錄中創(chuàng)建以下文件結構:

management/
    __init__.py
    commands/
        __init__.py
        enroll_reminder.py

編輯enroll_reminder.py文件甫男,并添加以下代碼:

import datetime
from django.conf import settings
from django.core.management.base import BaseCommand
from django.core.mail import send_mass_mail
from django.contrib.auth.models import User
from django.db.models import Count

class Command(BaseCommand):
    help = 'Send 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', typy=int)

    def handle(self, *args, **kwargs):
        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.'.format(user.first_name)
            emails.append((
                subject,
                message,
                settings.DEFAULT_FROM_EMAIL,
                [user.email]
            ))
        send_mass_mail(emails)
        self.stdout.write('Sent {} reminders' % len(emails))

這是我們的enroll_reminder命令。這段代碼完成以下任務:

  • Command類從BaseCommand繼承验烧。
  • 我們包括了一個help屬性板驳。該屬性為命令提供了一個簡單描述,如果你執(zhí)行python manage.py help enroll_reminder命令碍拆,則會打印這個描述若治。
  • 我們使用add_arguments()方法添加--days命名參數(shù)慨蓝。該參數(shù)用于指定用戶注冊了,但沒有報名參加任何課程端幼,從而需要接收提醒郵件的最小天數(shù)礼烈。
  • handle()方法包括實際命令。我們從命令行解析中獲得days屬性婆跑。我們檢索注冊天數(shù)超過指定天數(shù)此熬,當仍沒有參加任何課程的用戶。我們用一個用戶報名參加的總課程數(shù)量注解(annotate)QuerySet實現(xiàn)此目的滑进。我們?yōu)槊總€用戶生成一封提醒郵件犀忱,并把它添加到emails列表中。最后扶关,我們用send_mass_mail()函數(shù)發(fā)送郵件阴汇,這個函數(shù)打開單個SMTP連接發(fā)送所有郵件,而不是每發(fā)送一封郵件打開一個連接节槐。

你已經創(chuàng)建了第一個管理命令鲫寄。打開終端執(zhí)行你的命令:

python manage.py enroll_reminder --days=20

如果你沒有正在運行的本地SMTP服務器,你可以參考第二章,我們?yōu)榈谝粋€Django項目配置了SMTP設置。另外的诵,你可以添加以下行到settings/local.py文件,讓Django在開發(fā)期間輸出郵件到標準輸出:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

讓我們調度管理命令未斑,讓服務器沒有早上8點運行它。如果你正在使用基于Unix的系統(tǒng)币绩,比如Linux或者Mac OS X蜡秽,打開終端執(zhí)行crontab -e來編輯計劃任務。在其中添加下面這一行:

0 8 * * * python /path/to/educa/manage.py enroll_reminder --days=20 --settings=educa.settings.pro

如果你不熟悉Cron缆镣,你可以在這里學習它芽突。

如果你正在使用Windows,你可以使用Task scheduler調度任務董瞻。你可以在這里進一步學習它寞蚌。

定期執(zhí)行操作的另一個方法是用Celery創(chuàng)建和調度任務。記住钠糊,我們在第七章使用Celery執(zhí)行了異步任務挟秤。除了使用Cron創(chuàng)建和調用管理命令,你還可以使用Celery beat scheduler創(chuàng)建異步任務并執(zhí)行它們抄伍。你可以在這里進一步學習使用Celery調度定時任務艘刚。

對要使用Cron或者Windows調度任務控制面板調度的獨立腳本使用管理命令。

Django還包括一個用Python調用管理命令的工具截珍。你可以在代碼中如下執(zhí)行管理命令:

from django.core import management
management.call_command('enroll_reminder', days=20)

恭喜你攀甚!現(xiàn)在你已經為你的應用創(chuàng)建了自定義管理命令箩朴,并在需要時調度它們。

13.6 總結

在這一章中秋度,你使用uWSGINginx配置了一個生產環(huán)境炸庞。你還實現(xiàn)了一個自定義中間件,并學習了如何創(chuàng)建自定義管理命令静陈。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末燕雁,一起剝皮案震驚了整個濱河市诞丽,隨后出現(xiàn)的幾起案子鲸拥,更是在濱河造成了極大的恐慌,老刑警劉巖僧免,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刑赶,死亡現(xiàn)場離奇詭異,居然都是意外死亡懂衩,警方通過查閱死者的電腦和手機撞叨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浊洞,“玉大人牵敷,你說我怎么就攤上這事》ㄏ#” “怎么了枷餐?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長苫亦。 經常有香客問我毛肋,道長,這世上最難降的妖魔是什么屋剑? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任润匙,我火速辦了婚禮,結果婚禮上唉匾,老公的妹妹穿的比我還像新娘孕讳。我一直安慰自己,他們只是感情好巍膘,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布卫病。 她就那樣靜靜地躺著,像睡著了一般典徘。 火紅的嫁衣襯著肌膚如雪蟀苛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天逮诲,我揣著相機與錄音帜平,去河邊找鬼幽告。 笑死,一個胖子當著我的面吹牛裆甩,可吹牛的內容都是我干的冗锁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼嗤栓,長吁一口氣:“原來是場噩夢啊……” “哼冻河!你這毒婦竟也來了?” 一聲冷哼從身側響起茉帅,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤叨叙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后堪澎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體擂错,經...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年樱蛤,在試婚紗的時候發(fā)現(xiàn)自己被綠了钮呀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡昨凡,死狀恐怖爽醋,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情便脊,我是刑警寧澤蚂四,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站就轧,受9級特大地震影響证杭,放射性物質發(fā)生泄漏。R本人自食惡果不足惜妒御,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一解愤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乎莉,春花似錦送讲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至边灭,卻和暖如春异希,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绒瘦。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工称簿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留扣癣,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓憨降,卻偏偏與公主長得像父虑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子授药,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容