Python有眾多優(yōu)點(diǎn),其中之一就是“開機(jī)即用”原則: 安裝Python的同時(shí)會安裝好大量的標(biāo)準(zhǔn)軟件包,這樣 你可以立即使用而不用自己去下載。 Django也遵循這個原則,它同樣包含了自己的標(biāo)準(zhǔn)庫。 這一章就來講 這些集成的子框架。
Django的標(biāo)準(zhǔn)庫存放在 django.contrib 包中。每個子包都是一個獨(dú)立的附加功能包。 這些子包一般是互相獨(dú)立的,不過有些django.contrib子包需要依賴其他子包。
在 django.contrib 中對函數(shù)的類型并沒有強(qiáng)制要求 。其中一些包中帶有模型(因此需要你在數(shù)據(jù)庫中安裝對應(yīng)的數(shù)據(jù)表),但其它一些由獨(dú)立的中間件及模板標(biāo)簽組成。
django.contrib 開發(fā)包共有的特性是: 就算你將整個django.contrib開發(fā)包刪除,你依然可以使用 Django 的基礎(chǔ)功能而不會遇到任何問題。 當(dāng) Django 開發(fā)者向框架增加新功能的時(shí),他們會嚴(yán)格根據(jù)這一原則來決定是否把新功能放入django.contrib中。
django.contrib 由以下開發(fā)包組成:
admin : 自動化的站點(diǎn)管理工具。 請查看第6章。
admindocs:為Django admin站點(diǎn)提供自動文檔。 本書沒有介紹這方面的知識;詳情請參閱Django官方文檔。
auth : Django的用戶驗(yàn)證框架。 參見第十四章。
comments : 一個評論應(yīng)用,目前,這個應(yīng)用正在緊張的開發(fā)中,因此在本書出版的時(shí)候還不能給出一個完整的說明,關(guān)于這個應(yīng)用的更多信息請參見Django的官方網(wǎng)站. 本書沒有介紹這方面的知識;詳情請參閱Django官方文檔。
contenttypes : 這是一個用于引入文檔類型的框架,每個安裝的Django模塊作為一種獨(dú)立的文檔類型。 這個框架主要在Django內(nèi)部被其他應(yīng)用使用,它主要面向Django的高級開發(fā)者。 可以通過閱讀源碼來了解關(guān)于這個框架的更多信息,源碼的位置在 django/contrib/contenttypes/。
csrf : 這個模塊用來防御跨站請求偽造(CSRF)。參 見后面標(biāo)題為”CSRF 防御”的小節(jié)。
databrowse:幫助你瀏覽數(shù)據(jù)的Django應(yīng)用。 本書沒有介紹這方面的知識;詳情請參閱Django官方文檔。
flatpages : 一個在數(shù)據(jù)庫中管理單一HTML內(nèi)容的模塊。 參見后面標(biāo)題為“Flatpages”的小節(jié)。
formtools:一些列處理表單通用模式的高級庫。 本書沒有介紹這方面的知識;詳情請參閱Django官方文檔。
gis:為Django提供GIS(Geographic Information Systems)支持的擴(kuò)展。 舉個例子,它允許你的Django模型保存地理學(xué)數(shù)據(jù)并執(zhí)行地理學(xué)查詢。 這個庫比較復(fù)雜,本書不詳細(xì)介紹。 請參看http://geodjango.org/上的文檔。
humanize : 一系列 Django 模塊過濾器,用于增加數(shù)據(jù)的人性化。 參閱稍后的章節(jié)《人性化數(shù)據(jù)》。
localflavor:針對不同國家和文化的混雜代碼段。 例如,它包含了驗(yàn)證美國的郵編 以及愛爾蘭的身份證號的方法。
markup : 一系列的 Django 模板過濾器,用于實(shí)現(xiàn)一些常用標(biāo)記語言。 參閱后續(xù)章節(jié)《標(biāo)記過濾器》。
redirects : 用來管理重定向的框架。 參看后面的“重定向”小節(jié)。
sessions : Django 的會話框架。 參見14章。
sitemaps : 用來生成網(wǎng)站地圖的 XML 文件的框架。 參見13章。
sites : 一個讓你可以在同一個數(shù)據(jù)庫與 Django 安裝中管理多個網(wǎng)站的框架。 參見下一節(jié):
syndication : 一個用 RSS 和 Atom 來生成聚合訂閱源的的框架。 參見13章。
本章接下來將詳細(xì)描述前面沒有介紹過的 django.contrib 開發(fā)包內(nèi)容。
Django 的多站點(diǎn)系統(tǒng)是一種通用框架,它讓你可以在同一個數(shù)據(jù)庫和同一個Django項(xiàng)目下操作多個網(wǎng)站。 這是一個抽象概念,理解起來可能有點(diǎn)困難,因此我們從幾個讓它能派上用場的實(shí)際情景入手。
正如我們在第一章里所講,Django 構(gòu)建的網(wǎng)站 LJWorld.com 和 Lawrance.com 是用由同一個新聞組織控制的: 肯薩斯州勞倫斯市的 勞倫斯日報(bào)世界 報(bào)紙。 LJWorld.com 主要做新聞,而 Lawrence.com 關(guān)注本地娛樂。 然而有時(shí),編輯可能需要把一篇文章發(fā)布到 兩個 網(wǎng)站上。
解決此問題的死腦筋方法可能是使用每個站點(diǎn)分別使用不同的數(shù)據(jù)庫,然后要求站點(diǎn)維護(hù)者把同一篇文章發(fā)布兩次: 一次為 LJWorld.com,另一次為Lawrence.com。 但這對站點(diǎn)管理員來說是低效率的,而且為同一篇文章在數(shù)據(jù)庫里保留多個副本也顯得多余。
更好的解決方案? 兩個網(wǎng)站用的是同一個文章數(shù)據(jù)庫,并將每一篇文章與一個或多個站點(diǎn)用多對多關(guān)系關(guān)聯(lián)起來。 Django 站點(diǎn)框架提供數(shù)據(jù)庫表來記載哪些文章可以被關(guān)聯(lián)。 它是一個把數(shù)據(jù)與一個或多個站點(diǎn)關(guān)聯(lián)起來的鉤子。
LJWorld.com 和 Lawrence.com 都有郵件提醒功能,使讀者注冊后可以在新聞發(fā)生后立即收到通知。 這是一種完美的的機(jī)制: 某讀者提交了注冊表單,然后馬上就受到一封內(nèi)容是“感謝您的注冊”的郵件。
把這個注冊過程的代碼實(shí)現(xiàn)兩遍顯然是低效、多余的,因此兩個站點(diǎn)在后臺使用相同的代碼。 但感謝注冊的通知在兩個網(wǎng)站中需要不同。 通過使用 Site 對象,我們通過使用當(dāng)前站點(diǎn)的 name (例如 'LJWorld.com' )和domain (例如 'www.ljworld.com' )可以把感謝通知抽提出來。
Django 的多站點(diǎn)框架為你提供了一個位置來存儲 Django 項(xiàng)目中每個站點(diǎn)的 name 和 domain ,這意味著你可以用同樣的方法來重用這些值。
多站點(diǎn)框架與其說是一個框架,不如說是一系列約定。 所有的一切都基于兩個簡單的概念:
位于 django.contrib.sites 的 Site 模型有 domain 和 name 兩個字段。
如何運(yùn)用這兩個概念由你決定,但 Django 是通過幾個簡單的約定自動使用的。
安裝多站點(diǎn)應(yīng)用要執(zhí)行以下幾個步驟:
將 'django.contrib.sites' 加入到 INSTALLED_APPS 中。
運(yùn)行 manage.py syncdb 命令將 django_site 表安裝到數(shù)據(jù)庫中。 這樣也會建立默認(rèn)的站點(diǎn)對象,域名為 example.com。
把example.com改成你自己的域名,然后通過Django admin站點(diǎn)或Python API來添加其他Site對象。 為該 Django 項(xiàng)目支撐的每個站(或域)創(chuàng)建一個 Site 對象。
下面幾節(jié)講述的是用多站點(diǎn)框架能夠完成的幾項(xiàng)工作。
正如在情景一中所解釋的,要在多個站點(diǎn)間重用數(shù)據(jù),僅需在模型中為 Site 添加一個 多對多字段 即可,例如:
from django.db import models
from django.contrib.sites.models import Site
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
sites = models.ManyToManyField(Site)
這是在數(shù)據(jù)庫中為多個站點(diǎn)進(jìn)行文章關(guān)聯(lián)操作的基礎(chǔ)步驟。 在適當(dāng)?shù)奈恢檬褂迷摷夹g(shù),你可以在多個站點(diǎn)中重復(fù)使用同一段 Django 視圖代碼。 繼續(xù) Article 模型范例,下面是一個可能的 article_detail 視圖:
from django.conf import settings
from django.shortcuts import get_object_or_404
from mysite.articles.models import Article
def article_detail(request, article_id):
a = get_object_or_404(Article, id=article_id, sites__id=settings.SITE_ID)
# ...
該視圖方法是可重用的,因?yàn)樗鶕?jù) SITE_ID 設(shè)置的值動態(tài)檢查 articles 站點(diǎn)。
例如, LJWorld.coms 設(shè)置文件中有有個 SITE_ID 設(shè)置為 1 ,而 Lawrence.coms 設(shè)置文件中有個 SITE_ID 設(shè)置為 2 。如果該視圖在 LJWorld.coms 處于激活狀態(tài)時(shí)被調(diào)用,那么它將把查找范圍局限于站點(diǎn)列表包括 LJWorld.com 在內(nèi)的文章。
同樣,你也可以使用 外鍵 在多對一關(guān)系中將一個模型關(guān)聯(lián)到 Site 模型。
舉例來說,如果某篇文章僅僅能夠出現(xiàn)在一個站點(diǎn)上,你可以使用下面這樣的模型:
from django.db import models
from django.contrib.sites.models import Site
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
site = models.ForeignKey(Site)
這與前一節(jié)中介紹的一樣有益。
在底層,通過在 Django 視圖中使用多站點(diǎn)框架,你可以讓視圖根據(jù)調(diào)用站點(diǎn)不同而完成不同的工作,例如:
from django.conf import settings
def my_view(request):
if settings.SITE_ID == 3:
# Do something.
else:
# Do something else.
當(dāng)然,像那樣對站點(diǎn) ID 進(jìn)行硬編碼是比較難看的。 略為簡潔的完成方式是查看當(dāng)前的站點(diǎn)域:
from django.conf import settings
from django.contrib.sites.models import Site
def my_view(request):
current_site = Site.objects.get(id=settings.SITE_ID)
if current_site.domain == 'foo.com':
# Do something
else:
# Do something else.
從 Site 對象中獲取 settings.SITE_ID 值的做法比較常見,因此 Site 模型管理器 (Site.objects ) 具備一個get_current() 方法。 下面的例子與前一個是等效的:
from django.contrib.sites.models import Site
def my_view(request):
current_site = Site.objects.get_current()
if current_site.domain == 'foo.com':
# Do something
else:
# Do something else.
注意
在這個最后的例子里,你不用導(dǎo)入 django.conf.settings 。
正如情景二中所解釋的那樣,依據(jù)DRY原則(不做重復(fù)工作),你只需在一個位置儲存站名和域名,然后引用當(dāng)前Site 對象的 name 和 domain 。例如: 例如:
from django.contrib.sites.models import Site
from django.core.mail import send_mail
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
current_site = Site.objects.get_current()
send_mail('Thanks for subscribing to %s alerts' % current_site.name,
'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % current_site.name,
'editor@%s' % current_site.domain,
[user_email])
# ...
繼續(xù)我們正在討論的 LJWorld.com 和 Lawrence.com 例子,在Lawrence.com 該郵件的標(biāo)題行是“感謝注冊 Lawrence.com 提醒信件”。 在 LJWorld.com ,該郵件標(biāo)題行是“感謝注冊 LJWorld.com 提醒信件”。 這種站點(diǎn)關(guān)聯(lián)行為方式對郵件信息主體也同樣適用。
完成這項(xiàng)工作的一種更加靈活(但更重量級)的方法是使用 Django 的模板系統(tǒng)。 假定 Lawrence.com 和 LJWorld.com 各自擁有不同的模板目錄( TEMPLATE_DIRS ),你可將工作輕松地轉(zhuǎn)交給模板系統(tǒng),如下所示:
from django.core.mail import send_mail
from django.template import loader, Context
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
subject = loader.get_template('alerts/subject.txt').render(Context({}))
message = loader.get_template('alerts/message.txt').render(Context({}))
send_mail(subject, message, 'do-not-reply@example.com', [user_email])
# ...
本例中,你不得不在 LJWorld.com 和 Lawrence.com 的模板目錄中都創(chuàng)建一份 subject.txt 和 message.txt模板。 正如之前所說,該方法帶來了更大的靈活性,但也帶來了更多復(fù)雜性。
盡可能多的利用 Site 對象是減少不必要的復(fù)雜、冗余工作的好辦法。
如果 站點(diǎn) 在你的應(yīng)用中扮演很重要的角色,請考慮在你的模型中使用方便的 CurrentSiteManager 。 這是一個模型管理器(見第十章),它會自動過濾使其只包含與當(dāng)前站點(diǎn)相關(guān)聯(lián)的對象。
通過顯示地將 CurrentSiteManager 加入模型中以使用它。 例如:
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Photo(models.Model):
photo = models.FileField(upload_to='/home/photos')
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
site = models.ForeignKey(Site)
objects = models.Manager()
on_site = CurrentSiteManager()
通過該模型, Photo.objects.all() 將返回?cái)?shù)據(jù)庫中所有的 Photo 對象,而 Photo.on_site.all() 僅根據(jù)SITE_ID 設(shè)置返回與當(dāng)前站點(diǎn)相關(guān)聯(lián)的 Photo 對象。
換言之,以下兩條語句是等效的:
Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()
CurrentSiteManager 是如何知道 Photo 的哪個字段是 Site 呢?缺省情況下,它會查找一個叫做 site 的字段。如果你的模型包含了名字不是site的_外鍵_或者多對多關(guān)聯(lián),你需要把它作為參數(shù)傳給CurrentSiteManager以顯示指明。下面的模型擁有一個publish_on字段:
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Photo(models.Model):
photo = models.FileField(upload_to='/home/photos')
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
publish_on = models.ForeignKey(Site)
objects = models.Manager()
on_site = CurrentSiteManager('publish_on')
如果試圖使用 CurrentSiteManager 并傳入一個不存在的字段名, Django 將引發(fā)一個 ValueError 異常。
注意
即便是已經(jīng)使用了 CurrentSiteManager ,你也許還想在模型中擁有一個正常的(非站點(diǎn)相關(guān))的 管理器 。正如在附錄 B 中所解釋的,如果你手動定義了一個管理器,那么 Django 不會為你創(chuàng)建全自動的objects = models.Manager() 管理器。
同樣,Django 的特定部分(即 Django 超級管理站點(diǎn)和通用視圖)使用在模型中定義 的_第一個_管理器,因此如果希望管理站點(diǎn)能夠訪問所有對象(而不是僅僅站點(diǎn)特有對象),請于定義 CurrentSiteManager 之前在模型中放入 objects = models.Manager() 。
盡管并不是必須的,我們還是強(qiáng)烈建議使用多站點(diǎn)框架,因?yàn)?Django 在幾個地方利用了它。 即使只用 Django 來支持單個網(wǎng)站,你也應(yīng)該花一點(diǎn)時(shí)間用 domain 和 name 來創(chuàng)建站點(diǎn)對象,并將 SITE_ID 設(shè)置指向它的 ID 。
以下講述的是 Django 如何使用多站點(diǎn)框架:
在重定向框架中(見后面的重定向一節(jié)),每一個重定向?qū)ο蠖寂c一個特定站點(diǎn)關(guān)聯(lián)。 當(dāng) Django 搜索重定向的時(shí)候,它會考慮當(dāng)前的 SITE_ID 。
在注冊框架中,每個注釋都與特定站點(diǎn)相關(guān)。 每個注釋被顯示時(shí),其 site 被設(shè)置為當(dāng)前的 SITE_ID ,而當(dāng)通過適當(dāng)?shù)哪0鍢?biāo)簽列出注釋時(shí),只有當(dāng)前站點(diǎn)的注釋將會顯示。
在 flatpages 框架中 (參見后面的 Flatpages 一節(jié)),每個 flatpage 都與特定的站點(diǎn)相關(guān)聯(lián)。 創(chuàng)建 flatpage 時(shí),你都將指定它的 site ,而 flatpage 中間件在獲取 flatpage 以顯示它的過程中,將查看當(dāng)前的 SITE_ID 。
在 syndication 框架中(參閱第 13 章), title 和 description 的模板會自動訪問變量 {{ site }} ,它其實(shí)是代表當(dāng)前站點(diǎn)的 Site 對象。 而且,如果你不指定一個合格的domain的話,提供目錄URL的鉤子將會使用當(dāng)前“Site”對象的domain。
盡管通常情況下總是搭建運(yùn)行數(shù)據(jù)庫驅(qū)動的 Web 應(yīng)用,有時(shí)你還是需要添加一兩張一次性的靜態(tài)頁面,例如“關(guān)于”頁面,或者“隱私策略”頁面等等。 可以用像 Apache 這樣的標(biāo)準(zhǔn)Web服務(wù)器來處理這些靜態(tài)頁面,但卻會給應(yīng)用帶來一些額外的復(fù)雜性,因?yàn)槟惚仨毑傩脑趺磁渲?Apache,還要設(shè)置權(quán)限讓整個團(tuán)隊(duì)可以修改編輯這些文件,而且你還不能使用 Django 模板系統(tǒng)來統(tǒng)一這些頁面的風(fēng)格。
這個問題的解決方案是使用位于 django.contrib.flatpages 開發(fā)包中的 Django 簡單頁面(flatpages)應(yīng)用程序。該應(yīng)用讓你能夠通過 Django 管理站點(diǎn)來管理這些一次性的頁面,還可以讓你使用 Django 模板系統(tǒng)指定它們使用哪個模板。 它在后臺使用Django模型,這意味著它把頁面項(xiàng)別的數(shù)據(jù)一樣保存在數(shù)據(jù)庫中,也就是說你可以使用標(biāo)準(zhǔn)Django數(shù)據(jù)庫API來存取頁面。
簡單頁面以它們的 URL 和站點(diǎn)為鍵值。 當(dāng)創(chuàng)建簡單頁面時(shí),你指定它與哪個URL以及和哪個站點(diǎn)相關(guān)聯(lián) 。 (有關(guān)站點(diǎn)的更多信息,請查閱”多站點(diǎn)“一節(jié)。)
安裝簡單頁面應(yīng)用程序必須按照下面的步驟:
添加 'django.contrib.flatpages' 到 INSTALLED_APPS 設(shè)置。django.contrib.flatpages依賴django.contrib.sites,所以確保它們都在INSTALLED_APPS里。
將 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware' 添加到 MIDDLEWARE_CLASSES設(shè)置中。
簡單頁面應(yīng)用程序在數(shù)據(jù)庫中創(chuàng)建兩個表: django_flatpage 和 django_flatpage_sites 。 django_flatpage只是將 URL 映射到標(biāo)題和一段文本內(nèi)容。 django_flatpage_sites 是一個多對多表,用于關(guān)聯(lián)某個簡單頁面以及一個或多個站點(diǎn)。
該應(yīng)用捆綁的 FlatPage 模型在 django/contrib/flatpages/models.py 進(jìn)行定義,如下所示:
from django.db import models
from django.contrib.sites.models import Site
class FlatPage(models.Model):
url = models.CharField(max_length=100, db_index=True)
title = models.CharField(max_length=200)
content = models.TextField(blank=True)
enable_comments = models.BooleanField()
template_name = models.CharField(max_length=70, blank=True)
registration_required = models.BooleanField()
sites = models.ManyToManyField(Site)
讓我們逐項(xiàng)看看這些字段的含義:
url : 該簡單頁面所處的 URL,不包括域名,但是包含前導(dǎo)斜杠 (例如 /about/contact/ )。
title : 簡單頁面的標(biāo)題。 框架不對它作任何特殊處理。 由你通過模板來顯示它。
content : 簡單頁面的內(nèi)容 (即 HTML 頁面)。 框架不對它作任何特殊處理。 由你負(fù)責(zé)使用模板來顯示。
enable_comments : 是否允許該簡單頁面使用評論。 框架不對它作任何特殊處理。 你可在模板中檢查該值并根據(jù)需要顯示評論窗體。
template_name : 用來解析該簡單頁面的模板名稱。 這是一個可選項(xiàng);如果未指定模板或該模板不存在,系統(tǒng)會退而使用默認(rèn)模板 flatpages/default.html 。
registration_required : 是否注冊用戶才能查看此簡單頁面。 該設(shè)置項(xiàng)集成了 Djangos 驗(yàn)證/用戶框架,該框架于第十四章詳述。
你可以通過 Django 超級管理界面或者 Django 數(shù)據(jù)庫 API 來創(chuàng)建簡單頁面。 要了解更多內(nèi)容,請查閱“添加、修改和刪除簡單頁面”一節(jié)。
一旦簡單頁面創(chuàng)建完成, FlatpageFallbackMiddleware 將完成(剩下)所有的工作。 每當(dāng) Django 引發(fā) 404 錯誤,作為最后的辦法,該中間件將根據(jù)所請求的 URL 檢查簡單頁面數(shù)據(jù)庫。 確切地說,它將使用所指定的 URL以及 SITE_ID 設(shè)置對應(yīng)的站點(diǎn) ID 查找一個簡單頁面。
如果找到一個匹配項(xiàng),它將載入該簡單頁面的模板(如果沒有指定的話,將使用默認(rèn)模板flatpages/default.html )。 同時(shí),它把一個簡單的上下文變量flatpage(一個簡單頁面對象)傳遞給模板。 模板解析過程中,它實(shí)際用的是RequestContext。
如果 FlatpageFallbackMiddleware 沒有找到匹配項(xiàng),該請求繼續(xù)如常處理。
注意
該中間件僅在發(fā)生 404 (頁面未找到)錯誤時(shí)被激活,而不會在 500 (服務(wù)器錯誤)或其他錯誤響應(yīng)時(shí)被激活。 還要注意的是必須考慮 MIDDLEWARE_CLASSES 的順序問題。 通常,你可以把 FlatpageFallbackMiddleware放在列表最后,因?yàn)樗亲詈蟮霓k法。
可以用兩種方式增加、變更或刪除簡單頁面:
如果已經(jīng)激活了自動的 Django 超級管理界面,你將會在超級管理頁面的首頁看到有個 Flatpages 區(qū)域。 你可以像編輯系統(tǒng)中其它對象那樣編輯簡單頁面。
前面已經(jīng)提到,簡單頁面表現(xiàn)為 django/contrib/flatpages/models.py 中的標(biāo)準(zhǔn) Django 模型。這樣,你就可以使用Django數(shù)據(jù)庫API來存取簡單頁面對象,例如:
>>> from django.contrib.flatpages.models import FlatPage
>>> from django.contrib.sites.models import Site
>>> fp = FlatPage.objects.create(
... url='/about/',
... title='About',
... content='About this site...',
... enable_comments=False,
... template_name='',
... registration_required=False,
... )
>>> fp.sites.add(Site.objects.get(id=1))
>>> FlatPage.objects.get(url='/about/')
缺省情況下,系統(tǒng)使用模板 flatpages/default.html 來解析簡單頁面,但你也可以通過設(shè)定 FlatPage 對象的template_name 字段來更改特定簡單頁面的模板。
你必須自己創(chuàng)建 flatpages/default.html 模板。 只需要在模板目錄創(chuàng)建一個 flatpages 目錄,并把default.html 文件置于其中。
簡單頁面模板只接受有一個上下文變量—— flatpage ,也就是該簡單頁面對象。
以下是一個 flatpages/default.html 模板范例:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head>
<title>{{ flatpage.title }}</title>
</head>
<body>
{{ flatpage.content|safe }}
</body>
</html>
注意我們使用了safe模板過濾器來允許flatpage.content引入原始HTML而不必轉(zhuǎn)義。
通過將重定向存儲在數(shù)據(jù)庫中并將其視為 Django 模型對象,Django 重定向框架讓你能夠輕松地管理它們。 比如說,你可以通過重定向框架告訴Django,把任何指向 /music/ 的請求重定向到 /sections/arts/music/ 。當(dāng)你需要在站點(diǎn)中移動一些東西時(shí),這項(xiàng)功能就派上用場了——網(wǎng)站開發(fā)者應(yīng)該窮盡一切辦法避免出現(xiàn)壞鏈接。
安裝重定向應(yīng)用程序必須遵循以下步驟:
將 'django.contrib.redirects' 添加到 INSTALLED_APPS 設(shè)置中。
將 'django.contrib.redirects.middleware.RedirectFallbackMiddleware' 添加到 MIDDLEWARE_CLASSES設(shè)置中。
manage.py syncdb 在數(shù)據(jù)庫中創(chuàng)建了一個 django_redirect 表。 這是一個簡單的查詢表,只有site_id、old_path和new_path三個字段。
你可以通過 Django 超級管理界面或者 Django 數(shù)據(jù)庫 API 來創(chuàng)建重定向。 要了解更多信息,請參閱“增加、變更和刪除重定向”一節(jié)。
一旦創(chuàng)建了重定向, RedirectFallbackMiddleware 類將完成所有的工作。 每當(dāng) Django 應(yīng)用引發(fā)一個 404 錯誤,作為終極手段,該中間件將為所請求的 URL 在重定向數(shù)據(jù)庫中進(jìn)行查找。 確切地說,它將使用給定的old_path 以及 SITE_ID 設(shè)置對應(yīng)的站點(diǎn) ID 查找重定向設(shè)置。 (查閱前面的“多站點(diǎn)”一節(jié)可了解關(guān)于SITE_ID 和多站點(diǎn)框架的更多細(xì)節(jié)) 然后,它將執(zhí)行以下兩個步驟:
如果找到了匹配項(xiàng),并且 new_path 非空,它將重定向到 new_path 。
如果找到了匹配項(xiàng),但 new_path 為空,它將發(fā)送一個 410 (Gone) HTTP 頭信息以及一個空(無內(nèi)容)響應(yīng)。
該中間件僅為 404 錯誤激活,而不會為 500 錯誤或其他任何狀態(tài)碼的響應(yīng)所激活。
注意必須考慮 MIDDLEWARE_CLASSES 的順序。 通常,你可以將 RedirectFallbackMiddleware 放置在列表的最后,因?yàn)樗且环N終極手段。
注意
如果同時(shí)使用重定向和簡單頁面回退中間件, 必須考慮先檢查其中的哪一個(重定向或簡單頁面)。 我們建議將簡單頁面放在重定向之前(因此將簡單頁面中間件放置在重定向中間件之前),但你可能有不同想法。
你可以兩種方式增加、變更和刪除重定向:
如果已經(jīng)激活了全自動的 Django 超級管理界面,你應(yīng)該能夠在超級管理首頁看到重定向區(qū)域。 可以像編輯系統(tǒng)中其它對象一樣編輯重定向。
重定向表現(xiàn)為django/contrib/redirects/models.py 中的一個標(biāo)準(zhǔn) Django 模型。因此,你可以通過Django數(shù)據(jù)庫API來存取重定向?qū)ο螅纾?/p>
>>> from django.contrib.redirects.models import Redirect
>>> from django.contrib.sites.models import Site
>>> red = Redirect.objects.create(
... site=Site.objects.get(id=1),
... old_path='/music/',
... new_path='/sections/arts/music/',
... )
>>> Redirect.objects.get(old_path='/music/')
/sections/arts/music/>
django.contrib.csrf 開發(fā)包能夠防止遭受跨站請求偽造攻擊 (CSRF).
CSRF, 又叫會話跳轉(zhuǎn),是一種網(wǎng)站安全攻擊技術(shù)。 當(dāng)某個惡意網(wǎng)站在用戶未察覺的情況下將其從一個已經(jīng)通過身份驗(yàn)證的站點(diǎn)誘騙至一個新的 URL 時(shí),這種攻擊就發(fā)生了,因此它可以利用用戶已經(jīng)通過身份驗(yàn)證的狀態(tài)。 乍一看,要理解這種攻擊技術(shù)比較困難,因此我們在本節(jié)將使用兩個例子來說明。
假定你已經(jīng)登錄到 example.com 的網(wǎng)頁郵件賬號。該網(wǎng)站有一個指向example.com/logout的注銷按鈕。就是說,注銷其實(shí)就是訪問example.com/logout。
通過在(惡意)網(wǎng)頁上用隱藏一個指向 URL example.com/logout 的 ,惡意網(wǎng)站可以強(qiáng)迫你訪問該 URL 。因此,如果你登錄 example.com 的網(wǎng)頁郵件賬號之后,訪問了帶有指向 example.com/logout 之 的惡意站點(diǎn),訪問該惡意頁面的動作將使你登出 example.com 。 Thus, if you’re logged in to the example.comwebmail account and visit the malicious page that has an to example.com/logout , the act of visiting the malicious page will log you out from example.com .
很明顯,登出一個郵件網(wǎng)站也不是什么嚴(yán)重的安全問題。但是同樣的攻擊可能針對任何相信用戶的站點(diǎn),比如在線銀行和電子商務(wù)網(wǎng)站。這樣的話可能在用戶不知情的情況下就下訂單付款了。
在上一個例子中, example.com 應(yīng)該負(fù)部分責(zé)任,因?yàn)樗试S通過 HTTP GET 方法進(jìn)行狀態(tài)變更(即登入和登出)。 如果對服務(wù)器的狀態(tài)變更要求使用 HTTP POST 方法,情況就好得多了。 但是,即便是強(qiáng)制要求使用POST 方法進(jìn)行狀態(tài)變更操作也易受到 CSRF 攻擊。
假設(shè) example.com 對登出功能進(jìn)行了升級,登出 按鈕是通過一個指向 URL example.com/logout 的 POST動作完成,同時(shí)在 中加入了以下隱藏的字段:
<input type="hidden" name="confirm" value="true">
這就確保了用簡單的指向example.com/logout的POST 不會讓用戶登出;要讓用戶登出,用戶必須通過 POST 向example.com/logout 發(fā)送請求 并且發(fā)送一個值為’true’的POST變量。 confirm。
盡管增加了額外的安全機(jī)制,這種設(shè)計(jì)仍然會遭到 CSRF 的攻擊——惡意頁面僅需一點(diǎn)點(diǎn)改進(jìn)而已。 攻擊者可以針對你的站點(diǎn)設(shè)計(jì)整個表單,并將其藏身于一個不可見的 中,然后使用 Javascript 自動提交該表單。
那么,是否可以讓站點(diǎn)免受這種攻擊呢? 第一步,首先確保所有 GET 方法沒有副作用。 這樣以來,如果某個惡意站點(diǎn)將你的頁面包含為 ,它將不會產(chǎn)生負(fù)面效果。
該技術(shù)沒有考慮 POST 請求。 第二步就是給所有 POST 的form標(biāo)簽一個隱藏字段,它的值是保密的并根據(jù)用戶進(jìn)程的 ID 生成。 這樣,從服務(wù)器端訪問表單時(shí),可以檢查該保密的字段。不吻合時(shí)可以引發(fā)一個錯誤。
這正是 Django CSRF 防護(hù)層完成的工作,正如下面的小節(jié)所介紹的。
django.contrib.csrf 開發(fā)包只有一個模塊: middleware.py 。該模塊包含了一個 Django 中間件類——CsrfMiddleware ,該類實(shí)現(xiàn)了 CSRF 防護(hù)功能。
在設(shè)置文件中將 'django.contrib.csrf.middleware.CsrfMiddleware' 添加到 MIDDLEWARE_CLASSES 設(shè)置中可激活 CSRF 防護(hù)。 該中間件必須在 SessionMiddleware 之后 執(zhí)行,因此在列表中 CsrfMiddleware 必須出現(xiàn)在SessionMiddleware 之前 (因?yàn)轫憫?yīng)中間件是自后向前執(zhí)行的)。 同時(shí),它也必須在響應(yīng)被壓縮或解壓之前對響應(yīng)結(jié)果進(jìn)行處理,因此 CsrfMiddleware 必須在 GZipMiddleware 之后執(zhí)行。一旦將它添加到MIDDLEWARE_CLASSES設(shè)置中,你就完成了工作。 參見第十五章的“MIDDLEWARE_CLASSES順序”小節(jié)以了解更多。
如果感興趣的話,下面是 CsrfMiddleware 的工作模式。 它完成以下兩項(xiàng)工作:
它修改當(dāng)前處理的請求,向所有的 POST 表單增添一個隱藏的表單字段,使用名稱是 csrfmiddlewaretoken,值為當(dāng)前會話 ID 加上一個密鑰的散列值。 如果未設(shè)置會話 ID ,該中間件將 不會 修改響應(yīng)結(jié)果,因此對于未使用會話的請求來說性能損失是可以忽略的。
該步驟確保只有源自你的站點(diǎn)的表單才能將數(shù)據(jù) POST 回來。
該中間件特意只針對 HTTP POST 請求(以及對應(yīng)的 POST 表單)。 如我們所解釋的,永遠(yuǎn)不應(yīng)該因?yàn)槭褂昧薌ET 請求而產(chǎn)生負(fù)面效應(yīng),你必須自己來確保這一點(diǎn)。
未使用會話 cookie 的 POST 請求無法受到保護(hù),但它們也不 需要 受到保護(hù),因?yàn)閻阂饩W(wǎng)站可用任意方法來制造這種請求。
為了避免轉(zhuǎn)換非 HTML 請求,中間件在編輯響應(yīng)結(jié)果之前對它的 Content-Type 頭標(biāo)進(jìn)行檢查。 只有標(biāo)記為text/html 或 application/xml+xhtml 的頁面才會被修改。
CsrfMiddleware 的運(yùn)行需要 Django 的會話框架。 (參閱第 14 章了解更多關(guān)于會話的內(nèi)容。)如果你使用了自定義會話或者身份驗(yàn)證框架手動管理會話 cookies,該中間件將幫不上你的忙。
如果你的應(yīng)用程序以某種非常規(guī)的方法創(chuàng)建 HTML 頁面(例如:在 Javascript 的document.write語句中發(fā)送 HTML 片段),你可能會繞開了向表單添加隱藏字段的過濾器。 在此情況下,表單提交永遠(yuǎn)無法成功。 (這是因?yàn)樵陧撁姘l(fā)送到客戶端之前,CsrfMiddleware使用正則表達(dá)式來添加csrfmiddlewaretoken字段到你的HTML中,而正則表達(dá)式不能處理不規(guī)范的HTML。)如果你懷疑出現(xiàn)了這樣的問題。使用你瀏覽器的查看源代碼功能以確定csrfmiddlewaretoken是否插入到了表單中。
想了解更多關(guān)于 CSRF 的信息和例子的話,可以訪問 http://en.wikipedia.org/wiki/CSRF 。
包django.contrib.humanize包含了一些是數(shù)據(jù)更人性化的模板過濾器。 要激活這些過濾器,請把'django.contrib.humanize'加入到你的INSTALLED_APPS中。完成之后,向模版了加入{% load humanize %}就可以使用下面的過濾器了。
對于 1 到 9 的數(shù)字,該過濾器返回了數(shù)字的拼寫形式。 否則,它將返回?cái)?shù)字。 這遵循的是美聯(lián)社風(fēng)格。
舉例:
1 變成 one 。
2 變成 two 。
你可以傳入一個整數(shù)或者表示整數(shù)的字符串。
該過濾器將整數(shù)轉(zhuǎn)換為每三個數(shù)字用一個逗號分隔的字符串。
例子:
4500 變成 4,500 。
45000 變成 45,000 。
450000 變成 450,000 。
可以傳入整數(shù)或者表示整數(shù)的字符串。
該過濾器將一個很大的整數(shù)轉(zhuǎn)換成友好的文本表示方式。 它對于超過一百萬的數(shù)字最好用。
例子:
1000000 變成 1.0 million 。
1200000 變成 1.2 million 。
最大支持不超過一千的五次方(1,000,000,000,000,000)。
可以傳入整數(shù)或者表示整數(shù)的字符串。
該過濾器將整數(shù)轉(zhuǎn)換為序數(shù)詞的字符串形式。
例子:
1 變成 1st 。
2 變成 2nd 。
3 變成 3rd 。
可以傳入整數(shù)或者表示整數(shù)的字符串。
包django.contrib.markup包含了一些列Django模板過濾器,每一個都實(shí)現(xiàn)了一中通用的標(biāo)記語言。
textile : 實(shí)現(xiàn)了 Textile (http://en.wikipedia.org/wiki/Textile_%28markup_language%29)
markdown : 實(shí)現(xiàn)了 Markdown (http://en.wikipedia.org/wiki/Markdown)
每種情形下,過濾器都期望字符串形式的格式化標(biāo)記,并返回表示標(biāo)記文本的字符串。 例如:textile過濾器吧Textile格式的文本轉(zhuǎn)換為HTML。
{% load markup %}
{{ object.content|textile }}
要激活這些過濾器,僅需將 'django.contrib.markup' 添加到 INSTALLED_APPS 設(shè)置中。 一旦完成了該項(xiàng)工作,在模板中通過 {% load markup %} 就能使用這些過濾器。 要想掌握更多信息的話,可閱讀django/contrib/markup/templatetags/markup.py. 內(nèi)的源代碼。
這些繼承框架(CSRF、身份驗(yàn)證系統(tǒng)等等)通過提供 中間件 來實(shí)現(xiàn)其奇妙的功能。中間件是在請求之前/后執(zhí)行的可以修改請求和響應(yīng)的代碼,它擴(kuò)展了框架。 在下一章,我們將介紹Django的中間件并解釋怎樣寫出自己的中間件。