美文网首页
Django之路(三) - 模板详解

Django之路(三) - 模板详解

作者: Zoulf | 来源:发表于2018-03-01 15:29 被阅读0次

    模板

    模板就是一个简单的文本文件,它可以生成任何文本格式(HTML,XML,CSV等)。

    模板中包含变量和标签,当模板被执行时,变量会被赋值,而标签是控制模板的逻辑的。

    下面是个演示了一些基础知识的最小模板,稍后会讲解其中的每个元素。

    {% 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 %}
    
    

    tips:
    为什么我们要用基于文本的而不是基于XML的模板呢?我们是想让Django的模板语言用在更多的地方而不是仅仅在XML或HTML中。在联网的世界里,我们把它用在电子邮箱,JavaScript以及CSV。你可以在任何文本格式中使用模板语言。

    变量

    变量看起来像这样:{{ 变量名 }},当模板引擎遇到变量时,会计算这个变量,并把结果赋值给它。变量名是由任何的数字,字母以及下划线组成。点(“.”)也会出现在变量部分,当然他有特殊用途,稍后会说明。特别注意的是,变量名中不能出现空格符以及任何的标点符号。

    上面说到点(“.”)也会出现在变量部分,它的作用就是用来访问变量的属性的。
    严格来讲,当模板系统遇到点时,它会根据以下的顺序查找:

    1. 字典查找
    2. 属性或方法查找
    3. 数字索引查找

    如果结果的值是可调用,那么它就会被无参数调用。调用的结果就会变成模板中的值。

    这个查找顺序会对那些覆写了字典查找的对象造成一些不可预期的行为。比如下面是遍历collections.defaultdict的代码片:

    {% for k, v in defaultdict.iteritems %}
       Do something with k and v here...
    {% endfor %}
    

    因为先发生字典查找,所以这不可预期的行为就发生了,它会返回defaultdict的默认值而不会调用它的iteritems()方法。在这个例子中,可以先转化成字典。

    在上面的模板例子中,{{ section.title }}会被section对象的title属性代替。

    如果你调用的变量不存在,那么模板系统将默认插入空字符串('')

    tips:如果模板的上下文(context)中传入“bar”变量,而模板中存在{{ foo.bar }}表达式,那么该表达式仅仅具有字面意思,即调用foo的bar属性,而不会调用context中“bar”的值。

    过滤器(FIlters)

    过滤器看起来就像这样{{ name|lower }}。这是将name变量通过lower过滤器全部转换为小写字母。|用来调用过滤器。

    过滤器可以是链式的,一个过滤器的输出会被用在下一个。{{ text|escape|linebreaks }} 是一种通用的形式用来将文本内容转义,然后再转为<P>标签以适用HTML。

    有些过滤器带参数。如以下带参过滤器:{{ bio|truncatewords:30 }}意思是只展示bio变量的前30个字符。

    如果过滤器参数含有空格那么参数必须被引号引起来;比如想要用逗号和空格将list中的元素拼接起来,你可以这样{{ list|join:", "}}

    Django提供了大约60个内置模板过滤器。你可以到built-in filter reference.查阅更多的过滤器。下面是一些常用的过滤器:

    # default,如果一个变量是false或者是空的,那么它的值就是你给的默认值,否则就是变量本身的值。
    {{ value|default:"nothing" }}
    如果变量value没有提供,或者是空的话,上面就会显示nothing。
    
    # length,返回变量的长度。这对于字符串和列表都起作用。
    {{ value|length }}
    如果 value是一个列表['a','b','c','d'],那么输出就是4。
    
    # filesizeformat,将文件大小转换为符合人们阅读习惯的格式
    # 如:'13 KB','4.1 MB','102 bytes'等。
    {{ value|filesizeformat }}
    如果value是123456789的话,那么输出将会是117.7MB。
    

    自定义 simple_tag

    a、在app中创建templatetags模块
    b、创建任意 .py 文件,如:xx.py

    #!/usr/bin/env python
    #coding:utf-8
    from django import template
    from django.utils.safestring import mark_safe
       
    register = template.Library()
       
    @register.simple_tag
    def my_simple_time(v1,v2,v3):
        return  v1 + v2 + v3
       
    @register.simple_tag
    def my_input(id,arg):
        result = "<input type='text' id='%s' class='%s' />" %(id,arg,)
        return mark_safe(result)
    

    c、在使用自定义simple_tag的html文件中导入之前创建的 xx.py 文件名

    {% load xx %}
    

    d、使用simple_tag

    {% my_simple_time 1 2 3%}
    {% my_input 'id_username' 'hide'%}
    

    e、在settings中配置当前app,不然django无法找到自定义的simple_tag

    INSTALLED_APPS = (
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        # 配置当前app
        'app01',
    )
    

    tips: Django 的admin交互界面使用了所有的模板标签和过滤器。

    标签(Tags)

    标签的形式是{% tag %}。标签比变量要更复杂:有些标签在输出中创建文本,有些通过展示循环和逻辑来控制流,有些引入额外的信息到模板中以供后续变量使用。

    有些标签需要开头和结尾(比如:{% tag %}...标签内容...{% endtag %}

    Django大约有两打(24个)内置模板标签。你可以查阅这篇文档built-in tag reference.。以下仅列举一些常用标签:

    for,循环遍历数组或列表,比如展示名为athete_list的运动员列表:
    <ul>
    {% for athlete in athlete_list %}
        <li>{{ athete.name }}</li>
    {% endfor %}
    </ul>
    
    # 想要知道for循环了多少次记数
    {% for row in list %}
        {{  forloop.counter }} # 计数器从1开始,循环一次记一次,常用于表格的序号,即 1 2 3..
        {{  forloop.counter0 }} # 计数器从0开始,循环一次+1,即 0 1 2
        {{  forloop.revcounter }} # 计数器倒序进行,即 3 2 1
        {{  forloop.recounter0 }} # 2 1 0
        {{  forloop.lastloop }} # 判断是否是最后一次循环,返回的是Boolean
        {{  forloop.firstloop }} # 判断是否是第一次循环,返回的是Boolean
    {% endfor %}
    
    if,elif,else,
    {% if athlete_list %}
        Number of athletes: {{ athete_list|lengh }}
    {% elif athlete_in_locker_room_list %}
        Athletes should be out of the locker room soon!
    {% else %}
        No athletes.
    {% endif %}
    如上所示,如果athlete_list列表不为空,则会通过{{ athlete_list|length }}变量来显示列表长度。
    否则,如果athlete_in_locker_room_list列表不为空,则显示“Athletes should ...!”信息。
    如果两个列表都为空,则显示“”No athletes.”。
    
    你可以在if标签中使用过滤器以及进行一系列的操作:
    {% if athlete_list|length > 1 %}
       Team: {% for athlete in athlete_list %} ... {% endfor %}
    {% else %}
       Athlete: {{ athlete_list.0.name }}
    {% endif %}
    当上面的代码运行时,注意大部分模板过滤器是以字符串类型返回的,
    所以上面过滤器返回的length是无法与数字1进行比较的。
    
    block,extend,用于模板继承(下面会讲到),一种给力的方法可以在模板中减少样板的使用。
    
    注释,可以用{# #}来注释模板中的某一行。
    如下,下面只有“hello”会在模板中起作用。
    {# greeting #}hello
    注释中可以包含任何模板语句,如下:
    {# {% if foo %}bar{% else %} #}
    上述的注释语句只能用于单行注释({ # # }该注释语句中不允许存在多行)。
    

    模板继承

    在Django模板引擎中最给力也是最复杂的部分就是模板继承。模板继承可以使你建立一个包含网站所有公共元素的基本骨架,在里面可以定义一些区块,模板的子模板可以重写这些区块。

    下面的例子可以让你更好的理解模板继承:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <link rel="stylesheet" href="style.css" />
        <title>{% block title %}My amazing site{% endblock %}</title>
    </head>
    
    <body>
        <div id="sidebar">
            {% block sidebar %}
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/blog/">Blog</a></li>
            </ul>
            {% endblock %}
        </div>
    
        <div id="content">
            {% block content %}{% endblock %}
        </div>
    </body>
    </html>
    

    这个模板我们将它取名为base.html,定义了一个简单的HTML骨架文档,在一个简单的两列布局的页面你也许会用到它。子模板要做的就是将内容填充到空的区块中。

    在这个例子中,标签block定义了三个可以让子模板重写的区块。每个block标签都会告诉模板引擎,子模板可能会重写该区块。

    下面我们定义了一个子模板:

    {% extends "base.html" %}
    
    {% block title %}My amazing blog{% endblock %}
    
    {% block content %}
    {% for entry in blog_entries %}
        <h2>{{ entry.title }}</h2>
        <p>{{ entry.body }}</p>
    {% endfor %}
    {% endblock %}
    

    其中关键的地方是extends标签,这可以告诉模板引擎该模板是继承于其他模板。这样当模板系统执行该模板时,就可以先定位他的父模板,上面模板的父模板就是“base.html”。

    在该例中,模板引擎会注意到在base.html中有三个block标签,然后会将子模板的内容填充到父模板相应的区块中。最后的结果如下({% block content %}区块里具体的内容是由blog_entries 变量决定的):

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <link rel="stylesheet" href="style.css" />
        <title>My amazing blog</title>
    </head>
    
    <body>
        <div id="sidebar">
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/blog/">Blog</a></li>
            </ul>
        </div>
    
        <div id="content">
            <h2>Entry one</h2>
            <p>This is my first entry.</p>
    
            <h2>Entry two</h2>
            <p>This is my second entry.</p>
        </div>
    </body>
    </html>
    

    注意到,我们没有在子模板中定义sidebar区块,但还是有内容,没错这是从父模板中来的。因此父模板的{%block%}标签中的内容总是作为默认值,,即假如子模板没有重写该区块,那么就默认使用父模板中的内容。

    你可以根据需要采用多层继承,一般来讲我们采用的是三层继承:

    • 首先创建一个包含网站主体外观的基础模板base.html
    • 为每部分的网页创建名为base_部分名称.html的模板,比如,base_news.html,base_sports.html。这些模板都是继承base.html模板,并且都有自己的设计样式。
    • 为每一种类型的网页创建个性化模板,比如新闻或者博客,它们都是继承上述相应的部分模板

    上述的继承方案可以极大化的重用你的代码,并且方便扩展。

    以下是使用模板继承需要注意的地方:

    • 如果你要在模板标签中使用{% extends %},那么它必须作为第一个模板标签,否则模板继承将失效。
    • 在你的基础模板中多使用{ % block % }。记住,在子模板中不一定都要重写父模板的block标签,所以你可以在父模板中多定义些block,在里面填充合理的默认值,这样在子模板中你可以根据需要重写相应的block,其他的使用父模板的默认值就可以了。俗话说,有备无患吗。
    • 如果你发现在很多模板中有相同的内容,那么你可以将这些相同的内容提取出来放在父模板的block中。
    • 如果你想获取父模板block中的内容,你可以使用变量{{ block.super }}。如果你想要增加父模板block的内容,而不是重写它,那么这个变量将会非常有用。使用该变量插入的数据不会被自动转义(下面会提到),因为如果有必要的话它已经在父模板中被转义了。
    • 在{ % block % }标签外面用as标签创建的变量是无法在{ % block % }内部使用的,如下,该模板不会显示任何内容:
    {% trans "Title" as title %}
    {% block content %}{{ title }}{% endblock %}
    
    • 另外,你可以给 {% endblock %}标签取任意名字,比如:
    {% block content %}
    ...
    {% endblock content %}
    

    在很大的模板中,这可以让你知道哪个block标签正在关闭。

    最后,注意你不能在同一模板中定义相同名字的block标签,这是因为block标签是双向作用的,block标签不仅仅提供区域让子模板填充,它也可以在父模板中填充相应的内容。如果在模板中有相同名字的block标签,那么,父模板将不知道到底使用哪个block中的内容来填充。

    HTML自动转义

    当通过模板生成HTML时,有个风险,就是变量中可能存在一些特殊字符会影响HTML的结果。比如,如下模板片段:

    hello, {{ name }}
    

    这从表面上看上去没什么问题,但是你需要考虑到,如果用户的变量name输入以下内容会发生什么:

    <script>alert('hello')</script>
    

    如果name变量是以上内容,那么最后HTML中会是这样的结果:

    Hello, <script>alert('hello')</script>
    

    ...这意味着浏览器会弹出一个脚本对话框!

    类似的,如果name变量包含‘<’字符呢,比如这样:

    <b>username
    

    这会导致网页的剩余部分加粗。

    显然,用户提交的数据是不能盲目信任的,并且不能直接插入到网页中,因为会有恶意的用户利用这种漏洞来干坏事。这种安全漏洞叫做跨站脚本攻击(XSS)。

    为了避免这个问题,你有两种选择:

    • 第一种,你可以利用escape过滤器来运行每个不被信任的变量,这会把有潜在危险的字符转换为安全的。当然在Django推出的头几年,这是一种默认的解决方法。但是存在一个问题,你需要确保每个变量都被转义,显然我们很容易漏掉一些数据。

    • 第二种,你可以利用Django的HTML自动转义机制。这部分的剩余部分会讲解自动转义是如何工作的。

    在Django中,每个模板都会默认转义每个变量,以下是五个比较特别的字符转义。

    < 会被转义成<
    > 会被转义成>
    '(单引号)会被转义成'
    " (双引号)会被转义成"
    & 会被转义成&
    

    再强调一遍,这种自动转义行为是默认的,当你在使用Django的模板系统时你是受保护的。

    如何关闭自动转义

    如果你不想要数据被自动转义,比如你不想要某个网页,或者某个模板亦或者某个变量被转义的话,你有几个方法来关闭它。

    为什么你会想关闭自动转义呢?因为有时候你想把原生的HTML语句嵌入到模板变量中,在这种情况下,你是不会想要将变量里的内容转义的。举个例子,你可能在数据库中存了一点HTML,你想把它直接嵌入到模板中。或者,你可能正在用Django的模板系统生成非HTML文本,比如电子邮箱信息。

    关闭变量的自动转义

    可以用safe过滤器来关闭单个变量的自动转义:

    这会被转义: {{ data }}
    这不会被转义: {{ data|safe }}
    

    关闭模板区块的自动转义

    为了控制模板的自动转义,需要在外面套一层autoescape标签,比如:

    {% autoescape off %}
        Hello {{ name }}
    {% endautoescape %}
    

    你可以用on或者off来控制是否自动转义,如下例子:

    Auto-escaping is on by default. Hello {{ name }}
    
    {% autoescape off %}
        This will not be auto-escaped: {{ data }}.
    
        Nor this: {{ other_data }}
        {% autoescape on %}
            Auto-escaping applies again: {{ name }}
        {% endautoescape %}
    {% endautoescape %}
    

    自动转义标签会通过继承来传递它的影响,包括include标签。举例:

    以下是base.html
    {% autoescape off %}
    <h1>{% block title %}{% endblock %}</h1>
    {% block content %}
    {% endblock %}
    {% endautoescape %}
    
    以下是child.html
    {% extends "base.html" %}
    {% block title %}This & that{% endblock %}
    {% block content %}{{ greeting }}{% endblock %}
    

    由于在base.html中自动转义是关闭的,在他的子模板child.html中也将是关闭的。如果greeting变量传入字符串<b>Hello!</b>,那么模板最终的结果将会是如下:

    <h1>This & that</h1>
    <b>Hello!</b>
    

    注意事项

    通常来讲,我们不用过多的担心自动转义。只有在写views时或者自定义过滤器时需要注意自动转义,并合理的标记数据。所以尽情的使用模板吧。

    如果你创建的模板在被使用时无法确定自动转义是否打开,你可以添加escape过滤器到每个需要转义的变量上。当自动转义已开启,escape过滤器对已转义的变量进行再次转义并不会对该变量产生影响。

    转自蛇发女妖

    相关文章

      网友评论

          本文标题:Django之路(三) - 模板详解

          本文链接:https://www.haomeiwen.com/subject/ijsgxftx.html