Django 模板

创建 Django 练习项目

创建 Django 项目

django-admin startproject myproject
cd myproject

创建 Django 应用

python manage.py startapp myapp

设置模板目录

Django 默认在应用目录中的 templates 文件夹查找模板。你可以选择将模板放在应用目录下,也可以在项目层面上设置一个全局模板目录。

目录结构示例

我们可以将模板放在以下位置:

myproject/
    myproject/
        settings.py
        urls.py
    templates/
        myapp/
            index.html  # 应用的模板文件
    manage.py

在 settings.py 中配置模板

打开你的 settings.py 文件,找到或添加 TEMPLATES 配置项,通常默认为设置好的,可以根据需求修改:

# myproject/settings.py
import os

# 基础配置
BASE_DIR = Path(__file__).resolve().parent.parent

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',  
        'DIRS': [os.path.join(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',
            ],
        },
    },
]

# 注册应用
INSTALLED_APPS = [
    ...
    'myapp',  # 确保这里包含 'myapp'
]

创建模板文件

在你的项目目录中,创建一个目录 templates,然后在其中创建一个子目录 myapp 以便组织模板文件。

<!-- myproject/templates/myapp/index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>My First Django Template</title>
</head>
<body>
    <h1>Hello, {{ name }}!</h1>
</body>
</html>

在这个简单的模板中,{{ name }} 是一个占位符,它将在渲染时被实际值替换。

创建视图来渲染模板

在应用的 views.py 文件中,定义一个视图函数来渲染模板:

# myapp/views.py
from django.shortcuts import render

def index(request):
    # 组织数据
    context = {'name': 'World'} # 模板引用 view.py中 context 的 key 来引用变量
    return render(request, 'myapp/index.html', context)

配置 URL

# myproject/urls.py
from django.contrib import admin
from django.urls import path, include # 添加 include
# from myapp.views import index 

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls')),  # 引入 myapp 的 URLs
]


# myapp/urls.py
from django.urls import path
from .views import index

urlpatterns = [
    path('', index, name='index'),  # 将根 URL 映射到 index 视图
]

启动开发服务器并访问

python manage.py runserver

然后在浏览器中访问 http://127.0.0.1:8000/,你应该能够看到页面显示 "Hello, World!"。这个 "World" 是在视图中定义的上下文数据,它替换了模板中的 {{ name }} 占位符。

Django 模板

模板引用方式

在 Django 模板中,视图(view)返回的数据(通常称为上下文数据)可以通过特定的语法在模板中引用。这一过程是 Django 模板引擎的核心部分,下面将详细解释模板如何引用视图的变量、原因及遵循的规则。

模板如何引用视图的变量

在视图中,我们可以创建一个上下文字典,将数据传递给模板。例如,以下是一个简单的视图:

# myapp/views.py
from django.shortcuts import render

def index(request):
    context = {
        'greeting': 'Hello, World!',
        'items': ['Apple', 'Banana', 'Cherry']
    }
    return render(request, 'myapp/index.html', context)

在这个示例中,我们将一些数据存储在 context 字典中并传递给模板。

在模板中引用变量
在模板 myapp/index.html 中,我们可以使用模板语言来引用这些变量:

<!-- myapp/templates/myapp/index.html -->
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>示例模板</title>
</head>
<body>
    <h1>{{ greeting }}</h1>  <!-- 引用视图中的变量 greeting -->
    
    <ul>
        {% for item in items %}  <!-- 使用 for 循环遍历 items 列表 -->
            <li>{{ item }}</li>   <!-- 引用列表中的每个 item -->
        {% endfor %}
    </ul>
</body>
</html>
Django 模板

Django 模板常用语法

Django 模板提供了丰富的语法来处理数据和渲染动态内容。接下来通过一些示例展示在 Django 模板中可以使用的常用语法。

引用变量

引用变量的基本语法是使用双大括号 {{ variable_name }}

<h1>Welcome, {{ username }}!</h1>

模板标签

模板标签用 {% tag %} 语法定义。常见的标签包括:

a. 控制结构

条件语句:使用 {% if %} 标签进行条件判断。

