Skip to content

第7章:模板系统

7.3 自定义模板

自定义过滤器

Django允许你创建自定义过滤器来扩展模板功能。自定义过滤器是一个Python函数,最多接收两个参数:

  1. 变量的值(输入)
  2. 参数的值(可选)

首先在应用目录下创建templatetags目录,并添加__init__.py文件:

myapp/
    templatetags/
        __init__.py
        custom_filters.py

然后在custom_filters.py中定义过滤器:

python
from django import template

register = template.Library()

# 简单过滤器示例
@register.filter
def lower(value):
    """将字符串转换为小写"""
    return value.lower()

# 带参数的过滤器示例
@register.filter
def truncate_chars(value, max_length):
    """截断字符串到指定字符数"""
    if len(value) > max_length:
        return value[:max_length] + '...'
    return value

# 更复杂的过滤器示例
@register.filter
def word_count(value):
    """计算文本中的单词数"""
    return len(value.split())

@register.filter
def multiply(value, arg):
    """将数值乘以参数"""
    try:
        return float(value) * float(arg)
    except (ValueError, TypeError):
        return 0

@register.filter
def highlight_search(text, search_term):
    """高亮搜索关键词"""
    if search_term:
        return text.replace(
            search_term, 
            f'<mark>{search_term}</mark>'
        )
    return text

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

html
{% load custom_filters %}

<p>标题:{{ title|lower }}</p>
<p>摘要:{{ content|truncate_chars:100 }}</p>
<p>字数:{{ content|word_count }}</p>
<p>价格:{{ price|multiply:1.2|floatformat:2 }}</p>
<p>搜索结果:{{ content|highlight_search:query }}</p>

自定义标签

自定义标签比过滤器更复杂,可以执行更复杂的逻辑。它们分为几种类型:

  1. 简单标签
  2. 包含标签
  3. 赋值标签

templatetags/custom_tags.py中定义标签:

python
from django import template
from django.utils.safestring import mark_safe
from ..models import Article, Category

register = template.Library()

# 简单标签示例
@register.simple_tag
def total_articles():
    """返回文章总数"""
    return Article.objects.count()

@register.simple_tag
def article_count_by_category(category_slug):
    """返回指定分类的文章数"""
    try:
        category = Category.objects.get(slug=category_slug)
        return category.articles.count()
    except Category.DoesNotExist:
        return 0

# 带参数的简单标签
@register.simple_tag(takes_context=True)
def user_article_count(context):
    """返回当前用户的文章数"""
    user = context['user']
    if user.is_authenticated:
        return Article.objects.filter(author=user).count()
    return 0

# 包含标签示例
@register.inclusion_tag('tags/recent_articles.html')
def recent_articles(count=5):
    """显示最近的文章"""
    articles = Article.objects.filter(
        status='published'
    ).order_by('-publish_date')[:count]
    return {'articles': articles}

# 赋值标签示例
@register.simple_tag
def get_categories():
    """获取所有分类"""
    return Category.objects.all()

# 复杂标签示例
@register.tag
def highlight_text(parser, token):
    """
    高亮文本标签
    用法:{% highlight_text "搜索词" %}被搜索的文本{% endhighlight_text %}
    """
    try:
        # 解析标签参数
        tag_name, search_term = token.split_contents()
    except ValueError:
        raise template.TemplateSyntaxError(
            "%r tag requires a single argument" % token.contents.split()[0]
        )
    
    # 解析标签内容直到endhighlight_text
    nodelist = parser.parse(('endhighlight_text',))
    parser.delete_first_token()
    
    return HighlightTextNode(nodelist, search_term)

class HighlightTextNode(template.Node):
    def __init__(self, nodelist, search_term):
        self.nodelist = nodelist
        self.search_term = template.Variable(search_term)
    
    def render(self, context):
        # 渲染标签内容
        text = self.nodelist.render(context)
        try:
            search_term = self.search_term.resolve(context)
            # 高亮搜索词
            highlighted = text.replace(
                search_term,
                f'<mark>{search_term}</mark>'
            )
            return mark_safe(highlighted)
        except template.VariableDoesNotExist:
            return text

在模板中使用自定义标签:

html
{% load custom_tags %}

