书籍出处:https://www.packtpub.com/web-development/django-example
原做者:Antonio Melécss
2017年1月4日初稿发布html
2017年5月5日第一次精校完成(感谢大牛 @kukoo 的精校!)python
(译者注:祝你们新年快乐,此次带来《Django By Example》第四章的翻译,这章很是的实用,就这样)git
在上一章中,你学习了如何建立站点地图(sitemaps)和feeds,你还为你的blog应用建立了一个搜索引擎。在本章中,你将开发一个社交应用。你会为用户建立一些功能,例如:登陆,登出,编辑,以及重置他们的密码。你会学习如何为你的用户建立一个定制的profile,你还会为你的站点添加社交认证。github
本章将会覆盖一下几点:web
让咱们开始建立咱们的新项目吧。shell
咱们要建立一个社交应用容许用户分享他们在网上找到的图片。咱们须要为这个项目构建如下元素:数据库
本章主要讲述第一点。django
打开终端使用以下命令行为你的项目建立一个虚拟环境而且激活它:
mkdir evn
virtualenv evn/bookmarks
source env/bookmarks/bin/activate后端
shell提示将会展现你激活的虚拟环境,以下所示:
(bookmarks)laptop:~ zenx$
经过如下命令在你的虚拟环境中安装Django:
pip install Django==1.8.6
运行如下命令来建立一个新项目:
django-admin startproject 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)框架用来操做用户认证(authentication),会话(sessions),权限(permissions)以及用户组。这个认证(authentication)系统包含了一些普通用户的操做视图(views),例如:登陆,登出,修改密码以及重置密码。
这个认证(authentication)框架位于django.contrib.auth,被其余Django的contrib包调用。请记住你在第一章 建立一个Blog应用中使用过这个认证(authentication)框架并用来为你的blog应用建立了一个超级用户来使用管理站点。
当你使用startproject命令建立一个新的Django项目,认证(authentication)框架已经在你的项目设置中默认包含。它是由django.contrib.auth应用和你的项目设置中的MIDDLEWARE_CLASSES中的两个中间件类组成,以下:
中间件就是一个在请求和响应阶段带有全局执行方法的类。你会在本书中的不少场景中使用到中间件。你将会在第十三章 Going Live中学习如何建立一个定制的中间件(译者注:啥时候能翻译到啊)。
这个认证(authentication)系统还包含了如下模型(models):
这个框架还包含默认的认证(authentication)视图(views)和表单(forms),咱们以后会用到。
咱们将要开始使用Django认证(authentication)框架来容许用户登陆咱们的网站。咱们的视图(view)须要执行如下操做来登陆用户:
首先,咱们要建立一个登陆表单(form)。在你的account应用目录下建立一个新的forms.py文件,添加以下代码:
from django import forms class LoginForm(forms.Form): username = forms.CharField() password = forms.CharField(widget=forms.PasswordInput)
这个表单(form)被用来经过数据库认证用户。请注意,咱们使用PasswordInput控件来渲染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)时,咱们执行如下操做:
一、经过使用form = LoginForm(request.POST)
使用提交的数据实例化表单(form)
二、检查这个表单是否有效。若是无效,咱们在模板(template)中展现表单错误信息(举个例子,好比用户没有填写其中一个字段就进行提交)
三、若是提交的数据是有效的,咱们使用authenticate()
方法经过数据库对这个用户进行认证(authentication)。这个方法带入一个username和一个password,若是这个用户成功的进行了认证则返回一个用户对象,不然是None。若是用户没有被认证经过,咱们返回一个HttpResponse展现一条消息。
四、若是这个用户认证(authentication)成功,咱们使用is_active
属性来检查用户是否可用。这是一个Django的User模型(model)属性。若是这个用户不可用,咱们返回一个HttpResponse展现信息。
五、若是用户可用,咱们登陆这个用户到网站中。咱们经过调用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/ login.html base.html
编辑base.html文件添加以下代码:
{% load staticfiles %} <!DOCTYPE html> <html> <head> <title>{% block title %}{% endblock %}</title> <link href="{% static "css/base.css" %}" rel="stylesheet"> </head> <body> <div id="header"> <span class="logo">Bookmarks</span> </div> <div id="content"> {% block content %} {% endblock %} </div> </body> </html>
以上将是这个网站的基础模板(template)。就像咱们在上一章项目中作过的同样,咱们在这个主模板(template)中包含了CSS样式。你能够在本章的示例代码中找到这些静态文件。复制示例代码中的account应用下的static/目录到你的项目中的相同位置,这样你就可使用这些静态文件了。
基础模板(template)定义了一个title和一个content区块可让被继承的子模板(template)填充内容。
让咱们为咱们的登陆表单(form)建立模板(template)。打开account/login.html模板(template)添加以下代码:
{% extends "base.html" %} {% block title %}Log-in{% endblock %} {% block content %} <h1>Log-in</h1> <p>Please, use 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 %}
这个模板(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):
如今,只填写一个字段保持另一个字段为空进行表单(from)提交。在这例子中,你会看到这个表单(form)是无效的而且显示了一个错误信息:
若是你输入了一个不存在的用户或者一个错误的密码,你会获得一个Invalid login信息。
若是你输入了有效的认证信息,你会获得一个Authenticated successfully消息:
Django在认证(authentication)框架中包含了一些开箱即用的表单(forms)和视图(views)。你以前建立的登陆视图(view)是一个很是好的练习用来理解Django中的用户认证(authentication)过程。不管如何,你能够在大部分的案例中使用默认的Django认证(authentication)视图(views)。
Django提供如下视图(views)来处理认证(authentication):
Django提供如下视图(views)来操做密码修改:
Django还包含了如下视图(views)容许用户重置他们的密码:
当你建立一个带有用户帐号的网站时,以上的视图(views)能够帮你节省不少时间。你能够覆盖这些视图(views)使用的默认值,例如须要渲染的模板位置或者视图(view)须要使用到的表单(form)。
你能够经过访问 https://docs.
djangoproject.com/en/1.8/topics/auth/default/#module-django.contrib.auth.views 获取更多内置的认证(authentication)视图(views)信息。
编辑你的account应用下的urls.py文件,以下所示:
from django.conf.urls import url from . import views urlpatterns = [ # previous login view # url(r'^login/$', views.user_login, name='login'), # login / logout urls 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'), ]
咱们将以前建立的user_login视图(view)URL模式进行注释,而后使用Django认证(authentication)框架提供的login视图(view)。
【译者注】若是使用Django 1.10以上版本,urls.py 须要改写为如下方式(参见源书提供的源代码):
from django.conf.urls import url from django.contrib.auth.views import login # With django 1.10 I need to pass the callable instead of # url(r'^login/$', 'django.contrib.auth.views.login', name='login') from django.contrib.auth.views import logout from django.contrib.auth.views import logout_then_login from django.contrib.auth.views import password_change from django.contrib.auth.views import password_change_done from django.contrib.auth.views import password_reset from django.contrib.auth.views import password_reset_done from django.contrib.auth.views import password_reset_confirm from django.contrib.auth.views import password_reset_complete from . import views urlpatterns = [ # post views # url(r'^login/$', views.user_login, name='login'), # login logout url(r'^login/$', login, name='login'), url(r'^logout/$', logout, name='logout'), url(r'^logout-then-login/$', logout_then_login, name='logout_then_login'), # change password url(r'^password-change/$', password_change, name='password_change'), url(r'^password-change/done/$', password_change_done, name='password_change_done'), # reset password ## 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应用中的template目录下建立一个新的目录命名为registration。这个路径是Django认证(authentication)视图(view)指望你的认证(authentication)模块(template)默认的存放路径。在这个新目录中建立一个新的文件,命名为login.html,而后添加以下代码:
{% extends "base.html" %} {% block title %}Log-in{% endblock %} {% block content %} <h1>Log-in</h1> {% if form.errors %} <p> Your username and password didn't match. Please try again. </p> {% else %} <p>Please, use 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> </div> {% endblock %}
这个登陆模板(template)和咱们以前建立的基本相似。Django默认使用位于django.contrib.auth.forms中的AuthenticationForm。这个表单(form)会尝试对用户进行认证,若是登陆不成功就会抛出一个验证错误。在这个例子中,若是用户提供了错误的认证信息,咱们能够在模板(template)中使用{% if form.errors %}
来找到错误。请注意,咱们添加了一个隐藏的HTML<input>
元素来提交叫作next的变量值。当你在请求(request)中传递一个next参数(举个例子:http://127.0.0.1:8000/account/login/?next=/account/),这个变量是登陆视图(view)首个设置的参数。
next参数必须是一个URL。当这个参数被给予的时候,Django登陆视图(view)将会在用户登陆完成后重定向到给予的URL。
如今,在registrtion模板(template)目录下建立一个logged_out.html模板(template)添加以下代码:
{% 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会在用户登出的时候展现这个模板(template)。
在添加了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)中添加的隐藏<input>
就是为了这个目的。
咱们还定义了一个section变量。咱们会使用该变量来跟踪用户在站点中正在查看的页面。多个视图(views)可能会对应相同的section。这是一个简单的方法用来定义每一个视图(view)对应的section。
如今,你须要建立一个给dashboard视图(view)使用的模板(template)。在templates/account/目录下建立一个新文件命名为dashboard.html。添加以下代码:
{% extends "base.html" %} {% block title %}Dashboard{% endblock %} {% block content %} <h1>Dashboard</h1> <p>Welcome to your dashboard.</p> {% endblock %}
以后,为这个视图(view)在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')
这些设置的意义:
咱们使用reverse_lazy()来经过它们的名字动态构建URL。reverse_lazy()方法就像reverse()所作的同样reverses URLs,可是你能够经过使用这种方式在你项目的URL配置被读取以前进行reverse URLs。
让咱们总结下目前为止咱们都作了哪些工做:
如今,咱们要给咱们的主模板(template)添加登陆和登出连接将全部的一切都链接起来。
为了作到这点,咱们必须肯定不管用户是已登陆状态仍是没有登陆的时候,都会显示适当的连接。经过认证(authentication)中间件当前的用户被设置在HTTP请求(request)对象中。你能够经过使用request.user
访问用户信息。你会发现一个用户对象在请求(request)中,即使这个用户并无认证经过。一个未认证的用户在请求(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> <li {% if section == "images" %}class="selected"{% endif %}> <a href="#">Images</a> </li> <li {% if section == "people" %}class="selected"{% endif %}> <a href="#">People</a> </li> </ul> {% 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> </div>
就像你所看到的,咱们只给经过认证(authentication)的用户显示站点菜单。咱们还检查当前的section并经过使用CSS来给对应的<li>
组件添加一个selected的class属性来使当前section在菜单中进行高亮显示。若是用户已经经过认证(authentication),咱们还显示用户的名字(first_name)和一个登出的连接,不然,就是一个登出连接。
如今,在浏览器中打开 http://127.0.0.1:8000/account/login/ 。你会看到登陆页面。输入可用的用户名和密码而后点击Log-in按钮。你会看到以下图所示:
你能看到经过CSS的做用 My Dashboard section 由于拥有一个 selected class 而高亮显示。由于当前用户已经经过了认证(authentication),因此用户的名字在右上角进行了显示。点击Logout连接。你会看到以下图所示:
在这个页面中,你能看到用户已经登出,而后,你没法看到当前网站的任何菜单。在右上角如今显示的是Log-in连接。
若是在你的登出页面中看到了Django管理站点的登出页面,检查项目settings.py中的INSTALLED_APPS,确保django.contrib.admin在account应用的后面。每一个模板(template)被定位在一样的相对路径时,Django模板(template)读取器将会使用它找到的第一个应用中的模板(templates)。
咱们还须要咱们的用户在登陆成功后能够进行修改密码。咱们要集成Django认证(authentication)视图(views)来修改密码。打开account应用中的urls.py文件,添加以下URL模式:
# change password urls url(r'^password-change/$', 'django.contrib.auth.views.password_change', name='password_change'), url(r'^password-change/done/$', 'django.contrib.auth.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 %} <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 %}
这个模板(template)包含了修改密码的表单(form)。如今,在相同的目录下建立另外一个文件,命名为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 %}
这个模板(template)只包含显示一条成功的信息 当用户成功的修改他们的密码。
在浏览器中打开 http://127.0.0.1:8000/account/password-change/ 。若是你的用户没有登陆,浏览器会重定向你到登陆页面。在你成功认证(authentication)登陆后,你会看到以下图所示的修改密码页面:
在表单(form)中填写你的旧密码和新密码,而后点击Change按钮。你会看到以下所示的成功页面:
登出再使用新的密码进行登陆来验证每件事是否如预期同样工做。
在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<uidb64>[-\w]+)/(?P<token>[-\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 %} <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,为它添加以下代码:
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 %} <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 %}
再建立另外一个模板(template)命名为passowrd_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"> {{ form.as_p }} {% csrf_token %} <p><input type="submit" value="Change my password" /></p> </form> {% else %} <p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p> {% endif %} {% endblock %}
在以上模板中,咱们将会检查重置连接是否有效。Django重置密码视图(view)会设置这个变量而后将它带入这个模板(template)的上下文环境中。若是重置连接有效,咱们展现用户密码重置表单(form)。
建立另外一个模板(template)命名为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模板(template),添加以下代码在<form>
元素以后:
<p><a href="{% url "password_reset" %}">Forgotten your password?</a></p>
如今,在浏览器中打开 http://127.0.0.1:8000/account/login/ 而后点击Forgotten your password?连接。你会看到以下图所示页面:
在这部分,你须要在你项目中的settings.py中添加一个SMTP配置,这样Django才能发送e-mails。咱们已经在第二章 使用高级特性来优化你的blog学习过如何添加e-mail配置。固然,在开发期间,咱们能够配置Django在标准输出中输出e-mail内容来代替经过SMTP服务发送邮件。Django提供一个e-mail后端来输出e-mail内容到控制器中。编辑项目中settings.py文件,添加以下代码:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
EMAIL_BACKEND设置这个类用来发送e-mails。
回到你的浏览器中,填写一个存在用户的e-mail地址,而后点击Send e-mail按钮。你会看到以下图所示页面:
当你运行开发服务的时候看眼控制台输出。你会看到以下所示生成的e-mail:
IME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Subject: Password reset on 127.0.0.1:8000 From: webmaster@localhost To: user@domain.com Date: Thu, 24 Sep 2015 14:35:08 -0000 Message-ID: <20150924143508.62996.55653@zenx.local> Someone asked for password reset for email user@domain.com. Follow the link below: http://127.0.0.1:8000/account/password-reset/confirm/MQ/45f-9c3f30caafd523055fcc/ Your username, in case you've forgotten: zenx
这封e-mail被咱们以前建立的password_reset_email.html模板给渲染。这个给你重置密码的URL带有一个Django动态生成的token。在浏览器中打开这个连接,你会看到以下所示页面:
这个页面对应password_reset_confirm.html模板(template)用来设置新密码。填写新的密码而后点击Change my password button。Django会建立一个新的加密后密码保存进数据库,你会看到以下图所示页面:
如今你可使用你的新密码登陆你的帐号。每一个用来设置新密码的token只能使用一次。若是你再次打开你以前获取的连接,你会获得一条信息告诉你这个token已经无效了。
你在项目中集成了Django认证(authentication)框架的视图(views)。这些视图(views)对大部分的状况都适合。固然,若是你须要一种不一样的行为你能建立你本身的视图。
现有的用户已经能够登陆,登出,修改他们的密码,以及当他们忘记密码的时候重置他们的密码。如今,咱们须要构建一个视图(view)容许访问者建立他们的帐号。
让咱们建立一个简单的视图(view)容许用户在咱们的网站中进行注册。首先,咱们须要建立一个表单(form)让用户填写用户名,他们的真实姓名以及密码。编辑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模型(model)建立了一个model表单(form)。在咱们的表单(form)中咱们只包含了模型(model)中的username,first_name,email字段。这些字段会在它们对应的模型(model)字段上进行验证。例如:若是用户选择了一个已经存在的用户名,将会获得一个验证错误。咱们还添加了两个额外的字段password和password2给用户用来填写他们的新密码和肯定密码。咱们定义了一个clean_password2()
方法去检查第二次输入的密码是否和第一次输入的保持一致,若是不一致这个表单将会是无效的。当咱们经过调用is_valid()
方法验证这个表单(form)时这个检查会被执行。你能够提供一个clean_<fieldname>()
方法给任何一个你的表单(form)字段用来清理值或者抛出表单(from)指定的字段的验证错误。表单(forms)还包含了一个clean()
方法用来验证表单(form)的全部内容,这对验证须要依赖其余字段的字段是很是有用的。
Django还提供一个UserCreationForm表单(form)给你使用,它位于django.contrib.auth.forms很是相似与咱们刚才建立的表单(form)。
编辑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})
这个建立用户帐号的视图(view)很是简单。为了保护用户的隐私,咱们使用User模型(model)的set_password()
方法将用户的原密码进行加密后再进行保存操做。
如今,编辑account应用中的urls.py文件,添加以下URL模式:
url(r'^register/$', views.register, name='register'),
最后,建立一个新的模板(template)在account/模板(template)目录下,命名为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 %}
在相同的目录中添加一个模板(template)文件命名为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/ 。你会看到你建立的注册页面:
填写用户信息而后点击Create my account按钮。若是全部的字段都验证都过,这个用户将会被建立而后会获得一条成功信息,以下所示:
点击log-in连接输入你的用户名和密码来验证帐号是否成功建立。
如今,你还能够添加一个注册连接在你的登陆模板(template)中。编辑registration/login.html模板(template)而后替换如下内容:
<p>Please, use the following form to log-in:</p>
为:
<p>Please, use the following form to log-in. If you don't have an account <a href="{% url "register" %}">register here</a></p>
如此,咱们就能够从登陆页面进入注册页面。
当你须要处理用户帐号,你会发现Django认证(authentication)框架的User模型(model)只适应通常的案例。不管如何,User模型(model)只有一些最基本的字段。你可能但愿扩展User模型包含额外的数据。最好的办法就是建立一个profile模型(model)包含全部额外的字段而且和Django的User模型(model)作一对一的关联。
编辑account应用中的model.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 'Profile for user {}'.format(self.user.username)
为了保持你的代码通用化,当须要定义模型(model)和用户模型的关系时,使用
get_user_model()
方法来取回用户模型(model)并使用AUTH_USER_MODEL设置来引用这个用户模型,替代直接引用auth的User模型(model)。
user一对一字段容许咱们关联用户和profiles。photo字段是一个ImageField字段。你须要安装一个Python包来管理图片,使用PIL(Python Imaging Library)或者Pillow(PIL的分叉),在shell中运行一下命令来安装Pillow:
pip install Pillow==2.9.0
为了Django能在开发服务中管理用户上传的多媒体文件,在项目setting.py文件中添加以下设置:
MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL 是管理用户上传的多媒体文件的主URL,MEDIA_ROOT是这些文件在本地保存的路径。咱们动态的构建这些路径相对咱们的项目路径来确保咱们的代码更通用化。
如今,编辑bookmarks项目中的主urls.py文件,修改代码以下所示:
from django.conf.urls import include, url from django.contrib import admin from django.conf import settings from django.conf.urls.static import static urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'^account/', include('account.urls')), ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
在这种方法中,Django开发服务器将会在开发时改变对多媒体文件的服务。
static()帮助函数最适合在开发环境中使用而不是在生产环境使用。绝对不要在生产环境中使用Django来服务你的静态文件。
打开终端运行如下命令来为新的模型(model)建立数据库迁移:
python manage.py makemigrations
你会得到如下输出:
Migrations for 'account': 0001_initial.py: - Create model Profile
接着,同步数据库经过如下命令:
python manage.py migrate
你会看到包含如下内容的输出:
Applying account.0001_initial... OK
编辑account应用中的admin.py文件,在管理站点注册Profiel模型(model),以下所示:
from django.contrib import admin from .models import Profile class ProfileAdmin(admin.ModelAdmin): list_display = ['user', 'date_of_birth', 'photo'] admin.site.register(Profile, ProfileAdmin)
使用python manage.py runnserver
命令从新运行开发服务。如今,你能够看到Profile模型已经存在你项目中的管理站点中,以下所示:
如今,咱们要让用户能够在网站编辑它们的profile。添加以下的模型(model)表单(forms)到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')
这两个表单(forms)的功能:
编辑account应用中的view.py文件,导入Profile模型(model),以下所示:
from .models import Profile
而后添加以下内容到register视图(view)中的new_user.save()
下方:
# Create the user profile profile = Profile.objects.create(user=new_user)
当用户在咱们的站点中注册,咱们会建立一个对应的空的profile给他们。你须要在管理站点中为以前建立的用户们一个个手动建立对应的Profile对象。
如今,咱们要让用户可以编辑他们的pfofile。添加以下代码到相同文件中:
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装饰器decorator是由于用户编辑他们的profile必须是认证经过的状态。在这个例子中,咱们使用两个模型(model)表单(forms):UserEditForm用来存储数据到内置的User模型(model)中,ProfileEditForm用来存储额外的profile数据。为了验证提交的数据,经过is_valid()
方法是否都返回True咱们来检查每一个表单(forms)。在这个例子中,咱们保存两个表单(form)来更新数据库中对应的对象。
在account应用中的urls.py文件中添加以下URL模式:
url(r'^edit/$', views.edit, name='edit'),
最后,在templates/account/中建立一个新的模板(template)命名为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 %}
咱们在表单(form)中包含
enctype="multipart/form-data"
用来支持文件上传。咱们使用一个HTML表单来提交两个表单(forms): user_form和profile_form。
注册一个新用户而后打开 http://127.0.0.1:8000/account/edit/。你会看到以下所示页面:
如今,你能够编辑dashboard页面包含编辑profile的页面连接和修改密码的页面连接。打开account/dashboard.html模板(model)替换以下代码:
<p>Welcome to your dashboard.</p>
为:
<p>Welcome to your dashboard. You can <a href="{% url "edit" %}">edit your profile</a> or <a href="{% url "password_change" %}">change your password</a>.</p>
用户如今能够从他们的dashboard访问编辑他们的profile的表单。
Django还提供一个方法可使用你本身定制的模型(model)来替代整个User模型(model)。你本身的用户类须要继承Django的AbstractUser类,这个类提供了一个抽象的模型(model)用来完整执行默认用户。你可访问 https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#substituting-a-custom-user-model来得到这个方法的更多信息。
使用一个定制的用户模型(model)将会带给你不少的灵活性,可是它也可能给一些须要与User模型(model)交互的即插即用的应用集成带来必定的困难。
当处理用户的操做时,你可能想要通知你的用户关于他们操做的结果。Django有一个内置的messages框架容许你给你的用户显示一次性的提示。messages框架位于django.contrib.messages,当你使用python manage.py startproject
命令建立一个新项目的时候,messages框架就被默认包含在settings.py文件中的INSTALLED_APPS中。你会注意到你的设置文件中在MIDDLEWARE_CLASSES设置里包含了一个名为django.contrib.messages.middleware.MessageMiddleware的中间件。messages框架提供了一个简单的方法添加消息给用户。消息被存储在数据库中而且会在用户的下一次请求中展现。你能够在你的视图(views)中导入messages模块来使用消息messages框架,用简单的快捷方式添加新的messages,以下所示:
from django.contrib import messages messages.error(request, 'Something went wrong')
你可使用add_message()
方法建立新的messages或用如下任意一个快捷方法:
让咱们显示messages给用户。由于messages框架是被项目全局应用,咱们能够在主模板(template)给用户展现messages。打开base.html模板(template)在id为header的<div>
和id为content的<div>
之间添加以下内容:
{% if messages %} <ul class="messages"> {% for message in messages %} <li class="{{ message.tags }}"> {{ message|safe }} <a href="#" class="close"> </a> </li> {% endfor %} </ul> {% endif %}
messages框架带有一个上下文环境(context)处理器用来添加一个messages变量给请求的上下文环境(context)。因此你能够在模板(template)中使用这个变量用来给用户显示当前的messages。
如今,让咱们修改edit视图(view)来使用messages框架。编辑应用中的views.py文件,使edit视图(view)以下所示:
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) # ...
当用户成功的更新他们的profile时咱们就添加了一条成功的message,但若是某个表单(form)无效,咱们就添加一个错误message。
在浏览器中打开 http://127.0.0.1:8000/account/edit/ 编辑你的profile。当profile更新成功,你会看到以下message:
当表单(form)是无效的,你会看到以下message:
Django容许你经过不一样的来源进行认证(authentication)。AUTHENTICATION_BACKENDS设置包含了全部的给你的项目的认证(authentication)后台。默认的,这个设置以下所示:
('django.contrib.auth.backends.ModelBackend',)
默认的ModelBackend经过数据库使用django.contrib.auth中的User模型(model)来认证(authentication)用户。这适用于你的大部分项目。固然,你还能够建立定制的后台经过其余的来源例如一个LDAP目录或者其余任何系统来认证你的用户。
你能够经过访问 https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#other-authentication-sources 得到更多的信息关于自定义的认证(authentication)。
当你使用django.contrib.auth的authenticate()函数,Django会经过每个定义在AUTHENTICATION_BACKENDS中的后台一个接一个地尝试认证(authentication)用户,直到其中有一个后台成功的认证该用户才会中止进行认证。只有全部的后台都没法进行用户认证(authentication),他或她才不会在你的站点中经过认证(authentication)。
Django提供了一个简单的方法来定义你本身的认证(authentication)后台。一个认证(authentication)后台就是提供了以下两种方法的一个类:
建立一个定制认证(authentication)后台很是容易,就是编写一个Python类实现上面两个方法。咱们要建立一个认证(authentication)后台让用户在咱们的站点中使用他们e-mail替代他们的用户名来进行认证(authentication)。
在你的account应用中建立一个新的文件命名为authentication.py,为它添加以下代码:
from django.contrib.auth.models import User class EmailAuthBackend(object): """ Authenticate 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: return None def get_user(self, user_id): try: return User.objects.get(pk=user_id) except User.DoesNotExist: return None
这是一个简单的认证(authentication)后台。authenticate()
方法接收了username和password两个可选参数。咱们可使用不一样的参数,可是咱们须要使用username和password来确保咱们的后台能够立马在认证(authentication)框架视图(views)中工做。以上代码完成了如下工做内容:
check_password()
方法来检查密码。这个方法会对给予密码进行哈希化来和数据库中存储的加密密码进行匹配。编辑项目中的settings.py文件添加以下设置:
AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', 'account.authentication.EmailAuthBackend', )
咱们保留默认的ModelBacked用来保证用户仍然能够经过用户名和密码进行认证,接着咱们包含进了咱们本身的email-based认证(authentication)后台。如今,在浏览器中打开 http://127.0.0.1:8000/account/login/ 。请记住,Django会对每一个后台都尝试进行用户认证(authentication),因此你可使用用户名或者使用email来进行无缝登陆。
AUTHENTICATION_BACKENDS设置中的后台排列顺序。若是相同的认证信息在多个后台都是有效的,Django会中止在第一个成功认证(authentication)经过的后台,再也不继续进行认证(authentication)。
你能够但愿给你的站点添加一些社交认证(authentication)服务,例如 Facebook,Twitter或者Google(国内就算了- -|||)。Python-social-auth是一个Python模块提供了简化的处理为你的网站添加社交认证(authentication)。经过使用这个模块,你可让你的用户使用他们的其余服务的帐号来登陆你的网站。你能够访问 https://github.com/omab/python-social-auth 获得这个模块的代码。
这个模块自带不少认证(authentication)后台给不一样的Python框架,其中就包含Django。
使用pip来安装这个包,打开终端运行以下命令:
pip install python-social-auth==0.2.12
安装成功后,咱们须要在项目settings.py文件中的INSTALLED_APPS设置中添加social.apps.django_app.default:
INSTALLED_APPS = ( #... 'social.apps.django_app.default', )
这个default应用会在Django项目中添加python-social-auth。如今,运行如下命令来同步python-social-auth模型(model)到你的数据库中:
python manage.py migrate
你会看到以下default应用的数据迁移输出:
Applying default.0001_initial... OK Applying default.0002_add_related_name... OK Applying default.0003_alter_email_max_length... OK
python-social-auth包含了不少服务的后台。你能够访问 https://python-social-auth.readthedocs.org/en/latest/backends/index.html#supported-backends 看到全部的后台支持。
咱们要包含的认证(authentication)后台包括Facebook,Twitter,Google。
你须要在你的项目中添加社交登陆URL模型。打开bookmarks项目中的主urls.py文件,添加以下URL模型:
url('social-auth/', include('social.apps.django_app.urls', namespace='social')),
为了确保社交认证(authentication)能够工做,你还须要配置一个hostname,由于有些服务不容许重定向到127.0.0.1或localhost。为了解决这个问题,在Linux或者Mac OSX下,编辑你的/etc/hosts文件添加以下内容:
127.0.0.1 mysite.com
这是用来告诉你的计算机指定mysite.com hostname指向你的本地机器。若是你使用Windows,你的hosts文件在 C:\Winwows System32\Drivers\etc\hosts。
为了验证你的host重定向是否可用,在浏览器中打开 http://mysite.com:8000/account/login/ 。若是你看到你的应用的登陆页面,host重定向已经可用。
(译者注:如下的几种社交认证操做步骤可能已通过时,请根据实际状况操做)
为了让你的用户可以使用他们的Facebook帐号来登陆你的网站,在项目settings.py文件中的AUTHENTICATION_BACKENDS设置中添加以下内容:
'social.backends.facebook.Facebook2OAuth2',
为了添加Facebook的社交认证(authentication),你须要一个Facebook开发者帐号,而后你必须建立一个新的Facebook应用。在浏览器中打开 https://developers.facebook.com/apps/?action=create 点击Add new app按钮。点击Website平台而后为你的应用取名为Bookmarks,输入 http://mysite.com:8000/ 做为你的网站URL。跟随快速开始步骤而后点击Create App ID。
回到网站的Dashboard。你会看到相似下图所示的:
拷贝App ID和App Secret关键值,将它们添加在项目中的*settings.py**文件中,以下所示:
SOCIAL_AUTH_FACEBOOK_KEY = 'XXX' # Facebook App ID SOCIAL_AUTH_FACEBOOK_SECRET = 'XXX' # Facebook App Secret
此外,你还能够定义一个SOCIAL_AUTH_FACEBOOK_SCOPE设置若是你想要访问Facebook用户的额外权限,例如:
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
最后,打开registration/login.html模板(template)而后添加以下代码到content block中:
<div class="social"> <ul> <li class="facebook"><a href="{% url "social:begin" "facebook" %}">Sign in with Facebook</a></li> </ul> </div>
在浏览器中打开 http://mysite.com:8000/account/login/ 。如今你的登陆页面会以下图所示:
点击**Login with Facebook按钮。你会被重定向到Facebook,而后你会看到一个对话询问你的权限是否让Bookmarks*应用访问你的公共Facebook profile:
点击Okay按钮。Python-social-auth会对认证(authentication)进行操做。若是每一步都没有出错,你会登陆成功而后被重定向到你的网站的dashboard页面。请记住,咱们已经使用过这个URL在LOGIN_REDIRECT_URL设置中。就像你所看到的,在你的网站中添加社交认证(authentication)是很是简单的。
为了使用Twitter进行认证(authentication),在项目settings.py中的AUTHENTICATION_BACKENDS设置中添加以下内容:
'social.backends.twitter.TwitterOAuth',
你须要在你的Twitter帐户中建立一个新的应用。在浏览器中打开 https://apps.twitter.com/app/new 而后输入你应用信息,包含如下设置:
确保你勾选了复选款Allow this application to be used to Sign in with Twitter。以后点击Keys and Access Tokens。你会看到以下所示信息:
拷贝Consumer Key和Consumer Secret关键值,将它们添加到项目settings.py的设置中,以下所示:
SOCIAL_AUTH_TWITTER_KEY = 'XXX' # Twitter Consumer Key SOCIAL_AUTH_TWITTER_SECRET = 'XXX' # Twitter Consumer Secret
如今,编辑login.html模板(template),在<ul>
元素中添加以下代码:
<li class="twitter"><a href="{% url "social:begin" "twitter" %}">Login with Twitter</a></li>
在浏览器中打开 http://mysite.com:8000/account/login/ 而后点击Login with Twitter连接。你会被重定向到Twitter而后它会询问你受权给应用,以下所示:
点击Authorize app按钮。你会登陆成功而且重定向到你的网站dashboard页面。
Google提供OAuth2认证(authentication)。你能够访问 https://developers.google.com/accounts/docs/OAuth2 得到关于Google OAuth2的信息。
首先,你徐闯建立一个API key在你的Google开发者控制台。在浏览器中打开 https://console.developers.google.com/project 而后点击Create project按钮。输入一个名字而后点击Create按钮,以下所示:
在项目建立以后,点击在左侧菜单的APIs & auth连接,而后点击Credentials部分。点击Add credentials按钮,而后选择OAuth2.0 client ID,以下所示:
Google首先会询问你配置赞成信息页面。这个页面将会展现给用户告知他们是否赞成使用他们的Google帐号来登陆访问你的网站。点击Configure consent screen按钮。选择你的e-mail地址,填写Bookmarks为Product name,而后点击Save按钮。这个给你的项目使用的赞成信息页面将会配置完成而后你会被重定向去完成建立你的Client ID。
在表单(form)中填写如下内容:
这表单(form)将会以下所示:
点击Create按钮。你将会得到Client ID和Client Secret关键值。在你的settings.py中添加它们,以下所示:
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '' # Google Consumer Key SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '' # Google Consumer Secret
在Google开发者控制台的左方菜单,APIs & auth部分的下方,点击APIs连接。你会看到包含全部Google Apis的列表。点击 Google+ API而后点击Enable API按钮在如下页面中:
编辑login.html模板(template)在<ul>
元素中添加以下代码:
<li class="google"><a href="{% url "social:begin" "google" %}">Login with Google</a></li>
在浏览器中打开 http://mysite.com:8000/account/login/ 。登陆页面将会看上去以下图所示:
点击Login with Google按钮。你将会被重定向到Google而且被询问权限经过咱们以前配置的赞成信息页面:
点击Accept按钮。你会登陆成功并重定向到你的网站的dashboard页面。
咱们已经添加了社交认证(authentication)到咱们的项目中。python-social-auth模块还包含更多其余很是热门的在线服务。
在本章中,你学习了如何建立一个认证(authentication)系统到你的网站而且建立了定制的用户profile。你还为你的网站添加了社交认证(authentication)。
在下一章中,你会学习如何建立一个图片收藏系统(image bookmarking system),生成图片缩微图,建立AJAX视图(views)。
终于写到了这里,呼出一口气,第四章的页数是前几章的两倍,在翻译以前还有点担忧会不会坚持不下去,不过看样子我仍是坚持了下来,并且发现一旦翻译起来就不想中止(- -|||莫非心中真的有翻译之魂!?)。这一章仍是比较基础,主要介绍了集成用户的认证系统到网站中,比较有用的是经过第三方的平台帐户登陆,惋惜3个平台Facbook,Twitter,Google国内都很差访问,你们练习的时候仍是用国内的QQ,微信,新浪等平台来练习吧。第五章的翻译不清楚何时能完成,也许过年前也可能过年后,反正无论如何,这本书我必定要翻译到最后!
最后,请保佑我公司年会让我抽到特大奖,集各位祈祷之力,哈哈哈哈!