Django之template操做

1、模板渲染的原理

(一)使用

模板渲染首先有一個模板對象Template,而後有一個上下文對象Context,經過render方法進行渲染,最後返回字符串,render方法的本質仍是調用了HttpResponse方法。css

>>> python manange.py shell  (進入該django項目的環境)
>>> from django.template import Context, Template
>>> t = Template('My name is {{ name }}.')
>>> c = Context({'name': 'shenjianping'})
>>> t.render(c)
'My name is shenjianping.'

# 同一模板,多個上下文,一旦有了模板對象,能夠經過它渲染多個context
# 固然使用同一模板源渲染多個context,只進行 一次模板建立,而後屢次調用render方法渲染會更爲高效:
# 每次循環都建立了模板 for name in ('aa', 'bb'): t = Template('Hello, {{ name }}') print(t.render(Context({'name': name}))) # 只建立一次模板,更爲高效 t = Template('Hello, {{ name }}') for name in ('aa', 'bb'): print(t.render(Context({'name': name})))

(二)源碼的簡單流程

一、Engine

Engine類位於django.template.Engine,這個類主要是將settings中模板的配置讀入:html

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

在Engine類的初始化方法中:python

class Engine:

...
    def __init__(self, dirs=None, app_dirs=False, context_processors=None,
                 debug=False, loaders=None, string_if_invalid='',
                 file_charset='utf-8', libraries=None, builtins=None, autoescape=True):
        if dirs is None:
            dirs = []
        if context_processors is None:
            context_processors = []
        if loaders is None:
            loaders = ['django.template.loaders.filesystem.Loader']
            if app_dirs:
                loaders += ['django.template.loaders.app_directories.Loader']
            if not debug:
                loaders = [('django.template.loaders.cached.Loader', loaders)]
        else:
            if app_dirs:
                raise ImproperlyConfigured(
                    "app_dirs must not be set when loaders is defined.")
        if libraries is None:
            libraries = {}
        if builtins is None:
            builtins = []

        self.dirs = dirs
        self.app_dirs = app_dirs
        self.loaders = loaders

...

根據傳遞的app_dirs的布爾值獲取加載模板的類loaders:jquery

django.template.loaders.app_directories.Loader

另外,Engine類提供了獲取默認的模板引擎的方法:shell

 def get_default():
        """
        Return the first DjangoTemplates backend that's configured, or raise
        ImproperlyConfigured if none are configured.

        This is required for preserving historical APIs that rely on a
        globally available, implicitly configured engine such as:

        >>> from django.template import Context, Template
        >>> template = Template("Hello {{ name }}!")
        >>> context = Context({'name': "world"})
        >>> template.render(context)
        'Hello world!'
        """
        # Since Engine is imported in django.template and since
        # DjangoTemplates is a wrapper around this Engine class,
        # local imports are required to avoid import loops.
        from django.template import engines
        from django.template.backends.django import DjangoTemplates
        for engine in engines.all():
            if isinstance(engine, DjangoTemplates):
                return engine.engine
        raise ImproperlyConfigured('No DjangoTemplates backend is configured.')

二、Loaders

在settings.py文件的配置中,傳入的app_dirs=True,因此使用的loaders是:數據庫

django.template.loaders.app_directories.Loader

在這裏它作這麼幾件事:django

  • 根據默認參數templates獲取模板路徑並將路徑轉化成元組形式
  • 提供get_template方法,此函數使用給定名稱(好比get_template('index.html'))加載模板並返回一個 Template對象(這裏的Template對象類型假設就是後臺默認配置的DjangoTemplates)
  • 提供select_template方法,select_template()就像get_template(),除了它採用模板名稱列表,好比select_template([index.html,user.html])。它按順序嘗試每一個名稱並返回存在的第一個模板。

三、Templatebootstrap

  • Template對象是經過上述Loaders類中的get_template方法返回獲得的對象:
class DjangoTemplates(BaseEngine):

    app_dirname = 'templates'

...

    def get_template(self, template_name):
        try:
            return Template(self.engine.get_template(template_name), self)
        except TemplateDoesNotExist as exc:
            reraise(exc, self)
...
  • Template類:
