Django Template模板层详解

课程目标

  • 理解Django模板系统的工作原理
  • 掌握模板语法和标签的使用
  • 学会创建和使用自定义模板标签和过滤器
  • 了解模板继承和包含机制

模板系统概述

Django模板系统是Django MTV架构中的"T"层(Template),负责将数据渲染成HTML或其他格式的输出。模板系统提供了一种简洁而强大的语法,让设计师和开发者能够协作构建动态网页。

模板系统的组成:

  • 模板:包含占位符和标签的文本文件
  • 上下文:传递给模板的数据
  • 渲染:将上下文数据填充到模板中的过程

模板基础语法

变量

<!-- 在模板中显示变量 -->
<p>欢迎,{{ name }}!</p>
<p>今天的日期是 {{ date }}</p>

<!-- 访问对象属性 -->
<p>用户名:{{ user.username }}</p>
<p>邮箱:{{ user.email }}</p>

<!-- 访问字典值 -->
<p>城市:{{ profile.address.city }}</p>

过滤器

<!-- 过滤器用于格式化变量 -->
<p>{{ name|upper }}</p>  <!-- 转为大写 -->
<p>{{ content|truncatewords:30 }}</p>  <!-- 截断为30个单词 -->
<p>{{ price|floatformat:2 }}</p>  <!-- 保留两位小数 -->
<p>{{ date|date:"Y-m-d H:i:s" }}</p>  <!-- 格式化日期 -->

<!-- 链式过滤器 -->
<p>{{ content|striptags|truncatewords:50 }}</p>

模板标签

控制结构标签

if/elif/else标签

{% if user.is_authenticated %}
    <p>欢迎回来,{{ user.username }}!</p>
{% elif user.is_anonymous %}
    <p>请登录</p>
{% else %}
    <p>未知用户</p>
{% endif %}

<!-- 使用比较运算符 -->
{% if articles|length > 5 %}
    <p>文章数量较多</p>
{% endif %}

for循环标签

<!-- 基本for循环 -->
<ul>
{% for article in articles %}
    <li>{{ article.title }}</li>
{% empty %}
    <li>暂无文章</li>
{% endfor %}
</ul>

<!-- 带索引的循环 -->
{% for article in articles %}
    <p>{{ forloop.counter }}. {{ article.title }}</p>
{% endfor %}

<!-- forloop变量 -->
<!-- forloop.counter: 从1开始的计数器 -->
<!-- forloop.counter0: 从0开始的计数器 -->
<!-- forloop.revcounter: 反向计数器 -->
<!-- forloop.first: 是否是第一个元素 -->
<!-- forloop.last: 是否是最后一个元素 -->

模板继承

<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}默认标题{% endblock %}</title>
    {% block extra_head %}{% endblock %}
</head>
<body>
    <header>
        {% block header %}
            <h1>我的网站</h1>
        {% endblock %}
    </header>
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    <footer>
        {% block footer %}
            <p>&copy; 2023 版权所有</p>
        {% endblock %}
    </footer>
    
    {% block extra_js %}{% endblock %}
</body>
</html>
<!-- article_list.html -->
{% extends "base.html" %}

{% block title %}文章列表 - 我的博客{% endblock %}

{% block content %}
    <h2>文章列表</h2>
    {% for article in articles %}
        <article>
            <h3><a href="{% url 'article_detail' article.id %}">{{ article.title }}</a></h3>
            <p>{{ article.summary|truncatewords:30 }}</p>
            <p class="meta">发布于 {{ article.pub_date|date:"Y-m-d" }}</p>
        </article>
    {% empty %}
        <p>暂无文章</p>
    {% endfor %}
    
    <!-- 分页 -->
    {% if articles.has_other_pages %}
        <div class="pagination">
            {% if articles.has_previous %}
                <a href="?page={{ articles.previous_page_number }}">上一页</a>
            {% endif %}
            
            <span class="current">
                第 {{ articles.number }} 页,共 {{ articles.paginator.num_pages }} 页
            </span>
            
            {% if articles.has_next %}
                <a href="?page={{ articles.next_page_number }}">下一页</a>
            {% endif %}
        </div>
    {% endif %}
{% endblock %}

URL标签和反转

url标签

<!-- 使用命名URL -->
<a href="{% url 'article_detail' article.id %}">查看详情</a>

<!-- 带参数的URL -->
<a href="{% url 'articles_by_category' category.slug %}">{{ category.name }}</a>

<!-- 带多个参数的URL -->
<a href="{% url 'article_edit' article.id user.id %}">编辑</a>

<!-- 使用命名参数 -->
<a href="{% url 'search' q=query page=1 %}">搜索</a>

static标签

{% load static %}
<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
    <link rel="icon" href="{% static 'images/favicon.ico' %}">
