创建社交网站

Posted 一个处女座的测试

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了创建社交网站相关的知识,希望对你有一定的参考价值。

第四章 创建社交网站

在之前的章节学习了如何创建站点地图、订阅信息和创建一个全文搜索引擎。这一章我们来开发一个社交网站。会创建用户登录、登出、修改和重置密码功能,为用户创建额外的用户信息,以及使用第三方身份认证登录。

本章包含以下内容:

  • 使用Django内置验证模块

  • 创建用户注册视图

  • 使用自定义的用户信息表扩展用户模型

  • 添加第三方身份认证系统

我们来创建本书的第二个项目。

1社交网站

我们将创建一个社交网站,让用户可以把网上看到的图片分享到网站来。这个社交网站包含如下功能:

  • 一个供用户注册、登录、登出、修改和重置密码的用户身份验证系统,还能够让用户自行填写用户信息

  • 关注系统,让用户可以关注其他用户

  • 一个JS小书签工具,让用户可以将外部的图片分享(上传)到本站

  • 一个追踪系统,让用户可以看到他所关注的用户的上传内容

本章涉及到其中的第一个内容:用户身份验证系统。

1.1启动社交网站项目

启动系统命令行,输入下列命令创建并激活一个虚拟环境:

Copymkdirenv
virtualenv env/bookmarks
sourceenv/bookmarks/bin/activate

终端会显示当前的虚拟环境,如下:

Copy(bookmarks)laptop:~ zenx$

在终端中安装Django并启动bookmarks项目:

Copypip install Django==2.0.5
django-admin startproject bookmarks

然后到项目根目录内创建account应用:

Copycd bookmarks/
django-admin startapp account

然后在settings.py中的INSTALLED_APPS设置中激活该应用:

CopyINSTALLED_APPS = [
    'account.apps.AccountConfig',
    # ...
]

这里将我们的应用放在应用列表的最前边,原因是:我们稍后会为自己的应用编写验证系统的模板,Django内置的验证系统自带了一套模板,如此设置可以让我们的模板覆盖其他应用中的模板设置。Django按照INSTALLED_APPS中的顺序寻找模板。

之后执行数据迁移过程。

译者注:新创建的Django项目默认依然使用Python的SQLlite数据库,建议读者为每个项目配置一个新创建的数据库。推荐使用上一章的PostgreSQL,因为本书之后还会使用PostgreSQL。

2使用Django内置验证框架

django提供了一个验证模块框架,具备用户验证,会话控制(session),权限和用户组功能并且自带一组视图,用于控制常见的用户行为如登录、登出、修改和重置密码。

验证模块框架位于django.contrib.auth,也被其他Django的contrib库所使用。在第一章里创建超级用户的时候,就使用到了验证模块。

使用startproject命令创建一个新项目时,验证模块默认已经被设置并启用,包括INSTALLED_APPS设置中的django.contrib.auth应用,和MIDDLEWARE设置中的如下两个中间件:

  • AuthenticationMiddleware:将用户与HTTP请求联系起来

  • SessionMiddleware:处理当前HTTP请求的session

中间件是一个类,在接收HTTP请求和发送HTTP响应的阶段被调用,在本书的部分内容中会使用中间件,第十三章上线中会学习开发自定义中间件。

验证模块还包括如下数据模型:

  • User:一个用户数数据表,包含如下主要字段:username,password,email,first_name,last_name和is_active。

  • Group:一个用户组表格

  • Permission:存放用户和组的权限清单

验证框架还包括默认的验证视图以及对应表单,稍后会使用到。

2.1创建登录视图

从这节开始使用Django的验证模块,一个登录视图需要如下功能:

  • 通过用户提交的表单获取用户名和密码

  • 将用户名和密码与数据库中的数据进行匹配

  • 检查用户是否处于活动状态

  • 通过在HTTP请求上附加session,让用户进入登录状态

首先需要创建一个登录表单,在account应用内创建forms.py文件,添加以下内容:

Copyfrom django import forms

classLoginForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField(widget=forms.PasswordInput)

这是用户输入用户名和密码的表单。由于一般密码框不会明文显示,这里采用了widget=forms.PasswordInput,令其在页面上显示为一个type="password"的INPUT元素。

然后编辑account应用的views.py文件,添加如下代码:

Copyfrom django.shortcuts import render, HttpResponse
from django.contrib.auth import authenticate, login
from .forms import LoginForm

defuser_login(request):
    if request.method == "POST":
        form = LoginForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            user = authenticate(request, username=cd['username'], password=cd['password'])
            if user isnotNone:
                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请求,通过form = LoginForm()实例化一个空白表单;如果接收到POST请求,则进行如下工作:

  1. 通过form = LoginForm(request.POST),使用提交的数据实例化一个表单对象。

  1. 通过调用form.is_valid()验证表单数据。如果未通过,则将当前表单对象展示在页面中。

  1. 如果表单数据通过验证,则调用内置authenticate()方法。该方法接受request对象,username和password三个参数,之后到数据库中进行匹配,如果匹配成功,会返回一个User数据对象;如果未找到匹配数据,返回None。在匹配失败的情况下,视图返回一个登陆无效信息。

  1. 如果用户数据成功通过匹配,则根据is_active属性检查用户是否为活动用户,这个属性是Django内置User模型的一个字段。如果用户不是活动用户,则返回一个消息显示不活动用户。

  1. 如果用户是活动用户,则调用login()方法,在会话中设置用户信息,并且返回登录成功的消息。

注意区分内置的authenticate()和login()方法。authenticate()仅到数据库中进行匹配并且返回User数据对象,其工作类似于进行数据库查询。而login()用于在当前会话中设置登录状态。二者必须搭配使用才能完成用户名和密码的数据验证和用户登录的功能。

现在需要为视图设置路由,在account应用下创建urls.py,添加如下代码:

Copyfrom django.urls import path
from . import views

urlpatterns = [
    path('login/', views.user_login, name='login'),
]

然后编辑项目的根ulrs.py文件,导入include并且增加一行转发到account应用的二级路由配置:

Copyfrom django.conf.urls import path, include
from django.contrib import admin

urlpatterns = [
    path('admin/', admin.site.urls),
    path('account/', include('account.urls')),
]

之后需要配置模板。由于项目还没有任何模板,可以先创建一个母版,在account应用下创建如下目录和文件结构:

Copytemplates/
    account/
        login.html
    base.html

编辑base.html,添加下列代码:

Copy% load staticfiles %
<!DOCTYPE html><html><head><title>% block title %% endblock %</title><linkhref="% static "css/base.css" %" rel="stylesheet"></head><body><divid="header"><spanclass="logo">Bookmarks</span></div><divid="content">
        % block content %
        % endblock %
    </div></body></html>

这是这个项目使用的母版。和上一个项目一样使用了CSS文件,你需要把static文件夹从源码复制到account应用目录下。这个母版有一个title块和一个content块用于继承。

译者注:原书第一章使用了% load static %,这里的模板使用了% load staticfiles %,作者并没有对这两者的差异进行说明,读者可以参考What is the difference between % load staticfiles % and % load static %

之后编写account/login.html:

Copy% extends 'base.html' %

% block title %Log-in% endblock %

% block content %
<h1>Log-in</h1><p>Please, use the following form to log-in:</p><formaction="."method="post">
     form.as_p 
    % csrf_token %
    <p><inputtype="submit"value="Log in"></p></form>
% endblock %

这是供用户填写登录信息的页面,由于表单通过Post请求提交,所以需要% csrf_token %。

我们的站点还没有任何用户,建立一个超级用户,然后使用超级用户到http://127.0.0.1:8000/admin/登录,会看到默认的管理后台:

使用管理后台添加一个用户,然后打开http://127.0.0.1:8000/account/login/,可以看到如下登录界面:

填写刚创建的用户信息并故意留空表单然后提交,可以看到错误信息如下:

