zoukankan      html  css  js  c++  java
  • django-templates

    模板内容总结:

    1.基础

    2.locals
    3.include
    4.模板继承
    5.使用RequestContext对上下文内容进行重用
    6.HTML代码自动转义(auto-escaping)
    7.扩展模板系统
    Template加载机制
    扩展你的模板系统
        - 创建模板库
        - 实现自定义过滤器
            - 1. 创建register变量
            - 2. 定义过滤器函数
            - 3. 注册过滤器函数
        - 实现自定义tag
            - 了解模板编译过程
            - 创建tag实战
                - 1. 定义Node节点类,实现render方法
                - 2. 创建Compilation函数
                - 3. 注册tag
                - 4. 运行
            - 复杂的实现自定义tag的其他几种方法
                - 1. 在Node类的render函数中设置context
                - 2. 实现块作用区域的tag
                - 3. 在块作用tag中保留context内容
                - 4. 快速创建简单tag的方法
                - 5. 创建Inclusion Tag
    创建自定义模板加载类
     
    模板使用基础
     
    模板基本由两个部分组成,一是HTML代码,二是逻辑控制代码。
    逻辑控制的实现又基本由三个部分组成:
    1. 变量的使用
    {{ person_name }} #使用双大括号来引用变量
    2. tag的使用
    {% if ordered_warranty %} #使用大括号和百分号的组成来表示使用Django提供的template tag
    {% for item in item_list %}
    <li>{{ item }}</li>
    {% endfor %}
    3. filter的使用
    {{ ship_date|date:"F j, Y" }},ship_date变量传给data过滤器,data过滤器通过使用
    "F j, Y"这几个参数来格式化日期数据。"|"代表类似Unix命令中的管道操作。
    Template system不仅仅可以和view进行合作,也可以自己独立使用。
    最基本的使用方法是:
    1. 使用模板代码字符串作为参数,创建一个Template类
    2. 创建模板代码中所需要的上下文Context对象,包含所需要的各个引用参数的值
    3. 调用Template.render()方法,把template渲染成一个完整的字符串。
    >>> from django import template
    >>> t = template.Template('My name is {{ name }}.')
    >>> c = template.Context({'name': 'Adrian'})
    >>> print t.render(c)
    >>> My name is Adrian.
     
    还可以在template代码中使用dict索引,然后在context传入所需要的dict
    >>> from django.template import Template, Context
    >>> person = {'name': 'Sally', 'age': '43'}
    >>> t = Template('{{ person.name }} is {{ person.age }} years old.')
    >>> c = Context({'person': person})
    >>> t.render(c)
    u'Sally is 43 years old.'
     
    还可以使用函数,不过只能使用无参数的函数
    >>> from django.template import Template, Context
    >>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}')
    >>> t.render(Context({'var': 'hello'}))
    u'hello -- HELLO -- False'
     
    还可以使用列表索引,但是item.-1是不被允许的
    >>> from django.template import Template, Context
    >>> t = Template('Item 2 is {{ items.2 }}.')
    >>> c = Context({'items': ['apples', 'bananas', 'carrots']})
    >>> t.render(c)
    u'Item 2 is carrots.'
     
    以上的使用方法被称为Dot Lookup方法。
     
    使用dot lookup的访问函数功能时,需要注意的问题:
    1. 当在模板代码中执行的函数抛出异常时,会一直向上层传播,除非这个异常中有一个参数
    silent_variable_failure=True; 这样的话,这个出错的函数信息就会被渲染成空字符串。
    >>> class SilentAssertionError(AssertionError):
    ... silent_variable_failure = True
    >>> class PersonClass4:
    ... def first_name(self):
    ... raise SilentAssertionError
    >>> p = PersonClass4()
    >>> t.render(Context({"person": p}))
    u'My name is .'
     
    2. 很明显,调用函数会产生一些不好的结果,安全漏洞之类的,如果你有一个BankAccout,
    然后在模板中写成{{ account.delete }}, 这样在渲染的时候,你的账号就被删除了。。。。
    所以要在修改一下你的delete函数
    def delete(self):
    # Delete the account
    delete.alters_data = True#缩进没有问题,把delete看成一个对象,设置它的alters_data属性。
    这样在渲染的时候,就会变成failed silent。
     
    当在渲染的时候,简单的key值没有找到时,会failed silent,变成空字符串,而不是大动干戈的
    报错。
    >>> from django.template import Template, Context
    >>> t = Template('Your name is {{ name }}.')
    >>> t.render(Context())
    u'Your name is .'
    >>> t.render(Context({'var': 'hello'}))
    u'Your name is .'
     
    Context对象也可以进行增删改值的操作。
    >>> from django.template import Context
    >>> c = Context({"foo": "bar"})
    >>> c['foo']
    'bar'
    >>> c['newvariable'] = 'hello'
    >>> del c['foo']
    >>> c['foo']
     
    使用python manage.py shell启动python交互式命令行窗口与一般直接启动python自带的
    交互式命令行窗口的区别是前者会通过找一个DJANGO_SETTINGS_MODULE环境变量,
    告诉Django导入settings.py的配置信息。
     

    基本的tag和filter的用法

    tag:
    • {% if %}的使用
    可以使用and, or, not来组织你的逻辑。但不允许and和or同时出现的条件语句中。
    没有{% elif %}这样的用法,只能用嵌套来实现多重if语句。
    {% if athlete_list %}
    <p>Here are the athletes: {{ athlete_list }}.</p>
    {% else %}
    <p>No athletes are available.</p>
    {% if not coach_list %}
    <p>Here are the coaches: {{ coach_list }}.</p>
    {% endif %}
    {% endif %}
    • {% for %} 的使用
    用来循环一个list,还可以使用resersed关键字来进行倒序遍历,一般可以用if语句来先
    判断一下列表是否为空,再进行遍历;还可以使用empty关键字来进行为空时候的跳转。
    {% for athlete in athlete_list resersed %}
    <li>{{ athlete.name }}</li>
    {% empty %}
    <p>There are no athletes. Only computer programmers.</p>
    {% endfor %}
     
    for tag还提供了一些内置参数来提供模板循环的信息。
    1. forloop.counter 当前循环计数,从1开始
    {% for item in todo_list %}
    <p>{{ forloop.counter }}: {{ item }}</p>
    {% endfor %}
    2. forloop.counter0 当前循环计数,从0开始,标准索引方式
    3. forloop.revcounter 当前循环的倒数计数,从列表长度开始
    4. forloop.revcounter0 当前循环的倒数计数,从列表长度减1开始,标准
    5. forloop.first bool值,判断是不是循环的第一个元素
    6. forloop.last 同上,判断是不是循环的最后一个元素
    7. forloop.parentloop 用在嵌套循环中,得到parent循环的引用,然后可以使用以上的参数
    {% for country in countries %}
    <table>
    {% for city in country.city_list %}
    <tr>
    <td>Country #{{ forloop.parentloop.counter }}</td>
    <td>City #{{ forloop.counter }}</td>
    <td>{{ city }}</td>
    </tr>
    {% endfor %}
    </table>
    {% endfor %}
    • ifequal和ifnotequal,一看就是直接比较值的tag,需要两个参数,用法比较有限,
    • 只限于字符串,整数,小数的比较,什么列表,字典,元组不支持。
    {% ifequal user currentuser %}
    <h1>Welcome!</h1>
    {% ifequal section "community" %}
    <h1>Community</h1>
    {% endifequal %}
    {% endifequal %}
    • {# #},模板中注释的用法,只能用在一行
    • 如果要使用多行注释,要使用{% comment %}
    {# This is a comment #}
    {% comment %}
    This is a
    multi-line comment.
    {% endcomment %}
    filter:
    filter用于变量在显示之前的一些简单的处理。使用类似管道的操作符"|",也可以进行链式操作
    {{ name|lower }}
    {{ my_list|first|upper }}
    {{ bio|truncatewords:"30" }}
    介绍几个重要的filter:
    • addslashes :给任何的反斜线,单引号,双引号,再加一个反斜线。在文本中含有javascript字符串的时候有用。
    • date :用来对data和datatime对象的字符串信息进行格式化。
    • {{ pub_date|date:"F j, Y" }}
    • length :返回变量的长度。

    在view中使用template:

    首先在settings.py中配置模板文件的路径。
    TEMPLATE_DIRS = (
    '/home/django/mysite/templates',
    )
    记住一个路径的时候要使用逗号,这样是来区分是一个tuple还是一个block expression
    也可以在设置的时候使用python文件路径操作代码:
    import os.path
     
    TEMPLATE_DIRS = (
    os.path.join(os.path.dirname(__file__), 'templates').replace('\','/'),
    )
     
    然后,可以在view中使用模板
    from django.template.loader import get_template
    from django.template import Context
    from django.http import HttpResponse
    import datetime

    def current_datetime(request):
    now = datetime.datetime.now()
    t = get_template('current_datetime.html')
    html = t.render(Context({'current_date': now}))
    return HttpResponse(html)
     
    大多数情况下,你会使用一种shortcut方法,render_to_response()去完成以上的工作。
    from django.shortcuts import render_to_response
    import datetime

    def current_datetime(request):
    now = datetime.datetime.now()
    return render_to_response('current_datetime.html', {'current_date': now})
     

    模板渲染

    一旦你创建一个 Template 对象,你可以用 context 来传递数据给它。 一个context是一系列变量和它们值的集合。

    context在Django里表现为 Context 类,在 django.template 模块里。 她的构造函数带有一个可选的参数: 一个字典映射变量和它们的值。

    locals()的小技巧
    如果你有很多变量要传给render,一个一个构造dict元素很麻烦。直接把变量名改成模板中所需要的变量名,
    再使用locasl()函数,轻松搞定
    def current_datetime(request):
    current_date = datetime.datetime.now()
    return render_to_response('current_datetime.html', locals())
    locals()会返回局部空间中变量信息的dict,直接可以传给render,但有一点需要注意,它会返回把有的局部变量
    信息,有一些可能不需要用到,如request变量。
     
    {% include %}的使用
    {% include 'nav.html' %},用来引入其它模板的内容,减少重复的模板代码
    {% include template_name %},还可以使用变量名
    如果include的模板文件没有找到,当DEBUG为真时,会报错TemplateDoesNotExist,当为假时,页面那一块为空白。 
    诚然,include可以有效减少模板的重复代码。但一种更优雅的方式是:
    template inheritance.
     
    首先,创建base.html
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
    <html lang="en">
    <head>
    <title>{% block title %}{% endblock %}</title>
    </head>
    <body>
    <h1>My helpful timestamp site</h1>
    {% block content %}{% endblock %}
    {% block footer %}
    <hr>
    <p>Thanks for visiting my site.</p>
    {% endblock %}
    </body>
    </html>
     
    我们使用一个新的tag,{% block %}用来告诉template engine,这个部分会被子模板
    来实现。如果子模板没有实现这些部分,就会默认使用父模板的代码。
     
    再看看,子模板要怎么写:
    {% extends "base.html" %} 
    {% block title %}The current time{% endblock %}
    {% block content %}
    <p>It is now {{ current_date }}.</p>
    {% endblock %}
    只需要先使用{% extends %}继承父模板,再把相应需要实现的部分写上所需要的内容。
    {% extends template_name %}也可以使用变量名来实现动态。
    jumpserver对模板的使用堪称典范
     
    模板继承的三层继承策略:
    1. 创建一个base.html,用来设置外观
    2. 为网站的每一个部分,创建base_SECTION.html,比如base_phote.html, base_forum.html
    3. 为每一个页面创建自己的模板。
     
    1.模板继承
    #base.html
    <html><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <link type="text/css" rel="stylesheet" href="/static/css/base.css"/>
    <title>{%block title %} {%endblock%}</title>
    <body>
    <h1>后台管理</h1><p class="path"> 当前位置:{%block path%}{%endblock%} <span>欢迎你:{{admin}} <a href="#">注销</a></span></p> {%block content%}{%endblock%} </body>
    </html>

    #piclist.html

    {%extends "base.html"%}
    {%block title %}图片列表页面{%endblock%}
    {%block path%}图片列表{%endblock%}
    {%block content%}
    内容
    {%endblock%}
    模板学习笔记

    1.模板的简单使用:

    from django import template
    t = template.Template('My name is {{ name }}.')
    c = template.Context({'name':'nick'})
    print t.render(c)

    创建一个Template对象,实例化。默认构造参数 直接接受字符串作为模板内容。

    其中{{}}表示模板的变量。

    一旦你创建了Template模板实例,你可以用Context来传递参数给它。

    格式 Context({'name':'nick'}) 第一个参数为模板变量的映射,第二个为要传递的值。并以 “:”分开,如果是多个集合用“,”分隔。

    最后调用Template对象的 render()方法,并用context对象来填充模板。 

    同一个模板对象,可对应多个上下文对象。 

    如果遍历一个结构对象,可以使用如下方法。

    from django.template import Template,Context
    person = {'name':'sally','age':'43'}
    t = Template('{{ person.name }} is {{ person.age }} years old.')
    c = Context({'person':person})
    t.render(c)

    在模板的方法调用中,有一个操作流程是需要注意的。 

    在查找方法的过程中,如果方法抛出了一个异常,除非该异常有一个silent_variable_failure属性,别且值为true,否则它将继续传播。

    举例:例如你有一个 BankAccount 对象,有一个Del()方法。

    如果某个模板包含了 {{ account.del }} 的标签,account是BankAccount的一个实例。这个模板载入时,account对象被删除。

    防止这样的事情发生,必须设置 alters_data函数.

    def Del(self)
          Del.alters_data = True

     模板不会执行任何以该方式进行标记的方法。

    模板中得注释,使用 {#  注释内容  #} 该注释不能跨行。

    如果跨行,需要使用模板标记 

    {% comment %} 

    注释内容

    {% endcomment %}
     

    ===================================================================================== 

    2.在视图中使用模板

    模板加载机制

    打开settings.py文件,找到TEMPLATE_DIRS这项设置。

    添加一个用于存放模板的路径,如下:

    TEMPLATE_DIRS = ( 'C:/www/django/templates', )

    如果考虑到以后部署等问题,可以使用动态的路径方法如:

    import os.path

    TEMPLATE_DIRS = ( os.path.join(os.path.dirname(__file__), 'templates').replace('\','/'), )

    设置好模板路径后 

    在项目路径下,创建文件夹  templates 

    1.在文件下创建一个HTML页面文件template1.html,内容如下: 

    <html><body>现在的时间是: {{ nowtime }}.</body></html>

    2.然后创建一个 视图函数 代码如下 

    from django.http import HttpResponse
    from django.template import Template,Context
    from django.template.loader import get_template
    import datetime

    def current_datetime(request):
           now = datetime.datetime.now()
           t = get_template('template1.html')
           c = Context({'nowtime':now})
          html  = t.render(c)
          return HttpResponse(html)

    运行SERVER,看效果吧。

    再记录个 简化模板处理的方法。

    from django.shortcuts import render_to_response
    import datetime
    def current_datetime(request):
           now = datetime.datetime.now()
           return render_to_response('template1.html',{'nowtime':now}) 

    ========================================================================================

    include 模板标签

    {% include %} 允许在模板中包含其它模板的内容。

    标签的参数可以是:模板名称 、变量、字符串。

    {% include 'nav.html' %} 

    {% include 'includes/nav.html' %}

    {% include template_name %}

    模板继承

    本质上说:模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点共用部分和定义进行重载。 

    创建 base.html模板:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
    <html>
        <head>
            <title>{% block title %}{% endblock %}</title>
        </head>
        <body>
            <h1>我得时间表</h1>
            {% block content %}{% endblock %}
            {% block footer %}
            <hr>
            <p>谢谢对网站的支持</p>
            {% endblock %}
        </body>
    </html>

    创建子模板  test.html

    {% extends "base.html" %}
    {% block title %}现在时间{% endblock %}
    {% block content %}
    <p>现在时间是:{{nowtime}}</p>
    {% endblock %}

     省略 视图函数。

     因为子模板没有定义 footer 块。所以使用父模板中定义的值。

    你可以根据需要使用任意多的继承次数,使用继承的一种常见方式如下三层法则。

    1.  创建base.html模板,在其中定义站点的主要外观感受。这些都是不常修改甚至从不修改的内容。

    2.  为网站的每个区域创建 base_SECTION.html模板(例如,base_photos.html和base_forum.html) 。

         这些模板对base.html进行拓展,并包含区域特定的风格与设计。

    3.  为每种类型的页面创建独立的模板,例如论坛页或图片库。这些模板扩展相应的区域模板。

    使用模板的一些诀窍

    •  如果在模板中使用{% extends %},必须保持其为模板中的第一个模板标记。否则,模板继承将不起作用。
    •  一般来说,基础模板中得{% block %}标签越多越好。子模板不必定义父模板中所有的代码块,因此你可以 
       合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行重载。
    •  如果发现自己在多个模板之间拷贝代码,你应该考虑将该代码放置到父模板的某个 {% block %}中。
    •  如果你需要访问父模板中块的内容,使用{{ block.super }}这个标签。
    •  主要不要在同一个模板中定义同名的{% block %}.
    • {% extends %}对所传入模板名称使用的加载方法和get_template()相同。
    • 多数情况下,{% extends %}的参数应该是字符串,但如果想实现动态的父模板名,这个参数可以是变量。
     

    复习一下模板语言的用法

    {# 模板tag的用法 #}
    {% if done %}
    <strong>Over</strong>
    {% else %}
    <strong>wait</strong>
    {% endif %}
    {# 模板变量的用法 #}
    Now is {{ nowtime }}
    在views.py中使用模板的时候:
    1. 通过模板名,获得模板对象
    2. 创建context对象,类似字典,用于像模板提供变量实际的值
    3. 使用context对象进行模板的渲染,返回的是html网页的内容
     

    使用RequestContext对上下文内容进行重用

    当渲染一个模板的时候,我们通常使用的是django.template.Context的对象,
    这里要介绍另外一个它的子类,django.template.RequestContext,
    RequestContext提供了一种把不同的context内容中公共的部分提取出来的方法,让context的内容重用。
     

    1. Context版

     
    from django.template import loader, Context
    from django.http import HttpResponse

    def view_1(request):
        # ...
        t = loader.get_template('template1.html')
        c = Context({
            'app': 'My app',
            'user': request.user,
            'ip_address': request.META['REMOTE_ADDR'],
            'message': 'I am view 1.'
        })
        return HttpResponse(t.render(c))

    def view_2(request):
        # ...
        t = loader.get_template('template2.html')
        c = Context({
            'app': 'My app',
            'user': request.user,
            'ip_address': request.META['REMOTE_ADDR'],
            'message': 'I am the second view.'
        })
        return HttpResponse(t.render(c))
    可以看到两个context的内容有些是重复的。比如app, user, ip_address
    2. 下面改写成RequestContext版
    from django.template import loader, RequestContext
    from django.http import HttpResponse

    # 使用context processro去提供一些context内容中公共的部分,也就是返回一个字典而已。
    def custom_proc(request):
        "A context processor that provides 'app', 'user' and 'ip_address'."
        return {
            'app': 'My app',
            'user': request.user,
            'ip_address': request.META['REMOTE_ADDR']
        }

    def view_1(request):
        # ...
        t = loader.get_template('template1.html')
        # 创建方式不同,需要提供的参数有三个,request对象,字典类型,processors可选 
        c = RequestContext(request, {'message': 'I am view 1.'},
                processors=[custom_proc])
        return HttpResponse(t.render(c))

    def view_2(request):
        # ...
        t = loader.get_template('template2.html')
        c = RequestContext(request, {'message': 'I am the second view.'},
                processors=[custom_proc])
        return HttpResponse(t.render(c))
    可以看到所谓的context processors其实就是一个函数,参数为request,返回一个字典类型。这就是它所做的所有的事。在这里custom_proc返回的是包含共同的那三个参数的字典
     
    RequestContext构造函数中的第三个参数processors是可选的,可以是多个custom_proc函数的列表或是元组,在这里我们只传递了一个,可以为多个。
     
    return render_to_response('template2.html',
            {'message': 'I am the second view.'},
             context_instance=RequestContext(request, processors=[custom_proc]))
     
    结合RequestContext使用render_to_response函数直接返回HttpResponse对象

    当使用模板生成HTML代码时,如果变量内容是一些影响HTML结果的字符时,那就挺危险的。

    例如,模板内容如下:
    Hello {{ name }}
    当name的值为:
    <script>alert('hello')</script>
    渲染后的HTML结果就是:
    Hello <script>alert('hello')</script>
    以上的代码运行的结果就是会让浏览器弹出一个javascript的警告窗口。
     
    同理,如果name的值为<b>hanks,那么结果中Hello以后的所有内容就会被字体加粗,因为没有写</b>结束标记。
     
    这种攻击方式被称为跨站脚本攻击(Cross Site Scripting,CSS或者XSS),是一种站点应用程序的安全漏洞攻击,是代码注入的一种。它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了HTML以及用户端脚本语言。
    使用Django的模板自动转义功能。其实是默认开启的Django默认转义每一个变量的内容,尤其是下面5个字符:
    < 被转义成 &lt;
    > 被转义成 &gt;
    ' 被转义成 &#39;
    " 被转义成 &quot;
    & 被转义成 &amp;
     
    上面中的分号也是转义后的一部分。
     
    例如网页内容:
     
    页面源代码为:
     
    可以看到,转义只对变量的内容进行使用,模板本身的HTML代码不会被转义。
    如何关闭这个功能?
    为什么呢,有可能你有时就是想让变量的内容渲染成原始的HTML代码,所以不想被转义。比如你想让template系统产生文本内容而不是HTML,就像email信息一样。
     
    Django提供了三种方式关闭自动转义:变量级别,模板级别和站点级别。
    1. 变量级别
    使用safe这个过滤器对每一个变量进行禁用自动转义
    This will be escaped: {{ data }}
    This will not be escaped: {{ data|safe }}
    效果如下:
    网页内容
    页面源代码
    上面之所以没有显示出来,是HTML语法错误,这就是不转义的下场。。。
     
    2. 模板级别
    在模板中使用autoescape标签来控制,可以嵌套使用
    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 %}
    同时,autoescape标签的影响具有继承性,可以从父模板影响到子模板。
    # 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 %}
    继承之后的子模板,也都禁用了自动转义功能。如果greeting变量含有<b>,
    将不会被转义。
     
    需要注意的是,模板的作者不用去担心自动转义的使用。更多的python端的
    开发人员需要考虑哪些数据需要去转义,合理去使用这些数据。
    如果你创建了一个模板,而且不太清楚所应用的环境是否开启了自动转义功能。那就在所有
    的变量上加上escape过滤器,escape过滤器不会对已经escape的内容产生影响。
    对于filter过滤器中参数的自动转义
    Django中的自动转义功能不会对filter中的参数已作用,也就是说,最好在filter的参数中,自己
    写成转义后的代码,比如这种情况你应该手写成
    {{ data|default:"3 &lt; 2" }}
    而不是
    {{ data|default:"3 < 2" }}
    这是为了安全起见。
    扩展模板系统
     
    一般是扩展模板的tag和filter两个功能。可以用来创建你自己的tag和filter功能库。

    创建模板库

    分为两步:
    1. 首先决定由模板库在哪一个注册的app下放置,你可以放在一个已有的app目录下,也可以新建一个
    专门管理模板库的app,比如python manage.py startapp myTemplateLibrary。推荐后者,
    因为可以方便将来的重用。
     
    2. 在app目录下创建templatetags子目录,并在里面创建两个文件,__init__.py,用来声明这是一个
    包,另一个是你的tag/filter定义文件。比如myNewLibrary.py,那么在模板文件中可以这样使用:
    {% load myNewLibrary %}
     
    {% load %}只允许导入注册app目录下的模板库。这样做是为了保证你的模板库可以不被其它Django
    程序使用。

    实现自定义过滤器

    1. 创建register变量
    在你的模块文件中,你必须首先创建一个全局register变量,它是用来注册你自定义标签和过滤器的,
    你需要在你的python文件的开始处,插入几下代码:
    from django import template
    register = template.Library()
     
    2. 定义过滤器函数
    自定义的过滤器就是一个带1,2个参数的python函数,一个参数放变量值,一个用来放选项值。
    比如{{ var|remove:"bar" }}, var是变量值,"bar"是选项值,remove过滤器可以定义为:
    def remove(var, arg):
        #移除字符串中var的arg字串
        return var.replace(arg, '')
    过滤器函数应该总是返回一些信息,即使出错,也不应该抛出异常,可以返回默认值或者空字符串。
    不带参数的过滤器也很常见:
    def lower(value):
        "Converts a string into all lowercase"
        return value.lower()
    3. 注册过滤器函数
    #第一个参数是在模板中使用的过滤器的名字
    #第二个就是你的过滤器函数引用名
    register.filter('remove', remove)
    register.filter('lower', lower)
    python2.4以上版本,可以使用装饰符(decorator)功能
    @register.filter(name='remove')
    def remove(value, arg):
        return value.replace(arg, '')
    @register.filter
    def lower(value):
        return value.lower()
    如果装饰符不加name,则默认使用函数名来当作使用名。
    下面是完整代码:
    from django import template
    register = template.Library()
    @register.filter(name='remove')
    def remove(value, arg):
        return value.replace(arg, '')
    @register.filter
    def lower(value):
        return value.lower()

    实现自定义tag

    过程比实现过滤器要复杂,首先回顾一下模板系统的工作流程:
    1. 编译生成模板对象
    2. 模板对象使用上下文对象,渲染生成HTML内容
     
    如果你要实现自己的tag,就需要告诉Django怎样对你的tag进行上面的两个步骤。
     
    了解模板编译过程
    当Django编译一个模板时,它把原始的模板文件中的内容变成一个个节点,每一个节点
    是django.template.Node的实例,节点都有一个render()函数。因此,一个编译过的
    模板对象可以看成是一个结点对象的列表。例如,模板文件内容:
    Hello, {{ person.name }}.
    {% ifequal name.birthday today %}
    Happy birthday!
    {% else %}
    Be sure to come back on your birthday
    for a splendid surprise message.
    {% endifequal %}
    被编译后的Node列表:
    • Text node: "Hello, "
    • Variable node: person.name
    • Text node: ". "
    • IfEqual node: name.birthday and today
    当你调用模板对象的render()方法时,它会去调用Node列表上的每一个Node的render方法。最
    后输出的结果就是所有render方法的输出结果的合并。所以要创建你自己的tag,需要实现你自己的
    Node类,实现你自己的render方法。
     
    创建tag实战
    下面我们来实现一个tag,调用方法为:
    {% current_time "%Y-%m-%D %I:%M %p" %}
    功能是按照给定格式,显示当前时间,这个格式字符串和time.strftime()中定义的格式一样
     
    这里是为了演示一下,格式内容可以参考http://docs.python.org/library/time.html#l2h-1941
    这个标签也支持不需要参数的默认显示。
     
    1. 定义Node节点类,实现render方法
    import datetime
    from django import template
    #这一句还是要的
    register = template.Library()
    class CurrentTimeNode(template.Node):
        def __init__(self, format_string):
            self.format_string = str(format_string)
        def render(self, context):
            now = datetime.datetime.now()
            #返回的是格式化后的时间表示字符串
            return now.strftime(self.format_string)
    render函数一定返回的是字符串,即使是空字符串
    2. 创建Compilation函数
    这个函数主要用于获取模板中的参数,并创建相应的Node类对象
    def do_current_time(parser, token):
        try:
            tag_name, format_string = token.split_contents()
        except ValueError:
            msg = '%r tag requires a single argument' % token.split_contents()[0]
            raise template.TemplateSyntaxError(msg)
        return CurrentTimeNode(format_string[1:-1])
    每一个tag的编译函数都需要两个参数parser和token:
    parser是模板分析对象
    token是被parser分析后的内容,可以直接使用
     
    token.contents 是tag的内容,这里token的值是'current_time "%Y-%m-%d %I:%M %p"'
    token.split_contents 按空格分割字符串,返回tuple,但保留引号之单位的内容,这里得到
    ('current_time', '%Y-%m-%d %I:%M %p')
    和实现filter不一样,如果tag运行出错,一定要抛出TemplateSyntaxError,返回一些有用的信息。
    不要硬编码你的tag名,使用token.split_contents()[0]可以得到它。
    编译函数总是返回一个Node子类实例,返回其它类型会报错。
    3. 注册tag
    register.tag('current_time', do_current_time)
    和注册filter类似,两个参数,一个是使用名,一个是对应的函数引用名
    python2.4版本以上,也可以使用装饰符功能
    @register.tag(name="current_time")
    def do_current_time(parser, token):
    # ...
    @register.tag
    def shout(parser, token):
    # ...
    不用名字,表示默认使用函数名
    完整代码为:
    from django import template
    import datetime
    register = template.Library()
    @register.filter(name='remove')
    def remove(value, arg):
        return value.replace(arg, '')

    @register.filter
    def lower(value):
        return value.lower()

    class CurrentTimeNode(template.Node):
        def __init__(self, format_string):
            self.format_string = str(format_string)
        def render(self, context):
            now = datetime.datetime.now()
            return now.strftime(self.format_string)
        def do_current_time(parser, token):
            try:
                tag_name, format_string = token.split_contents()
            except ValueError:
                msg = '%r tag requires a single argument' % token.split_contents()[0]
                raise template.TemplateSyntaxError(msg)
         return CurrentTimeNode(format_string[1:-1])

    register.tag('current_time', do_current_time)
    4. 运行
    在模板文件中添加:
    访问页面:
    复杂的实现自定义tag的其他几种方法
    1. 在Node类的render函数中设置context
    def render(self, context):
        now = datetime.datetime.now()
        #设置context对象的值
        context['current_time'] = now.strftime(self.format_string)
        # render函数一定要返回字符串,即使是空串
        return ''
    这样调用的时候,就是如下用法:
    {% current_time "%Y-%M-%d %I:%M %p" %}
    <p>The time is {{ current_time }}.</p>
    但这样做一个不好的地方就是,current_time变量名是硬编码,可能会覆盖相同名字的值。
     
    重新设计一个tag的使用格式,如:
    {% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
    <p>The current time is {{ my_current_time }}.</p>
    这样就需要修改一下编译函数,Node类和注册代码,代码如下:
    import re
    class CurrentTimeNode3(template.Node):
        def __init__(self, format_string, var_name):
            #增加自定义变量名的参数
            self.format_string = str(format_string)
            self.var_name = var_name
        def render(self, context):
            now = datetime.datetime.now()
            context[self.var_name] = now.strftime(self.format_string)
            return ''

    def do_current_time(parser, token):
        #使用正规表达式来处理token
        try:
        # 使用string.split(sep[, maxsplit]),1代表最大分割数,也就是
        # 分割后会产生maxsplit+1个元素
        # 这里分割后的结果为(get_current_time, '"%Y-%M-%d %I:%M %p" as my_current_time')
            tag_name, arg = token.contents.split(None, 1)
        except ValueError:
            msg = '%r tag requires arguments' % token.contents.split_contents()[0]
            raise template.TemplateSyntaxError(msg)
        #使用()代表正则组,匹配as两边的字符串
        m = re.search(r'(.*?) as (w+)', arg)
        if m:
            fmt, var_name = m.groups()
        else:
            msg = '%r tag had invalid arguments' % tag_name
            raise template.TemplateSyntaxError(msg)
        #如果格式没被引号引用,报错
        if not (fmt[0] == fmt[-1] and fmt[0] in ('"', "'")):
            msg = "%r tag's argument should be in quotes" % tag_name
            raise template.TemplateSyntaxError(msg)
        # [1:-1]去除格式两边的引号
        return CurrentTimeNode3(fmt[1:-1], var_name)

    register.tag('get_current_time', do_current_time)
    运行结果:
    2. 实现块作用区域的tag
    如{% if %}...{% endif %},需要在你的编译函数中使用parse.parse()
    例如我们想要实现{% comment %}...{% endcomment %},功能是
    忽略中tag中间的所有内容。
    def do_comment(parser, token):
        nodelist = parser.parse(('endcomment',))
        parser.delete_first_token()
        return CommentNode()

    class CommentNode(template.Node):
        def render(self, context):
        return ''
    parse.parse()的参数是一个包含多个tag名的元组,返回的是它遇到元组中任何一个
    tag名之前的所有Node对象列表,所以这里的nodelist包含{% comment %}和
    {% endcomment %}之间的所有node对象,并且不包含它们自身两个node对象。
     
    parser.delete_first_token():因为执行完parse.parse()之后,{% endcomment %}
    tag还在,所以需要显示调用一次,防止这个tag被处理两次。
    3. 在块作用tag中保留context内容
    代码如下
    {% upper %}
    This will appear in uppercase, {{ user_name }}.
    {% endupper %}
    这里需要context中的user_name参数,怎么才能在处理tag的时候,不丢失context信息呢?
    def do_upper(parser, token):
        nodelist = parser.parse(('endupper',))
        parser.delete_first_token()
        return UpperNode(nodelist)

    class UpperNode(template.Node):
        def __init__(self, nodelist):
            self.nodelist = nodelist

        def render(self, context):
            output = self.nodelist.render(context)
            return output.upper()
    只需要保留下nodelist,然后调用self.nodelist.render(contest),就可以间接调用每一个Node
    的render函数。
     
     
    有更多的例子,可以查看Django源代码,位置为:
    D:Python27Libsite-packagesdjango emplatedefaulttags.py
    4. 快速创建简单tag的方法
    简单tag的定义,只带一个参数,返回经过处理的字符串,像之前的current_time标签一样。
    Django提供了一种simple_tag方法来快速创建类似这样的tag。
    def current_time(format_string):
        try:
            return datetime.datetime.now().strftime(str(format_string))
        except UnicodeEncodeError:
            return ''

    register.simple_tag(current_time)
    simple_tag参数为一个函数引用,会把它包装进render函数中,然后再进行注册。也不用定义
    Node子类了。
     
    python2.4以上,可以使用装饰符
    @register.simple_tag
    def current_time(token):
    # ...
    5. 创建Inclusion Tag
    另外一种比较普遍的tag类型是只是渲染其它模块显示下内容,这样的类型叫做Inclusion Tag。
    例如,实现以下tag:
    {% books_for_author author %}
    渲染结果为:
    <ul>
    <li>The Cat In The Hat</li>
    <li>Hop On Pop</li>
    <li>Green Eggs And Ham</li>
    </ul>
    列出某个作者所有的书。
    • 定义函数
    def books_for_author(author):
        books = Book.objects.filter(authors__id=author.id)
        return {'books': books}
    • 创建另一个模板文件book_snippet.html
    <ul>
    {% for book in books %}
    <li>{{ book.title }}</li>
    {% endfor %}
    </ul>
    • 注册tag
    register.inclusion_tag('book_snippet.html')(books_for_author)
     
    有些你的模板可以使用父模板的context内容,Django提供一个takes_context参数来实现,
    使用之后,tag不能再带参数,
    @register.inclusion_tag('link.html', takes_context=True)
    def jump_link(context):
        return {
            'link': context['home_link'],
            'title': context['home_title'],
    }
    模板文件link.html为
    Jump directly to <a href="{{ link }}">{{ title }}</a>.
    使用方法:
    {% jump_link %}

    创建自定义模板加载类

    可以自定其它的加载行为,比如从数据库中加载,从svn中加载,从zip中加载等。
     
    需要实现一个接口load_template_source(template_name, template_dirs=None):
    template_name就是类似'link.html'这样的模板名称
    template_dirs是一个可选的路径列表,为空就使用TEMPLATE_DIRS属性定义的路径。
     
     
    如果一个加载器加载模板成功,它将返回一个元组(template_source, template_path)。
    template_source:模板文件的内容字符中,会用于被编译
    template_path:模板文件的路径
     
    如果加载失败,报错django.template.TemplateDoesNotExist
     
     
    每一个加载函数都需要有一个is_usable的函数属性,对,是函数的属性,因为在python中,
    函数也是个对象。这个属性告诉模板引擎在当前的python环境下这个加载器是否可用。
     
    例如,之前的eggs加载器是默认关闭的,is_usable=False,因为需要pkg_resources模块
    中的从egg中读取信息的功能。不一定每个用户会安装,如果安装了,就可以设置为True,开启
    功能。
     
     
    下面实现一个从zip文件中加载模板的自定义加载器,它使用TEMPLATE_ZIP_FILES作为搜索路径,来
    代替系统的TEMPLATE_DIRS,路径上都是zip文件名。
    from django.conf import settings
    from django.template import TemplateDoesNotExist
    import zipfile
    def load_template_source(template_name, template_dirs=None):
        "Template loader that loads templates from a ZIP file."
        #从settings.py配置文件中读取属性TEMPLATE_ZIP_FILES的值,默认返回空列表
        template_zipfiles = getattr(settings, "TEMPLATE_ZIP_FILES", [])
        # Try each ZIP file in TEMPLATE_ZIP_FILES.
        for fname in template_zipfiles:
            try:
                z = zipfile.ZipFile(fname)
                source = z.read(template_name)
            except (IOErrorKeyError):
                continue
            z.close()
            # 找到一个可用的文件就返回
            template_path = "%s:%s" % (fname, template_name)
            return (source, template_path)
        
        # 如果一个zip文件没找到,报错
        raise TemplateDoesNotExist(template_name)

    # 设置为可用
    load_template_source.is_usable = True
    保存为zip_loader.py,放在app目录下,剩下我们需要做的是,在TEMPLATE_LOADERS属性
    中注册你的加载器:
    TEMPLATE_LOADERS = (
        'books.zip_loader.load_template_source',
    )
     
     
  • 相关阅读:
    filter与compress
    groupby,分组
    按照属性排序,使用lambda与itemgetter,attrgetter
    python3-cookbook电子书在线文档
    numpy中的向量操作
    向量Vector
    defaultdict与OrderedDict
    heapq堆队列
    (转载)SVM-基础(一)
    决策树-剪枝算法(二)
  • 原文地址:https://www.cnblogs.com/muzinan110/p/5015537.html
Copyright © 2011-2022 走看看