第十四章: 会话、用户和注册
是时候承认了: 我们有意的避开了Web开发中极其重要的方面。 到目前为止,我们都在假定,网站流量是大量的匿名用户带来的。
这当然不对。 浏览器的背后都是活生生的人(至少某些时候是)。 这忽略了重要的一点: 互联网服务于人而不是机器。 要开发一个真正令人心动的网站,我们必须面对浏览器后面活生生的人。
很不幸,这并不容易。 HTTP被设计为”无状态”,每次请求都处于相同的空间中。 在一次请求和下一次请求之间没有任何状态保持,我们无法根据请求的任何方面(IP地址,用户代理等)来识别来自同一人的连续请求。
在本章中你将学会如何搞定状态的问题。 好了,我们会从较低的层次(cookies)开始,然后过渡到用高层的工具来搞定会话,用户和注册的问题。
Cookies
浏览器的开发者在很早的时候就已经意识到, HTTP’s 的无状态会对Web开发者带来很大的问题,于是(cookies)应运而生。 cookies 是浏览器为 Web 服务器存储的一小段信息。 每次浏览器从某个服务器请求页面时,它向服务器回送之前收到的cookies
来看看它是怎么工作的。 当你打开浏览器并访问 google.com ,你的浏览器会给Google发送一个HTTP请求,起始部分就象这样:
GET / HTTP/1.1
Host: google.com
...
当 Google响应时,HTTP的响应是这样的:
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671;
expires=Sun, 17-Jan-2038 19:14:07 GMT;
path=/; domain=.google.com
Server: GWS/2.1
...
注意 Set-Cookie 的头部。 你的浏览器会存储cookie值( PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671 ) ,而且每次访问google 站点都会回送这个cookie值。 因此当你下次访问Google时,你的浏览器会发送像这样的请求:
GET / HTTP/1.1
Host: google.com
Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671
...
于是 Cookies 的值会告诉Google,你就是早些时候访问过Google网站的人。 这个值可能是数据库中存储用户信息的key,可以用它在页面上显示你的用户名。 Google会(以及目前)使用它在网页上显示你账号的用户名。
存取Cookies
在Django中处理持久化,大部分时候你会更愿意用高层些的session 和/或 后面要讨论的user 框架。 但在此之前,我们需要停下来在底层看看如何读写cookies。 这会帮助你理解本章节后面要讨论的工具是如何工作的,而且如果你需要自己操作cookies,这也会有所帮助。
读取已经设置好的cookies极其简单。 每一个HttpRequest 对象都有一个COOKIES 对象,该对象的行为类似一个字典,你可以使用它读取任何浏览器发送给视图(view)的cookies。
def show_color(request):
if "favorite_color" in request.COOKIES:
return HttpResponse("Your favorite color is %s" % request.COOKIES["favorite_color"])
else:
return HttpResponse("You don't have a favorite color.")
def show_color(request): if "favorite_color" in request.COOKIES: return HttpResponse("Your favorite color is %s" % request.COOKIES["favorite_color"]) else: return HttpResponse("You don't have a favorite color.")
cookie的例子:
def set_color(request):
if "favorite_color" in request.GET:
# Create an HttpResponse object...
response = HttpResponse("Your favorite color is now %s" % request.GET["favorite_color"])
# ... and set a cookie on the response
response.set_cookie("favorite_color",
request.GET["favorite_color"])
return response
else:
return HttpResponse("You didn't give a favorite color.")
response = HttpResponse("Your favorite color is now %s" % request.GET["favorite_color"]) 调整下
a) def show_color(request): errors = [] if "favorite_color" in request.COOKIES: return HttpResponse("Your favorite color is %s" % request.COOKIES["favorite_color"]) else: errors.append("You don't have a cookie favorite color. Pls set it.") return render_to_response('cookie_test1.html',{'errors':errors}) b)基于 GET 参数来设置 favorite_color cookie的例子: def set_color(request): errors = [] if not request.GET['favoriteclr']: errors.append("You don't set a cookie favorite color. Pls set it.") else: # Create an HttpResponse object... response = HttpResponse("Your favorite color is now %s" % request.GET["favoriteclr"]) # ... and set a cookie on the response response.set_cookie("favorite_color", request.GET["favoriteclr"]) return response return render_to_response('cookie_test1.html',{'errors':errors}) c)写模板/home/yxk/djcode/mysite/mysite/template/cookie_test1.html 与 写url.py映射 <html> <head> <title>Cookie test</title> </head> <body> {% if errors %} <ul> {% for error in errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} <form action="/set_color/" method="get"> {# <form action="" method="get"> should visit http://127.0.0.1:8000/search/ in web after delete /search/ in action </form>#} <input type="text" name="favoriteclr"> <input type="submit" value="Set"> </form> </body> </html> (r'^show_color/$', 'mysite.views.show_color'), (r'^set_color/$', 'mysite.views.set_color'),
作者这里的这个例子很坑,,,request.GET 这个GET属性是客户提交的,也就是表单的信息,我们做这个测试,更本没有,其实设置就是用set_cookie 来设置,用这个例子 def get_cookies(request): if "user_id" in request.COOKIES: cookies = request.COOKIES for key, value in cookies.items(): print(key, " ", value) return HttpResponse("Your favorite color is %s" % request.COOKIES["user_id"]) def set_cookies(request): if "user_id" not in request.COOKIES: response = HttpResponse("ok") response.set_cookie("user_id", "10098") return response else: return HttpResponse("ok")
你可以给 response.set_cookie() 传递一些可选的参数来控制cookie的行为,详见表14-1。
Error parsing content block for the “table” directive: exactly one table expected.
.. table:: 表 14-1: Cookie 选项
+---------------------------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|参数 |缺省值 |描述 |
+=================================+===========================+====================================================================================================================================================================================+
|``max_age`` |``None`` |cookie需要延续的时间(以秒为单位) 如果参数是\ `` None`` ,这个cookie会延续到浏览器关闭为止。 |
+---------------------------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|``expires`` |``None`` |cookie失效的实际日期/时间。 它的格式必须是:\ `` "Wdy, DD-Mth-YY HH:MM:SS GMT"`` 。如果给出了这个参数,它会覆盖\ `` max_age`` 参数。 |
+---------------------------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|``path`` |``"/"`` |cookie生效的路径前缀。 浏览器只会把cookie回传给带有该路径的页 面,这样你可以避免将cookie传给站点中的其他的应用。 |
| | | |
| | |当你不是控制你的站点的顶层时,这样做是特别有用的。 |
+---------------------------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|``domain`` |``None`` |这个cookie有效的站点。 你可以使用这个参数设置一个跨站点(cross-domain)的cookie。 比如,\ `` domain=".example.com"`` 可以设置一个在\ `` www.example.com`` 、\ `` www2.example.com`` 以及\ `` an.other.sub.domain.example.com`` 站点下都可读到的cookie。|
| | | |
| | |如果这个参数被设成\ `` None`` ,cookie将只能在设置它的站点下可以读到。 |
+---------------------------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|``False`` |``False`` |如果设置为 ``True`` ,浏览器将通过HTTPS来回传cookie。 |
+---------------------------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+---------------------------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |参数 |缺省值 |描述 | +=======+=======+=================+===========================+====================================================================================================================================================================================+ |max_age|None |cookie需要延续的时间(以秒为单位) 如果参数是None,这个cookie会延续到浏览器关闭为止。 | +-------+-------+-----------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |expires|None |cookie失效的实际日期/时间。如果给出了这个参数,它会覆盖max_age参数。 | +-------+-------+-----------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |path |"/" |cookie生效的路径前缀。浏览器只会把cookie回传给带有该路径的页面,这样你可以避免将cookie | | | | | | | |传给站点中的其他的应用。当你不是控制你的站点的顶层时,这样做是特别有用的。 | +-------+-------+-----------------+----
可以看英文版本的.这里的是markdown格式的表格,未能正确渲染. http://www.djangobook.com/en/2.0/chapter14.html,查找"Table 14-1: Cookie options",跳转到即可.
好坏参半的Cookies
也许你已经注意到了,cookies的工作方式可能导致的问题。 让我们看一下其中一些比较重要的问题:
cookie的存储是自愿的,一个客户端不一定要去接受或存储cookie。 事实上,所有的浏览器都让用户自己控制 是否接受cookies。 如果你想知道cookies对于Web应用有多重要,你可以试着打开这个浏览器的 选项:
尽管cookies广为使用,但仍被认为是不可靠的的。 这意味着,开发者使用cookies之前必须 检查用户是否可以接收cookie。
Cookie(特别是那些没通过HTTPS传输的)是非常不安全的。 因为HTTP数据是以明文发送的,所以 特别容易受到嗅探攻击。 也就是说,嗅探攻击者可以在网络中拦截并读取cookies,因此你要 绝对避免在cookies中存储敏感信息。 这就意味着您不应该使用cookie来在存储任何敏感信息。
还有一种被称为”中间人”的攻击更阴险,攻击者拦截一个cookie并将其用于另一个用户。 第19章将深入讨论这种攻击的本质以及如何避免。
MITM下会话劫持,使用arp欺骗,截获用户与路由器之间的HTTP流量,将用户已登陆状态下的会话cookie放置本地浏览器中,从而server会视攻击者的访问为此已登陆用户的请求。使用工具:ettercap(arp欺骗),wireshark(找cookie),EditThisCookie(放cookie)可模拟。操作的背后还是需要技术积累的。少年,想知道旁边美女的QQ号吗,想进她的QQ空间加密相册吗?在她的新浪微博里发一条迷恋你的微博然后她当你的面吐一口口水吗?相信自己,你可以的!
即使从预想中的接收者返回的cookie也是不安全的。 在大多数浏览器中您可以非常容易地修改cookies中的信息。有经验的用户甚至可以通过像mechanize(http://wwwsearch.sourceforge.net/mechanize/) 这样的工具手工构造一个HTTP请求。
因此不能在cookies中存储可能会被篡改的敏感数据。 在cookies中存储 IsLoggedIn=1 ,以标识用户已经登录。 犯这类错误的站点数量多的令人难以置信; 绕过这些网站的安全系统也是易如反掌。
Django的 Session 框架
由于存在的限制与安全漏洞,cookies和持续性会话已经成为Web开发中令人头疼的典范。 好消息是,Django的目标正是高效的“头疼杀手”,它自带的session框架会帮你搞定这些问题。
你可以用session 框架来存取每个访问者任意数据, 这些数据在服务器端存储,并对cookie的收发进行了抽象。 Cookies只存储数据的哈希会话ID,而不是数据本身,从而避免了大部分的常见cookie问题。
打开 Sessions功能
Sessions 功能是通过一个中间件(参见第17章)和一个模型(model)来实现的。 要打开sessions功能,需要以下几步操作:
- 编辑
MIDDLEWARE_CLASSES配置,确保MIDDLEWARE_CLASSES中包含'django.contrib.sessions.middleware.SessionMiddleware'。 - 确认
INSTALLED_APPS中有'django.contrib.sessions'(如果你是刚打开这个应用,别忘了运行manage.py syncdb)
如果项目是用 startproject 来创建的,配置文件中都已经安装了这些东西,除非你自己删除,正常情况下,你无需任何设置就可以使用session功能。
如果不需要session功能,你可以删除 MIDDLEWARE_CLASSES 设置中的 SessionMiddleware 和 INSTALLED_APPS 设置中的 'django.contrib.sessions' 。虽然这只会节省很少的开销,但积少成多啊。
在视图中使用Session
SessionMiddleware 激活后,每个传给视图(view)函数的第一个参数HttpRequest 对象都有一个 session 属性,这是一个字典型的对象。 你可以象用普通字典一样来用它。 例如,在视图(view)中你可以这样用:
+---------------------------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |参数 |缺省值 |描述 | +=======+=======+=================+===========================+====================================================================================================================================================================================+ |max_age|None |cookie需要延续的时间(以秒为单位) 如果参数是None,这个cookie会延续到浏览器关闭为止。 | +-------+-------+-----------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |expires|None |cookie失效的实际日期/时间。如果给出了这个参数,它会覆盖max_age参数。 | +-------+-------+-----------------+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ |path |"/" |cookie生效的路径前缀。浏览器只会把cookie回传给带有该路径的页面,这样你可以避免将cookie | | | | | | | |传给站点中的其他的应用。当你不是控制你的站点的顶层时,这样做是特别有用的。 | +-------+-------+-----------------+----
# Set a session value:
request.session["fav_color"] = "blue"
# Get a session value -- this could be called in a different view,
# or many requests later (or both):
fav_color = request.session["fav_color"]
# Clear an item from the session:
del request.session["fav_color"]
# Check if the session has a given key:
if "fav_color" in request.session:
...
其他的映射方法,如 keys() 和 items() 对 request.session 同样有效:
下面是一些有效使用Django sessions的简单规则:
用正常的字符串作为key来访问字典 request.session , 而不是整数、对象或其它什么的。
Session字典中以下划线开头的key值是Django内部保留key值。 框架只会用很少的几个下划线 开头的session变量,除非你知道他们的具体含义,而且愿意跟上Django的变化,否则,最好 不要用这些下划线开头的变量,它们会让Django搅乱你的应用。
request.session['_fav_color'] = 'blue' # Don't do this!
不要用一个新对象来替换掉 request.session ,也不要存取其属性。 可以像Python中的字典那样使用。 例如:
request.session = some_other_object # Don't do this!
request.session.foo = 'bar' # Don't do this!
我们来看个简单的例子。 这是个简单到不能再简单的例子:在用户发了一次评论后将has_commented设置为True。 这是个简单(但不很安全)的、防止用户多次评论的方法。
def post_comment(request):
if request.method != 'POST':
raise Http404('Only POSTs are allowed')
if 'comment' not in request.POST:
raise Http404('Comment not submitted')
if request.session.get('has_commented', False):
return HttpResponse("You've already commented.")
c = comments.Comment(comment=request.POST['comment'])
c.save()
request.session['has_commented'] = True
return HttpResponse('Thanks for your comment!')
if request.session.get('has_commented', False): ---> if request.session.get('has_commented', True):
上面说has_comment为True则代表评论过,为什么这里要判断“if request.session.get('has_commented', False):”则返回You've already commented???
@TT 你没有理解get函数的用法,第二个参数是在第一个参数key所对应的值为None的时候才作为它的值,所以这句话的意思是得到has_commented的值,如果他为空就赋值为false,这样这个if只有在has_commented为true时才会成立
第三个if相当于: if 'has_commented' in request.session: result = request.session['has commented'] else: result = False if result: return HttpResponse("You've already commented.")
if request.session.get('has_commented', False): ---> if request.session.get('has_commented', True):
下面是一个很简单的站点登录视图(view):
from django.http import HttpResponse,Http404,HttpResponseRedirect from django.shortcuts import render from django.template import RequestContext from login_chapter1401.models import Member def login(request): if request.method == 'POST': try: m = Member.objects.get(username=request.POST['user']) if m.password == request.POST['pass']: request.session['member_id'] = m.id return HttpResponseRedirect('../you-are-logged-in/') except Member.DoesNotExist: return HttpResponse("Your username and password didn't math.") return render(request,'login_chapter14.html') def login_ok(request): return render(request,'login_ok.html')
<html> <head> <title>Test Login</title> </head> <body> <form action="" method="post"> {% csrf_token %} <p> UserName:<input type="text" name= "user"</p> <p> PassWord:<input type="password" name="pass"></p> <input type="submit" value="Submit"> </form> </body> </html>
<html> <head> <title>Login OK</title> </head> <body> <p>Successful.</p> <br> <a href='../logout'>logout</a> </body> </html>
from django.db import models class Member(models.Model): username = models.CharField(max_length=10) password = models.CharField(max_length=100) def __unicode__(self): return self.username
from django.conf.urls import include, url from django.contrib import admin from urls13 import url13 from urls14 import url14 from views import * urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'^thirteen/',include(url13)), url(r'^fourteen/',include(url14)), ]
from django.conf.urls import include, url from django.contrib import admin from fourteen_views import * url14 = [ url(r'^getcook/$',show_color), url(r'^setcook/$',set_color), url(r'^post_comment/$',post_comment), url(r'^login/$',login), url(r'^logout/$',logout), url(r'^you-are-logged-in/$',login_ok), ]
request.session['member_id'] = m.id 里面的member_id是什么意思 这个m.id中的id,如果我表格中就有一个自增长的id,这个id我应该用表格中的项名称 ,还是可以直接用id啊,求解
def login(request):
if request.method != 'POST':
raise Http404('Only POSTs are allowed')
try:
m = Member.objects.get(username=request.POST['username'])
if m.password == request.POST['password']:
request.session['member_id'] = m.id
return HttpResponseRedirect('/you-are-logged-in/')
except Member.DoesNotExist:
return HttpResponse("Your username and password didn't match.")
下面的例子将登出一个在上面已通过login() 登录的用户:
from django.http import HttpResponse,Http404,HttpResponseRedirect from django.shortcuts import render from django.template import RequestContext from login_chapter1401.models import Member def logout(request): try: del request.session['member_id'] except KeyError: return HttpResponse("Not member_id.") return HttpResponse("You're logged out.")
<html> <head> <title>Login OK</title> </head> <body> <p>Successful.</p> <br> <a href='../logout'>logout</a> </body> </html>
def logout(request):
try:
del request.session['member_id']
except KeyError:
pass
return HttpResponse("You're logged out.")
注意
在实践中,这是很烂的用户登录方式,稍后讨论的认证(authentication )框架会帮你以更健壮和有利的方式来处理这些问题。 这些非常简单的例子只是想让你知道这一切是如何工作的。 这些实例尽量简单,这样你可以更容易看到发生了什么
设置测试Cookies
,并在后续的视图(view)、而不是当前的视图(view)中检查 request.session.test_cookie_worked() 。
虽然把 set_test_cookie() 和 test_cookie_worked() 分开的做法看起来有些笨拙,但由于cookie的工作方式,这无可避免。 当设置一个cookie时候,只能等浏览器下次访问的时候,你才能知道浏览器是否接受cookie。
检查cookie是否可以正常工作后,你得自己用 delete_test_cookie() 来清除它,这是个好习惯。 在你证实了测试cookie已工作了之后这样操作。
这是个典型例子:
def login(request):
# If we submitted the form...
if request.method == 'POST':
# Check that the test cookie worked (we set it below):
if request.session.test_cookie_worked():
# The test cookie worked, so delete it.
request.session.delete_test_cookie()
# In practice, we'd need some logic to check username/password
# here, but since this is an example...
return HttpResponse("You're logged in.")
# The test cookie failed, so display an error message. If this
# were a real site, we'd want to display a friendlier message.
else:
return HttpResponse("Please enable cookies and try again.")
# If we didn't post, send the test cookie along with the login form.
request.session.set_test_cookie()
return render_to_response('foo/login_form.html')
在这里做实例测试时,如果开启了csfr,view添加了“django.middleware.csrf.CsrfViewMiddleware”,表单添加了“{% csrf_token %}”,当浏览器取消存取cookie访问时,会提示“Forbidden (403)Reason given for failure:CSRF cookie not set. ”之类的错误,我这里是关闭了csrf,“from django.views.decorators.csrf import csrf_exempt”,在登陆方法上添加装饰器“@csrf_exempt”,这样方法流程可以正常。在开启csrf前提下实现,还请知道的告知下
csrf就起到了cookie检测的作用啊,如果django开启csrf,而浏览器禁用cookie则django会报错,因为csrf中间件要求cookie要含有csrftoken名值对。 “您看到此消息是由于该站点在提交表单时需要一个CSRF cookie。此项是出于安全考虑,以确保您的浏览器没有被第三方劫持。 如果您已经设置浏览器禁用cookies,请重新启用,至少针对这个站点,全部HTTPS请求,或者同源请求(same-origin)启用cookies。”
注意
再次强调,内置的认证函数会帮你做检查的。
在视图(View)外使用Session
从内部来看,每个session都只是一个普通的Django model(在 django.contrib.sessions.models 中定义)。每个session都由一个随机的32字节哈希串来标识,并存储于cookie中。 因为它是一个标准的模型,所以你可以使用Django数据库API来存取session。
>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)
你需要使用get_decoded() 来读取实际的session数据。 这是必需的,因为字典存储为一种特定的编码格式。
>>> s.session_data
'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
>>> s.get_decoded()
{'user_id': 42}
何时保存Session
缺省的情况下,Django只会在session发生变化的时候才会存入数据库,比如说,字典赋值或删除。
# Session is modified.
request.session['foo'] = 'bar'
# Session is modified.
del request.session['foo']
# Session is modified.
request.session['foo'] = {}
# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session['foo']['bar'] = 'baz'
request.session['foo']['bar'] 的意思是foo这个session值是一个字典,这里修改的是session['foo']字典中的bar,所以不是修改session本身,所以session['foo']没变,变的是它的值
只有函数执行完,所发生的变化才在数据库里修改,如果request.session['foo']={}执行完后马上用Session.objects.get获取数据库里session不会看到foo,只有函数结束后,才会发现
你可以设置 SESSION_SAVE_EVERY_REQUEST 为 True 来改变这一缺省行为。如果置为True的话,Django会在每次收到请求的时候保存session,即使没发生变化。
“设置SESSION_SAVE_EVERY_REQUEST ,在settings.py中自定义吗?”,原来在global_settings.py中(我的是Django-1.4.3版本)。可是加载了DJANGO_SETTINGS_MODULE=mysite.settings后,不是不会加载global_settigns.py吗?还是说会加载settings.py中没有的部分?
"下面是django在编译settings文件时运用的规则: 从 global_settings.py 装载settings . 从指定settings 文件装载settings , 在需要的时候,覆盖全局的settings 注意一个settings 文件不应该导入 global_settings ,因为那是多余(冗余)的。"原来是我自己理解错了,原意是编译settings时,会先加载global_settings.py,再加载自己工程目录下的settings.py,所以没有必要在自己工程目录下的settings.py写上这么一句“from django.conf import global_settings”
注意,会话cookie只会在创建和修改的时候才会送出。 但如果 SESSION_SAVE_EVERY_REQUEST 设置为 True ,会话cookie在每次请求的时候都会送出。 同时,每次会话cookie送出的时候,其 expires 参数都会更新。
浏览器关闭即失效会话 vs 持久会话
你可能注意到了,Google给我们发送的cookie中有 expires=Sun, 17-Jan-2038 19:14:07 GMT; cookie可以有过期时间,这样浏览器就知道什么时候可以删除cookie了。 如果cookie没有设置过期时间,当用户关闭浏览器的时候,cookie就自动过期了。 你可以改变 SESSION_EXPIRE_AT_BROWSER_CLOSE 的设置来控制session框架的这一行为。
缺省情况下, SESSION_EXPIRE_AT_BROWSER_CLOSE 设置为 False ,这样,会话cookie可以在用户浏览器中保持有效达 SESSION_COOKIE_AGE 秒(缺省设置是两周,即1,209,600 秒)。 如果你不想用户每次打开浏览器都必须重新登陆的话,用这个参数来帮你。
如果 SESSION_EXPIRE_AT_BROWSER_CLOSE 设置为 True ,当浏览器关闭时,Django会使cookie失效。
@hliao 一般情况下sessionid是存在cookie里的,而cookie如果不设置expire date那么浏览器关闭后就自动删除了。而服务器端只要验证每次过来的请求头里是否有正确的sessionid,就可以判断是否是存储的会话了。而这也意味着需要对session表做定期清理,如果用web2py框架,会发现经常出现一堆session记录,要手动去清理的
其他的Session设置
除了上面提到的设置,还有一些其他的设置可以影响Django session框架如何使用cookie,详见表 14-2.
技术细节
如果你还是好奇的话,下面是一些关于session框架内部工作方式的技术细节:
session 字典接受任何支持序列化的Python对象。 参考Python内建模块pickle的文档以获取更多信息。
Session 数据存在数据库表 django_session 中
Session 数据在需要的时候才会读取。 如果你从不使用 request.session , Django不会动相关数据库表的一根毛。
Django 只在需要的时候才送出cookie。 如果你压根儿就没有设置任何会话数据,它不会 送出会话cookie(除非 SESSION_SAVE_EVERY_REQUEST 设置为 True )。
Django session 框架完全而且只能基于cookie。 它不会后退到把会话ID编码在URL中(像某些工具(PHP,JSP)那样)。
这是一个有意而为之的设计。 把session放在URL中不只是难看,更重要的是这让你的站点 很容易受到攻击——通过 Referer header进行session ID”窃听”而实施的攻击。
如果你还是好奇,阅读源代码是最直接办法,详见 django.contrib.sessions 。
通过session,我们可以在多次浏览器请求中保持数据, 接下来的部分就是用session来处理用户登录了。 当然,不能仅凭用户的一面之词,我们就相信,所以我们需要认证。
当然了,Django 也提供了工具来处理这样的常见任务(就像其他常见任务一样)。 Django 用户认证系统处理用户帐号,组,权限以及基于cookie的用户会话。 这个系统一般被称为 auth/auth (认证与授权)系统。 这个系统的名称同时也表明了用户常见的两步处理。 我们需要
- 验证 (认证) 用户是否是他所宣称的用户(一般通过查询数据库验证其用户名和密码)
- 验证用户是否拥有执行某种操作的 授权 (通常会通过检查一个权限表来确认)
根据这些需求,Django 认证/授权 系统会包含以下的部分:
- 用户 : 在网站注册的人
- 权限 : 用于标识用户是否可以执行某种操作的二进制(yes/no)标志
- 组 :一种可以将标记和权限应用于多个用户的常用方法
- Messages : 向用户显示队列式的系统消息的常用方法
如果你已经用了admin工具(详见第6章),就会看见这些工具的大部分。如果你在admin工具中编辑过用户或组,那么实际上你已经编辑过授权系统的数据库表了。
打开认证支持
像session工具一样,认证支持也是一个Django应用,放在 django.contrib 中,所以也需要安装。 与session系统相似,它也是缺省安装的,但如果它已经被删除了,通过以下步骤也能重新安装上:
- 根据本章早前的部分确认已经安装了session 框架。 需要确认用户使用cookie,这样sesson 框架才能正常使用。
- 将
'django.contrib.auth'放在你的INSTALLED_APPS设置中,然后运行manage.py syncdb以创建对应的数据库表。 - 确认
SessionMiddleware后面的MIDDLEWARE_CLASSES设置中包含'django.contrib.auth.middleware.AuthenticationMiddleware'SessionMiddleware。
这样安装后,我们就可以在视图(view)的函数中处理user了。 在视图中存取users,主要用 request.user ;这个对象表示当前已登录的用户。 如果用户还没登录,这就是一个AnonymousUser对象(细节见下)。
译文:这个对象表示当前已登录的用户。 原文:this is an object that represents the currently logged-in user 应为:这是一个能呈现已经登录的用户的对象
你可以很容易地通过 is_authenticated() 方法来判断一个用户是否已经登录了:
request.user.is_authenticated()再django 3.0中会报错:“'bool' object is not callable”。解决方法是不要后面的括号,即:request.user.is_authenticated
if request.user.is_authenticated():
# Do something for authenticated users.
else:
# Do something for anonymous users.
使用User对象
User 实例一般从 request.user ,或是其他下面即将要讨论到的方法取得,它有很多属性和方法。 AnonymousUser 对象模拟了 部分 的接口,但不是全部,在把它当成真正的user对象 使用前,你得检查一下 user.is_authenticated() 表14-3和14-4分别列出了User 对象中的属性(fields)和方法。
Error parsing content block for the “table” directive: exactly one table expected.
.. table:: 表 14-4. ``User`` 对象方法
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|方法 |描述 |
+=============================================================================================+======================================================================================================================================================+
|``is_authenticated()`` |对于真实的User对象,总是返回\ `` True`` 。 |
| |这是一个分辨用户是否已被鉴证的方法。 它并不意味着任何权限,也不检查用户是否仍是活动的。 它仅说明此用户已被成功鉴证。 |
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|``is_anonymous()`` |对于\ `` AnonymousUser`` 对象返回\ `` True`` (对于真实的\ `` User`` 对象返回\ `` False`` )。 |
| |总的来说,比起这个方法,你应该倾向于使用\ `` is_authenticated()`` 方法。 |
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|``get_full_name()`` |返回\ `` first_name`` 加上\ `` last_name`` ,中间插入一个空格。 |
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|``set_password(passwd)`` |设定用户密码为指定字符串(自动处理成哈希串)。 实际上没有保存\ ``User``\对象。 |
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|check_password(passwd) |如果指定的字符串与用户密码匹配则返回\ ``True``\。 比较时会使用密码哈希表。 |
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|``get_group_permissions()`` |返回一个用户通过其所属组获得的权限字符串列表。 |
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|``get_all_permissions()`` |返回一个用户通过其所属组以及自身权限所获得的权限字符串列表。 |
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|``has_perm(perm)`` |如果用户有指定的权限,则返回\ `` True`` ,此时\ `` perm`` 的格式是\ `` "package.codename"`` 。如果用户已不活动,此方法总是返回\ `` False`` 。 |
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|has_perms(perm_list) |如果用户拥有\ * 全部* 的指定权限,则返回\ `` True`` 。 如果用户是不活动的,这个方法总是返回\ `` False`` 。 |
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|``has_module_perms(app_label)`` |如果用户拥有给定的\ `` app_label`` 中的任何权限,则返回\ `` True`` 。如果用户已不活动,这个方法总是返回\ `` False`` 。 |
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|get_and_delete_messages() |返回一个用户队列中的\ `` Message`` 对象列表,并从队列中将这些消息删除。 |
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
|``email_user(subj, msg)`` |向用户发送一封电子邮件。 这封电子邮件是从\ `` DEFAULT_FROM_EMAIL`` 设置的地址发送的。 你还可以传送一个第三参数:\ `` from_email`` ,以覆盖电邮中的发送地址。|
+---------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
最后,User 对象有两个many-to-many属性。 groups 和permissions 。正如其他的many-to-many属性使用的方法一样,User 对象可以获得它们相关的对象:
# Set a user's groups:
myuser.groups = group_list
# Add a user to some groups:
myuser.groups.add(group1, group2,...)
# Remove a user from some groups:
myuser.groups.remove(group1, group2,...)
# Remove a user from all groups:
myuser.groups.clear()
# Permissions work the same way
myuser.permissions = permission_list
myuser.permissions.add(permission1, permission2, ...)
myuser.permissions.remove(permission1, permission2, ...)
myuser.permissions.clear()
登录和退出
奇技淫巧 ( qí jì yín qiǎo ) 解 释 指过于奇巧而无益的技艺与制品。奇,奇异。淫,过分,极端。 出 处 谓过于奇巧而无益的技艺与制品。《书·泰誓下》:“﹝ 商王 ﹞作奇技淫巧,以悦妇人。” 孔颖达 疏:“奇技谓奇异技能,淫巧谓过度工巧。二者大同,但技据人身,巧指器物为异耳。” 清 管同 《禁用洋货议》:“昔者,圣王之世,服饰有定制,而作奇技淫巧者有诛。” 用 法 联合式;作宾语、定语;指新奇的技艺和作品
和login()。
认证给出的用户名和密码,使用 authenticate() 函数。它接受两个参数,用户名 username 和 密码 password ,并在密码对给出的用户名合法的情况下返回一个 User 对象。 如果密码不合法,authenticate()返回None。
>>> from django.contrib import auth
>>> user = auth.authenticate(username='john', password='secret')
>>> if user is not None:
... print "Correct!"
... else:
... print "Invalid password."
authenticate() 只是验证一个用户的证书而已。 而要登录一个用户,使用 login() 。该函数接受一个 HttpRequest 对象和一个 User 对象作为参数并使用Django的会话( session )框架把用户的ID保存在该会话中。
下面的例子演示了如何在一个视图中同时使用 authenticate() 和 login() 函数:
from django.contrib import auth
def login_view(request):
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=username, password=password)
if user is not None and user.is_active:
# Correct password, and the user is marked "active"
auth.login(request, user)
# Redirect to a success page.
return HttpResponseRedirect("/account/loggedin/")
else:
# Show an error page
return HttpResponseRedirect("/account/invalid/")
注销一个用户,在你的视图中使用 django.contrib.auth.logout() 。 它接受一个HttpRequest对象并且没有返回值。
from django.contrib import auth
def logout_view(request):
auth.logout(request)
# Redirect to a success page.
return HttpResponseRedirect("/account/loggedout/")
注意,即使用户没有登录, logout() 也不会抛出任何异常。
在实际中,你一般不需要自己写登录/登出的函数;认证系统提供了一系例视图用来处理登录和登出。 使用认证视图的第一步是把它们写在你的URLconf中。 你需要这样写:
from django.contrib.auth.views import login, logout
urlpatterns = patterns('',
# existing patterns here...
(r'^accounts/login/$', login),
(r'^accounts/logout/$', logout),
)
/accounts/login/ 和 /accounts/logout/ 是Django提供的视图的默认URL。
缺省情况下, login 视图渲染 registragiton/login.html 模板(可以通过视图的额外参数 template_name 修改这个模板名称)。 这个表单必须包含 username 和 password 域。如下示例: 一个简单的 template 看起来是这样的
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.views.login
{% extends "base.html" %}
{% block content %}
{% if form.errors %}
<p class="error">Sorry, that's not a valid username or password</p>
{% endif %}
<form action="" method="post">
<label for="username">User name:</label>
<input type="text" name="username" value="" id="username">
<label for="password">Password:</label>
<input type="password" name="password" value="" id="password">
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next|escape }}" />
</form>
{% endblock %}
没错,在login函数的最后面的TemplateResponse里面也需要加上 context_instance=RequestContext(request)才行
一楼是正确的,在表单内部使用csrf_token 因为这个系统自带的login使用了@csrf_protect修饰符,处理的表单里面必须有这个token以便框架生成一个隐藏的域提交数据 2L是错误的,不需要修改哦
隐藏区域改成<input type="hidden" name="next" value="{{ '/hello' }}" />则登陆成功会跳转到/hello页面
如果用户登录成功,缺省会重定向到 /accounts/profile 。 你可以提供一个保存登录后重定向URL的next隐藏域来重载它的行为。 也可以把值以GET参数的形式发送给视图函数,它会以变量next的形式保存在上下文中,这样你就可以把它用在隐藏域上了。
logout视图有一些不同。 默认情况下它渲染 registration/logged_out.html 模板(这个视图一般包含你已经成功退出的信息)。 视图中还可以包含一个参数 next_page 用于退出后重定向。
login函数可以通过两个参数来指定login的模板和跳转链接分别是template='模板'和extra_context': {'next': '/foo'}写到url里面的格式如下 url(r'^login/$', login, {‘template’:‘template.html’'extra_context': {'next': '/foo'}})
限制已登录用户的访问
有很多原因需要控制用户访问站点的某部分。
一个简单原始的限制方法是检查 request.user.is_authenticated() ,然后重定向到登陆页面:
from django.http import HttpResponseRedirect
def my_view(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/?next=%s' % request.path)
# ...
或者显示一个出错信息:
def my_view(request):
if not request.user.is_authenticated():
return render_to_response('myapp/login_error.html')
# ...
作为一个快捷方式, 你可以使用便捷的 login_required 修饰符:
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
# ...
login_required 做下面的事情:
- 如果用户没有登录, 重定向到
/accounts/login/, 把当前绝对URL作为next在查询字符串中传递过去, 例如: /accounts/login/?next=/polls/3/ 。 - 如果用户已经登录, 正常地执行视图函数。 视图代码就可以假定用户已经登录了。
对通过测试的用户限制访问
限制访问可以基于某种权限,某些检查或者为login视图提供不同的位置,这些实现方式大致相同。
一般的方法是直接在视图的 request.user 上运行检查。 例如,下面视图确认用户登录并是否有 polls.can_vote权限:
def vote(request):
if request.user.is_authenticated() and request.user.has_perm('polls.can_vote')):
# vote here
else:
return HttpResponse("You can't vote in this poll.")
并且Django有一个称为 user_passes_test 的简洁方式。它接受参数然后为你指定的情况生成装饰器。
def user_can_vote(user):
return user.is_authenticated() and user.has_perm("polls.can_vote")
@user_passes_test(user_can_vote, login_url="/login/")
def vote(request):
# Code here can assume a logged-in user with the correct permission.
...
是否认证,你应该自己做这件事。
例子中我们也展示了第二个可选的参数 login_url ,它让你指定你的登录页面的URL(默认为 /accounts/login/ )。 如果用户没有通过测试,那么user_passes_test将把用户重定向到login_url
既然检查用户是否有一个特殊权限是相对常见的任务,Django为这种情形提供了一个捷径: permission_required() 装饰器。 使用这个装饰器,前面的例子可以改写为:
from django.contrib.auth.decorators import permission_required
@permission_required('polls.can_vote', login_url="/login/")
def vote(request):
# ...
注意, permission_required() 也有一个可选的 login_url 参数, 这个参数默认为 '/accounts/login/' 。
限制通用视图的访问
在Django用户邮件列表中问到最多的问题是关于对通用视图的限制性访问。 为实现这个功能,你需要自己包装视图,并且在URLconf中,将你自己的版本替换通用视图:
from django.contrib.auth.decorators import login_required
from django.views.generic.date_based import object_detail
@login_required
def limited_object_detail(*args, **kwargs):
return object_detail(*args, **kwargs)
当然, 你可以用任何其他限定修饰符来替换 login_required 。
管理 Users, Permissions 和 Groups
管理认证系统最简单的方法是通过管理界面。 第六章讨论了怎样使用Django的管理界面来编辑用户和控制他们的权限和可访问性,并且大多数时间你使用这个界面就可以了。
然而,当你需要绝对的控制权的时候,有一些低层 API 需要深入专研,我们将在下面的章节中讨论它们。
使用 create_user 辅助函数创建用户:
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user(username='john',
... email='jlennon@beatles.com',
... password='glass onion')
在这里, user 是 User 类的一个实例,准备用于向数据库中存储数据。(create_user()实际上没有调用save())。 create_user() 函数并没有在数据库中创建记录,在保存数据之前,你仍然可以继续修改它的属性值。
>>> user.is_staff = True
>>> user.save()
你可以使用 set_password() 来修改密码:
>>> user = User.objects.get(username='john')
>>> user.set_password('goo goo goo joob')
>>> user.save()
除非你清楚的知道自己在做什么,否则不要直接修改 password 属性。 其中保存的是密码的 加入salt的hash值 ,所以不能直接编辑。
一般来说, User 对象的 password 属性是一个字符串,格式如下:
hashtype$salt$hash
这是哈希类型,salt和哈希本身,用美元符号($)分隔。
hashtype 是 sha1 (默认)或者 md5 ,它是用来处理单向密码哈希的算法。 Salt是一个用来加密原始密码以创建哈希的随机字符串,例如:
sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4
User.set_password() 和 User.check_password() 函数在后台处理和检查这些值。
salt化得哈希值
一次 哈希 是一次单向的加密过程,你能容易地计算出一个给定值的哈希码,但是几乎不可能从一个哈希码解出它的原值。
如果我们以普通文本存储密码,任何能进入数据库的人都能轻易的获取每个人的密码。 使用哈希方式来存储密码相应的减少了数据库泄露密码的可能。
然而,攻击者仍然可以使用 暴力破解 使用上百万个密码与存储的值对比来获取数据库密码。 这需要花一些时间,但是智能电脑惊人的速度超出了你的想象。
更糟糕的是我们可以公开地得到 rainbow tables (一种暴力密码破解表)或预备有上百万哈希密码值的数据库。 使用rainbow tables可以在几秒之内就能搞定最复杂的一个密码。
在存储的hash值的基础上,加入 salt 值(一个随机值),增加了密码的强度,使得破解更加困难。 因为每个密码的salt值都不相同,这也限制了rainbow table的使用,使得攻击者只能使用最原始的暴力破解方法。
我们可以使用这些底层工具来创建允许用户注册的视图。 最近每个开发人员都希望实现各自不同的注册方法,所以Django把写注册视图的工作留给了你。 幸运的是,这很容易。
作为这个事情的最简化处理, 我们可以提供一个小视图, 提示一些必须的用户信息并创建这些用户。 Django为此提供了可用的内置表单, 下面这个例子就使用了这个表单:
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
new_user = form.save()
return HttpResponseRedirect("/books/")
else:
form = UserCreationForm()
return render_to_response("registration/register.html", {
'form': form,
})
1.2以后,需要使用RequestContext.例如: return render_to_response('registration/register.html',{'form':form},context_instance=RequestContext(request))
http://www.qttc.net/201209211.html 解决global name 'RequestContext' is not defined问题
from django.shortcuts import render_to_response, RequestContext以及添加context_instance=RequestContext(request)到render_to_response第三个栏位
这个表单需要一个叫 registration/register.html 的模板。这个模板可能是这样的:
{% extends "base.html" %}
{% block title %}Create an account{% endblock %}
{% block content %}
<h1>Create an account</h1>
<form action="" method="post">
{{ form.as_p }}
<input type="submit" value="Create the account">
</form>
{% endblock %}
在模板中使用认证数据
当前登入的用户以及他(她)的权限可以通过 RequestContext 在模板的context中使用(详见第9章)。
注意
从技术上来说,只有当你使用了 RequestContext这些变量才可用。 并且TEMPLATE_CONTEXT_PROCESSORS 设置包含了 “django.core.context_processors.auth” (默认情况就是如此)时,这些变量才能在模板context中使用。 TEMPLATE_CONTEXT_PROCESSORS 设置包含了 "django.core.context_processors.auth" (默认情况就是如此)时,这些变量才能在模板context中使用。
当使用 RequestContext 时, 当前用户 (是一个 User 实例或一个 AnonymousUser 实例) 存储在模板变量 {{ user }} 中:
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}. Thanks for logging in.</p>
{% else %}
<p>Welcome, new user. Please log in.</p>
{% endif %}
这些用户的权限信息存储在 {{ perms }} 模板变量中。
你有两种方式来使用 perms 对象。 你可以使用类似于 {{ perms.polls }} 的形式来检查,对于某个特定的应用,一个用户是否具有 任意 权限;你也可以使用 {{ perms.polls.can_vote }} 这样的形式,来检查一个用户是否拥有特定的权限。
这样你就可以在模板中的 {% if %} 语句中检查权限:
{% if perms.polls %}
<p>You have permission to do something in the polls app.</p>
{% if perms.polls.can_vote %}
<p>You can vote!</p>
{% endif %}
{% else %}
<p>You don't have permission to do anything in the polls app.</p>
{% endif %}
这个地方不对吧, {% if perms.polls %}不对,应该是 {% perms %}, {% if perms.polls.can_vote %}应该改为 {% if perms.app.can_vote %}才对,我的是1.4.5的版本, 不知道是不是版本的问题
权限、组和消息
在认证框架中还有其他的一些功能。 我们会在接下来的几个部分中进一步地了解它们。
权限
权限可以很方便地标识用户和用户组可以执行的操作。 它们被Django的admin管理站点所使用,你也可以在你自己的代码中使用它们。
Django的admin站点如下使用权限:
- 只有设置了 add 权限的用户才能使用添加表单,添加对象的视图。
- 只有设置了 change 权限的用户才能使用变更列表,变更表格,变更对象的视图。
- 只有设置了 delete 权限的用户才能删除一个对象。
权限是根据每一个类型的对象而设置的,并不具体到对象的特定实例。 例如,我们可以允许Mary改变新故事,但是目前还不允许设置Mary只能改变自己创建的新故事,或者根据给定的状态,出版日期或者ID号来选择权限。
会自动为每一个Django模型创建三个基本权限:增加、改变和删除。 当你运行manage.py syncdb命令时,这些权限被添加到auth_permission数据库表中。
权限以 "<app>.<action>_<object_name>" 的形式出现。
就跟用户一样,权限也就是Django模型中的 django.contrib.auth.models 。因此如果你愿意,你也可以通过Django的数据库API直接操作权限。
组
组提供了一种通用的方式来让你按照一定的权限规则和其他标签将用户分类。 一个用户可以隶属于任何数量的组。
在一个组中的用户自动获得了赋予该组的权限。 例如, Site editors 组拥有 can_edit_home_page 权限,任何在该组中的用户都拥有这个权限。
组也可以通过给定一些用户特殊的标记,来扩展功能。 例如,你创建了一个 'Special users' 组,并且允许组中的用户访问站点的一些VIP部分,或者发送VIP的邮件消息。
和用户管理一样,admin接口是管理组的最简单的方法。 然而,组也就是Django模型 django.contrib.auth.models ,因此你可以使用Django的数据库API,在底层访问这些组。
消息
消息系统会为给定的用户接收消息。 每个消息都和一个 User 相关联。
在每个成功的操作以后,Django的admin管理接口就会使用消息机制。 例如,当你创建了一个对象,你会在admin页面的顶上看到 The object was created successfully 的消息。
你也可以使用相同的API在你自己的应用中排队接收和显示消息。 API非常地简单:
- 要创建一条新的消息,使用
user.message_set.create(message='message_text')。 - 要获得/删除消息,使用
user.get_and_delete_messages(),这会返回一个Message对象的列表,并且从队列中删除返回的项。
在例子视图中,系统在创建了播放单(playlist)以后,为用户保存了一条消息。
def create_playlist(request, songs):
# Create the playlist with the given songs.
# ...
request.user.message_set.create(
message="Your playlist was added successfully."
)
return render_to_response("playlists/create.html",
context_instance=RequestContext(request))
这里在Django1.4之后需要这么写: def create_playlist(request): messages.add_message(request, messages.INFO, 'Your playlist was added successfully') return render(request, "playlists/create.html")
当使用 RequestContext ,当前登录的用户以及他(她)的消息,就会以模板变量 {{ messages }} 出现在模板的context中。
参见: http://t.cn/RhTztXz 1) URLcof里接收参数songs 2) 视图里,使用messages.add_message()方法来创建一条消息. 需要导入包from django.contrib import messages 3) 在模版里 * 使用RequestContext * 传递songs参数供模版使用,**但是不显式提供messages给模版!** * 模版里使用for循环来遍历 4) **使用messages框架,不需要用户登录.** 5) request.user.message_set.creat()方法已经过时.
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
需要注意的是 RequestContext 会在后台调用 get_and_delete_messages ,因此即使你没有显示它们,它们也会被删除掉。
最后注意,这个消息框架只能服务于在用户数据库中存在的用户。 如果要向匿名用户发送消息,请直接使用会话框架。
下一章
是的,会话和认证系统有太多的东西要学。 大多数情况下,你并不需要本章所提到的所有功能。
在下一章 <../chapter15/>__ ,我们会看一下Django的缓存机制,这是一个提高你的网页应用性能的便利的办法。