frasco

2011-11-02 17 views
36

Mi diseño de aplicaciones frasco es:frasco

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

_ init _ archivos .py están vacías. admin/views.py contenido es:

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

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

principal/views.py es similar 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 es:

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

Ahora, si tengo acceso a http://127.0.0.1:5000/admin/, muestra correctamente admin/index.html. Sin embargo, http://127.0.0.1:5000/main/ muestra todavía admin/index.html en lugar de main/index.html. Revisé app.url_map:

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

También pude comprobar que la función principal índice en/views.py se llama como se esperaba. Si cambio el nombre de main/index.html a algo diferente, entonces funciona. Entonces, sin cambiar el nombre, ¿cómo se puede lograr que 1http: //127.0.0.1: 5000/main/1 muestre main/index.html?

Respuesta

44

A partir de Flask 0.8, los planos añaden la plantilla_carpeta especificada a la ruta de búsqueda de la aplicación, en lugar de tratar cada uno de los directorios como entidades separadas. Esto significa que si tiene dos plantillas con el mismo nombre de archivo, la primera que se encuentra en el buscador es la que se usa. Esto es ciertamente confuso, y está mal documentado en este momento (ver this bug). It seems que no fuiste el único confundido por este comportamiento.

El motivo de diseño de este comportamiento es que las plantillas de planos pueden anularse fácilmente desde las plantillas de la aplicación principal, que son las primeras en línea en el recorrido de búsqueda de plantillas de Flask.

Dos opciones vienen a la mente.

  • Cambiar nombre de cada uno de los archivos index.html que ser único (por ejemplo admin.html y main.html).
  • En cada una de las carpetas de plantillas, coloque cada una de las plantillas en un subdirectorio de la carpeta de planos y luego llame a usando ese subdirectorio. Su plantilla de administrador, por ejemplo, sería yourapp/admin/pages/admin/index.html, y luego se llama desde el modelo como render_template('admin/index.html').
+0

Después de haber problema similar. Desearía que esto se manejara de manera diferente de la caja. Cambiar la ubicación de la carpeta estática funciona bien con los archivos de publicación, pero la plantilla get se reemplaza si el mismo archivo ya existe. –

+0

Como jay chan había señalado, la forma más sencilla es almacenar plantillas dentro de la carpeta de plantillas principal para que admin.html se almacene en 'myapp/templates/admin/index.html' –

+0

¿Por qué no cambias' template_folder' a '" admin/pages "' o '" main/pages "'? –

21

Además de las buenas sugerencias de linqq anteriores, también puede anular la funcionalidad predeterminada si es necesario. Hay un par de formas:

Se puede anular create_global_jinja_loader en una aplicación Flask subclasificada (que devuelve DispatchingJinjaLoader definida en el matraz/templating.py). Esto no es recomendable, pero funcionaría. La razón por la cual esto se desaconseja es que el DispatchingJinjaLoader tiene suficiente flexibilidad para admitir la inyección de cargadores personalizados.Y si atornillas tu propio cargador, podrá apoyarse en funcionalidad sana y predeterminada.

Por lo tanto, lo que se recomienda es que uno "anule la función jinja_loader" en su lugar. Aquí es donde entra la falta de documentación. La estrategia de carga de Patching Flask requiere un conocimiento que no parece estar documentado, así como una buena comprensión de Jinja2.

Hay dos componentes que necesita para comprender:

  • El entorno Jinja2
  • La plantilla cargador Jinja2

Estos son creados por frasco, con parámetros por defecto, de forma automática. (Por cierto, puede especificar su propio Jinja2 options anulando app.jinja_options, pero tenga en cuenta que perderá dos extensiones que Flask incluye de manera predeterminada, autoescape y with, a menos que usted mismo las haya especificado. Eche un vistazo a frasco/app.py para ver la forma en que hacen referencia a aquellos.)

el medio ambiente contiene todos esos procesadores de contexto (por ejemplo, para que pueda hacer var|tojson en una plantilla), funciones de ayuda (url_for, etc.) y variables (g, session, app). También contiene una referencia a un cargador de plantillas, en este caso, el mencionado DispatchingJinjaLoader autoejecutivo. Entonces, cuando llamas al render_template en tu aplicación, encuentra o crea el entorno Jinja2, configura todas esas cosas y llama al get_template, que a su vez llama al get_source dentro del DispatchingJinjaLoader, que intenta algunas estrategias que se describen más adelante.

