Web开发程序员FLask初探

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

2018-06-25  本文已影响11人  DragonFangQy

模板文件夹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

那么问题来了, 为什么是页面1 不是页面2? Flask初探一(Flask 各参数的应用) 中介绍了各参数的作用, 其中template_folder 就是模板文件的文件夹, 要想清楚为什么是页面1 不是页面2 , 就要先弄清楚template_folder 文件夹的路径问题

官方注释

: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.

可以从注释得出一个结论, templates 路径和 root path 有关, 那么 Flask初探一(Flask 各参数的应用) 中得到一个结论

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)

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

 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
 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开始回答.

Flask初探一 已经知道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

上一篇下一篇

猜你喜欢

热点阅读