b、模板
模板
每一个Web框架都需要一种便利的方法用于动态渲染HTML页面。 最常见的做法是使用模板系统。
Django可以配置一个或多个模板引擎(语言),也可以不用引擎。
Django自带一个称为DTL(Django Template Language )的模板语言,以及另外一种流行的Jinja2语言(需要提前安装,pip install Jinja2)。
Django为加载和渲染模板定义了一套标准的API,与具体的后台无关。加载指的是,根据给定的模版名称找到的模板然后预处理,通常会将它编译好放在内存中。渲染则是使用Context数据对模板插值并返回生成的字符串。
模板用于快速生成动态页面返回给客户端,模板是⼀个文本,用于分离文档的表现形式和内容。 模板定义了占位符以及各种用于规范文档该如何显示的模板标签。
模板通常用于产生HTML,但是Django的模板也能产生任何基于文本格式的文档。
模板包含两部分:
- html代码
- 模板标签
⼀、模板位置
-
在应用中建立templates目录,好处不需要注册,不好的地方,有多个应用的时候不能复用页面
-
第⼆种是放在工程的目录下,好处是如果有多个应用,可以调用相同的页面,需要注册
-
需要修改项目的配置文件settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates', # 这里默认使用Django自带的模板引擎
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 模板绝对路径,如果是自己手动创建的django项目,则该内容为空,需要手动设置
'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.django.DjangoTemplates
和django.template.backends.jinja2.Jinja2
。
OPTIONS中包含了具体的后端设置。
由于绝大多数引擎都是从文件加载模板的,所以每种模板引擎都包含两项通用设置:
- DIRS:定义了一个目录列表,模板引擎按列表顺序搜索这些目录以查找模板源文件。
- APP_DIRS:告诉模板引擎是否应该进入每个已安装的应用中查找模板。通常请将该选项保持为True。
每种模板引擎后端都定义了一个惯用的名称作为应用内部存放模板的子目录名称。(例如Django为它自己的模板引擎指定的是 ‘templates’,为jinja2指定的名字是‘jinja2’)。尤其是,django允许你有多个模板引擎后端实例,且每个实例有不同的配置选项。 在这种情况下你必须为每个配置指定一个唯一的NAME .
DTL引擎的OPTIONS配置项中接受以下参数:
-
autoescape
:一个布尔值,用于控制是否启用HTML自动转义功能。默认为True。 -
context_processors
: 以"."为分隔符的Python调用路径的列表。默认是个空列表。 -
'debug'
:打开/关闭模板调试模式的布尔值。默认和setting中的DEBUG有相同的值。 -
'loaders'
:模板加载器类的虚拟Python路径列表。默认值取决于DIRS和APP_DIRS
的值。 -
string_if_invalid
:非法变量时输出的字符串。默认为空字符串。 -
file_charset
:用于读取磁盘上的模板文件的字符集编码。默认为FILE_CHARSET
的值。 -
'libraries'
:用于注册模板引擎。 这可以用于添加新的库或为现有库添加备用标签。 -
'builtins'
:以圆点分隔的Python路径的列表。 每个元素都是一个py文件,文件中编写模板标签和过滤器。这些tag和filter会成为内置的类型,无需通过{% load %}
加载即可开箱使用。
绝大多数情况下,直接使用DTL和默认配置即可。
Django 模板查找机制: Django首先会查找DIRS下配置的模板路径,如果不在的话,则是在每个 app 的templates文件夹中找(而不只是当前 app 中的代码只在当前的 app 的 templates 文件夹中找)。各个 app 的 templates 形成⼀个文件夹列表,Django 遍历这个列表,⼀个个文件夹进行查找,当在某⼀个文件夹找到的时候就停止,所有的都遍历完了还找不到指定的模板的时候就是 Template Not Found (过程类似于Python找包)。这样设计有利当然也有弊,有利是的地方是⼀个app可以用另⼀个app的模板文件,弊是有可能会找错了。所以我们使用的时候在templates 中建立⼀个 app 同名的文件夹,这样就好了。
⼆、模板的渲染
2.1 loader加载
好处是可以加载⼀次模板,然后进行多次渲染
from django.template import loader #导入loader
def index(request):
temp = loader.get_template('index.html')
# 渲染模板,生出html源码
res = temp.render(context={'content':'hello index'})
print(res)
return HttpResponse(res)
2.2 render
from django.shortcuts import render
render(request,templatesname,context=None)
参数:
request:请求对象
templatesname:模板名称
context:参数字典,必须是字典
三、模板语法
django模板中包括两部分:变量和内置标签。变量会在模板渲染时被其值代替,内置标签负责逻辑控制。
3.1 变量
变量在模板中的表示为:{{ 变量名 }},变量名就是render中context中的键,变量的命名包括任何字母数字以及下划线("_")的组合,最重要的是,变量名称中不能有空格或标点符号。变量可以基本类型中的数值、字符串、布尔,也可以是字典、对象、列表等。django提供了点号来访问复杂数据结构。
- 列表、元组的元素可以使用索引引用,不能使用负索引,语法:变量.索引
- 字典: 字典变量.key
- 对象: 对象.属性 对象.方法名(方法不能有参数)
当模板系统在变量名中遇到点时,按照以下顺序尝试进行查找:
- 字典类型查找
- 属性查找
- 方法调用
- 列表类型索引
如果模板中引用变量未传值,则会被置为空,不会报错,除非你对其进行了操作。
3.2 过滤器
过滤器是在变量显示之前修改它的值的⼀个方法,过滤器使用管道符。过滤器可以串联调用
语法格式:
{{ 变量|方法 }}
常见的过滤器方法:
方法名 | 作用 | 示例 |
---|---|---|
default | 缺省值 | {{ li|default:"缺省值"}} |
default_if_none | 如果变量是none,则显示缺省值 | {{ value|default_if_none:'"hello" }} |
cut | 从字符中删除指定字符 | {{ value|cut:" "}} 删除value中所有的空格 |
length | 获取字符串或列表的长度 | {{ str1|length}} |
lower | 将所有字母都变为⼩写 | {{ value|lower}} |
upper | 将所有字母都变为大写 | {{ value|upper}} |
truncatechars | 截取字符串前n个字符 | {{ value|truncatechars:9 }} |
date | 格式化日期字符串 | {{ value|date:"Y-m-d H:i:s"}} |
add | 增加变量的值 | {{ num|add:"3"}} |
divisibleby | 把变量的值除以指定值 | {{ value|divisibleby:"3"}} |
first | 获取列表第⼀个元素 | {{ value|first }} |
last | 获取列表最后⼀个元素 | {{ value|last }} |
join | 将列表内容链接为一个字符串 | {{ value|join:'-' }} |
autoescape | 设置或取消转义 | {% autoescape off %}{{ data }} |
一些过滤器方法带有参数。 过滤器的参数看起来像是这样: {{ bio|truncatewords:30 }}
。 这将显示bio变量的前30个词。
过滤器参数包含空格的话,必须用引号包起来。例如,使用逗号和空格去连接一个列表中的元素,你需要使用{{ list|join:", " }}
。
-
自定义过滤器
内置过滤器功能有限,如果不能满足需求,可以自己定义过滤器。
-
在app里创建⼀个包:templatetags(名字必须是这个,且为包)
-
在包里创建⼀个py文件
from django import template # 实例化自定义过滤器注册对象 register = template.Library() # name代表在模板中使用的过滤器的名称 @register.filter(name='hello') def hello(value,arg): """ :param value: 传给hello过滤的值 :param arg: hello自带的参数 :return: """ return value + str(arg) @register.filter('time_ago') def time_ago(value): """ 定义⼀个距离当前时间多久之前的过滤器 :param value: :return: 1.如果时间间隔⼩于1分钟内,那么就显示刚刚 2.如果时间间隔大于1分钟⼩于1⼩时,那么就显示xx分钟前 3.如果时间间隔大于1⼩时⼩于24⼩时,那么就显示xx⼩时前 4.如果时间间隔大于24⼩时⼩于30天,那么就显示xx天前 5.如果时间间隔大于30天,那么就显示具体时间 """ if not isinstance(value, datetime.datetime): return value now = datetime.datetime.now() timestamp = (now - value).total_seconds() if timestamp < 60: return '刚刚' elif timestamp >= 60 and timestamp < 60 * 60: return '{}分钟前'.format(int(timestamp / 60)) elif timestamp >= 60 * 60 and timestamp < 60 * 60 * 24: return '{}⼩时前'.format(timestamp / 60 / 60) elif timestamp >= 60 * 60 * 24 and timestamp < 60 * 60 * 23 * 30: return‘{}天前'.format(int(timestamp/68/68/24)) else: return value.strftime('%Y-%m-%d %H:%M')
-
在模板中使用
{% load customfilter %} #加载自定义过滤器的模块
Title {{ name |hello:' how are you' }} #使用自定义过滤器 ``` -
3.3 内置标签
语法:{% tag %}
1.if标签
{% if express1 %}
# to do
{% elif express2 %}
# to do
{% else %}
# to do
{% endif %}
- if表达式中使用以下运算符(优先级从⾼到低):
- < >= <= == !=
- in 、not in
- is、is not
- not
- and
- or
- 不要在表达式中使用(),可以使用if嵌套实现功能
- 不⽀持 if 3 < b < 5这种写法
2.for
- 遍历可迭代对象
{% for x in y %}
...
{% endfor %}
- 反向迭代(reversed)
{% for value in c [1,2,3,4,5] reversed %}
<span>{{ value }}---</span>
{% endfor %}
- empty 当可迭代对象为空或不存在时执行,否则不执行
{% for value in c %}
<span>{{ value }}---</span>
{% empty %}
数据不存在
{% endfor %}
- 字典迭代
# e = {'a1':20,'b1':40}
{% for k,v in e.items %}
<div>{{ k }}---{{ v }}</div>
{% endfor %}
- 获取for循环迭代的状态
变量名称 | 变量说明 |
---|---|
forloop.counter | 获取迭代的索引 从1开始 |
forloop.counter0 | 获取迭代的索引 从0开始 |
forloop.revcounter | 迭代的索引从最大递减到1 |
forloop.revcounter0 | 迭代的索引从最大递减到0 |
forloop.first | 是否为第⼀次迭代 |
forloop.last | 是否为最后⼀次迭代 |
forloop.parentloop | 获取上层的迭代对象 |
{% for i in c %}
<li>{{ forloop.first }}</li>
<li>{{ forloop.last }}</li>
<li>{{ forloop.counter }}</li>
<li>{{ forloop.counter0 }}</li>
<li>{{ forloop.revcounter }}</li>
<li>{{ forloop.revcounter0 }}</li>
{% endfor %}
3. ifequal/ifnotequal
用于判断两个值相等或不等的
{% ifequal var var %}
{% endifequal %}
{% ifnotequal var var %}
{% endifnotequal %}
4.注释
- 单行注释
{# 注释内容 #}
- 多行注释
{% comment %}
...
{% endcomment %}
5.跨站请求伪造 csrf
防止网站受第三方服务器的恶意攻击(确定表单到底是不是本网站的表单传递过来的)。csrf相当于在表达中增加了⼀个隐藏的input框,用于向服务器提交⼀个唯⼀的随机字符串用于服务器验证表单是否是本服务器的表单。
使用:
settings.py
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
]
表单里
<form action="" method="post">
{% csrf_token %}
<input type="text" name="username">
<p><input type="submit"></p>
</form>
- 全站禁用csrf
#在settings中设置
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
#'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
- 局部禁用csrf
#在不想检验csrf的视图函数前添加装饰器@csrf_exempt。
from django.views.decorators.csrf import csrf_exempt,csrf_protect
@csrf_exempt
def csrf1(request):
pass
- ajax验证csrf
Ajax提交数据时候,携带CSRF:
a. 放置在data中携带
<form method="POST" action="/csrf1.html">
{% csrf_token %}
<input id="username" type="text" name="username" />
<input type="submit" value="提交"/>
<a onclick="submitForm();">Ajax提交</a>
</form>
<script src="/static/jquery-1.12.4.js"></script>
<script>
function submitForm(){
var csrf = $('input[name="csrfmiddlewaretoken"]').val();
var user = $('#user').val();
$.ajax({
url: '/csrf1.html',
type: 'POST',
data: { "user":user,'csrfmiddlewaretoken': csrf},
success:function(arg){
console.log(arg);
}
})
}
</script>
注意:
csrf的意义在于给每⼀个表单都设置⼀个唯⼀的csrf的值,并且cookie也存储⼀份当提交表单过来的时候判断cookie中的值 和csrf_token中的值是否都是本网站生成的,如果验证通过则提交,否则 403
6.模板导入标签( include)
可以把指定html文件代码导入到当前文件,实现模板代码的复用/重用。语法格式:
{% include '路径/xxx.html' %}
7.url标签
在模板中url标签可用于反向解析
<h2><a href="{% url 'App:index' %}">动态生成路由地址不带参的跳转</a></h2>
<h2><a href="{% url 'App:args1' 1 2 %}">动态生成路由地址带参的跳转</a></h2>
<h2><a href="{% url 'App:args1' num1=1 num2=2 %}">动态生成路由地址带关键字参数的跳转</a></h2>
8.模板中获取当前网址,当前用户等:
建议在 views.py 中用的 render 函数,而不是 render_to_response,这样在模板中我们就可以获取到 request 变量。
8.1 获取当前用户:
{{ request.user }}
如果登陆就显示内容,不登陆就不显示内容:
{% if request.user.is_authenticated %}
{{ request.user.username }},您好!
{% else %}
请登陆,这里放登陆链接
{% endif %}
8.2 获取当前网址:
{{ request.path }}
8.3 获取当前 GET 参数:
{{ request.GET.urlencode }}
8.4 合并到一起用的一个例子:
<a href="{{ request.path }}?{{ request.GET.urlencode }}&delete=1">当前网址加参数 delete</a>
比如我们可以判断 delete 参数是不是 1 来删除当前的页面内容。
四、模板继承
在整个网站中,如何减少共用页面区域(比如站点导航)所引起的重复和冗余代码?Django 解决此类问题的首方法是使用⼀种优雅的策略—— 模板继承 。
本质上来说,模板继承就是先构造⼀个基础框架模板,而后在其子模板中对它所包含站点公用部分和定义块进行重载。
- {% extends %} 继承父模板
- {% block %} 子模板可以重载这部分内容。
- {{ block.super }}调用父模板的代码
使用继承的⼀种常见方式是下面的三层法:
- 创建base.html模板,在其中定义站点的主要外观感受。这些都是不常修改甚⾄从不修改的部分。
- 为每种类型的页面创建独立的模板,例如论坛页面或者图片库。这些模板拓展相应的区域模板。
- 自己的页面继承自模板,覆盖父模板中指定block
注意事项:
- 如果在模板中使用 {% extends %} ,必须保证其为模板中的第⼀个模板标记。否则,模板继承将不起作用。
- ⼀般来说,基础模板中的 {% block %} 标签越多越好。
- 如果发觉自己在多个模板之间有重复代码,你应该考虑将该代码放置到父模板的某个 {% block %} 中。
- 不在同⼀个模板中定义多个同名的 {% block %} 。
- 多数情况下, {% extends %} 的参数应该是字符,但是如果直到运行时方能确定父模板名称,这个参数也可以是个变量。
五、静态资源配置
什么是静态资源:css、js、images 需要从外部导入的资源
5.1创建static文件夹(通常放在根目录下)
5.2需要在settings注册
STATIC_URL='/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static')
]
5.3在模板中使用静态资源
{% load static %} #放置到模板开头
#因上传该语句时触发模板错误,故被删除掉
#硬编码
#动态写法,建议用这种
六、jinja2模板引擎配置
- 安装jinja2模板引擎
pip install jinja2
- 设置jinja2环境变量
# -*- coding: utf-8 -*-
from jinja2 import Environment
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
def jinja2_environment(**options):
env = Environment(**options)
env.globals.update({
'static': staticfiles_storage.url,
'url': reverse,
})
return env
-
配置(setting.py)
- 独立使用jinja2,不使用Django模板引擎
#独立使用jinja2 INSTALLED_APPS = [ #'django.contrib.admin', # 注释了admin ..... ] #模板配置 TEMPLATES = [{ 'BACKEND': 'django.template.backends.jinja2.Jinja2',#jinja2模版 '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', ], 'environment': 'App.jinja2_env.environment', # 配置环境,jinja的配置文件位置 }, }, ]
-
两个同时使用
#模板配置
TEMPLATES = [{
'BACKEND': 'django.template.backends.jinja2.Jinja2',#jinja2模板
'DIRS': [
os.path.join(BASE_DIR, 'templates2'),#修改模板文件位置
],
'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',
],
'environment': 'App.jinja2_env.environment', # 配置环境,jinja的配置文件位置
},
},
{
'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',
],
},
},
]
- 静态资源和url
<p>
<a href="{{ url('app:index') }}">dddd</a>
</p>