Si todo va según lo planeado, esa cadena se resolverá al encontrar un archivo y devolverá su contenido (y algunos other data). Además, tenga en cuenta que esta es la misma ruta de ejecución que toma {% extend 'foo.htm' %}.

DispatchingJinjaLoader hace dos cosas: Primero comprueba si el cargador global de la aplicación, que es app.jinja_loader puede localizar el archivo. En su defecto, comprueba todos los planos de aplicación (en orden de registro, AFAIK) para blueprint.jinja_loader en un intento de localizar el archivo. Rastreo de esa cadena hasta el final, aquí es la definición de jinja_loader (en matraz/helpers.py, _PackageBoundObject, la clase base tanto de la aplicación Flask y modelos):

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! Entonces ahora vemos. Obviamente, los espacios de nombres de ambos entrarán en conflicto con los mismos nombres de directorio. Como el cargador global se llama primero, siempre ganará. (FileSystemLoader es uno de varios cargadores Jinja2 estándar.) Sin embargo, lo que esto significa es que no hay una manera verdaderamente simple de reordenar el Blueprint y el cargador de plantillas de toda la aplicación.

Por lo tanto, tenemos que modificar el comportamiento de DispatchingJinjaLoader. Por un tiempo, pensé que no había una forma buena y no desalentada de hacer esto. Sin embargo, al parecer, si anulas app.jinja_options['loader'], podemos obtener el comportamiento que deseamos. Entonces, si subclasificamos DispatchingJinjaLoader, y modificamos una función pequeña (supongo que sería mejor volver a implementarla por completo, pero esto funciona por ahora), tenemos el comportamiento que queremos.En total, una estrategia razonable sería el siguiente (no probado, pero debería funcionar con aplicaciones Frasco modernas):

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 

Esto modifica la estrategia del cargador original en dos formas: Intento de cargar con el proyecto básico (y sólo el plano que se está ejecutando actualmente, no todos los planos) primero, y si eso falla, solo entonces cargue desde la aplicación. Si le gusta el comportamiento all-blueprint, puede hacer un poco de copy-pasta desde el matraz/templating.py.

Para unirlo todo, usted tiene que fijar jinja_options en el objeto Frasco:

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 primera vez que se necesita un entorno de plantilla (y por lo tanto crea una instancia), lo que significa la primera render_template vez que se llama, su cargador debe ser utilizado.

+0

¡Esto es asombroso! ¡Gracias por el consejo! –

+0

¿Este método permite la herencia de la plantilla? – Andy

7

La respuesta de twooster es interesante, pero otro problema es que Jinja almacena en caché de forma predeterminada una plantilla basada en su nombre. Debido a que ambas plantillas se denominan "index.html", el cargador no se ejecutará para los planos siguientes.

Además de las dos sugerencias de linqq, una tercera opción es ignorar la opción templates_folder del blueprint y colocar las plantillas en las carpetas respectivas en el directorio de plantillas de la aplicación.

es decir:

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

Esta respuesta (aunque Twoosters estuvo bien resuelto) tiene más sentido aquí IMO porque si estás tratando de lograr el efecto de prioridad local, entonces NO estás usando plantillas de nivel de proyecto para su propósito previsto-- extenderse de y/o reemplazado por plantillas específicas de la aplicación. Más simple es usualmente mejor. (y en este caso, mucho más en línea con la naturaleza de la intención). –

+0

Si entiendo correctamente tu respuesta, dices que Jinja tiene problemas de caché cuando dos plantillas se llaman 'index.html' pero luego recomiendan una alternativa que todavía tenga dos plantillas llamadas' index.html'. ¿Me estoy perdiendo de algo? –

+0

La diferencia parece ser que los dos nuevos archivos index.html se diferencian por su ruta en la carpeta de plantilla, en lugar de estar directamente debajo de la carpeta de plantilla de su plano, como 'myapp/admin/templates/index.html' y ' myapp/main/templates/index.html '. – Thinkable