class Template:

    def __init__(self, template, backend):
        self.template = template
        self.backend = backend

    @property
    def origin(self):
        return self.template.origin

    def render(self, context=None, request=None):
        context = make_context(context, request, autoescape=self.backend.engine.autoescape)
        try:
            return self.template.render(context)
        except TemplateDoesNotExist as exc:
            reraise(exc, self.backend)
  • 使用render渲染

注意這個render是django.shortcuts.render不是Template中的renderapp

def render(request, template_name, context=None, content_type=None, status=None, using=None):
    """
    Return a HttpResponse whose content is filled with the result of calling
    django.template.loader.render_to_string() with the passed arguments.
    """
    content = loader.render_to_string(template_name, context, request, using=using)
    return HttpResponse(content, content_type, status)

調用loader中的render_to_string方法,而這個方法返回的就是上述Template類中render方法的返回值,可是最後仍是以HttpResponse的方法返回內容函數

def render_to_string(template_name, context=None, request=None, using=None):
    """
    Load a template and render it with a context. Return a string.

    template_name may be a string or a list of strings.
    """
    if isinstance(template_name, (list, tuple)):
        template = select_template(template_name, using=using)
    else:
        template = get_template(template_name, using=using)
    return template.render(context, request)

這就是使用render渲染的簡單過程。

總結:

  • Engine類從配置文件讀取模板配置信息
  • Loaders類加載模板,默認加載DjangoTemplates
  • Template類使用的是其render方法

 2、模板語言

(一)變量

變量從上下文輸出一個值,這是一個相似於dict的對象,它將鍵映射到值,在html中進行渲染時使用{{}}來渲染變量。

  • 字符串
from django.shortcuts import render,HttpResponse

def test(request):

    return render(request,'index.html',{'first_name': 'John', 'last_name': 'Doe'})

在index.html渲染:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

{{ first_name }}
{{ last_name }}

</body>
</html>
  • 字典查找,對象屬性查找和列表索引查找使用實現
def test(request):
    #字典
    dict = {'first_name': 'John', 'last_name': 'Doe'}
    #列表
    list = ['first_name','last_name']
    #對象
    book_obj = models.Book.objects.get(id=2)

    return render(request,'index.html',{'dict':dict,'list':list,'book_obj':book_obj})

 

在index.html渲染:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--字典-->
{{ dict.keys }}{{ dict.values }}
<!--列表-->
{{ list.0 }}
<!--對象-->
{{ book_obj.title }}
</body>
</html>

(二)內置標籤

  使用大括號與%的組合進行表示{% tag %},標籤在渲染過程當中提供任意邏輯。例如,標籤能夠輸出內容,用做控制結構(if或for結構),從數據庫中獲取內容,甚至容許訪問其餘模板標籤。

一、{% if %}

該標籤計算一個變量,若是該變量是「真」(即存在,不爲空,而且不是假布爾值)的數據塊的內容被輸出:{% if %}

{% if num >= 150  %}

    {% if num > 200 %}
        <p>num大於200</p>
    {% else %}
        <p>num大於100小於200</p>
    {% endif %}

{% elif num < 150 %}
    <p>num小於150</p>

{% else %}
    <p>num等於150</p>

{% endif %}

注意:

(1){% if %}語句以{% endif %}結尾;

(2){% if %}容許在同一標記中使用both andor子句, and優先級高於or,例如:

{% if user_list and role_list or depart_list %}

#這將被解釋爲:
 if (user_list and role_list) or depart_list:

(3){% if %} 標籤接受and,or或者not來測試多個變量值或者否認一個給定的變量;

{% if not user_list or role_list %}
    There are no usersor there are some roles.
{% endif %}


{% if user_list and not role_list %}
    There are some usersor and no roles.
{% endif %}

(4){% if %}標籤可使用運算符 ==(等於)!=(不等於)<(小於)>(大於), <=(小於等於)>=(大於等於)in(在...中)

二、{% for %}

(1)遍歷列表

循環遍歷列表中的每一個item,使item在上下文變量中可用。

{% for item in list %} <!--循環列表-->
   {{ item }} <!--取出列表中的每個值-->
{% endfor %}

固然,也能夠反向循環列表,加入reversed參數便可:

