• 定制Django的Tag和Filter(二)


    配置

    (1)最常见的放置自定义Tag和Filter的地方是在Django的app下。当一个app被添加到settings.py的INSTALLED_APPS 后,任何在它下面的合法位置将自动的可在templates中被调用。合法位置就是在app下的templatetags子文件夹下,但不要忘记添加init.py文件来表明它是个包。

    自定义的tag或者filter就在templatetag文件夹下的py文件中,在template中调用的是该py文件的名字,比如

    app名字是polls,tags/filter在poll_extras.py中:

    polls/
        __init__.py
        models.py
        templatetags/
            __init__.py
            poll_extras.py
        views.py
    

    在template中这样加载定义在poll_extras.py中的tags/filters:

    {% load poll_extras %}
    

    包含定制的tags/filters的app必须在INSTALLED_APPS,并且{% load ... %}也是在INSTALLED_APPS从上往下搜索。

    (2)当然放置Tag和Filter的文件也不一定非得在app中,可以在project的任何地方,但是这种情况下,需要在DjangoTemplateslibraries中注册,比如,将放置tag/filters的文件testcommon.py放置在工程的新建common文件夹下。

    然后在settings.py的TEMPLATE中注册该文件,需要注意的是在templates中load的是‘commonfile'。

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [TEMPLATE_DIR],
            'APP_DIRS': True,
            'OPTIONS': {
                # 'string_if_invalid':InvalidTemplateVariable('%s'),
                # 'autoescape':False,
                'context_processors': [
                    
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                    'banners.processors.onsale',
                    
                ],
                'libraries':{'commonfile':'testBegin.common.testcommon'},
            },
        },
    ]
    

    testcommon.py中定义commonfilter:

    from django import template
    from django.utils.html import escape
    from django.utils.safestring import mark_safe
    register=template.Library()
    @register.filter(needs_autoescape=True)
    def commonfilter(value,autoescape=True):
        if autoescape:
            value=escape(value)
        result='<b>%s</b>'%value 
        return mark_safe(result)
    

    最后在template中调用:

    {% load commonfile %}
    {{ coffee|commonfilter }}  #其中coffee变量为'mocamoca'
    

    那能不能像built-in filter一样,不用每次都声明{% load custom filter %}?可以的,只需要在settings.py中的'TEMPLATES'的'OPTIONS'字段内加入'builtins':['location of files containing filter'],比如:

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [TEMPLATE_DIR],
            'APP_DIRS': True,
            'OPTIONS': {
                # 'string_if_invalid':InvalidTemplateVariable('%s'),
                # 'autoescape':False,
                'context_processors': [
                    
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                    'banners.processors.onsale',
                    
                ],
                'builtins':['testBegin.common.testcommon'],
            },
        },
    ]
    

    写自定义的模板filter

    自定义filter是有1个或者2个参数的Python函数:

    • 输入变量的值(不一定必须是string)

    • 值的参数,可以有默认值

      比如,{{ var|foo:'bar' }},foo被掺入了变量var和参数'bar'。

      下面是个简单的例子:

      #bannerTags.py
      from django import template
      register=template.Library()
      
      @register.filter()
      def john(value,arg):
          return value.replace(arg,'john')
      
      {% load bannerTags %}
      {{ coffee|john:'m' }}
      

      coffee的值为'macomaco',

    @register.filter可以没有参数,直接用@register.filter来装饰,也可以有参数,起到特定的功能:

    • name:默认的filter的名字就是函数名字,而name参数可以改变filter的名字

    @register.filter(name='cutname')
    def cut(value, arg):
        return value.replace(arg, '')
    
    {{ coffee|cutname:'m' }}
    
    • 此外,还有is_safe,needs_autoscape,expects_localtime.

    字符期望的Filter(Template filters that expect strings)

    django.template.defaultfilters.stringfilter()

    在上面的例子中,如果coffee的值是11,则会报错:

    解决这个只需要再加层装饰器就可以了:

    from django import template
    from django.template.defaultfilters import stringfilter
    register=template.Library()
    @register.filter
    @stringfilter
    def john(value,arg):
        return value.replace(arg,'john')
    

    可以发现已经没有报错了:

    Filter 和 auto-escaping

    from django import template
    from django.template.defaultfilters import stringfilter
    register=template.Library()
    from django.utils.html import conditional_escape
    from django.utils.safestring import mark_safe
    
    @register.filter
    @stringfilter
    def john(value,arg):
        return value.replace(arg,'john')
    @register.filter(is_safe=True)
    def myfilter0(value):
        return value 
    @register.filter()
    def myfilter(value):
        return mark_safe(value)
    
    @register.filter(needs_autoescape=True)
    def testautoes(text,autoescape=True):
        first,other=text[0],text[1:]
        if autoescape:
            esc=conditional_escape
        else:
            esc=lambda x:x 
        result='<b>%s</b>%s' % (esc(first),esc(other))
        return mark_safe(result)
        # return result
    
    @register.filter()
    def myfilter1(value):
        value=conditional_escape(value)
        return mark_safe(value)
    
    @register.filter()
    def myfilter2(value):
        value=conditional_escape(value)
        return value
    
    @register.filter()
    def myfilter3(value):
        return value
    
    {% load bannerTags %}
    {{ coffee|john:'a' }}
    <br>
    {{ coffee|testautoes }}--testautoes
    <br>
    {{ coffee|myfilter }}--myfilter
    <br>
    {{ coffee|myfilter0 }}--myfilter0
    <br>
    {{ coffee|myfilter1 }}--myfilter1
    <br>
    {{ coffee|myfilter2 }}--myfilter2
    <br>
    {{ coffee|myfilter3 }}--myfilter3
    

    当在settings.py的Templates的'OPTIONS'添加autoexcape:False时,

    image-20200909200612382

    从这个例子可以看出几点:

    • 自定义filter的函数大多都受总的autoescape设定(即settings.py中关于autoescape)的影响,如果总的autoescape被设定为False,则含有”不安全“的符号如'<,>,'将不被转义,但是当通过conditional_escape处理后,则仍然被转义,具体表现为上图'myfilter1'和'myfilter2'
    • 当总的autoescape是True(默认情况),大多含有”不安全“的符号,都要进行转义,但是通过mark_safe而没有通过conditional_escape的处理,将直接被渲染。

    写自定义Tags

    Tags 比Filter更复杂,因为tags可以做任何事情,Django提供了很多快捷方式来使得写tags更方便。

    Simple tags

    django.template.Library.simple_tag()

    很多模板tag有很多参数,字符串或者模板变量,然后仅仅基于输入的参数进行一些操作,然后返回结果。比如,‘current_time'可以接受一个字符串模板,返回时间的格式化的字符。

    为了简化这种tag的创建过程,Django提供了个帮助函数,simple_tag,这个函数是django.template.Library的方法,它的接受任意的参数,用render函数包裹该函数,所以它就是起了一个装饰器的作用。

    #bannerTags.py
    import datetime
    from django import template
    
    register = template.Library()
    
    @register.simple_tag
    def current_time(format_string):
        return datetime.datetime.now().strftime(format_string)
    
    {% load bannerTags %} 
    {% current_time ' %Y/ %m/ %d' as tim %} 
    {{tim}}
    
    image-20200909232328124

    不像其他tag的用法,如果是autoescape模式,simple_tag就会将它的输出再输入conditional_escape()中,来保证正确的HTML,来免于XSS攻击。

    如果十分确定没有XSS攻击代码,则可以用mark_safe(),而对于创建小的HTML代码块,建议用format_html(),而不是mark_safe().

    • 如果需要重命名,老规矩,就像Filter一样,在register.simple_tag()中传入name即可。

    • 如果需要接受任意数量的positional argument或者keyword arguments,比如:

      @register.simple_tag
      def my_tag(a, b, *args, **kwargs):
          warning = kwargs['warning']
          profile = kwargs['profile']
          ...
          return ...
      

      在模板中,任意的参数,被空格分隔,就会被传入tag中。与python类似的是,对于keyword argument,用 等于号 '='来表示,而keyword argument跟在python中的类似,也必须放在position arguments之后。

      {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
      
    • 如果不直接输出tag的结果,而是将该结果储存起来,也是可以的。通过as就可以达到这个效果,比如:

      {% current_time "%Y-%m-%d %I:%M %p" as the_time %}
      <p>The time is {{ the_time }}.</p>
      

      Inclusion tags

      django.template.Library.inclusion_tag()

      另一种常见的类型是通过render其他的模板来展示数据的,这种tags被称为'inclusion tags‘。

    写法步骤如下:

    (1)首先定义一个函数及其输入参数,然后返回一个字典,该字典将被当做一个template context传入被rendered 的其他模板。

    #testcommon.py
    def show_results(strings):
        choices=strings.split('s')
        return {'choices':choices}
    

    (2)定义被rendered的其他模板,其位置必须能被搜索到。

    # results.html
    <ul>
        {% for alpha in choices %}
        {% if alpha %}
        <li> {{ alpha }}</li>
        {% endif %}
        {% endfor %}
    </ul>
    

    (3)通过调用inclusion_tag来创建,和注册一个inclusion tag。

    #testcommon.py
    @register.inclusion_tag('banners/results.html')
    def show_results(strings):
        choices=strings.split('s')
        return {'choices':choices}
    

    (4)在模板中使用该tag。

    #test.html
    {% show_results coffee %}
    

    其路由为:

    urlpatterns=[
        url(r'^method',views.method,name='method'),
        url(r'^$',TemplateView.as_view(template_name='banners/test.html',extra_context={'coffee':'moscamsocas'}),name='index'),
    ]
    

    运行:

    image-20200910130951792
    • 也可以在模板中直接传字符串:

      #test.html
      {% show_results 'asbscs' %}
      
    image-20200910131132382

    还有一种等效的方法是:

    #testcommon.py
    from django.template.loader import get_template
    t=get_template('banners/results.html')
    register.inclusion_tag(t)(show_results)
    

    可以看到,本质上与第一种方法是一样的,只不过是显式的找到html然后传递给register.inclusion_tag,然后再传入show_results,这个过程本身就是装饰器的显式表达。

    有时候,inclusion tags需要将大量的参数传入,这时候对于使用tags的人来说非常痛苦,还要记住参数顺序。为解决这种问题,Django为inclusion tag提供了take_context选项,当表明了它,并设置为True,使用模板时,可以不需要参数,相应的python function只需要传入一个参数context,从而达到直接将被调用的template的context_dict传入tag function,并通过tag function 传入被rendered的Html。

    比如:

    @register.inclusion_tag('banners/results.html',takes_context=True)
    def show_results(context):
        choices=context['coffee'].split('s')
        return {'coffee':choices }
    
    #被call的test.html
    {% show_results  %}
    
    #被rendered的html,可见coffee被传入
    <ul>
        {% for alpha in coffee %}
        {% if alpha %}
        <li> {{ alpha }}</li>
        {% endif %}
        {% endfor %}
    </ul>
    

    image-20200910134347726

    最后,inclusion_tag也可以接受任意数量的position argument和keyword argument:

    如:

    @register.inclusion_tag('my_template.html')
    def my_tag(a, b, *args, **kwargs):
        warning = kwargs['warning']
        profile = kwargs['profile']
        ...
        return ...
    
    {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
    
    ##### 愿你一寸一寸地攻城略地,一点一点地焕然一新 #####
  • 相关阅读:
    JavaScript 之 typeof
    Octotree Chrome安装与使用方法
    支持主流MySQL中间件的数据迁移工具ora2mysql
    Eclipse搭建SpringBoot之HelloWorld
    Spring Boot插件spring tool suite安装及使用
    树的前中后序遍历非递归实现
    判断是否是完全二叉树
    Leetcode 437_path sum III todo
    DFS回溯只在递归基回溯————leetcode112
    Leetcode 94 Binary Tree Inorder
  • 原文地址:https://www.cnblogs.com/johnyang/p/13645212.html
Copyright © 2020-2023  润新知