注意和第一章一样,很可能一些现代浏览器会阻止表单提交,修改模板关闭表单的浏览器验证即可。

再进行一些实验,如果输入不存在的用户名或密码,会得到无效登录的提示,如果输入了正确的信息,就会看到如下的登录成功信息:

2.2使用内置验证视图

Django内置很多视图和表单可供直接使用,上一节的登录视图就是一个很好的例子。在大多数情况下都可以使用Django内置的验证模块而无需自行编写。

Django在django.contrib.auth.views中提供了如下基于类的视图供使用:

  • LoginView:处理登录表单填写和登录功能(和我们写的功能类似)

  • LogoutView:退出登录

  • PaswordChangeView:处理一个修改密码的表单,然后修改密码

  • PasswordChangeDoneView:成功修改密码后执行的视图

  • PasswordResetView:用户选择重置密码功能执行的视图,生成一个一次性重置密码链接和对应的验证token,然后发送邮件给用户

  • PasswordResetDoneView:通知用户已经发送给了他们一封邮件重置密码

  • PasswordResetConfirmView:用户设置新密码的页面和功能控制

  • PasswordResetCompleteView:成功重置密码后执行的视图

上边的视图列表按照一般处理用户相关功能的顺序列出相关视图,在编写带有用户功能的站点时可以参考使用。这些内置视图的默认值可以被修改,比如渲染的模板位置和使用的表单等。

可以通过官方文档https://docs.djangoproject.com/en/2.0/topics/auth/default/#all-authentication-views了解更多内置验证视图的信息。

2.3登录与登出视图

由于直接使用内置视图和内置数据模型,所以不需要编写模型与视图,来为内置登录和登出视图配置URL,编辑account应用的urls.py文件,注释掉之前的登录方法,改成内置方法:

Copyfrom django.urls import path
from django.contrib.auth import views as auth_views
from . import views

urlpatterns = [
    # path('login/', views.user_login, name='login'),
    path('login/',auth_views.LoginView.as_view(),name='login'),
    path('logout/',auth_views.LogoutView.as_view(),name='logout'),
]

现在我们把登录和登出的URL导向了内置视图,然后需要为内置视图建立模板

在templates目录下新建registration目录,这个目录是内置视图默认到当前应用的模板目录里寻找具体模板的位置。

django.contrib.admin模块中自带一些验证模板,用于管理后台使用。我们在INSTALLED_APPS中将account应用放到admin应用的上边,令django默认使用我们编写的模板。

在templates/registration目录下创建login.html并添加如下代码:

Copy% 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 %

    <divclass="login-form"><formaction="% url 'login' %"method="post">
             form.as_p 
            % csrf_token %
            <inputtype="hidden"name="next"value=" next "><p><inputtype="submit"value="Log-in"></p></form></div>

% endblock %

这个模板和刚才自行编写登录模板很类似。内置登录视图默认使用django.contrib.auth.forms里的AuthenticationForm表单,通过检查% if form.errors %可以判断验证信息是否错误。注意我们添加了一个name属性为next的隐藏<input>元素,这是内置视图通过Get请求获得并记录next参数的位置,用于返回登录前的页面,例如http://127.0.0.1:8000/account/login/?next=/account/

next参数必须是一个URL地址,如果具有这个参数,登录视图会在登录成功后将用户重定向到这个参数的URL。

在registration目录下创建logged_out.html:

Copy% extends 'base.html' %

% block title %
Logged out
% endblock %

% block content %
<h1>Logged out</h1><p>You have been successfully logged out. You can <ahref="% url 'login' %">log-in again</a>.</p>
% endblock %

这是用户登出之后显示的提示页面。

现在我们的站点已经可以使用用户登录和登出的功能了。现在还需要为用户制作一个登录成功后自己的首页,打开account应用的views.py文件,添加如下代码:

Copyfrom django.contrib.auth.decorators import login_required
@login_requireddefdashboard(request):
    return render(request, 'account/dashboard.html', 'section': 'dashboard')