语法: {% if %}{% elif %}{% else %}{% endif %}

{% if user.is_authenticated %}
    <p>Hello, {{ user.username }}!</p>
{% else %}
    <p>Please log in.</p>
{% endif %}

案例:

# myapp/views.py
from django.shortcuts import render

def index(request):
    # 假设的用户信息,用于演示
    user = request.user  
    is_premium_user = user.is_authenticated and user.is_staff  # 判断用户是否是认证用户和高级用户

    context = {
        'greeting': 'Hello, World!',
        'items': ['Apple', 'Banana', 'Cherry'],
        'is_authenticated': user.is_authenticated,
        'is_premium_user': is_premium_user,
        'item_count': len(['Apple', 'Banana', 'Cherry']),  # 计算项的数量
    }

    return render(request, 'myapp/index.html', context)

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Index Page</title>
</head>
<body>
    <h1>{{ greeting }}</h1>
    
    {% if is_authenticated %}
        <p>Welcome back, {{ user.username }}!</p>
        {% if is_premium_user %}
            <p>Thank you for being a premium member!</p>
        {% else %}
            <p>Consider upgrading to premium for more benefits!</p>
        {% endif %}
    {% else %}
        <p>Please log in to access more features.</p>
    {% endif %}

    <h2>Item List:</h2>
    <ul>
        {% for item in items %}
            <li>{{ item }}</li>
        {% empty %}
            <li>No items available.</li>
        {% endfor %}
    </ul>
    
    {% if item_count > 3 %}
        <p>You have a lot of items!</p>
    {% elif item_count > 0 %}
        <p>You have some items.</p>
    {% else %}
        <p>Your item list is empty.</p>
    {% endif %}
</body>
</html>
Django 模板

解释

  1. 用户状态判断:
    • 使用 is_authenticated 变量判断用户是否已经登录,展示不同的欢迎词。
    • 使用 is_premium_user 变量进一步判断用户是否为高级用户,并根据这个条件展示不同的信息。
  2. 项数量判断:
    • 使用 item_count 变量来判断项的数量,并根据不同的数量范围展示不同的消息。这演示了嵌套的条件结构,也展示了如何使用数量比较的逻辑。
  3. {% for %} 标签用于循环展示信息列表,如果列表为空则展示相应的消息(使用 {% empty %})。

循环语句:使用 {% for %} 标签遍历列表。

语法{% for item in item_list %}{% endfor %}

<ul>
{% for item in item_list %}
    <li>{{ item }}</li>
{% endfor %}
</ul>

b. 其他常用标签

包含文件:使用 {% include %} 加载另一个模板片段。

{% include 'header.html' %}

加载自定义模板标签:使用 {% load %} 引入自定义标签库。

{% load custom_tags %}

过滤器

语法: {{ variable|filter_name }}

过滤器用 | 符号应用于变量,改变其输出格式。例如:

字符串过滤器

<p>{{ name|upper }}</p>  <!-- 将 name 转换为大写 -->

时间过滤器

<p>当前日期: {{ current_date|date:"Y-m-d H:i:s" }}</p> <!-- 使用过滤器格式化日期 -->

代表的含义:
Y:四位数的年份(例如 2023)。
m:两位数的月份(01 到 12)。
d:两位数的日期(01 到 31)。
H:24小时制的小时(0 到 23)。
i:分钟(00 到 59)。
s:秒(00 到 59)。

默认值过滤器

<p>{{ greeting|default:"Hello, Stranger!" }}</p>  <!-- 当 greeting 为 None 或空值时显示默认值 -->

其他类型过滤器:

length
返回列表或字符串的长度。
{{ items|length }}  <!-- 返回 item 列表的元素数量 -->

join
将列表的元素连接成一个字符串。
{{ items|join:", " }}  <!-- 输出如 "Apple, Banana, Cherry" -->

default
如果变量为空,则返回指定的默认值。
{{ your_variable|default:"Default Value" }}  <!-- 如果 your_variable 为空,则输出 "Default Value" -->

lower 和 upper
将字符串转换为小写或大写。
{{ your_string|lower }}  <!-- 转为小写 -->
{{ your_string|upper }}  <!-- 转为大写 -->

