您的当前位置:首页正文

FLask初探四 ( 确定项目模板的加载路径)

来源:要发发知识网

模板文件夹templates

模板文件夹的是怎么确定的? 放到什么位置才能保证模板能被正确加载 / 或者访问?

项目目录

01-测试目录结构.png

可以看出有两个模板文件夹templates

News\info\templates\news\demo.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
我是页面1,我在 E:\workspace\git\News\info\templates\news\demo.html
</body>
</html>

News\templates\news\demo.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
我是页面2,我在 E:\workspace\git\News\templates\news\demo.html
</body>
</html>

main.py

from flask_migrate import MigrateCommand
from flask_script import Manager

from application.config import DevelopmentConfig
from info import create_app

app = create_app(DevelopmentConfig)
manager = Manager(app)
manager.add_command("db", MigrateCommand)

if __name__ == '__main__':
    manager.run()

E:\workspace\git\News\info_init_.py

import redis
from flask import Flask
from flask_migrate import Migrate
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy

from application.config import Config

db_SQLAlchemy = SQLAlchemy()
redis_store = None  # type:redis.StrictRedis


def create_app(config_name: Config):
    global redis_store

    # 重点在这 __name__
    app = Flask(__name__,
                instance_path="E:\\workspace\\git\\News\\config_private",
                instance_relative_config=True)
    
    app.config.from_pyfile("config_private.py")
    app.config.from_object(app.config["CONFIGPRIVATE"])
    app.config.from_object(config_name)

    redis_store = redis.StrictRedis(host=app.config["CONFIGPRIVATE"].REDIS_HOST,
                                    port=app.config["CONFIGPRIVATE"],
                                    db=app.config["CONFIGPRIVATE"].REDIS_DB)
    db_SQLAlchemy.init_app(app)
    Session(app)
    Migrate(app, db_SQLAlchemy)

    from info.modules.index import index_blue
    app.register_blueprint(index_blue)

    return app


if __name__ == '__main__':
    create_app(Config)

views.py

from flask import render_template

from . import index_blue


@index_blue.route('/')
def index():
    print("index")
    return render_template("news/index.html")


@index_blue.route('/demo')
def demo():
    print("demo")
    return render_template("news/demo.html")

如果我要加载页面是加载demo.html , 程序是加载页面1 还是页面2 ?

运行结果

02-运行结果.png

官方注释

:param template_folder: the folder that contains the templates that should
be used by the application. Defaults to
'templates' folder in the root path of the
application.

root_path: 默认情况下,flask将自动计算引用程序根的绝对路径, 由import_name 决定.

所以可以从import_name 出发, 研究加载template_folder 的路径

默认情况

E:\workspace\git\News\info_init_.py

import redis
from flask import Flask
from flask_migrate import Migrate
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy

from application.config import Config

db_SQLAlchemy = SQLAlchemy()
redis_store = None  # type:redis.StrictRedis


def create_app(config_name: Config):
    global redis_store
    
    # 重点 import_name 为 __name__
    app = Flask(__name__,
                instance_path="E:\\workspace\\git\\News\\config_private",
                instance_relative_config=True)

    app.config.from_pyfile("config_private.py")
    app.config.from_object(app.config["CONFIGPRIVATE"])
    app.config.from_object(config_name)

    redis_store = redis.StrictRedis(host=app.config["CONFIGPRIVATE"].REDIS_HOST,
                                    port=app.config["CONFIGPRIVATE"],
                                    db=app.config["CONFIGPRIVATE"].REDIS_DB)
    db_SQLAlchemy.init_app(app)
    Session(app)
    Migrate(app, db_SQLAlchemy)

    from info.modules.index import index_blue
    app.register_blueprint(index_blue)

    return app


if __name__ == '__main__':
    create_app(Config)

删除demo.html,从报错信息查找路径

报错信息

Traceback (most recent call last):
  File "D:\software\Python\lib\site-packages\flask\app.py", line 1836, in __call__
    return self.wsgi_app(environ, start_response)
  File "D:\software\Python\lib\site-packages\flask\app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "D:\software\Python\lib\site-packages\flask\app.py", line 1403, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "D:\software\Python\lib\site-packages\flask\_compat.py", line 33, in reraise
    raise value
  File "D:\software\Python\lib\site-packages\flask\app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "D:\software\Python\lib\site-packages\flask\app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "D:\software\Python\lib\site-packages\flask\app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "D:\software\Python\lib\site-packages\flask\_compat.py", line 33, in reraise
    raise value
  File "D:\software\Python\lib\site-packages\flask\app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "D:\software\Python\lib\site-packages\flask\app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "E:\workspace\git\News\info\modules\index\views.py", line 15, in demo
    return render_template("news/demo.html")
  File "D:\software\Python\lib\site-packages\flask\templating.py", line 127, in render_template
    return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list),
  File "D:\software\Python\lib\site-packages\jinja2\environment.py", line 869, in get_or_select_template
    return self.get_template(template_name_or_list, parent, globals)
  File "D:\software\Python\lib\site-packages\jinja2\environment.py", line 830, in get_template
    return self._load_template(name, self.make_globals(globals))
  File "D:\software\Python\lib\site-packages\jinja2\environment.py", line 804, in _load_template
    template = self.loader.load(self, name, globals)
  File "D:\software\Python\lib\site-packages\jinja2\loaders.py", line 113, in load
    source, filename, uptodate = self.get_source(environment, name)
  File "D:\software\Python\lib\site-packages\flask\templating.py", line 64, in get_source
    raise TemplateNotFound(template)
