使用 jinja2 渲染 HTML 模板

Vimiix發表於2018-01-22

前言

首先,如果一個正常的flask帶路由的介面,我們是不需要關心上下文物件的,Flask做了很多“魔術”的方法,當一個Flask應用接收到一個請求的時候,它會在將邏輯委託給你的檢視函式之前,建立好一個上下文物件。

當我們返回的時候呼叫render_template(template, **context),就可以正常的渲染介面返回,在這個函式中,如果看一下原始碼就會發現,返回渲染之前,會建立一個ctx去獲得當前環境的app變數。然後通過這個ctx去渲染傳進來的context引數列表。

def render_template(template_name_or_list, **context):
    """Renders a template from the template folder with the given
    context.

    :param template_name_or_list: the name of the template to be
                                  rendered, or an iterable with template names
                                  the first one existing will be rendered
    :param context: the variables that should be available in the
                    context of the template.
    """
    ctx = _app_ctx_stack.top
    ctx.app.update_template_context(context)
    return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list),
                   context, ctx.app)

基於之前知道有這個函式可以替換html中的模板變數,今天在寫程式碼的過程中,需要將一個html檔案中變數動態更新後通過郵件傳送走。出於慣性思維,理所當然的就直接擼程式碼:

from flask import render_template
# 要更新模板中的使用者名稱和密碼,返回要郵件傳送的內容
message = render_template("email.html", name="xxx", password='xxx')

一執行結果就報錯:

Traceback (most recent call last):
  File "xxx.py", line 14, in <module>
    message = render_template("email.html", name="xxx", password='xxx'))
  File "/usr/local/lib/python2.7/dist-packages/flask/templating.py", line 123, in render_template
    ctx.app.update_template_context(context)
AttributeError: 'NoneType' object has no attribute 'app'

經過一番摸索,大概知道這個ctx為嘛是個None,在werkzeug/local.py中有說明:

@property
def top(self):
    """The topmost item on the stack.  If the stack is empty,
    `None` is returned.
    """
    try:
        return self._local.stack[-1]
    except (AttributeError, IndexError):
        return None

果不其然,在這段程式碼中,我直接呼叫了render_template(),並沒有通過Flask路由,所以,上下文物件並沒有被建立。而render_template()嘗試去通過ctx獲取當前應用的app物件,所以ctx被賦值為None,因而出現上面的報錯資訊。

提出問題

這個問題該如何解決呢?我在這個地方,並不需要建立也不應該建立一個flask應用,但我又想使用jinja2的模板語言去更新資料。

解決問題

首先,發生這個錯誤,是因為Flask幫我們代理了jinja2的渲染動作,提供出一些更適合開發者使用的介面。也就是Flask把jinja2藏在了它內部,包裝了一下,我們想直接用裡面的東西,Flask就不幹了。明白人都看得出來,一切都是Flask引起的,負全責,jinja2是清白的。既然這樣,我們的解決方法就是撥調flask的包裝, 直接去呼叫jinja2的渲染機制,這個過程就需要我們自己來寫個小函式實現渲染功能。

示例程式碼如下:

import jinja2

def render_without_request(template_name, **context):
    """
    用法同 flask.render_template:

    render_without_request('template.html', var1='foo', var2='bar')
    """
    env = jinja2.Environment(
        loader=jinja2.PackageLoader('package','templates')
    )
    template = env.get_template(template_name)
    return template.render(**context)

這個函式假設你的應用程式檔案結構普通的那種。具體來說,你的專案結構應該是這個樣子:

package/
├── __init__.py <--- init檔案必須保留,python包的標誌
└── templates/
    └── template.html

這裡需要主要就是關注一下jinja2的PackageLoader這個函式,設計到檔案之間的引用關係,可以參考一下原始碼,在jinjia2/loaders.py檔案:

class PackageLoader(BaseLoader):
    """Load templates from python eggs or packages.  It is constructed with
    the name of the python package and the path to the templates in that
    package::

        loader = PackageLoader('mypackage', 'views')

    If the package path is not given, ``'templates'`` is assumed.

    Per default the template encoding is ``'utf-8'`` which can be changed
    by setting the `encoding` parameter to something else.  Due to the nature
    of eggs it's only possible to reload templates if the package was loaded
    from the file system and not a zip file.
    """

初始化這個類的時候,第一個引數是模板所在的目錄包名,第二個是模板目錄名,可選,預設為templates,第三個可選引數是encoding,預設為utf-8

至此我們這個函式寫好以後,就可以直接像flask.render_template()一樣來呼叫了,它可以單純的幫助我們把html模板渲染出來啦!

單純多美好~~^.^

本作品採用《CC 協議》,轉載必須註明作者和本文連結

To be a full stack man.

相關文章