模板文件夹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 的值.pngsearchpath 的值
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