mvp v2

加入了用户注册

开始做 MVP v2:用户系统 + 文章作者关联 + 登录用户发布文章

这一版目标是:

用户可以注册
用户可以登录
用户可以登出
文章可以关联作者
只有登录用户可以发布文章
文章列表和详情页显示作者

暂时不做:

编辑文章
删除文章
评论系统
搜索
分类标签
复杂权限

第 1 步:创建 accounts 应用

在项目根目录执行:

python manage.py startapp accounts

现在会多一个目录:

accounts
├── admin.py
├── apps.py
├── models.py
├── tests.py
├── views.py
└── migrations

然后打开 config/settings.py,在 INSTALLED_APPS 里加入:

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",

    "blog",
    "accounts",
]

作用是告诉 Django:

这个项目现在多了一个 accounts 用户模块,请加载它。

第 2 步:配置登录相关跳转

config/settings.py 底部加入:

LOGIN_URL = "accounts:login"
LOGIN_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "home"

含义:

LOGIN_URL:未登录用户访问受保护页面时,跳到哪里登录
LOGIN_REDIRECT_URL:登录成功后跳到哪里
LOGOUT_REDIRECT_URL:登出后跳到哪里

第 3 步:创建注册表单

accounts 目录下新建:

accounts/forms.py

写入:

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User


class RegisterForm(UserCreationForm):
    email = forms.EmailField(
        required=False,
        label="邮箱",
        help_text="选填。以后可以用于找回密码。"
    )

    class Meta:
        model = User
        fields = ["username", "email", "password1", "password2"]

这里使用的是 Django 内置的 UserCreationForm

它会自动处理:

用户名
密码
二次确认密码
密码强度校验
密码加密存储

你不用自己写密码加密逻辑。

第 4 步:写注册、登出视图

打开 accounts/views.py,写入:

from django.contrib import messages
from django.contrib.auth import login, logout
from django.shortcuts import redirect, render
from django.views.decorators.http import require_POST

from .forms import RegisterForm


def register_view(request):
    if request.method == "POST":
        form = RegisterForm(request.POST)

        if form.is_valid():
            user = form.save()
            login(request, user)
            messages.success(request, "注册成功,已自动登录。")
            return redirect("home")
    else:
        form = RegisterForm()

    return render(request, "accounts/register.html", {"form": form})


@require_POST
def logout_view(request):
    logout(request)
    messages.success(request, "你已成功登出。")
    return redirect("home")

这里做了两件事:

register_view:处理用户注册
logout_view:处理用户登出

注意登出使用 POST,不是普通链接跳转,这更安全。

第 5 步:创建 accounts 路由

accounts 目录下新建:

accounts/urls.py

写入:

from django.contrib.auth.views import LoginView
from django.urls import path

from . import views

app_name = "accounts"

urlpatterns = [
    path("register/", views.register_view, name="register"),
    path("login/", LoginView.as_view(template_name="accounts/login.html"), name="login"),
    path("logout/", views.logout_view, name="logout"),
]

这里登录使用 Django 内置的 LoginView

第 6 步:接入总路由

打开 config/urls.py,改成类似这样:

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

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", views.home, name="home"),
    path("blog/", include("blog.urls")),
    path("accounts/", include("accounts.urls")),
]

现在会有这些地址:

/accounts/register/    注册
/accounts/login/       登录
/accounts/logout/      登出,POST 请求

第 7 步:创建注册和登录模板

templates 下创建目录:

templates/accounts

创建:

templates/accounts/register.html

写入:

{% extends "base.html" %}

{% block title %}注册 - 我的 Django 博客{% endblock %}

{% block content %}
<section class="form-page">
    <h1>注册账号</h1>

    <form method="post" class="form-card">
        {% csrf_token %}

        {{ form.as_p }}

        <button class="button" type="submit">注册</button>
    </form>

    <p>
        已有账号?
        <a href="{% url 'accounts:login' %}">去登录</a>
    </p>
</section>
{% endblock %}

再创建:

templates/accounts/login.html

写入:

{% extends "base.html" %}

{% block title %}登录 - 我的 Django 博客{% endblock %}

{% block content %}
<section class="form-page">
    <h1>登录</h1>

    <form method="post" class="form-card">
        {% csrf_token %}

        {{ form.as_p }}

        <button class="button" type="submit">登录</button>
    </form>

    <p>
        没有账号?
        <a href="{% url 'accounts:register' %}">去注册</a>
    </p>
</section>
{% endblock %}

第 8 步:修改导航栏,显示登录状态

打开 templates/base.html,找到导航栏部分,改成类似这样:

