Django模板层
一 模板简介
在刚刚介绍完的视图层中我们提到,浏览器发送的请求信息会转发给视图进行处理,而视图在经过一系列处理后必须要有返回信息给浏览器。如果我们要返回html标签、css等数据给浏览器进行渲染,我们可以在视图中这么做
from django.shortcuts import HttpResponse
import time
# 返回静态内容(静态页面):页面内容固定不变
def index(request):
html = "<html><body><h1>静态内容,固定不变</h1></body></html>"
return HttpResponse(html)
# 返回动态内容(动态页面):后台处理会用变量值填充内容,每次得到的内容都可能不同
def current_datetime(request):
now_time = time.strftime('%Y-%m-%d %X')
html = "<html><body><h1>动态内容,%s</h1></body></html>" % now_time
return HttpResponse(html)
上例所示,我们直接将HTML代码放到视图里,然后进行返回,这可以使我们很直观地看清楚浏览器从发送请求到看到前端界面内容的这个过程中视图的基本工作原理,但是这种将前端代码与后端代码完全耦合到了一起开发方式会使得程序的可维护性与可扩展性变差
前端界面一旦需要重新设计、修改,则必须对后端的Python代码进行相应的修改。 然而前端界面的修改往往比后端 Python 代码的修改要频繁得多,因此如果可以在不进行 Python 代码修改的情况下变更前端界面的设计,那将会方便得多。
我们可以很容易想到的解决方案就是
# 1、首先将前端代码放入单独的HTML文件中
# 2、然后编写查找/加载这些文件的统一方法/API(否则需要在每个视图里重复编写查找/加载的代码)。
作为一个成熟的Web框架,上述方案早已被Django实现:
# 1、Django提供了模板系统 (Template System)用来专门定制html文件,一个html文件称之为一个模板
对于静态页面来说,直接编写就好
而针对动态页面,django额外提供了专门的模板语言(Django template language,简称DTL),允许我们在页
面中嵌入模板变量,这便为后期为页面动态填充内容提供了可能性。
DTL是模板系统的核心,因此django的模板系统也被等同于DTL
详见第三小节
# 2、Django定义了一套标准的API用来查找/加载(读取并进行预处理称之为加载)并且渲染模板
渲染rendering指的是用上下文数据context data插入/填充模板并返回结果,结果为字符串。
将要插入/填充入模板的变量组织到一个字典里,该字典称之为上个文context data
详见第二小节->2.1
![c2e31fbfb7d43177ab822b14426e8b66.png](https://i-blog.csdnimg.cn/blog_migrate/34a8f013720042c1b0beae01be3b0ce1.png)
一个简单的示例如下
templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>静态内容,固定不变</h1>
</body>
</html>
templates/current_datetime.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>动态内容,{{ now }}</h1>
</body>
</html>
views.py
from django.shortcuts import render
import time
# 返回静态内容(静态页面)
def index(request):
return render(request,'index.html')
# 返回动态内容(动态页面)
def current_datetime(request):
now_time = time.strftime('%Y-%m-%d %X')
context={"now":now_time}
return render(request,'current_datetime.html',context)
urls.py
from django.urls import path
from app01.views import *
urlpatterns = [
path('index/', index),
path('current_datetime/', current_datetime),
]
二 模板的使用
2.1 标准API
Django内置了一套标准API(兼容任何模板引擎的BACKEND)用于查找/加载、渲染模板。历史原因,模板的相关实现都存在于django.template名称空间中。
# 例如
# 1、django.template.loader 加载器,定义了函数get_template与select_template用于模板的查找
get_template(template_name, using=None)
select_template(template_name_list, using=None)
# 2、django.template.backends 模板引擎后端实现
django.template.backends.django.DjangoTemplates
django.template.backends.jinja2.Jinja2
# 3、Template.render(context=None, request=None) 用于渲染模板
from django.template.backends.django import Template
from django.template.backends.jinja2 import Template
# 4、django.template.loader.render_to_string 是一个快捷方法,内部就是调用1和3的API
render_to_string(template_name, context=None, request=None, using=None)
# 若想深入了解,可以参照官网来阅读源码https://docs.djangoproject.com/en/3.0/topics/templates/#usage
上述API了解即可,因为我们在日常开发过程中,常用的一个render方法(from django.shortcuts import render),其内部已经整合了上述API。
from django.shortcuts import render
# 通过查看源码会发现render内部就是在调用上述API
render(request, template_name, context=None, content_type=None, status=None, using=None)
render参数介绍,详细使用见下一小节
1、request就是一个HttpRequest对象
2、template_name:
可以是一个单独的模板
'story_detail.html'
'news/story_detail.html'
也可以是一个模板列表
['story_1_detail.html', 'story_detail.html']
3、context:
是一个字典,包含了将要插入/填充入模板的变量,称之为上个文数据(context data)
4、content_type
指定响应的内容类型,如content_type='text/plain; charset=utf-8'
5、status:
指定响应的状态码,如status=200
6、using:
指定使用的模板引擎名字,如using='name1',代表使用名为name1的模板引擎
2.2 模板引擎配置
要解析DTL,需要有专门的解析器,称之为template engine模板引擎,需要在settings.py中配置TEMPLATES列表,其默认值为空,但在创建项目时会自动生成如下配置
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',
],
},
},
]
列表中一个字典就是一个引擎配置,一个django项目可以配置一个或多个模板引擎(当然,如果你不需要使用模板,也可以不配置),如下
TEMPLATES = [
# 引擎配置一
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
# ... 同上,篇幅问题,下述带有同上字样的请读者在测试时自行填充上述代码 ...
},
},
# 引擎配置二
{
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
# ... 同上 ...
},
},
]
1)关键配置项之BACKEND:
每个引擎都需要有专门的后端程序BACKEND,BACKEND的值为要导入的python类,Django为自己的模块系统以及非常受欢迎的Jinja2模板系统内置了后端
django.template.backends.django.DjangoTemplates
django.template.backends.jinja2.Jinja2
而其他模板语言的后端需要从第三方获得。
#1、基于安全性考虑,最好不要使用不知名作者开发的第三方模板系统
#2、如果你不是必须选择其他的模板系统,还是推荐使用DTL
2)关键配置项之DIRS与APP_DIRS
由于大多数引擎加载的模板都是文件,这就涉及到文件的路径查找,所以在每个引擎配置的顶级都包含两个相似的配置来专门负责路径查找问题
'DIRS': [],
1、列表中包含一系列的目录,引擎会按照列表的顺序依次查找模板文件
2、列表为空则代表没有对应的查找目录
'APP_DIRS': True, # 默认为False
1、APP_DIRS 值为True时,会去按照INSTALLED_APPS = []中注册的app顺序,依次去每个app目录下的templates目录中查找模板文件
2、APP_DIRS 值为False时,不会检索app目录下的templates目录
查找的优先级为: djanog会先依次检索每个引擎下的DIRS,然后按照APP_DIRS的规定去找模板,直到找到为止。下面我们就依次举例进行验证
我们先将ARR_DIRS设置为False来讨论加载顺序,如下
TEMPLATES = [
{
# django引擎
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
'/home/html/example.com',
'/home/html/default',
],
'APP_DIRS': False, # 关闭去每个APP下查找,不设置默认值为False
'OPTIONS': {
# ... 同上 ...
},
},
{
# jinja2引擎
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [
'/home/html/jinja2',
],
'APP_DIRS': False, # 关闭去每个APP下查找,不设置默认值为False
'OPTIONS': {
# ... 同上 ...
},
},
]
若调用render(request,‘story_detail.html’),查找顺序如下,找到为止
# 1、先检索列表中的第一个引擎,即'django' 引擎
/home/html/example.com/story_detail.html
/home/html/default/story_detail.html
# 2、再检索列表中的第二个引擎,即'jinja2' 引擎
/home/html/jinja2/story_detail.html
若调用render(request,['story_1_detail.html', 'story_detail.html']),查找顺序如下,找到为止
# 一:首先查找模板story_1_detail.html
# 1.1、先检索列表中的第一个引擎,即'django' 引擎
/home/html/example.com/story_1_detail.html
/home/html/default/story_1_detail.html
# 1.2、再检索列表中的第二个引擎,即'jinja2' 引擎
/home/html/jinja2/story_1_detail.html
# 二:其次查找模板story_detail.html
# 2.1、先检索列表中的第一个引擎,即'django' 引擎
/home/html/example.com/story_detail.html
/home/html/default/story_detail.html
# 2.2、再检索列表中的第二个引擎,即'jinja2' 引擎
/home/html/jinja2/story_detail.html
所以针对['story_1_detail.html', 'story_detail.html'],后一个模板通常作为第一个模板的备胎,从而保障第一个模板在未找到时依然能够找到模板作为替代品,这样便可以使我们的代码更为灵活,如下
def article(request, article_id):
first_template = 'article%s.html' % article_id
bak_template = 'article.html'
return render(request, [first_template, bak_template])
然后我们ARR_DIRS设置为True来讨论完整的加载顺序,需要事先在每个app目录下顶层建立目录templates(注意,目录名字必须为templates),
/Users/linhaifeng/PycharmProjects/EgonPro
├── app01
│ ├── templates # 目录名必须为templates
├── app02
│ ├── templates # 目录名必须为templates
然后配置如下
TEMPLATES = [
{
# django引擎
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
'/home/html/aaa',
'/home/html/bbb',
],
'APP_DIRS': True, # 关闭去每个APP下查找,不设置默认值为False
'OPTIONS': {
# ... 同上 ...
},
},
{
# jinja2引擎
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [
'/home/html/ccc',
],
'APP_DIRS': True, # 关闭去每个APP下查找,不设置默认值为False
'OPTIONS': {
# ... 同上 ...
},
},
]
若调用render(request,‘story_detail.html’),查找顺序如下,找到为止
# 一:djanog会先依次检索每个引擎下的DIRS
# 1.1、先检索列表中的第一个引擎,即'django' 引擎
/home/html/aaa/story_detail.html
/home/html/bbb/story_detail.html
# 1.2、再检索列表中的第二个引擎,即'jinja2' 引擎
/home/html/ccc/story_detail.html
# 二:因为APP_DIRS:True,所以接下来会按照INSTALLED_APPS中注册app的顺序,依次去每个app下的templates目录里查找
INSTALLED_APPS = [
# ......
'app02.apps.App02Config',
'app01.apps.App01Config',
]
# 2.1 先检索app02下的tempaltes目录
/Users/linhaifeng/PycharmProjects/EgonPro/app02/templates/story_detail.html
# 2.2 再检索app01下的tempaltes目录
/Users/linhaifeng/PycharmProjects/EgonPro/app01/templates/story_detail.html
ps:针对render(request,['story_1_detail.html', 'story_detail.html']),会按照列表中规定的模板顺序依次重复上述查找步骤,直到某一模板查找成功为止。
在一个项目的实际开发过程中,通常会包含多个app,多个app会有一些公共的模板文件,同时每个app也都会有自己对应的模板文件,要想清晰地组织这些模板,有如下两种解决方案
方案一:设置ARR_DIRS为False
#一:在项目根目录下的templates目录中新建子目录用来区分不同的模板归属
/Users/linhaifeng/PycharmProjects/EgonPro
├── templates
│ ├── base # 用于存放公共模板
│ │ ├── base.html
│ ├── app01 # 用于存放app01单独的模板
│ │ ├── index.html
│ ├── app02 # 用于存放app02单独的模板
│ │ ├── index.html
#二:配置如下
TEMPLATES = [
# 引擎配置一
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': False,
'OPTIONS': {
# ... 同上 ...
},
},
]
#三:渲染方式
render(request,'base/base.html')
render(request,'app01/index.html')
render(request,'app02/index.html')
方案二:设置ARR_DIRS为True(推荐使用)
#一:在ARR_DIRS为True的情况下,会检索每个app的templates目录,所以需要事先创建好下述目录:
1、项目根目录/templates
2、项目根目录/app01/templates # 目录名字必须为templates
3、项目根目录/app02/templates # 目录名字必须为templates
注意:如果只创建到这一层就结束了,会出现冲突
比如render(request,'index.html'),在三个目录中都存在同名模板的情况下,查找优先级会是1,2,3(假设只有配置了一个引擎,且app的注册顺序为app01、app02)。
如果我们既想清晰地组织模板的目录结构,又想找到指定的模板、避免冲突,那么需要在1、2、3的基础上继续建立子目录,子目录名无所谓,但其发挥的作用,相当于名称空间了,如下
/Users/linhaifeng/PycharmProjects/EgonPro
├── templates
│ ├── base
│ │ ├── base.html
├── app02
│ ├── templates
│ │ └── app01
│ │ └── index.html
├── app02
│ ├── templates
│ │ └── app02
│ │ └── index.html
#二:配置如下
TEMPLATES = [
# 引擎配置一
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
# ... 同上 ...
},
},
]
#三:渲染方式
# 最终找到的位置/templates/base/base.html
render(request,'base/base.html')
# 最终找到的位置/app01/templates/app01/index.html
render(request,'app01/index.html')
# 最终找到的位置/app02/templates/app02/index.html
render(request,'app02/index.html')
(3)关键配置项目之NAME
在调用render()时,除非指定了引擎,否则会按照列表规定的顺序依次使用引擎来查找/加载模板,直到查找/加载成功为止。
如果想选用指定的模板引擎,需要使用参数NAME为每个模板引擎设定唯一的名字,然后在render中使用using参数指定即可
#一:settings.py配置如下
TEMPLATES = [
# 引擎配置一
{
'NAME': 'b1',
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'aaa')],
'APP_DIRS': True,
'OPTIONS': {
# ... 同上 ...
},
},
# 引擎配置二
{
'NAME': 'b2',
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'bbb')],
'APP_DIRS': True,
'OPTIONS': {
# ... 同上 ...
},
},
]
#二:为render函数指定using参数
render(request, 'index.html',using='b1') # b1->限定使用引擎配置一
render(request, 'index.html',using='b2') # b2->限定使用引擎配置二
ps:如果没有设定参数NAME,那么引擎的名字默认为BACKEND按照点为分隔符的倒数第二个值,例如
'django.template.backends.django.DjangoTemplates' 引擎名字为django
'django.template.backends.jinja2.Jinja2' 引擎名字为jinjia2
(4)关键配置项之OPTIONS
OPTIONS值为一字典,包含了要传递到模板后端的额外参数。比如
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',
],
},
},
]
我们在模板中常用的变量{{ request }}就是'django.template.context_processors.request'的功劳。
更多配置项请参照官网https://docs.djangoproject.com/en/3.0/topics/templates/#module-django.template.backends.django
![a339983c1116590995df45192a142e54.png](https://i-blog.csdnimg.cn/blog_migrate/e173a6f9201cca3390b8d7bd708f7c8e.png)
三 DTL语法
Django模板指的是用Django模板语言(DTL)标记的文本文档(如HTML、XML、CSV等任何文本文档都可以)或者python字符串(文本文档也是由字符组成)。
DTL的语法主要由四部分构成:变量、过滤器、标签、注释,如下
{% extends "base_generic.html" %}
{% block title %}{{ section.title }}{% endblock %}
{% block content %}
{# <h1>hello</h1> #}
<h1>{{ section.title }}</h1>
{% for story in story_list %}
<h2>
<a href="{{ story.get_absolute_url }}">
{{ story.headline|upper }}
</a>
</h2>
<p>{{ story.tease|truncatewords:"100" }}</p>
{% endfor %}
{% endblock %}
让我们来分别作详细介绍