7. 模板介绍

作为Web框架,Django提供了模板,可以很便利的动态生成HTML。

模版系统致力于表达外观,而不是程序逻辑,我们把页面不需要更改的部分写成模板, 在视图中,跟进需要选用同的模板并用数据对动态部分进行填充,最终生成返回给 使用者的页面。

模板的设计实现了业务逻辑(view)与显示内容(template)的分离, 一个视图可以使用任意一个模板,一个模板可以供多个视图使用。

从Django1.8以后,支持自定义模板,可以更换系统默认模板系统,比如jinja2。 具体更换教程可以百度。

  • 模板包含

    • HTML的静态部分
    • 动态插入内容部分
    • 可以把模板理解成是包含特定标签的网页文件,特定标签内可以执行类似于python语言的代码 , 模板的渲染就是执行标签内的类python语言并最终生成一共纯网页页面。
  • Django模板语言,简写DTL,定义在django.template包中

  • settings.py定义关于模板的值:

    • DIRS定义了一个目录列表,模板引擎按列表顺序搜索这些目录以查找模板源文件

    • APP_DIRS告诉模板引擎是否应该在每个已安装的应用中查找模板

    • 常用方式:在项目的根目录下创建templates目录,设置DIRS值

       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',
                ],
            },
        },
      

      ]

  • 模板语言包括

    • 变量
    • 标签
    • 过滤器
    • 注释

7.1. 变量

  • 语法:

     {{variable}}
    
  • 当模版引擎遇到一个变量,将计算这个变量,然后将结果输出

  • 变量名必须由字母、数字、下划线(不能以下划线开头)和点组成

  • 当模版引擎遇到点(“.”),会按照下列顺序查询:

    • 字典查询,例如:foo[“bar”]
    • 属性或方法查询,例如:foo.bar
    • 数字索引查询,例如:foo[bar]
  • 如果变量不存在, 模版系统将插入’’ (空字符串)

  • 在模板中调用方法时不能传递参数

7.2. 标签

  • 语法

    {% tag %}
    
  • 作用

    • 在输出中创建文本
    • 控制循环或逻辑
    • 加载外部信息到模板中供以后的变量使用
  • for

      { %for ... in ...%}
      循环逻辑
      {{forloop.counter}}表示当前是第几次循环
      { %empty%}
      给出的列表为或列表不存在时,执行此处
      { %endfor%}
    
  • if

      { %if ...%}
      逻辑1
      { %elif ...%}
      逻辑2
      { %else%}
      逻辑3
      { %endif%}
    
  • comment

      { % comment % }
      多行注释
      { % endcomment % }
    
  • include:加载模板并以标签内的参数渲染

      { %include "foo/bar.html" % }
    
  • url: 反向解析

      { % url 'name' p1 p2 %}
    
  • csrf_token:这个标签用于跨站请求伪造保护
    以前的案例为了省事我们会关掉csrf控制(在中间件中), 实际 使用中,页面的表单中必须包含csrf标签。
    防火防盗防破坏,安全责任重于老泰山。

      { % csrf_token %}
    
  • 布尔标签:and、or,and比or的优先级高

  • block、extends:详见“模板继承”

  • autoescape:详见“HTML转义”

7.3. 过滤器

  • 语法:{ { 变量|过滤器 }},例如{ { name|lower }},表示将变量name的值变为小写输出

  • 使用管道符号 (|)来应用过滤器

  • 通过使用过滤器来改变变量的计算结果

  • 可以在if标签中使用过滤器结合运算符

      if list1|length > 1
    
  • 过滤器能够被“串联”,构成过滤器链

      name|lower|upper
    
  • 过滤器可以传递参数,参数使用引号包起来

      list|join:", "
    
  • default:如果一个变量没有被提供,或者值为false或空,则使用默认值,否则使用变量的值

      value|default:"什么也没有"
    
  • date:根据给定格式对一个date变量格式化

      value|date:'Y-m-d'
    

