2011-11-02 127 views
36

我的瓶的應用程序佈局是:燒瓶藍圖模板文件夾

myapp/ 
    run.py 
    admin/ 
     __init__.py 
     views.py 
     pages/ 
      index.html 
    main/ 
     __init__.py 
     views.py 
     pages/ 
      index.html 

_ 初始化 _ .py文件是空的。 管理員/ views.py內容是:

from flask import Blueprint, render_template 
admin = Blueprint('admin', __name__, template_folder='pages') 

@admin.route('/') 
def index(): 
    return render_template('index.html') 

主/ views.py類似於管理員/ views.py

from flask import Blueprint, render_template 
main = Blueprint('main', __name__, template_folder='pages') 

@main.route('/') 
def index(): 
    return render_template('index.html') 

run.py是:

from flask import Flask 
from admin.views import admin 
from main.views import main 

app = Flask(__name__) 
app.register_blueprint(admin, url_prefix='/admin') 
app.register_blueprint(main, url_prefix='/main') 

print app.url_map 

app.run() 

現在,如果我訪問http://127.0.0.1:5000/admin/,它正確顯示admin/index.html。 但是,http://127.0.0.1:5000/main/仍顯示admin/index.html而不是main/index.html。我查app.url_map:

<Rule 'admin' (HEAD, OPTIONS, GET) -> admin.index, 
<Rule 'main' (HEAD, OPTIONS, GET) -> main.index, 

此外,我驗證了主/ views.py該索引功能按預期的方式調用。 如果我將main/index.html重命名爲不同的東西,那麼它可以工作。所以,如果沒有 重命名,那麼如何實現1http://127.0.0.1:5000/main/1顯示main/index.html?

回答

44

從Flask 0.8開始,藍圖將指定的template_folder添加到應用程序的搜索路徑中,而不是將每個目錄視爲單獨的實體。這意味着如果您有兩個具有相同文件名的模板,則在搜索路徑中找到的第一個模板是使用的模板。這當然是令人困惑的,目前還沒有很好的記錄(見this bug)。 It seems你並不是唯一一個被這種行爲困惑的人。

此行爲的設計原因是,藍圖模板可以輕鬆地從主應用程序的模板中覆蓋,這些模板是Flask模板搜索路徑中的第一行。

想到兩個選項。

  • 重命名每個index.html文件是唯一的(例如admin.htmlmain.html)。
  • 在每個模板文件夾中,將每個 模板放在blueprint文件夾的子目錄中,然後使用該子目錄調用 模板。例如,您的管理員模板將爲yourapp/admin/pages/admin/index.html,然後在 之內調用藍圖render_template('admin/index.html')
+0

有類似的問題。我希望這個處理方式不同。更改靜態文件夾的位置對於服務文件來說工作得很好,但如果同一個文件已存在,模板將被覆蓋。 –

+0

正如jay chan指出的,最簡單的方法是將模板存儲在主模板文件夾中,以便將admin.html存儲在'myapp/templates/admin/index.html'下。 –

+0

爲什麼不更改'template_folder'到'「admin/pages」或'「main/pages」'? –

21

除了linqq上面的好建議,如果需要,還可以覆蓋默認功能。有幾種方法:

可以在subclassed Flask應用程序(它返回flask/templating.py中定義的DispatchingJinjaLoader)中覆蓋create_global_jinja_loader。這不被推薦,但會起作用。不鼓勵這樣做的原因是DispatchingJinjaLoader具有足夠的靈活性來支持定製裝載機的注入。如果你使用自己的裝載器,它將能夠依靠默認的,理智的功能。

那麼,什麼建議是一個「覆蓋jinja_loader功能」來代替。這是缺乏文檔的地方。修補Flask的加載策略需要一些似乎沒有記錄的知識,以及對Jinja2的理解。

有你需要了解兩個部分:

  • 的Jinja2的環境
  • 的Jinja2的模板加載器

這是由瓶創建,具有合理的默認值,自動。 (您可以指定自己的Jinja2 options,順便說一下,通過重寫app.jinja_options - 但是記住,你將失去兩個擴展該瓶包括默認 - autoescapewith - 除非你自己指定他們看看瓶/ app.py,看看他們如何引用這些。)

環境包含了所有這些情況下處理器(例如,所以你可以做一個模板var|tojson),輔助功能(url_for等)和變量(g的, sessionapp)。它還包含對模板加載器的引用,在這種情況下,前述和自動實例化DispatchingJinjaLoader。所以,當你在你的應用程序調用render_template,發現或創建的Jinja2環境,設置所有這些東西,並在其上調用get_template,進而調用get_sourceDispatchingJinjaLoader的,它試圖後面描述的一些策略。