<nav class="nav">
    <a href="{% url 'home' %}">首页</a>
    <a href="{% url 'blog:post_list' %}">博客</a>

    {% if user.is_authenticated %}
        <a href="{% url 'blog:post_create' %}">发布文章</a>
        <span class="nav-user">你好,{{ user.username }}</span>

        <form method="post" action="{% url 'accounts:logout' %}" class="logout-form">
            {% csrf_token %}
            <button type="submit">登出</button>
        </form>
    {% else %}
        <a href="{% url 'accounts:login' %}">登录</a>
        <a href="{% url 'accounts:register' %}">注册</a>
    {% endif %}

    <a href="/admin/">后台</a>
</nav>

这个逻辑是:

如果用户已登录:
显示用户名、发布文章、登出

如果用户未登录:
显示登录、注册

第 9 步:给 Post 添加 author 作者字段

打开 blog/models.py

先在顶部加入:

from django.conf import settings

然后在 Post 模型里加入:

author = models.ForeignKey(
    settings.AUTH_USER_MODEL,
    on_delete=models.CASCADE,
    related_name="posts",
    verbose_name="作者",
    null=True,
    blank=True,
)

完整结构大概变成:

from django.conf import settings
from django.db import models
from django.urls import reverse


class Post(models.Model):
    title = models.CharField("标题", max_length=200)
    slug = models.SlugField("URL 标识", unique=True)
    summary = models.TextField("摘要", blank=True)
    content = models.TextField("正文 Markdown")

    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="posts",
        verbose_name="作者",
        null=True,
        blank=True,
    )

    created_at = models.DateTimeField("创建时间", auto_now_add=True)
    updated_at = models.DateTimeField("更新时间", auto_now=True)
    is_published = models.BooleanField("是否发布", default=True)

    class Meta:
        ordering = ["-created_at"]
        verbose_name = "文章"
        verbose_name_plural = "文章"

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("blog:post_detail", kwargs={"slug": self.slug})

这里暂时使用:

null=True
blank=True

原因是你 MVP v1 里可能已经有旧文章。旧文章没有作者,如果直接强制作者不能为空,迁移时会要求你给旧数据指定作者。初学阶段先允许旧文章作者为空,后面再慢慢规范。

第 10 步:迁移数据库

因为你修改了 models.py,所以要执行:

python manage.py makemigrations
python manage.py migrate

这一步会给数据库里的 blog_post 表增加一个 author_id 字段。

第 11 步:更新 Admin 后台显示作者

打开 blog/admin.py,把 author 加入列表:

from django.contrib import admin
from .models import Post


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ("title", "author", "slug", "is_published", "created_at")
    list_filter = ("is_published", "created_at", "author")
    search_fields = ("title", "summary", "content")
    prepopulated_fields = {"slug": ("title",)}

这样后台文章列表会多显示一列作者。

第 12 步:创建文章发布表单

blog 目录下新建:

blog/forms.py

写入:

from django import forms

from .models import Post


class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ["title", "slug", "summary", "content", "is_published"]

        widgets = {
            "summary": forms.Textarea(attrs={"rows": 4}),
            "content": forms.Textarea(attrs={"rows": 12}),
        }

注意这里没有把 author 放进表单。

原因是:

作者不应该让用户自己选;
作者应该由当前登录用户自动绑定。

也就是后面这句:

post.author = request.user

第 13 步:添加发布文章视图

打开 blog/views.py,加入这些 import:

from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, redirect, render

你原来可能已经有:

from django.shortcuts import get_object_or_404, render

改成包含 redirect 即可。

再加入:

from .forms import PostForm

然后添加发布文章视图:

@login_required
def post_create(request):
    if request.method == "POST":
        form = PostForm(request.POST)

        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.save()

            messages.success(request, "文章发布成功。")
            return redirect(post.get_absolute_url())
    else:
        form = PostForm()

    return render(
        request,
        "blog/post_form.html",
        {
            "form": form,
            "page_title": "发布文章",
        },
    )

这段代码的核心是:

@login_required

表示:

只有登录用户才能访问发布文章页面。

以及:

post.author = request.user

表示:

当前登录用户就是这篇文章的作者。

第 14 步:修改 blog 路由

打开 blog/urls.py

注意:create/ 要放在 <slug:slug>/ 前面,否则 Django 可能会把 create 当成文章 slug。

写成:

from django.urls import path
from . import views

app_name = "blog"

urlpatterns = [
    path("", views.post_list, name="post_list"),
    path("create/", views.post_create, name="post_create"),
    path("<slug:slug>/", views.post_detail, name="post_detail"),
]

现在新增地址:

/blog/create/

第 15 步:创建文章发布模板

创建:

templates/blog/post_form.html

写入:

{% extends "base.html" %}

{% block title %}{{ page_title }} - 我的 Django 博客{% endblock %}

{% block content %}
<section class="form-page">
    <h1>{{ page_title }}</h1>

    <form method="post" class="form-card">
        {% csrf_token %}

        {{ form.as_p }}

        <button class="button" type="submit">保存文章</button>
    </form>

    <p>
        <a href="{% url 'blog:post_list' %}">← 返回博客列表</a>
    </p>
