在刚刚介绍完的视图层中我们提到,浏览器发送的请求信息会转发给视图进行处理,而视图在经过一系列处理后必须要有返回信息给浏览器。如果我们要返回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 代码的情况下变更前端界面的设计,那将会方便得多。
我们可以很容易想到的解决方案就是
作为一个成熟的Web框架,上述方案早已被Django实现:
| |
| 对于静态页面来说,直接编写就好 |
| 而针对动态页面,django额外提供了专门的模板语言(Django template language,简称DTL),允许我们在页 |
| 面中嵌入模板变量,这便为后期为页面动态填充内容提供了可能性。 |
| DTL是模板系统的核心,因此django的模板系统也被等同于DTL |
| |
| 详见第三小节 |
| |
| |
| 渲染rendering指的是用上下文数据context data插入/填充模板并返回结果,结果为字符串。 |
| 将要插入/填充入模板的变量组织到一个字典里,该字典称之为上个文context data |
| |
| 详见第二小节->2.1 |
一个简单的示例如下
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), |
| ] |
Django内置了一套标准API(兼容任何模板引擎的BACKEND)用于查找/加载、渲染模板。历史原因,模板的相关实现都存在于django.template名称空间中。
| |
| |
| get_template(template_name, using=None) |
| select_template(template_name_list, using=None) |
| |
| |
| django.template.backends.django.DjangoTemplates |
| django.template.backends.jinja2.Jinja2 |
| |
| |
| from django.template.backends.django import Template |
| from django.template.backends.jinja2 import Template |
| |
| |
| render_to_string(template_name, context=None, request=None, using=None) |
| |
| |
上述API了解即可,因为我们在日常开发过程中,常用的一个render方法(from django.shortcuts import render),其内部已经整合了上述API。
| from django.shortcuts import render |
| |
| |
| 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的模板引擎 |
要解析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 |
而其他模板语言的后端需要从第三方获得。
(2)关键配置项之DIRS与APP_DIRS
由于大多数引擎加载的模板都是文件,这就涉及到文件的路径查找,所以在每个引擎配置的顶级都包含两个相似的配置来专门负责路径查找问题
| 'DIRS': [], |
| 1、列表中包含一系列的目录,引擎会按照列表的顺序依次查找模板文件 |
| 2、列表为空则代表没有对应的查找目录 |
| |
| 'APP_DIRS': True, |
| 1、APP_DIRS 值为True时,会去按照INSTALLED_APPS = []中注册的app顺序,依次去每个app目录下的templates目录中查找模板文件 |
| 2、APP_DIRS 值为False时,不会检索app目录下的templates目录 |
| 查找的优先级为: djanog会先依次检索每个引擎下的DIRS,然后按照APP_DIRS的规定去找模板,直到找到为止。下面我们就依次举例进行验证 |
| 我们先将ARR_DIRS设置为False来讨论加载顺序,如下 |
| TEMPLATES = [ |
| { |
| |
| 'BACKEND': 'django.template.backends.django.DjangoTemplates', |
| 'DIRS': [ |
| '/home/html/example.com', |
| '/home/html/default', |
| ], |
| 'APP_DIRS': False, |
| 'OPTIONS': { |
| |
| }, |
| }, |
| { |
| |
| 'BACKEND': 'django.template.backends.jinja2.Jinja2', |
| 'DIRS': [ |
| '/home/html/jinja2', |
| ], |
| 'APP_DIRS': False, |
| 'OPTIONS': { |
| |
| }, |
| }, |
| ] |
若调用render(request,‘story_detail.html’),查找顺序如下,找到为止
| |
| /home/html/example.com/story_detail.html |
| /home/html/default/story_detail.html |
| |
| |
| /home/html/jinja2/story_detail.html |
若调用render(request,[‘story_1_detail.html’, ‘story_detail.html’]),查找顺序如下,找到为止
| |
| |
| /home/html/example.com/story_1_detail.html |
| /home/html/default/story_1_detail.html |
| |
| |
| /home/html/jinja2/story_1_detail.html |
| |
| |
| |
| /home/html/example.com/story_detail.html |
| /home/html/default/story_detail.html |
| |
| |
| /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 = [ |
| { |
| |
| 'BACKEND': 'django.template.backends.django.DjangoTemplates', |
| 'DIRS': [ |
| '/home/html/aaa', |
| '/home/html/bbb', |
| ], |
| 'APP_DIRS': True, |
| 'OPTIONS': { |
| |
| }, |
| }, |
| { |
| |
| 'BACKEND': 'django.template.backends.jinja2.Jinja2', |
| 'DIRS': [ |
| '/home/html/ccc', |
| ], |
| 'APP_DIRS': True, |
| 'OPTIONS': { |
| |
| }, |
| }, |
| ] |
若调用render(request,‘story_detail.html’),查找顺序如下,找到为止
| |
| |
| /home/html/aaa/story_detail.html |
| /home/html/bbb/story_detail.html |
| |
| |
| /home/html/ccc/story_detail.html |
| |
| |
| INSTALLED_APPS = [ |
| |
| 'app02.apps.App02Config', |
| 'app01.apps.App01Config', |
| ] |
| |
| |
| /Users/linhaifeng/PycharmProjects/EgonPro/app02/templates/story_detail.html |
| |
| /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
| |
| /Users/linhaifeng/PycharmProjects/EgonPro |
| ├── templates |
| │ ├── base |
| │ │ ├── base.html |
| │ ├── app01 |
| │ │ ├── index.html |
| │ ├── 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
| |
| 1、项目根目录/templates |
| 2、项目根目录/app01/templates |
| 3、项目根目录/app02/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': { |
| |
| }, |
| }, |
| ] |
| |
| |
| |
| render(request,'base/base.html') |
| |
| |
| render(request,'app01/index.html') |
| |
| |
| render(request,'app02/index.html') |
(3)关键配置项目之NAME
在调用render()时,除非指定了引擎,否则会按照列表规定的顺序依次使用引擎来查找/加载模板,直到查找/加载成功为止。
如果想选用指定的模板引擎,需要使用参数NAME为每个模板引擎设定唯一的名字,然后在render中使用using参数指定即可
| |
| 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(request, 'index.html',using='b1') |
| render(request, 'index.html',using='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
Django模板指的是用Django模板语言(DTL)标记的文本文档(如HTML、XML、CSV等任何文本文档都可以)或者python字符串(文本文档也是由字符组成)。
Ps: DTL一定要在django的模板引擎下使用,不要使用jinja2引擎或者其他模板引擎
DTL的语法主要由四部分构成:变量、过滤器、标签、注释,如下
| {% extends "base_generic.html" %} |
| |
| {% block title %}{{ section.title }}{% endblock %} |
| |
| {% block content %} |
| { |
| <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 %} |
让我们来分别作详细介绍
模板中的变量格式为:{{ 变量名 }}。变量名由字母数字和下划线组成,但是不能以下划线开头否则会因为引擎无法解析导致服务端错误。
如果模板中的数据不是固定死的,而是动态变化的,则必须在html中嵌入变量,如下
test.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| <p>{{ msg }}</p> |
| <p>{{ dic }}</p> |
| <p>{{ obj }}</p> |
| <p>{{ li }}</p> |
| </body> |
| </html> |
我们需要在视图函数中为模板test.html的变量名msg、li、dic、obj、obj_li赋值,views.py内容如下
| from django.shortcuts import render |
| |
| def test(request): |
| |
| msg='hello world' |
| dic={'k1':1,'k2':2} |
| class Person(object): |
| def __init__(self,name,age): |
| self.name=name |
| self.age=age |
| |
| obj=Person('egon',18) |
| li = [1,'aaa',obj] |
| |
| return render(request,'test.html',{'msg':msg,'dic':dic,'obj':obj,'li':li}) |
| |
| |
| |
| return render(request,'test.html',locals()) |
变量名中不能有空格或者标点符号,但是有一个例外,点(".")可以出现在变量中,点后的可以是字典相关(字典的key或者字典内置方法)、对象的属性或方法、数字索引,如下所示
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| |
| { |
| |
| <!--调用字符串对象的upper方法,注意不要加括号--> |
| <p>{{ msg.upper }}</p> |
| |
| <!--取字典中k1对应的值--> |
| <p>{{ dic.k1 }}</p> |
| |
| <!--取对象的name属性--> |
| <p>{{ obj.name }}</p> |
| |
| <!--取列表的第2个元素,然后变成大写--> |
| <p>{{ li.1.upper }}</p> |
| |
| <!--取列表的第3个元素,并取该元素的age属性--> |
| <p>{{ li.2.age }}</p> |
| |
| </body> |
| </html> |
请注意四点
(1)在渲染页面时,若变量不存在,模板引擎默认用配置项string_if_invalid的值作为替代品,配置如下,默认值为空字符串”,可以进行配置
| |
| TEMPLATES = [ |
| |
| { |
| 'NAME': 'b1', |
| 'BACKEND': 'django.template.backends.django.DjangoTemplates', |
| 'DIRS': [os.path.join(BASE_DIR, 'aaa')], |
| 'APP_DIRS': True, |
| 'OPTIONS': { |
| 'string_if_invalid':'egon是大帅比', |
| |
| 'context_processors': [ |
| 'django.template.context_processors.debug', |
| 'django.template.context_processors.request', |
| 'django.contrib.auth.context_processors.auth', |
| 'django.contrib.messages.context_processors.messages', |
| ], |
| }, |
| }, |
| ] |
| |
| |
(2)当点后的值是可调用对象时,模板引擎在渲染时会当做无参函数进行调用,将调用的返回值插入模板。(Django不推荐我们在模板中做过多的逻辑处理,所以模板中的方法调全部为无参调用,即便是遇到有参的需求,那也应该是在后端处理,然后为模板提供处理结果即可)
(3)约定俗成:以下划线开头的变量属性通常被视为私有属性,可能无法访问
例如在视图函数中的变量是下划线开头的
_x = 222
在模板中是无法引用的
{{ _x }} 报错*
*
ps:__x也属于下划线开头的
(4)点后值的查找优先级如下
1、先进行: 字典key的查找
2、然后进行:属性或方法的查找
3、最后进行:数字索引的查找
针对上述优先级,需要尤其注意冲突问题,例如
| def test(request): |
| from collections import defaultdict |
| msg='helloegon' |
| d=defaultdict(int) |
| for s in msg: |
| d[s]+=1 |
| |
| return render(request, 'test.html', {'d':d}) |
针对defaultdict字典,在访问某一个不存在的key时会返回一个默认值,这就为冲突埋下了祸根:
在模板中插入{{ d.items }},我们的本意是想调用字典d的方法items(),但实际情况是会首先查找字典的key,所以items会被首先当做key来进行查找,很明显字典d中不存在名为items的key,而针对defaultdict类型的字典,会在key不存在时返回一个默认值(本例返回默认值为0),所以此时并不会调用items()方法。
针对上述问题的解决方案就是需要在视图中事先将defaultdict转换为dict类型
d=dict(d)
然后再传递给模板,这样在渲染{{ d.items }}时,先去字典中查找键items,无法找到也不会返回任何默认值,所以会转而查找d的属性或方法,从而调用到方法items()
过滤器类似于python的内置函数,用来把变量值加以修饰后再显示,具体语法如下
| |
| {{ 变量名|过滤器名 }} |
| |
| |
| {{ 变量名|过滤器1|过滤器2 }} |
| |
| |
| {{ 变量名|过滤器名:传给过滤器的参数 }} |
常用内置过滤器
| |
| |
| {{ value|default:"nothing" }} |
| |
| |
| |
| |
| {{ value|default_if_none:"None..." }} |
| |
| |
| |
| {{ value|length }} |
| |
| |
| |
| {{ value|filesizeformat }} |
| |
| |
| |
| {{ value|date:"Y-m-d" }} |
| |
| |
| |
| {{ value|slice:"0:2" }} |
| |
| |
| |
| {{ value|truncatechars:8 }} |
| |
| |
| |
| {{ value|truncatewords:2 }} |
模板在生成HTML时,如果变量中包含一些具有语法意义的特殊字符,则会影响HTML的结果,比如
针对变量{{ name }},如果用户注册自己的用户名name是一个中规中矩的用户名时,上述代码并无问题,但如果用户恶意注册用户名为下述内容
| |
| <script>alert('hello')</script> |
后台取出name = "alert(‘hello’)",然后执行render渲染的结果为
| Hello, <script>alert('hello')</script> |
上述结果交给浏览器后,意味着浏览器将弹出一个JavaScript警报框!试想,如果是一个博客类网站,恶意作者在自己提交的文章中掺杂了类似上面这种恶意代码,这意味着每个读者在读取他的文章时,自己的浏览器都会弹出一个JavaScript警报框!
类似的,如果注册的用户名包含'<‘号,如下
后台取出name = "egon",然后执行render渲染的结果为
上述结果交给浏览器后,有没有对应的闭合标签,意味着其后的内容都会被加粗
综上,可以确定的是:用户提交的数据不应该被盲目地信任并直接插入到我们的的网页中,因为恶意用户可能会利用这种漏洞来做潜在的坏事。这种类型的安全攻击称为跨站点脚本(XSS,详见:https://en.wikipedia.org/wiki/Cross-site_scripting)攻击。
好在django已经为了做了相应的处理:django的模板引擎在生成模板时,默认就会对所有变量的值进行转移,具体是针对变量值中包含的以下五个字符的转义
| |
| 1、< 被转换成 < |
| 2、> 被转换成 > |
| 3、' 单引号被转换成 ' |
| 4、" 双引号被转换成 " |
| 5、& 被转换成 & |
| |
| # 首先经过转义后得到模板,然后递交给浏览器解析,上述内容均会被当成普通字符输出 |
| 例如: |
| 针对value="<script>alert(123)</script>",模板变量{{ value }}会被渲染成<script>alert(123)</script>交给浏览器后会被解析成普通字符”<script>alert(123)</script>“,失去了js代码的语法意义 |
虽然django默认会将所有模板变量进行转义,但有时候我们需要关闭自动转义,比如我们存入数据库的就是一段HTML代码,当我们取出来时就想让浏览器解析其中的语法呈现结果,而不是显示一堆普通字符。那如何关闭自动转义呢?具体操作如下
(1)针对单个变量->使用过滤器safe
| {{ value|safe }} |
| |
| 比如如value='<a href="https://www.baidu.com">点我啊</a>',经过过滤器safe的处理,浏览器在进行解析时就会将其当做超链接显示,不加safe过滤器则会当做普通字符显示’<a href="https://www.baidu.com">点我啊</a>‘ |
(2)针对模板块->使用标签autoescape
autoescape通过参数on和off来控制开启或关闭模板块的整体转义行为,示例如下
| { |
| {% autoescape off %} |
| <p> |
| 不会转义 {{ name }}. |
| </p> |
| |
| { |
| {% autoescape on %} |
| <p> |
| 会被转义: {{ name }} |
| </p> |
| {% endautoescape %} |
| {% endautoescape %} |
autoescape的效果会遗传给子模板(使用标签extends继承当前模板),也会留给引入了当前模板的模板(使用标签include引入当前模板),例如
base.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| {% autoescape on %} |
| <h1> |
| {% block title %}{% endblock %} |
| </h1> |
| |
| {% block content %}{% endblock %} |
| {% endautoescape %} |
| |
| </body> |
| </html> |
child.html
| { |
| {% extends "base.html" %} |
| |
| { |
| {% block title %}egon & dsb{% endblock %} |
| |
| { |
| {% block content %}{{ name }}{% endblock %} |
ps: autoescape的使用涉及到的标签与模板继承的概念,请读者自行查看后续章节
我们在介绍常用的内置过滤器时提到过:过滤器的参数可以是字符串,比如default过滤器,而default过滤器默认不会对字符串进行转义
| {{ value1|default:"<a href='http://www.egonav.com'>egon草裙舞</a>" }} |
即便是我们用标签{% autoescape on %}开启了自动转义,过滤器default仍然不会对字符串进行转义,所以上述代码在value值为Flase/空/None的情况下,浏览器展示的就是一个超链接,如果我们仅仅只想把defaul的值显示成字符串,那么需要我们手动把默认值里的特殊符号写成命名实体,如下
| {{ value1|default:"<a href='http://www.egonav.com'>egon草裙舞</a>" }} |
当我们要显示的默认值是“3 < 2”时,因为小于号<与数值2并不会组成一个特殊标签,所以浏览器会显示出“3 < 2”
| {{ data|default:"3 < 2" }} { |
但是我们还是应该写成下面这种格式,这才是通用的书写格式
| {{ data|default:"3 < 2" }} |
详见官网:https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#ref-templates-builtins-filters
过滤器 |
描述 |
示例 |
upper |
以大写方式输出 |
{{ user.name | upper }} |
add |
给value加上一个数值 |
{{ user.age | add:”5” }} |
addslashes |
单引号加上转义号 |
|
capfirst |
第一个字母大写 |
{{ ‘good’| capfirst }} 返回”Good” |
center |
输出指定长度的字符串,把变量居中 |
{{ “abcd”| center:”50” }} |
cut |
删除指定字符串 |
{{ “You are not a Englishman” | cut:”not” }} |
date |
格式化日期 |
|
default_if_none |
如果值为None, 则使用默认值代替 |
|
dictsort |
按某字段排序,变量必须是一个dictionary |
{% for moment in moments | dictsort:”id” %} |
dictsortreversed |
按某字段倒序排序,变量必须是dictionary |
|
divisibleby |
判断是否可以被数字整除 |
{{ 224 | divisibleby:2 }} 返回 True |
escape |
按HTML转义,比如将”<”转换为”<” |
|
filesizeformat |
增加数字的可读性,转换结果为13KB,89MB,3Bytes等 |
{{ 1024 | filesizeformat }} 返回 1.0KB |
first |
返回列表的第1个元素,变量必须是一个列表 |
|
floatformat |
转换为指定精度的小数,默认保留1位小数 |
{{ 3.1415926 | floatformat:3 }} 返回 3.142 四舍五入 |
get_digit |
从个位数开始截取指定位置的数字 |
{{ 123456 | get_digit:’1’}} |
join |
用指定分隔符连接列表 |
{{ [‘abc’,’45’] | join:’’ }} 返回 abc45 |
length |
返回列表中元素的个数或字符串长度 |
|
length_is |
检查列表,字符串长度是否符合指定的值 |
{{ ‘hello’| length_is:’3’ }} |
linebreaks |
用或 标签包裹变量 |
{{ “Hi\n\nDavid”|linebreaks }} 返回HiDavid |
linebreaksbr |
用 标签代替换行符 |
|
linenumbers |
为变量中的每一行加上行号 |
|
ljust |
输出指定长度的字符串,变量左对齐 |
{{‘ab’|ljust:5}}返回 ‘ab ’ |
lower |
字符串变小写 |
|
make_list |
将字符串转换为列表 |
|
pluralize |
根据数字确定是否输出英文复数符号 |
|
random |
返回列表的随机一项 |
|
removetags |
删除字符串中指定的HTML标记 |
{{value | removetags: “h1 h2”}} |
rjust |
输出指定长度的字符串,变量右对齐 |
|
slice |
切片操作, 返回列表 |
{{[3,9,1] | slice:’:2’}} 返回 [3,9] {{ ‘asdikfjhihgie’ | slice:’:5′ }} 返回 ‘asdik’ |
slugify |
在字符串中留下减号和下划线,其它符号删除,空格用减号替换 |
{{ ‘5-2=3and5 2=3’ | slugify }} 返回 5-23and5-23 |
stringformat |
字符串格式化,语法同python |
|
time |
返回日期的时间部分 |
|
timesince |
以“到现在为止过了多长时间”显示时间变量 |
结果可能为 45days, 3 hours |
timeuntil |
以“从现在开始到时间变量”还有多长时间显示时间变量 |
|
title |
每个单词首字母大写 |
|
truncatewords |
将字符串转换为省略表达方式 |
{{ ‘This is a pen’ | truncatewords:2 }}返回“This is … |
truncatewords_html |
同上,但保留其中的HTML标签 |
{{ ‘
This is a pen
‘ | truncatewords:2 }}返回“
This is …
|
urlencode |
将字符串中的特殊字符转换为url兼容表达方式 |
{{ ‘http://www.aaa.com/foo?a=b&b=c’ | urlencode}} |
urlize |
将变量字符串中的url由纯文本变为链接 |
|
wordcount |
返回变量字符串中的单词数 |
|
yesno |
将布尔变量转换为字符串yes, no 或maybe |
{{ True | yesno }}{{ False | yesno }}{{ None | yesno }} 返回 yes no maybe |

相比于模板中的变量,标签更为复杂一些,如
模板中的标签的格式为
| |
| {% 标签名 %} |
| |
| |
| {% 标签名 参数1 参数2 %} |
| |
| |
| {% 标签名 %} |
| ...内容... |
| {% end标签名 %} |
| |
| {% for person in person_list %} |
| <p>{{ person.name }}</p> |
| {% endfor %} |
| |
| |
| |
| |
| {% for key,val in dic.items %} |
| <p>{{ key }}:{{ val }}</p> |
| {% endfor %} |
| |
| |
| forloop.counter 当前循环的索引值(从1开始) |
| forloop.counter0 当前循环的索引值(从0开始) |
| forloop.revcounter 当前循环的倒序索引值(从1开始) |
| forloop.revcounter0 当前循环的倒序索引值(从0开始) |
| forloop.first 当前循环是第一次循环则返回True,否则返回False |
| forloop.last 当前循环是最后一次循环则返回True,否则返回False |
| forloop.parentloop 本层循环的外层循环 |
| |
| |
| {% for person in person_list %} |
| <p>{{ person.name }}</p> |
| |
| {% empty %} |
| <p>sorry,no person here</p> |
| {% endfor %} |
| |
| 了解:Django框架的for循环,没有break和continue方法,可以使用自定义过滤器实现forloop | continue和forloop | break,参考:https://djangosnippets.org/snippets/2093/ |
案列如下
url.py
| from django.urls import re_path |
| from app01 import views |
| |
| urlpatterns = [ |
| re_path(r'^test/',views.test) |
| ] |
view.py
| def test(request): |
| names=['egon','kevin'] |
| dic={'name':'egon','age':18,'sex':'male'} |
| |
| list1=[] |
| |
| return render(request,'test.html',locals()) |
test.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| <hr> |
| {% for name in names %} |
| <p>{{ forloop.counter0 }} {{ name }}</p> |
| {% endfor %} |
| <!-- |
| 输出结果为: |
| 0 egon |
| 1 kevin |
| --> |
| |
| <hr> |
| {% for name in names reversed %} |
| <p>{{ forloop.revcounter0 }} {{ name }}</p> |
| {% endfor %} |
| <!-- |
| 输出结果为: |
| 1 kevin |
| 0 egon |
| --> |
| |
| <hr> |
| {% for k,v in dic.items %} |
| <p>{{ forloop.counter }} {{ k }} {{ v }}</p> |
| {% endfor %} |
| <!-- |
| 输出结果为: |
| 1 name egon |
| 2 age 18 |
| 3 sex male |
| --> |
| |
| <hr> |
| {% for item in list1 %} |
| <p>{{ item }}</p> |
| {% empty %} |
| <p>sorry,no value here</p> |
| {% endfor %} |
| <!-- |
| 输出结果为: |
| sorry,no value here |
| --> |
| |
| </body> |
| </html> |
| |
| {% if 条件 %}条件为真时if的子句才会生效,条件也可以是一个变量,if会对变量进行求值,在变量值为空、或者视图没有为其传值的情况下均为False |
| |
| |
| {% if num > 100 or num < 0 %} |
| <p>无效</p> |
| {% elif num > 80 and num < 100 %} |
| <p>优秀</p> |
| {% else %} |
| <p>凑活吧</p> |
| {% endif %} |
| |
| |
| |
| |
| {% if athlete_list|length > 1 %} |
| Team: {% for athlete in athlete_list %} ... {% endfor %} |
| {% else %} |
| Athlete: {{ athlete_list.0.name }} |
| {% endif %} |
| |
| 过滤器length返回的数字可用于与数字进行比较,除此之外大多数过滤器返回的都是字符串并不能用于与数字比较 |
| |
| |
| 针对下述多分支 |
| {% if var1 %} |
| {{ var1 }} |
| {% elif var2 %} |
| {{ var2 }} |
| {% elif var3 %} |
| {{ var3 }} |
| {% endif %} |
| |
| 可以简写为一行 |
| {% firstof var1 var2 var3 %} |
| |
| 也可以定义一个备用值,当var1、var2、var3均无值的时使用 |
| {% firstof var1 var2 var3 "fallback value" %} |
案例如下
urls.py
| from django.urls import path,register_converter,re_path |
| from app01 import views |
| |
| urlpatterns = [ |
| |
| re_path(r'^$',views.index), |
| re_path(r'^index/$',views.index), |
| |
| re_path(r'^login/',views.login), |
| |
| ] |
views.py
| from django.shortcuts import render |
| |
| def index(request): |
| return render(request,'index.html') |
| |
| def login(request): |
| if request.method == 'GET': |
| return render(request,'login.html') |
| |
| name=request.POST.get('name') |
| pwd=request.POST.get('pwd') |
| if name == 'egon' and pwd == '123': |
| current_user=name |
| return render(request,'index.html',locals()) |
| else: |
| msg='账号或密码错误' |
| return render(request,'login.html',locals()) |
在templates目录下新建模板文件index.html与login.html
index.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>首页</title> |
| </head> |
| <body> |
| |
| <h3>首页</h3> |
| <!-- |
| 如果用户已经登录,则current_user变量有值,if判断结果为真,会打印变量current_user的值,为当前登录的用户名 |
| 如果用户没有登录,则current_user变量无值,if判断结果为假,会打印a标签要求用户先登录 |
| --> |
| {% if current_user %} |
| <p>当前登录用户为:{{ current_user }}</p> |
| {% else %} |
| <p><a href="/login/">请先登录</a></p> |
| {% endif %} |
| |
| </body> |
| </html> |
login.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>登录页面</title> |
| </head> |
| <body> |
| |
| <form action="" method="POST"> |
| {% csrf_token %} |
| <p>用户名:<input type="text" name="name"></p> |
| <p>密码:<input type="password" name="pwd"></p> |
| <p><input type="submit" value="提交"></p> |
| </form> |
| <!--输错账号密码时的提示信息--> |
| <p style="color: red">{{ msg }}</p> |
| </body> |
| </html> |
测试
| python manage.py runserver 8008 |
| |
| {% with li.1.upper as v %} |
| {{ v }} |
| {% endwith %} |
| |
| <form action="" method="POST"> |
| {% csrf_token %} |
| <p>用户名:<input type="text" name="name"></p> |
| <p>密码:<input type="password" name="pwd"></p> |
| <p><input type="submit" value="提交"></p> |
| </form> |
| |
| |
| |
| |
| |
更多内置标签与过滤器:https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#ref-templates-builtins-tags
单行注释,用注释语法:{# #},在{#和#}分隔符之间不允许有换行符
多行注释,用comment标签,注意:comment标签不能嵌套
| {% comment "可以在该引号里写好下述代码被注释的原因" %} |
| <p>备注释的内容</p> |
| {% endcomment %} |
在实际开发中,模板文件彼此之间可能会有大量冗余代码,为此django提供了专门的语法来解决这个问题,主要围绕三种标签的使用:include标签、extends标签、block标签,详解如下
{% include ‘模版名称’ %}
案例:
可以把广告栏写到专门的文件里advertise.html
| <div class="adv"> |
| <div class="panel panel-default"> |
| <div class="panel-heading"> |
| <h3 class="panel-title">Panel title</h3> |
| </div> |
| <div class="panel-body"> |
| Panel content |
| </div> |
| </div> |
| <div class="panel panel-danger"> |
| <div class="panel-heading"> |
| <h3 class="panel-title">Panel title</h3> |
| </div> |
| <div class="panel-body"> |
| Panel content |
| </div> |
| </div> |
| <div class="panel panel-warning"> |
| <div class="panel-heading"> |
| <h3 class="panel-title">Panel title</h3> |
| </div> |
| <div class="panel-body"> |
| Panel content |
| </div> |
| </div> |
| </div> |
然后在base.html文件中用include标签引入advertise.html文件的内容
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" |
| integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| } |
| |
| .header { |
| height: 50px; |
| width: 100%; |
| background-color: black; |
| } |
| |
| </style> |
| </head> |
| <body> |
| <div class="header"></div> |
| <div class="container"> |
| <div class="row"> |
| <div class="col-md-3"> |
| <!--在base.html引入advertise.html文件的内容--> |
| {% include "advertise.html" %} |
| </div> |
| <div class="col-md-9"></div> |
| </div> |
| </div> |
| </body> |
| </html> |
案例
Django模版引擎中最复杂且最强大的部分就是模版继承了。我们以先创建一个基本的“骨架”模版,它包含我们站点中的全部元素,并且可以定义多处blocks ,例如我们创建base.html内容如下
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title> |
| {% block title %}自定义title名{% endblock %} |
| </title> |
| |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" |
| integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| } |
| |
| .header { |
| height: 50px; |
| width: 100%; |
| background-color: |
| margin-bottom: 20px; |
| } |
| |
| </style> |
| |
| </head> |
| <body> |
| <div class="header"></div> |
| |
| <div class="container"> |
| <div class="row"> |
| <div class="col-md-3"> |
| <div class="list-group"> |
| {% block sidebar %} |
| <a href="#" class="list-group-item active">服装城</a> |
| <a href="#" class="list-group-item">美妆馆</a> |
| <a href="#" class="list-group-item">超市</a> |
| <a href="#" class="list-group-item">全球购</a> |
| <a href="#" class="list-group-item">闪购</a> |
| <a href="#" class="list-group-item">团购</a> |
| {% endblock %} |
| |
| </div> |
| </div> |
| |
| <div class="col-md-9"> |
| {% block content %} |
| base.html页面内容 |
| {% endblock %} |
| </div> |
| </div> |
| |
| </div> |
| |
| </body> |
| </html> |
模板base.html 定义了一个可以用于两列排版页面的简单HTML骨架。我们新建子模板index.html的主要工作就是继承base.html然后填充/覆盖其内部的blocks。
| {% extends "base.html" %} |
| |
| <!--用新内容完全覆盖了父模板内容--> |
| {% block title %} |
| index页面 |
| {% endblock %} |
| |
| {% block sidebar %} |
| <!--该变量会将父模板中sidebar中原来的内容继承过来,然后我们可以在此基础上新增,否则就是纯粹地覆盖--> |
| {{ block.super }} |
| |
| <!--在继承父模板内容的基础上新增的标签--> |
| <a href="#" class="list-group-item">拍卖</a> |
| <a href="#" class="list-group-item">金融</a> |
| {% endblock %} |
| |
| {% block content %} |
| <!--用新内容完全覆盖了父模板内容--> |
| <p>index页面内容</p> |
| {% endblock %} |
我们通过django访问index.html看到内容如下(block标签的内容都完成了替换或更新)
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title> |
| index页面 |
| </title> |
| |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" |
| integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| } |
| |
| .header { |
| height: 50px; |
| width: 100%; |
| background-color: |
| margin-bottom: 20px; |
| } |
| |
| </style> |
| |
| </head> |
| <body> |
| <div class="header"></div> |
| |
| <div class="container"> |
| <div class="row"> |
| <div class="col-md-3"> |
| <div class="list-group"> |
| <!--基于{{ block.super }}继承自父模板中的内容--> |
| <a href="#" class="list-group-item active">服装城</a> |
| <a href="#" class="list-group-item">美妆馆</a> |
| <a href="#" class="list-group-item">超市</a> |
| <a href="#" class="list-group-item">全球购</a> |
| <a href="#" class="list-group-item">闪购</a> |
| <a href="#" class="list-group-item">团购</a> |
| |
| <!--在继承父模板内容的基础上新增的标签--> |
| <a href="#" class="list-group-item">拍卖</a> |
| <a href="#" class="list-group-item">金融</a> |
| </div> |
| </div> |
| |
| <div class="col-md-9"> |
| <!--用新内容完全覆盖了父模板内容--> |
| <p>index页面内容</p> |
| </div> |
| </div> |
| |
| </div> |
| |
| </body> |
| </html> |
我们可以根据需要使用尽可能多的继承级别,使用继承的一种常见方式是分成三个级别
1、创建一个base.html用来保存站点的主要外观
2、为站点的每个部分创建一个base_SECTIONNAME.html模板。例如,base_news.html、base_sports.html。这些模板都继承自base.html并包含每部分特定的样式/设计。
3、为每种类型的页面创建单独的模板,如新闻文章或博客条目。这些继承自2中的对应的模板。
总结与注意:
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| {% block content %} |
| ... |
| {% endblock content %} |
| |
| |
当内置的过滤器或标签无法满足我们需求时,我们可以自定义,具体操作步骤如下
1、在settings中的INSTALLED_APPS添加当前app的名字,不然django无法找到自定义的过滤器或标签
settings.py
| |
| INSTALLED_APPS = [ |
| 'django.contrib.admin', |
| 'django.contrib.auth', |
| 'django.contrib.contenttypes', |
| 'django.contrib.sessions', |
| 'django.contrib.messages', |
| 'django.contrib.staticfiles', |
| 'app01.apps.App01Config', |
| 'app01', |
| ] |
2、在文件夹app01下创建包templatetags
1、templatetags目录下需要有init.py文件,确保它是一个python的包
2、文件夹名只能是templatetags
3、templatetags与models.py,views.py在同一级别
3、在templatetags新建任意.py文件,如my_tags.py,在该文件中自定义过滤器或标签,由于我们后期是要通过加载文件来引入自定义的过滤器或标签,所以请保障文件名的唯一性,如此方可保障多个app之间不相互冲突
目录层级如下
| app01/ |
| __init__.py |
| models.py |
| templatetags/ |
| __init__.py |
| my_tags.py |
| views.py |
| |
| |
my_tags.py文件内容如下
| |
| from django import template |
| |
| register = template.Library() |
| |
| |
| ''' |
| 2.1、自定义过滤器 |
| 自定义过滤器是一个python函数,该函数需要1或2个参数,两个参数会一同传入 |
| (1) 第一个参数来自过滤器左侧的变量(固定有) |
| (2) 第二个参数来自过滤右侧的参数值(可有可无) |
| 例如:针对自定义过滤器foo的使用 |
| {{ var|foo:"bar" }},会执行foo(var,"bar") |
| ''' |
| @register.filter(name='cut') |
| def cut(value, arg): |
| """Removes all values of arg from the given string""" |
| return value.replace(arg, '') |
| |
| @register.filter |
| def lower(value): |
| """Converts a string into all lowercase""" |
| return value.lower() |
| |
| |
| @register.simple_tag |
| def my_multi_tag(v1, v2): |
| return v1 * v2 |
| |
| ''' |
| 2.3、自定义标签扩展之mark_safe |
| 我们可以用内置标签safe来关闭转义,从而让标签内容有语法意义,但如果我们想让自定义标签处理的结果也有语法意义,则不能使用内置标签safe了,需要使用mark_safe,可以实现与内置标签safe同样的功能 |
| ''' |
| from django.utils.safestring import mark_safe |
| |
| @register.simple_tag |
| def my_input_tag(id, name): |
| res = "<input type='text' id='%s' name='%s' />" % (id, name) |
| return mark_safe(res) |
4、自定义过滤器或标签必须重新启动django方可生效
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| |
| <!--必须先加载存有自定义过滤器和标签的文件--> |
| {% load my_tags %} |
| |
| <!--过滤器cut使用:处理结果为:Egon is 18 years old--> |
| {{ 'Egon is 180 years old'|cut:"0" }} |
| |
| <!--过滤器lower使用:处理结果为:egon--> |
| {{ 'Egon'|lower }} |
| |
| <!--结果为2--> |
| {% my_multi_tag 1 2 %} |
| |
| <!-- |
| 结果为一个input标签,该表的属性id="inp1" name="username" |
| 注意:input的属性值均为字符串类型,所以my_input_tag后的两个值均为字符串类型 |
| --> |
| {% my_input_tag "inp1" "username" %} |
| |
| </body> |
| </html> |
对比自定义标签与自定义过滤器
| |
| |
| |
| {% if salary|my_multi_filter:12 > 200 %} |
| <p>优秀</p> |
| {% else %} |
| <p>垃圾</p> |
| {% endif %} |
在4.1小节我们提及内置的include标签可以在当前模板中引入其他的模板,但有一个问题是:被引入的模板内容是固定死的,如果我们想根据传入不同的参数控制被引入模板的内容,就需要自定义inclusion标签。
先创建好被引入的模板userinfo.html,嵌入变量
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| <h1>靓仔的名字: {{ username }}</h1> |
| <h1>靓仔的年龄: {{ age }}</h1> |
| </body> |
| </html> |
然后自定义inclusion标签
| |
| from django import template |
| |
| register = template.Library() |
| |
| @register.inclusion_tag('userinfo.html') |
| def get_userinfo(username, age): |
| ''' |
| 可以函数内编写一系列的逻辑,比如根据传来的参数,去后台数据库中取出相应的数据 |
| ''' |
| |
| res = { |
| 'username': username, |
| 'age': age, |
| } |
| return res |
自定义标签get_userinfo的返回值必须是一个字典,该字典用来渲染模板userinfo.html。
如果我们想在index.html中引入userinfo.html的内容,做法如下,为标签get_userinfo传入不同的参数则会得到不同的模板内容
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| {% load my_tags %} |
| {% get_userinfo "egon" age=18 %} { |
| </body> |
| </html> |
自定义inclusion标签函数可以接收任意数量的位置或关键字参数,例如
| @register.inclusion_tag('my_template.html') |
| def my_inclusion_tag(a, b, *args, **kwargs): |
| warning = kwargs['warning'] |
| profile = kwargs['profile'] |
| ... |
| return 用于渲染模板'my_template.html'的字典 |
但此时的参数都需要在模板中直接传递,比如
| {% my_inclusion_tag "egon" 18 book.title warning=message|lower profile=user.profile %} |
毫无疑问,让模板的开发者去记住所有参数的顺序并进行传递是非常痛苦的,为此register.inclusion_tag提供了一个参数takes_context专门用来解决这个问题。该参数默认值为False,当设置成True时,就像单词字面所表达的一样:从contex中获取参数。context是由当前模板对应的视图返回的,如当前模板是index.html,那context就来自于index.html对应的后台视图返回。所以当takes_context=True时,自定义标签只需有一个参数,且参数名必须为context,与render函数的context参数相呼应
my_template.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| {{ aaa }} |
| {{ bbb }} |
| {{ ccc }} |
| {{ ddd }} |
| </body> |
| </html> |
my_tags.py
| from django import template |
| |
| register = template.Library() |
| |
| @register.inclusion_tag('my_template.html', takes_context=True) |
| def my_inclusion_tag(context): |
| |
| res = { |
| 'aaa': context['a'], |
| 'bbb': context['b'], |
| 'ccc': context['warning'], |
| 'ddd': context['profile'], |
| 'other_data':'...' |
| } |
| return res |
如果我们想在index.html中引入my_template.html的内容,做法如下,此时无需为标签my_inclusion_tag传入参数
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| {% load my_tags %} |
| {% my_inclusion_tag %} |
| </body> |
| </html> |
my_inclusion_tag的参数是来自于当前模板(即index.html)对应的后台视图返回的context
| from django.shortcuts import render |
| |
| def index(request): |
| |
| context = { |
| 'a': "egon", |
| 'b': 18, |
| 'warning': 'xxx', |
| 'profile': 'yyy' |
| } |
| return render(request, 'index.html', context=context) |
我们在编写模板文件时,需要大量引用css、js、图片等静态文件,如果我们将这些文件在服务端存放的路径都固定写死那么将非常不利于后期的扩展,我们可以这么做
1、settings.py
| STATIC_URL = '/static/' |
| STATICFILES_DIRS = [ |
| os.path.join(BASE_DIR, 'statics'), |
| ] |
| |
2、在项目根目录下新增文件夹statics,为了更便于管理,可以在statics下新建子文件夹css、js、img等
插图:statics目录结构
3、新建模板文件index.html,在该文件中对静态文件的引用如下
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| <link rel="stylesheet" href="/static/css/my.css"> |
| </head> |
| <body> |
| <h4>我是红色的,点我就绿</h4> |
| <img src="/static/img/rb.jpeg" alt=""> |
| |
| <script src="/static/js/jquery-3.3.1.min.js"></script> |
| <script src="/static/js/my.js"></script> |
| |
| </body> |
| </html> |
综上,在配置完settings.py后,所有的静态文件路径都可以采用别名/static/作为起始,这在一定程度上会有利于程序的扩展性,但如果我们在项目后期维护时,连/static/这个值也需要修改,那意味着所有模板文件中也都需要跟着改了,扩展性依然很差,为此,django在一个名为static.py的文件中定义了标签static、get_static_prefix,二者都可以解决该问题
test.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| <!--注意:必须先加载文件static.py--> |
| {% load static %} |
| <!--注意:此处的static是一个定义在static.py中的一个标签,名字与文件名一样而已,不要搞混--> |
| <link rel="stylesheet" href="{% static 'css/my.css' %}"> |
| </head> |
| <body> |
| <h4>我是红色的,点我就绿</h4> |
| <img src="{% static 'img/rb.jpeg' %}" alt=""> |
| |
| {% load static %} |
| <script src="{% static 'js/jquery-3.3.1.min.js' %}"></script> |
| <script src="{% static 'js/my.js' %}"></script> |
| |
| </body> |
| </html> |
标签static会接收传入的参数,然后这根据settings.py中变量STATIC_URL的值拼接出一个完整的路径,如果STATIC_URL = ‘/static/’,那么href="{% static ‘css/my.css’ %}"会被渲染成href="/static/css/my.css",如果STATIC_URL = ‘/static123/’,那么href="{% static ‘css/my.css’ %}"会被渲染成href="/static123/css/my.css"。
标签get_static_prefix也可以完成同样的效果,只不过用法不同。我们不能为标签get_static_prefix传参,因为标签get_static_prefix代表的只是settings.py中STATIC_URL的值,所以我们需要做的是在get_static_prefix的基础上自行拼接路径,如下
test.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| <!--注意:同样需要先加载文件static.py--> |
| {% load static %} |
| <!--使用标签get_static_prefix拼接路径--> |
| <link rel="stylesheet" href="{% get_static_prefix %}css/my.css"> |
| </head> |
| <body> |
| <h4>我是红色的,点我就绿</h4> |
| <img src="{% get_static_prefix %}img/rb.jpeg" alt=""> |
| |
| {% load static %} |
| <script src="{% get_static_prefix %}js/jquery-3.3.1.min.js"></script> |
| <script src="{% get_static_prefix %}js/my.js"></script> |
| |
| </body> |
| </html> |
如果STATIC_URL = ‘/static/’,那么href="{% get_static_prefix %}css/my.css"会被渲染成href="/static/css/my.css",其它同理
静态文件的查找与模板的查找是一致的,当我们有多个app时,可以如下组织目录结构(只列举了静态文件相关目录)
/Users/linhaifeng/PycharmProjects/EgonPro
├── app01
│ ├── static
│ │ └── a.css *# 文件内容为:h1 { color: red; }
├── app02
│ ├── static
│ │ └── a.css # 文件内容为:h1 { color: green; }
├── static
│ └── a.css # 文件内容为:h1 { color: blue; }
├── static1
│ └── a.css # 文件内容为:h1 { color: goldenrod; }
├── templates
*│ └── index.html
配置文件settings.py
| STATIC_URL = '/static/' |
| |
| STATICFILES_DIRS = [ |
| os.path.join(BASE_DIR, "static"), |
| os.path.join(BASE_DIR, "static1"), |
| ] |
在模板index.html中,
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| {% load static %} |
| |
| <link rel="stylesheet" href="{% static "a.css" %}"> |
| </head> |
| <body> |
| <h1>模板内容</h1> |
| </body> |
| </html> |
若每个app下存放静态文件的目录名均为static,静态文件的加载优先级模板的查找优先级是一样的原理,{% static "a.css" %}查找优先级为
1、djanog首先会先依次检索STATICFILES_DIRS列表中的目录
2、如果每个app下存在名为static的目录,则
为了避免出现冲突,与模板的组织原理一样,我们可以在每个static下创建子目录来充当名称空间的作用,所以上述目录结构调整为
/Users/linhaifeng/PycharmProjects/EgonPro
├── app01
│ ├── static
│ │ └── app01
│ │ └── a.css
├── app02
│ ├── static
│ │ └── app02
│ │ └── a.css
├── static
│ └── base
│ └── a.css
├── static1
│ └── base1
│ └── a.css
├── templates
│ └── index.html
模板index.html中引入静态文件a.css的路径如下,即便静态文件重名,也不会发生冲突
| <link rel="stylesheet" href="{% static "base/a.css" %}"> |
| |
| <link rel="stylesheet" href="{% static "base1/a.css" %}"> |
| |
| <link rel="stylesheet" href="{% static "app01/a.css" %}"> |
| |
| <link rel="stylesheet" href="{% static "app02/a.css" %}"> |