Python有衆多優勢,其中之一就是「開機即用」原則: 安裝Python的同時會安裝好大量的標準軟件包,這樣 你能夠當即使用而不用本身去下載。 Django也遵循這個原則,它一樣包含了本身的標準庫。 這一章就來說 這些集成的子框架。html
Django的標準庫存放在 django.contrib
包中。每一個子包都是一個獨立的附加功能包。 這些子包通常是互相獨立的,不過有些django.contrib
子包須要依賴其餘子包。web
在 django.contrib
中對函數的類型並無強制要求 。其中一些包中帶有模型(所以須要你在數據庫中安裝對應的數據表),但其它一些由獨立的中間件及模板標籤組成。正則表達式
django.contrib
開發包共有的特性是: 就算你將整個django.contrib
開發包刪除,你依然可使用 Django 的基礎功能而不會遇到任何問題。 當 Django 開發者向框架增長新功能的時,他們會嚴格根據這一原則來決定是否把新功能放入django.contrib
中。數據庫
django.contrib
由如下開發包組成:django
admin
: 自動化的站點管理工具。 請查看第6章。跨域
admindocs
:爲Django admin站點提供自動文檔。 本書沒有介紹這方面的知識;詳情請參閱Django官方文檔。瀏覽器
auth
: Django的用戶驗證框架。 參見第十四章。安全
comments
: 一個評論應用,目前,這個應用正在緊張的開發中,所以在本書出版的時候還不能給出一個完整的說明,關於這個應用的更多信息請參見Django的官方網站. 本書沒有介紹這方面的知識;詳情請參閱Django官方文檔。服務器
contenttypes
: 這是一個用於引入文檔類型的框架,每一個安裝的Django模塊做爲一種獨立的文檔類型。 這個框架主要在Django內部被其餘應用使用,它主要面向Django的高級開發者。 能夠經過閱讀源碼來了解關於這個框架的更多信息,源碼的位置在 django/contrib/contenttypes/
。markdown
csrf
: 這個模塊用來防護跨站請求僞造(CSRF)。參 見後面標題爲」CSRF 防護」的小節。
databrowse
:幫助你瀏覽數據的Django應用。 本書沒有介紹這方面的知識;詳情請參閱Django官方文檔。
flatpages
: 一個在數據庫中管理單一HTML內容的模塊。 參見後面標題爲「Flatpages」的小節。
formtools
:一些列處理表單通用模式的高級庫。 本書沒有介紹這方面的知識;詳情請參閱Django官方文檔。
gis
:爲Django提供GIS(Geographic Information Systems)支持的擴展。 舉個例子,它容許你的Django模型保存地理學數據並執行地理學查詢。 這個庫比較複雜,本書不詳細介紹。 請參看http://geodjango.org/上的文檔。
humanize
: 一系列 Django 模塊過濾器,用於增長數據的人性化。 參閱稍後的章節《人性化數據》。
localflavor
:針對不一樣國家和文化的混雜代碼段。 例如,它包含了驗證美國的郵編 以及愛爾蘭的身份證號的方法。
markup
: 一系列的 Django 模板過濾器,用於實現一些經常使用標記語言。 參閱後續章節《標記過濾器》。
redirects
: 用來管理重定向的框架。 參看後面的「重定向」小節。
sessions
: Django 的會話框架。 參見14章。
sitemaps
: 用來生成網站地圖的 XML 文件的框架。 參見13章。
sites
: 一個讓你能夠在同一個數據庫與 Django 安裝中管理多個網站的框架。 參見下一節:
syndication
: 一個用 RSS 和 Atom 來生成聚合訂閱源的的框架。 參見13章。
webdesign
:對設計者很是有用的Django擴展。 到編寫此文時,它只包含一個模板標籤{% lorem %}
。詳情參閱Django文檔。
本章接下來將詳細描述前面沒有介紹過的 django.contrib
開發包內容。
Django 的多站點系統是一種通用框架,它讓你能夠在同一個數據庫和同一個Django項目下操做多個網站。 這是一個抽象概念,理解起來可能有點困難,所以咱們從幾個讓它能派上用場的實際情景入手。
正如咱們在第一章裏所講,Django 構建的網站 LJWorld.com 和 Lawrance.com 是用由同一個新聞組織控制的: 肯薩斯州勞倫斯市的 勞倫斯日報世界 報紙。 LJWorld.com 主要作新聞,而 Lawrence.com 關注本地娛樂。 然而有時,編輯可能須要把一篇文章發佈到 兩個 網站上。
解決此問題的死腦筋方法多是使用每一個站點分別使用不一樣的數據庫,而後要求站點維護者把同一篇文章發佈兩次: 一次爲 LJWorld.com,另外一次爲Lawrence.com。 但這對站點管理員來講是低效率的,並且爲同一篇文章在數據庫裏保留多個副本也顯得多餘。
更好的解決方案? 兩個網站用的是同一個文章數據庫,並將每一篇文章與一個或多個站點用多對多關係關聯起來。 Django 站點框架提供數據庫表來記載哪些文章能夠被關聯。 它是一個把數據與一個或多個站點關聯起來的鉤子。
LJWorld.com 和 Lawrence.com 都有郵件提醒功能,使讀者註冊後能夠在新聞發生後當即收到通知。 這是一種完美的的機制: 某讀者提交了註冊表單,而後立刻就受到一封內容是「感謝您的註冊」的郵件。
把這個註冊過程的代碼實現兩遍顯然是低效、多餘的,所以兩個站點在後臺使用相同的代碼。 但感謝註冊的通知在兩個網站中須要不一樣。 經過使用 Site
對象,咱們經過使用當前站點的 name
(例如 'LJWorld.com'
)和 domain
(例如 'www.ljworld.com'
)能夠把感謝通知抽提出來。
Django 的多站點框架爲你提供了一個位置來存儲 Django 項目中每一個站點的 name
和 domain
,這意味着你能夠用一樣的方法來重用這些值。
多站點框架與其說是一個框架,不如說是一系列約定。 全部的一切都基於兩個簡單的概念:
位於 django.contrib.sites
的 Site
模型有 domain
和 name
兩個字段。
SITE_ID
設置指定了與特定配置文件相關聯的 Site
對象之數據庫 ID。
如何運用這兩個概念由你決定,但 Django 是經過幾個簡單的約定自動使用的。
安裝多站點應用要執行如下幾個步驟:
將 'django.contrib.sites'
加入到 INSTALLED_APPS
中。
運行 manage.py syncdb 命令將 django_site 表安裝到數據庫中。 這樣也會創建默認的站點對象,域名爲 example.com。
把example.com
改爲你本身的域名,而後經過Django admin站點或Python API來添加其餘Site
對象。 爲該 Django 項目支撐的每一個站(或域)建立一個 Site
對象。
在每一個設置文件中定義一個 SITE_ID
變量。 該變量值應當是該設置文件所支撐的站點Site
對象的數據庫 ID。
下面幾節講述的是用多站點框架可以完成的幾項工做。
正如在情景一中所解釋的,要在多個站點間重用數據,僅需在模型中爲 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)
這是在數據庫中爲多個站點進行文章關聯操做的基礎步驟。 在適當的位置使用該技術,你能夠在多個站點中重複使用同一段 Django 視圖代碼。 繼續 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) # ...
該視圖方法是可重用的,由於它根據 SITE_ID
設置的值動態檢查 articles 站點。
例如, LJWorld.coms 設置文件中有有個 SITE_ID
設置爲 1
,而 Lawrence.coms 設置文件中有個 SITE_ID
設置爲 2
。若是該視圖在 LJWorld.coms 處於激活狀態時被調用,那麼它將把查找範圍侷限於站點列表包括 LJWorld.com 在內的文章。
一樣,你也可使用 外鍵
在多對一關係中將一個模型關聯到 Site
模型。
舉例來講,若是某篇文章僅僅可以出如今一個站點上,你可使用下面這樣的模型:
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)
這與前一節中介紹的同樣有益。
在底層,經過在 Django 視圖中使用多站點框架,你可讓視圖根據調用站點不一樣而完成不一樣的工做,例如:
from django.conf import settings def my_view(request): if settings.SITE_ID == 3: # Do something. else: # Do something else.
固然,像那樣對站點 ID 進行硬編碼是比較難看的。 略爲簡潔的完成方式是查看當前的站點域:
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.
注意
在這個最後的例子裏,你不用導入 django.conf.settings
。
正如情景二中所解釋的那樣,依據DRY原則(不作重複工做),你只需在一個位置儲存站名和域名,而後引用當前 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]) # ...
繼續咱們正在討論的 LJWorld.com 和 Lawrence.com 例子,在Lawrence.com 該郵件的標題行是「感謝註冊 Lawrence.com 提醒信件」。 在 LJWorld.com ,該郵件標題行是「感謝註冊 LJWorld.com 提醒信件」。 這種站點關聯行爲方式對郵件信息主體也一樣適用。
完成這項工做的一種更加靈活(但更重量級)的方法是使用 Django 的模板系統。 假定 Lawrence.com 和 LJWorld.com 各自擁有不一樣的模板目錄( TEMPLATE_DIRS
),你可將工做輕鬆地轉交給模板系統,以下所示:
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 的模板目錄中都建立一份 subject.txt
和 message.txt
模板。 正如以前所說,該方法帶來了更大的靈活性,但也帶來了更多複雜性。
儘量多的利用 Site
對象是減小沒必要要的複雜、冗餘工做的好辦法。
若是 站點
在你的應用中扮演很重要的角色,請考慮在你的模型中使用方便的 CurrentSiteManager
。 這是一個模型管理器(見第十章),它會自動過濾使其只包含與當前站點相關聯的對象。
經過顯示地將 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()
將返回數據庫中全部的 Photo
對象,而 Photo.on_site.all()
僅根據 SITE_ID
設置返回與當前站點相關聯的 Photo
對象。
換言之,如下兩條語句是等效的:
Photo.objects.filter(site=settings.SITE_ID) Photo.on_site.all()
CurrentSiteManager
是如何知道 Photo
的哪一個字段是 Site
呢?缺省狀況下,它會查找一個叫作 site
的字段。若是你的模型包含了名字不是
site
的外鍵或者多對多
關聯,你須要把它做爲參數傳給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 將引起一個 ValueError
異常。
注意
即使是已經使用了 CurrentSiteManager
,你也許還想在模型中擁有一個正常的(非站點相關)的 管理器
。正如在附錄 B 中所解釋的,若是你手動定義了一個管理器,那麼 Django 不會爲你建立全自動的 objects = models.Manager()
管理器。
一樣,Django 的特定部分(即 Django 超級管理站點和通用視圖)使用在模型中定義 的第一個管理器,所以若是但願管理站點可以訪問全部對象(而不是僅僅站點特有對象),請於定義 CurrentSiteManager
以前在模型中放入 objects = models.Manager()
。
儘管並非必須的,咱們仍是強烈建議使用多站點框架,由於 Django 在幾個地方利用了它。 即便只用 Django 來支持單個網站,你也應該花一點時間用 domain
和 name
來建立站點對象,並將 SITE_ID
設置指向它的 ID 。
如下講述的是 Django 如何使用多站點框架:
在重定向框架中(見後面的重定向一節),每個重定向對象都與一個特定站點關聯。 當 Django 搜索重定向的時候,它會考慮當前的 SITE_ID
。
在註冊框架中,每一個註釋都與特定站點相關。 每一個註釋被顯示時,其 site
被設置爲當前的 SITE_ID
,而當經過適當的模板標籤列出註釋時,只有當前站點的註釋將會顯示。
在 flatpages 框架中 (參見後面的 Flatpages 一節),每一個 flatpage 都與特定的站點相關聯。 建立 flatpage 時,你都將指定它的 site
,而 flatpage 中間件在獲取 flatpage 以顯示它的過程當中,將查看當前的 SITE_ID
。
在 syndication 框架中(參閱第 13 章), title
和 description
的模板會自動訪問變量 {{ site }}
,它實際上是表明當前站點的 Site
對象。 並且,若是你不指定一個合格的domain的話,提供目錄URL的鉤子將會使用當前「Site」對象的domain。
在權限框架中(參見十四章),視圖django.contrib.auth.views.login
把當前Site
名字和對象分別以{{ site_name }}
和{{ site }}
的形式傳給了模板。
儘管一般狀況下老是搭建運行數據庫驅動的 Web 應用,有時你仍是須要添加一兩張一次性的靜態頁面,例如「關於」頁面,或者「隱私策略」頁面等等。 能夠用像 Apache 這樣的標準Web服務器來處理這些靜態頁面,但卻會給應用帶來一些額外的複雜性,由於你必須操心怎麼配置 Apache,還要設置權限讓整個團隊能夠修改編輯這些文件,並且你還不能使用 Django 模板系統來統一這些頁面的風格。
這個問題的解決方案是使用位於 django.contrib.flatpages
開發包中的 Django 簡單頁面(flatpages)應用程序。該應用讓你可以經過 Django 管理站點來管理這些一次性的頁面,還可讓你使用 Django 模板系統指定它們使用哪一個模板。 它在後臺使用Django模型,這意味着它把頁面項別的數據同樣保存在數據庫中,也就是說你可使用標準Django數據庫API來存取頁面。
簡單頁面以它們的 URL 和站點爲鍵值。 當建立簡單頁面時,你指定它與哪一個URL以及和哪一個站點相關聯 。 (有關站點的更多信息,請查閱」多站點「一節。)
安裝簡單頁面應用程序必須按照下面的步驟:
添加 'django.contrib.flatpages'
到 INSTALLED_APPS
設置。django.contrib.flatpages
依賴django.contrib.sites
,因此確保它們都在INSTALLED_APPS
裏。
將 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'
添加到 MIDDLEWARE_CLASSES
設置中。
運行 manage.py syncdb
命令在數據庫中建立必需的兩個表。
簡單頁面應用程序在數據庫中建立兩個表: django_flatpage
和 django_flatpage_sites
。 django_flatpage
只是將 URL 映射到標題和一段文本內容。 django_flatpage_sites
是一個多對多表,用於關聯某個簡單頁面以及一個或多個站點。
該應用捆綁的 FlatPage
模型在 django/contrib/flatpages/models.py
進行定義,以下所示:
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)
讓咱們逐項看看這些字段的含義:
url
: 該簡單頁面所處的 URL,不包括域名,可是包含前導斜槓 (例如 /about/contact/
)。
title
: 簡單頁面的標題。 框架不對它做任何特殊處理。 由你經過模板來顯示它。
content
: 簡單頁面的內容 (即 HTML 頁面)。 框架不對它做任何特殊處理。 由你負責使用模板來顯示。
enable_comments
: 是否容許該簡單頁面使用評論。 框架不對它做任何特殊處理。 你可在模板中檢查該值並根據須要顯示評論窗體。
template_name
: 用來解析該簡單頁面的模板名稱。 這是一個可選項;若是未指定模板或該模板不存在,系統會退而使用默認模板 flatpages/default.html
。
registration_required
: 是否註冊用戶才能查看此簡單頁面。 該設置項集成了 Djangos 驗證/用戶框架,該框架於第十四章詳述。
sites
: 該簡單頁面放置的站點。 該項設置集成了 Django 多站點框架,該框架在本章的「多站點」一節中有所闡述。
你能夠經過 Django 超級管理界面或者 Django 數據庫 API 來建立簡單頁面。 要了解更多內容,請查閱「添加、修改和刪除簡單頁面」一節。
一旦簡單頁面建立完成, FlatpageFallbackMiddleware
將完成(剩下)全部的工做。 每當 Django 引起 404 錯誤,做爲最後的辦法,該中間件將根據所請求的 URL 檢查簡單頁面數據庫。 確切地說,它將使用所指定的 URL以及 SITE_ID
設置對應的站點 ID 查找一個簡單頁面。
若是找到一個匹配項,它將載入該簡單頁面的模板(若是沒有指定的話,將使用默認模板 flatpages/default.html
)。 同時,它把一個簡單的上下文變量flatpage
(一個簡單頁面對象)傳遞給模板。 模板解析過程當中,它實際用的是RequestContext
。
若是 FlatpageFallbackMiddleware
沒有找到匹配項,該請求繼續如常處理。
注意
該中間件僅在發生 404 (頁面未找到)錯誤時被激活,而不會在 500 (服務器錯誤)或其餘錯誤響應時被激活。 還要注意的是必須考慮 MIDDLEWARE_CLASSES
的順序問題。 一般,你能夠把 FlatpageFallbackMiddleware
放在列表最後,由於它是最後的辦法。
能夠用兩種方式增長、變動或刪除簡單頁面:
若是已經激活了自動的 Django 超級管理界面,你將會在超級管理頁面的首頁看到有個 Flatpages 區域。 你能夠像編輯系統中其它對象那樣編輯簡單頁面。
前面已經提到,簡單頁面表現爲 django/contrib/flatpages/models.py
中的標準 Django 模型。這樣,你就可使用Django數據庫API來存取簡單頁面對象,例如:
>>> from django.contrib.flatpages.models import FlatPage >>> from django.contrib.sites.models import Site >>> fp = FlatPage.objects.create( ... url='/about/', ... title='About', ... content='<p>About this site...</p>', ... enable_comments=False, ... template_name='', ... registration_required=False, ... ) >>> fp.sites.add(Site.objects.get(id=1)) >>> FlatPage.objects.get(url='/about/') <FlatPage: /about/ -- About>
缺省狀況下,系統使用模板 flatpages/default.html
來解析簡單頁面,但你也能夠經過設定 FlatPage
對象的 template_name
字段來更改特定簡單頁面的模板。
你必須本身建立 flatpages/default.html
模板。 只須要在模板目錄建立一個 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而沒必要轉義。
經過將重定向存儲在數據庫中並將其視爲 Django 模型對象,Django 重定向框架讓你可以輕鬆地管理它們。 好比說,你能夠經過重定向框架告訴Django,把任何指向 /music/
的請求重定向到 /sections/arts/music/
。當你須要在站點中移動一些東西時,這項功能就派上用場了——網站開發者應該窮盡一切辦法避免出現壞連接。
安裝重定向應用程序必須遵循如下步驟:
將 'django.contrib.redirects'
添加到 INSTALLED_APPS
設置中。
將 'django.contrib.redirects.middleware.RedirectFallbackMiddleware'
添加到 MIDDLEWARE_CLASSES
設置中。
運行 manage.py syncdb
命令將所需的表添加到數據庫中。
manage.py syncdb
在數據庫中建立了一個 django_redirect
表。 這是一個簡單的查詢表,只有site_id
、old_path和new_path三個字段。
你能夠經過 Django 超級管理界面或者 Django 數據庫 API 來建立重定向。 要了解更多信息,請參閱「增長、變動和刪除重定向」一節。
一旦建立了重定向, RedirectFallbackMiddleware
類將完成全部的工做。 每當 Django 應用引起一個 404 錯誤,做爲終極手段,該中間件將爲所請求的 URL 在重定向數據庫中進行查找。 確切地說,它將使用給定的 old_path
以及 SITE_ID
設置對應的站點 ID 查找重定向設置。 (查閱前面的「多站點」一節可瞭解關於 SITE_ID
和多站點框架的更多細節) 而後,它將執行如下兩個步驟:
若是找到了匹配項,而且 new_path
非空,它將重定向到 new_path
。
若是找到了匹配項,但 new_path
爲空,它將發送一個 410 (Gone) HTTP 頭信息以及一個空(無內容)響應。
若是未找到匹配項,該請求將如常處理。
該中間件僅爲 404 錯誤激活,而不會爲 500 錯誤或其餘任何狀態碼的響應所激活。
注意必須考慮 MIDDLEWARE_CLASSES
的順序。 一般,你能夠將 RedirectFallbackMiddleware
放置在列表的最後,由於它是一種終極手段。
注意
若是同時使用重定向和簡單頁面回退中間件, 必須考慮先檢查其中的哪個(重定向或簡單頁面)。 咱們建議將簡單頁面放在重定向以前(所以將簡單頁面中間件放置在重定向中間件以前),但你可能有不一樣想法。
你能夠兩種方式增長、變動和刪除重定向:
若是已經激活了全自動的 Django 超級管理界面,你應該可以在超級管理首頁看到重定向區域。 能夠像編輯系統中其它對象同樣編輯重定向。
重定向表現爲django/contrib/redirects/models.py
中的一個標準 Django 模型。所以,你能夠經過Django數據庫API來存取重定向對象,例如:
>>> 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/') <Redirect: /music/ ---> /sections/arts/music/>
django.contrib.csrf
開發包可以防止遭受跨站請求僞造攻擊 (CSRF).
CSRF, 又叫會話跳轉,是一種網站安全攻擊技術。 當某個惡意網站在用戶未察覺的狀況下將其從一個已經經過身份驗證的站點誘騙至一個新的 URL 時,這種攻擊就發生了,所以它能夠利用用戶已經經過身份驗證的狀態。 乍一看,要理解這種攻擊技術比較困難,所以咱們在本節將使用兩個例子來講明。
假定你已經登陸到 example.com
的網頁郵件帳號。該網站有一個指向example.com/logout
的註銷按鈕。就是說,註銷其實就是訪問example.com/logout
。
經過在(惡意)網頁上用隱藏一個指向 URL example.com/logout
的 <iframe>
,惡意網站能夠強迫你訪問該 URL 。所以,若是你登陸 example.com
的網頁郵件帳號以後,訪問了帶有指向 example.com/logout
之 <iframe>
的惡意站點,訪問該惡意頁面的動做將使你登出 example.com
。 Thus, if you’re logged in to the example.com
webmail account and visit the malicious page that has an <iframe>
to example.com/logout
, the act of visiting the malicious page will log you out from example.com
.
很明顯,登出一個郵件網站也不是什麼嚴重的安全問題。可是一樣的攻擊可能針對任何相信用戶的站點,好比在線銀行和電子商務網站。這樣的話可能在用戶不知情的狀況下就下訂單付款了。
在上一個例子中, example.com
應該負部分責任,由於它容許經過 HTTP GET
方法進行狀態變動(即登入和登出)。 若是對服務器的狀態變動要求使用 HTTP POST
方法,狀況就好得多了。 可是,即使是強制要求使用 POST
方法進行狀態變動操做也易受到 CSRF 攻擊。
假設 example.com
對登出功能進行了升級,登出 <form>
按鈕是經過一個指向 URL example.com/logout
的 POST
動做完成,同時在 <form>
中加入瞭如下隱藏的字段:
<input type="hidden" name="confirm" value="true">
這就確保了用簡單的指向example.com/logout
的POST
不會讓用戶登出;要讓用戶登出,用戶必須經過 POST
向 example.com/logout
發送請求 而且發送一個值爲’true’的POST變量。 confirm
。
儘管增長了額外的安全機制,這種設計仍然會遭到 CSRF 的攻擊——惡意頁面僅需一點點改進而已。 攻擊者能夠針對你的站點設計整個表單,並將其藏身於一個不可見的 <iframe>
中,而後使用 Javascript 自動提交該表單。
那麼,是否可讓站點免受這種攻擊呢? 第一步,首先確保全部 GET
方法沒有反作用。 這樣以來,若是某個惡意站點將你的頁面包含爲 <iframe>
,它將不會產生負面效果。
該技術沒有考慮 POST
請求。 第二步就是給全部 POST
的form標籤一個隱藏字段,它的值是保密的並根據用戶進程的 ID 生成。 這樣,從服務器端訪問表單時,能夠檢查該保密的字段。不吻合時能夠引起一個錯誤。
這正是 Django CSRF 防禦層完成的工做,正以下面的小節所介紹的。
django.contrib.csrf
開發包只有一個模塊: middleware.py
。該模塊包含了一個 Django 中間件類—— CsrfMiddleware
,該類實現了 CSRF 防禦功能。
在設置文件中將 'django.contrib.csrf.middleware.CsrfMiddleware'
添加到 MIDDLEWARE_CLASSES
設置中可激活 CSRF 防禦。 該中間件必須在 SessionMiddleware
以後 執行,所以在列表中 CsrfMiddleware
必須出如今 SessionMiddleware
以前 (由於響應中間件是自後向前執行的)。 同時,它也必須在響應被壓縮或解壓以前對響應結果進行處理,所以 CsrfMiddleware
必須在 GZipMiddleware
以後執行。一旦將它添加到MIDDLEWARE_CLASSES
設置中,你就完成了工做。 參見第十五章的「MIDDLEWARE_CLASSES順序」小節以瞭解更多。
若是感興趣的話,下面是 CsrfMiddleware
的工做模式。 它完成如下兩項工做:
它修改當前處理的請求,向全部的 POST
表單增添一個隱藏的表單字段,使用名稱是 csrfmiddlewaretoken
,值爲當前會話 ID 加上一個密鑰的散列值。 若是未設置會話 ID ,該中間件將 不會 修改響應結果,所以對於未使用會話的請求來講性能損失是能夠忽略的。
對於全部含會話 cookie 集合的傳入 POST
請求,它將檢查是否存在 csrfmiddlewaretoken
及其是否正確。 若是不是的話,用戶將會收到一個 403 HTTP
錯誤。 403 錯誤頁面的內容是檢測到了跨域請求假裝。 終止請求。
該步驟確保只有源自你的站點的表單才能將數據 POST 回來。
該中間件特地只針對 HTTP POST
請求(以及對應的 POST 表單)。 如咱們所解釋的,永遠不該該由於使用了 GET
請求而產生負面效應,你必須本身來確保這一點。
未使用會話 cookie 的 POST
請求沒法受到保護,但它們也不 須要 受到保護,由於惡意網站可用任意方法來製造這種請求。
爲了不轉換非 HTML 請求,中間件在編輯響應結果以前對它的 Content-Type
頭標進行檢查。 只有標記爲 text/html
或 application/xml+xhtml
的頁面纔會被修改。
CsrfMiddleware
的運行須要 Django 的會話框架。 (參閱第 14 章瞭解更多關於會話的內容。)若是你使用了自定義會話或者身份驗證框架手動管理會話 cookies,該中間件將幫不上你的忙。
若是你的應用程序以某種很是規的方法建立 HTML 頁面(例如:在 Javascript 的document.write
語句中發送 HTML 片斷),你可能會繞開了向表單添加隱藏字段的過濾器。 在此狀況下,表單提交永遠沒法成功。 (這是由於在頁面發送到客戶端以前,CsrfMiddleware
使用正則表達式來添加csrfmiddlewaretoken
字段到你的HTML中,而正則表達式不能處理不規範的HTML。)若是你懷疑出現了這樣的問題。使用你瀏覽器的查看源代碼功能以肯定csrfmiddlewaretoken
是否插入到了表單中。
想了解更多關於 CSRF 的信息和例子的話,能夠訪問 http://en.wikipedia.org/wiki/CSRF 。
包django.contrib.humanize
包含了一些是數據更人性化的模板過濾器。 要激活這些過濾器,請把'django.contrib.humanize'
加入到你的INSTALLED_APPS
中。完成以後,向模版了加入{% load humanize %}
就可使用下面的過濾器了。
對於 1 到 9 的數字,該過濾器返回了數字的拼寫形式。 不然,它將返回數字。 這遵循的是美聯社風格。
舉例:
1 變成 one 。
2 變成 two 。
10 變成 10 。
你能夠傳入一個整數或者表示整數的字符串。
該過濾器將整數轉換爲每三個數字用一個逗號分隔的字符串。
例子:
4500 變成 4,500 。
45000 變成 45,000 。
450000 變成 450,000 。
4500000 變成 4,500,000 。
能夠傳入整數或者表示整數的字符串。
該過濾器將一個很大的整數轉換成友好的文本表示方式。 它對於超過一百萬的數字最好用。
例子:
1000000 變成 1.0 million 。
1200000 變成 1.2 million 。
1200000000 變成 1.2 billion 。
最大支持不超過一千的五次方(1,000,000,000,000,000)。
能夠傳入整數或者表示整數的字符串。
該過濾器將整數轉換爲序數詞的字符串形式。
例子:
1 變成 1st 。
2 變成 2nd 。
3 變成 3rd 。
254變成254th。
能夠傳入整數或者表示整數的字符串。
包django.contrib.markup
包含了一些列Django模板過濾器,每個都實現了一中通用的標記語言。
textile
: 實現了 Textile (http://en.wikipedia.org/wiki/Textile_%28markup_language%29)
markdown
: 實現了 Markdown (http://en.wikipedia.org/wiki/Markdown)
restructuredtext
: 實現了 ReStructured Text (http://en.wikipedia.org/wiki/ReStructuredText)
每種情形下,過濾器都指望字符串形式的格式化標記,並返回表示標記文本的字符串。 例如:textile
過濾器吧Textile格式的文本轉換爲HTML。
{% load markup %} {{ object.content|textile }}
要激活這些過濾器,僅需將 'django.contrib.markup'
添加到 INSTALLED_APPS
設置中。 一旦完成了該項工做,在模板中經過 {% load markup %}
就能使用這些過濾器。 要想掌握更多信息的話,可閱讀 django/contrib/markup/templatetags/markup.py.
內的源代碼。