web-dev-qa-db-fra.com

dossier de modèle de flacon

La mise en page de mon application de flacon est:

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

_init _ ​​ .py les fichiers sont vides. admin/views.py contenu est:

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

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

main/views.py est similaire à 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 est:

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

Maintenant, si j'accède à http://127.0.0.1:5000/admin/, il affiche correctement admin/index.html . Cependant, http://127.0.0.1:5000/main/ affiche toujours admin/index.html au lieu de main/index.html. J'ai vérifié app.url_map:

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

En outre, j'ai vérifié que la fonction d'index dans main/views.py s'appelle comme prévu . Si je renomme main/index.html en quelque chose de différent, cela fonctionne. Donc, sans Renommer, comment peut-on atteindre cet objectif? 1http: //127.0.0.1: 5000/main/1 affiche main/index.html?

41
synergetic

À partir du flacon 0.8, les plans ajoutent le template_folder spécifié au chemin de recherche de l'application, au lieu de traiter chacun des répertoires comme des entités séparées. Cela signifie que si vous avez deux modèles avec le même nom de fichier, le premier trouvé dans le chemin de recherche est celui utilisé. Ceci est certes déroutant et mal documenté à l’heure actuelle (voir ce bogue ). Il semble que tu n'étais pas le seul à avoir été dérouté par ce comportement.

La raison de ce comportement est que les modèles de plan peuvent facilement être remplacés des modèles de l'application principale, qui sont les premiers dans le chemin de recherche de modèle de Flask.

Deux options me viennent à l’esprit.

  • Renommez chacun des fichiers index.html pour qu'ils soient uniques (par exemple, admin.html Et main.html). 
  • Dans chacun des dossiers de modèles, placez chacun des modèles Dans un sous-répertoire du dossier blueprint, puis appelez le modèle À l'aide de ce sous-répertoire. Votre modèle d’administrateur, par exemple, serait yourapp/admin/pages/admin/index.html, puis appelé à partir de Le plan en tant que render_template('admin/index.html').
49
linqq

Outre les bonnes suggestions de linqq ci-dessus, vous pouvez également remplacer la fonctionnalité par défaut si nécessaire. Il y a deux manières:

On peut remplacer create_global_jinja_loader dans une application Flask sous-classée (qui retourne une DispatchingJinjaLoader définie dans flask/templating.py). Ce n'est pas recommandé, mais fonctionnerait. La raison pour laquelle cela est déconseillé est que la DispatchingJinjaLoader a suffisamment de souplesse pour prendre en charge l’injection de chargeurs personnalisés. Et si vous bousillez votre propre chargeur, il pourra s’appuyer sur des fonctionnalités saines par défaut.

Donc, ce qui est recommandé, c'est de "remplacer la fonction jinja_loader" à la place. C’est là que le manque de documentation entre en jeu. La stratégie de chargement de Patching Flask nécessite des connaissances qui ne semblent pas être documentées, ainsi qu’une bonne compréhension de Jinja2.

Vous devez comprendre deux composants:

  • L'environnement Jinja2
  • Le chargeur de modèles Jinja2