truncatewords
截断文本,仅保留指定数量的单词。
{{ long_text|truncatewords:10 }}  <!-- 仅保留前 10 个单词 -->

slice
用于从列表或字符串中提取切片。
{{ items|slice:":3" }}  <!-- 取前 3 个元素 -->
{{ your_string|slice:"0:5" }}  <!-- 取字符串的前 5 个字符 -->

safe
将字符串标记为“安全”,使 HTML 不被转义。
{{ your_html_string|safe }}  <!-- 原样输出 HTML,注意安全性 -->

first 和 last
获取列表的第一个/最后一个元素。
{{ items|first }}  <!-- 获取列表的第一个元素 -->
{{ items|last }}   <!-- 获取列表的最后一个元素 -->

变量和标签的组合

可以同时使用变量和标签。

<ul>
{% for product in products %}
    <li>{{ product.name }} - ${{ product.price|floatformat:2 }}</li>
{% endfor %}
</ul>

模板继承与块

使用 {% block %} 定义可重写的块,结合 {% extends %} 使用模板继承。

a. 基础模板

语法: 使用 {% block block_name %} 和 {% endblock %} 定义可重写区域。

<!-- base.html -->
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}默认标题{% endblock %}</title>
</head>
<body>
    <header>
        {% block header %}
        <h1>欢迎来到我的网站</h1>
        {% endblock %}
    </header>
    <main>
        {% block content %}{% endblock %}
    </main>
    <footer>
        {% block footer %}版权所有 © 2023{% endblock %}
    </footer>
</body>
</html>

b. 子模板

在子模板中,你可以继承一个基础模板并重写其中需要修改的块:

比如说标题和内容不一样,可以单独定义title 和 content 的 block,header 和 footer 照搬

<!-- home.html -->
{% extends "base.html" %}

{% block title %}主页{% endblock %}

{% block content %}
    <h2>主页内容</h2>
    <p>这是主页的内容。</p>
{% endblock %}

模板继承练习 demo

修改项目结构如下

myproject/
    myproject/
        settings.py
        urls.py
    myapp/
        views.py
    templates/
        myapp/
            base.html         # 基础模板
            index.html        # 主页模板
            about.html        # 关于页模板
    manage.py

创建基础模板 base.html

在 templates/myapp/ 下创建一个文件 base.html,该文件将作为所有页面的基础模板:

<!-- myproject/templates/myapp/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}My Django App{% endblock %}</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.5.2/css/bootstrap.min.css">
    <style>
        /* 自定义样式 */
        body {
            padding-top: 56px;
        }
        .sidebar {
            height: 100%;
            position: fixed;
            top: 56px;
            left: 0;
            width: 250px;
            background-color: #f8f9fa;
            padding-top: 20px;
        }
        .content {
            margin-left: 260px;
            padding: 20px;
        }
    </style>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
        <a class="navbar-brand" href="#">My Django App</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav">
                <li class="nav-item active">
                    <a class="nav-link" href="{% url 'index' %}">首页</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="{% url 'about' %}">关于</a>
                </li>
            </ul>
        </div>
    </nav>

    <div class="sidebar">
        <h5>侧边栏</h5>
        <ul class="nav flex-column">
            <li class="nav-item">
                <a class="nav-link" href="{% url 'index' %}">首页</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="{% url 'about' %}">关于</a>
            </li>
        </ul>
    </div>

    <div class="content">
        {% block content %}
        {% endblock %}
    </div>
    
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>

修改主页模板 index.html

在 templates/myapp/ 下创建 index.html 文件,继承 base.html

<!-- myproject/templates/myapp/index.html -->
{% extends 'myapp/base.html' %}

{% block title %}首页 - My Django App{% endblock %}

{% block content %}
    <h1>欢迎来到我的 Django 应用</h1>
    <p>这是一个使用 Django 创建的示例网页。</p>
{% endblock %}

创建关于页面模板 about.html

同样在 templates/myapp/ 下创建 about.html 文件:

<!-- myproject/templates/myapp/about.html -->
{% extends 'myapp/base.html' %}