{% for item in list reversed %} <!--反向循環列表-->
   {{ item }} <!--取出列表中的每個值-->
{% endfor %}

(2)遍歷字典

{% for %}標籤支持遍歷字典:

{% for key, value in dict.items %}
    {{ key }}: {{ value }}
{% endfor %}

若是直接遍歷,獲得的value值:

{% for item in dict %}
    {{ item }} <!--value值-->
{% endfor %}

(3)forloop

在遍歷過程當中,系統不支持中斷循環,也不支持continue語句,可是{% for %}標籤內置了一個forloop模板變量,這個能夠提供一些循環信息:

#for循環設置循環中可用的許多變量:

變量                           描述
forloop.counter          循環的當前迭代(1索引)
forloop.counter0         循環的當前迭代(0索引)
forloop.revcounter       循環結束時的迭代次數(1索引)
forloop.revcounter0      循環結束時的迭代次數(0索引)
forloop.first            若是這是第一次經過循環,則爲真
forloop.last             若是這是最後一次循環,則爲真
forloop.parentloop       對於嵌套循環,這是圍繞當前循環的循環

變量的簡單使用:

#forloop.counter

{% for item in list  %}
   {{ item }}{{ forloop.counter }}
{% endfor %}


#forloop.first 若是這是第一次經過循環,則爲真,在某些狀況下仍是頗有用的,好比加入樣式

    {% for user in user_list%}   
         {% if forloop.first %}
<li class="first">
{% else %}
<li>
{% endif %} {{ user}} </li> {% endfor %}

注意:forloop變量只能在循環中獲得,當模板解析器到達{% endfor %}時forloop就消失了

(4){% for %}標籤能夠嵌套

#{%for%}之間的嵌套
 {% for country in countries %}
       {{ country.title}}
        <ul>
         {% for city in country.city_list %}
            <li>{{ city }}</li>
         {% endfor %}
        </ul>
    {% endfor %}

#{%for%}與{%if%}之間的嵌套
 {% for country in countries %}      
         {% if country.title =='abx' %}
            {{ country}}
         {% endif %}    
    {% endfor %}

三、{% empty %}

for標籤可使用一個可選選項,若是給定的列表爲空或沒法找到,就顯示該標籤下的內容

   {%  for i in list %}
          <li>{{ forloop.counter }}----{{ i }}</li>
      {% empty %}
          <li>this is empty!</li>
      {% endfor %}
<!--
1----1
2----2
3----3
若是list爲空列表,就顯示this is empty!
-->

四、{% csrf_token %}

用於生成csrf_token驗證碼,用於防止跨站攻擊驗證,這裏會生成一個隱藏的input標籤,包含鍵值對一塊兒提交給後臺驗證。

#使用方式

#在任何使用POST表單的模板中,對於內部url,使用元素csrf_token標記<form>,例如:

<form method="post">
{% csrf_token %}
...
</form>

注意:後臺若是使用render_to_response()方法,不會生效。

五、{% url %}

返回與給定視圖和可選參數匹配的絕對路徑引用(不帶域名的URL)

{% url 'some-url-name' v1 v2 %}

#第一個參數是URL模式名稱。它能夠是帶引號的文字或任何其餘上下文變量。其餘參數是可選的,應該是以空格分隔的值,這些值將用做URL中的參數。上面的例子顯示了傳遞位置參數。
或者,可使用關鍵字語法:
{% url 'some-url-name' arg1=v1 arg2=v2 %} #不要在一次調用中混合使用位置和關鍵字語法。應該存在URLconf所需的全部參數。

例如:

#路由
path('user/<int:id>/', views.user,name='user-detail'),
#表單
<form action="{% url "user-detail" user.id%}" > <input type="text"> <input type="submit"value="提交"> {%csrf_token%} </form>

六、{% with %}

以更簡單的名稱代替複雜變量。

{% with total=book.authors.count %}
    {{ total }} 
{% endwith %}

#或者這樣書寫

{% with book.authors.count as total %}
    {{ total }} 
{% endwith %}

固然也支持多個上下文變量:

{% with x=abc y=jkl %}
    {{x}}--{{y}}
{% endwith %}
  • {% verbatim %}

中止模板引擎渲染此塊標記的內容

