# 第十六章:集成的子框架django.contrib
# 第十六章:集成的子框架django.contrib
Python有眾多優點,其中之一就是“開機即用”原則: 安裝Python的同時會安裝好大量的標準軟件包,這樣 你可以立即使用而不用自己去下載?Django也遵循這個原則,它同樣包含了自己的標準庫?這一章就來講 這些集成的子框架
## Django標準
Django的標準庫存放?`django.contrib` 包中。每個子包都是一個獨立的附加功能包?這些子包一般是互相獨立的,不過有些`django.contrib`子包需要依賴其他子包
?`django.contrib` 中對函數的類型并沒有強制要求 。其中一些包中帶有模型(因此需要你在數據庫中安裝對應的數據表),但其它一些由獨立的中間件及模板標簽組成
`django.contrib` 開發包共有的特性是: 就算你將整個`django.contrib`開發包刪除,你依然可以使用 Django 的基礎功能而不會遇到任何問題??Django 開發者向框架增加新功能的時,他們會嚴格根據這一原則來決定是否把新功能放?tt class="docutils literal">django.contrib中
`django.contrib` 由以下開發包組成
- `admin` : 自動化的站點管理工具?請查看第6章
- `admindocs`:為Django admin站點提供自動文檔?本書沒有介紹這方面的知識;詳情請參閱Django官方文檔
- `auth` : Django的用戶驗證框架?參見第十四章
- `comments` : 一個評論應用,目前,這個應用正在緊張的開發中,因此在本書出版的時候還不能給出一個完整的說明,關于這個應用的更多信息請參見Django的官方網? 本書沒有介紹這方面的知識;詳情請參閱Django官方文檔
- `contenttypes` : 這是一個用于引入文檔類型的框架,每個安裝的Django模塊作為一種獨立的文檔類型?這個框架主要在Django內部被其他應用使用,它主要面向Django的高級開發者?可以通過閱讀源碼來了解關于這個框架的更多信息,源碼的位置?`django/contrib/contenttypes/`
- `csrf` : 這個模塊用來防御跨站請求偽?CSRF)。參 見后面標題為”CSRF 防御”的小節
- `databrowse`:幫助你瀏覽數據的Django應用?本書沒有介紹這方面的知識;詳情請參閱Django官方文檔
- `flatpages` : 一個在數據庫中管理單一HTML內容的模塊?參見后面標題為“Flatpages”的小節
- `formtools`:一些列處理表單通用模式的高級庫?本書沒有介紹這方面的知識;詳情請參閱Django官方文檔
- `gis`:為Django提供GIS(Geographic Information Systems)支持的擴展?舉個例子,它允許你的Django模型保存地理學數據并執行地理學查詢?這個庫比較復雜,本書不詳細介紹?請參?a class="reference external" href="javascript:if(confirm('<http://geodjango.org/> \\n\\n?δ Teleport Pro ??? ·????Χ \\n\\n???'))window.location='<http://geodjango.org/>'" tppabs="[http://geodjango.org/">http://geodjango.org/上的文檔](http://geodjango.org/%22>http://geodjango.org/%E4%B8%8A%E7%9A%84%E6%96%87%E6%A1%A3)
- `humanize` : 一系列 Django 模塊過濾器,用于增加數據的人性化?參閱稍后的章節《人性化數據》
- `localflavor`:針對不同國家和文化的混雜代碼段?例如,它包含了驗證美國的郵編 以及愛爾蘭的身份證號的方法
- `markup` : 一系列?Django 模板過濾器,用于實現一些常用標記語言?參閱后續章節《標記過濾器》
- `redirects` : 用來管理重定向的框架?參看后面的“重定向”小節
- `sessions` : Django 的會話框架?參見14章
- `sitemaps` : 用來生成網站地圖?XML 文件的框架?參見13章
- `sites` : 一個讓你可以在同一個數據庫?Django 安裝中管理多個網站的框架?參見下一節:
- `syndication` : 一個用 RSS ?Atom 來生成聚合訂閱源的的框架?參見13章
- `webdesign`:對設計者非常有用的Django擴展?到編寫此文時,它只包含一個模板標?tt class="docutils literal">{% lorem %}。詳情參閱Django文檔
本章接下來將詳細描述前面沒有介紹過的 `django.contrib` 開發包內容
## 多個站點
Django 的多站點系統是一種通用框架,它讓你可以在同一個數據庫和同一個Django項目下操作多個網站?這是一個抽象概念,理解起來可能有點困難,因此我們從幾個讓它能派上用場的實際情景入手
### 情景1:多站點間復用數
正如我們在第一章里所講,Django 構建的網?LJWorld.com ?Lawrance.com 是用由同一個新聞組織控制的?肯薩斯州勞倫斯市?*勞倫斯日報世 報紙?LJWorld.com 主要做新聞,?Lawrence.com 關注本地娛樂?然而有時,編輯可能需要把一篇文章發布到* 兩個 *網站上*
*解決此問題的死腦筋方法可能是使用每個站點分別使用不同的數據庫,然后要求站點維護者把同一篇文章發布兩次: 一次為 LJWorld.com,另一次為Lawrence.com?但這對站點管理員來說是低效率的,而且為同一篇文章在數據庫里保留多個副本也顯得多余*
*更好的解決方案? 兩個網站用的是同一個文章數據庫,并將每一篇文章與一個或多個站點用多對多關系關聯起來?Django 站點框架提供數據庫表來記載哪些文章可以被關聯?它是一個把數據與一個或多個站點關聯起來的鉤子*
### *情景2:把網站的名?域名保存在一個地*
*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 是通過幾個簡單的約定自動使用的*
*安裝多站點應用要執行以下幾個步驟*
1. *?`'django.contrib.sites'` 加入?`INSTALLED_APPS` 中*
2. *運行 <cite>manage.py syncdb</cite> 命令?<cite>django\_site</cite> 表安裝到數據庫中?這樣也會建立默認的站點對象,域名?example.com*
3. *?tt class="docutils literal">example.com改成你自己的域名,然后通過Django admin站點或Python API來添加其?tt class="docutils literal">Site對象?為該 Django 項目支撐的每個站(或域)創建一?`Site` 對象*
4. *在每個設置文件中定義一?`SITE_ID` 變量?該變量值應當是該設置文件所支撐的站?tt class="docutils literal">Site 對象的數據庫 ID*
### *多站點框架的功能*
*下面幾節講述的是用多站點框架能夠完成的幾項工作*
#### *多個站點的數據重*
*正如在情景一中所解釋的,要在多個站點間重用數?僅需在模型中?`Site` 添加一?`多對多字` 即可,例如:*
```
<pre class="calibre9">```
_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` 視圖*
```
<pre class="calibre9">```
_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` 模型*
*舉例來說,如果某篇文章僅僅能夠出現在一個站點上,你可以使用下面這樣的模型:*
```
<pre class="calibre9">```
_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 視圖中使用多站點框架,你可以讓視圖根據調用站點不同而完成不同的工作,例如:*
```
<pre class="calibre9">```
_from django.conf import settings
def my_view(request):
if settings.SITE_ID == 3:
# Do something.
else:
# Do something else._
```
```
*當然,像那樣對站?ID 進行硬編碼是比較難看的?略為簡潔的完成方式是查看當前的站點域:*
```
<pre class="calibre9">```
_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()` 方法?下面的例子與前一個是等效的:*
```
<pre class="calibre9">```
_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` 。例如: 例如*
```
<pre class="calibre9">```
_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` ),你可將工作輕松地轉交給模板系統,如下所示:*
```
<pre class="calibre9">```
_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` 加入模型中以使用它?例如*
```
<pre class="calibre9">```
_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` 對象*
*換言之,以下兩條語句是等效的*
```
<pre class="calibre9">```
_Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()_
```
```
*`CurrentSiteManager` 是如何知?`Photo` 的哪個字段是 `Site` 呢?缺省情況下,它會查找一個叫?`site` 的字段。如果你的模型包含了名字`不是``site`?em>外鍵*或?tt class="docutils literal">多對關聯,你需要把它作為參數傳?tt class="docutils literal">CurrentSiteManager以顯示指明。下面的模型擁有一?tt class="docutils literal">publish\_on字段
```
<pre class="calibre9">```
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 超級管理站點和通用視圖)使用在模型中定??em>第一管理器,因此如果希望管理站點能夠訪問所有對象(而不是僅僅站點特有對象),請于定?`CurrentSiteManager` 之前在模型中放入 `objects = models.Manager()`
### Django如何使用多站點框
盡管并不是必須的,我們還是強烈建議使用多站點框架,因?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`把當?tt class="docutils literal">Site名字和對象分別以`{{ site_name }}`?tt class="docutils literal">{{ site }}的形式傳給了模板
## Flatpages(簡單頁?
盡管通常情況下總是搭建運行數據庫驅動的 Web 應用,有時你還是需要添加一兩張一次性的靜態頁面,例如“關于”頁面,或者“隱私策略”頁面等等?可以用像 Apache 這樣的標準Web服務器來處理這些靜態頁面,但卻會給應用帶來一些額外的復雜性,因為你必須操心怎么配置 Apache,還要設置權限讓整個團隊可以修改編輯這些文件,而且你還不能使用 Django 模板系統來統一這些頁面的風格
這個問題的解決方案是使用位于 `django.contrib.flatpages` 開發包中的 Django 簡單頁面(flatpages)應用程序。該應用讓你能夠通過 Django 管理站點來管理這些一次性的頁面,還可以讓你使用 Django 模板系統指定它們使用哪個模板?它在后臺使用Django模型,這意味著它把頁面項別的數據一樣保存在數據庫中,也就是說你可以使用標準Django數據庫API來存取頁面
簡單頁面以它們?URL 和站點為鍵值?當創建簡單頁面時,你指定它與哪個URL以及和哪個站點相關聯 ?(有關站點的更多信息,請查閱”多站點“一節。)
### 使用簡單頁
安裝簡單頁面應用程序必須按照下面的步驟
1. 添加 `'django.contrib.flatpages'` ?`INSTALLED_APPS` 設置?tt class="docutils literal">django.contrib.flatpages依賴`django.contrib.sites`,所以確保它們都?tt class="docutils literal">INSTALLED\_APPS里
2. ?`'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'` 添加?`MIDDLEWARE_CLASSES` 設置中
3. 運行 `manage.py syncdb` 命令在數據庫中創建必需的兩個表
簡單頁面應用程序在數據庫中創建兩個表: `django_flatpage` ?`django_flatpage_sites` ?`django_flatpage` 只是?URL 映射到標題和一段文本內容?`django_flatpage_sites` 是一個多對多表,用于關聯某個簡單頁面以及一個或多個站點
該應用捆綁的 `FlatPage` 模型?`django/contrib/flatpages/models.py` 進行定義,如下所示:
```
<pre class="calibre9">```
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` )?同時,它把一個簡單的上下文變?tt class="docutils literal">flatpage(一個簡單頁面對象)傳遞給模板?模板解析過程中,它實際用的是`RequestContext`
如果 `FlatpageFallbackMiddleware` 沒有找到匹配項,該請求繼續如常處理
注意
該中間件僅在發生 404 (頁面未找到)錯誤時被激活,而不會在 500 (服務器錯誤)或其他錯誤響應時被激活?還要注意的是必須考慮 `MIDDLEWARE_CLASSES` 的順序問題?通常,你可以?`FlatpageFallbackMiddleware` 放在列表最后,因為它是最后的辦法
### 添加、修改和刪除簡單頁
可以用兩種方式增加、變更或刪除簡單頁面:
#### 通過超級管理界面
如果已經激活了自動?Django 超級管理界面,你將會在超級管理頁面的首頁看到有個 Flatpages 區域?你可以像編輯系統中其它對象那樣編輯簡單頁面
#### 通過 Python API
前面已經提到,簡單頁面表現為 `django/contrib/flatpages/models.py` 中的標準 Django 模型。這樣,你就可以使用Django數據庫API來存取簡單頁面對象,例如
```
<pre class="calibre9">```
>>> 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` 模板范例:
```
<pre class="calibre9">```
<html>
<head>
<title>{{ flatpage.title }}</title>
</head>
<body>
{{ flatpage.content|safe }}
</body>
</html>
```
```
注意我們使用?tt class="docutils literal">safe模板過濾器來允許`flatpage.content`引入原始HTML而不必轉義
## 重定
通過將重定向存儲在數據庫中并將其視為 Django 模型對象,Django 重定向框架讓你能夠輕松地管理它們?比如說,你可以通過重定向框架告訴Django,把任何指向 `/music/` 的請求重定向?`/sections/arts/music/` 。當你需要在站點中移動一些東西時,這項功能就派上用場了——網站開發者應該窮盡一切辦法避免出現壞鏈接
### 使用重定向框
安裝重定向應用程序必須遵循以下步驟:
1. ?`'django.contrib.redirects'` 添加?`INSTALLED_APPS` 設置中
2. ?`'django.contrib.redirects.middleware.RedirectFallbackMiddleware'` 添加?`MIDDLEWARE_CLASSES` 設置中
3. 運行 `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 超級管理界面,你應該能夠在超級管理首頁看到重定向區域?可以像編輯系統中其它對象一樣編輯重定向
#### 同過Python API
重定向表現為`django/contrib/redirects/models.py` 中的一個標?Django 模型。因此,你可以通過Django數據庫API來存取重定向對象,例如:
```
<pre class="calibre9">```
>>> 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/>
```
```
## CSRF 防護
`django.contrib.csrf` 開發包能夠防止遭受跨站請求偽造攻?(CSRF).
CSRF, 又叫會話跳轉,是一種網站安全攻擊技術?當某個惡意網站在用戶未察覺的情況下將其從一個已經通過身份驗證的站點誘騙至一個新?URL 時,這種攻擊就發生了,因此它可以利用用戶已經通過身份驗證的狀態?乍一看,要理解這種攻擊技術比較困難,因此我們在本節將使用兩個例子來說明
### 一個簡單的 CSRF 例子
假定你已經登錄到 `example.com` 的網頁郵件賬號。該網站有一個指?tt class="docutils literal">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` .
很明顯,登出一個郵件網站也不是什么嚴重的安全問題。但是同樣的攻擊可能針對任何相信用戶的站點,比如在線銀行和電子商務網站。這樣的話可能在用戶不知情的情況下就下訂單付款了
### 稍微復雜一點的CSRF例子
在上一個例子中?`example.com` 應該負部分責任,因為它允許通過 HTTP `GET` 方法進行狀態變更(即登入和登出)?如果對服務器的狀態變更要求使?HTTP `POST` 方法,情況就好得多了?但是,即便是強制要求使用 `POST` 方法進行狀態變更操作也易受?CSRF 攻擊
假設 `example.com` 對登出功能進行了升級,登?`<form>` 按鈕是通過一個指?URL `example.com/logout` ?`POST` 動作完成,同時在 `<form>` 中加入了以下隱藏的字段:
```
<pre class="calibre9">```
<input type="hidden" name="confirm" value="true">
```
```
這就確保了用簡單的指向`example.com/logout`?tt class="docutils literal">POST 不會讓用戶登出;要讓用戶登出,用戶必須通過 `POST` ?`example.com/logout` 發送請? 并且發送一個值為’true’的POST變量?`confirm`
盡管增加了額外的安全機制,這種設計仍然會遭到 CSRF 的攻擊——惡意頁面僅需一點點改進而已?攻擊者可以針對你的站點設計整個表單,并將其藏身于一個不可見?`<iframe>` 中,然后使用 Javascript 自動提交該表單
### 防止 CSRF
那么,是否可以讓站點免受這種攻擊呢? 第一步,首先確保所?`GET` 方法沒有副作用?這樣以來,如果某個惡意站點將你的頁面包含?`<iframe>` ,它將不會產生負面效果
該技術沒有考慮 `POST` 請求?第二步就是給所?`POST` 的form標簽一個隱藏字段,它的值是保密的并根據用戶進程?ID 生成?這樣,從服務器端訪問表單時,可以檢查該保密的字段。不吻合時可以引發一個錯誤
這正?Django CSRF 防護層完成的工作,正如下面的小節所介紹的
#### 使用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` 的工作模式?它完成以下兩項工作:
1. 它修改當前處理的請求,向所有的 `POST` 表單增添一個隱藏的表單字段,使用名稱是 `csrfmiddlewaretoken` ,值為當前會話 ID 加上一個密鑰的散列值?如果未設置會?ID ,該中間件將 *不會* 修改響應結果,因此對于未使用會話的請求來說性能損失是可以忽略的
2. 對于所有含會話 cookie 集合的傳?`POST` 請求,它將檢查是否存?`csrfmiddlewaretoken` 及其是否正確?如果不是的話,用戶將會收到一?403 `HTTP` 錯誤?403 錯誤頁面的內容是檢測到了跨域請求偽裝?終止請求
該步驟確保只有源自你的站點的表單才能將數?POST 回來
該中間件特意只針?HTTP `POST` 請求(以及對應的 POST 表單)?如我們所解釋的,永遠不應該因為使用了 `GET` 請求而產生負面效應,你必須自己來確保這一點
未使用會?cookie ?`POST` 請求無法受到保護,但它們也不 *需 受到保護,因為惡意網站可用任意方法來制造這種請求*
*為了避免轉換?HTML 請求,中間件在編輯響應結果之前對它的 `Content-Type` 頭標進行檢查?只有標記?`text/html` ?`application/xml+xhtml` 的頁面才會被修改*
#### *CSRF中間件的局限*
*`CsrfMiddleware` 的運行需?Django 的會話框架?(參閱第 14 章了解更多關于會話的內容。)如果你使用了自定義會話或者身份驗證框架手動管理會?cookies,該中間件將幫不上你的忙*
*如果你的應用程序以某種非常規的方法創?HTML 頁面(例如:?Javascript ?tt class="docutils literal">document.write語句中發?HTML 片段),你可能會繞開了向表單添加隱藏字段的過濾器?在此情況下,表單提交永遠無法成功?(這是因為在頁面發送到客戶端之前,`CsrfMiddleware`使用正則表達式來添加`csrfmiddlewaretoken`字段到你的HTML中,而正則表達式不能處理不規范的HTML。)如果你懷疑出現了這樣的問題。使用你瀏覽器的查看源代碼功能以確定`csrfmiddlewaretoken`是否插入到了表單中*
*想了解更多關?CSRF 的信息和例子的話,可以訪?<http://en.wikipedia.org/wiki/CSRF>*
## *人性化數據*
*?tt class="docutils literal">django.contrib.humanize包含了一些是數據更人性化的模板過濾器?要激活這些過濾器,請?tt class="docutils literal">'django.contrib.humanize'加入到你?tt class="docutils literal">INSTALLED\_APPS中。完成之后,向模版了加入`{% load humanize %}`就可以使用下面的過濾器了*
### *apnumber*
*對于 1 ?9 的數字,該過濾器返回了數字的拼寫形式?否則,它將返回數字?這遵循的是美聯社風格*
*舉例*
- *1 變成 one*
- *2 變成 two*
- *10 變成 10*
*你可以傳入一個整數或者表示整數的字符串*
### *intcomma*
*該過濾器將整數轉換為每三個數字用一個逗號分隔的字符串*
*例子*
- *4500 變成 4,500*
- *45000 變成 45,000*
- *450000 變成 450,000*
- *4500000 變成 4,500,000*
*可以傳入整數或者表示整數的字符串*
### *intword*
*該過濾器將一個很大的整數轉換成友好的文本表示方式?它對于超過一百萬的數字最好用*
*例子*
- *1000000 變成 1.0 million*
- *1200000 變成 1.2 million*
- *1200000000 變成 1.2 billion*
*最大支持不超過一千的五次方(1,000,000,000,000,000)*
*可以傳入整數或者表示整數的字符串*
### *ordinal*
*該過濾器將整數轉換為序數詞的字符串形式*
*例子*
- *1 變成 1st*
- *2 變成 2nd*
- *3 變成 3rd*
- *254變成254th*
*可以傳入整數或者表示整數的字符串*
## *標記過濾*
*?tt class="docutils literal">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>)*
*每種情形下,過濾器都期望字符串形式的格式化標記,并返回表示標記文本的字符串?例如?tt class="docutils literal">textile過濾器吧Textile格式的文本轉換為HTML*
```
<pre class="calibre9">```
_{% load markup %}
{{ object.content|textile }}_
```
```
\_要激活這些過濾器,僅需?`'django.contrib.markup'` 添加?`INSTALLED_APPS` 設置中?一旦完成了該項工作,在模板中通過 `{% load markup %}` 就能使用這些過濾器?要想掌握更多信息的話,可閱讀 `django/contrib/markup/templatetags/markup.py.` 內的源代碼
## *下一*
*這些繼承框架(CSRF、身份驗證系統等等)通過提供* 中間 來實現其奇妙的功能。中間件是在請求之前/后執行的可以修改請求和響應的代碼,它擴展了框架?在下一章,我們將介紹Django的中間件并解釋怎樣寫出自己的中間件