使用@login_required装饰器,表示被装饰的视图只有在用户登录的情况下才会被执行,如果用户未登录,则会将用户重定向至Get请求附加的next参数指定的URL。这样设置之后,如果用户在未登录的情况下,无法看到首页。

还定义了一个参数section,可以用来追踪用户当前所在的功能板块。

现在可以创建首页对应的模板,在templates/account/目录下创建dashboard.html:

Copy% extends 'base.html' %

% block title %
Dashboard
% endblock %

% block content %
    <h1>Dashboard</h1><p>Welcome to your dashboard.</p>
% endblock %

然后在account应用的urls.py里增加新视图对应的URL:

Copyurlpatterns = [
    # ...
    path('', views.dashboard, name='dashboard'),
]

还需要在settings.py里增加如下设置:

CopyLOGIN_REDIRECT_URL = 'dashboard'
LOGIN_URL = 'login'
LOGOUT_URL = 'logout'

这三个设置分别表示:

  • 如果没有指定next参数,登录成功后重定向的URL

  • 用户需要登录的情况下被重定向到的URL地址(例如@login_required重定向到的地址)

  • 用户需要登出的时候被重定向到的URL地址

这里都使用了path()方法中的name属性,以动态的返回链接。在这里也可以硬编码URL。

总结一下我们现在做过的工作:

  • 为项目添加内置登录和登出视图

  • 为两个视图编写模板并编写了首页视图和对应模板

  • 为三个视图配置了URL

最后需要在母版上添加登录和登出相关的展示。为了实现这个功能,必须根据当前用户是否登录,决定模板需要展示的内容。在内置函数LoginView成功执行之后,验证模块的中间件在HttpRequest对象上设置了用户对象User,可以通过request.user访问用户信息。在用户未登录的情况下,request.user也存在,是一个AnonymousUser类的实例。判断当前用户是否登录最好的方式就是判断User对象的is_authenticated只读属性。

编辑base.html,修改ID为header的<div>标签:

Copy<divid="header"><spanclass="logo">Bookmarks</span>
    % if request.user.is_authenticated %
    <ulclass="menu"><li % ifsection == 'dashboard' %class="selected"% endif %><ahref="% url 'dashboard' %">My dashboard</a></li><li % ifsection == 'images' %class="selected"% endif %><ahref="#">Images</a></li><li % ifsection == 'people' %class="selected"% endif %><ahref="#">People</a></li></ul>
    % endif %

    <spanclass="user">
        % if request.user.is_authenticated %
        Hello  request.user.first_name , request.user.username ,<ahref="% url 'logout' %">Logout</a>
            % else %
            <ahref="% url 'login' %">Log-in</a>
        % endif %
 </span></div>

上边的视图只显示站点的菜单给已登录用户。还添加了了根据section的内容为<li>添加CSS类selected的功能,用于显示高亮当前的板块。最后对登录用户显示名称和登出链接,对未登录用户则显示登录链接。

现在启动项目,到http://127.0.0.1:8000/account/login/,会看到登录页面,输入有效的用户名和密码并点击登录按钮,之后会看到如下页面:

可以看到当前的 My dashboard 应用了selected类的CSS样式。当前用户的信息显示在顶部的右侧,点击登出链接,会看到如下页面:

可以看到用户已经登出,顶部的菜单栏已经不再显示,右侧的链接变为登录链接。

如果这里看到Django内置的管理站点样式的页面,检查settings.py文件中的INSTALLED_APPS设置,确保account应用在django.contrib.admin应用的上方。由于内置的视图和我们自定义的视图使用了相同的相对路径,Django的模板加载器会使用先找到的模板。

2.4修改密码视图

在用户登录之后需要允许用户修改密码,我们在项目中集成Django的内置修改密码相关的视图。编辑account应用的urls.py文件,添加如下两行URL:

Copypath('password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'),

asswordChangeView视图会控制渲染修改密码的页面和表单,PasswordChangeDoneView视图在成功修改密码之后显示成功消息。

