第 8 章:用户账户¶
我们的博客应用还有一个主要领域需要添加:用户账户。我们有一个 author 字段,但目前用户无法注册、登录、注销等。从头开始实现用户身份验证是出了名的困难,不建议这样做!这是你真的不想出错的一个领域,因为由此产生的所有安全隐患。幸运的是,Django 有一个强大的、经过实战检验的内置用户身份验证系统,我们可以根据需要使用和自定义。
每当你创建一个新项目时,Django 默认安装 auth 应用,它为我们提供了一个包含以下内容的 User 对象:
- username
- password
- first_name
- last_name
我们将使用这个 User 对象在我们的博客应用中实现登录、注销和注册。
登录¶
Django 通过 LoginView 为我们提供了登录页面的默认视图。我们需要添加的只是一个 auth 系统的 URL 模式、一个登录模板,以及对 django_project/settings.py 文件的小更新。
首先,更新 django_project/urls.py 文件。我们将登录和注销页面放在 accounts/ URL 上:在倒数第二行添加一行。
# django_project/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("django.contrib.auth.urls")), # new
path("", include("blog.urls")),
]
正如 LoginView 文档所述,默认情况下,Django 会在 templates 目录中名为 registration 的目录下查找一个名为 login.html 的登录表单文件。所以我们需要在 templates 中创建一个名为 registration 的新目录以及其中的必要文件。从命令行输入 Control+c 来退出我们的本地服务器。然后,创建新目录。
(.venv) $ mkdir templates/registration
然后,使用你的文本编辑器创建一个新的模板文件 templates/registration/login.html,填入以下代码:
<!-- templates/registration/login.html -->
{% extends "base.html" %}
{% block content %}
<h2>Log In</h2>
<form method="post">{% csrf_token %}
{{ form }}
<button type="submit">Log In</button>
</form>
{% endblock content %}
我们使用 HTML <form></form> 标签并指定 POST 方法,因为我们正在向服务器发送数据(如果我们是在请求数据,例如在搜索引擎表单中,我们会使用 GET)。我们出于安全考虑添加 {% csrf_token %} 以防止 CSRF 攻击,然后包含一个"submit"按钮。
最后一步是指定用户成功登录后重定向到哪里。我们可以使用 LOGIN_REDIRECT_URL 设置来设定。在 django_project/settings.py 文件底部添加以下内容:
# django_project/settings.py
LOGIN_REDIRECT_URL = "home" # new
现在用户将被重定向到"home"模板,即我们的主页。我们现在实际上已经完成了!如果你现在使用 python manage.py runserver 再次启动 Django 服务器,并导航到 http://127.0.0.1:8000/accounts/login/ 的登录页面,你会看到以下内容:

输入我们超级用户账户的登录信息后,我们被重定向到主页。

请注意,我们没有添加任何视图逻辑或创建数据库模型,因为 Django auth 系统自动为我们提供了两者。谢谢你,Django!
让我们更新我们的 base.html 模板,以便无论用户是否登录,我们都向他们显示一条消息。我们可以使用 is_authenticated 属性来实现。
现在,将此代码放在一个显眼的位置。稍后,我们可以更适当地对其进行样式设置。从 </header> 结束标签下方开始,使用新代码更新 base.html 文件。
<!-- templates/base.html -->
...
<body>
<div>
<header>
<div class="nav-left">
<h1><a href="{% url 'home' %}">Django blog</a></h1>
</div>
<div class="nav-right">
<a href="{% url 'post_new' %}">+ New Blog Post</a>
</div>
</header>
<!-- start new HTML... -->
{% if user.is_authenticated %}
<p>Hi {{ user.username }}!</p>
{% else %}
<p>You are not logged in.</p>
<a href="{% url 'login' %}">Log In</a>
{% endif %}
<!-- end new HTML... -->
{% block content %}
{% endblock content %}
</div>
</body>
</html>
如果用户已登录,我们按名字向他们问好;如果没有,我们提供一个指向我们新创建的登录页面的链接。

成功了!我的超级用户名称是 wsv,我在页面上看到了它。
注销链接¶
Django 5.0 的一个主要更改,正如发行说明中所指出的,是移除了对通过 GET 请求注销的支持。以前,你可以在模板文件中添加一个注销链接,如 <a href="{% url 'logout' %}">Log Out</a>。但现在需要通过表单发送 POST 请求。
在 home.html 文件的"Hi {{ user.username }}!"部分下面添加一个注销按钮。
<!-- templates/base.html-->
...
{% if user.is_authenticated %}
<p>Hi {{ user.username }}!</p>
<form action="{% url 'logout' %}" method="post">
{% csrf_token %}
<button type="submit">Log Out</button>
</form>
{% else %}
...
Django auth 应用为我们提供了必要的视图,所以我们需要做的就是指定用户注销时重定向到哪里。更新 django_project/settings.py 以提供一个名为 LOGOUT_REDIRECT_URL 的重定向链接。我们可以将它添加到登录重定向旁边,所以文件底部应该如下所示:
# django_project/settings.py
LOGIN_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "home" # new
如果你刷新主页,你会看到它现在有一个已登录用户的"log out"按钮。

点击它会带你回到带有登录链接的主页。

