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

那么问题来了, 为什么是页面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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容