转自:http://www.jianshu.com/p/06670f9aa439
第四章
创建一个社交网站
在上一章中,你学习了如何创建站点地图(sitemaps)和feeds,你还为你的blog应用创建了一个搜索引擎。在本章中,你将开发一个社交应用。你会为用户创建一些功能,例如:登录,登出,编辑,以及重置他们的密码。你会学习如何为你的用户创建一个定制的profile,你还会为你的站点添加社交认证。
本章将会覆盖以下几点:
- 使用认证(authentication)框架
- 创建用户注册视图(views)
- 通过一个定制的profile模型(model)扩展User模型(model)
- 使用python-social-auth添加社交认证
让我们开始创建我们的新项目吧。
创建一个社交网站项目
我们要创建一个社交应用允许用户分享他们在网上找到的图片。我们需要为这个项目构建以下元素:
- 一个用来给用户注册,登录,编辑他们的profile,以及改变或重置密码的认证(authentication)系统
- 一个允许用户用来关注其他人的关注系统(这里原文是follow,‘跟随’,感觉用‘关注’更加适合点)
- 为用户从其他任何网站分享过来的图片进行展示和打上书签
- 每个用户都有一个活动流允许用户看到他们关注的人上传的内容
本章主要讲述第一点。
开始你的社交网站项目
运行以下命令来创建一个新项目:
django-admin statproject bookmarks
在创建好一个初始的项目结构以后,使用以下命令进入你的项目目录并且创建一个新的应用命名为account:
cd bookmarks/django-admin startapp account
请记住在你的项目中激活一个新应用需要在settings.py文件中的INSTALLED_APPS设置中添加它。将新应用的名字添加在INSTALLED_APPS列中的所有已安装应用的最前面,如下所示:
INSTALLED_APPS = (
'account',
# ...
)
运行下一条命令为INSTALLED_APPS中默认包含的应用模型(models)同步到数据库中:
python manage.py migrate
我们将要使用认证(authentication)框架来构建一个认证系统到我们的项目中。
使用Django认证(authentication)框架
Django拥有一个内置的认证(authentication)框架用来操作用户认证(authentication),会话(sessions),权限(permissions)以及用户组。这个认证(authentication)系统包含了一些普通用户的操作视图(views),例如:登录,登出,修改密码以及重置密码。
这个认证(authentication)框架位于django.contrib.auth,被其他Django的contrib包调用。请记住你使用过这个认证(authentication)框架在第一章 创建一个Blog应用中用来为你的blog应用创建了一个超级用户来使用管理站点。
当你使用startproject命令创建一个新的Django项目,认证(authentication)框架已经在你的/项目设置中默认包含。它是由django.contrib.auth应用和你的项目设置中的MIDDLEWARE_CLASSES中的两个中间件类组成,如下:
- AuthenticationMiddlwware:使用会话(sessions)将用户和请求(requests)进行关联
- SessionMiddleware:通过请求(requests)操作当前会话(sessions)
中间件就是一个在请求和响应阶段带有全局执行方法的类。你会在本书中的很多场景中使用到中间件。你将会学习如何创建一个定制的中间件在第十三章 Going Live(译者注:啥时候能翻译到啊)。
这个认证(authentication)系统还包含了以下模型(models):
- User:一个用户模型(model)包含基础字段;这个模型(model)的主要字段有:
username
,password
,first_name
,last_name
,is_active
。
- Group:一个组模型(model)用来分类用户
- Permission:执行特定操作的标识
这个框架还包含默认的认证(authentication)视图(views)和表单(forms),我们之后会用到。
创建一个log-in视图(view)
我们将要开始使用Django认证(authentication)框架来允许用户登录我们的网站。我们的视图(view)需要执行以下操作来登录用户:
1、通过提交的表单(form)获取username和password
2、通过存储在数据库中的数据对用户进行认证
3、检查用户是否可用
4、登录用户到网站中并且开始一个认证(authentication)会话(session)
首先,我们要创建一个登录表单(form)。在你的account应用目录下创建一个新的forms.py文件,添加如下代码:
from django import forms
class LoginForm(forms.Form):
username = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput)
这个表单(form)被用来通过数据库认证用户。请注意,我们使用PsswordInput控件来渲染HTMLinput
元素,包含type="password
属性。编辑你的account应用中的views.py文件,添加如下代码:
from django.http import HttpResponse
from django.shortcuts import render
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})
以上就是我们在视图(view)中所作的基本登录操作:当user_login被一个GET请求(request)调用,我们实例化一个新的登录表单(form)通过form = LoginForm()
在模板(template)中展示它。当用户通过POST方法提交表单(form),我们执行以下操作:
1、使用提交的数据实例化表单(form)通过使用
form = LoginForm(request.POST)
2、检查这个表单是否有效。如果无效,我们在模板(template)中展示表单错误信息(举个例如,比如用户没有填写其中一个字段就进行提交)
3、如果提交的数据是有效的,我们通过数据库对这个用户进行认证(authentication)通过使用authenticate()
方法。这个方法带入一个username和一个password并且返回一个用户对象如果这个用户成功的进行了认证,或者是None。如果用户没有被认证通过,我们返回一个HttpResponse展示一条消息。
4、如果这个用户认证(authentication)成功,我们使用is_active
属性来检查用户是否可用。这是一个Django的User模型(model)属性。如果这个用户不可用,我们返回一个HttpResponse展示信息。
5、如果用户可用,我们登录这个用户到网站中。我们通过调用login()
方法集合用户到会话(session)中然后返回一条成功消息。
- 请注意authentication和login中的不同点:authenticate()检查用户认证信息然后返回一个用户对象如果用户是正确的;login()集合用户到当前的会话(session)中。
现在,你需要为这个视图(view)创建一个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 include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^account/',include('account.urls')),
]
这个登录视图(view)现在已经可以通过URL进行访问。现在是时候为这个视图(view)创建一个模板。
因为之前你没有这个项目的任何模板,你可以开始创建一个主模板(template)可以被登录模板(template)继承使用。创建以下文件和结构在account应用目录中:
templates/account/base.html, templates/account/login.html
编辑base.html文件添加如下代码:
{% load staticfiles %}
{% block title %}{% endblock %}
Bookmarks
{% block content %}
{% endblock %}
以上将是这个网站的基础模板(template)。就像我们在上一章项目中做过的一样,我们在这个住模板(template)中包含了CSS样式。你可以在本章的示例代码中找到这些静态文件。复制示例代码中的account应用下的static/目录到你的项目中的相同位置,这样你就可以使用这些静态文件了。
基础模板(template)定义了一个title和一个content区块可以被继承的子模板(template)填充内容。
让我们为我们的登录表单(form)创建模板(template)。打开account/login.html模板(template)添加如下代码:
{% extends "account/base.html" %}
{% block title %}Log-in{% endblock %}
{% block content %}
Log-in
Please, use the following form to log-in:
{% endblock %}
这个模板(template)包含了视图(view)中实例化的表单(form)。因为我们的表单(form)将会通过POST方式进行提交,所以我们包含了{% csrf_token %}
模板(template)标签(tag)用来通过CSRF保护。你已经学习过CSRF保护在第二章 使用高级特性扩展你的博客应用。
目前还没有用户在你的数据库中。你首先需要创建一个超级用户用来登录管理站点来管理其他的用户。打开命令行执行python manage.py createsuperuser
。填写username,e-mail以及password。之后通过命令python manage.py runserver
运行开发环境,然后在你的浏览器中打开 http://127.0.0.1:8000/admin/ 。使用你刚才创建的username和passowrd来进入管理站点。你会看到Django管理站点包含Django认证(authentication)框架中的User和Group模型(models)。如下所示:
使用管理站点创建一个新的用户然后打开 http://127.0.0.1:8000/account/login/ 。你会看到被渲染过的模板(template),包含一个登录表单(form):
# 因为没有css,所以页面是原始样式
加个css样式
https://pan.baidu.com/s/1bpaKg83 密码: tzic ,里面示例代码有base.css
加好后访问
现在,只填写一个字段保持另外一个字段为空进行表单(from)提交。在这例子中,你会看到这个表单(form)是无效的并且显示了一个错误信息:
如果你输入了一个不存在的用户或者一个错误的密码,你会得到一个 Invalid login信息。
如果你输入了有效的认证信息,你会得到一个 Authenticated successfully消息。
使用Django认证(authentication)视图(views)
Django包含了一些表单(forms)和视图(views)在认证(authentication)框架中让你可以立刻(straightaway)使用。你之前创建的登录视图(view)是一个非常的练习用来理解Django中的用户认证(authentication)过程。无论如何,你可以使用默认的Django认证(authentication)视图(views)在大部分的例子中。
Django提供以下视图(views)来处理认证(authentication):
- login:操作表单(form)中的登录然后登录一个用户
- logout:登出一个用户
- logout_then_login:登出一个用户然后重定向这个用户到登录页面
Django提供以下视图(views)来操作密码修改:
- password_change:操作一个表单(form)来修改用户密码
- password_change_done:当用户成功修改他的密码后提供一个成功提示页面
Django还包含了以下视图(views)允许用户重置他们的密码:
- password_reset:允许用户重置他的密码。它会生成一条带有一个token的一次性使用链接然后发送到用户的邮箱中。
- password_reset_done:告知用户已经发送了一封可以用来重置密码的邮件到他的邮箱中。
- password_reset_complete:当用户重置完成他的密码后提供一个成功提示页面。
以上的视图(views)可以帮你节省很多时间当你创建一个带有用户账号的网站。这些视图(views)使用的默认值你可以进行覆盖,例如需要渲染的模板位置或者视图(view)需要使用到的表单(form)。
你可以通过访问 https://docs.djangoproject.com/en/1.8/topics/auth/default/#module-django.contrib.auth.views 获取更多内置的认证(authentication)视图(views)信息。
登录和登出视图(views)
编辑你的account应用下的urls.py文件,如下所示:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^login/$', 'django.contrib.auth.views.login', name='login'),
url(r'^logout/$', 'django.contrib.auth.views.logout', name='logout'),
url(r'^logout-then-login/$', 'django.contrib.auth.views.logout_then_login', name='logout_then_login'),
]
这种写法会报错,view must be a callable or a list/tuple in the case of include()
解决:
from django.conf.urls import url
from .views import dashboard
from django.contrib.auth import views
urlpatterns = [
# post views
url(r'^login/$', views.login, name='login'),
url(r'^logout/$', views.logout, name='logout'),
url(r'^logout-then-login/$', views.logout_then_login, name='logout_then_login'),
]
我们将之前创建的user_login视图(view)URL模式进行注释,然后使用Django认证(authentication)框架提供的login视图(view)。
在你的account应用中的template目录下创建一个新的目录命名为registration。这个路径是Django认证(authentication)视图(view)期望你的认证(authentication)模块(template)默认的存放路径。在这个新目录中创建一个新的文件,命名为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, use the following form to log-in:
{% endif %}
{% endblock %}
这个登录模板(template)和我们之前创建的基本类似。Django默认使用位于django.contrib.auth.forms中的AuthenticationForm。这个表单(form)会尝试对用户进行认证如果登录不成功就会抛出一个验证错误。在这个例子中,我们可以在模板(template)中使用{% if form.errors %}来找到错误如果用户提供了错误的认证信息。
请注意,我们添加了一个隐藏的HTML元素来提交叫做next的变量值。这个变量是登录视图(view)的首个设置当你在请求(request)中传递一个next参数(举个例子:http://127.0.0.1:8000/account/login/?next=/account/)。
next参数必须是一个URL。当这个参数被给予的时候,Django登录视图(view)将会在用户登录完成后重定向到给予的URL。
现在,在registrtion模板(template)目录下创建一个logged_out.html模板(template)添加如下代码:
{% extends "base.html" %}
{% block title %}Logged out{% endblock %}
{% block content %}
Logged out
You have been successfully logged out. You can log-in again.
{% endblock %}
这个模板(template)Django会在用户登出的时候进行展示。
在添加了URL模式以及登录和登出视图(view)的模板之后,你的网站已经准备好让用户使用Django认证(authentication)视图进行登录。
请注意,在我们的地址配置中包含的logtou_then_login视图(view)不需要任何模板(template),因为它执行了一个重定向到登录视图(view)。
现在,我们要创建一个新的视图(view)来显示一个dashboard给用户当他或她登录他们的账号。打开你的account应用中的views.py文件,添加以下代码:
from django.contrib.auth.decorators import login_required
@login_required
def dashboard(request):
return render(request, 'account/dashboard.html', {'section': 'dashboard'})
我们使用认证authentication
框架的login_required装饰器decorator
装饰我们的视图view
。login_required装饰器decorator
会检查当前用户是否通过认证,如果用户通过认证,它会执行装饰的视图view
,如果用户没有通过认证,它会把用户重定向到带有一个名为next的GET参数的登录URL,该GET参数保存的变量为用户当前尝试访问的页面URL。通过这些动作,登录视图view
会将登录成功的用户重定向到用户登录之前尝试访问过的URL。请记住我们在登录模板template
中的登录表单form
中添加的隐藏就是为了这个目的。
我们还定义了一个section变量。我们会使用该变量来跟踪用户在站点中正在查看的页面。多个视图(views)可能会对应相同的section。这是一个简单的方法用来定义每个视图(view)对应的section。
现在,你需要创建一个给dashboard视图(view)使用的模板(template)。在templates/account/目录下创建一个新文件命名为dashboard.html。添加如下代码:
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block content %}
Dashboard
Welcome to your dashboard.
{% endblock %}
之后,为这个视图(view)在account应用中的urls.py文件中添加如下URL模式:
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视图(view)没有获取到next参数将会默认重定向到哪个URL。
- LOGIN_URL:重定向用户登录的URL(例如:使用login_required装饰器(decorator))。
- LOGOUT_URL:重定向用户登出的URL。
我们使用reverse_laze()来动态构建URL通过它们的名字。reverse_laze()方法reverses URLs就像reverse()所做的一样,但是你可以使用它当你需要reverse URLs在你项目的URL配置读取之前。
让我们总结下目前为止我们都做了哪些工作:
你在项目中添加了Django内置的认证(authentication)登录和登出视图(views)。
你为每一个视图(view)创建了定制的模板(templates),并且定义了一个简单的视图(view)让用户登录后进行重定向。
最后,你配置了你的Django设置使用默认的URLs。
现在,我们要给我们的主模板(template)添加登录和登出链接将所有的一切都连接起来。
为了做到这点,我们必须确定无论用户是已登录状态还是没有登录的时候,都会显示适当的链接。通过认证(authentication)中间件当前的用户被集合在HTTP请求(request)对象中。你可以访问用户信息通过使用request.user
。你会防线一个用户对象在请求(request)中,即使这个用户并没有认证通过。一个非认证的用户在请求(request)被设置成一个AnonymousUser的实例。一个最好的方法来检查当前的用户是否通过认证是通过调用request.user.is_authenticated()
。
编辑你的base.html, 如下所示:
{% load staticfiles %}
{% block title %}{% endblock %}
Bookmarks
{% if request.user.is_authenticated %}
{% endif %}
{% if request.user.is_authenticated %}
Hello {{ request.user.first_name }},
Logout
{% else %}
Log-in
{% endif %}
{% block content %}
{% endblock %}
就像你所看到的,我们只给通过认证(authentication)的用户显示站点菜单。我们还检查当前的section来给对应的组件添加一个selected的class属性来使当前section在菜单中进行高亮显示通过使用CSS。我们还显示用户的第一个名字和一个登出的链接如果用户已经通过认证(authentication),否则,就是一个登出链接。
现在,在浏览器中打开 http://127.0.0.1:8000/account/login/ 。你会看到登录页面。输入可用的用户名和密码然后点击Log-in按钮。你会看到如下图所示:
你能看到My Dashboard section 通过CSS的作用高亮显示因为它拥有一个selected class。因为当前用户已经通过了认证(authentication)所有用户的第一个名字在右上角进行了显示。点击Logout链接。你会看到如下图所示:
在这个页面中,你能看到用户已经登出,然后,你无法看到当前网站的任何菜单。在右上角现在显示的是 Log-in链接。
如果你在你的登出页面中看到了Django管理站点的登出页面,检查项目settings.py中的INSTALLED_APPS,确保django.contrib.admin在account应用的后面。每个模板(template)被定位在同样的相对路径时,Django模板(template)读取器将会使用它找到的第一个应用中的模板(templates)。
修改密码视图(views)
我们还需要我们的用户在登录成功后可以进行修改密码。我们要集成Django认证(authentication)视图(views)来修改密码。打开account应用中的urls.py文件,添加如下URL模式:
url(r'^password-change/$', views.password_change, name='password_change'),
url(r'^password-change/done/$', views.password_change_done, name='password_change_done'),
password_change视图(view)将会操作表单(form)进行修改密码,password_change_done将会显示一条成功信息当用户成功的修改他的密码。让我们为每个视图(view)创建一个模板(template)。
在你的account应用templates/registration/目录下添加一个新的文件命名为password_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 %}
这个模板(template)包含了修改密码的表单(form)。现在,在相同的目录下创建另一个文件,命名为password_change_done.html,为它添加如下代码:
{% extends "base.html" %}
{% block title %}Password changed{% endblock %}
{% block content %}
Password changed
Your password has been successfully changed.
{% endblock %}
这个模板(template)只包含显示一条成功的信息 当用户成功的修改他们的密码。
在base.html的 {% if request.user.is_authenticated %}
下面加一行
修改密码
刷新页面即可看到,如果你的用户没有登录,浏览器会重定向你到登录页面。在你成功认证(authentication)登录后,你会看到如下
点击修改密码页面,按要求改完密码后,会看到如下页面:
登出再使用新的密码进行登录来验证每件事是否如预期一样工作。
重置密码视图(views)
在account应用urls.py文件中为密码重置添加如下URL模式:
# restore password urls
url(r'^password-reset/$', 'django.contrib.auth.views.password_reset', name='password_reset'),
url(r'^password-reset/done/$', 'django.contrib.auth.views.password_reset_done', name='password_reset_done'),
url(r'^password-reset/confirm/(?P[-\w]+)/(?P[-\w]+)/$',
'django.contrib.auth.views.password_reset_confirm', name='password_reset_confirm'),
url(r'^password-reset/complete/$', 'django.contrib.auth.views.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,为它添加如下代码:
Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol }}://{{ domain }}{% url "password_reset_confirm" uidb64=uid token=token %}
Your username, in case you've forgotten: {{ user.get_username }}
这个模板(template)会被用来渲染发送给用户的重置密码邮件。
在相同目录下添加另一个文件命名为*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 %}
再创建另一个模板(template)命名为passowrd_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, possibly because it has already been used. Please request a new password reset.
{% endif %}
{% endblock %}
在以上模板中,如果重置链接是有效的我们将会进行检查。Django重置密码视图(view)会设置这个变量然后将它带入这个模板(template)的上下文环境中。如果重置链接有效,我们展示用户密码重置表单(form)。
创建另一个模板(template)命名为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模板(template),添加如下代码在