之后要为两个视图创建模板,在templates/registration/目录下创建password_change_form.html,添加如下代码:

Copy% extends 'base.html' %

% block title %
Change your password
% endblock %

% block content %
<h1>Change your password</h1><p>Use the form below to change your password.</p><formaction="."method="post"novalidate>
     form.as_p 
    <p><inputtype="submit"value="Change"></p>
    % csrf_token %
    </form>
% endblock %

password_change_form.html模板包含修改密码的表单,再在同一目录下创建password_change_done.html:

Copy% extends 'base.html' %

% block title %
Password changed
% endblock %

% block content %
<h1>Password changed</h1>
    <p>Your password has been successfully changed.</p>
% endblock %

password_change_done.html模板包含成功创建密码后的提示消息。

启动服务,到http://127.0.0.1:8000/account/password_change/,成功登录之后可看到如下页面:

填写表单并修改密码,之后可以看到成功消息:

之后登出再登录,验证是否确实成功修改密码。

2.5重置密码视图

编辑account应用的urls.py文件,添加如下对应到内置视图的URL:

Copypath('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),

然后在account应用的templates/registration/目录下创建password_reset_form.html:

Copy% 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><formaction="."method="post"novalidate>
     form.as_p 
    % csrf_token %
    <p><inputtype="submit"value="Send e-mail"></p></form>
% endblock %

在同一目录下创建发送邮件的页面password_reset_email.html,添加如下代码:

CopySomeone 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 

这个模板用来渲染向用户发送的邮件内容。

之后在同一目录再创建password_reset_done.html,表示成功发送邮件的页面:

Copy% 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,这个页面是用户从邮件中打开链接后经过视图处理后返回的页面:

Copy% 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><formaction="."method="post">
             form.as_p 
            % csrf_token %
            <p><inputtype="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 %

这个页面里有一个变量validlink,表示用户点击的链接是否有效,由PasswordResetConfirmView视图传入模板。如果有效就显示重置密码的表单,如果无效就显示一段文字说明链接无效。

在同一目录内建立password_reset_complete.html:

Copy% extends "base.html" %
% block title %Password reset% endblock %
% block content %
<h1>Password set</h1><p>Your password has been set. You can <ahref="% url "login" %">log in
now</a></p>
% endblock %

最后编辑registration/login.html,在<form>元素之后加上如下代码,为页面增加重置密码的链接:

Copy<p><ahref="% url 'password_reset' %">Forgotten your password?</a></p>

之后在浏览器中打开http://127.0.0.1:8000/account/login/,点击Forgotten your password?链接,会看到如下页面:

这里必须在settings.py中配置SMTP服务器,在第二章中已经学习过配置STMP服务器的设置。如果确实没有SMTP服务器,可以增加一行:

CopyEMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

以让Django将邮件内容输出到命令行窗口中。

返回浏览器,填入一个已经存在的用户的电子邮件地址,之后点SEND E-MAIL按钮,会看到如下页面:

此时看一下启动Django站点的命令行窗口,会打印如下邮件内容(或者到信箱中查看实际收到的电子邮件):

CopyContent-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Password reset on127.0.0.1:8000From: webmaster@localhostTo: user@domain.com
Date: Fri, 15Dec201714: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/reset/MQ/45f-9c3f30caafd523055fcc/
Your username, incase you've forgotten: zenx

这个邮件的内容就是password_reset_email.html经过渲染之后的实际内容。其中的URL指向视图动态生成的链接,将这个URL复制到浏览器中打开,会看到如下页面:

这个页面使用password_reset_confirm.html模板生成,填入一个新密码然后点击CHANGE MY PASSWORD按钮,Django会用你输入的内容生成加密后的密码保存在数据库中,然后会看到如下页面:

现在就可以使用新密码登录了。这里生成的链接只能使用一次,如果反复打开该链接,会收到无效链接的错误。

我们现在已经集成了Django内置验证模块的主要功能,在大部分情况下,可以直接使用内置验证模块。也可以自行编写所有的验证程序。

在第一个项目中,我们提到为应用配置单独的二级路由,有助于应用的复用。现在的account应用的urls.py文件中所有配置到内置视图的URL,可以用如下一行来代替:

Copyurlpatterns = [
    # ...
    path('', include('django.contrib.auth.urls')),
]

可以在github上看到django.contrib.auth.urls的源代码:https://github.com/django/django/blob/stable/2.0.x/django/contrib/auth/urls.py

3用户注册与用户信息

已经存在的用户现在可以登录、登出、修改和重置密码了。现在需要建立一个功能让用户注册。

3.1用户注册

为用户注册功能创建一个简单的视图:先建立一个供用户输入用户名、姓名和密码的表单。编辑account应用的forms.py文件,添加如下代码:

Copyfrom django.contrib.auth.models import User

classuserRegistrationForm(forms.ModelForm):
    password = forms.CharField(label='password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Repeat password', widget=forms.PasswordInput)

    classMeta:
        model = User
        fields = ('username','first_name','email')

    defclean_password2(self):
        cd = self.cleaned_data
        if cd['password'] != cd['password2']:
            raise forms.ValidationError(r"Password don't match.")
        return cd['password2']

这里通过用户模型建立了一个模型表单,只包含username,first_name和email字段。这些字段会根据User模型中的设置进行验证,比如如果输入了一个已经存在的用户名,则验证不会通过,因为username字段被设置了unique=True。添加了两个新的字段password和password2,用于用户输入并且确认密码。定义了一个clean_password2()方法用于检查两个密码是否一致,这个方法是一个验证器方法,会在调用is_valid()方法的时候执行。可以对任意的字段采用clean_<fieldname>()方法名创建一个验证器。Forms类还拥有一个clean()方法用于验证整个表单,可以方便的验证彼此相关的字段。

译者注:这里必须了解表单的验证顺序。clean_password2()方法中使用了cd['password2'];为什么验证器还没有执行完毕的时候,cleaned_data中已经存在password2数据了呢?这里有一篇介绍django验证表单顺序的文章,可以看到,在执行自定义验证器之前,已经执行了每个字段的clean()方法,这个方法仅针对字段本身的属性进行验证,只要这个通过了,cleaned_data中就有了数据,之后才执行自定义验证器,最后执行form.clean()完成验证。如果过程中任意时候抛出ValidationError,cleaned_data里就会只剩有效的值,errors属性内就有了错误信息。

关于用户注册,Django提供了一个位于django.contrib.auth.forms的UserCreationForm表单供使用,和我们自行编写的表单非常类似。

编辑account应用的views.py文件,添加如下代码:

Copyfrom .forms import LoginForm, UserRegistrationForm

defregister(request):
    if request.method == "POST":
        user_form = UserRegistrationForm(request.POST)
        if user_form.is_valid():
            # 建立新数据对象但是不写入数据库
            new_user = user_form.save(commit=False)
            # 设置密码
            new_user.set_password(user_form.cleaned_data['password'])
            # 保存User对象
            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)