<!-- 简单标签 -->
<p>总文章数:{% total_articles %}</p>
<p>Python分类文章数:{% article_count_by_category "python" %}</p>
<p>您的文章数:{% user_article_count %}</p>

<!-- 包含标签 -->
{% recent_articles 10 %}

<!-- 赋值标签 -->
{% get_categories as categories %}
<ul>
{% for category in categories %}
    <li>{{ category.name }}</li>
{% endfor %}
</ul>

<!-- 复杂标签 -->
{% highlight_text "Django" %}
Django是一个高级Python Web框架,它鼓励快速开发和干净、实用的设计。
{% endhighlight_text %}

包含标签的模板文件 templates/tags/recent_articles.html

html
<div class="recent-articles">
    <h3>最近文章</h3>
    <ul>
    {% for article in articles %}
        <li>
            <a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
            <span class="date">{{ article.publish_date|date:"m-d" }}</span>
        </li>
    {% empty %}
        <li>暂无文章</li>
    {% endfor %}
    </ul>
</div>

模板上下文处理器

模板上下文处理器是在所有模板上下文中自动添加变量的函数。它们在settings.pyTEMPLATES配置中定义。

创建context_processors.py

python
from django.conf import settings

def site_info(request):
    """站点信息上下文处理器"""
    return {
        'site_name': getattr(settings, 'SITE_NAME', '我的网站'),
        'site_description': getattr(settings, 'SITE_DESCRIPTION', '一个Django网站'),
    }

def user_permissions(request):
    """用户权限上下文处理器"""
    if request.user.is_authenticated:
        return {
            'user_permissions': request.user.get_all_permissions(),
            'is_admin': request.user.is_staff,
        }
    return {}

def common_data(request):
    """常用数据上下文处理器"""
    from ..models import Category
    
    try:
        categories = Category.objects.all()[:10]
    except:
        categories = []
    
    return {
        'common_categories': categories,
        'current_year': datetime.now().year,
    }

settings.py中注册上下文处理器:

python
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        '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',
                # 自定义上下文处理器
                'myapp.context_processors.site_info',
                'myapp.context_processors.user_permissions',
                'myapp.context_processors.common_data',
            ],
        },
    },
]

在任何模板中使用上下文处理器提供的变量:

html
<!-- 所有模板都可以访问这些变量 -->
<header>
    <h1>{{ site_name }}</h1>
    <p>{{ site_description }}</p>
</header>

<nav>
    <ul>
    {% for category in common_categories %}
        <li><a href="{{ category.get_absolute_url }}">{{ category.name }}</a></li>
    {% endfor %}
    </ul>
</nav>

<footer>
    <p>&copy; {{ current_year }} {{ site_name }}</p>
    {% if is_admin %}
    <p><a href="/admin/">管理后台</a></p>
    {% endif %}
</footer>

静态文件处理

在模板中正确引用静态文件(CSS、JavaScript、图片等):

html
{% load static %}

<!DOCTYPE html>
<html>
<head>
    <title>我的网站</title>
    <!-- CSS文件 -->
    <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'css/responsive.css' %}">
</head>
<body>
    <!-- 图片 -->
    <img src="{% static 'images/logo.png' %}" alt="Logo">
    
    <div class="content">
        <!-- 页面内容 -->
    </div>
    
    <!-- JavaScript文件 -->
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <script src="{% static 'js/main.js' %}"></script>
    
    <!-- 带版本控制的静态文件 -->
    <script src="{% static 'js/app.js' %}?v=1.0"></script>
</body>
</html>

在开发环境中,需要在settings.py中配置静态文件:

python
# settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    BASE_DIR / "static",
]
STATIC_ROOT = BASE_DIR / "staticfiles"

在生产环境中,通常需要配置Web服务器(如Nginx)来直接提供静态文件,以提高性能。

小结

自定义模板功能扩展了Django模板系统的能力:

  1. ✅ 自定义过滤器处理变量格式化
  2. ✅ 自定义标签实现复杂逻辑
  3. ✅ 模板上下文处理器全局注入变量
  4. ✅ 静态文件的正确引用和管理
  5. ✅ 包含标签和赋值标签的灵活使用

掌握自定义模板功能能够创建更强大和灵活的前端界面。

下一篇

我们将学习Django表单系统。

8.1 表单基础 →

目录

返回课程目录

Released under the Apache 2.0 License.