Django 从入门到精通:知行博客实战(6.1) · 第 13 / 36 节

文章列表与详情

吴秀峰 3 次阅读 0 条评论 ↓

学习目标

让首页从数据库读取文章,建立稳定的详情路由,用 get_object_or_404 处理不存在或不可见的记录。

为什么把公开条件写在查询里

列表只显示已发布文章,详情也必须执行同样的可见性限制。如果仅在模板隐藏草稿链接,知道 id 的访客仍可能访问草稿详情。权限和可见性应在查库时体现,而不是依赖用户看不到某个按钮。

select_related('author', 'category') 同时取出展示所需的关联记录。视图把对象交给模板,模板用 post.title、post.author.username 等属性显示内容。正文使用纯文本和 linebreaks,能保留段落且保持转义。

具体怎么做

  1. 替换视图和模板,给 blog/urls.py 增加带整数主键的路由。
  2. 首页循环显示公开文章;每篇文章的链接使用模型里的 get_absolute_url。
  3. 详情视图用公开状态和 pk 一起过滤,找不到时返回 404。
  4. 在后台创建一篇草稿,通过数据库或后台地址确认它的 id,然后尝试直接访问详情。

本章新增或替换的完整文件

以下路径均相对于练习项目根目录。替换同名文件的全部内容;未列出的文件保持上一章版本。

blog/urls.py

from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [
    path("", views.home, name="home"),
    path("posts/<int:pk>/", views.detail, name="detail"),
    path("about/", views.about, name="about"),
    path("old-home/", views.old_home, name="old_home"),
]

blog/views.py

from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_GET
from .models import Post

@require_GET
def home(request):
    posts = Post.objects.filter(status=Post.Status.PUBLISHED).select_related("author", "category")
    return render(request, "blog/home.html", {"posts": posts})

@require_GET
def detail(request, pk):
    post = get_object_or_404(Post.objects.select_related("author", "category"), pk=pk, status=Post.Status.PUBLISHED)
    return render(request, "blog/detail.html", {"post": post})

@require_GET
def about(request):
    return render(request, "blog/about.html")

@require_GET
def old_home(request):
    return redirect("blog:home")

templates/blog/detail.html

{% extends "base.html" %}
{% block title %}{{ post.title }} · 知行博客{% endblock %}
{% block content %}<article>
<h1>{{ post.title }}</h1><p class="muted">{{ post.author.username }} · {{ post.category.name }} · {{ post.created_at|date:"Y-m-d H:i" }}</p>
<div>{{ post.body|linebreaks }}</div>
</article>{% endblock %}

templates/blog/home.html

{% extends "base.html" %}
{% block content %}
<h1>学习,实践,分享</h1>
{% for post in posts %}<article><h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>
<p class="muted">{{ post.author.username }} · {{ post.category.name }} · {{ post.created_at|date:"Y-m-d" }}</p>
<p>{{ post.body|truncatechars:120 }}</p></article>{% empty %}<p>暂时没有公开文章。</p>{% endfor %}
{% endblock %}

如何确认成功

python manage.py check
python manage.py runserver

首页应出现第 10 章创建的文章;点击标题可以进入详情,浏览器标题随文章变化。公开文章返回 200,草稿和不存在的 id 返回 404。此时连作者也不能从前台查看草稿,作者预览会在第 17 章加入,这是主线当前阶段的明确限制。

练习:在后台把同一篇文章从 published 改为 draft,刷新列表和详情,确认两处都执行公开条件。把正文输入 <img src=x onerror=alert(1)>,页面应显示文本而不是执行事件。

常见问题

标题有 HTML 被转义是不是错误? 不是,文章标题是纯文本,转义保护读者。DoesNotExist 为什么不直接暴露? 对用户而言应看到可理解的 404,而不是服务器异常。文章很多会怎样? 当前查询还没有分页;下一章用 Paginator 限制每页记录数,避免一次渲染整个表。

官方参考

把学习连起来

登录后可保存阅读位置、标记章节完成,并在其他设备继续学习。

登录并保存学习进度 →
联系站长

微信:VIPdjango。技术问题建议先在 社区讨论 中交流,方便更多人参与和查阅。

章节讨论 0