这个视图逻辑很简单,我们使用了set_password()方法设置加密后的密码。

再配置account应用的urls.py文件,添加如下的URL匹配:

Copypath('register/', views.register, name='register'),

在templates/account/目录下创建模板register.html,添加如下代码:

Copy% 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><formaction="."method="post"novalidate>
     user_form.as_p 
    % csrf_token %
    <p><inputtype="submit"value="Register"></p></form>
% endblock %

在同一目录下创建register_done.html模板,用于显示注册成功后的信息:

Copy% 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 <ahref="% url 'login' %">log in</a>.</p>
% endblock %

现在可以打开http://127.0.0.1:8000/account/register/,看到注册界面如下:

填写表单并点击CREATE MY ACCOUNT按钮,如果表单正确提交,会看如下成功页面:

3.2扩展用户模型

Django内置验证模块的User模型只有非常基础的字段信息,可能需要额外的用户信息。最好的方式是建立一个用户信息模型,然后通过一对一关联字段,将用户信息模型和用户模型联系起来。

编辑account应用的models.py文件,添加以下代码:

Copyfrom django.db import models
from django.conf import settings

classProfile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    date_of_birth = models.DateField(blank=True, null=True)
    photo = models.ImageField(upload_to='user/%Y/%m/%d/', blank=True)

    def__str__(self):
        return"Profile for user ".format(self.user.username)