7.4. 注释

  • 单行注释

      {# ....... #}
    
  • 注释可以包含任何模版代码,有效的或者无效的都可以

      {# { % if foo % }bar{ % else % } #}
    
  • 使用comment标签注释模版中的多行内容

7.5. 模板继承

  • 模板继承可以减少页面内容的重复定义,实现页面内容的重用

  • 典型应用:网站的头部、尾部是一样的,这些内容可以定义在父模板中,子模板不需要重复定义

  • block标签:在父模板中预留区域,在子模板中填充

  • extends继承:继承,写在模板文件的第一行

  • 定义父模板base.html

      { %block block_name%}
      这里可以定义默认值
      如果不定义默认值,则表示空字符串
      { %endblock%}
    
  • 定义子模板index.html

      { % extends "base.html" %}
    
  • 在子模板中使用block填充预留区域

      { %block block_name%}
      实际填充内容
      { %endblock%}
    
  • 说明

    • 如果在模版中使用extends标签,它必须是模版中的第一个标签

    • 不能在一个模版中定义多个相同名字的block标签

    • 子模版不必定义全部父模版中的blocks,如果子模版没有定义block,则使用了父模版中的默认值

    • 如果发现在模板中大量的复制内容,那就应该把内容移动到父模板中

    • 使用可以获取父模板中block的内容

    • 为了更好的可读性,可以给endblock标签一个名字

        { % block block_name %}
        区域内容
        { % endblock block_name %}
      

7.6. 继承实例-三层继承结构

在实际使用中, 三层继承结构使代码得到最大程度的复用,并且使得添加内容更加简单。

我们以三级继承结构为例进行代码示例。

如下图为常见的电商页面:

三层继承结构

7.6.1. 创建根级模板

  • 名称为“base.html”

  • 存放整个站点共用的内容

      <!DOCTYPE html>
      <html>
          <head>
              <title>{ %  block title  % }{ %  endblock  % } 北京图灵学院课程小超市 </title>
          </head>
          <body>
               这是头部内容  
              <hr/>
              { %  block left  % }
    
              { %  endblock  % }
    
              { %  block content  % }
    
              { %  endblock  % }
              <hr/>
              底部内容 
          </body>
      </html>
    

7.6.2. 创建分支模版

  • 继承自base.html

  • 名为“base_*.html”

  • 定义特定分支共用的内容

  • 定义base_course.html

      { %  extends 'templates/base.html'  % }
    
      { %  block title  % }课程{ %   endblock   % }
    
      { %  block left  % }
          <h1>courese left</h1>
      { %  endblock  % }
      定义base_user.html
      { %  extends 'temtest/base.html'  % }
    
      { %  block title  % }用户中心{ %  endblock   % }
    
      { %  block left  % }
          <font color='blue'>user left</font>
      { %  endblock  % }
      定义index.html,继承自base.html,不需要写left块
      { %  extends 'temtest/base.html'  % }
    
      { %  block content  % }
          首页内容
      { %  endblock content  % }
    

7.6.3. 为具体页面创建模板,继承自分支模板

  • 定义商品列表页goodslist.html

      { %  extends 'templates/base_course.html'  % }
      { %  block content  % }
          课程正文列表
      { %  endblock content  % }
      定义用户密码页userpwd.html
      { %  extends 'templates/base_user.html'  % }
      { %  block content  % }
          用户密码修改
      { %  endblock content  % }
    

7.6.4. 视图调用具体页面,并传递模板中需要的数据

  • 首页视图index

      logo='Hello 北京图灵学院'
      def index(request):
          return render(request, 'templates/index.html', {'logo': logo})
      课程列表视图courseslist
      def courses_list(request):
          return render(request, 'templates/courseslist.html', {'logo': logo})
      用户密码视图userpwd
      def user_pwd(request):
          return render(request, 'templates/userpwd.html', {'logo': logo})
    

7.6.5. 配置url

from django.conf.urls import url
from . import views
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^list/$', views.courses_list, name='list'),
    url(r'^pwd/$', views.user_pwd, name='pwd'),
]

7.7. HTML转义

  • 转义原因-安全

  • Django对字符串进行自动HTML转义,如在模板中输出如下值:

      #视图代码:
      def to_esc(request):
          return render(request, 'test_esc.html',
                        {
                            't1': '<h1>hello</h1>'
                        })
      模板代码:
      {{t1}}
    
  • 转义内容

      <: 转换为&lt;
      >: 转换为&gt;
      ': (单引号) 会转换为&#39;
      ": (双引号)会转换为 &quot;
      &: 转换为 &amp;
    
  • 当显示不被信任的变量时使用escape过滤器,一般省略,因为Django会自动转义

      {{to_esc|escape}}
    
  • 关闭转义

    • 对于变量使用safe过滤器

        {{ data|safe }}
      
    • 对于代码块使用autoescape标签

         { % autoescape off %}
         {{ body }}
         { % endautoescape %}
      
    • 标签autoescape接受on或者off参数

    • 自动转义标签在base模板中关闭,在child模板中也是关闭的

  • 字符串字面值

    • 手动转义

      { { data|default:"<b>123</b>" }}
      
    • 应写为

         { { data|default:"&lt;b&gt;123&lt;/b&gt;" }}
      

7.8. CSRF

  • 全称Cross Site Request Forgery,跨站请求伪造

  • 某些恶意网站上包含链接、表单按钮或者JavaScript,它们会利用登录过的用户在浏览器中的认证信息试图在你的网站上完成某些操作,这就是跨站攻击

  • 演示csrf如下

    • 创建视图csrf1用于展示表单,csrf2用于接收post请求

        def csrf1(request):
            return render(request,'booktest/csrf1.html')
        def csrf2(request):
            uname=request.POST['uname']
            return render(request,'booktest/csrf2.html',{'uname':uname})
      
    • 配置url

        url(r'^csrf1/$', views.csrf1),
        url(r'^csrf2/$', views.csrf2),
      
    • 创建模板csrf1.html用于展示表单

        <html>
        <head>
            <title>Title</title>
        </head>
        <body>
        <form method="post" action="/crsf2/">
            <input name="uname"><br>
            <input type="submit" value="提交"/>
        </form>
        </body>
        </html>
      
    • 创建模板csrf2用于展示接收的结果

        <html>
        <head>
            <title>Title</title>
        </head>
        <body>
        {{ uname }}
        </body>
        </html>
      
    • 在浏览器中访问,会出现Forbidden错误, 这是django默认对csrf做了防护,不受csrf 保护的页面禁止访问

    • 取消settings中的设置,可以取消掉csrf保护,一般调试的时候习惯取消掉

        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',
        ]
      

