Python有眾多優點,其中之一就是「開機即用」原則:安裝Python的同時安裝好大量的標準軟體包,這樣你可以立即使用而不用自己去下載。 Django也遵循這個原則,它同樣包含了自己的標準函式庫。這一章就來講這些整合的子框架。
Django標準庫
Django的標準庫存放在django.contrib包中。每個子包都是一個獨立的附加功能包。它們互相之間一般沒有必然的關聯,但是有些django.contrib子包可能依賴其他的套件。
在django.contrib中對函數的類型並沒有強制要求。其中一些套件中帶有模型(因此需要你在資料庫中安裝對應的資料表),但其它一些由獨立的中間件及模板標籤組成。
django.contrib開發包共有的特性是:就算你將整個django.contrib開發包刪除,你依然可以使用
Django 的基礎功能而不會遇到任何問題。當 Django開發者向框架增加新功能的時,他們會嚴格根據這項教條來決定是否把新功能放入django.contrib中。
django.contrib由以下開發套件組成:
§ admin:自動化的網站管理工具。請查看Django管理網站
§ auth: Django的使用者驗證框架。請查會話、使用者和註冊
§ comments:一個評論應用,目前,這個應用正在緊張的開發中,因此在本書出版的時候還不能給出一個完整的說明,關於這個應用的更多資訊請參閱Django的官方網站.
§ contenttypes:這是一個用於文檔類型鉤子的框架,每個安裝的Django模組作為一種獨立的文檔類型。這個框架主要在Django內部被其他應用使用,它主要面向Django的高級開發者。可以透過閱讀原始碼來了解關於這個框架的更多信息,源碼的位置在django/contrib/contenttypes/.
§ csrf:這個模組用來防禦跨站請求偽造(CSRF).參見後面標題為”CSRF防禦」的小節。
§ flatpages:一個在資料庫中管理單一HTML內容的模組,請參閱後面標題為「Flatpages」的小節。
§ humanize:一系列 Django
模組過濾器,用於增加資料的人性化。
§ markup:一系列的 Django
模板過濾器,用於實現一些常用標記語言。
§ redirects:用來管理重定向的框架。
§ sessions: Django的會話框架,請參閱會話、使用者和註冊。
§ sitemaps:用來產生網站地圖的 XML
檔案的框架。參見Django輸出非HTML內容。
§ sites:一個讓你可以在同一個資料庫與 Django安裝中管理多個網站的框架。
§ syndication:一個用 RSS
和 Atom 來產生聚合訂閱源的的框架。參見Django輸出非HTML內容。
本章接下來將詳細描述前面沒有介紹過的django.contrib開發套件內容。
多個站點
Django 的多站點系統是一種通用框架,它讓你可以在同一個資料庫和同一個Django專案下操作多個網站。這是一個抽象概念,理解起來可能有點困難,因此我們從幾個讓它能派上用場的實際情景入手。
情景1:對多個站點重用資料
正如我們在第一章所建構的講,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是透過幾個簡單的約定自動使用的。
安裝多站點應用要執行以下幾個步驟:
1. 將'django.contrib.sites'加入到INSTALLED_APPS。
2. 執行 manage.py syncdb指令將 django_site表安裝到資料庫中。
3. 透過 Django管理後台或透過 Python API新增一個或多個
‘Site’物件。為該 Django
專案支撐的每個站(或域)建立一個Site物件。
4. 在每個設定檔中定義一個SITE_ID變數。此變數值應為該設定檔所支撐的站點之Site物件的資料庫
ID 。
多站點框架的功能
下面幾節講述的是用多站點框架能夠完成的幾項工作。
多個站點的數據重用
正如在情景一中所解釋的,要在多個站點間重用數據,僅需在模型中為Site添加一個多對多字段即可,例如:
from django.db import models from django.contrib.sites.models import Site class Article(models.Model): headline = models.CharField(maxlength=200) # ... sites = models.ManyToManyField(Site)
這是在資料庫中為多個站點進行文章關聯操作的基礎步驟。在適當的位置使用該技術,你可以在多個網站中重複使用同一段 Django視圖程式碼。繼續Article模型範例,以下是一個可能的article_detail視圖:
from django.conf import settings def article_detail(request, article_id): try: a = Article.objects.get(id=article_id, sites__id=settings.SITE_ID) except Article.DoesNotExist: raise Http404
# ...
該視圖方法是可重用的,因為它根據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(maxlength=200) # ... site = models.ForeignKey(Site)
這與前一節中介紹的一樣有益。
從視圖鉤掛當前站點
在底層,透過在Django視圖中使用多站點框架,你可以讓視圖根據調用站點不同而完成不同的工作,例如:
reee像那樣對網站ID進行硬編碼是比較難看的。略為簡潔的完成方式是查看目前的網站域:from django.conf import settings def my_view(request): if settings.SITE_ID == 3: # Do something. else: # Do something else.
(Site.objects)具備一個get_current()方法。下面的例子與前一個是等效的:
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.
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.
例子,在Lawrence.com
該郵件的標題行是「感謝註冊 Lawrence.com提醒信件」。在
LJWorld.com,該郵件標題行是「感謝註冊 LJWorld.com提醒信件」。這種網站關聯行為方式對郵件資訊主體也同樣適用。
LJWorld.com 各自擁有不同的模板目錄(TEMPLATE_DIRS),你可將工作輕鬆地轉交給模板系統,如下所示:
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]) # ...
的範本目錄中都建立一份subject.txt和message.txt範本。正如之前所說,該方法帶來了更大的靈活性,但也帶來了更多複雜性。
Django 的get_absolute_url()约定对与获取不带域名的对象 URL非常理想,但在某些情形下,你可能想显示某个对象带有http://和域名以及所有部分的完整
URL。要完成此工作,你可以使用多站点框架。下面是个简单的例子:
>>> from django.contrib.sites.models import Site >>> obj = MyModel.objects.get(id=3) >>> obj.get_absolute_url() '/mymodel/objects/3/' >>> Site.objects.get_current().domain 'example.com' >>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())
'http://example.com/mymodel/objects/3/'
当前站点管理器
如果站点在你的应用中扮演很重要的角色,请考虑在你的模型中使用方便的CurrentSiteManager。这是一个模型管理器(见附录B),它会自动过滤使其只包含与当前站点相关联的对象。
通过显示地将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(maxlength=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(maxlength=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在几个地方利用了它。即使只用 Django来支持单个网站,你也应该花一点时间用domain和name来创建站点对象,并将SITE_ID设置指向它的
ID 。
以下讲述的是 Django如何使用多站点框架:
§ 在重定向框架中(见后面的重定向一节),每一个重定向对象都与一个特定站点关联。当 Django搜索重定向的时候,它会考虑当前的SITE_ID。
§ 在注册框架中,每个注释都与特定站点相关。每个注释被张贴时,其site被设置为当前的SITE_ID,而当通过适当的模板标签列出注释时,只有当前站点的注释将会显示。
§ 在 flatpages框架中 (参见后面的
Flatpages一节),每个 flatpage都与特定的站点相关联。创建 flatpage时,你都将指定它的site,而
flatpage中间件在获取 flatpage以显示它的过程中,将查看当前的SITE_ID。
§ 在 syndication框架中(参阅Django输出非HTML内容),title和description的模板自动访问变量{{
site }},它就是代表当前着桨的Site对象。Also, the hook for providing item URLs will use thedomainfrom
the currentSiteobject if you dont specify a fully qualified domain.
§ 在身份验证框架(参见Django会话、用户和注册)中,django.contrib.auth.views.login视图将当前Site名称作为{{
site_name }}传递给模板。
Flatpages - 简单页面
尽管通常情况下总是建造和运行数据库驱动的 Web应用,你还是会需要添加一两张一次性的静态页面,例如“关于”页面,或者“隐私策略”页面等等。可以用像
Apache 这样的标准Web服务器来处理这些静态页面,但却会给应用带来一些额外的复杂性,因为你必须操心怎么配置 Apache,还要设置权限让整个团队可以修改编辑这些文件,而且你还不能使用
Django 模板系统来统一这些页面的风格。
这个问题的解决方案是使用位于django.contrib.flatpages开发包中的 Django简单页面(flatpages)应用程序。该应用让你能够通过
Django超级管理站点来管理这些一次性的页面,还可以让你使用 Django模板系统指定它们使用哪个模板。它在后台使用了 Django模型,也就是说它将页面存放在数据库中,你也可以像对待其他数据一样用标准
Django数据库 API
存取简单页面。
简单页面以它们的 URL和站点为键值。当创建简单页面时,你指定它与哪个URL以及和哪个站点相关联。
使用简单页面
安装平页面应用程序必须按照下面的步骤:
1. 添加'django.contrib.flatpages'到INSTALLED_APPS设置。django.contrib.flatpages依赖于django.contrib.sites,所以确保这两个开发包都包括在``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进行定义,如下所示:
from django.db import models from django.contrib.sites.models import Site class FlatPage(models.Model): url = models.CharField(maxlength=100) title = models.CharField(maxlength=200) content = models.TextField() enable_comments = models.BooleanField() template_name = models.CharField(maxlength=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区域。你可以像编辑系统中其它对象那样编辑简单页面。
通过 Python API
前面已经提到,简单页面表现为django/contrib/flatpages/models.py中的标准 Django模型。因此,你可以通过
Django数据库 API
来存取简单页面对象,例如:
>>> from django.contrib.flatpages.models import FlatPage >>> from django.contrib.sites.models import Site >>> fp = FlatPage( ... url='/about/', ... title='About', ... content='<p>About this site...</p>', ... enable_comments=False, ... template_name='', ... registration_required=False, ... ) >>> fp.save() >>> 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 }} </body> </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 来存取重定向对象,例如:
>>> from django.contrib.redirects.models import Redirect >>> from django.contrib.sites.models import Site >>> red = Redirect( ... site=Site.objects.get(id=1), ... old_path='/music/', ... new_path='/sections/arts/music/', ... ) >>> red.save() >>> Redirect.objects.get(old_path='/music/') <Redirect: /music/ ---> /sections/arts/music/>
CSRF 防护
django.contrib.csrf开发包能够防止遭受跨站请求伪造攻击 (CSRF).
CSRF, 又叫进程跳转,是一种网站安全攻击技术。当某个恶意网站在用户未察觉的情况下将其从一个已经通过身份验证的站点诱骗至一个新的 URL时,这种攻击就发生了,因此它可以利用用户已经通过身份验证的状态。开始的时候,要理解这种攻击技术比较困难,因此我们在本节将使用两个例子来说明。
一个简单的 CSRF例子
假定你已经登录到example.com的网页邮件账号。该网页邮件站点上有一个登出按钮指向了 URLexample.com/logout,换句话说,要登出的话,需要做的唯一动作就是访问
URL :example.com/logout。
通过在(恶意)网页上用隐藏一个指向 URLexample.com/logout的
诚然,对你而言登出一个网页邮件站点并不会构成多大的安全破坏,但同样的攻击可能发生在任何信任用户的站点之上,比如在线银行网站或者电子商务网站。
稍微复杂一点的CSRF例子
In the previous example, example.com should be partially responsible because it allows status changes (i.e. login and logout) through the HTTPGET method. The situation would be much better if the
HTTPPOST method is required for server state changes. However, even if it is mandatory to use the POST method for state change operations, it is still vulnerable to CSRF attacks.
Assume that example.com has upgraded the logout function. The logout of the