为了保持代码通用性,使用get_user_model()方法来获取用户模型;当定义其他表与内置User模型的关系时使用settings.AUTH_USER_MODEL指代User模型。

这个Profile模型的user字段是一个一对一关联到用户模型的关系字段。将on_delete设置为CASCADE,当用户被删除时,其对应的信息也被删除。这里还有一个图片文件字段,必须安装Python的Pillow库才能使用图片文件字段,在系统命令行中输入:

Copypip install Pillow==5.1.0

由于我们要允许用户上传图片,必须配置Django让其提供媒体文件服务,在settings.py中加入下列内容:

CopyMEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

MEDIA_URL表示存放和提供用户上传文件的URL路径,MEDIA_ROOT表示实际媒体文件的存放目录。这里都采用相对地址动态生成URL。

来编辑一下bookmarks项目的根urls.py,修改其中的代码如下:

Copyfrom django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('account/', include('account.urls')),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)

这样设置后,Django开发服务器在DEBUG=True的情况下会提供媒体文件服务。

static()方法仅用于开发环境,在生产环境中,不要用Django提供静态文件服务(而是用Web服务程序比如nginx等提供静态文件服务)。

建立了新的模型之后需要执行数据迁移过程。之后将新的模型加入到管理后台,编辑account应用的admin.py文件,将Profile模型注册到管理后台中:

Copyfrom django.contrib import admin
from .models import Profile

@admin.register(Profile)classProfileAdmin(admin.ModelAdmin):
    list_display = ['user', 'date_of_birth', 'photo']

启动站点,打开http://127.0.0.1:8000/admin/,可以在管理后台中看到新增的模型:

现在需要让用户填写额外的用户信息,为此需要建立表单,编辑account应用的forms.py文件:

Copyfrom .models import Profile

classUserEditForm(forms.ModelForm):
    classMeta:
        model = User
        fields = ('first_name', 'last_name', 'email')

classProfileEditForm(forms.ModelForm):
    classMeta:
        model = Profile
        fields = ('date_of_birth', 'photo')

这两个表单解释如下:

  • UserEditForm:这个表单依据User类生成,让用户输入姓,名和电子邮件。

  • ProfileEditForm:这个表单依据Profile类生成,可以让用户输入生日和上传一个头像。

之后建立视图,编辑account应用的views.py文件,导入Profile模型:

Copyfrom .models import Profile

然后在register视图的new_user.save()下增加一行:

CopyProfile.objects.create(user=new_user)

当用户注册的时候,会自动建立一个空白的用户信息关联到用户。在之前创建的用户,则必须在管理后台中手工为其添加对应的Profile对象

还必须让用户可以编辑他们的信息,在同一个文件内添加下列代码:

Copyfrom .forms import LoginForm, UserRegistrationForm, UserEditForm, ProfileEditForm