7.8.1. 防csrf

  • django默认对csrf攻击提供了基础保护
    如果因为调试关闭了CSRF防护,只要打开就可以

    • step1:在settings.py中启用’django.middleware.csrf.CsrfViewMiddleware’中间件,此项在创建项目时,默认被启用

    • step2:在csrf1.html中添加标签

        <form>
        {% csrf_token %}
        ...
        </form>
      
  • CSRF攻击将会最大限度收到拒绝

7.8.2. 取消csrf

  • 如果某些视图不需要保护,可以使用装饰器csrf_exempt,模板中也不需要写标签,修改csrf2的视图如下

      from django.views.decorators.csrf import csrf_exempt
    
      @csrf_exempt
      def csrf2(request):
          uname=request.POST['uname']
          return render(request,'booktest/csrf2.html',{'uname':uname})
    
  • 运行上面的两个请求,发现都可以请求

7.8.3. 保护原理

  • 加入标签后,可以查看源代码,发现多了如下代码

      <input type='hidden' name='csrfmiddlewaretoken' value='nGjAB3Md9ZSb4NmG1sXDolPmh3bR2g59' />
    
  • 在浏览器的调试工具中,通过network标签可以查看cookie信息,自动添加了csrftocken在cookie中

  • 查看跨站的信息,并没有cookie信息,即使加入上面的隐藏域代码

  • 当提交请求时,中间件’django.middleware.csrf.CsrfViewMiddleware’会对提交的cookie及隐藏域的内容进行验证,如果失败则返回403错误

  • 结论:因为采用cookie验证,django的csrf不是完全的安全,只属于简单防护。

7.9. 管理静态模板

  • 项目中的CSS、图片、js都是静态文件

  • 静态文件的存储需要在settings中设置

  • 配置静态文件

    • 在settings 文件中定义静态内容

        STATIC_URL = '/static/'
        STATICFILES_DIRS = [
            os.path.join(BASE_DIR, 'static'),
        ]
      
    • 在项目根目录下创建static目录,再创建当前应用名称的目录

        mysite/static/myapp/
      
    • 在模板中可以使用硬编码

        /static/my_app/myexample.jpg
      
    • 在模板中可以使用static编码

        { % load static from staticfiles %}
        <img src="{ % static "my_app/myexample.jpg" %}" alt="My image"/>