2011-11-02 25 views
36

mio fiasco layout di app è:pallone

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

_ init _ file .py sono vuoti. admin/views.py contenuto è:

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

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

principale/views.py è simile a admin/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() 

Ora, se accedo allo http://127.0.0.1:5000/admin/, visualizza correttamente admin/index.html. Tuttavia, http://127.0.0.1:5000/main/ mostra ancora admin/index.html invece di main/index.html. Ho controllato app.url_map:

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

Inoltre, ho verificato che la funzione principale indice in/views.py è chiamato come previsto. Se rinominare main/index.html in qualcosa di diverso, allora funziona. Quindi, senza ridenominazione , come si può ottenere che 1http: //127.0.0.1: 5000/main/1 mostri main/index.html?

risposta

44

A partire da Flask 0.8, i blueprints aggiungono la template_folder specificata al searchpath dell'app, anziché trattare ognuna delle directory come entità separate. Ciò significa che se si hanno due template con lo stesso nome file, il primo trovato nel percorso di ricerca è quello utilizzato. Questo è certamente confuso, ed è scarsamente documentato in questo momento (vedi this bug). It seems che non sei l'unico che è stato confuso da questo comportamento.

Il motivo di progettazione di questo comportamento è che i modelli di progetto possono essere facilmente sostituiti dai modelli dell'applicazione principale, che sono i primi in linea nel percorso di ricerca del modello di Flask.

Due opzioni vengono in mente.

  • rinominare tutti i file index.html essere unico (ad esempio admin.html e main.html).
  • In ciascuna delle cartelle modello, inserire ciascuno dei modelli in una sottodirectory della cartella del modello e quindi chiamare il modello utilizzando tale sottodirectory. Il tuo modello di amministrazione, ad esempio, sarebbe yourapp/admin/pages/admin/index.html e quindi chiamato da all'interno del progetto come render_template('admin/index.html').
+0

Avendo problema simile. Vorrei che questo fosse gestito diversamente fuori dagli schemi. La modifica della posizione della cartella statica funziona correttamente con la pubblicazione dei file, ma il modello get viene sovrascritto se esiste già lo stesso file. –

+0

Come jay chan ha sottolineato il modo più semplice è quello di archiviare i modelli all'interno della cartella dei modelli principale in modo che admin.html sia archiviato in 'myapp/templates/admin/index.html' –

+0

Perché non cambi semplicemente' template_folder'? a '" admin/pages "' o '" main/pages "'? –

21

In aggiunta ai buoni suggerimenti di linqq sopra, è anche possibile sovrascrivere la funzionalità predefinita, se necessario. Ci sono un paio di modi:

si può ignorare create_global_jinja_loader in un'applicazione Flask sottoclasse (che restituisce un DispatchingJinjaLoader definito nel pallone/templating.py). Questo non è raccomandato, ma funzionerebbe. Il motivo per cui questo è scoraggiato è che il DispatchingJinjaLoader ha abbastanza flessibilità per supportare l'iniezione di caricatori personalizzati.E se avviti il ​​tuo caricatore, sarà in grado di appoggiarsi a funzionalità predefinite e sensate.

Quindi, si consiglia di sostituire "la funzione jinja_loader". È qui che manca la documentazione. La strategia di caricamento di Patching Flask richiede alcune conoscenze che non sembrano essere documentate, così come una buona conoscenza di Jinja2.

Ci sono due componenti è necessario comprendere:

  • L'ambiente Jinja2
  • Il modello di pala Jinja2

Questi sono creati da Flask, con ragionevoli valori predefiniti, automaticamente. (Puoi specificare il tuo , ignorando app.jinja_options - ma tieni a mente che perderai due estensioni che Flask include per impostazione predefinita - autoescape e with - a meno che non le specifichi tu stesso. boccetta/app.py per vedere come fanno riferimento quelli.)

L'ambiente contiene tutti questi processori di contesto (ad esempio, in modo da poter fare var|tojson in un modello), funzioni di supporto (url_for, ecc) e variabili (g, session, app). Contiene anche un riferimento a un caricatore di template, in questo caso il già citato e auto-istanziato DispatchingJinjaLoader. Quindi, quando chiami render_template nella tua app, trova o crea l'ambiente Jinja2, configura tutti quei gadget e chiama get_template su di esso, che a sua volta chiama get_source all'interno dello DispatchingJinjaLoader, che tenta alcune strategie descritte in seguito.

