介绍
几乎每个Web应用程序中都存在用户注册和账户管理功能,用户在发布信息或者共享内容之前需要进行身份验证。在在线论坛和交友网络中用户账户也是必不可少的。因此,这一章我们将介绍用户账户的注册和管理功能,以及Django提供的身份管理系统中的高级功能。
本章我们将学习一下内容:
创建一个登录页面。
提供退出登录功能。
创建一个注册表的。
提供可以让用户自行修改个人信息的功能。
在介绍以上内容的过程中我们将学习Django中的两个重要功能:
模板继承。
表单库。
会话验证
上一章中我们学习了用户数据模型以及如何把用户信息保存到数据库中。实际上用户数据模型是Django提供的一个完整的用户认证管理功能的一部分。用户认证管理系统位于django.contrib.auth包下。在我们通过django-admin.py 创建一个项目的时候这个模块已经被缺省安装了。
你可以再确认一下你是否安装了这个模块,打开settings.py文件查看INSTALLED_APPS变量,这个变量包含了你创建的项目中所有的应用程序,你也许还记得,为了把我们创建的bookmarks应用程序增加到项目中,我们曾经修改过这个文件。最终的代码差不多是这个样子:
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django_bookmarks.bookmarks',
)
将来在你需要在项目中增加新的应用程序的时候,你只要在 INSTALLED_APPS 变量中增加新的包就可以了。根据应用程序的需要你可能还需要通过 python manage.py syncdb 命令将新的数据模型同步到数据库中。这样你就在当前项目中激活了这个应用程序。
在我们开始之前,先来快速的浏览一下用户认证管理系统都提供了哪些功能:
用户:提供了一个用户数据模型,这个模型中包含了应用程序中通常需要包含的用户信息字段。
许可:Yes/No 标记指出是否允许一个用户访问某个功能。
组:提供了一个数据模型,这个模型用户包含一个或多个拥有相同许可的用户。
消息:提供向用户展示信息或者错需消息的功能。
本章我们只使用用户管理功能,其他部分将在以后的章节中介绍。
创建登录页面
在我们分析用户数据模型的时候,我们已经注意到其中包含了一个用户账户。这个账户是在创建项目的时候建立的。所以现在的问题是,“如何登录这个账户”。
这里不得不提到会话控制系统,这是一个web的底层系统(就像PHP中提供的会话控制系统)理解其它比较抽象。在使用会话控制系统时要非常消息,任何一个小小的失误都可能给整个系统带来安全风险。不过庆幸的是,Django已经给我们提供了一个非常完善的会话控制系统,我们不必再为如何管理用户会话和密码而担心。Django已经实现了这些功能,我们只要直接使用就可以了。
为了介绍我们项目中的会话系统,让我们从用户登录页面开始。首先,你需要在urls.py中添加一个URL入口。打开urls.py文件加入新的URL之后文件大概是这样:
from django.conf.urls.defaults import *
from bookmarks.views import *
urlpatterns = patterns('',
(r'^$',main_page),
(r'^user/(\w+)/$',user_page),
(r'^login/$','django.contrib.auth.views.login')
)
这个新增的的URL和以前我们开到的略有不同。我们没有给这个URL指定一个具体的视图函数,而是将Python模型的路径作为字符串代替了视图函数。这是一种便捷当时,通常用于你要引用的函数不在当前的项目目录中。Django会自动把我们需要的函数导入到当前项目中。这个路径的开始部分django.contrib代表的是一个Python包,其中包含了许多Django的扩展。我们的用户认证管理系统就包含在这个包中。 django.contrib.auth.views 这个模块中包含了很多与用户会话管理相关的视图函数,在这里我们只使用了login函数,如函数名字所示,这个函数用于控制用户登录请求。但是如果要看到这个页面,我们需要写一个登录页面模板。
我们需要在registration目录下为login函数创建一个login.html模板。在加载这个模板的时候我们会给它传一个表单对象作为参数,在我们创建用户注册页面的时候会详细介绍什么是表单对象,现在我们只要记住这个对象的名字为form,并且包含以下属性form.username, form.password 和form.has_errors。在页面打开的时候前两个属性会创建用户名和密码文本框,form.has_errors是一个布尔值,如果在提交表单后登录失败,那么这个值为true。
接下来我们需要在templates目录下创建一个文件夹registration,然后再创建一个文件 login.html并输入一下内容:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Django Bookmarks - User Login</title>
</head>
<body>
<h1>User Login</h1>
{%if form.has_errors%}
<p>Your username and password didn't match.
Please try again.</p>
{%endif%}
<form method="post" action=".">
<p><label for="id_username">Username:</label>
{{ form.username }}</p>
<p><label for="id_password">Password:</label>
{{ form.password }}</p>
<input type="hidden" name="next" value="/" />
<input type="submit" value="login" />
</form>
</body>
</html>
上面的代码包含了输入用户名和密码的文本框以及一个提交按钮,隐藏于<input type="hidden" name="next" value="/" /> 包含了,如果成功登录后跳转的页面,这里我们将页面重定向到主页面。
现在一切都准备好了,打开浏览器输入下面的地址http://127.0.0.1:8000/login/,你应该看到类似下面的页面:
还记得在前面第二章中我们创建项目的时候曾经创建过一个用户吗?用那个用户信息进行登录,你应该可以成功登录主页面,然后再试试输入错误的用户名和密码,你应该能够看到一条提示信息(注意如果你使用的是Django1.0那么{%if form.has_errors%}这部分是不会工作的,你应该将它改成{%if form.errors%})。
好了,现在是时候给我们的主页增加些功能了,我们希望在主页上显示当前登录的用户是谁?跟着我修改main_page.html文件:
<html>
<head>
<title>Django Bookmarks</title>
</head>
<body>
<h1>Welcome to Django Bookmarks</h1>
{% if user.username %}
<p>Welcome {{ user.username }}!
Here you can store and share bookmarks!</p>
{% else %}
<p>Welcome anonymous user!
You need to <a href="/login/">login</a>
before you can store and share bookmarks.</p>
{% endif %}
</body>
</html>
在上面的模板文件中,模板文件假定有一个user对象参数,其中保存了用户数据信息,如果这个对象的用户名不为空,则说明用户已经成功登录,则显示欢迎信息,否则显示一个信息提示用户需要登录,并给出登录的链接。好了,现在我们有了这个模板,下面要做的事情是修改一下主页的视图函数,打开bookmarks/views.py 文件,修改函数为一下内容:
def main_page(request):
template = get_template('main_page.html')
variables = Context({ 'user': request.user })
output = template.render(variables)
return HttpResponse(output)
上面的代码加载了主页面并且把一个user对象作为参数传给它,获得user对象的方法是通过 request.user,我们将在后面介绍这种获得user对象的方法 。重新加载页面将显示下面这样更有好的欢迎页面:
你可能已经注意到了,像这种加载模板页面;创建变量;把变量传给模板页面的工作在Django中使用非常频繁,于是Django提供了一种更快捷的方式,让我们重写一下bookmarks/views.py文件:
from django.shortcuts import render_to_response
def main_page(request):
return render_to_response(
'main_page.html',
{ 'user': request.user }
)
django.shortcuts包中的render_to_response 方法简化了以上步骤,这个方法有两个参数模板文件名和包含传给模板文件的变量的字典对象,返回值是一个repsonse对象,这样你通过一行代码就完成了上面多行代码完成的工作。
request.user 返回的对象和我们前面介绍的User对象是一个类型,我们已经知道了User对象的一些字段属性,下面让我们来看看其中包含哪些方法:
is_authenticated()方法返回一个布尔值,用于表明用户是否成功登录。
get_full_name()方法返回用户的全名,姓和名之间用空格隔开。
email_user(subject, message, from_email=None)方法会给用户发一封邮件。
set_password(raw_password)设置当前用户密码。
check_password(raw_password)方法用于检查参数内提供的密码是否与用户的密码匹配。
这些方法通过名字就可以看出他们的用途,这里需要解释一下的是 set_password方法,为什么要单独提供一个方法来设置用户的密码呢,直接设置User对象的密码字段属性不行吗?要回答这个问题,我们需要看一下当前用户密码的内容。打开控制台,输入以下命令:
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(id=1)
>>> user.password
'sha1$e1f02$bc3c0ef7d3e5e405cbaac0a44cb007c3d34c372c'
根据你设置的密码,你得到的密码字符串可能和我的不同,但是不管怎样,他看起来是一连串像是随机数的字符。到底是怎么回事,其实为了安全起见,Django并没有将你的密码存储为纯未必字符串,而是在数据库中存储了你的密码的哈希值,你很难破解这个哈希值的内容,不过通过Django你一样可以验证密码。记住,我说过Django提供的会话管理系统避免了很多错误,密码存储就是其中之一,Django把这一切做得很好。你需要记住的就是当你要修改密码的时候调用set_password方法,而不要直接访问User对象的password属性,这个方法会自动把你的密码转换为哈希值。
提供注销登录功能
现在我们已经创建了登录页面,接下来需要为用户提供注销登录的功能。当用户点击 logout/链接的时候,用户将能够注销登录并返回主页面。
为了实现这个功能,我们首先要在 bookmarks/views.py中增加一个视图函数:
from django.http import HttpResponseRedirect
from django.contrib.auth import logout
def logout_page(request):
logout(request)
return HttpResponseRedirect('/')
我们导入了django.contrib.auth模块,并利用其中的logout方法使用户会话失效,接下来调用HttpResponseRedirect对象将页面重定向到主页,这个对象的参数是一个你要重定向的URL。
现在要做的事情是给这个视图函数配置一个URL入口,打开 urls.py文件输入以下内容:
urlpatterns = patterns('',
(r'^$', main_page),
(r'^user/(\w+)/$', user_page),
(r'^login/$', 'django.contrib.auth.views.login'),
(r'^logout/$', logout_page),
)
好了,现在打开浏览器,先登录然后点击logout链接http://127.0.0.1:8000/logout/,这时候你应该退出当前登录并以匿名用户的身份返回到主页面。为了在页面上增加这个logout链接,我们必须修改以前的页面模板,即使目前我们只要修改很少的页面,但是随着页面的逐渐增加,为了增加一个功能而不得不修改很多页面就变得很麻烦了,因此我们将学习Django中另一个有用的功能,模板继承。
改进模板结构
目前为止我们已经创建了三个模板页面,他们都存在相同的页面结构,唯一区别是标题和内容的不同。要是我们能够把相同的部分提取出来放在一个单独的文件中,那样在需要修改这些相同部分的时候我们只有修改一个文件就可以了。
幸运的是,Django已经为我们提供了这样的功能,成为模板继承。模板继承的设计原理很简单,我们提取系统中结构相同的元素,然后把他们放在一个基础(base)模板中,然后我们在这个基础模板中定义一些块(blocks),这些块可以被继承他的子模板(child)修改。然后我们就可以定义一些子模板来继承(extends )这个基础模板,并根据子模板的需要修改这些块的内容。这种设计非常类似于面向对象编程语言中的对象继承。
好了,现在让我们将这一技术应用在当前的项目中。我们在templates目录下创建一个名为base.html的文件,并输入如下内容:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Django Bookmarks | {% block title %}{% endblock %}</title>
</head>
<body>
<h1>{% block head %}{% endblock %}</h1>
{% block content %}{% endblock %}
</body>
</html>
在我们刚才创建的模板中使用了一个新的标签 block,这个标签用于定义一个段落,这个段落可以被继承他的子模板修改。我们创建的模板中包含三个段落标签,一个用于定义页面标题,其他的两个是定义页面的主题和页面内容段落。
现在我们看看如何使用新的标签,打开 templates/main_page.html 文件用下面的代码替换原来的内容:
{% extends "base.html" %}
{% block title %}Welcome to Django Bookmarks{% endblock %}
{% block head %}Welcome to Django Bookmarks{% endblock %}
{% block content %}
{% if user.username %}
<p>Welcome {{ user.username }}!
Here you can store and share bookmarks!</p>
{% else %}
<p>Welcome anonymous user!
You need to <a href="/login/">login</a>
before you can store and share bookmarks.</p>
{% endif %}
{% endblock %}
上面代码的第一行,我们使用了extends标签来继承base.html,这表明main_page.html是base.html的子模板。他继承了base.html中的所有元素并根据需要修改段落(block)中的内容。重新定义子模板中的块和在基础模板中定义块是一样的, main_page.html 中不再包含基本的HTML结构,它只是重新定义了它希望重用的部分。
利用同样的方法,我们再将templates/user_page.html页面也重写一下,让他继承基础页面(base.html):
{% extends "base.html" %}
{% block title %}{{ username }}{% endblock %}
{% block head %}Bookmarks for {{ username }}{% endblock %}
{% block content %}
{% if bookmarks %}
<ul>
{% for bookmark in bookmarks %}
<li><a href="{{ bookmark.link.url }}">
{{ bookmark.title }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No bookmarks found.</p>
{% endif %}
{% endblock %}
user_page.html中的变化和main_page.html中的类似,我们重新定义了需要动态改变的内容。最后我们来修改一下templates/registration/login.html页面,让他也继承base.html页面:
{% extends "base.html" %}
{% block title %}User Login{% endblock %}
{% block head %}User Login{% endblock %}
{% block content %}
{% if form.has_errors %}
<p>Your username and password didn't match.
Please try again.</p>
{% endif %}
<form method="post" action=".">
<p><label for="id_username">Username:</label>
{{ form.username }}</p>
<p><label for="id_password">Password:</label>
{{ form.password }}</p>
<input type="submit" value="login" />
<input type="hidden" name="next" value="/" />
</form>
{% endblock %}
现在我们的站点有了统一的基础模板,是时候改进一下站点的可用性和外观了。我们先从给站点页面添加CSS修饰开始。CSS文件和图片属于静态文件,Django没有提供对这些的文件的支持,在最终的产品环境中这些文件的维护由web方服务器负责。因为我们目前使用的是开发服务器,一个临时的解决方案是创建一个工作区来维护这些静态文件。
打开urls.py文件,输入以下内容:
import os.path
from django.conf.urls.defaults import *
from bookmarks.views import *
site_media = os.path.join(
os.path.dirname(__file__), 'site_media'
)
urlpatterns = patterns('',
(r'^$', main_page),
(r'^user/(\w+)/$', user_page),
(r'^login/$', 'django.contrib.auth.views.login'),
(r'^logout/$', logout_page),
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
{ 'document_root': site_media }),
)
我们刚才定义的入口将 site_media 目录下所有的URL绑定到django.views.static.serve视图函数上。与前面定义URL入口的方式有所不同,这个新增的部分出现了第三个参数,一些视图函数可以有附加的可以选字典对象参数。我们通过这种方式告诉Django静态文件存储的位置。
接下来在项目目录下创建一个site_media文件夹,然后在其中创建一个空的style.css文件。现在我们修改 templates/base.html 文件将新增的CSS加入到base.html文件中,并创建一个导航菜单:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Django Bookmarks |
{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="/site_media/style.css"
type="text/css" />
</head>
<body>
<div id="nav">
<a href="/">home</a> |
{% if user.is_authenticated %}
welcome {{ user.username }}
(<a href="/logout">logout</a>)
{% else %}
<a href="/login/">login</a>
{% endif %}
</div>
<h1>{% block head %}{% endblock %}</h1>
{% block content %}{% endblock %}
</body>
</html>
现在我们需要调整一下导航菜单的位置,打开style.css样式文件,并输入下面的内容:
#nav {
float: right;
}
现在在看一下页面,你会发现大部分页面都满足了我们的需求,只是当你打开user_page视图的时候,你会发现,无论你是否登录页面上都会显示一个login的链接。这是因为在base.html中if语句判断user对象是否已经验证登录。但是我们并没有给user_page.html传递user对象,有两个办法可以解决这个问题:
编辑 user_page 视图函数,给他传递一个user对象。
使用RequestContext对象,这个对象和Context对象稍有不同,它会自动加user对象(还有其他的一些对象)传递给模板。为了实现对象的传递,RequestContext对象的第一个参数是request,一个包含模板变量对象的字典对象作为它的第二个参数。
我们将采用第二种方法,因为这种方法更清晰,而且在将来我们可能要给每个页面传递user对象。而且这种方法也利于我们提前通用代码并减少代码录入。
我们将修改 main_page 和 user_page函数,让他们使用RequestContext对象,打开 bookmarks/views.py文件,找到 main_page 函数并把它改成下面这样:
from django.template import RequestContext
def main_page(request):
return render_to_response(
'main_page.html', RequestContext(request)
)
如你所见,我们不再将 request.user 作为变量传递给模板,我们改用RequestContext对象来管理这一切。现在用同样的方法重写 user_page 函数:
def user_page(request, username):
try:
user = User.objects.get(username=username)
except:
raise Http404('Requested user not found.')
bookmarks = user.bookmark_set.all()
variables = RequestContext(request, {
'username': username,
'bookmarks': bookmarks
})
return render_to_response('user_page.html', variables)
现在整个代码看起来更紧凑了,虽然我们花了一些时间重构代码,但是从长远来讲新的代码结构是由好处的。
目前为止,我们为项目提供了登录和注销功能,在下一节中我们将讲述用户的注册功能。
用户注册
我们在创建项目的时候增加了第一个账户,然后站点的访问者希望能够创建自己的账户。如今几乎所有的社区站点都提供用户注册的功能,本节我们将创建一个表单用于提供用户注册功能,并通过它学习Django是如何控制表单的输出和处理的。
Django表单
在Web应用程序中创建,验证和处理表单是再平常不过的事情了。Web应用程序通过用户输入表单来收集整理数据,所以Django提供了一套函数库来处理这些任务。在Django0.96中的newforms库,在Django1.0中被替换为forms库,为了避免版本升级过程中带来的大量改变强烈建议通过以下方式导入包:
from django import newforms as forms
通过这种方式当Django升级为1.0之后你只要将包的导入方式改为一下这种方式就可以了:
from django import forms
我们介绍的这种包的导入方式将前面的包重新命名为forms,通过这种方式开发人员可以轻松的在Django的不同版本之间切换。
Django的forms包负责处理3中常归任务:
- HTML表单的输出。
- 用户输入的服务器端校验。
- 当校验出现错误时,表单信息的重新显示。
设计用户注册表单
现在我们开始创建表单对象,首先在bookmarks文件夹下创建 forms.py.文件,然后输入以下代码:
from django import newforms as forms
class RegistrationForm(forms.Form):
username = forms.CharField(label='Username', max_length=30)
email = forms.EmailField(label='Email')
password1 = forms.CharField(
label='Password',
widget=forms.PasswordInput()
)
password2 = forms.CharField(
label='Password (Again)',
widget=forms.PasswordInput()
)
仔细看看代码你会发现定义表单类的方式和定义数据模型类的方式非常相似。我们让RegistrationForm类继承forms.Form类,所有的表单类都需要继承forms.Form类。接下来我们定义了表单包含的字段,forms中包含了很多字段类型,我们根据需要为每个字段指定不同的类型,如你所见,每个代表字段类型的方法都有若干个参数,他们都用于描述这个表单字段的特性:
label:用于在HTML中输出字段的标签。
required:代表这个字段是否属于必填字段,默认情况下所有的字段都是必填字段,除非你通过required=False设置为可选字段。
widget:这个属性用于控制当前这个字段在HTML中的显示方式,即使用那种窗口部件。比如上面的代码中我们将密码字段指定为CharField(这表示它是一个字符串),然后通过widget=forms.PasswordInput()参数指定在HTML中它应该显示为一个密码输入框。
help_text:为字段定义一个帮助信息,当输出HTML的时候会显示。
一下列出表单中使用的常用字段类型:
字段类型 | 描述 |
CharField | 返回字符串 |
IntegerField | 返回一个整数 |
DateField | 返回Python的datetime.date 对象类型 |
DateTimeField | 返回Python的datetime.datetime对象类型 |
EmailField | 返回一个有效的Email字符串 |
URLField | 返回一个有效的URL字符串 |
一下列出部分窗口部件类型:
部件类型 | 描述 |
PasswordInput | 一个密码输入框 |
HiddenInput | 一个隐藏字段 |
Textarea | 一个文本输入域 |
FileInput | 一个文件上传组件 |
我们可以通过一些练习来学习表单对象的API,请打开Python交互式控制台,并输入下面的内容:
$ python manage.py shell
>>> from bookmarks.forms import *
>>> form = RegistrationForm()
这样我们就有了一个RegistrationForm类的实例,现在让我们看看他是如何输出HTML的:
>>> print form.as_table()
执行上面的命令你会看到一串HTML代码,我们刚才定义的表单字段被放在一个HTML表格中,你还可以使用form.as_ul()和form.as_p()方法输出HTML,原理是一样的,他们分别把表单以ul(列表)和p(换行)的方式显示出来。
另外,你还可以通过这种方式查看某一个字段的HTML代码输出:
>>> print form['username']
<input id="id_username" type="text" name="username" maxlength="30" />
现在我们已经知道了表单是怎么输出HTML代码的,现在看看表单是如何验证的,我们通过初始化RegistrationForm对象参数的方式创建一个用户注册表单对象:
>>> form = RegistrationForm({
... 'username': 'test',
... 'email': 'test@example.com',
... 'password1': 'test',
... 'password2': 'test'
... })
>>> form.is_valid()
True
这里的form.is_valid()方法返回True说明我们输入的所有字段信息都是合法的,现在我们试试输入一些不合法的值:
>>> form = RegistrationForm({
... 'username': 'test',
... 'email': 'invalid email',
... 'password1': 'test',
... 'password2': 'test'
... })
>>> form.is_valid()
False
>>> form.errors
{'email': [u'Enter a valid e-mail address.']}
Django为我们完成了表单的验证,如果你没有为每个字段都赋值,你会得到类似的验证失败信息,因为缺省情况下所有的字段值都是必填的。如果你想知道一个表单对象中是否有数据,可以使用form.is_bound 方法,如果你尝试对一个不包含数据的表单对象进行校验,你会得到一个异常。你可以通过 form.data属性访问表单的数据,如果表单数据通过了验证,你可以通过form.clean_data(在Django1.0中这个属性是form.cleaned_data)方法已经校验过的数据。
现在你已经了解了forms.Form对象,我们还需要增强表单对象的验证,虽然现在的验证逻辑可以保证表单中的字段不能为空、必须输入有效的Email地址,但我们还想增加其他的验证规则:
确保用户不输入了无效的用户名或者注册一个已经存的用户名。
确保两次输入的密码是相同的。
让我们先从密码验证开始,因为这个相对简单一些。打开 bookmarks/forms.py文件在RegistrationForm类中增加以下代码:
def clean_password2(self):
if 'password1' in self.clean_data:
password1 = self.clean_data['password1']
password2 = self.clean_data['password2']
if password1 == password2:
return password2
raise forms.ValidationError('Passwords do not match.')
然我们来逐行解释以上代码:
方法名是 clean_password2,自定义验证方法总是遵循clean加字段名 clean_fieldname 的规则。
首先取得password1的值,如果用户输入的值是有效的,那么可以通过self.clean_data['password1']获得。password2的值用同样的方法获得。
接下来我们判断两个密码值是否相等,如果相等就返回password2的值。
如果两个密码值不相等,我们抛出一个 forms.ValidationError类型的异常。这个类型接收一个异常消息字符串作为参数。
给密码加上验证规则后,我们来给用户名增加验证规则,打开bookmarks/forms.py文件并导入下面的Python包:
import re
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
其中re是Python正则表达式的库包,我们需要用它来确保用户名中没有非法字符。我们同时还导入了User用户模型,以便检验用户是否输入的是一个已经存在的用户名。最后我们导入ObjectDoesNotExist,这是一个异常类型用于当无法找到数据对象时抛出异常。
接下来将下面的代码加入到RegistrationForm中:
def clean_username(self):
username = self.clean_data['username']
if not re.search(r'^\w+$', username):
raise forms.ValidationError('Username can only contain alphanumeric characters and the underscore.')
try:
User.objects.get(username=username)
except ObjectDoesNotExist:
return username
raise forms.ValidationError('Username is already taken.').
我们再来看看每行代码都是什么含义:
首先我们通过正则表达式^\w+$来检查用户名中是否有非法字符,我们要求用户名必须是字母数字或者下划线。我们在这里使用了re.search,如果它返回的是None,说明用户名和我们要求的模式不匹配。
如果用户名不包含非法字符,那么我们通过User对象来查看现在是否已经存在一个同名的用户,如果没有(ObjectDoesNotExist)说明这是一个新的用户名,可以被使用,那么我们直接返回当前用户名,否则就抛出“用户名已经存在”的异常。
如果你愿意可以向我们前面做的那样,使用Python交互式命令行,在RegistrationForm中加入非法数据然后通过.errors看看是否按照预期显示了异常信息。你还可以给你的email 加上验证规则,比如一个email只能被一个用户名使用,只要按照clean_加字段名的方式就可以了。
我们现在已经有了注册表单,但是还缺少视图函数和模板。让我们先从创建视图函数开始,打开 bookmarks/views.py 文件,插入如下代码:
from bookmarks.forms import *
def register_page(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username=form.clean_data['username'],
password=form.clean_data['password1'],
email=form.clean_data['email']
)
return HttpResponseRedirect('/')
else:
form = RegistrationForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response(
'registration/register.html',
variables
)
以上代码完成两件事情:
首先判断用户的请求类型是什么,如果是POST,说明用户是提交表单,那么就收集用户输入信息,如果一切正常就重定向到主页面。
否则就将表单的HTML代码输出到 registration/register.html模板中。
我们通过 request.POST对象来就收用户输入的数据,这个对象是一个字典对象,它包含了用户输入的数据,我们通过调用RegistrationForm(request.POST)将它作为参数传给RegistrationForm对象,这样就可以将POST中的用户输入信息填充到我们定义的RegistrationForm对象中,然后我们就可以对表单进行验证,如果一切顺利将页面重新定向到主页。
你应该已经注意到了,我们这里创建用户时使用了 User.object.create_user方法,使用这个方法是因为它可以直接将我们输入的代码转换成哈希值,这使用起来更方便。
如果表单验证失败,表单信息还是会传给注册模板页面,并提供有用的帮助信息,告诉用户哪个字段验证失败了。
好了,现在我们来定义模板文件。创建一个新文件templates/registration/register.html加入如下代码:
{% extends "base.html" %}
{% block title %}User Registration{% endblock %}
{% block head %}User Registration{% endblock %}
{% block content %}
<form method="post" action=".">
{{ form.as_p }}
<input type="submit" value="register" />
</form>
{% endblock %}
你有没有注意到,视图函数和模板都非常紧凑简洁?这一切得益于Django强大的表单功能。在我们的模板文件中我们只是简单的使用 form.as_p显示用户数据,并增加了一个提交按钮。
最后,为了测试我们新增的用户注册功能,需要给他添加一个URL入口,打开urls.py文件输入如下内容:
(r'^register/$', register_page),
好了,完成了。现在启动开发服务器,打开浏览器输入下面的地址http://127.0.0.1:8000/register/ ,你应该看到用户注册页面,而且功能也如我们预期的那样,但是页面看起来有点凌乱,让我稍微修整一下,打开site_media下的style.css文件,输入下面的内容:
input {
display: block;
}
现在页面看起来好多了:
现在试着输入一些非法的信息,你将会看到自动输出的错误提示信息。好了,我们有了一个新的注册连接,你也许想把它加入到主页面的菜单中,那么打开templates/base.html为文件,加入如下内容:
<div id="nav">
<a href="/">home</a> |
{% if user.is_authenticated %}
welcome {{ user.username }}
(<a href="/logout">logout</a>)
{% else %}
<a href="/login/">login</a> |
<a href="/register/">register</a>
{% endif %}
</div>
最后一件事,如果用户注册成功后系统显示注册成功的消息页面不是很好吗?这个页面中没有什么动态内容,就是一个静态的模板文件,对这种不需要处理动态内容的模板,Django在django.views.generic.simple包下direct_to_template 方法,好了,现在让我们实现这个页面。
创建一个显示注册成功的页面 templates/registration/register_success.html ,输入如下内容:
{% extends "base.html" %}
{% block title %}Registration Successful{% endblock %}
{% block head %}
Registration Completed Successfully
{% endblock %}
{% block content %}
Thank you for registering. Your information has been
saved in the database. Now you can either
<a href="/login/">login</a> or go back to the
<a href="/">main page</a>.
{% endblock %}
接下来我们在urls.py文件中导入下面的包:
from django.views.generic.simple import direct_to_template
然后定义URL入口:
(r'^register/success/$', direct_to_template,
{ 'template': 'registration/register_success.html' }),
在上面的URL定义中我们在第三个参数总用字典对象定义了传输给视图函数的参数。最后修改register_page 视图函数,找到return HttpResponseRedirect('/')把它替换为:
return HttpResponseRedirect('/register/success/')
做完这些后我们测试一下,打开浏览器输入下面的链接:
http://127.0.0.1:8000/register/success/
当你成功注册一个用户后你会看到一个提示注册成功的页面。
账户管理
目前为止,我们已经实现了用户的会话管理和注册功能。现在我们希望用户可以更新自己的账户信息,比如密码和电子邮件。为了实现这一功能,我们可以:
我们可以直接使用Django提供的通用账户管理功能。
或者像定义注册表单那样,我们自己实现处理更新用户账户的功能。
我们将看到如何通过这两种方式实现,每种方式都有它的好处和坏处。很显然如果你自己实现这一功能,可操控性更强,不过你需要写更多的代码。另一方面,如果使用Django提供的视图更加方便快捷,不过损失的是灵活性。最终的决定取决于你的需求。
我将在下面总结 django.contrib.auth 包中提供的视图,每一个视图都需要一个模板,并且给这些模板传递一些变量。这些视图内部对输入信息进行维护,你不用操心。以下列出了 django.contrib.auth.views 中的视图函数:
logout:退出当前登录并显示一个模板页。
logout_then_login:退出当前登录并重定向到登录页面。
password_change:允许用户修改他们的密码。
password_change_done:用于在密码修改后的调用。
password_reset:允许用户重新设置密码,并通过邮件接收一个新的密码。
password_reset_done:用于重新设置密码后调用。
redirect_to_login:重定向到登录页面。
这些视图函数的使用方法和我们前面介绍的login函数类似,如果感兴趣可以参考Django的在线文档 http://www.djangoproject.com/documentation/0.96/authentication/
介绍完以上内容,我们准备结束本章的内容。现在你已经基本理解了Django用户身份管理系统,将来你也可以在自己的项目中实现用户会话管理和用户注册的功能了。
总结
本章你学到了如何使用Django提供的用户身份管理系统。这个系统提供的功能囊括了会话管理和账户管理。你使用这套系统在我们的标签分享应用程序中实现了用户注册和登录验证的功能。你还在这一过程中学到了Django的表单对象,并使用它创建了用户注册表单,并为这个表单增加了验证功能。你还学到了模板之间的继承功能,这是一个非常重要而又用的功能,利用它你可以建立一个结构清晰易于维护和重用的站点。
下面总结了本章学到的Django的功能:
当前登录的用户对象User,可以通过 request.user 获得,它是通过HttpRequest对象提供给视图函数的。
由于加载模板,装饰模板并把它封装到HttpResponse对象中是一种常用的功能,Django提供了 render_to_response的快捷方式,这个函数位于 django.shortcuts 包中。
不要通过访问 user.password 属性直接设置用户密码,而是应该使用 user.set_passwor方法,这个方法会将密码转换成哈希值。
要装饰一个表单对象,可以调用 as_table, as_p, 或者 as_ul方法。
要向表单中填充数据,可以将数据放入一个字典对象中并在构造表单对象时传入这个字典对象。可以通过 is_valid()方法和errors 属性来检查表单数据的合法性。用户输入数据可以通过form.data属性获得,已经验证合法的用户输入数据可以通过 form.clean_data 属性获得(Django1.0中是form.cleaned_data)
我们可以自定义表单字段的校验规则,只要按照 clean_fieldname的方式定义方法就可以了,记住如果校验成功方法返回校验后的字段值,否则要抛出 forms.ValidationError异常。
可以通过HttpResponseRedirect对象将用户从一个视图重定向到另一个视图,这个对象的参数是要重定向的目的视图的路径。
下一章我们将给现在的应用程序增加更多的功能。我们将创建一个表单让用户自己将书签保持到站点上。这个表单将通过加“书签”的方式让用户对自己的标签内容进行整理。此外,我们还将利用书签集的方式让用户浏览数据库中的热门书签。下一章将包含很多有意思的内容,所以跟着我一起学习吧。
No comments:
Post a Comment