19.2 创建用户账户

本节将建立用户注册和身份验证系统,让用户能够注册账户,进而登录和注销。为此,我们将新建一个应用程序,其中包含与处理用户账户相关的所有功能。这个应用程序将尽可能使用Django自带的用户身份验证系统来完成工作。本节还将对模型Topic 稍做修改,让每个主题都归属于特定用户。

19.2.1 应用程序users

首先使用命令startapp 创建一个名为users 的应用程序:

(llenv)learninglog$ python manage.py startapp users
(ll_env)learning_log$ ls
❶ db.sqlite3 learning_log learning_logs ll_env manage.py users
(ll_env)learning_log$ ls users
❷ __init
.py admin.py apps.py migrations models.py tests.py views.py


这个命令新建一个名为users的目录(见❶),其结构与应用程序learning_logs 相同(见❷)。

19.2.2 将users 添加到settings.py中

在settings.py中,需要将这个新的应用程序添加到INSTALLED_APPS 中,如下所示:

settings.py

—snip—
INSTALLED_APPS = [
# 我的应用程序
'learning_logs',
'users',

# Django默认创建的应用程序
—snip—
]
—snip—


这样,Django将把应用程序users 包含到项目中。

19.2.3 包含users 的URL

接下来,需要修改项目根目录中的urls.py,使其包含将为应用程序users 定义的URL:

urls.py

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

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


