4 创建一个社交网站
在上一章中,你学习了如何创建站点地图和订阅,并且为博客应用构建了一个搜索引擎。在这一章中,你会开发一个社交应用。你会为用户创建登录,登出,编辑和修改密码的功能。你会学习如何为用户创建自定义的个人资料,并在网站中添加社交认证。
本章会涉及以下知识点:
User
模型
python-social-auth
添加社交认证
让我们从创建新项目开始。
4.1 创建一个社交网站项目
我们将会创建一个社交应用,让用户可以分享他们在Internet上发现的图片。我们需要为该项目构建以下元素:
本章讨论第一点。
4.1.1 启动社交网站项目
打开终端,使用以下命令为项目创建一个虚拟环境,并激活:
mkdir env
virtualenv env/bookmarks
source env/bookmarks/bin/activate
终端会如下显示你激活的虚拟环境:
(bookmarks)laptop:~ zenx$
使用以下命令,在虚拟环境中安装Django:
pip install Django
执行以下命令创建一个新项目:
django-admin startproject bookmarks
创建初始项目结构之后,使用以下命令进入项目目录,并创建一个account的新应用:
cd bookmarks/
django-admin startapp account
通过把该应用添加到settings.py文件的INSTALLED_APPS中,来激活它。把它放在INSTALLED_APPS列表的最前面:
INSTALLED_APPS = (
'account',
# ...
执行下面的命令,同步INSTALLED_APPS设置中默认应用的模型到数据库中:
python manage.py migrate
接下来,我们用authentication框架在项目中构建一个认证系统。
4.2 使用Django认证框架
Django内置一个认证框架,可以处理用户认证,会话,权限和用户组。该认证系统包括常见的用户操作视图,比如登录,登出,修改密码和重置密码。
认证框架位于django.contrib.auth中,并且被其它Django contrib包使用。记住,你已经在第一章中使用过认证框架,为博客应用创建了一个超级用户,以便访问管理站点。
当你使用startproject命令创建新Django项目时,认证框架已经包括在项目的默认设置中。它由django.contrib.auth应用和以下两个中间件(middleware)类组成(这两个中间类位于项目的MIDDLEWARE_CLASSES设置中):
AuthenticationMiddleware:使用会话管理用户和请求
SessionMiddleware:跨请求处理当前会话
一个中间件是一个带有方法的类,在解析请求或响应时,这些方法在全局中执行。你会在本书的好几个地方使用中间件类。你会在第13章学习如何创建自定义的中间件。
该认证框架还包括以下模块:
User:一个有基础字典的用户模型;主要字段有:username,password,email,first_name,last_name和is_active。
Group:一个用于对用户分类的组模型。
Permission:执行特定操作的标识。
该框架还包括默认的认证视图和表单,我们之后会学习。
4.2.1 创建登录视图
我们从使用Django认证框架允许用户登录网站开始。我们的视图要执行以下操作来登录用户:
通过提交表单获得用户名和密码。
对比数据库中的数据,来验证用户。
检查用户是否激活。
用户登录,并开始一个认证的会话(authenticated session)。
首先,我们将创建一个登录表单。在account应用目录中创建forms.py文件,添加以下代码:
from django import forms
class LoginForm(forms.Form):
username = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput)
该表单用于在数据库用验证用户。注意,我们使用PasswordInput组件来渲染包括type="password"属性的HTML input元素。编辑account应用的views.py文件,添加以下代码:
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth import authenticate, login
from .forms import LoginForm
def user_login(request):
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
user = authenticate(username=cd['username'],
password=cd['password'])
if user is not None:
if user.is_active:
login(request, user)
return HttpResponse('Authenticated successfully')
else:
return HttpResponse('Disabled account')
else:
return HttpResponse('Invalid login')
else:
form = LoginForm()
return render(request, 'account/login.html', {'form': form})
这是我们在视图中所做的基本登录操作:当使用GET请求调用user_login视图时,我们使用form = LoginForm()实例化一个新的登录表单,用于在模板中显示。当用户通过POST提交表单时,我们执行以下操作:
使用form = LoginForm(request.POST)实例化带有提交的数据的表单。
检查表单是否有效。如果无效,则在模板中显示表单错误(例如,用户没有填写某个字段)。
如果提交的数据有效,我们使用authenticate()方法,在数据库中验证用户。该方法接收username和password参数,如果用户验证成功,则返回User对象,否则返回None。如果用户没有通过验证,我们返回一个原始的HttpResponse,显示一条消息。
如果用户验证成功,我们通过is_active属性检查用户是否激活。这是Django User模型的属性。如果用户没有激活,我们返回一个HttpResponse显示信息。
如果是激活的用户,我们在网站登录用户。我们调用login()方法,把用户设置在session中,并返回一条成功消息。
注意authenticate和login之间的区别:authenticate()方法检查用户的认证信息,如果正确,则返回User对象;login()在当前session中设置用户。
现在,你需要为该视图创建URL模式。在account应用目录中创建urls.py文件,并添加以下代码:
from django.conf.urls import url
from . import views
urlpatterns = [
# post views
url(r'^login/$', views.user_login, name='login'),
编辑bookmarks项目目录中的urls.py文件,在其中包括account应用的URL模式:
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^account/', include('account.urls')),
现在可以通过URL访问登录视图了。是时候为该视图创建一个模板了。因为该项目还没有模板,所以你可以创建一个基础模板,在登录模板中扩展它。在account应用目录中创建以下文件和目录:
templates/
account/
login.html
base.html
编辑base.html文件,添加以下代码:
{% load staticfiles %}
<!DOCTYPE html>
<title>{% block title %}{% endblock %}</title>
<link href="{% static "css/base.css" %}" rel="stylesheet">
</head>
<div id="header">
<span class="logo">Bookmarks</span>
<div id="content">
{% block content %}
{% endblock %}
</body>
</html>
这是网址的基础模板。跟之前的项目一样,我们在主模板中包括CSS样式。该基础模板定义了title和content区域,可以被从它扩展的模板填充内容。
让我们为登录表单创建模板。打开account/login.html模板,添加以下代码:
{% extends "base.html" %}
{% block title %}Log-in{% endblock %}
{% block content %}
<h1>Log-in</h1>
<p>Please, user the following form to log-in</p>
<form action="." method="post">
{{ form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Log-in"></p>
</form>
{% endblock %}
该模板包括了在视图中实例化的表单。因为我们的表单会通过POST提交,所以我们使用{% csrf_token %}模板标签进行CSRF保护。你在第2章学习了CSRF保护。
现在数据库中还没有用户。首先,你需要创建一个超级用户,访问管理站点来管理其他用户。打开命令行,执行python manage.py createsuperuser。填写必需的用户名,邮箱和密码。然后使用python manage.py runserver启动开发服务器,并在浏览器中打开http://127.0.0.1:8000/admin/。使用你刚创建的用户登录管理站点。你会看到Django管理站点中包括了Django认证框架的User和Group模型,如下图所示:
4.2.2 使用Django认证视图
Django在认证框架中包括了几个表单和视图,你可以直接使用。你已经创建的登录视图对于理解Django中的用户认证过程是一个很好的练习。然而,你在绝大部分情况下可以使用默认的Django认证视图。
Django提供了以下视图处理认证:
login:操作一个登录表单,并登录用户
logout:登出一个用户
logout_then_login:登出一个用户,并重定向用户到登录页面
Django提供以下视图处理修改密码:
password_change:操作一个修改用户密码的表单
password_change_done:修改密码后,显示成功页面
Django还提供以下视图用于重置密码:
password_reset:允许用户重置密码。它生成一个带令牌的一次性链接,并发送到用户的电子邮箱中。
password_reset_done:告诉用户,重置密码的邮件已经发送到他的邮箱中。
password_reset_confirm:让用户设置新密码。
password_reset_complete:用户重置密码后,显示成功页面。
创建一个带用户账户的网站时,这里列出的视图会节省你很多时间。你可以覆盖这些视图使用的默认值,比如需要渲染的模板的位置,或者视图使用的表单。
你可以在这里获得更多关于内置的认证视图的信息。
4.2.3 登录和登出视图
编辑account应用的urls.py文件,如下所示:
from django.conf.urls import url
from django.contrib.auth.views import login, logout, logout_then_login
from . import views
urlpatterns = [
# previous login view
# url(r'^login/$', views.user_login, name='login'),
# login / logout urls
url(r'^login/$', login, name='login'),
url(r'^logout/$', logout, name='logout'),
url(r'^logout-then-login/$', logout_then_login, name='logout_then_login'),
译者注:Django新版本中,URL模式使用方式跟旧版本不一样。
我们注释了之前为user_login视图创建的URL模式,使用了Django认证框架的login视图。
在account应用的templates目录中创建一个registration目录。这是Django认证视图的默认路径,它期望你的认证模板在这个路径下。在新创建的目录中创建login.html文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Log-in{% endblock %}
{% block content %}
<h1>Log-in</h1>
{% if form.errors %}
Your username and password didn't match.
Please try again.
{% else %}
<p>Please, user the following form to log-in</p>
{% endif %}
<div class="login-form">
<form action="{% url 'login' %}" method="post">
{{ form.as_p }}
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}" />
<p><input type="submit" value="Log-in"></p>
</form>
{% endblock %}
这个login模板跟我们之前创建那个很像。Django默认使用django.contrib.auth.forms中的AuthenticationForm。该表单尝试验证用户,如果登录不成功,则抛出一个验证错误。这种情况下,如果认证信息出错,我们可以在模板中使用{% if form.errors %}查找错误。注意,我们添加了一个隐藏的HTML <input>元素,用于提交名为next的变量的值。当你在请求中传递一个next参数时(比如,http://127.0.0.1:8000/account/login/?next=/account/),这个变量首次被登录视图设置。
next参数必须是一个URL。如果指定了这个参数,Django登录视图会在用户登录后,重定义到给定的URL。
现在,在registration模板目录中创建一个logged_out.html模板,添加以下代码:
{% extends "base.html" %}
{% block title %}Logged out{% endblock %}
{% block content %}
<h1>Logged out</h1>
<p>You have been successfully logged out. You can <a href="{% url "login" %}">log-in again></a>.</p>
{% endblock %}
用户登出之后,Django会显示这个模板。
为登录和登出视图添加URL模式和模板后,网站已经可以使用Django认证视图登录了。
注意,我们在urlconf中包含的logout_then_login视图不需要任何模板,因为它重定义到了登录视图。
现在我们开始创建一个新的视图,当用户登录账号时,用于显示用户的仪表盘。打开account应用的views.py文件,添加以下代码:
from django.contrib.auth.decorators import login_required
@login_required
def dashboard(request):
return render(request,
'account/dashboard.html',
{'section': 'dashboard'})
我们用认证框架的login_required装饰器装饰视图。该装饰器检查当前用户是否认证。如果是认证用户,它会执行被装饰的视图。如果不是认证用户,它会重定向用户到登录URL,并在登录URL中带上一个名为next的GET参数,该参数是用户试图访问的URL。通过这样的做法,当用户成功登录后,登录视图会重定向用户到用户登录之前试图访问的页面。记住,我们在登录模板的表单中添加了一个隐藏的<input>元素就是为了这个目的。
我们还定义了一个section变量。我们用这个变量跟踪用户正在查看网站的哪一部分(section)。多个视图可能对应相同的部分。这是定义每个视图对应的section的简便方式。
现在,你需要为仪表盘视图创建一个模板。在templates/account/目录下创建dashboard.html文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block content %}
<h1>Dashboard</h1>
<p>Welcome to your dashboard.</p>
{% endblock %}
接着,在account应用的urls.py文件中,为该视图添加URL模式:
urlpatterns = [
# ...
url(r'^$', views.dashboard, name='dashboard'),
编辑项目的settings.py文件,添加以下代码:
from django.core.urlresolvers import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('dashboard')
LOGIN_URL = reverse_lazy('login')
LOGOUT_URL = reverse_lazy('logout')
这些设置是:
LOGIN_REDIRECT_URL:告诉Django,如果contrib.auth.views.login视图没有获得next参数时,登录后重定向到哪个URL
LOGIN_URL:重定向用户登录的URL(比如使用login_required装饰器)
LOGOUT_URL:重定向用户登出的URL
我们使用reverse_lazy(),通过URL的名字动态创建URL。reverse_lazy()函数跟reverse()函数一样逆向URL。当你需要在项目URL配置加载之前逆向URL时,可以使用reverse_lazy()。
让我们总结一下,到现在为止,我们做了哪些工作:
你在项目中添加了内置的Django认证登录和登出视图
你为这两个视图创建了自定义模板,并定义了一个简单的视图,让用户登录后重定向到这个视图
最后,你配置了设置,让Django默认使用这些URL
现在,我们需要把登录和登出链接到基础模板中,把所有功能串起来。
要做到这点,我们需要确定,无论当前用户是否登录,都能显示适当的链接。通过认证中间件,当前用户被设置在HttpRequest对象中。你可以通过request.user访问。即使用户没有认证,你也可以找到一个用户对象。一个未认证的用户在request中是一个AnonymousUser的实例。调用request.user.is_authenticated()是检测当前用户是否认证最好的方式。
编辑base.html文件,修改ID为header的<div>,如下所示:
<div id="header">
<span class="logo">Bookmarks</span>
{% if request.user.is_authenticated %}
<ul class="menu">
<li {% if section == "dashboard" %}class="selected"{% endif %}>
<a href="{% url "dashboard" %}">My dashboard</a>
<li {% if section == "images" %}class="selected"{% endif %}>
<a href="#">Images</a>
<li {% if section == "people" %}class="selected"{% endif %}>
<a href="#">People</a>
{% endif %}
<span class="user">
{% if request.user.is_authenticated %}
Hello {{ request.user.first_name }},
<a href="{% url "logout" %}">Logout</a>
{% else %}
<a href="{% url "login" %}">Log-in</a>
{% endif %}
</span>
正如你所看到的,我们只为认证的用户显示网站的菜单。我们还检查当前的section,通过CSS为相应的<li>项添加selected类属性来高亮显示菜单中的当前section。我们还显示用户的姓,如果是认证过的用户,还显示一个登出链接,否则显示登录链接。
现在,在浏览器中打开http://127.0.0.1:8000/account/login。你会看到登录页面。输入有效的用户名和密码,点击Log-in按钮,你会看到这样的页面:
在这个页面中,用户已经登出,所以你不能再看到网站的菜单。现在头部右边显示Log-in链接。
如果你看到的是Django管理站点的登出页面,而不是你自己的登出页面,检查项目的INSTALLED_APPS设置,确保django.contrib.admin在account应用之后。这两个模板位于同样的相对路径中,Django目录加载器会使用第一个。
4.2.4 修改密码视图
用户登录我们的网站后,我们需要用户可以修改他们的密码。我们通过集成Django认证视图来修改密码。打开account应用的urls.py文件,添加以下URL模式:
from django.contrib.auth.views import password_change
from django.contrib.auth.views import password_change_done
# change password urls
urlpatterns = [
url(r'^password-change/$', password_change, name='password_change'),
url(r'^password_change/done/$', password_change_done, name='password_change_done'),
password_change视图会处理修改密码表单,password_change_done会在用户成功修改密码后显示一条成功消息。让我们为每个视图创建一个模板。
在account应用的templates/registration/目录中创建password_change_form.html文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Change you password{% endblock %}
{% block content %}
<h1>Change you password</h1>
<p>Use the form below to change your password.</p>
<form action="." method="post">
{{ form.as_p }}
<p><input type="submit" value="Change"></p>
{% csrf_token %}
</form>
{% endblock %}
该模板包括修改密码的表单。在同一个目录下创建password_change_done.html文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Password changed{% endblock %}
{% block content %}
<h1>Password changed</h1>
<p>Your password has been successfully changed.</p>
{% endblock %}
该模板只包括一条用户成功修改密码后显示的成功消息。
在浏览器中打开http://127.0.0.1:8000/account/password-change/。如果用户没有登录,浏览器会重定向到登录页面。当你认证成功后,你会看到下面的修改密码页面:
4.2.5 重置密码视图
在account应用的urls.py文件中,为重置密码添加以下URL模式:
from django.contrib.auth.views import password_reset
from django.contrib.auth.views import password_reset_done
from django.contrib.auth.views import password_rest_confirm
from django.contrib.auth.views import password_reset_complete
# restore password urls
url(r'^password-reset/$', password_reset, name='password_reset'),
url(r'^password-reset/done/$', password_reset_done, name='password_reset_done'),
url(r'^password-reset/confirm/(?P<uidb64>[-\w]+)/(?P<token>[-\w]+)/$', password_reset_confirm, name='password_reset_confirm'),
url(r'^password-reset/complete/$', password_reset_complete, name='password_reset_complete'),
在account应用的templates/registration/目录中创建password_reset_form.html文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Reset your password{% endblock %}
{% block content %}
<h1>Forgotten your password?</h1>
<p>Enter your e-mail address to obtain a new password.</p>
<form action="." method="post">
{{ form.as_p }}
<p><input type="submit" value="Send e-mail"></p>
{% csrf_token %}
</form>
{% endblock %}
在同一个目录下创建password_reset_email.html文件,添加以下代码:
Someon asked for password reset for email {{ email }}. Fllow the link below:
{{ protocol }}://{{ domain }}/{% url "password_reset_form" uidb64=uid token=token %}
Your usernmae, in case you've forgotten: {{ user.get_username }}
这个模板用于渲染发送给用户重置密码的邮件。
在同一个目录下创建password_reset_done.html文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Reset your password{% endblock %}
{% block content %}
<h1>Reset your password</h1>
<p>We've emailed you instructions for setting your password.</p>
<p>If you don't receive an email, please make sure you've entered the address you registered with.</p>
{% endblock %}
创建另一个模板文件password_reset_confirm.html,添加以下代码:
{% extends "base.html" %}
{% block title %}Reset your password{% endblock %}
{% block content %}
<h1>Reset your password</h1>
{% if validlink %}
<p>Please enter your new password twice:</p>
<form action="." method="post">
{{ formo.as_p }}
{% csrf_token %}
<p><input type="submit" value="Change my password" /></p>
</form>
{% else %}
<p>The password reset link was invalid, possible because it has already been used.
Please request a new password reset.</p>
{% endif %}
{% endblock %}
我们检查提供的链接是否有效。Django重置页面视图设置该变量,并把它放在这个模板的上下文中。如果链接有效,我们显示重置密码表单。
创建另一个password_reset_complete.html文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Password reset{% endblock %}
{% block content %}
<h1>Password set</h1>
<p>Your password has been set. You can <a href="{% url "login" %}">log in now</a></p>
{% endblock %}
最后,编辑account应用的registration/login.html模板,在<form>元素后面添加以下代码:
<p><a href="{% url "password_reset" %}">Forgotten your password?</a></p>
现在,在浏览器中打开htth://127.0.0.1:8000/account/login/,点击Forgotten your password?链接,你会看到以下链接:
此时,你需要在项目的settings.py文件中添加SMTP配置,让Django可以发送邮件。我们已经在第二章学习了如何添加邮件设置。但是在开发期间,你可以让Django在标准输出中写邮件,代替通过SMTP服务发送邮件。Django提供了一个邮件后台,可以把邮件输出到控制台。编辑项目的settings.py文件,添加下面这一行代码:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
EMAIL_BACKEND设置指定用于发送邮件的类。
回到浏览器,输入已有用户的邮箱地址,点击Send a e-mail按钮。你会看到以下页面:
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Password reset on 127.0.0.1:8000
From: webmaster@localhost
To: lakerszhy@gmail.com
Date: Tue, 02 May 2017 03:50:20 -0000
Message-ID: <20170502035020.7440.93778@bogon>
Someon asked for password reset for email lakerszhy@gmail.com. Fllow the link below:
http://127.0.0.1:8000/account/password-reset/confirm/Mg/4lp-4b14906c833231658e9f/
Your usernmae, in case you've forgotten: antonio
邮件使用我们之间创建的password_reset_email.html模板渲染。重置密码的URL包括一个Django动态生成的令牌。在浏览器中打开连接,会看到以下页面:
现在你可以使用新密码再次登录。每个用于设置新密码的令牌只能使用一次。如果你再次打开收到的链接,会看到一条令牌无效的消息。
你已经在项目中集成了Django认证框架的视图。这些视图适用于大部分场景。如果需要不同的行为,你可以创建自己的视图。
4.3 用户注册和用户资料
现在,已存在的用户可以登录,登出和修改密码,如果用户忘记密码,可以重置密码。现在,我们需要创建视图,用于游客创建账户。
4.3.1 用户注册
让我们创建一个简单的视图,允许用户在我们的网站注册。首先,我们必须创建一个表单,让用户输入用户名,姓名和密码。编辑account应用中的forms.py文件,添加以下代码:
from django.contrib.auth.models import User
class UserRegistrationForm(forms.ModelForm):
password = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Repeat Password', widget=forms.PasswordInput)
class Meta:
model = User
fields = ('username', 'first_name', 'email')
def clean_password2(self):
cd = self.cleaned_data
if cd['password'] != cd['password2']:
raise forms.ValidationError("Passwords don't match.")
return cd['password2']
我们为User模型创建了一个模型表单。在表单中,我们只包括了模型的username,first_name,email字段。这些字段会根据相应的模型字段验证。例如,如果用户选择了一个已存在的用户名,会得到一个验证错误。我们添加了两个额外字段:password和password2,用来设置密码和确认密码。我们定义了clean_password2()方法,检查两次输入的密码是否一致,如果不一致,则让表单无效。当我们调用表单的is_valid()方法验证时,这个检查会执行。你可以为任何表单字段提供clean_<fieldname>()方法,清理特定字段的值或抛出表单验证错误。表单还包括一个通用的clean()方法验证整个表单,验证相互依赖的字段时非常有用。
Django还在django.contrib.auth.forms中提供了UserCreationForm表单供你使用,这个表单跟我们刚创建的表单类似。
编辑account应用中的views.py文件,添加以下代码:
from .forms import LoginForm, UserRegistrationForm
def register(request):
if request.method == 'POST':
user_form = UserRegistrationForm(request.POST)
if user_form.is_valid():
# Create a new user object but avoid saving it yet
new_user = user_form.save(commit=False)
# Set the chosen password
new_user.set_password(user_form.cleaned_data['password'])
# Save the User object
new_user.save()
return render(request, 'account/register_done.html', {'new_user': new_user})
else:
user_form = UserRegistrationForm()
return render(request, 'account/register.html', {'user_form': user_form})
这个创建用户账户的视图非常简单。为了安全,我们使用User模型的set_password()方法处理加密保存,来代替保存用户输入的原始密码。
现在编辑account应用的urls.py文件,添加以下URL模式:
url(r'^register/$', views.register, name='register')
最后,我们在account/模板目录中创建register.html文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Create an account{% endblock %}
{% block content %}
<h1>Create an account</h1>
<p>Please, sign up using the following form:</p>
<form action="." method="post">
{{ user_form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Create my account"></p>
</form>
{% endblock %}
在同一个目录中添加register_done.html模板文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Welcome{% endblock %}
{% block content %}
<h1>Welcome {{ new_user.first_name }}!</h1>
<p>Your account has been successfully created. Now you can <a href="{% url "login" %}">log in</a>.</p>
{% endblock %}
现在,在浏览器中打开http://127.0.0.1:8000/account/register/,你会看到刚创建的注册页面:
点击log in链接,输入你的用户名和密码验证能否访问你的账户。
现在,你还可以在登录模板中添加注册链接。编辑registration/login.html模板,把这行代码:
<p>Please, user the following form to log-in</p>
<p>Please, user the following form to log-in.
If you don't have an account <a href="{% url "register" %}">register here</a></p>
我们可以通过登录页面访问注册页面了。
4.3.2 扩展User模型
当你必须处理用户账户时,你会发现Django认证框架的User模型适用于常见情况。但是User模型有非常基础的字段。你可能希望扩展User模型包含额外的数据。最好的方式是创建一个包括所有额外字段的个人资料模型,并且与Django的User模型是一对一的关系。
编辑account应用的models.py文件,添加以下代码:
from django.db import models
from django.conf import settings
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
date_of_birth = models.DateField(blank=True, null=True)
photo = models.ImageField(upload_to='users/%Y/%m/%d', blank=True)
def __str__(self):
return 'Pofile for User {}'.format(self.user.username)
为了让代码保持通用性,请使用get_user_model()方法检索用户模型。同时,定义模型和用户模型之间的关系时,使用AUTH_USER_MODEL设置引用用户模型,而不是直接引用该用户模型。
一对一的user字段允许我们用用户关联个人资料。photo字段是一个ImageField字段。你需要安装PIL(Python Imaging Library)或Pillow(PIL的一个分支)Python包来管理图片。在终端中执行以下命令安装Pillow:
pip install Pillow
为了在Django开发服务器中提供多媒体文件上传功能,需要在项目的settings.py文件中添加以下设置:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL是用户上传的多媒体文件的基URL,MEDIA_ROOT是多媒体文件的本地路径。我们根据项目路径动态构建该路径,让代码更通用。
现在,编辑bookmarks项目的主urls.py文件,如下所示修改代码:
from django.conf import settings
from django.conf.urls.static import static
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
这样,Django开发服务器将在开发过程中负责多媒体文件服务。
static()帮助函数只适用于开发环境,不适合生产环境。永远不要在生产环境使用Django为静态文件提供服务。
打开终端执行以下命令,为新模型创建数据库迁移:
python manage.py makemigrations
你会得到这样的输出:
Migrations for 'account':
account/migrations/0001_initial.py
- Create model Profile
接着使用以下命令同步数据库:
python manage.py migrate
你会看到包括下面这一样的输出:
Applying account.0001_initial... OK
编辑account应用的admin.py文件,在管理站点注册Profile模型,如下所示:
from .models import Profile
class ProfileAdmin(admin.ModelAdmin):
list_display = ('user', 'date_of_birth', 'photo')
admin.site.register(Profile, ProfileAdmin)
使用python manage.py runserver命令运行开发服务器。现在,你会在项目的管理站点看到Profile模型,如下图所示:
UserEditForm:允许用户编辑存在内置的User模型中的姓,名和邮箱。
ProfileEditForm:允许用户编辑存在自定义的Profile模型中的额外数据。用户可以编辑出生日期,并上传一张图片。
编辑account应用的views.py文件,导入Profile模型:
from .models import Profile
在register视图的new_user.save()下面添加以下代码:
# Create the user profile
profile = Profile.objects.create(user=new_user)
当用户在我们网站注册时,我们会创建一个空的个人资料关联到用户。你需要使用管理站点手动为之前创建的用户创建Profile对象。
现在我们让用户可以编辑个人资料。添加以下代码到同一个文件中:
from .forms import LoginForm, UserRegistrationForm, UserEditForm, ProfileEditForm
@login_required
def edit(request):
if request.method == 'POST':
user_form = UserEditForm(instance=request.user, data=request.POST)
profile_form = ProfileEditForm(instance=request.user.profile,
data=request.POST,
files=request.FILES)
if user_form.is_valid() and profile_form.is_valid():
user_form.save()
profile_form.save()
else:
user_form = UserEditForm(instance=request.user)
profile_form = ProfileEditForm(instance=request.user.profile)
return render(request, 'account/edit.html', {'user_form': user_form, 'profile_form': profile_form})
我们使用了login_required装饰器,因为用户必须认证后才能编辑个人资料。在这里,我们使用了两个模型表单:UserEditForm存储内置的User模型数据,ProfileEditForm存储额外的个人数据。我们检查两个表单的is_valid()方法返回True来验证提交的数据。在这里,我们保持两个表单,用来更新数据库中相应的对象。
在account应用的urls.py文件中添加以下URL模式:
url(r'^edit/$', views.edit, name='edit')
最后,在templates/account/目录中,为该视图创建edit.html模板,添加以下代码:
{% extends "base.html" %}
{% block title %}Edit your account{% endblock %}
{% block content %}
<h1>Edit your account</h1>
<p>You can edit your account using the following form:</p>
<form action="." method='post' enctype="multipart/form-data">
{{ user_form.as_p }}
{{ profile_form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Save changes"></p>
</form>
{% endblock %}
我们在表单中包括了enctype="multipart/form-data",来启用文件上传。我们使用一个HTML表单提交user_form和profile_form两个表单。
注册一个新用户,并在浏览器中打开http://127.0.0.1:8000/account/edit/,你会看到以下界面:
现在你可以编辑仪表盘页面,来包括编辑个人资料和修改密码的页面链接。打开account/dashboard.html模板,把这一行代码:
<p>Welcome to your dashboard.</p>
Welcome to your dashboard.
You can <a href="{% url "edit" %}">edit your profiles</a>
or <a href="{% url "password_change" %}">change your password</a>.
用户现在可以通过仪表盘访问编辑个人资料的表单。
4.3.2.1 使用自定义User模型
Django还提供了方式,可以用自定义模型代替整个User模型。你的用户类应从Django的AbstractUser类继承,它作为一个抽象模型,提供了默认用户的完整实现。你可以在这里阅读更多关于这个模型的信息。
使用自定义用户模型会有更多的灵活性,但它也可能给一些需要与User模型交互的可插拔应用应用的集成带来一定的困难。
4.3.3 使用消息框架
处理用户动作时,你可能想要通知用户动作的结果。Django内置一个消息框架,允许你显示一次性提示。该消息框架位于django.contrib.message中,当你用python manage.py startproject创建新项目时,它默认包括在settings.py的INSTALLED_APPS列表中。你注意到,设置文件的MIDDLEWARE_CLASSES设置列表中,包括一个名为django.contrib.message.middleware.MessageMiddleware的中间件。该消息框架提供了一种简单的方式来给用户添加消息。消息存储在数据库中,并会在用户下次请求时显示。你可以通过导入消息模块,使用简单的快捷方式添加新消息,来在视图中使用消息框架,如下所示:
from django.contrib import message
message.error(request, 'Something went wrong')
你可以使用add_message()方法,或者以下任何一个快捷方法创建新消息:
success():动作执行成功后显示成功消息
info():信息消息
waring():还没有失败,但很可能马上失败
error():一个不成功的操作,或某些事情失败
debug():调试信息,会在生产环境移除或忽略
让我们显示消息给用户。因为消息框架对项目来说是全局的,所以我们可以在基础模板中显示消息给用户。打开base.html模板,在id为header和content的<div>元素之间添加以下代码:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li class="{{ message.tags }}">
{{ message|safe }}
<a href="#" class="close">✖</a>
{% endfor %}
{% endif %}
消息框架包括一个上下文处理器(context processor),它会添加messages变量到请求上下文中。因此,你可以在模板使用该变量显示当前消息。
现在,让我们修改edit视图来使用消息框架。编辑account应用的views.py文件,如下修改edit视图:
from django.contrib import messages
@login_required
def edit(request):
if request.method == 'POST':
# ...
if user_form.is_valid() and profile_form.is_valid():
user_form.save()
profile_form.save()
messages.success(request, 'Profile updated successfully')
else:
messages.error(request, 'Error updating your profile')
else:
user_form = UserEditForm(instance=request.user)
# ...
当用户成功更新个人资料后,我们添加一条成功消息。如果任何一个表单无效,我们添加一条错误消息。
在浏览器中打开http://127.0.0.1:8000/account/edit/,并编辑你的个人资料。当个人资料更新成功后,你会看到以下消息:
4.4 创建自定义认证后台
Django允许你针对不同来源进行身份验证。AUTHENTICATION_BACKENDS设置包括了项目的认证后台列表。默认情况下,该设置为:
('django.contrib.auth.backends.ModelBackend',)
默认的ModelBackend使用django.contrib.auth的User模型,验证数据库中的用户。这适用于大部分项目。但是你可以创建自定义的后台,来验证其它来源的用户,比如一个LDAP目录或者其它系统。
你可以在这里阅读更多关于自定义认证的信息。
一旦你使用django.contrib.auth中的authenticate()函数,Django会一个接一个尝试AUTHENTICATION_BACKENDS中定义的每一个后台来验证用户,直到其中一个验证成功。只有所有后台都验证失败,才不会在站点中验证通过。
Django提供了一种简单的方式来定义自己的认证后台。一个认证后台是提供了以下两个方法的类:
authenticate():接收用户信息作为参数,如果用户认证成功,则返回True,否则返回False。
get_user():接收用户ID作为参数,并返回一个User对象。
创建一个自定义认证后台跟编写一个实现这两个方法的Python类一样简单。我们会创建一个认证后台,让用户使用邮箱地址代替用户名验证。
在account应用目录中创建一个authentication.py文件,添加以下代码:
from django.contrib.auth.models import User
class EmailAuthBackend:
Authenticates using e-mail account.
def authenticate(self, username=None, password=None):
user = User.objects.get(email=username)
if user.check_password(password):
return user
return None
except User.DoesNotExist:
retur None
def get_user(self, user_id):
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
这是一个很简单的认证后台。authenticate()方法接收username和password作为可选参数。我们可以使用不同的参数,但我们使用username和password确保后台可以立即在认证框架中工作。上面的代码完成以下工作:
authenticate():我们尝试使用给定的邮箱地址检索用户,并用User模型内置的check_password()方法检查密码。该方法会处理密码哈希化,并比较给定的密码和数据库中存储的密码。
get_user():我们通过user_id参数获得一个用户。在用户会话期间,Django使用认证用户的后台来检索User对象。
编辑项目的settings.py,添加以下设置:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'account.authentication.EmailAuthBackend',
我们保留了默认的ModelBackend,使用用户名和密码认证,并包括了自己的基于邮箱地址的认证后台。现在,在浏览器中打开http://127.0.0.1/8000/account/login/。记住,Django会试图使用每一个后台验证用户,所以你现在可以使用用户名或邮箱账号登录。
AUTHENTICATION_ BACKENDS设置中列出的后端顺序很重要。如果同样的信息对于多个后台都有效,Django会在第一个成功验证用户的后台停止。
4.5 为网站添加社交认证
你可能还想为网站添加社交认证,比如使用Facebook,Twitter或Google服务认证。Python-socail-auth是一个Python模块,可以简化添加社交认证过程。通过这个模块,你可以让用户使用其他服务的账户登录你的网站。