{% block title %}关于我们 - My Django App{% endblock %}

{% block content %}
    <h1>关于我们</h1>
    <p>这是关于我们页面的内容。</p>
{% endblock %}

修改视图

# myapp/views.py
from django.shortcuts import render

def index(request):
    context = {'name': 'World'}
    return render(request, 'myapp/index.html', context)

def about(request):
    return render(request, 'myapp/about.html')

配置 URL

在 myapp/urls.py 中,添加关于页面的 URL 配置:

# myapp/urls.py
from django.urls import path
from .views import index, about

urlpatterns = [
    path('', index, name='index'),  # 根 URL 映射到 index 视图
    path('about/', about, name='about'),  # 关于页面的 URL
]
Django 模板
Django 模板

动态 URL 使用

可以在模板中使用来自视图的动态 URL:使用 {% url 'view_name' arguments %} 来生成 URL。

<a href="{% url 'product_detail' product.id %}">{{ product.name }}</a>

自定义模板标签和过滤器

如果 Django 提供的标签和过滤器不足够满足你的需求,您还可以创建自己的标签和过滤器。

a. 自定义过滤器

# myapp/templatetags/custom_filters.py
from django import template

register = template.Library()

@register.filter
def add_suffix(value, suffix):
    return f"{value}{suffix}"

在模板中使用:

<p>{{ username|add_suffix:"!" }}</p>  <!-- 输出 "username!" -->

处理静态文件

在模板中处理静态文件,首先确保在 settings.py 中配置了 STATIC_URL 和 STATICFILES_DIRS。接着在模板中引入静态文件。

{% load static %}
<img src="{% static 'images/logo.png' %}" alt="Logo">

jinja2 模板学习

一、安装 Jinja2

Django 默认自带 DTL,但 Jinja2 需要自己安装:

pip install Jinja2

二、修改 settings.py 配置模板引擎

TEMPLATES 中增加 Jinja2 配置(可以同时保留 Django 的模板引擎,也可以只用 Jinja2)。

我们这里示例 同时支持 DTL 和 Jinja2

# myproject/settings.py
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.jinja2.Jinja2',   # ✅ 使用 Jinja2
        'DIRS': [os.path.join(BASE_DIR, 'templates')],         # 全局模板目录
        'APP_DIRS': True,
        'OPTIONS': {
            'environment': 'myproject.jinja2.environment',     # 指定 Jinja2 环境
        },
    },
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',  # ✅ 保留 DTL
        'DIRS': [os.path.join(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',
            ],
        },
    },
]

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

三、创建 jinja2.py 配置文件

myproject/ 目录下新建 jinja2.py,配置 Jinja2 的环境:

# myproject/jinja2.py
from jinja2 import Environment
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse

def environment(**options):
    env = Environment(**options)
    # 注册一些全局函数,方便模板里用
    env.globals.update({
        'static': staticfiles_storage.url,  # {% static 'css/style.css' %}
        'url': reverse,                     # {{ url('index') }}
    })
    return env

四、修改模板文件

Jinja2 的语法和 DTL 非常相似,主要差别:

  • 变量输出{{ name }} (和 DTL 一样)
  • 控制语句{% if ... %}{% endif %}(和 DTL 一样)
  • 过滤器{{ name|upper }} (和 DTL 一样,但有些 Django 特有过滤器不可用)

初步版本模板几乎不用修改

index.html

<!-- myproject/templates/myapp/index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>My First Jinja2 Template</title>
</head>
<body>
    <h1>Hello, {{ name }}!</h1>
</body>
</html>

五、修改视图

Django 渲染模板时,需要告诉它用哪个引擎:

  • 默认 render() 用 Django Template Engine (DTL)。
  • 想用 Jinja2,可以用 django.template.loader.render_to_string,并指定引擎。

例子:

# myapp/views.py
from django.http import HttpResponse
from django.template.loader import render_to_string

def index(request):
    context = {'name': 'Jinja2 World'}
    html = render_to_string('myapp/index.html', context, using='jinja2')  # ✅ 指定 Jinja2
    return HttpResponse(html)

六、访问效果