我们添加了一行代码,以包含应用程序users 中的文件urls.py。这行代码与任何以单词users打头的URL(如http://localhost:8000/users/login/)都匹配。

19.2.4 登录页面

首先来实现登录页面。我们将使用Django提供的默认视图login ,因此这个应用程序的URL模式稍有不同。在目录learning_log/users/中,新建一个名为urls.py的文件,并在其中添加如下代码:

urls.py

"""为应用程序users定义URL模式。"""

from django.urls import path, include

❶ app_name = 'users'
urlpatterns = [
# 包含默认的身份验证URL。
❷ path('', include('django.contrib.auth.urls')),
]


导入函数path 和include ,以便包含Django定义的一些默认的身份验证URL。这些默认的URL包含具名的URL模式,如'login' 和'logout' 。我们将变量app_name 设置成'users' ,让Django能够将这些URL与其他应用程序的URL区分开来(见❶)。即便是Django提供的默认URL,将其包含在应用程序users 的文件中后,也可通过命名空间users 进行访问。

登录页面的URL模式与URL http://localhost:8000/users/login/匹配(见❷)。这个URL中的单词users让Django在users/urls.py中查找,而单词login让它将请求发送给Django的默认视图login

  • 模板login.html

用户请求登录页面时,Django将使用一个默认的视图函数,但我们依然需要为这个页面提供模板。默认的身份验证视图在文件夹registration中查找模板,因此我们需要创建这个文件夹。为此,在目录learning_log/users/中新建一个名为templates的目录,再在这个目录中新建一个名为registration的目录。下面是模板login.html,应将其存储到目录learning_log/users/templates/ registration中:

login.html

{% extends "learning_logs/base.html" %}

{% block content %}

❶ {% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}

❷ <form method="post" action="{% url 'users:login' %}">
{% csrf_token %}
❸ {{ form.as_p }}

❹ <button name="submit">Log in</button>
❺ <input type="hidden" name="next"
value="{% url 'learning_logs:index' %}"
>
<
form>

{% endblock content %}


这个模板继承了base.html,旨在确保登录页面的外观与网站的其他页面相同。请注意,一个应用程序中的模板可继承另一个应用程序中的模板。

如果设置表单的errors 属性,就显示一条错误消息(见❶),指出输入的用户名密码对与数据库中存储的任何用户名密码对都不匹配。

我们要让登录视图对表单进行处理,因此将实参action 设置为登录页面的URL(见❷)。登录视图将一个表单发送给模板。在模板中,我们显示这个表单(见❸)并添加一个提交按钮(见❹)。在❺处,包含了一个隐藏的表单元素'next' ,其中的实参value 告诉Django在用户成功登录后将其重定向到什么地方。在本例中,用户将返回主页。

  • 链接到登录页面

下面在base.html中添加到登录页面的链接,让所有页面都包含它。用户已登录时,我们不想显示这个链接,因此将它嵌套在一个{% if %} 标签中:

base.html

<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a> -
<a href="{% url 'learning_logs:topics' %}">Topics</a> -
❶ {% if user.is_authenticated %}
❷ Hello, {{ user.username }}.
{% else %}
❸ <a href="{% url 'users:login' %}">Log in</a>
{% endif %}
</p>

{% block content %}{% endblock content %}


在Django身份验证系统中,每个模板都可使用变量user 。这个变量有一个is_authenticated 属性:如果用户已登录,该属性将为True ,否则为False 。这让你能够向已通过身份验证的用户显示一条消息,而向未通过身份验证的用户显示另一条消息。

这里向已登录的用户显示问候语(见❶)。对于已通过身份验证的用户,还设置了属性username 。这里使用该属性来个性化问候语,让用户知道自己已登录(见❷)。在❸处,对于尚未通过身份验证的用户,显示到登录页面的链接。

  • 使用登录页面

前面建立了一个用户账户,下面来登录一下,看看登录页面是否管用。请访问http://localhost:8000/admin/,如果你依然是以管理员身份登录的,请在页眉上找到注销链接并单击它。

注销后,访问http://localhost:8000/users/login/将看到类似于图19-4所示的登录页面。输入你在前面设置的用户名和密码,将进入索引页面。在这个主页的页眉中,显示了一条个性化问候语,其中包含你的用户名。

19.2 创建用户账户 - 图1

图19-4 登录页面

19.2.5 注销

现在需要提供一个让用户注销的途径。为此,我们将在base.html中添加一个注销链接。用户单击这个链接时,将进入一个确认其已注销的页面。

  • 在base.html中添加注销链接

下面在base.html中添加注销链接,让每个页面都包含它。将注销链接放在{% if user.is_authenticated %} 部分中,这样只有已登录的用户才能看到它:

base.html

—snip—
{% if user.is_authenticated %}
Hello, {{ user.username }}.
<a href="{% url 'users:logout' %}">Log out</a>
{% else %}
—snip—


默认的具名注销URL模式为'logout' 。

  • 注销确认页面

成功注销后,用户希望获悉这一点。因此默认的注销视图使用模板logged_out.html渲染注销确认页面,我们现在就来创建该模板。下面这个简单的页面确认用户已注销,请将其存储到目录templates/registration(login.html所在的目录)中:

logged_out.html

{% extends "learning_logs/base.html" %}

{% block content %}
<p>You have been logged out. Thank you for visiting!</p>
{% endblock content %}


在这个页面中,不需要提供其他内容,因为base.html提供了到主页和登录页面的链接。

图19-5显示了用户单击Log out链接后出现的注销确认页面。这里的重点是创建能够正确工作的网站,因此几乎没有设置样式。确定所需的功能都能正确运行后,我们将设置这个网站的样式,使其看起来更专业。

19.2 创建用户账户 - 图2

图19-5 注销确认页面指出用户已成功注销

19.2.6 注册页面

下面来创建一个页面供新用户注册。我们将使用Django提供的表单UserCreationForm ,但编写自己的视图函数和模板。

  • 注册页面的URL模式

下面的代码定义了注册页面的URL模式,它也包含在users/urls.py中:

urls.py

"""为应用程序users定义URL模式。"""

from django.urls import path, include

from . import views

app_name = 'users'
urlpatterns = [
# 包含默认的身份验证URL。
path('', include('django.contrib.auth.urls')),
# 注册页面
path('register/', views.register, name='register'),
]


我们从users 中导入模块views 。为何需要这样做呢?因为我们将为注册页面编写视图函数。注册页面的URL模式与URL http://localhost:8000/users/register/匹配,并将请求发送给即将编写的函数register() 。

  • 视图函数register()

在注册页面首次被请求时,视图函数register() 需要显示一个空的注册表单,并在用户提交填写好的注册表单时对其进行处理。如果注册成功,这个函数还需让用户自动登录。请在users/views.py中添加如下代码:

views.py

from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm

def register(request):
"""注册新用户。"""
if request.method != 'POST':
# 显示空的注册表单。
❶ form = UserCreationForm()
else:
# 处理填写好的表单。
❷ form = UserCreationForm(data=request.POST)

❸ if form.is_valid():
❹ new_user = form.save()
# 让用户自动登录,再重定向到主页。
❺ login(request, new_user)
❻ return redirect('learning_logs:index')

# 显示空表单或指出表单无效。
context = {'form': form}
return render(request, 'registration/register.html', context)


首先导入函数render() 和redirect() ,然后导入函数login() ,以便在用户正确填写了注册信息时让其自动登录。我们还导入了默认表单UserCreationForm 。在函数register() 中,检查要响应的是否是POST请求。如果不是,就创建一个UserCreationForm 实例,且不给它提供任何初始数据(见❶)。

如果响应的是POST请求,就根据提交的数据创建一个UserCreationForm 实例(见❷),并检查这些数据是否有效(见❸)。就本例而言,有效是指用户名未包含非法字符,输入的两个密码相同,以及用户没有试图做恶意的事情。

如果提交的数据有效,就调用表单的方法save() ,将用户名和密码的散列值保存到数据库中(见❹)。方法save() 返回新创建的用户对象,我们将它赋给了new_user 。保存用户的信息后,调用函数login() 并传入对象request 和new_user ,为用户创建有效的会话,从而让其自动登录(见❺)。最后,将用户重定向到主页(见❻),而主页的页眉中显示了一条个性化的问候语,让用户知道注册成功了。

在这个函数的末尾,我们渲染了注册页面:它要么显示一个空表单,要么显示提交的无效表单。

  • 注册模板

下面来创建注册页面的模板,它与登录页面的模板类似。请务必将其保存到login.html所在的目录中:

register.html

{% extends "learning_logs/base.html" %}

{% block content %}

<form method="post" action="{% url 'users:register' %}">
{% csrf_token %}
{{ form.as_p }}

<button name="submit">Register</button>
<input type="hidden" name="next" value="{% url 'learning_logs:index' %}"
>
<
form>

{% endblock content %}


这里也使用了方法as_p ,让Django在表单中正确地显示所有的字段,包括错误消息——如果用户没有正确地填写表单。

  • 链接到注册页面

下面来添加一些代码,在用户没有登录时显示到注册页面的链接:

base.html

—snip—
{% if user.is_authenticated %}
Hello, {{ user.username }}.
<a href="{% url 'users:logout' %}">Log out</a>
{% else %}
<a href="{% url 'users:register' %}">Register</a> -
<a href="{% url 'users:login' %}">Log in</a>
{% endif %}
—snip—


现在,已登录的用户看到的是个性化的问候语和注销链接,而未登录的用户看到的是注册链接和登录链接。请尝试使用注册页面创建几个用户名各不相同的用户账户。

下一节会将一些页面限制为仅让已登录的用户访问,还将确保每个主题都归属于特定用户。

注意  这里的注册系统允许用户创建任意数量的账户。有些系统要求用户确认其身份:发送一封确认邮件,用户回复后账户才生效。通过这样做,这些系统会比本例的简单系统生成更少的垃圾账户。然而,学习创建应用程序时,完全可以像这里所做的那样,使用简单的用户注册系统。
动手试一试
练习19-2:博客账户  在为完成练习19-1而开发的项目Blog中,添加用户身份验证和注册系统。向已登录的用户显示其用户名,向未注册的用户显示到注册页面的链接。