歡迎來到我的 Jinja2 教程系列的另壹部分。到目前為止,我們已經了解了很多關於渲染、控制結構和各種功能的知識。在這裏,我們將開始討論幫助我們處理組織模板的語言特性。我們將看的第壹個構造是 include 和 import 語句。
Include 和 Import 語句是 Jinja 為我們提供幫助組織模板集合的壹些工具,尤其是當這些模板的大小增加時。
通過使用這些結構,我們可以將模板拆分為更小的邏輯單元,從而生成具有明確定義範圍的文件。反過來,當出現新需求時,這將使修改模板變得更加容易。
結構良好的模板集合的最終目標是提高可重用性和可維護性。
“包含”語句允許您將大型模板分解為更小的邏輯單元,然後可以在最終模板中組裝這些單元。
當妳使用時, include 妳引用另壹個模板並告訴 Jinja 渲染引用的模板。Jinja 然後將渲染文本插入到當前模板中。
語法為 include :
{% include 'path_to_template_file' %}
其中 'path_to_template_file' 是我們想要包含的模板的完整路徑。
例如,下面我們有壹個名為模板的模板, cfg_draft.j2 它告訴 Jinja 找到名為模板的模板,渲染它,並用渲染的文本 users.j2 替換塊。 {% include ... %}
cfg_draft.j2
users.j2
最後結果:
如果您查看典型的設備配置,您將看到與給定功能相對應的許多部分。您可能有接口配置部分、路由協議壹、訪問列表、路由策略等。我們可以編寫單個模板來生成整個配置:
device_config.j2
通常我們會有機地擴展我們的模板,並將壹個接壹個的部分添加到負責生成配置的單個模板中。然而,隨著時間的推移,這個模板變得太大,並且變得難以維護。
處理日益增長的復雜性的壹種方法是識別大致對應於單個特征的部分。然後我們可以將它們移動到自己的模板中,這些模板將包含在最後壹個模板中。
我們的目標是使用壹些較小的模板來處理明確定義的功能配置部分。這樣,當您需要進行更改時,更容易找到要修改的文件。也更容易分辨哪個模板做什麽,因為我們現在可以給它們適當的名稱,如“bgp.j2”和“acls.j2”,而不是壹個名為“device_config.j2”的大模板。
使用前面的模板,我們可以將其分解為更小的邏輯單元:
base.j2
dns.j2
ntp.j2
interfaces.j2
prefix_lists.j2
bgp.j2
我們現在有壹組單獨的模板,每個模板都有壹個明確的名稱來傳達其用途。雖然我們的示例沒有太多行,但我認為您會同意我的觀點,即我們得出的邏輯分組將更易於使用,並且可以更快地建立關於這裏發生的事情的心理模型。
隨著功能移動到單獨的模板,我們終於可以使用 include 語句來組成我們的最終配置模板:
config_final.j2
您打開此模板,只需快速瀏覽壹下,您就應該能夠了解它正在嘗試做什麽。它更簡潔,我們可以輕松添加不會在數百行其他行中丟失的註釋。
作為獎勵,您可以通過註釋單行或簡單地暫時刪除它來快速測試禁用壹個功能的模板。
現在更容易進行更改,並且可以更快地識別特征和相應的模板,而不是搜索壹個可能有數百行的大模板。
同樣,當需要新部分時,我們可以創建單獨的模板並將其包含在最終模板中,從而實現我們增加模塊化的目標。
base.j2
您可以 include 在模板層次結構中的任何級別以及您想要的模板中的任何位置使用語句。這正是我們在這裏所做的;我們將橫幅的文本移動到壹個單獨的文件中,然後我們將其包含在 base.j2 模板中。
我們可以爭辯說,橫幅本身並不重要,不足以保證有自己的模板。但是,還有另壹類 include 有用的用例。我們可以維護跨許多不同模板使用的通用片段庫。
這與我們之前的示例不同,在之前的示例中,我們將壹個大模板分解為更小的邏輯單元,所有這些單元都與最終模板緊密相關。使用通用庫,我們可以在許多不同的模板中重復使用這些單元,這些模板可能沒有任何相似之處。
Jinja 允許我們 optionally 通過 ignore missing 向 include .
{% include 'guest_users.j2' ignore missing %}
它本質上告訴 Jinja 尋找 guest_users.j2 模板並在找到時插入呈現的文本。如果找不到模板,這將導致空行,但不會引發錯誤。
我通常建議不要在您的模板中使用它。它不是被廣泛使用的東西,所以閱讀您的模板的人可能不知道它的用途。最終結果還取決於特定文件的存在,這可能會使故障排除更加困難。
有更好的方法來處理可選特性,其中壹些依賴於我們將在下壹篇文章中討論的模板繼承。
與“忽略缺失”密切相關的是提供要包含的模板列表的可能性。Jinja 將檢查模板是否存在,包括第壹個存在的模板。
在下面的示例中,如果 local_users.j2 不存在但 radius_users.j2 存在,則渲染 radius_users.j2 最終將被插入。
{% include ['local_users.j2', 'radius_users.j2'] %}
您甚至可以將模板列表與 ignore missing 參數結合起來:
{% include ['local_users.j2', 'radius_users.j2'] ignore missing %}
這將導致搜索列出的模板, 如果沒有找到它們,則不會引發錯誤 。
同樣,雖然很誘人,但我建議不要使用此功能,除非您用盡其他途徑。如果在我的最終渲染中有些東西看起來不正確,我不會喜歡弄清楚列出的模板中的哪壹個最終被包含在內。
總而言之,您可以使用“包含”來:
在 Jinja 中,我們使用 import 語句來訪問保存在其他模板中的宏。這個想法是經常在他們自己的文件中使用宏,然後將這些宏導入需要它們的模板中。
我們可以通過三種方式導入宏。
所有三種方式都將使用導入以下模板:
macros/ip_funcs.j2
您還可以將 2 與 3 結合起來:
imp_ipfn_way2_3
我的建議是始終使用 1 . 這迫使您通過顯式命名空間訪問宏。方法 2 和 3 風險與當前命名空間中定義的變量和宏發生沖突。正如 Jinja 中經常出現的情況壹樣,顯式優於隱式。
導入被緩存,這意味著它們在每次後續使用時都會非常快速地加載。這是要付出代價的,即導入的模板無法訪問導入它們的模板中的變量。
這意味著默認情況下,您無法訪問從另壹個文件導入的宏內部的上下文中傳遞的變量。
相反,您必須構建宏,以便它們僅依賴於顯式傳遞給它們的值。
為了說明這壹點,我編寫了兩個版本的宏命名 def_if_desc ,壹個嘗試訪問可用於模板導入的變量。另壹個宏依賴於通過值顯式傳遞給它的字典。
兩個版本都使用以下數據:
default_desc.yaml
希望現在您可以看到導入宏時的默認行為。由於上下文中的變量值可以隨時更改,Jinja 引擎無法緩存它們,我們也不允許從宏中訪問它們。
但是,如果出於某種原因您認為允許宏訪問上下文變量是個好主意,您可以使用 with context 傳遞給 import 語句的附加參數來更改默認行為。
Note: This will automatically disable caching.
為了完整起見,這是我們如何“修復”失敗的宏:
macros/def_desc_ctxvars.j2
im_defdesc_vars_wctx.j2
現在它起作用了:
就個人而言,我不認為 import 壹起使用是壹個好主意 with context 。從單獨的文件中導入宏的全部意義在於允許它們在其他模板中使用並利用緩存。可能有數百個,如果不是數千個,並且壹旦您使用 with context 緩存就消失了。
我還可以在依賴於從模板上下文訪問變量的宏中看到壹些非常微妙的錯誤。
為了安全起見,我會說堅持標準導入並始終使用命名空間,例如
{% import 'macros/ip_funcs.j2' as ipfn %}
我們了解了兩種 Jinja 構造,它們可以幫助我們管理隨著模板大小增加而出現的復雜性。通過利用 import 和 include 語句,我們可以 提高可重用性並使我們的模板更易於維護 。我希望包含的示例向您展示了如何使用這些知識來使您的模板集合更好地組織和更容易理解。
這是我在以下情況下使用的內容的快速摘要: