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 %}
Bookmarks
{% block content %}
{% endblock %}
这是网址的基础模板。跟之前的项目一样,我们在主模板中包括CSS样式。该基础模板定义了title和content区域,可以被从它扩展的模板填充内容。
让我们为登录表单创建模板。打开account/login.html模板,添加以下代码:
{% extends "base.html" %}
{% block title %}Log-in{% endblock %}
{% block content %}
Log-in
Please, user the following form to log-in
{% 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模型,如下图所示:
通过管理站点创建一个新用户,并在浏览器中打开http://127.0.0.1:8000/account/login/。你会看到包括登录表单的模板:
现在,提交表单时不填其中一个字段。这时,你会看到表单是无效的,并显示错误信息,如下图所示:
如果你输入一个不存在的用户,或者错误的密码,你会看到一条Invalid login消息。
如果你输入有效的认证信息,会看到一条Authenticated successfully消息,如下图所示:
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 %}
Log-in
{% if form.errors %}
Your username and password didn't match.
Please try again.
{% else %}
Please, user the following form to log-in
{% endif %}
{% endblock %}
这个login模板跟我们之前创建那个很像。Django默认使用django.contrib.auth.forms中的AuthenticationForm。该表单尝试验证用户,如果登录不成功,则抛出一个验证错误。这种情况下,如果认证信息出错,我们可以在模板中使用{% if form.errors %}查找错误。注意,我们添加了一个隐藏的HTML 元素,用于提交名为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 %}
Logged out
You have been successfully logged out. You can log-in again>.
{% 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。通过这样的做法,当用户成功登录后,登录视图会重定向用户到用户登录之前试图访问的页面。记住,我们在登录模板的表单中添加了一个隐藏的元素就是为了这个目的。
我们还定义了一个section变量。我们用这个变量跟踪用户正在查看网站的哪一部分(section)。多个视图可能对应相同的部分。这是定义每个视图对应的section的简便方式。
现在,你需要为仪表盘视图创建一个模板。在templates/account/目录下创建dashboard.html文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block content %}
Dashboard
Welcome to your dashboard.
{% 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参数时,登录后重定向到哪个URLLOGIN_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的
Bookmarks
{% if request.user.is_authenticated %}
{% endif %}
{% if request.user.is_authenticated %}
Hello {{ request.user.first_name }},
{% else %}
{% endif %}
正如你所看到的,我们只为认证的用户显示网站的菜单。我们还检查当前的section,通过CSS为相应的
现在,在浏览器中打开http://127.0.0.1:8000/account/login。你会看到登录页面。输入有效的用户名和密码,点击Log-in按钮,你会看到这样的页面:
因为My dashboard有selected属性,所以你会看到它是高亮显示的。因为是认证过的用户,所以用户的姓显示在头部的右边。点击Logout链接,你会看到下面的页面:
在这个页面中,用户已经登出,所以你不能再看到网站的菜单。现在头部右边显示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 %}
Change you password
Use the form below to change your password.
{% endblock %}
该模板包括修改密码的表单。在同一个目录下创建password_change_done.html文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Password changed{% endblock %}
{% block content %}
Password changed
Your password has been successfully changed.
{% endblock %}
该模板只包括一条用户成功修改密码后显示的成功消息。
在浏览器中打开http://127.0.0.1:8000/account/password-change/。如果用户没有登录,浏览器会重定向到登录页面。当你认证成功后,你会看到下面的修改密码页面:
在表单中填写当前密码和新密码,点击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
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 %}
Forgotten your password?
Enter your e-mail address to obtain a new password.
{% 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 %}
Reset your password
We've emailed you instructions for setting your password.
If you don't receive an email, please make sure you've entered the address you registered with.
{% endblock %}
创建另一个模板文件password_reset_confirm.html,添加以下代码:
{% extends "base.html" %}
{% block title %}Reset your password{% endblock %}
{% block content %}
Reset your password
{% if validlink %}
Please enter your new password twice:
{% else %}
The password reset link was invalid, possible because it has already been used.
Please request a new password reset.
{% endif %}
{% endblock %}
我们检查提供的链接是否有效。Django重置页面视图设置该变量,并把它放在这个模板的上下文中。如果链接有效,我们显示重置密码表单。
创建另一个password_reset_complete.html文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Password reset{% endblock %}
{% block content %}
Password set
Your password has been set. You can log in now
{% endblock %}
最后,编辑account应用的registration/login.html模板,在
{% endblock %}
在同一个目录中添加register_done.html模板文件,添加以下代码:
{% extends "base.html" %}
{% block title %}Welcome{% endblock %}
{% block content %}
Welcome {{ new_user.first_name }}!
Your account has been successfully created. Now you can log in.
{% endblock %}
现在,在浏览器中打开http://127.0.0.1:8000/account/register/,你会看到刚创建的注册页面:
为新用户填写信息,点击Create my account按钮。如果所有字段都有效,则会创建用户,你会看到下面的成功消息:
点击log in链接,输入你的用户名和密码验证能否访问你的账户。
现在,你还可以在登录模板中添加注册链接。编辑registration/login.html模板,把这行代码:
Please, user the following form to log-in
替换为:
Please, user the following form to log-in.
If you don't have an account register here
我们可以通过登录页面访问注册页面了。
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模型,如下图所示:
现在,我们将让用户在网站上编辑个人资料。在account应用的forms.py文件中添加以下模型表单:
from .models import Profile
class UserEditForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'email')
class ProfileEditForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('date_of_birth', 'photo')
这些表单的作用是:
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 %}
Edit your account
You can edit your account using the following form:
{% endblock %}
我们在表单中包括了enctype="multipart/form-data",来启用文件上传。我们使用一个HTML表单提交user_form和profile_form两个表单。
注册一个新用户,并在浏览器中打开http://127.0.0.1:8000/account/edit/,你会看到以下界面:
现在你可以编辑仪表盘页面,来包括编辑个人资料和修改密码的页面链接。打开account/dashboard.html模板,把这一行代码:
Welcome to your dashboard.
替换为:
Welcome to your dashboard.
You can edit your profiles
用户现在可以通过仪表盘访问编辑个人资料的表单。
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的
{% if messages %}
{% 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):
try:
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):
try:
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模块,可以简化添加社交认证过程。通过这个模块,你可以让用户使用其他服务的账户登录你的网站。
译者注:从2016年12月3日开始,这个模块迁移到了Python Social Auth。原书的内容已经过时,所以就不翻译了。
4.6 总结
在本章中,你学习了如何在网站中构建认证系统和创建自定义用户资料。你还在网站中添加了社交认证。
下一章中,你会学习如何创建一个图片书签系统,生成图片的缩略图,以及创建AJAX视图。