现在位置: 首页 > Django 教程 > 正文

视图与模板 — 渲染文章列表

本章你将学会用 Django 视图从数据库取数据,配合模板渲染出博客首页的文章卡片列表。


视图(View)的作用

视图是 Django 的核心逻辑层,负责:

  • 接收用户请求(request 参数)
  • 从数据库查询需要的数据
  • 将数据传给模板渲染 HTML,返回给用户

Django 视图有两种写法:函数视图(FBV) 和类视图(CBV)。

前八章我们优先使用 FBV——它更直观,适合初学者理解请求处理流程。


函数视图基础

一个 Django 视图函数接收 request 参数,返回 HttpResponse 对象。

实例

# 文件路径:blog/views.py
from django.shortcuts import render
from .models import Post

def index(request):
    """博客首页:展示所有文章"""
    # 从数据库查询所有文章(按时间倒序)
    posts = Post.objects.all().order_by('-created_at')

    # render 的三个参数:
    # 1. request — 请求对象
    # 2. 模板路径 — blog/index.html
    # 3. context 字典 — 传给模板的数据
    context = {
        'posts': posts,
        'title': 'RUNOOB 博客 - 首页'
    }
    return render(request, 'blog/index.html', context)

render() 是 Django 中最常用的快捷函数,它组合了:加载模板 → 注入数据 → 生成 HTML → 返回响应。


Django 模板语法

Django 模板语法与 Vue3 模板语法非常相似,但有一些关键区别。

用途Django 模板Vue3 模板
输出变量{{ variable }}{{ variable }}
循环{% for item in list %}...{% endfor %}v-for="item in list"
条件{% if cond %}...{% endif %}v-if="cond"
URL 生成{% url 'name' %}router-link to="name"
继承布局{% extends 'base.html' %}组件嵌套

关键区别:Django 模板用 {% %} 包裹控制标签,Vue3 用 v- 指令。Django 模板在服务器端渲染——HTML 生成完毕后才发给浏览器;Vue3 在浏览器端渲染——浏览器收到 JS 后才拼接 HTML。


模板继承:base.html

多个页面共享相同的导航栏和页脚,用 模板继承 避免重复。

父模板定义「块(block)」,子模板填充具体内容。

创建 base.html 父模板

实例

<!-- 文件路径:blog/templates/blog/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}RUNOOB 博客{% endblock %}</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: #f5f5f5;
            color: #333;
        }
        .navbar {
            display: flex; justify-content: space-between; align-items: center;
            padding: 16px 40px; background: #fff;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        }
        .navbar .logo { font-size: 22px; font-weight: bold; color: #2c3e50; text-decoration: none; }
        .navbar a { margin-left: 20px; text-decoration: none; color: #333; }
        .container { max-width: 960px; margin: 40px auto; padding: 0 20px; }
        .footer { text-align: center; padding: 20px; color: #999; border-top: 1px solid #eee; }
        .section-title { font-size: 24px; margin-bottom: 20px; }
    </style>
    {% block extra_head %}{% endblock %}
</head>
<body>
    <!-- 导航栏 -->
    <header class="navbar">
        <a href="/" class="logo">RUNOOB 博客</a>
        <nav>
            <a href="/">首页</a>
            <a href="#">关于</a>
        </nav>
    </header>

    <!-- 主内容区(子模板填充) -->
    <main class="container">
        {% block content %}
        {% endblock %}
    </main>

    <!-- 页脚 -->
    <footer class="footer">
        <p>© 2024 RUNOOB 博客. Powered by Django.</p>
    </footer>
</body>
</html>

创建 index.html 子模板

实例

<!-- 文件路径:blog/templates/blog/index.html -->
<!-- extends 必须在最开头 -->
{% extends 'blog/base.html' %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
<h2 class="section-title">最新文章</h2>

{% if posts %}
    <div class="article-grid">
        {% for post in posts %}
        <div class="article-card">
            <div class="card-content">
                <span class="card-category">{{ post.category.name }}</span>
                <h3>{{ post.title }}</h3>
                <p>{{ post.summary|truncatechars:80 }}</p>
                <span class="card-date">{{ post.created_at|date:"Y-m-d" }}</span>
            </div>
        </div>
        {% endfor %}
    </div>
{% else %}
    <p class="empty-tip">还没有文章,敬请期待。</p>
{% endif %}
{% endblock %}

模板中用到的几个知识点:

语法含义
{% extends 'blog/base.html' %}继承父模板,必须写在第一行
{% block content %}...{% endblock %}填充父模板中定义的 content 块
{{ post.category.name }}通过外键访问关联对象的属性
{{ post.summary|truncatechars:80 }}过滤器:截取前 80 个字符
{{ post.created_at|date:"Y-m-d" }}过滤器:格式化日期

Django 模板中的 过滤器 用管道符 | 表示,跟在变量后面,用于格式化输出。常用过滤器:datetruncatecharslengthdefault


添加文章列表样式

在 base.html 的 style 标签中追加以下 CSS:

实例

/* 文章卡片网格布局 */
.article-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 24px;
}

.article-card {
    background: #fff;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 2px 12px rgba(0,0,0,0.08);
    transition: transform 0.2s, box-shadow 0.2s;
}

.article-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

.card-content {
    padding: 20px;
}

.card-category {
    display: inline-block;
    padding: 2px 10px;
    background: #e8f5e9;
    color: #2e7d32;
    border-radius: 12px;
    font-size: 12px;
    margin-bottom: 8px;
}

.article-card h3 {
    font-size: 18px;
    margin-bottom: 8px;
    color: #222;
}

.article-card p {
    font-size: 14px;
    color: #666;
    line-height: 1.6;
    margin-bottom: 12px;
}

.card-date {
    font-size: 12px;
    color: #999;
}

.empty-tip {
    text-align: center;
    color: #999;
    padding: 60px 0;
    font-size: 16px;
}

动手:验证首页效果

确认以下几点已做好:

  • models.py 中定义了 Post 和 Category 模型,并已 migrate
  • Admin 后台已录入至少 3-5 篇文章(归属于不同的分类)
  • views.py 中的 index 函数从数据库查询文章并传给模板
  • urls.py 中 / 路由指向 index 视图

启动服务器,访问首页,应该能看到由数据库数据动态渲染的文章卡片列表。


本章小结

本章你掌握了 Django 视图和模板的核心配合:views.py 从数据库查询数据并传入 context、render() 渲染模板、base.html 父模板定义布局 + block、子模板 extends 继承并填充内容、Django 模板语法({{ }}、{% %}、过滤器)。

现在博客首页由数据库驱动,不再硬编码。