第 8 章:用户账户
第 8 章:用户账户
还有一个主要的领域需要添加到我们的博客应用中:用户账户。我们有一个 author 字段,但目前还没有可以让用户注册、登录、注销等功能。从头开始实现用户认证是出了名的困难,而且不推荐!这是一个你绝对不想犯错的地方,因为会带来所有的安全后果。幸运的是,Django 有一个强大、久经考验的内置用户认证系统,我们可以根据需要进行使用和自定义。
每当你创建一个新项目时,Django 默认会安装 auth 应用,它为我们提供了一个 User 对象,包含:
username(用户名)password(密码)email(电子邮件)first_name(名)last_name(姓)
我们将使用这个 User 对象在博客应用中实现登录、注销和注册功能。
登录
Django 通过 LoginView 为我们提供了一个默认的登录页面视图。我们需要添加的只是认证系统的 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")), # 新增
path("", include("blog.urls")),
]正如 LoginView 文档所指出的,默认情况下 Django 会在名为 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" # 新增现在用户将被重定向到”home”模板,也就是我们的首页。至此,我们实际上已经完成了!如果你现在使用 python manage.py runserver 启动 Django 服务器,并导航到登录页面 http://127.0.0.1:8000/accounts/login/,你会看到以下内容:

输入超级用户账户的登录信息后,我们将被重定向到首页。注意,我们没有添加任何视图逻辑或创建数据库模型,因为 Django 认证系统自动为我们提供了这两者。谢谢 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>
<!-- 开始新 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 %}
<!-- 结束新 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" # 新增如果你刷新首页,你会看到现在已登录用户有一个”Log Out”按钮。

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

尝试用你的用户账户登录和注销几次。
注册
Django 没有提供内置的注册视图、URL 或模板。一种常见的方法是为此创建一个专门的 app,在这里称为 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", # 新增
]接下来,在 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")), # 新增
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 默认包含的大量额外文本。我们可以使用类似内置的 messages 框架来自定义它,但就目前而言,可以试试这个表单。
我创建了一个名为”william”的新用户,提交后我被重定向到了登录页面。用新的用户名和密码成功登录后,我被重定向到了首页,看到了个性化的”Hi username”问候语。

因此,我们的最终流程是:注册 -> 登录 -> 首页。当然,我们可以随意调整这一点。SignUpView 重定向到登录页面,因为我们设置了 success_url = reverse_lazy('login')。登录页面重定向到首页,因为我们在 django_project/settings.py 文件中设置了 LOGIN_REDIRECT_URL = 'home'。
起初,跟踪 Django 项目的所有各个部分可能会让人不知所措,但这是正常的。随着时间的推移,它们会开始变得更有意义。
注册链接
我们可以做的最后一项改进是在已注销的首页上添加一个注册链接。毕竟,我们不能指望用户知道正确的 URL!怎么做呢?我们需要找出 URL 名称,然后可以将其放入我们的模板中。在 accounts/urls.py 中,我们为其提供了 signup 的名称,所以我们只需要将其添加到 base.html 模板中,使用 url 模板标签,就像我们对其他链接所做的一样。
在现有的”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。请确保在设置远程源的命令中使用你自己的 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 功能的组合存在于大多数网站中。
在下一章中,我们将开始本书的最后一个项目:一个包含更多功能的报纸网站。