python manage.py runserver
Django 模板

Jinja2 过滤器

概念:过滤器就是一种在模板中“加工变量”的方法,用来改变变量的输出结果。

语法{{ 变量 | 过滤器 }}
也可以链式使用:{{ 变量 | 过滤器1 | 过滤器2 }}

特点

  • 类似 Python 函数,但更方便在模板里用。
  • 可以对字符串、数字、列表、字典等操作。

常用内置过滤器

过滤器功能示例
upper转大写`{{ 'abc'
lower转小写`{{ 'ABC'
title每个单词首字母大写`{{ 'hello world'
capitalize只首字母大写`{{ 'hello world'
length返回长度`{{ [1,2,3]
default('默认值')如果变量为空使用默认值`{{ name
replace('旧','新')替换字符串`{{ 'hello'
round(n)数字四舍五入`{{ 3.14159
join(', ')把列表拼成字符串`{{ ['a','b','c']
reverse反转列表或字符串`{{ [1,2,3]

基础示例

假设 views.py 里 context:

context = {
    'name': 'jinja2 world',
    'numbers': [1, 2, 3, 4, 5],
    'price': 12.3456,
    'empty_value': '',
}

字符串过滤器

<h1>{{ name|upper }}</h1>         <!-- JINJA2 WORLD -->
<h2>{{ name|title }}</h2>         <!-- Jinja2 World -->
<h3>{{ name|replace('world','Python') }}</h3>  <!-- jinja2 Python -->
<h4>{{ empty_value|default('默认值') }}</h4>   <!-- 默认值 -->

列表过滤器

<p>长度:{{ numbers|length }}</p>          <!-- 5 -->
<p>反转:{{ numbers|reverse }}</p>        <!-- [5,4,3,2,1] -->
<p>拼接:{{ numbers|join(', ') }}</p>      <!-- 1, 2, 3, 4, 5 -->

数字过滤器

<p>原价:{{ price }}</p>                  <!-- 12.3456 -->
<p>保留两位小数:{{ price|round(2) }}</p>  <!-- 12.35 -->

自定义过滤器

如果内置过滤器不够用,可以在 jinja2.py 中注册自定义过滤器:

def double(value):
    return value * 2

def environment(**options):
    from jinja2 import Environment
    env = Environment(**options)
    env.filters['double'] = double   # 注册过滤器
    return env

模板里就能用:

<p>{{ 5 | double }}</p>  <!-- 10 -->

练习

views.py 示例

# myapp/views.py
from django.http import HttpResponse
from django.template.loader import render_to_string

def filter_demo(request):
    context = {
        'name': 'jinja2 world',
        'numbers': [1, 2, 3, 4, 5],
        'price': 12.3456,
        'empty_value': '',
    }
    html = render_to_string('myapp/index.html', context, using='jinja2')
    return HttpResponse(html)

templates/myapp/index.html 示例

<!DOCTYPE html>
<html>
<head>
    <title>Jinja2 过滤器示例</title>
</head>
<body>
    <h1>字符串过滤器</h1>
    <p>原始值:{{ name }}</p>
    <p>大写:{{ name|upper }}</p>
    <p>小写:{{ name|lower }}</p>
    <p>首字母大写:{{ name|capitalize }}</p>
    <p>每个单词首字母大写:{{ name|title }}</p>
    <p>替换 "world" 为 "Python":{{ name|replace('world','Python') }}</p>
    <p>默认值(空值使用默认):{{ empty_value|default('默认值') }}</p>

    <hr>

    <h1>数字过滤器</h1>
    <p>原始价格:{{ price }}</p>
    <p>四舍五入保留两位小数:{{ price|round(2) }}</p>

    <hr>

    <h1>列表过滤器</h1>
    <p>原始列表:{{ numbers }}</p>
    <p>长度:{{ numbers|length }}</p>
    <p>反转:{{ numbers|reverse|list }}</p>
    <p>拼接为字符串:{{ numbers|join(', ') }}</p>

    <hr>

    <h1>自定义过滤器示例</h1>
    <p>原始数字 5,使用自定义 double 过滤器:{{ 5 | double }}</p>
</body>
</html>

自定义过滤器 jinja2.py

# myproject/jinja2.py
from jinja2 import Environment
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse

# 自定义过滤器
def double(value):
    return value * 2

def environment(**options):
    env = Environment(**options)
    # 注册全局函数
    env.globals.update({
        'static': staticfiles_storage.url,
        'url': reverse,
    })
    # 注册自定义过滤器
    env.filters['double'] = double
    return env
Django 模板

生产环境的 Jinja2 模板项目改造

如果要把这个示例改造成 贴近生产环境的 Jinja2 模板项目,那就需要做到:

  • 模板结构化(模板继承、组件化);
  • 统一布局(layout):header、footer、导航、消息提示;
  • 支持静态文件(CSS、JS、图片);
  • 支持模板继承 & 块定义(方便扩展页面);
  • 支持全局变量和函数(url、static)
  • 预留用户认证、消息系统等常用功能

基于当前的 myproject 项目进行完整改造

一、项目目录结构(推荐生产结构)

myproject/
    myproject/
        settings.py
        urls.py
        jinja2.py              # Jinja2 环境配置
    templates/
        base.html               # 全局基础模板
        includes/               # 公共组件
            navbar.html
            footer.html
            messages.html
        myapp/
            index.html          # 首页(继承 base.html)
            about.html          # 示例页面
    static/                     # 静态文件
        css/
            style.css
        js/
            app.js
    myapp/
        views.py
        urls.py
    manage.py

二、配置 jinja2.py(生产推荐)

# myproject/jinja2.py
from jinja2 import Environment
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
from datetime import datetime

def environment(**options):
    env = Environment(**options)

    # 全局函数
    env.globals.update({
        'static': staticfiles_storage.url,  # {{ static('css/style.css') }}
        'url_for': reverse,                  # {{ url_for('view_name') }}
        'now': datetime.now,                 # {{ now().year }}
    })

    return env

三、基础模板(base.html)

{# templates/base.html #}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}My Jinja2 Project{% endblock %}</title>
    <link rel="stylesheet" href="{{ static('css/style.css') }}">
    {% block head %}{% endblock %}
</head>
<body>
    {% include "includes/navbar.html" %}
    {% include "includes/messages.html" %}

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

    {% include "includes/footer.html" %}

    <script src="{{ static('js/app.js') }}"></script>
    {% block scripts %}{% endblock %}
</body>
</html>

四、公共组件(includes)

导航栏 navbar.html

{# templates/includes/navbar.html #}
<nav>
    <a href="{{ url_for('index') }}">Home</a> |
    <a href="{{ url_for('about') }}">About</a> |
    {% if request.user.is_authenticated %}
        <span>Welcome, {{ request.user.username }}!</span>
        <a href="#">Logout</a>
    {% else %}
        <a href="#">Login</a>
    {% endif %}
</nav>
<hr>

消息提示 messages.html

{# templates/includes/messages.html #}
{% if messages %}
    <ul class="messages">
    {% for message in messages %}
        <li class="message {{ message.tags }}">{{ message }}</li>
    {% endfor %}
    </ul>
{% endif %}

页脚 footer.html

{# templates/includes/footer.html #}
<footer>
    <hr>
    <p>&copy; {{ now.year }} My Jinja2 Project</p>
</footer>

五、应用模板(继承 base.html)

index.html

{# templates/myapp/index.html #}
{% extends "base.html" %}

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

{% block content %}
<h1>Hello, {{ name }}!</h1>
<p>Welcome to the Jinja2 version of this Django site.</p>
{% endblock %}

about.html

{# templates/myapp/about.html #}
{% extends "base.html" %}

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

{% block content %}
<h1>About Page</h1>
<p>This is a sample about page for the Jinja2 Django project.</p>
{% endblock %}

六、视图改造(views.py)

# myapp/views.py
from django.http import HttpResponse
from django.template.loader import render_to_string
from datetime import datetime

def index(request):
    context = {
        'name': 'Jinja2 World',
        'request': request,
        'now': datetime.now(),
    }
    html = render_to_string('myapp/index.html', context, using='jinja2')
    return HttpResponse(html)

def about(request):
    context = {
        'request': request,
        'now': datetime.now(),
    }
    html = render_to_string('myapp/about.html', context, using='jinja2')
    return HttpResponse(html)

七、urls.py 配置

# myapp/urls.py
from django.urls import path
from .views import index, about

urlpatterns = [
    path('', index, name='index'),
    path('about/', about, name='about'),
]

八、静态文件示例

static/css/style.css

body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f9f9f9; /* 整体背景浅灰,突出内容 */
}

nav {
    background-color: #1e90ff;  /* 鲜艳的深蓝色,突出导航 */
    color: white;               /* 字体颜色白色,更醒目 */
    padding: 15px 20px;
    font-size: 1.1em;
}

footer {
    background-color: #333333;  /* 深灰色底,突出底部信息 */
    color: #f0f0f0;             /* 字体浅色,更清晰 */
    text-align: center;
    padding: 15px 10px;
    font-size: 0.9em;
}

static/js/app.js

console.log("Jinja2 Django project loaded.");

项目就拥有了一个 生产环境级别的 Jinja2 模板结构

  • 可扩展的 base.html
  • 公共组件拆分(导航、页脚、消息)
  • 支持静态文件加载
  • 全局函数 url / static
  • 适配 Django 的 messages / user

测试访问

Django 模板

在首页按 F12(开发者工具)Network,看看是否加载了:

  • /static/css/style.css
  • /static/js/app.js
Django 模板

csrf

CSRF 的原理

CSRF 的风险在于:浏览器会自动带上受信任站点的 Cookie(包括登录 Session),攻击者可以诱导用户浏览器向受信任站点发送请求,浏览器会带上这些 Cookie,使得目标站点误以为是用户本人在操作。

Django 模板

CSRF 的防护思路:

为了理解防护思路,我们必须先知道:

攻击者能做什么(浏览器的行为)

    • 攻击者可以让受害者访问攻击者控制的页面(attacker.com)。
    • 攻击者页面可以构造并自动提交一个指向 target.com 的表单、或用 、<script>、fetch(受限)等方式让浏览器向 target.com 发送请求。</script>
    • 浏览器在发向 target.com 的请求时,会自动带上属于 target.com 的 cookie(包括登录 session cookie)。这是浏览器的正常行为,也是 CSRF 利用的根本。

    攻击者不能做的(同源策略和浏览器限制)

      • 攻击者不能从受害者浏览器中读取 target.com 的 cookie 或页面 DOM(比如页面上隐藏的 CSRF token),因为同源策略禁止跨域访问 document.cookie、DOM、XHR 返回等资源。
      • 所以攻击者无法“得知”目标站点的 token 值,不能把正确的 token 填到自己构造的请求里。

      服务器让浏览器在页面里包含一个无法由第三方站点获取或伪造的随机 token(通常放在 cookie + 表单字段中),服务器在接到修改性请求(如 POST)时,校验提交的 token 是否与浏览器当前的 cookie 中的 token 一致 —— 不一致则拒绝。

      更具体的实现(Django 常用):

      1. 生成 token:服务器(Django)生成一个随机 token,并通过 Set-Cookie 把它放到浏览器的 csrftoken cookie 里(这个 cookie 浏览器会自动发送回服务端,但第三方站点无法读取该 cookie 的值(受同源策略保护))。
      2. 页面里插入 token:服务端在模板里把同样的 token 渲染成隐藏字段 <input name="csrfmiddlewaretoken" value="token">(或者前端 AJAX 把 cookie 值作为 X-CSRFToken header 发送)。
      3. 提交校验:当浏览器提交 POST(或其它“非安全”方法)时,服务器会比对 POST 表单字段里的 token(或 X-CSRFToken header 的值)与浏览器发送的 cookie 值是否匹配。匹配就通过;不匹配或缺失就返回 403(拒绝)。

      CSRF 实验

      项目结构

      myproject/
      ├─ manage.py
      ├─ myproject/
      │  ├─ settings.py       
      │  ├─ urls.py           
      │  ├─ jinja2.py         # 自定义 Jinja2 environment:扩展、全局函数(我们放 csrf 扩展在这里)
      │  ├─ wsgi.py
      ├─ myapp/
      │  ├─ views.py          # 视图:渲染 index, 处理 submit
      │  ├─ urls.py (可选)
      ├─ templates/
      │  └─ myapp/
      │      ├─ index.html    # 表单页(使用 {% csrf_token %})
      │      └─ result.html   # 提交结果页

      myproject/settings.py

      # 模板引擎:先保留 DjangoTemplates(Admin 需要),再添加 Jinja2
      
      MIDDLEWARE = [
          ...
          "django.middleware.csrf.CsrfViewMiddleware",   # ✅ 必须启用
          ...
      ]
      
      TEMPLATES = [
          {
              "BACKEND": "django.template.backends.django.DjangoTemplates",
              "DIRS": [os.path.join(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",
                  ],
              },
          },
          {
              "BACKEND": "django.template.backends.jinja2.Jinja2",
              "DIRS": [os.path.join(BASE_DIR, "templates")],
              # APP_DIRS: False/True 均可,根据你是否想自动加载 app 内的模板选择
              "APP_DIRS": False,
              "OPTIONS": {
                  "environment": "myproject.jinja2.environment",
              },
          },
      ]

      myproject/jinja2.py

      自定义 CsrfExtension 来实现 {% csrf_token %};并暴露 staticurl 全局函数。

      from jinja2 import Environment
      from django.contrib.staticfiles.storage import staticfiles_storage
      from django.urls import reverse
      from django.middleware.csrf import get_token
      
      
      def environment(**options):
          env = Environment(**options)
          env.globals.update({
              'static': staticfiles_storage.url,
              'url': reverse,
              'csrf_token': lambda request: get_token(request),  # CSRF Token 函数
          })
          return env

      这里定义了 csrf_token(request) 可在模板中调用。

      myproject/urls.py

      from django.contrib import admin
      from django.urls import path, include
      
      urlpatterns = [
          path('admin/', admin.site.urls),
          path('', include('myapp.urls')),  # myapp 的路由
      ]

      myapp/urls.py

      from django.urls import path
      from . import views
      
      urlpatterns = [
          path('', views.index, name='index'),
          path('submit/', views.submit, name='submit'),
      ]

      myapp/views.py

      from django.shortcuts import render, redirect
      from django.views.decorators.csrf import csrf_protect
      from django.urls import reverse
      from django.middleware.csrf import get_token
      
      @csrf_protect
      def index(request):
          context = {
              'submit_url': reverse('submit'),
              'csrf_token_value': get_token(request),
          }
          return render(request, 'myapp/index.html', context)
      
      @csrf_protect
      def submit(request):
          if request.method == 'POST':
              name = request.POST.get('name', '')
              return render(request, 'myapp/result.html', {
                  'name': name,
                  'index_url': reverse('index'),
              })
          else:
              return redirect('index')
      

      templates/myapp/index.html

      <!DOCTYPE html>
      <html>
      <head>
          <title>CSRF Demo</title>
      </head>
      <body>
          <h1>CSRF Demo 表单</h1>
          <form action="{{ submit_url }}" method="post">
              <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token_value }}">
              <label for="name">姓名:</label>
              <input type="text" name="name" id="name">
              <button type="submit">提交</button>
          </form>
      </body>
      </html>

      templates/myapp/result.html

      <!DOCTYPE html>
      <html>
      <head>
          <title>提交结果</title>
      </head>
      <body>
          <h1>提交结果</h1>
          <p>你输入的名字是:{{ name }}</p>
          <a href="{{ index_url }}">返回表单</a>
      </body>
      </html>

      测试:

      Django 模板

      删除掉 cookie 后重新提交

      Django 模板

      返回 403

      Django 模板

      发布者:LJH,转发请注明出处:https://www.ljh.cool/43737.html

      Like (0)
      LJH的头像LJH
      Previous 2025年8月15日 下午4:37
      Next 2021年9月7日 下午3:57

      相关推荐

      发表回复

      您的邮箱地址不会被公开。 必填项已用 * 标注