</head>
<body>
    <img src="{% static 'images/logo.png' %}" alt="Logo">
    <script src="{% static 'js/main.js' %}"></script>
</body>
</html>

常用模板标签

include标签

<!-- 包含其他模板 -->
{% include 'partials/header.html' %}
{% include 'partials/sidebar.html' %}

<!-- 带上下文的包含 -->
{% include 'partials/comment_form.html' with comment=article.comment %}

<!-- 有条件地包含 -->
{% include 'partials/ad_banner.html' ignore missing %}

with标签

<!-- 创建临时变量 -->
{% with total=article.views|add:article.likes %}
    <p>互动总数:{{ total }}</p>
{% endwith %}

<!-- 复杂表达式 -->
{% with user.profile as profile %}
    <p>昵称:{{ profile.nickname }}</p>
    <p>积分:{{ profile.points }}</p>
{% endwith %}

cycle标签

<!-- 循环使用值 -->
{% for item in items %}
    <tr class="{% cycle 'row1' 'row2' %}">
        <td>{{ item.name }}</td>
        <td>{{ item.value }}</td>
    </tr>
{% endfor %}

自定义模板过滤器

创建自定义过滤器

# templatetags/custom_filters.py
from django import template
from django.utils.safestring import mark_safe
import markdown

register = template.Library()

@register.filter
def markdown_to_html(text):
    """将Markdown转换为HTML"""
    return mark_safe(markdown.markdown(text))

@register.filter
def pluralize_cn(count, singular, plural):
    """中文复数形式"""
    if count == 1:
        return singular
    else:
        return plural

@register.filter
def truncatechars_middle(text, length):
    """从中间截断文本"""
    if len(text) <= length:
        return text
    else:
        start = length // 2
        end = length - start - 3
        return text[:start] + "..." + text[-end:]

在模板中使用自定义过滤器

{% load custom_filters %}

<!-- 使用自定义过滤器 -->
<div class="content">
    {{ article.content|markdown_to_html }}
</div>

<p>有 {{ comments.count }} {{ comments.count|pluralize_cn:"条评论":"条评论" }}</p>

<p>{{ long_text|truncatechars_middle:50 }}</p>

自定义模板标签

简单标签

# templatetags/custom_tags.py
from django import template
from django.utils import timezone
from myapp.models import Article

register = template.Library()

@register.simple_tag
def current_time(format_string):
    """显示当前时间"""
    return timezone.now().strftime(format_string)

@register.simple_tag
def get_recent_articles(count=5):
    """获取最新文章"""
    return Article.objects.filter(
        is_published=True
    ).order_by('-pub_date')[:count]

@register.simple_tag(takes_context=True)
def get_user_articles(context, user):
    """获取用户的文章"""
    request = context['request']
    return user.article_set.filter(author=request.user)

包含标签

@register.inclusion_tag('tags/article_list.html')
def show_articles(articles, show_author=True):
    """显示文章列表"""
    return {
        'articles': articles,
        'show_author': show_author
    }

@register.inclusion_tag('tags/pagination.html', takes_context=True)
def show_pagination(context, page_obj):
    """显示分页"""
    request = context['request']
    return {
        'page_obj': page_obj,
        'request': request
    }

模板中使用自定义标签

{% load custom_tags %}

<!-- 使用简单标签 -->
<p>当前时间:{% current_time "%Y-%m-%d %H:%M:%S" %}</p>

<!-- 获取最新文章 -->
{% get_recent_articles 3 as recent_articles %}
<ul>
{% for article in recent_articles %}
    <li><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></li>
{% endfor %}
</ul>

模板配置

settings.py中的模板配置

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / 'templates',  # 模板目录
        ],
        'APP_DIRS': True,  # 自动查找应用中的templates目录
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

模板最佳实践

模板组织原则

  • 使用模板继承减少重复代码
  • 将公共部分提取为独立模板
  • 合理使用include标签
  • 避免在模板中放置复杂逻辑

性能优化

<!-- 使用select_related和prefetch_related优化查询 -->
{% for article in articles.all %}
    <!-- 如果需要访问外键对象 -->
    <p>作者:{{ article.author.username }}</p>
{% endfor %}
# 在视图中优化查询
def article_list(request):
    articles = Article.objects.select_related('author').prefetch_related('tags')
    return render(request, 'articles/list.html', {'articles': articles})

安全考虑

  • Django模板系统默认对输出进行HTML转义
  • 使用safe过滤器时要谨慎
  • 避免在模板中执行危险操作
<!-- 安全的输出 -->
<p>{{ user_input|escape }}</p>

<!-- 只有在信任内容时才使用safe -->
<div>{{ trusted_html|safe }}</div>

课程总结

本节课我们深入学习了Django的模板系统,包括模板语法、标签使用、模板继承、自定义过滤器和标签等内容。模板系统是Django的重要组成部分,掌握它对于构建动态网页至关重要。