美文网首页Web开发程序员FLask初探
FLask初探四 ( 确定项目模板的加载路径)

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

作者: DragonFangQy | 来源:发表于2018-06-25 15:33 被阅读11次

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

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

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

    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

    相关文章

      网友评论

        本文标题:FLask初探四 ( 确定项目模板的加载路径)

        本文链接:https://www.haomeiwen.com/subject/ltyyyftx.html