#傳遞變量
def index(request):
    k='123'
    return render(request,'index.html',locals())

#不會渲染m變量
{% verbatim %}
{{ k }}
{% endverbatim %}

#頁面效果
{{k}}
  • {% autoescape%}
#要控制模板的自動轉義
 {{ value}} #若是value爲<a href="#">跳轉</a>,輸出將是<a href="#">跳轉</a>

#假如使用autoescape標籤

{% autoescape off %}
  {{ value }}
{% endautoescape %}
#輸出將是「跳轉」

更多請查看:https://docs.djangoproject.com/zh-hans/2.0/ref/templates/builtins/#built-in-tag-reference

(三)內置過濾器

過濾器經過修改要顯示的變量,從而使變量的值變成所指望的值。

#語法格式:
{{obj|filter:param}}

注意:冒號(:)後只能傳一個參數,可是過濾器能夠用於控制語句中,如{% if ****%}{% endif %},因此用的頻率較高。

一、add

#給變量加上相應的值
{{ value|add:'6' }}

此過濾器將首先嚐試將兩個值強制轉換爲整數。若是失敗,它將嘗試將值一塊兒添加到一塊兒。

二、addslashes   

# 給變量中的引號前加上斜線
{{ value|addslashes}}

#若是value爲:"I'm using Django",輸出將是"I\'m using Django"

 三、capfirst     

#首字母大寫 若是value是"django",輸出將是"Django"。

{{ value|capfirst }}

四、cut

# 從字符串中移除指定的字符

{{ value|cut:" " }} #移除空格
#若是value是"String with spaces",輸出將是 "Stringwithspaces"

五、date

#格式化日期字符串

import datetime
value=datetime.datetime.now()

{{ value|date:'Y-m-d' }}

六、default

# 若是值是False,就替換成設置的默認值,不然就是用原本的值
   {{ value|default:"nothing" }}

#若是value是""(空字符串),則輸出爲nothing。

七、default_if_none

#若是值是None,就替換成設置的默認值,不然就使用原本的值
{{ value|default_if_none:"nothing" }}

#若是value是None,輸出將是nothing。

八、escape

#轉義字符串的HTML。具體來講,它使這些替換:

< 轉換爲 &lt;
> 轉換爲 &gt;
' (單引號)轉換爲 &#39;
" (雙引號)轉換爲 &quot;
& 轉換爲 &amp;

#使用
{% autoescape off %}
    {{ title|escape }}
{% endautoescape %}

九、first

#返回列表中的第一個項目
{{ value|first }}

#若是value是列表['a','b','c','d'],則輸出將是'a'

十、last

#返回列表中的最後一個項目
{{ value|first }}

#若是value是列表['a','b','c','d'],則輸出將是'd'

十一、length

#返回列表的長度
{{ value|length}}

#若是value是列表['a','b','c','d'],則輸出將是4

十二、floatformat

#若是在沒有參數的狀況下使用,則將浮點數舍入到一個小數位 - 但前提是要顯示小數部分

{{ value|floatformat }}

#若是value爲12.21,輸出將是12.2
#若是value爲12.00,輸出將是12
#若是value爲12.71,輸出將是12.3

1三、join

#使用字符串鏈接列表,如Python str.join(list)

{{ value|join:" // " }}

#若是value是列表,則輸出將是字符串 。['a', 'b', 'c']"a // b // c"

1四、lower

#將字符串所有轉換爲小寫

{{ value|lower }}

#若是value是ABC,輸出將是abc

注意:與之相對的是upper

1五、urlencode

#轉義值以在URL中使用
{{ value|urlencode }}

#若是value是"http://www.yw.com/?page=1&pageSize=3",輸出將是
"http%3A//www.yw.com/%3Fpage%3D1%26pageSize%3D3"

更多詳情參見:https://docs.djangoproject.com/zh-hans/2.0/ref/templates/builtins/#ref-templates-builtins-filters

(四)自定義過濾器和標籤

自定義標籤應按照如下步驟執行:

一、在app中建立templatetags目錄

注意:在建立templatetags目錄不要忘了__init__.py文件,以確保該目錄做爲一個Python的包裝處理

二、建立任意 .py 文件,如:my_tags.py

from django import template
from django.utils.safestring import mark_safe