尝试使用你的用户账户多次登录和注销。
注册¶
Django 没有为注册提供内置的视图、URL 或模板。一个常见的方法是为此创建一个专门的应用,这里称为 accounts,但在其他项目中有时也称为 users。这样,如果我们将来需要进一步自定义用户身份验证过程,它只包含在一个地方。
在命令行上,使用 Control+c 停止本地服务器,并创建一个专门的新应用 accounts。
(.venv) $ python manage.py startapp accounts
然后,将新应用添加到 django_project/settings.py 文件中的 INSTALLED_APPS 设置,以便它注册到 Django。
# django_project/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"blog",
"accounts", # new
]
接下来,在 django_project/urls.py 中添加一个新的 URL 路径,指向这个新应用,就在我们引入内置 auth 应用的位置下方。
# django_project/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("django.contrib.auth.urls")),
path("accounts/", include("accounts.urls")), # new
path("", include("blog.urls")),
]
我们 URL 的顺序在这里很重要,因为 Django 从上到下读取这个文件。因此当我们请求 /accounts/signup URL 时,Django 会先在 auth 中查找,找不到,然后继续到 accounts 应用。
在你的文本编辑器中,创建一个名为 accounts/urls.py 的文件并添加以下代码:
# accounts/urls.py
from django.urls import path
from .views import SignUpView
urlpatterns = [
path("signup/", SignUpView.as_view(), name="signup"),
]
我们使用一个尚未创建的视图 SignUpView,我们已经知道它是基于类的,因为它是大写的并且有 as_view() 后缀。它的路径只是 signup/,所以完整的 URL 路径将是 accounts/signup/。
对于视图,Django 有一个内置的表单类 UserCreationForm,它包含三个字段:username、password1 和 password2。我们可以将它与 CreateView 一起使用来创建我们的注册页面。将默认的 accounts/views.py 代码替换为以下内容:
# accounts/views.py
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy
from django.views.generic import CreateView
class SignUpView(CreateView):
form_class = UserCreationForm
success_url = reverse_lazy("login")
template_name = "registration/signup.html"
我们在 SignUpView 类中子类化了泛型类视图 CreateView,并指定了 UserCreationForm、尚未创建的模板 registration/signup.html。我们还使用 reverse_lazy 在成功注册后将用户重定向到登录页面。
为什么在这里使用 reverse_lazy 而不是 reverse?对于泛型类视图,URL 在文件导入时不会被加载,所以我们必须使用 reverse 的惰性形式来在它们可用时加载。
在你的文本编辑器中创建文件 templates/registration/signup.html 并用以下代码填充它。
<!-- templates/registration/signup.html -->
{% extends "base.html" %}
{% block content %}
<h2>Sign Up</h2>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<button type="submit">Sign Up</button>
</form>
{% endblock content %}
这个格式与我们之前做的非常相似。我们在顶部扩展基础模板,将逻辑放在 <form></form> 标签之间,加上 as_p 使其用段落标签渲染,使用 csrf_token 用于安全,并包含一个提交按钮。
我们完成了!要测试它,使用 python manage.py runserver 命令启动本地服务器,然后导航到 http://127.0.0.1:8000/accounts/signup/。

请注意 Django 默认包含的大量额外文本。我们可以使用类似内置消息框架之类的方法来自定义此内容,但现在,试试这个表单。
我创建了一个名为"william"的新用户,提交后被重定向到登录页面。使用我的新用户名和密码成功登录后,我被重定向到主页,并显示我们个性化的"Hi username"问候。

因此,我们的最终流程是:注册 -> 登录 -> 主页。当然,我们可以按照自己的意愿调整它。SignUpView 重定向到登录页面是因为我们设置了 success_url = reverse_lazy('login')。登录页面重定向到主页是因为在我们的 django_project/settings.py 文件中,我们设置了 LOGIN_REDIRECT_URL = 'home'。
最初可能会感到难以跟踪 Django 项目的所有各个部分,但这是正常的。随着时间推移,它们会变得更加合理。
注册链接¶
我们可以做的最后一个改进是向已注销的主页添加一个注册链接。毕竟,我们不能指望用户知道正确的 URL!我们怎么做呢?嗯,我们需要找出 URL 名称,然后就可以将它放入我们的模板中。在 accounts/urls.py 中,我们给它提供了名称 signup,所以这就是我们需要使用 url 模板标签添加到我们的 base.html 模板中的全部内容,就像我们其他链接一样。
在现有的"Log In"链接下方添加"Sign Up"链接如下:
<!-- templates/base.html-->
...
<p>You are not logged in.</p>
<a href="{% url 'login' %}">Log In</a> |
<a href="{% url 'signup' %}">Sign Up</a>
...
如果你导航到已注销的主页,注册链接将是可见的。好多了!

GitHub¶
我们已经有一段时间没有进行 git 提交了。让我们现在来做,然后将代码副本推送到 GitHub。首先,使用 git status 检查我们所有的新工作。
(.venv) $ git status
然后添加新内容并输入提交消息。
(.venv) $ git add -A
(.venv) $ git commit -m "forms, user accounts, and static files"
在 GitHub 上创建一个新仓库并按照推荐的步骤操作。我在这里选择了名称 blog;我的用户名是 wsvincent。确保在设置远程 origin 的命令中使用你自己的 GitHub 用户名和仓库名称。
(.venv) $ git remote add origin https://github.com/wsvincent/blog.git
(.venv) $ git branch -M main
(.venv) $ git push -u origin main
全部搞定!
结论¶
通过少量代码,我们为我们的网站添加了一个强大的用户身份验证流程:登录、注销和注册。在底层,Django 已经处理了如果你从头创建用户身份验证流程可能会出现的许多安全陷阱。博客网站现在已经完成。它在设计上是有意极简的,但用户身份验证和完整 CRUD 功能的组合存在于大多数网站中。
在下一章中,我们将开始本书的最后一个项目:一个包含更多功能的报社网站。