Se tutto va secondo i piani, tale catena si risolverà nel trovare un file e restituirà il suo contenuto (e alcuni other data). Inoltre, si noti che questo è lo stesso percorso di esecuzione utilizzato da {% extend 'foo.htm' %}.

DispatchingJinjaLoader fa due cose: prima controlla se il caricatore globale dell'applicazione, che è app.jinja_loader, può individuare il file. In caso contrario, controlla tutti i progetti di applicazione (in ordine di registrazione, AFAIK) per blueprint.jinja_loader nel tentativo di individuare il file. Tracciare la catena fino alla fine, ecco definizione di jinja_loader (nella beuta/helpers.py, _PackageBoundObject, la classe di base sia della domanda Flask e Blueprints):

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)) 

Ah! Quindi ora vediamo. Ovviamente, gli spazi dei nomi di entrambi saranno in conflitto sugli stessi nomi di directory. Dal momento che il caricatore globale viene chiamato per primo, vincerà sempre. (FileSystemLoader è uno dei diversi caricatori Jinja2 standard.) Tuttavia, ciò significa che non esiste un modo veramente semplice per riordinare Blueprint e il caricatore di modelli dell'applicazione.

Quindi, è necessario modificare il comportamento di DispatchingJinjaLoader. Per un po 'ho pensato che non ci fosse un buon modo non scoraggiato ed efficiente per far questo. Tuttavia, apparentemente se si sostituisce lo stesso app.jinja_options['loader'], è possibile ottenere il comportamento desiderato. Quindi, se eseguiamo la sottoclasse di DispatchingJinjaLoader e modifichiamo una piccola funzione (suppongo che potrebbe essere meglio reimplementarla interamente, ma questo funziona per ora), abbiamo il comportamento che vogliamo.In totale, una strategia ragionevole potrebbe essere il seguente (non testato, ma dovrebbe funzionare con le moderne applicazioni Flask):

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 

Questa modifica la strategia del caricatore originale in due modi: tentare di caricare dal progetto (e SOLO il modello correntemente in esecuzione, non tutti i progetti) prima, e se fallisce, solo allora carica dall'applicazione. Se ti piace il comportamento all-blueprint, puoi fare un po 'di copia-pasta da flask/templating.py.

Per legare tutto insieme, è necessario impostare jinja_options sull'oggetto Flask:

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) 

La prima volta che è necessario un ambiente modello (e quindi un'istanza), vale a dire la prima render_template volta che viene chiamato, il tuo caricatore dovrebbe essere usato

+0

È fantastico! Grazie per il consiglio! –

+0

Questo metodo consente l'ereditarietà dei modelli? – Andy

7

La risposta di twooster è interessante, ma un altro problema è che Jinja di default memorizza nella cache un modello basato sul suo nome. Poiché entrambi i modelli sono denominati "index.html", il caricatore non verrà eseguito per i progetti successivi.

Oltre ai due suggerimenti di linqq, una terza opzione è quella di ignorare l'opzione templates_folder del blueprint e posizionare i modelli nelle rispettive cartelle nella directory dei modelli dell'applicazione.

cioè:

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

Questa risposta (anche se Twoosters è stata ben risolta) ha più senso qui IMO perché se si sta tentando di ottenere l'effetto priorità locale, NON si utilizzano modelli a livello di modello per lo scopo previsto - da estendere da e/o sovrascritto da modelli specifici dell'applicazione. Più semplice di solito è meglio. (e in questo caso, molto più in linea con la natura dell'intenzione). –

+0

Se comprendo correttamente la tua risposta, tu dici che Jinja ha problemi di cache quando due modelli sono denominati 'index.html', ma poi consiglia un'alternativa che ha ancora due modelli chiamati' index.html'. Mi sto perdendo qualcosa? –

+0

La differenza sembra essere che i due nuovi file index.html si differenziano per il loro percorso nella cartella del modello, anziché essere direttamente sotto la cartella del modello del loro modello, come "mioapp/admin/templates/index.html" e " frontend/main/templates/index.html'. – Thinkable