如果一切按計劃進行,該鏈將尋找一個文件來解決,並返回其內容(以及一些other data)。另請注意,這與{% extend 'foo.htm' %}需要執行的路徑相同。

DispatchingJinjaLoader做了兩件事:首先它檢查應用程序的全局加載程序,即app.jinja_loader是否可以找到該文件。如果做不到這一點,則在試圖找到該文件檢查所有應用藍圖(在註冊,AFAIK的順序)爲blueprint.jinja_loader。跟蹤該鏈的最末端,這裏是jinja_loader的定義(在瓶/ helpers.py,_PackageBoundObject,基類兩種瓶應用和藍圖):

def jinja_loader(self): 
    """The Jinja loader for this package bound object. 

    .. versionadded:: 0.5 
    """ 
    if self.template_folder is not None: 
     return FileSystemLoader(os.path.join(self.root_path, 
              self.template_folder)) 

啊!所以現在我們看到了。顯然,兩者的命名空間將在相同的目錄名稱上發生衝突。由於全局加載程序首先被調用,它總會贏。 (FileSystemLoader是幾個標準Jinja2加載器之一)。但是,這意味着沒有真正簡單的方法來對Blueprint和應用程序範圍的模板加載器進行重新排序。

所以,我們需要修改的DispatchingJinjaLoader行爲。一段時間以來,我認爲沒有一種好的,不泄氣的,有效的方法來解決這個問題。但是,顯然如果你自己重寫app.jinja_options['loader'],我們可以得到我們想要的行爲。因此,如果我們繼承DispatchingJinjaLoader,並修改一個小函數(我認爲完全重新實現它可能會更好,但現在可行),但我們有我們想要的行爲。總體而言,一個合理的策略會是以下(未經測試,但應與現代瓶應用工作):

from flask.templating import DispatchingJinjaLoader 
from flask.globals import _request_ctx_stack 

class ModifiedLoader(DispatchingJinjaLoader): 
    def _iter_loaders(self, template): 
     bp = _request_ctx_stack.top.request.blueprint 
     if bp is not None and bp in self.app.blueprints: 
      loader = self.app.blueprints[bp].jinja_loader 
      if loader is not None: 
       yield loader, template 

     loader = self.app.jinja_loader 
     if loader is not None: 
      yield loader, template 

此修改原始裝載機的方式有兩種策略:嘗試從藍圖加載(且僅當前正在執行的藍圖,而不是所有的藍圖),如果失敗,只能從應用程序加載。如果你喜歡全藍圖的行爲,你可以從flask/templating.py中做一些複製意麪。

爲了將其結合在一起,你必須設置jinja_options的瓶對象:

app = Flask(__name__) 
# jinja_options is an ImmutableDict, so we have to do this song and dance 
app.jinja_options = Flask.jinja_options.copy() 
app.jinja_options['loader'] = ModifiedLoader(app) 

第一次需要一個模板環境(從而實例化),這意味着在第一時間render_template被調用時,你的裝載機應該使用。

+0

這太棒了!謝謝你的提示! –

+0

此方法是否允許模板繼承? – Andy

7

twooster的回答很有趣,但另一個問題是,神社默認緩存基於其名稱的模板。因爲這兩個模板都被命名爲「index.html」,所以加載程序不會爲後續藍圖運行。

除了linqq的兩個建議,第三個選擇是忽略藍圖的templates_folder選項一起並在其中放置各自的文件夾模板在應用程序的模板目錄。

即:

myapp/templates/admin/index.html 
myapp/templates/main/index.html 
+2

這個答案(雖然Twoosters很好的解決了)在這裏最有意義的IMO,因爲如果你試圖達到本地優先級的效果,那麼你沒有使用藍圖級別的模板來達到他們預期的目的 - 從/或者被應用程序特定的模板覆蓋。簡單通常更好。 (在這種情況下,更符合意圖的性質)。 –

+0

如果我理解正確的答案,您說的神社存在緩存問題,當兩個模板被命名爲'index.html'但隨後建議還有一個名爲'index.html'兩個模板替代。我錯過了什麼嗎? –

+0

區別似乎是,兩個新的index.html文件通過模板文件夾下的路徑進行區分,而不是直接在其藍圖的模板文件夾下,如「myapp/admin/templates/index.html」和「 MYAPP /主/模板/ index.html的」。 – Thinkable