评论和互动可以开放给访客;作者管理、附件上传、后台统计都需要身份。我先补上注册、登录、登出,让导航根据登录状态变化。

注册成功后马上建立会话;登录成功进入首页;登出只接受 POST,并回到首页。
注册表单扩展 Django 自带表单
不要手写密码哈希。Django 的 UserCreationForm 已经处理两次密码校验和密码安全规则。
1
2
3
4
5
6
7
8
9
10
| from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class UserRegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta(UserCreationForm.Meta):
model = User
fields = ["username", "email"]
|
只暴露用户名和邮箱。is_staff、is_superuser 都不能让访客提交。
注册视图先挡住已登录用户。
1
2
3
4
5
6
7
8
9
10
11
12
| from django.contrib.auth import login
def register(request):
if request.user.is_authenticated:
return redirect("blog:list")
form = UserRegistrationForm(request.POST or None)
if request.method == "POST" and form.is_valid():
user = form.save()
login(request, user)
messages.success(request, "注册成功,欢迎加入。")
return redirect("blog:list")
return render(request, "registration/register.html", {"form": form})
|
form.save() 会把密码转成哈希。视图拿到的是已创建的 User,再用 login() 建立会话。这样用户注册完不需要再走一次登录页。
登录和登出用 Django 内置视图。
1
2
3
4
5
6
7
| from django.contrib.auth.views import LoginView, LogoutView
class BlogLoginView(LoginView):
template_name = "registration/login.html"
class BlogLogoutView(LogoutView):
pass
|
设置里声明跳转目标。
1
2
| LOGIN_REDIRECT_URL = "blog:list"
LOGOUT_REDIRECT_URL = "blog:list"
|
LoginView 会在成功后跳到 LOGIN_REDIRECT_URL,也会处理 ?next=。如果是管理员从受保护页面跳来,登录后还能回到原来的地方。
登出必须是 POST
Django 5 之后,LogoutView 只接受 POST。导航里的退出也写成表单。
1
2
3
4
5
6
7
8
9
10
| {% if user.is_authenticated %}
<span>{{ user.username }}</span>
<form method="post" action="{% url 'blog:logout' %}">
{% csrf_token %}
<button type="submit">退出</button>
</form>
{% else %}
<a href="{% url 'blog:login' %}">登录</a>
<a href="{% url 'blog:register' %}">注册</a>
{% endif %}
|
如果退出用 GET,第三方页面可以塞一张图片或链接,让用户不经确认就被登出。POST 加 CSRF token 是更稳的边界。
登录模板继承公共骨架。
1
2
3
4
5
6
7
8
9
10
11
12
13
| {% extends "base.html" %}
{% block title %}登录{% endblock %}
{% block page %}
<section class="content auth-page">
<h1>登录</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">登录</button>
</form>
<p>还没有账号?<a href="{% url 'blog:register' %}">注册</a></p>
</section>
{% endblock %}
|
注册模板结构相同,表单交给 UserRegistrationForm。
测试注册、登录、登出
注册测试检查数据库和登录提示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
class AuthTests(TestCase):
def test_register_logs_user_in(self):
response = self.client.post(reverse("blog:register"), {
"username": "reader",
"email": "reader@example.com",
"password1": "strong-pass-2026",
"password2": "strong-pass-2026",
}, follow=True)
self.assertContains(response, "注册成功,欢迎加入。")
self.assertTrue(
get_user_model().objects.filter(username="reader").exists()
)
|
登录测试检查导航里出现用户名。
1
2
3
4
5
6
7
8
9
10
11
12
13
| def test_login_and_logout(self):
get_user_model().objects.create_user(
username="editor",
password="strong-pass-2026",
)
response = self.client.post(reverse("blog:login"), {
"username": "editor",
"password": "strong-pass-2026",
}, follow=True)
self.assertContains(response, "editor")
response = self.client.post(reverse("blog:logout"), follow=True)
self.assertNotContains(response, "退出")
|
没有单独测试 Django 密码校验器的每条规则,那是框架已经保证的部分。我们要守住的是注册真的能创建用户,登录真的能建立会话,登出真的能清除状态。
运行测试。
本阶段共 25 条测试。
1
2
3
| Ran 25 tests in 5.055s
OK
|
浏览器打开首页,未登录时看到登录和注册;注册后导航显示用户名和退出按钮。

前台投稿页还没加。当前作者仍在后台创建文章;认证先把身份和会话准备好,附件上传和权限控制后面都建立在这个基础上。
源码
GitHub 地址:https://github.com/yidao620c/simpleblog