前言
首先,如果一個正常的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 協議》,轉載必須註明作者和本文連結