Django基础之:模版系统
一 引子
什么是模版系统?这里做一个简单解释。要想明白什么是模版系统,那么我们得先分清楚静态页面和动态页面。我们之前学过的都是静态页面,所谓的静态页面就是浏览器向后端发送一个请求,后端接收到这个请求,然后返回给浏览器一个html页面,这个过程不涉及从数据库取出数据渲染到html页面上,只是单纯的返回一个页面(数据全部在html页面上)。而动态页面就是在给浏览器返回html页面之前,需要后端与数据库之间进行数据交互,然后将数据渲染到html页面上在返回给浏览器。言外之意静态页面不涉及数据库,动态页面需要涉及从数据库取出数据。那么模版系统是什么呢?如果你只是单纯的写静态页面,也就没必有必要用模版系统了,只用动态页面才需要模版系统。
简单来说,模版系统就是在html页面想要展示的数据库或者后端的数据的标签上做上特殊的占位(类似于格式化输出),通过render方法将这些占位的标签里面的数据替换成你想替换的数据,然后再将替换数据之后的html页面返回给浏览器,这个就是模版系统。
模板渲染的官方文档
关于模板渲染你只需要记两种特殊符号(语法):
{{ }}和 {% %}
变量相关的用{{}},逻辑相关的用{%%}。
二、变量
1. 简单示例
接下来,我们先搭一个简单流程:浏览器访问https://127.0.0.1:8000:/index,返回一个index.html页面,我们在views函数中设置一些变量,然后通过模版系统渲染,最终返回给浏览器。
url:
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^index/',views.index), ]
views:
render第三个参数接受一个字典的形式,通过字典的键值对index.html页面进行渲染,这也就是模块渲染。
def index(request): name = '太白金星' age = 18 name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] dic = {'classname': '人工智能', 'since': 2019} return render(request, 'index.html',{'name': name, 'age': age,'name_list':name_list, 'dic_class':dic})
html:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> <ul> {# 通过render进行模版语言渲染然后替换成后端的数据,最终发给浏览器 #} <li>{{ name }}</li> <li>{{ age }}</li> <li>{{ name_list }}</li> <li>{{ dic_class }}</li> </ul> </body> </html>
最终浏览器显示的结果为:
这样,你后端这些变量全部都渲染到前端了。
2. 语法
在Django的模板语言中按此语法使用:{{ 变量名 }}。
当模版引擎遇到一个变量,它将计算这个变量,然后用结果替换掉它本身。 变量的命名包括任何字母数字以及下划线 ("_")的组合。 变量名称中不能有空格或标点符号。
深度查询据点符(.)在模板语言中有特殊的含义。当模版系统遇到点("."),它将以这样的顺序查询:
字典查询(Dictionary lookup)
属性或方法查询(Attribute or method lookup)
数字索引查询(Numeric index lookup)
3. 万能的点 .
通过简单示例我们已经知道模版系统对于变量的渲染是如何做到的,非常简单,接下来我们研究一些深入的渲染,我们不想将整个列表或者字典渲染到html,而是将列表里面的元素、或者字典的某个值渲染到html页面中,那怎么做呢?就是通过万能的点。
views:
def index(request): name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] dic = {'classname': '人工智能', 'since': 2019} lis = [1, ['冠状病毒', '武汉加油'],3] # 使用locals是用于测试,实际生产环境中是不可以的。 return render(request, 'index.html', locals())
html:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> <ul> {# 万能的点 #} <li>{{ name_list.2 }}</li> <li>{{ dic.classname }}</li> <li>{{ lis.1.0 }}</li> </ul> </body> </html>
浏览器的显示结果:
刚才我们尝试的数据类型,那么在一切皆对象的python世界中,我们一个对象是否可以通过模版渲染到html页面中呢?
views:
def index(request): class A: def __init__(self): self.name = 'barry' def func(self): return '太白教你学python' obj = A() return render(request, 'index.html', locals())
html:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> <ul> {# 万能的点 #} <li>{{ obj.name }}</li> <li>{{ obj.func }}</li> </ul> </body> </html>
浏览器显示的结果:
如图所示,对象也是可以通过模版渲染到html页面中的,但是这里要注意一个点:obj.func是不可以加括号的,所以我们后端设置的函数是不可以有参数的(类中的方法self自动传递)。
注意我们直接在js代码中使用模板语法的时候,模板渲染的时候会有个转义的动作,将s = ['哈哈','xx']这种数据中的元素的引号变为一个特殊符号:这个我们后面会讲到
<script> // 不加safe的话,引号会被转义。
// var a = {{ s }} // var a = ['哈哈', 'xx']; // console.log(a[0]) // 加上safe就正常了 var a = {{ s|safe }}; console.log(a[0])
// 还要注意,当我们模板渲染的时候,后端返回的数据是字符串的话,我们需要将{{ s }}外面加上引号
比如s = '哈哈'
js中的写法
var a = '{{ s }}' </script>
三、过滤器
1.什么是过滤器
有的时候我们通过render渲染到html的数据并不是我们最终想要的数据,比如后端向前端传递的数据为hello,但是我们html想要显示为HELLO,当然这个在后端是可以提前处理的,但是诸如此类的需求我们可以通过过滤器解决,过滤器给我们提过了很多便捷的方法,加工你传递到html的数据,便于灵活开发。
2.语法
过滤器的语法: {{ value| filter_name:参数 }}
使用管道符"|"来应用过滤器。
例如:{{ name| lower }}会将name变量应用lower过滤器之后再显示它的值。lower在这里的作用是将文本全都变成小写。
注意事项:
- 过滤器支持“链式”操作。即一个过滤器的输出作为另一个过滤器的输入。
- 过滤器可以接受参数,例如:{{ sss|truncatewords:30 }},这将显示sss的前30个词。
- 过滤器参数包含空格的话,必须用引号包裹起来。比如使用逗号和空格去连接一个列表中的元素,如:{{ list|join:', ' }}
- '|'左右没有空格!没有空格!没有空格!
Django的模板语言中提供了大约六十个内置过滤器。
3.常用过滤器
default: 如果一个变量是false或者为空,使用给定的默认值。 否则,使用变量的值。
views: a = '' # 没有变量a或者变量a为空
html: # 显示啥也没有 {{ value|default:"啥也没有"}}
length:返回值的长度,作用于字符串和列表。
views: name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] s = '太白金星讲师' html: {{ name_list|length }} # 显示为5 {{s|length }} # 显示为6
filesizeformat: 将值格式化为一个 “人类可读的” 文件尺寸 (例如 '13 KB'
, '4.1 MB'
, '102 bytes'
, 等等)。
views: value = 1048576 html: {{ value|filesizeformat }} # 显示为1.0MB
slice:切片,支持pyhton中可以用切片的所有数据类型
views: name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] s = '太白金星讲师' html: {{ name_list|slice:'1:3' }} # ['天琪', '傻强'] {{ s|slice:'1::2' }} # '白星师'
date:时间格式化
views: time = datetime.datetime.now() html: {{ t|date:"Y-m-d H:i:s" }} # 2020-02-11 07:31:29
关于时间日期的可用的参数(除了Y,m,d等等)还有很多,有兴趣的可以去查查看看。
truncatechars:如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾。
参数:截断的字符数
views: describe = '1999年3月,马云正式辞去公职,后来被称为18罗汉的马云团队回到杭州,凑够50万元人民币' html: {{ describe|truncatechars:9 }} # 1999年3... # 截断9个字符,三个点也算三个字符
这个我们在浏览网页时经常见到,描述的内容很多,只能用...显示,比如:
truncatewords:在一定数量的字后截断字符串,是截多少个单词。
views: words = 'i love you my country china' html: {{ words|truncatewords:3 }} # i love you...
cut: 移除value中所有的与给出的变量相同的字符串
views:
words = 'i love you my country china'
html:
{{ words|cut:3 }} # iloveyou
join: 设定连接符将可迭代对象的元素连接在一起与字符串的join方法相同。
views: name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] dic = {'name':'太白','age': 18} tu = (1, 2, 3) html: <li>{{ name_list|join:'_'}}</li> <li>{{ dic|join:','}}</li> <li>{{ tu|join:'+'}}</li> ''' 王阔_天琪_傻强_志晨_健身哥 name,age 1+2+3 '''
safe
Django的模板中在进行模板渲染的时候会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全,django担心这是用户添加的数据,比如如果有人给你评论的时候写了一段js代码,这个评论一提交,js代码就执行啦,这样你是不是可以搞一些坏事儿了,写个弹窗的死循环,那浏览器还能用吗,是不是会一直弹窗啊,这叫做xss攻击,所以浏览器不让你这么搞,给你转义了。但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。
我们去network那个地方看看,浏览器看到的都是渲染之后的结果,通过network的response的那个部分可以看到,这个a标签全部是特殊符号包裹起来的,并不是一个标签,这都是django搞得事情。
比如:value = "<a href='#'>点我</a>" 和 value="<script>alert('123')</script>"
{{ value|safe}}
很多网站,都会对你提交的内容进行过滤,一些敏感词汇、特殊字符、标签、黄赌毒词汇等等,你一提交内容,人家就会检测你提交的内容,如果包含这些词汇,就不让你提交,其实这也是解决xss攻击的根本途径,例如博客园:
timesince(了解)
将日期格式设为自该日期起的时间(例如,“4天,6小时”)。
采用一个可选参数,它是一个包含用作比较点的日期的变量(不带参数,比较点为现在)。 例如,如果since_12是表示2012年6月28日日期实例,并且comment_date是2018年3月1日日期实例:
views: year_12 = datetime.datetime.now().replace(year=2012, month=6, day=28) year_18 = datetime.datetime.now().replace(year=2018, month=3, day=1) html: <li>{{ year_12|timesince:year_18}}</li>
'''
5 years, 8 months
'''
如果year_18不写,默认就是距现在的时间段。
timeuntil(了解)
似于timesince,除了它测量从现在开始直到给定日期或日期时间的时间。 例如,如果今天是2006年6月1日,而conference_date是保留2006年6月29日的日期实例,则{{ conference_date | timeuntil }}将返回“4周”。
使用可选参数,它是一个包含用作比较点的日期(而不是现在)的变量。 如果from_date包含2006年6月22日,则以下内容将返回“1周”:
{{ conference_date|timeuntil:from_date }}
这里简单介绍一些常用的模板的过滤器,更多详见
更多内置过滤器(此链接页面最下面的内置过滤器):https://docs.djangoproject.com/en/1.11/ref/templates/builtins/#filters
四、标签Tags
现在我们已经可以从后端通过模版系统替换掉前端的数据了,但是如果只是替换掉数据,确实不够灵活,比如,我们要想在前端页面展示可迭代对象name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥']每个元素,你如和展示呢?
# 前端页面 <ul> <li>{{ name_list.0 }}</li> <li>{{ name_list.1 }}</li> <li>{{ name_list.2 }}</li> <li>{{ name_list.3 }}</li> </ul>
这样写明显很low,我们要是可以用上for循环就好了。Django这么强大的框架,不可能想不到这点的,这里模版系统给我们提供了另一种标签,就是可以在html页面中进行for循环以及if判断等等。
标签看起来像是这样的: {% tag %}
。标签比变量更加复杂:一些在输出中创建文本,一些通过循环或逻辑来控制流程,一些加载其后的变量将使用到的额外信息到模版中。与python语法不同的是:一些标签需要开始和结束标签 (例如{% tag %} ...
标签 内容 ... {% endtag %})。
学习下面几种标签之前,我们要重新写一个url、views以及html,方便分类学习不与上面的变量产生冲突。
urls: url(r'^tags/',views.tags), views: def tags(request): num = 10
value = '' name_list = ['王阔', '天琪', '傻强', '志晨', '健身哥'] dic = {'name': '太白金星', 'age': 18} return render(request, 'tags.html', locals()) 先创建一个简单的tags.html即可。
for标签
基本语法:
{% for 变量 in render的可迭代对象 %}
{{ 变量 }}
{% endfor %}
例如:
{% for foo in name_list %}
{{ foo }}
{% endfor %}
便捷用法:
遍历每一个元素: 写个for,然后 tab键自动生成for循环的结构,循环很基础,就这么简单的用,没有什么break之类的,复杂一些的功能,你要通过js。可以利用{% for obj in list reversed %}反向完成循环。
遍历一个列表
<ul> {% for foo in name_list %} <li>{{ foo }}</li> {% endfor %} </ul>
反向遍历一个列表
{% for foo in name_list reversed %} <li>{{ foo }}</li> {% endfor %}
遍历一个字典:有items、keys、values参数
<ul> {% for key,value in dic.items %} <li>{{ key }}: {{ value }}</li> {% endfor %} {% for key in dic.keys %} <li>{{ key }}</li> {% endfor %} {% for value in dic %} <li>{{ value }}</li> {% endfor %} </ul>
forloop的使用
模版系统给我们的for标签还提供了forloop的功能,这个就是获取循环的次数,有多种用法:
forloop.counter 当前循环的索引值(从1开始),forloop是循环器,通过点来使用功能
forloop.counter0 当前循环的索引值(从0开始)
forloop.revcounter 当前循环的倒序索引值(从1开始)
forloop.revcounter0 当前循环的倒序索引值(从0开始)
forloop.first 当前循环是不是第一次循环(布尔值)
forloop.last 当前循环是不是最后一次循环(布尔值)
forloop.parentloop 本层循环的外层循环的对象,再通过上面的几个属性来显示外层循环的计数等
我们通过name_list示例:
<ul> {% for foo in name_list %} <li>{{ forloop.counter }} {{ foo }}</li> {% endfor %} {% for foo in name_list %} {{ forloop.counter0 }} <li>{{ foo }}</li> {% endfor %} </ul>
forloop.counter0数字与元素不在一行是因为我html标签摆放的问题,与方法无关。
{% for foo in name_list %} <li>{{ forloop.revcounter }} {{ foo }}</li> {% endfor %}
{% for foo in name_list %} <li>{{ forloop.first }} {{ foo }}</li> {% endfor %}
forloop.first、forloop.last多用于下面我们讲到if判断条件。 forloop.parentloop我们讲到if标签在演示。
for...empty...组合
如果遍历的可迭代对象是空的或者就没有这个对象,利用这个组合可以提示用户。
<ul> {% for foo in aaa %} <li>{{ foo }}</li> {% empty %} <li>查询的内容啥也没有</li> {% endfor %} {% for foo in value %} <li>{{ foo }}</li> {% empty %} <li>查询的内容啥也没有</li> {% endfor %} </ul>
if标签
基本语法:
{% if %}会对一个变量求值,如果它的值是“True”(存在、不为空、且不是boolean类型的false值),对应的内容块会输出。
{% if 条件 %} 结果 <!--不满足条件,不会生成这个标签--> {% elif 条件 %} 结果 {% else %} <!--也是在if标签结构里面的--> 结果 {% endif %}
elif和else一定要在if endif标签里面,设置多个elif或者没有elif、有没有else都可以。
示例:
{% if dic.age > 18 %} <p>可以干点儿该做的事儿了~</p> {% elif dic.age < 18 %} <p>小孩子,懂什么</p> {% else %} <p> 风华正茂的年龄~</p> {% endif %}
条件也可以与过滤功能配合
% if name_list|length > 4 %} <p>列表元素超过4个</p> {% else %} <p>列表元素太少!</p> {% endif %}
条件也可以加逻辑运算符
if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断,注意条件两边都有空格。
{% if name_list|length > 4 and '王阔' in name_list %} <p>条件都满足</p> {% endif %}
with
使用一个简单地名字缓存一个复杂的变量,多用于给一个复杂的变量起别名,当你需要使用一个“昂贵的”方法(比如访问数据库)很多次的时候是非常有用的,记住!等号的左右不要加空格!!
{% with total=business.employees.count %} {{ total }} <!--只能在with语句体内用--> {% endwith %}
{% with business.employees.count as total %} {{ total }} {% endwith %}
forloop.first、forloop.last、forloop.parentloop
当时讲for循环时有三个方法,我们没有尝试,因为需要与if条件配合。
forloop.first
{% for foo in name_list %} {% if forloop.first %} {{ foo }} {% else %} <p>只有第一次循环打印</p> {% endif %} {% endfor %}
forloop.parentloop : 一定注意!他是返回本此循环的外层循环对象,这个对象可以调用forloop的各种方法进行获取相应的数据。
测试此方法,我们要在views函数中加一个数据类型:lis = [['A', 'B'], ['C', 'D'], ['E', 'F']]
{% for i in lis %} {% for j in i %} {# <p>{{ forloop.parentloop }}</p>#} <p>{{ forloop.parentloop.counter }} {{ forloop.counter }} {{ j }}</p> {% endfor %} {% endfor %}
注意事项
1. Django的模板语言不支持连续判断,即不支持以下写法:
{% if a > b > c %} ... {% endif %}
2. Django的模板语言中属性的优先级大于方法(了解)
def xx(request): d = {"a": 1, "b": 2, "c": 3, "items": "100"} return render(request, "xx.html", {"data": d})
如上,我们在使用render方法渲染一个页面的时候,传的字典d有一个key是items并且还有默认的 d.items() 方法,此时在模板语言中:
{{ data.items }}
默认会取d的items key的值。
csrf_token标签
这个标签是不是非常熟悉?之前我们以post方式提交表单的时候,会报错,还记得我们在settings里面的中间件配置里面把一个csrf的防御机制给注销了啊,本身不应该注销的,而是应该学会怎么使用它,并且不让自己的操作被forbiden,通过这个标签就能搞定。
接下来我们重写构建一个流程:
urls、views、html:
urlpatterns = [ url(r'^login/',views.login), ] from django.shortcuts import render, HttpResponse, redirect def login(request): if request.method == 'GET': return render(request, 'login.html') else: print(request.POST) # 这里我们就不进行验证了,只是研究csrf_token return HttpResponse('登录成功!!!') <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <h3>登录页面</h3> <form action="" method="post"> 用户名:<input type="text" name="username"> 密码:<input type="text" name="password"> <button>提交</button> </form> </body> </html>
注意:post、get请求不一定非要写在一个视图函数中,那么我们之前为什么写在一个视图函数中,就是因为post、get都是请求相关的消息,所以我们都用一个视图函数去处理,你也可以写在两个视图函数中,自己可以尝试一下。
此时我们开启项目,通过浏览器进行访问,当你点击提交时,浏览器返回这样一个画面:
这就是csrf安全机制给你做出的响应。我们之前都是找到settings给对应的中间件注释掉,但是现在我们要研究一下如何遵循这个安全机制,只要我们在你的form表单中加上一个csrf_token标签就行了。
那么这个标签起到了什么作用呢?他的原理是什么?我们再次刷新这个页面,看一下源码:
当我们加上此标签之后,再次发出get请求,render返回给我们的页面中多了一个隐藏的input标签,并且这个标签里面有个一键值对:
键:name,值:随机的一堆密文。 那么这个是干什么用的呢?其实他的流程是这样的:
说了这么多,目的就是一个:验证当你post提交请求时,是不是从我给你(你通过get请求的)页面上提交的数据。
那么接下来,我们写一个简单的爬虫验证一下:
import requests ret = requests.post('http://127.0.0.1:8000/login/', data={'username': 'taibai', 'password': '123'} ) print(ret.content)
如果你保留这这个验证,则通过爬虫是登录不成功的,只能返回你一个forbidden的html页面。
如果你将settings那个中间件注释掉,那么就可以成功访问了:
五、模版继承
1. 引子
什么是模版继承?将这两个字拆开我们都清楚,模版就是django提供的用于html发送浏览器之前,需要在某些标签进行替换的系统,而继承我们立马就会想到这是面向对象的三大特性之一。其实模版继承就是拥有这两个特性的模版的高级用法。先不着急直接上知识点,我们用一个例子引出模版继承。
我们经常访问一些网站,你会发现只要是一个网站的多个html页面,他们有一部分是一模一样的,剩下的部分是本页面独有的。比如我们经常使用的博客园:
这个是我博客园的首页:
然后我们随机点一篇博客进去:
个人博客园设定了博客园的样式之后,你所有的博客都是按照这个样式创建的,在我选择的主题这里:导航条和侧边栏布局是一样的。再比如如果是公司内部的xx管理系统,更是如此。我们打开bootstraps网站(起步):
一般管理系统都是这样的布局,这个就是固定的导航条和侧边栏。无论我点击侧边栏里的那个按钮,这两部分不会更换只会改变中间的内容。
那么接下来我们实现一个这样的布局。
我们要准备4个html页面:base.html、menu1.html、menu2.html、menu3.html,这四个页面的导航条与左侧侧边栏一样,每个页面对应一个url。并且每个页面的左侧侧边栏菜单一、菜单二、菜单三可以实现跳转:跳转到menu1.html、menu2.html、menu3.html三个页面。而顶端导航条只是样式即可。接下来借助于Django,我们实现这四个页面并对应urls可以跑通流程。
urls:
urlpatterns = [ url(r'^base/', views.base), url(r'^menu1/', views.menu1), url(r'^menu2/', views.menu2), url(r'^menu3/', views.menu3), ]
views:
def base(request): return render(request, 'base.html') def menu1(request): return render(request, 'menu1.html') def menu2(request): return render(request, 'menu2.html') def menu3(request): return render(request, 'menu3.html')
html:
base.html: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <style> body{ margin: 0; padding: 0; } .nav{ background-color: black; color: #eaeaea; height: 30px; width: 100%; } .clearfix{ content: ''; display: block; clear: both; } .sidebar{ background-color: #b2b2b2; color: #435dff; width: 20%; height: 1000px; float: left; } .sidebar ul{ margin: 0; } .menu{ width: 80%; float: right; } </style> </head> <body> <div class="nav clearfix"> <a href="">普通洗浴</a> <a href="">盆儿堂</a> <a href="">局部护理</a> <a href="">关于我们</a> <a href="">预约电话</a> <input type="text">搜索 </div> <div class='sidebar'> <ul> <li><a href="/menu1/">菜单一</a></li> <li><a href="/menu2/">菜单二</a></li> <li><a href="/menu3/">菜单三</a></li> </ul> </div> <div class="menu"> 首页 </div> </body> </html> menu1.html: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <style> body{ margin: 0; padding: 0; } .nav{ background-color: black; color: #eaeaea; height: 30px; width: 100%; } .clearfix{ content: ''; display: block; clear: both; } .sidebar{ background-color: #b2b2b2; color: #435dff; width: 20%; height: 1000px; float: left; } .sidebar ul{ margin: 0; } .menu{ width: 80%; float: right; } </style> </head> <body> <div class="nav clearfix"> <a href="">普通洗浴</a> <a href="">盆儿堂</a> <a href="">局部护理</a> <a href="">关于我们</a> <a href="">预约电话</a> <input type="text">搜索 </div> <div class='sidebar'> <ul> <li><a href="/menu1/">菜单一</a></li> <li><a href="/menu2/">菜单二</a></li> <li><a href="/menu3/">菜单三</a></li> </ul> </div> <div class="menu"> 菜单一首页 </div> </body> </html> menu2.html: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <style> body{ margin: 0; padding: 0; } .nav{ background-color: black; color: #eaeaea; height: 30px; width: 100%; } .clearfix{ content: ''; display: block; clear: both; } .sidebar{ background-color: #b2b2b2; color: #435dff; width: 20%; height: 1000px; float: left; } .sidebar ul{ margin: 0; } .menu{ width: 80%; float: right; } </style> </head> <body> <div class="nav clearfix"> <a href="">普通洗浴</a> <a href="">盆儿堂</a> <a href="">局部护理</a> <a href="">关于我们</a> <a href="">预约电话</a> <input type="text">搜索 </div> <div class='sidebar'> <ul> <li><a href="/menu1/">菜单一</a></li> <li><a href="/menu2/">菜单二</a></li> <li><a href="/menu3/">菜单三</a></li> </ul> </div> <div class="menu"> 菜单2首页 </div> </body> </html> menu3.html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <style> body{ margin: 0; padding: 0; } .nav{ background-color: black; color: #eaeaea; height: 30px; width: 100%; } .clearfix{ content: ''; display: block; clear: both; } .sidebar{ background-color: #b2b2b2; color: #435dff; width: 20%; height: 1000px; float: left; } .sidebar ul{ margin: 0; } .menu{ width: 80%; float: right; } </style> </head> <body> <div class="nav clearfix"> <a href="">普通洗浴</a> <a href="">盆儿堂</a> <a href="">局部护理</a> <a href="">关于我们</a> <a href="">预约电话</a> <input type="text">搜索 </div> <div class='sidebar'> <ul> <li><a href="/menu1/">菜单一</a></li> <li><a href="/menu2/">菜单二</a></li> <li><a href="/menu3/">菜单三</a></li> </ul> </div> <div class="menu"> 菜单三首页 </div> </body> </html>
上面我的的需求虽然完成了但是有没有什么没问题?你会发现html重复代码太多了,如果领导不瞎,最晚后天你就可以领盒饭了。
2. 母版继承示例
所以针对与我上面的需求,很显然你现在所拥有的知识点已经解决不了了。那么接下来就是本节的重点:模版继承。根据这个知识点名字的特点,我们应该想到,我们可不可以建立一个父类,然后让所有的子孙类都继承我的父类,这样我就可以节省很多代码了,让我的代码非常的清新、简单。这里的父类就不叫父类了他有一个专有名词:母版。
接下来我们先创建一个母版。(最好不要将base页面直接作为母版,母版就是只设置公用的部分,其他一概不要。)
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <style> body{ margin: 0; padding: 0; } .nav{ background-color: black; color: #eaeaea; height: 30px; width: 100%; } .clearfix{ content: ''; display: block; clear: both; } .sidebar{ background-color: #b2b2b2; color: #435dff; width: 20%; height: 1000px; float: left; } .sidebar ul{ margin: 0; } .menu{ width: 80%; float: right; } </style> </head> <body> <div class="nav clearfix"> <a href="">普通洗浴</a> <a href="">盆儿堂</a> <a href="">局部护理</a> <a href="">关于我们</a> <a href="">预约电话</a> <input type="text">搜索 </div> <div class='sidebar'> <ul> <li><a href="/menu1/">菜单一</a></li> <li><a href="/menu2/">菜单二</a></li> <li><a href="/menu3/">菜单三</a></li> </ul> </div> <div class="menu"> </div> </body> </html>
接下来,我们将base menu1 menu2 menu3这四个页面全部清空,然后在每个页面的最上面加上这么一行代码:
{% extends 'master_edition.html' %}
这个就实现了我要继承母版master_edition.html。
3. 自定制效果
现在已经完成了继承母版,这个只是减少了重复代码,还没有实现每个页面自定制的一些内容,如果你想要实现自定制的内容怎么做?类似于模版系统你是不是应该在母版的具体位置做一个标识,然后在自己的页面对应的地方进行自定制?那么这个类似于%占位符的特定的标识叫做钩子。这几个页面只是在menu div不同,所以我们就在这里做一个钩子就行了。
在母版的html对应的位置:
<div class="menu"> {% block content %} {% endblock %} </div>
block endblock就是对应的钩子,content是此钩子的名字。
然后在base menu1 menu2 menu3的页面上(此时就以base页面举例):
{% block content %}
base页面首页
{% endblock %}
这样你的代码是不是非常的简单了?
那么我们不仅可以在对应的html标签设置钩子,还可以在css、js设定对应的钩子。所以母版继承中一般设定钩子的地方就是三部分: html、css、js。
以css举例:
我们将base页面的顶端导航条的背景颜色设置成红色:
首先现在母版页面对应的位置设置钩子:
<style> body{ margin: 0; padding: 0; } .nav{ background-color: black; color: #eaeaea; height: 30px; width: 100%; } .clearfix{ content: ''; display: block; clear: both; } .sidebar{ background-color: #b2b2b2; color: #435dff; width: 20%; height: 1000px; float: left; } .sidebar ul{ margin: 0; } .menu{ width: 80%; float: right; } {% block nav %} {% endblock %} </style>
然后找到base页面:
{% block nav %}
.nav{
background-color: red;
}
{% endblock %}
这样你的base页面的导航条就变成红色啦!
js的母版继承我们就不再这里尝试了,自己私下可以尝试一下。
4. 保留母版内容并添加新特性
还有一个情况我们也会遇到,就是我既要留住母版的内容,又要在自己的html添加一些新的标签。这个我们在面向对象时是不是也遇到过?当时用什么方法既执行父类方法又可以执行子类方法?super!在这里我们也用super!
母版html:
<div class="menu"> {% block content %} <div>这是母版测试页面</div> {% endblock %} </div>
base.html:
{% block content %}
{{ block.super }}
base页面首页
{% endblock %}
在钩子里面加上{{ block.super }}即可。
5. 注意
-
如果你在模版中使用
{% extends %}
标签,它必须是模版中的第一个标签。其他的任何情况下,模版继承都将无法工作,模板渲染的时候django都不知道你在干啥。 -
在base模版中设置越多的
{% block %}
标签越好。请记住,子模版不必定义全部父模版中的blocks,所以,你可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。多一点钩子总比少一点好。 -
如果你发现你自己在大量的模版中复制内容,那可能意味着你应该把内容移动到父模版中的一个
{% block %}
中。 -
If you need to get the content of the block from the parent template, the
{{ block.super }}
variable will do the trick. This is useful if you want to add to the contents of a parent block instead of completely overriding it. Data inserted using{{ block.super }}
will not be automatically escaped (see the next section), since it was already escaped, if necessary, in the parent template. 将子页面的内容和继承的母版中block里面的内容同时保留。 - 不能在一个模版中定义多个相同名字的
block
标签。 - 结束一个钩子时可以标注此钩子的名字,比如 {% endblock content%}这样就可以结束此钩子不至于将其他的block钩子一并结束。
六、组件
组件就是将一组常用的功能封装起来,保存在单独的html文件中,(如导航条,页尾信息等)其他页面需要此组功能时,按如下语法导入即可。 这个与继承比较类似,但是‘格局’不同,继承是需要写一个大的母版,凡是继承母版的一些html基本上用的都是母版页面的布局,只有一部分是自己页面单独展现的,这好比多以及排布好的多个组件。
{% include 'xx.html' %}
比如我们写一个nav导航条组件:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <style> body{ margin: 0; padding: 0; } .nav{ background-color: black; color: #eaeaea; height: 30px; width: 100%; } .clearfix{ content: ''; display: block; clear: both; } </style> </head> <body> <div class="nav clearfix"> <a href="">普通洗浴</a> <a href="">盆儿堂</a> <a href="">局部护理</a> <a href="">关于我们</a> <a href="">预约电话</a> <input type="text">搜索 </div> </body> </html>
然后我们在创建流程去使用我们的组件:
urls: url(r'^component/', views.component), views: def component(request): return render(request,'component.html') component页面: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> {% include 'nav.html' %} <h1>你好,世界!</h1> <div>我是componet页面</div> </body> </html>
组件和插件的区别:
组件是提供某一完整功能的模块,如:编辑器组件,QQ空间提供的关注组件 等。
而插件更倾向封闭某一功能方法的函数。
这两者的区别在 Javascript 里区别很小,组件这个名词用得不多,一般统称插件。
七 自定义标签和过滤器
我们都学过了Django给我们提供的标签和过滤器,但是这些不能满足我们日常开发的所有需求,所以DJango为我们提供了一个很牛逼的功能就是我们可以自定义标签和过滤器。接下来我们看看这个怎么玩。
1. 自定义过滤器
Django的模版系统给我们提供了很多过滤器,比如切割、全大写、获取元素个数,联合join,安全性质的safe等等,这些都是对数据(尤其是字符串类型的数据)进行加工,但是这些都是模版系统提供给我们的,不能满足应对工作中的所有需求,所以Django给我们提供了口子:让我们可以自己定义过滤器,这样我们开发起来更加的灵活。接下来我们讲解如何自己定义过滤器。
1. 在应用的目录下创建templatetags文件夹(文件夹必须这样命名)。
2. 在templatetags文件下创建py文件(任意命名即可)。
我这里命名为network_language.py。
3. 在py文件中引入template模块并创建注册器。
from django import template register = template.Library()
4. 自定义过滤器函数。
下面讲到的自定义标签以及simple_tags,与前三步一模一样,并且都是在本文件中操作。
@register.filter def adjoin(v1): pass # adjoin就是我们自定义的过滤器函数。 # 此函数必须被register.filter装饰,这样才能生效。 # 我们现在先不做任何功能,一会在定义功能。
完成上面这个四步,我们就算是自定义了一个过滤器函数。那么如何使用呢?模版系统自带的过滤器如何使用?他是通过对render要渲染的变量加上管道符,然后在管道符后面加上具体的过滤器的名字,这样就可以对变量进行简单加工了。我们自定义的过滤器也是这样用法。接下来我们重新设计一个url流程:
urls: url(r'^templatetag/', views.template_tag), views: def template_tag(request): name = '天琪' return render(request,'templatetag.html', {'name': name}) html: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> {% load network_language %} # 引入templatetags文件夹下面的network_language.py文件 {{ name|adjoin }} # name就是render渲染的变量,adjoin就是自定义的过滤器的函数名 </body> </html>
此时你自定义的过滤器里面的adjoin代码:
from django import template register = template.Library() @register.filter def adjoin(v1): return v1 + '噢里给'
我们自定义的过滤器就是完成了简单的功能:给name变量拼接了一个网络用语:奥利给。那么整个他的执行流程是什么呢?
首先浏览器发送请求至url,url通过请求路径找到对应的views视图函数template_tag,然后执行到return时先不会给浏览器马上返回html页面,而是通过render进行模版渲染,当渲染到 {% load network_language %}这一行,render会自动找到templatetags文件夹下面的network_language文件,然后在向下读取,执行自定义过滤器adjoin函数,并且将变量name对应的'天琪'传递给adjoin函数的形参v1,得到返回值'天琪噢里给'之后,替换{{ name|adjoin }},渲染完毕之后,将此html页面返回给浏览器。
这个就是整体的过程。那么有人可能会问了,我自定义的过滤器函数只能设定一个形参么?不是的,自定的过滤器函数设定形式参数至多两个。接下来我们演示一个两个参数的。
# html: {% load network_language %} {{ name|_adjoin:'微笑的面对它' }} # templatetags/network_language.py: @register.filter def _adjoin(v1, v2): return v1 + v2 + '噢里给!'
第二个参数是通过过滤器的冒号后面传递的。
至此,我们自定义过滤器这部分就已经给大家讲完了,可以练习一下~
2. 自定义标签
我们学习完自定义过滤器之后,再来看看自定义标签,自定义标签与自定义过滤器差不多,前三个步骤是一样,接下来就是定义自定义标签函数不同:
views:我们还是使用template_tag函数,只是增加了一些变量。
def template_tag(request): name = '天琪' content = '我太难了' status = '在线学习' return render(request,'templatetag.html', locals())
html:
{% load network_language %} {% all_join name status content '不要怕'%}
network_language.py:
@register.simple_tag def all_join(v1, v2, v3, v4): return v1 + v2 + v3 + v4 + '噢里给!'
其实自定义标签和自定义过滤器差不多,都是我们设计一些函数,增加一些自定制的功能,也有一些细微的区别,我们总结一下:
- 自定制过滤器至多只能接受两个参数,而自定制标签可以接受多个参数,这样看来自定制标签更加灵活。
- 自定制过滤器必须依赖于 变量,他的目的就是对变量进行加工{{ name|adjoin }} ;而自定制标签可以不依赖于变量直接使用{% all_join %}.
- 自定制过滤器可以用在if、for等语句后,自定制标签不可以。
{% if num|filter_multi:30 > 100 %} {{ num|filter_multi:30 }} {% endif %}
3.inclusion_tag
这个标签不同于上面的两种,这个标签是将一段html代码插入到我们html页面中,有点儿类似于组件,但是比组件还灵活。上一节我们讲组件时,我们将自己设计的一个顶端的导航条nav.html作为一个组件,然后让另一个页面去使用,还记得这个例子吧?这次我们用inclusion_tag去展示一下,对比分析一下这两个有什么不同。
views:我们加了一个show_list变量
def template_tag(request): name = '天琪' content = '我太难了' status = '在线学习' show_list = ['课程分类', '在线学习', '解答疑问', '讲师介绍', '网站介绍'] return render(request,'templatetag.html', locals())
teamplatetag.html: 我设计在network_language中自定制inclusion标签函数,函数名为nav_data,然后将show_list传递进去。
{% load network_language %}
{% nav_data show_list %}
network_language.py:
@register.inclusion_tag('nav.html') def nav_data(argv): argv += [datetime.datetime.now()]return {'data': argv}
此时我在这个自定制函数中对show_list进行了加工,并且通过return(这里的return有点儿类似于render)将数据传递到nav.html页面对应的位置。
nav.html:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <style> body{ margin: 0; padding: 0; } .nav{ background-color: black; color: #eaeaea; height: 30px; width: 100%; } .clearfix{ content: ''; display: block; clear: both; } </style> </head> <body> <div class="nav clearfix"> {% for foo in data %} <a href="">{{ foo }}</a> {% endfor %} <input type="text">搜索 </div> </body> </html>
然后将数据展示到导航条上面相应的位置上,最终的效果如下:
然后我们对比分析组件,组件是只能将html里面的一部分(导航条、左右侧栏等)原封不动被引入到相应的其他html页面中,而
inclusion_tag这个自定制标签,不仅可以完成组件的功能,而且还可以操作被导入的html组件,使其更加灵活。
八、静态文件
截止到目前为止,我们还没有应用到css、js等文件,只是使用了html页面,现在我们的页面可算是不要太丑了。所以项目中我们肯定是要引入静态文件的,什么是静态文件?静态文件就是我们之前学过的css、js、图片文件、视频文件等等,我们在html想要使用他们就是引用静态文件,之前我们写页面是需要通过标签引入静态文件,并且需要各种路径的配置,现在我们利用Django做一个简单的配置就可以直接引入静态文件了。
我们在重新创建一个项目static_pro:
url: url(r'^index/', views.index), views: def index(request): return render(request, 'index.html') html: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> </body> </html>
我们的静态文件不只是一个文件,这里面包含很多的文件,所以我们应该创建一个文件夹,将所有的静态文件都放置在此目录中,所以,我们在整个项目的目录下创建一个专门放置静态文件的文件夹:我们取名'jingtaiwenjian'(为了教学使用,工作中决不允许出现拼音形式),然后我们在此静态文件里面创建一个css1.css文件,专门放置css代码。
接下来我们如何引用我们的css1静态文件呢?按照我们之前的操作,在html页面的link标签中引入此文件的相对路径,是行不通的。
引入静态文件方式一
我们在Django框架中,就遵循Django规范,那么Django是如何配置css。
1. 在settings配置文件中,配置静态文件路径
STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'jingtaiwenjian'), ]
# STATICFILES_DIRS 此常量名设置可以任意构建,一般约定俗成都是这样命名的。
2. 在index.html文件中,引入css文件
<link href="/static/css1.css" rel="stylesheet">
注意:这里面的路径如果你写的是/jingtaiwenjian/css1.css,这样css文件是引用不到的,你要像我上面写的一样。那么为什么呢?这个'static'是什么东西?接下来跟你简单聊聊这个static。
别名static
这个static是给静态文件(比我们的项目的静态文件的名字为:jingtaiwenjian)起的别名,他在哪里可以配置呢?在我们settings里面就可以配置:
Django默认给静态文件起了别名,当然你也可以自己设定别名,比如:
STATIC_URL = '/olddriver/'
如果你要是更改这个别名,你在index.html中的引用就需要更改,所以一般我们不去更改别名:
这样也可以成功访问,那么Django为什么要给静态文件起别名呢?这样有什么好处呢?
1. 使用别名代替真实的静态文件名字,防止有人对你的静态文件发起恶意攻击,保证静态文件的安全。
因为请求一个web网站时,都会对css、js发起请求,并且这些请求都是可以访问到的url:
我们访问一下这个URL:
再比如我们打开京东,看一下京东的网络请求:
通过这个网址我们可以拿到他的css代码:
这种css代码是压缩混淆处理的,有些内容你是看不懂的,就相当于加密了,但是我们可以获取到。虽然可以让你获取到,但是由于你的static是别名,他是不能对你的静态文件发起攻击的。
2. 使用别名代替真实的静态文件名字,保持了代码的拓展性。
如果以后遇到了需求,必须将静态文件的名字改掉(比如我们本次取的名字jingtaiwenjian就不规范),如果没有别名的话,你必须将所有的引用静态文件的相关link路径都必须改掉,这样太麻烦了,但是我们有了别名的概念,无论你以后如何更改静态文件的名字,都不会造成影响的。
我将静态文件名字改成了staticfile,当我再次访问index页面时,依然还是可以访问的。
请求路径还是别名作为路径。
所以,针对于上述情况,起别名还是又好处的。
引入静态文件方式二
我们还可以通过第二种方式引入静态文件,第一种方式是有漏洞的,假如我们已经完成了一期项目,你的项目中已经创建了多个html文件,并且都一直通过第一种方式引入静态文件,此时你跳槽了,其他的人接收你的项目,他没有注意到你的别名为static,而当它二期项目已经进展了很长时间发现你的static别名与二期项目中的某些url冲突了,这时必须要更改你的别名才可以解决问题,那么怎么做?因为你通过第一种方式引入的静态文件,如果你把别名改了,你的很多html页面的link路径都必须要更改,所以这种方式还是有弊端的。那么接下来我介绍第二种引入静态文件的方式,就可以完美避开这个问题。
settings:
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'staticfile'), ]
index.html:
{% load static %} <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="{% static 'css1.css' %}" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> </body> </html>
通过这种方式引入静态文件,无论你更改别名,都没有影响,因为他是根据settings你配置的静态文件的路径找到你的静态文件的,不依赖于别名,所以这种方式也是我比较喜欢的一种方式。
了解:如果某个静态文件路径过长(比如一个图片)并且此图片多次被用到,我们每次引用这个图片写路径很麻烦时,这样可以给这个图片路径起个别名,以后在引用起来就简单了:
{% load static %} {% static 'css1.css' as c %} <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="{% static 'css1.css' %}" rel="stylesheet"> <link href="{{ c }}" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> </body>
引入静态文件方式三
我们也可以通过 {% get_static_prefix %}方式引入静态文件,这样你的文件分隔符可以省略不写了。
{% load static %} <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="{% get_static_prefix %}css1.css" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> </body> </html>
了解:如果某个静态文件路径过长(比如一个图片)并且此图片多次被用到,我们每次引用这个图片写路径很麻烦时,这样可以给这个图片路径起个别名,以后在引用起来就简单了:
{% load static %} {% get_static_prefix as c %} <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> {# <link href="{% static 'css1.css' %}" rel="stylesheet">#} {# <link href="{{ c }}" rel="stylesheet">#} <link href="{{ c }}css1.css" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> </body> </html>