Jinja2渲染HTML模板-python发送邮件html格式正文

背景

有用过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'</module>

报错内容很容易看出来这个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'</frozen></frozen></frozen></module>

这是因为jinja2无法找到这个包。

这里要注意一下包结构,我们追踪jinja2.PackageLoader的源码,查看它是如何处理模板包路径的

Jinja2渲染HTML模板-python发送邮件html格式正文

这里可以看到 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即可。

参考

pythonista模板_使用 jinja2 渲染 HTML 模板_weixin_39867509的博客-CSDN博客

Original: https://blog.csdn.net/m0_46427459/article/details/124832842
Author: m0_46427459
Title: Jinja2渲染HTML模板-python发送邮件html格式正文

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/749821/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球