1. 程式人生 > Django入門教學 >12 Django 中模板變數使用

12 Django 中模板變數使用

上一節中,我們簡單介紹了模板相關的概念,並手動實踐了 python 中的兩個流行的模板庫 Mako 和 Jinja2。接下來,我們將深入瞭解如何在 Django 中使用模板系統。

1. Django 中的模板系統

Django 中自帶了一個模板系統,叫做 Django Template Language (DTL),通過該引擎,我們可以方便的載入模板檔案到記憶體中進行編譯並動態插入資料,最後返回轉換後的文字內容。在 Django 中模板引擎的配置同樣是在 settings.py 中的,具體配置如下:

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

如果我們想讓 Django 使用 Jinja2 模板引擎,可以將上述配置中的 BACKEND 的值進行替換:

'BACKEND': 'django.template.backends.jinja2.Jinja2',

又或者我們可以同時支援多個模板引擎,只不過指定的模板目錄不同即可:

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',
            ],
        },
    },  
    {   
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['/root/test/html/jinja2'],
    }
]

Django 中提供了兩個方法載入模板:get_template() 方法和 select_template() 方法。從原始碼中可以看到,這兩個方法的實現幾乎是一樣的,只不過前者只能輸入一個模板檔案並返回 Template 物件,而後者可以輸入多個模板檔案但也只返回第一個存在的模板檔案的 Template 物件。


# django/template/loader.py
from . import engines
from .exceptions import TemplateDoesNotExist

def get_template(template_name, using=None):
   """
   Load and return a template for the given name.

   Raise TemplateDoesNotExist if no such template exists.
   """
   chain = []
   engines = _engine_list(using)
   # 遍歷模板引擎
   for engine in engines:
       try:
           return engine.get_template(template_name)
       except TemplateDoesNotExist as e:
           chain.append(e)
         
   # 找不到匹配的模板引擎,就直接丟擲TemplateDoesNotExist異常
   raise TemplateDoesNotExist(template_name, chain=chain)


def select_template(template_name_list, using=None):
   """
   Load and return a template for one of the given names.

   Try names in order and return the first template found.

   Raise TemplateDoesNotExist if no such template exists.
   """
   if isinstance(template_name_list, str):
       raise TypeError(
           'select_template() takes an iterable of template names but got a '
           'string: %r. Use get_template() if you want to load a single '
           'template by name.' % template_name_list
       )

   chain = []
   engines = _engine_list(using)
   for template_name in template_name_list:
       for engine in engines:
           try:
               return engine.get_template(template_name)
           except TemplateDoesNotExist as e:
               chain.append(e)

   if template_name_list:
       raise TemplateDoesNotExist(', '.join(template_name_list), chain=chain)
   else:
       raise TemplateDoesNotExist("No template names provided")

# 忽略其他程式碼

從 django 的原始碼中,可以看到 _engine_list() 方法就是獲取我們前面的引擎列表,然後開始找這個引擎對應的目錄下是否有輸入的模板檔案。如果一旦根據模板檔案找到了匹配的模板引擎,查詢工作就會停止,直接使用該模板引擎對模板檔案生成一個 Template 物件並返回。

對於不同的模板引擎,返回的 Template 物件是不同的,但是 Template 物件必須包含一個 render() 方法:

Template.render(context=None, request=None)

接下來,我們會使用兩個模板檔案進行一個簡單的實驗。實驗的環境還是在原來的 first-django-app 工程中進行。首先,我們準備兩個模板檔案 index.html 和 index.html.j2。其中 index.html 放到 django 工程下的 template 目錄中,對應著 Django 內建的模板引擎; index.html.j2 則放到 /root/test/html/ 目錄下,對應著 jinja2 模板引擎。

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.template.loader import get_template
>>> get_template('index.html')
<django.template.backends.django.Template object at 0x7f132afe08e0>
>>> get_template('index.html.j2')
<django.template.backends.jinja2.Template object at 0x7f132a952a60>
>>> 

可以看到,對應的模板檔案都找到了對應的模板引擎。接下來,我們來對 Django 內建的模板引擎進行測試:

(django-manual) [root@server first_django_app]# cat templates/index.html 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>{{ title }}</h1>
<p>{{ content }}</p>
</body>
</html>
(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.template.loader import get_template
>>> tf = get_template('index.html')
>>> print(tf.render(context={'title': '標題檔案', 'content': '這是正文'}))
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>標題檔案</h1>
<p>這是正文</p>
</body>
</html>

可以看到,Django 內建的模板引擎已經正確地將模板檔案進行了轉換,將模板變數進行替換,得到了我們想要的HTML 文字。

2. Django 中的 render 函式

有了上面的基礎,我們來看看前面給出的返回動態 HTML 的 render 函式:

 def index(request, *args, **kwargs):
      return render(request, "index.html", {"title":"首頁", "content": "這是正文"})

我們可以通過原始碼追蹤以下該函式的執行過程。首先 render 函式定義在 django/shortcut.py 中,程式碼內容如下:

# django/shortcut.py

# ...

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 方法,我們繼續追進去,就可以看到和前面熟悉的測試程式碼了:

# django/template/loader.py

# ...

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_to_string() 方法的第一步是判斷 template_name 是列表還是元組。如果是列表或者元祖,則使用select_template()方法得到對應的模板物件,否則使用 get_template()方法。最後由template.render() 方法加上對應的模板資料生成我們想要的 HTML 文字。可以看到最後的 render() 方法依舊是返回的 HttpResponse 示例,只不過 content 引數正好是動態生成的 HTML 文字。

3. 小結

今天我們探究了 Django 中模板系統的配置和使用。此外還追蹤了 render() 方法的原始碼,對 Django 中的模板系統以及提供的一些封裝的內部函式有了更清楚的認識。接下來,我們將深入學習 Django 內建的模板語言-DTL,徹底掌握複雜模板檔案的編寫。