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.
内的源代码。