赞
踩
有用过Flask的同学应该都知道,flask创建上下文之后就可以使用render_template(基于Jinja2模板引擎)去渲染HTML页面了。看这个函数的源码我们可以发现,渲染之前会创建一个ctx去获取当前环境的app变量。然后通过这个ctx去渲染传进来的context。
- # flask的render_template源码
- def render_template(
- template_name_or_list: t.Union[str, t.List[str]], **context: t.Any
- ) -> str:
- """Renders a template from the template folder with the given
- context.
- :param template_name_or_list: the name of the template to be
- rendered, or an iterable with template names
- the first one existing will be rendered
- :param context: the variables that should be available in the
- context of the template.
- """
- ctx = _app_ctx_stack.top
- ctx.app.update_template_context(context)
- return _render(
- ctx.app.jinja_env.get_or_select_template(template_name_or_list),
- context,
- ctx.app,
- )
现在我需要在邮件正文中嵌入HTML内容,HTML里面的内容是动态变化的,因此我想到可以基于这个render_template像flask一样去渲染一个html,然后再每次动态传入context即可。
直接上代码
- # test.html
- <p style="color: red">{{test}}</p>
-
- # test.py
- from flask import render_template
- msg = render_template('test.html', test="123")
运行一下test.py就会发现出错
- Traceback (most recent call last):
- File "E:\code\flask_demo\test.py", line 3, in <module>
- msg = render_template('test.html', test="123")
- File "E:\code\flask_demo\venv\lib\site-packages\flask\templating.py", line 146, in render_template
- ctx.app.update_template_context(context)
- AttributeError: 'NoneType' object has no attribute 'app'
报错内容很容易看出来这个ctx是None,因此没有app这个属性,那为什么ctx是None呢。通过跟踪源码可以发现,此时我们并没有启动flask 应用,并不存在上下文对象,ctx是从栈里拿的,因为没有所以返回了None。
那如何解决呢,我们肯定没必要去启动一个Flask应用对吧。
Flask把jinja2的模板渲染又经过了一层封装,因此会出现这个问题,所以我们直接跳过Flask去调用Jinja2的渲染机制不香吗。(记得安装jinja2)
代码如下:
- #test.py
- import jinja2
-
- def render_without_flask(template_name, **context):
- env = jinja2.Environment(
- loader=jinja2.PackageLoader('package', '/')
- )
- template = env.get_template(template_name)
- return template.render(**context)
-
- msg = render_without_flask('test.html', test="213")
运行一下test.py然后就报错了。。。
- Traceback (most recent call last):
- File "E:\code\flask_demo\test.py", line 10, in <module>
- msg = render_without_flask('test.html', test="213")
- File "E:\code\flask_demo\test.py", line 5, in render_without_flask
- loader=jinja2.PackageLoader('package', '/')
- File "E:\code\flask_demo\venv\lib\site-packages\jinja2\loaders.py", line 287, in __init__
- import_module(package_name)
- File "D:\Python3.9.6\lib\importlib\__init__.py", line 127, in import_module
- return _bootstrap._gcd_import(name[level:], package, level)
- File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
- File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
- File "<frozen importlib._bootstrap>", line 984, in _find_and_load_unlocked
- ModuleNotFoundError: No module named 'package'
这是因为jinja2无法找到这个包。
这里要注意一下包结构,我们追踪jinja2.PackageLoader的源码,查看它是如何处理模板包路径的
这里可以看到 PackageLoader去导入包(包含html文件的那个目录/文件夹),并且这里没有指定从哪里去导入这个包(默认是从当前py文件的目录下的templates目录去找这个模板,但是我们前面PackageLoader传入了参数'package',所以它会在sys.path的所有目录下找package目录,因为都不存在所以就报错了),所以它会从sys.path里面的所有路径依次去寻找这个包,找了所有路径都没有package文件夹,因此报错。
PackageLoader的构造函数参数
package_name指的是包含html文件的包的文件夹名。
package_path指的是包含html文件的包名。
因此如果说我们的html文件的绝对路径是这样的:E:\code\flask_demo\test.html
那我们的package_name就传入'code',package_path就传入'flask_demo',并且python解释器还要能找到'code'这个文件夹,所以我们需要把'E:'加入sys.path
即sys.path.append('E:')
当然我们也可以这样子传参:package_name='flask_demo',package_path='/',这个/是相对路径来的,表示当前目录,其实就是flask_demo,那这时解释器要怎么找到flask_demo这个文件夹呢?那当然是从E:\code里面去寻找对吧,所以我们需要用sys.path.append(r'E:\code')把这个路径加到解释器的"寻址路径"里面去。
下面我重新写一下这个渲染的函数,输入的参数改成要渲染的模板(html)的绝对路径和模板上下文变量。
- import os
- import sys
- import jinja2
- def render_without_flask(template_path, **context):
- dirs = os.path.split(template_path)[0]
- file = os.path.split(template_path)[-1]
- sys.path.append(os.path.split(dirs)[0])
- env = jinja2.Environment(
- loader=jinja2.PackageLoader(os.path.split(dirs)[-1], '/')
- )
- template = env.get_template(file)
- return template.render(**context)
接下来我们就可以调用这个函数像flask.render_template一样去渲染html模板啦!
至于发送如何发送邮件,大家自行搜索吧!!!网上有很多例子,只不过我们需要把邮件内容content换成html即可。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。