@login_requireddefedit(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存放Profile类的数据。然后调用is_valid()验证两个表单数据,如果全部都通过,将使用save()方法写入数据库。

译者注:原书没有解释instance参数。instance用于指定表单类实例化为某个具体的数据对象。在这个例子里,将UserEditForm``instance指定为request.user表示该对象是数据库中当前登录用户那一行的数据对象,而不是一个空白的数据对象,ProfileEditForm的instance属性指定为当前用户对应的Profile类中的那行数据。这里如果不指定instance参数,则变成向数据库中增加两条新记录,而不是修改原有记录。

之后编辑account应用的urls.py文件,为新视图配置URL:

Copypath('edit/', views.edit, name='edit'),

最后,在templates/account/目录下创建edit.html,添加如下代码:

Copy#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><formaction="."method="post"enctype="multipart/form-data"novalidate>
     user_form.as_p 
     profile_form.as_p 
    % csrf_token %
        <p><inputtype="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,找到下边这行:

Copy<p>Welcome to your dashboard.</p>

将其替换为:

Copy<p>Welcome to your dashboard. You can <ahref="% url 'edit' %">edit your profile</a> or <ahref="% url "password_change" %">change your password</a>.</p>

用户现在可以通过登录后的首页修改用户信息,打开http://127.0.0.1:8000/account/然后可以看到新增了修改用户信息的链接,页面如下:

3.2.1使用自定义的用户模型

Django提供了使用自定义的模型替代内置User模型的方法,需要编写自定义的类继承AbstractUser类。这个AbstractUser类提供了默认的用户模型的完整实现,作为一个抽象类供其他类继承。关于模型的继承将在本书最后一个项目中学习。可以在https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#substituting-a-custom-user-model找到关于自定义用户模型的详细信息。

使用自定义用户模型比起默认内置用户模型可以更好的满足开发需求,但需要注意的是会影响一些使用Django内置用户模型的第三方应用。

3.3使用消息框架

当用户在我们的站点执行各种操作时,在一些关键操作可能需要通知用户其操作是否成功。Django有一个内置消息框架可以给用户发送一次性的通知。

消息模块位于django.contrib.messages,并且已经被包含在初始化的INSTALLED_APPS设置中,还有一个默认启用的中间件叫做django.contrib.messages.middleware.MessageMiddleware,共同构成了消息系统。

消息框架提供了非常简单的方法向用户发送通知:默认在cookie中存储消息内容(根据session的存储设置),然后会在下一次HTTP请求的时候在对应的响应上附加该信息。导入消息模块并且在视图中使用很简单的语句就可以发送消息,例如:

Copyfrom django.contrib import messages
messages.error(request, 'Something went wrong')

这样就在请求上附加了一个错误信息。可以使用add_message()或如下的方法创建消息:

  • success():一个动作成功之后发送的消息

  • info():通知性质的消息

  • warning():警告性质的内容,所谓警告就是还没有失败但很可能失败的情况

  • error():错误信息,通知操作失败

  • debug():除错信息,给开发者展示,在生产环境中需要被移除

在我们的站点中增加消息内容。由于消息是贯穿整个网站的,所以打算将消息显示的部分设置在母版中,编辑base.html,在ID为header的<div>标签和ID为content的<div>标签之间增加下列代码:

Copy% if messages %
    <ulclass="messages">
        % for message in messages %
            <liclass=" message.tags "> message|safe <ahref="#"class="close">X</a></li>
        % endfor %
    </ul>
% endif %

在模板中使用了messages变量,在后文可以看到视图并未向模板传入该变量。这是因为在settings.py中的TEMPLATES设置中,context_processors的设置中包含django.contrib.messages.context_processors.messages这个上下文管理器,从而为模板传入了messages变量,而无需经过视图。默认情况下可以看到还有debug,request和auth三个上下文处理器。其中后两个就是我们在模板中可以直接使用request.user而无需传入该变量,也无需为request对象添加user属性的原因。

之后来修改account应用的views.py文件,导入messages,然后编辑edit视图:

Copyfrom django.contrib import messages

@login_requireddefedit(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()
            messages.success(request, 'Profile updated successfully')
        else:
            messages.error(request, "Error updating your profile")
    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)

为视图增加了两条语句,分别在成功登录之后显示成功信息,在表单验证失败的时候显示错误信息。

浏览器中打开http://127.0.0.1:8000/account/edit/,编辑用户信息,之后可以看到成功信息如下:

故意填写通不过验证的数据,则可以看到错误信息如下:

关于

以上是关于创建社交网站的主要内容,如果未能解决你的问题,请参考以下文章

如何:社交媒体网站 [关闭]

创建社交网站

创建社交网站

测试 Django 电子邮件后端

有哪些大型公司在使用 Python?有哪些知名项目是主要由 Python ...

django是前端还是后端