Celles-ci sont créées automatiquement par Flask, avec des valeurs sensibles par défaut. (Vous pouvez spécifier vos propres options Jinja2 , au fait, en remplaçant app.jinja_options - mais n'oubliez pas que vous perdrez deux extensions que Flask inclut par défaut - autoescape et with - à moins de les spécifier vous-même. Jetez un coup d’œil à flask/app.py pour voir comment ils se réfèrent à ceux-ci.)

L'environnement contient tous ces processeurs de contexte (vous pouvez donc utiliser var|tojson dans un modèle, par exemple), des fonctions d'assistance (url_for, etc.) et des variables (g, session, app). Il contient également une référence à un chargeur de modèles, dans ce cas la variable DispatchingJinjaLoader susmentionnée et auto-instanciée. Ainsi, lorsque vous appelez render_template dans votre application, il trouve ou crée l'environnement Jinja2, configure tous ces goodies et appelle get_template, qui appelle à son tour get_source à l'intérieur de la DispatchingJinjaLoader, qui tente quelques stratégies décrites plus loin.

Si tout se passe comme prévu, cette chaîne résoudra la recherche d'un fichier et renverra son contenu (et certaines autres données ). Notez également qu'il s'agit du même chemin d'exécution que {% extend 'foo.htm' %}.

DispatchingJinjaLoader fait deux choses: Tout d'abord, il vérifie si le chargeur global de l'application, c'est-à-dire app.jinja_loader, peut localiser le fichier. Sinon, il vérifie tous les plans de l'application (par ordre d'enregistrement, autant que je sache) pour blueprint.jinja_loader afin de localiser le fichier. En traçant cette chaîne à la toute fin, voici la définition de jinja_loader (dans flask/helpers.py, _PackageBoundObject, la classe de base de l’application Flask et de 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! Alors maintenant on voit. Évidemment, les espaces de noms des deux seront en conflit sur les mêmes noms de répertoire. Puisque le chargeur global est appelé en premier, il gagnera toujours. (FileSystemLoader est l'un des nombreux chargeurs Jinja2 standard.) Toutefois, cela signifie qu'il n'existe pas de moyen vraiment simple de réorganiser le Blueprint et le chargeur de modèles à l'échelle de l'application.

Nous devons donc modifier le comportement de DispatchingJinjaLoader. Pendant un certain temps, j’ai pensé qu’il n’y avait pas de bonne manière efficace de ne pas se décourager et d’être efficace. Cependant, apparemment, si vous remplacez app.jinja_options['loader'] lui-même, nous pouvons obtenir le comportement souhaité. Donc, si nous sous-classe DispatchingJinjaLoader et modifions une petite fonction (je suppose qu'il serait préférable de la réimplémenter entièrement, mais cela fonctionne pour le moment), nous avons le comportement que nous souhaitons. Au total, une stratégie raisonnable serait la suivante (non testé, mais devrait fonctionner avec les applications Flask modernes):

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

Cela modifie la stratégie du chargeur d'origine de deux manières: Tentative de chargement à partir du plan directeur (et UNIQUEMENT du plan d'action en cours d'exécution, pas tous les plans) et si cela échoue, chargez uniquement à partir de l'application. Si vous aimez le comportement "tout plan", vous pouvez faire des copies de pâtes à partir de flask/templating.py.

Pour tout lier, vous devez définir jinja_options sur l'objet 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 première fois qu'un environnement de modèle est nécessaire (et donc instancié), ce qui signifie que la première fois que render_template est appelé, votre chargeur doit être utilisé.

22
twooster

la réponse de twooster est intéressante, mais un autre problème est que Jinja met par défaut en cache un modèle basé sur son nom. Les deux modèles étant nommés "index.html", le chargeur ne s'exécutera pas pour les modèles suivants.

Outre les deux suggestions de linqq, une troisième option consiste à ignorer l'option templates_folder du plan directeur et à placer les modèles dans des dossiers respectifs du répertoire de modèles de l'application.

c'est à dire:

myapp/templates/admin/index.html
myapp/templates/main/index.html
8
Jay Chan

J'utilise quelque chose comme ceci sur fypress et fybb car j'ai un système de thèmes. 

# utils.templates
from jinja2 import Environment, PackageLoader
from flask.templating import _default_template_ctx_processor
from flask import current_app, url_for, get_flashed_messages


admin_env = Environment(
    loader=PackageLoader('fypress', '/templates/admin/'),
    extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'],
    autoescape=True
)

def render_template(template, **kwargs):
    kwargs.update(_default_template_ctx_processor())
    kwargs.update({
        'url_for': url_for,
        'get_flashed_messages': get_flashed_messages # etc...

    })
    kwargs.update(dict(debug=current_app.config.get('DEBUG'), flask_config=current_app.config))

    template = admin_env.get_template(template)
    return template.render(**kwargs)

Et alors

# routes.admin.
from flask import Blueprint
from utils.templates import render_template

admin_bp = Blueprint('admin', __name__,  url_prefix='/admin')

@admin_bp.route('/')
def root():
    return render_template('index.html', title='Admin')
0
Fy-

Tks @linqq, votre méthode fonctionne vraiment bien ici, en plus j'ai fait une meilleure solution par le décorateur.

Attention ici, n'importez pas la fonction render_template comme ceci:

from flask import render_template

Vous devriez importer le module de flacon comme ceci:

import flask

Ensuite, placez ce bloc de code en haut de votre fichier de routeur:

def render_decorate(path_prefix):
    def decorate(func):
        def dec_func(*args, **kw):
            arg_list = list(args)
            arg_list[0] = path_prefix + str(arg_list[0])
            arg_Tuple = Tuple(arg_list)
            return func(*arg_Tuple, **kw)
        return dec_func
    return decorate

@render_decorate("%YOUR_DIRECTORY_NAME%/")
def render_template(template_name_or_list, **context):
    return flask.render_template(template_name_or_list, **context)

Remplacez le% YOUR_DIRECTORY_NAME% par votre chemin actuel et assurez-vous que votre dossier de modèles ressemble à ceci: Structure du dossier

Et tout est fait! Utilisez simplement la fonction render_template comme d'habitude.

0
Artrix