jinja2.exceptions.TemplateNotFound: news/demo.html

看这里

File "D:\software\Python\lib\site-packages\jinja2\loaders.py", line 113, in load
source, filename, uptodate = self.get_source(environment, name)

执行完这一句之后报错了, 那么我们在这里断点查看一下

  • self.get_source(environment, name) 是一个方法,进入
  • 进入之后可以看到如下方法
 def get_source(self, environment, template):
        for loader, local_name in self._iter_loaders(template):
            try:
                return loader.get_source(environment, local_name)
            except TemplateNotFound:
                pass
  • 进入 loader.get_source(environment, local_name) 方法
 def get_source(self, environment, template):
        pieces = split_template_path(template)
        for searchpath in self.searchpath:
            filename = path.join(searchpath, *pieces)
            f = open_if_exists(filename)
            if f is None:
                continue
            try:
                contents = f.read().decode(self.encoding)
            finally:
                f.close()

            mtime = path.getmtime(filename)

            def uptodate():
                try:
                    return path.getmtime(filename) == mtime
                except OSError:
                    return False
            return contents, filename, uptodate
        raise TemplateNotFound(template)

03-模板的默认搜索路径.png

在我的项目中, 默认情况下是在 'E:\workspace\git\News\info\templates' 路径下搜索 templates 模板文件夹, 为什么呢?
因为root_path 是根据import_name 由flask 自动计算出的路径, 如果想改变 templates 模板文件夹的搜索路径, 只需要改变
import_name 就可以实现.

验证

上面我们推测出一个结论

如果想改变 templates 模板文件夹的搜索路径, 只需要改变import_name 就可以实现.

因为 E:\workspace\git\News\templates\news\demo.html 在项目的目录的根目录下, 所以要将root_path 的路径改为E:\workspace\git\News ,这时 templates 模板文件夹的搜索路径才可能是 E:\workspace\git\News\templates, 假设
import_name 等于"News" ,既项目的根目录文件夹, 查看root_path 以及 模板文件夹的搜索路径searchpath 的变化

实验

E:\workspace\git\News\info_init_.py

import redis
from flask import Flask
from flask_migrate import Migrate
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy

from application.config import Config

db_SQLAlchemy = SQLAlchemy()
redis_store = None  # type:redis.StrictRedis


def create_app(config_name: Config):
    global redis_store

    # 让import_name 等于"News"
    app = Flask("News",
                instance_path="E:\\workspace\\git\\News\\config_private",
                instance_relative_config=True)

    app.config.from_pyfile("config_private.py")
    app.config.from_object(app.config["CONFIGPRIVATE"])
    app.config.from_object(config_name)

    redis_store = redis.StrictRedis(host=app.config["CONFIGPRIVATE"].REDIS_HOST,
                                    port=app.config["CONFIGPRIVATE"],
                                    db=app.config["CONFIGPRIVATE"].REDIS_DB)
    db_SQLAlchemy.init_app(app)
    Session(app)
    Migrate(app, db_SQLAlchemy)

    from info.modules.index import index_blue
    app.register_blueprint(index_blue)

    return app


if __name__ == '__main__':
    create_app(Config)

运行结果

root_path 的值

04-root_path 的值.png

searchpath 的值

05-searchpath 的值.png

页面显示

06-页面显示.png

通过以上结果证实确实可以通过改变 import_name 进而改变模板文件夹的搜索路径. 那么是怎么影响的?这个问题不得不从import_name 是如何得到root_path开始回答.

get_root_path

def get_root_path(import_name):
    """Returns the path to a package or cwd if that cannot be found.  This
    returns the path of a package or the folder that contains a module.

    Not to be confused with the package path returned by :func:`find_package`.
    """
    # Module already imported and has a file attribute.  Use that first.
    mod = sys.modules.get(import_name)
    if mod is not None and hasattr(mod, '__file__'):
        return os.path.dirname(os.path.abspath(mod.__file__))

    # Next attempt: check the loader.
    loader = pkgutil.get_loader(import_name)

    # Loader does not exist or we're referring to an unloaded main module
    # or a main module without path (interactive sessions), go with the
    # current working directory.
    if loader is None or import_name == '__main__':
        return os.getcwd()

    # For .egg, zipimporter does not have get_filename until Python 2.7.
    # Some other loaders might exhibit the same behavior.
    if hasattr(loader, 'get_filename'):
        filepath = loader.get_filename(import_name)
    else:
        # Fall back to imports.
        __import__(import_name)
        filepath = sys.modules[import_name].__file__

    # filepath is import_name.py for a module, or __init__.py for a package.
    return os.path.dirname(os.path.abspath(filepath))

通过get_root_path 方法传入import_name 得到root_path.
这个方法大致分为三个部分 Module already / check the loader / other loaders, 在前面文章中研究了Module already,这里研究check the loader的部分,
既下面的代码

    # Next attempt: check the loader.
    loader = pkgutil.get_loader(import_name)

    # Loader does not exist or we're referring to an unloaded main module
    # or a main module without path (interactive sessions), go with the
    # current working directory.
    if loader is None or import_name == '__main__':
        return os.getcwd()

通过这里可以看出当 loader is None 或者 import_name == 'main': 时, 将 当前的工作目录 ,在我当前的项目既是E:\workspace\git\News 这个路径. 所以可以推测模板搜索路径是在roo_path 的下级目录寻找templates 模板文件夹, 进而寻找模板文件.


到此结  DragonFangQy 2018.6.25