</section>
{% endblock %}

第 16 步:文章列表显示作者

打开 templates/blog/post_list.html

在文章卡片中加上作者显示,例如:

<p class="post-date">
    {{ post.created_at|date:"Y-m-d" }}
    {% if post.author %}
        · 作者:{{ post.author.username }}
    {% else %}
        · 作者:未设置
    {% endif %}
</p>

你的文章列表里大概变成:

{% for post in posts %}
    <article class="post-card">
        <p class="post-date">
            {{ post.created_at|date:"Y-m-d" }}
            {% if post.author %}
                · 作者:{{ post.author.username }}
            {% else %}
                · 作者:未设置
            {% endif %}
        </p>

        <h2>
            <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
        </h2>

        <p>{{ post.summary }}</p>
        <a href="{{ post.get_absolute_url }}">阅读全文 →</a>
    </article>
{% endfor %}

第 17 步:文章详情显示作者

打开 templates/blog/post_detail.html

在标题下面加入:

<p class="post-date">
    {{ post.created_at|date:"Y-m-d H:i" }}

    {% if post.author %}
        · 作者:{{ post.author.username }}
    {% else %}
        · 作者:未设置
    {% endif %}
</p>

第 18 步:base.html 加 messages 提示

Django 的 messages.success() 需要模板显示。

打开 templates/base.html,在 <main> 里、block content 前加:

<main class="container main-content">
    {% if messages %}
        <div class="messages">
            {% for message in messages %}
                <div class="message {{ message.tags }}">
                    {{ message }}
                </div>
            {% endfor %}
        </div>
    {% endif %}

    {% block content %}{% endblock %}
</main>

第 19 步:补充 CSS

打开 static/css/style.css,底部追加:

.nav-user {
    color: #4b5563;
    font-size: 14px;
}

.logout-form {
    margin: 0;
}

.logout-form button {
    border: none;
    background: transparent;
    color: #2563eb;
    cursor: pointer;
    font: inherit;
    padding: 0;
}

.logout-form button:hover {
    text-decoration: underline;
}

.form-page {
    max-width: 760px;
    margin: 0 auto;
}

.form-card {
    background: #ffffff;
    border: 1px solid #e5e7eb;
    border-radius: 16px;
    padding: 28px;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
}

.form-card p {
    display: grid;
    gap: 6px;
    margin-bottom: 18px;
}

.form-card input,
.form-card textarea,
.form-card select {
    width: 100%;
    padding: 10px 12px;
    border: 1px solid #d1d5db;
    border-radius: 10px;
    font: inherit;
}

.form-card input:focus,
.form-card textarea:focus {
    outline: 2px solid #bfdbfe;
    border-color: #2563eb;
}

.helptext {
    color: #6b7280;
    font-size: 13px;
}

.errorlist {
    color: #dc2626;
    margin: 0 0 8px;
    padding-left: 20px;
}

.messages {
    margin-bottom: 24px;
}

.message {
    padding: 12px 16px;
    border-radius: 10px;
    margin-bottom: 10px;
    background: #eff6ff;
    color: #1d4ed8;
    border: 1px solid #bfdbfe;
}

.message.success {
    background: #ecfdf5;
    color: #047857;
    border-color: #a7f3d0;
}

.message.error {
    background: #fef2f2;
    color: #b91c1c;
    border-color: #fecaca;
}

第 20 步:启动测试

运行:

python manage.py runserver

测试这些页面:

http://127.0.0.1:8000/accounts/register/
http://127.0.0.1:8000/accounts/login/
http://127.0.0.1:8000/blog/create/
http://127.0.0.1:8000/blog/

测试流程:

1. 注册一个普通用户
2. 注册成功后应该自动登录
3. 点击“发布文章”
4. 填写标题、slug、摘要、正文 Markdown
5. 保存文章
6. 自动跳转文章详情页
7. 详情页显示作者
8. 博客列表显示作者
9. 点击登出
10. 再访问 /blog/create/,应该跳转登录页

MVP v2 完成标准

完成后你应该具备:

用户可以注册
用户可以登录
用户可以登出
登录状态会显示在导航栏
未登录用户不能发布文章
登录用户可以发布文章
新文章会自动绑定作者
文章列表显示作者
文章详情显示作者
Admin 后台可以看到文章作者

完成后建议提交版本

测试没问题后提交:

git add .
git commit -m "complete blog mvp v2 user system and post author"

这一版完成后,下一版就是:

MVP v3:文章编辑、删除、权限控制

也就是:

作者可以编辑自己的文章
作者可以删除自己的文章
管理员可以管理所有文章
普通用户不能改别人的文章

评论

vqzynzotlt 2026-06-03 20:51

qwuwepzkzojrgjwvohsipnwmtjrgkj

wikhvfuwks 2026-06-03 20:51

wjddpwrnypzttnegngdxstvwtuejld

发表评论