register = template.Library()   #register的名字是固定的,不可改變
 #自定製過濾器
@register.filter
def filter_multi(v1,v2):
    return  v1 * v2

#自定製標籤
@register.simple_tag
def simple_tag_multi(v1,v2):
    return  v1 * v2


@register.simple_tag
def my_input(id,arg):
    result = "<input type='text' id='%s' class='%s' />" %(id,arg,)
    return mark_safe(result)

三、在使用自定義simple_tag和filter的html文件中導入以前建立的 my_tags.py 

#注意放在html文件的第一行

{% load my_tags %}

四、使用simple_tag和filter

{% load ‘my_tags’%}   <!--首行-->
    
{{ num|filter_multi:3 }} <!--假設num:12,輸出則是:36-->


{% simple_tag_multi 3 10 %}  <!--參數不限,但不能放在if for語句中-->
{% simple_tag_multi num 5 %}  <!--假設num:12,輸出則是:60-->

注意:過濾器能夠用於if等控制語句中,可是標籤不行

{% if price|filter_price:10 > 400 %}
{{ price|filter_price:20 }}
{% endif %}

更多請參考:https://docs.djangoproject.com/zh-hans/2.0/howto/custom-template-tags/

3、模板繼承

模板繼承主要是爲了提升代碼的重用性,減小開發和維護人員的壓力。

(一)父模板

若是發如今多個html文件中有不少的代碼是重複,就能夠將這些共同的代碼提取出來,組成一個公共模板(能夠稱之爲父模板),其它不一樣的地方只須要預留鉤子,好比:

{%block 名稱%}

   預留區域,能夠編寫默認內容,也能夠沒有默認內容

{%endblock 名稱%}

注意:字模板繼承的名稱應與父模板保持一致,而且不能有重名。

  base.html:

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>yw</title>
    <link rel="stylesheet" href="{% static 'stark/plugins/bootstrap/css/bootstrap.css' %} "/>
    <link rel="stylesheet" href="{% static 'stark/plugins/font-awesome/css/font-awesome.css' %} "/>
    <style>
        body {
            margin: 0;
        }
    </style>
    {% block css %}{% endblock %}<!--預留css-->
</head>
<body>
<div class="pg-header">
</div>
<div class="pg-body">
    <div class="left-menu">
    </div>
    <div class="right-body">
            {% block content %} {% endblock %}<!--預留內容-->
    </div>
</div>

<script src="{% static 'stark/js/jquery-3.3.1.min.js' %} "></script>
<script src="{% static 'stark/plugins/bootstrap/js/bootstrap.js' %} "></script>
{% block js %} {% endblock %} <!--預留js-->
</body>
</html>

(二)子模板

在繼承父模板時,須要使用標籤extends而且將其寫在子模板文件的第一行。

{% extends "父模板路徑"%}

在繼承時,能夠不用填充父模板中的預留區域,此時使用的就是默認內容;固然也能夠將預留block進行填充,若是在填充後仍然須要默認內容能夠進行獲取,經過:

{% block content %}
  填充實際內容
{{block.super}}<!--用於獲取父模板預留block content中的內容-->
{% endblock %}

  index.html:

{% extends 'base.html' %}<!--繼承父模板-->
{% load staticfiles %}
{% block css %}
    <style>
        textarea{
            min-height: 200px;
        }
    </style>
{% endblock %}
{% block content %}
        <form class="change" method="post" novalidate>
            {% csrf_token %}          
        </form>
    </div>
{% endblock %}
{% block js %}
    <script src="{% static "stark/js/m2m.js" %}"></script>
    <script src="{% static "stark/js/pop.js" %}"></script>
{% endblock %}

(三)總結

一、子模板繼承父模板的關鍵標籤{% extends %} 必須位於第一行,不然,模板繼承將不起做用。

二、子模板沒必要定義父模板中全部的block代碼塊,按需定義便可。

三、若是發現多個html文件中有共同的代碼,考慮將其移動到父模板中,並用block預留鉤子。

四、不容許在同一個模板中定義多個同名的 {% block %} 。

五、若是須要訪問父模板中block的內容,使用 {{ block.super }}這個標籤。

相關文章
相關標籤/搜索