
Flask 教程用 Jinja 模板渲染 Flaskr 页面——模板继承、自动转义与表单实战【免费下载链接】flaskThe Python micro framework for building web applications.项目地址: https://gitcode.com/gh_mirrors/fl/flask本篇是 Flask 官方教程Flaskr 博客应用的模板章节视图函数已经通过render_template指向了auth/register.html、auth/login.html等模板文件但模板本身尚未创建因此访问任何 URL 都会抛出TemplateNotFound错误。读完本文你将掌握 Jinja 模板的基础语法、Flask 的自动转义安全机制、如何用{% extends %}与{% block %}实现基础布局继承以及如何结合g.user、url_for、get_flashed_messages渲染带用户状态与错误提示的登录/注册表单并完成一次真实的注册流程验证。为什么页面会报 TemplateNotFound在 views.rst 一节中注册视图与登录视图已经调用了render_templatereturn render_template(auth/register.html)但如果此刻运行服务器并访问任意 URL你会看到TemplateNotFound错误。原因很简单视图在请求模板而模板文件还不存在。Flask 的模板文件存放在flaskr包内的templates目录中即 examples/tutorial/flaskr/templates/本节的后续步骤就是在该目录下逐个创建这些文件。模板Template是同时包含静态数据与动态数据占位符的文件。模板被渲染时会用具体的数据替换占位符最终生成一份完整的文档。Flask 使用 Jinja 模板库来完成渲染。在本应用中模板用于生成最终显示在用户浏览器中的 HTML 页面。render_template 的底层调用链从源码看render_template的实现位于 src/flask/templating.pydef render_template(template_name_or_list, **context): ctx app_ctx._get_current_object() template ctx.app.jinja_env.get_or_select_template(template_name_or_list) return _render(ctx, template, context)它从当前应用上下文中取出jinja_env按名称加载模板再通过_render渲染。_rendersrc/flask/templating.py会依次执行app.update_template_context注入全局模板上下文、发送before_render_template信号、执行template.render(context)最后发送template_rendered信号——这也是为什么扩展可以通过信号在渲染前后介入。Flask 如何找到模板文件Flask 为应用和蓝图分别管理模板目录。在 src/flask/sansio/scaffold.py 中可以看到模板目录由template_folder参数决定if self.template_folder is not None: return FileSystemLoader(os.path.join(self.root_path, self.template_folder))templates是应用和蓝图默认的template_folder。Flask 使用DispatchingJinjaLoadersrc/flask/templating.py在应用目录与所有已注册蓝图的目录中依次查找模板先查应用自身的jinja_loader再遍历iter_blueprints()检查每个蓝图的加载器全部找不到才抛出TemplateNotFound。这正是教程中把各蓝图的模板放在与蓝图同名的子目录如templates/auth/也能被正确找到的原因。自动转义默认开启的 HTML 安全机制在 Flask 中Jinja 被配置为对 HTML 模板中渲染的所有数据执行自动转义autoescape。这意味着渲染用户输入是安全的用户输入的、等可能破坏 HTML 结构的字符会被转义成浏览器中显示效果相同、但不会产生副作用的安全值。因此用户注册的用户名即使包含script之类的字符串也只会被当作普通文本显示而不会被当作代码执行。Jinja 模板语法速览Jinja 的语法与 Python 十分相似但使用特殊的定界符delimiter来区分 Jinja 语法与模板中的静态数据{{ 和 }}之间的内容是表达式其求值结果会被输出到最终文档中例如{{ g.user[username] }}{% 和 %}表示控制流语句如{% if %}、{% for %}与 Python 不同Jinja 的代码块不使用缩进区分层级而是用开始标签与结束标签如{% if %}…{% endif %}标示因为块内的静态文本会破坏缩进的可读性。此外模板中还包含{# ... #}形式的注释渲染时不会输出。基础布局base.html应用的每个页面都围绕不同的内容共享相同的基本布局。与其在每个模板中重复编写完整的 HTML 结构不如让每个模板**继承extend**一个基础模板并覆写其中的特定区块。基础模板位于templates目录直接下即 examples/tutorial/flaskr/templates/base.html!doctype html title{% block title %}{% endblock %} - Flaskr/title link relstylesheet href{{ url_for(static, filenamestyle.css) }} nav h1a href{{ url_for(index) }}Flaskr/a/h1 ul {% if g.user %} lispan{{ g.user[username] }}/span lia href{{ url_for(auth.logout) }}Log Out/a {% else %} lia href{{ url_for(auth.register) }}Register/a lia href{{ url_for(auth.login) }}Log In/a {% endif %} /ul /nav section classcontent header {% block header %}{% endblock %} /header {% for message in get_flashed_messages() %} div classflash{{ message }}/div {% endfor %} {% block content %}{% endblock %} /sectiong.user模板中自动可用的请求级全局对象:data:.g在模板中自动可用。根据g.user是否被设置由视图层 [examples/tutorial/flaskr/auth.py](https://link.gitcode.com/i/5666d20daeb1c10680cb61b509541c0e) 的load_logged_in_user 函数在每次请求前从 session 中加载用户数据填充导航栏会显示两种状态已登录时显示用户名与「Log Out」链接未登录时显示「Register」与「Log In」链接。g之所以在模板中无需手动传入是因为 Flask 的默认模板上下文处理器_default_template_ctx_processorsrc/flask/templating.py会把当前应用上下文中的g对象注入到每次渲染的上下文中当存在请求上下文时还会注入request对象。url_for按端点生成 URLurl_for同样在模板中自动可用用于根据视图的端点endpoint名称生成 URL而不是手写路径。例如url_for(index)生成首页 URLurl_for(auth.login)生成登录页 URL。这样做的好处是日后修改路由路径时无需改动任何引用它的代码。端点名称的规则在 views.rst 中已有说明应用内视图的端点默认等于函数名如index而注册在蓝图中的视图端点会加上蓝图名前缀如auth.login。在 examples/tutorial/flaskr/init.py 中可以看到教程用app.add_url_rule(/, endpointindex)将博客蓝图根路径绑定到index端点使url_for(index)等于url_for(blog.index)。闪现消息的展示循环在页面标题之后、正文内容之前基础模板循环遍历get_flashed_messages()返回的每条消息{% for message in get_flashed_messages() %} div classflash{{ message }}/div {% endfor %}视图层用flash(error)见 examples/tutorial/flaskr/auth.py存储的错误消息就是通过这里的循环显示出来的。三个可覆写的区块基础模板定义了三个将在其他模板中被覆写的区块{% block title %}改变浏览器标签页与窗口标题中显示的文字{% block header %}与title类似但改变的是页面内显示的标题{% block content %}存放每个页面的主体内容例如登录表单或一篇博客文章。注册模板auth/register.html为了保持组织清晰蓝图的模板会放在与蓝图同名的目录中因此注册模板位于templates/auth/下即 examples/tutorial/flaskr/templates/auth/register.html{% extends base.html %} {% block header %} h1{% block title %}Register{% endblock %}/h1 {% endblock %} {% block content %} form methodpost label forusernameUsername/label input nameusername idusername required label forpasswordPassword/label input typepassword namepassword idpassword required input typesubmit valueRegister /form {% endblock %}extends 与块覆写机制{% extends base.html %}告诉 Jinja此模板应替换基础模板中的区块。所有渲染出的内容都必须出现在{% block %}标签内以覆写基础模板中同名的区块。未被覆写的区块会保留基础模板中的默认内容。嵌套 block 的实用模式这里有一个值得记下的模式把{% block title %}放在{% block header %}内部{% block header %} h1{% block title %}Register{% endblock %}/h1 {% endblock %}这样做会先设置title区块再将其值输出到header区块中从而让浏览器窗口标题与页面标题共享同一个文字无需书写两遍。title区块的值最终还会被基础模板中title{% block title %}{% endblock %} - Flaskr/title引用形成「标题定义一次、两处使用」的效果。required 属性与「永远要在服务端验证」原则input标签使用了required属性它告诉浏览器在字段填写完成前不要提交表单。但注意两点如果用户使用不支持该属性的旧浏览器或者用户根本不是通过浏览器发起请求例如用 curl、脚本直接 POST这两种情况下required都不起作用因此仍然必须在 Flask 视图中验证数据。看 examples/tutorial/flaskr/auth.py 中register视图的实现它检查username与password是否为空、用户名是否已被占用出错时通过flash(error)记录错误并重新渲染表单。即使客户端做了一些验证服务端也必须始终做完整验证——这是安全编码的基本原则。登录模板auth/login.html登录模板与注册模板几乎完全一致区别仅在于标题和提交按钮的文字见 examples/tutorial/flaskr/templates/auth/login.html{% extends base.html %} {% block header %} h1{% block title %}Log In{% endblock %}/h1 {% endblock %} {% block content %} form methodpost label forusernameUsername/label input nameusername idusername required label forpasswordPassword/label input typepassword namepassword idpassword required input typesubmit valueLog In /form {% endblock %}表单通过methodpost提交name属性username、password对应视图层读取的request.form[username]、request.form[password]。渲染结果与下图一致实战验证注册一个用户现在认证模板已经就绪可以实际注册一个用户了。确保服务器仍在运行若已停止则执行flask run然后访问 http://127.0.0.1:5000/auth/register。验证浏览器端 required 校验先不填写表单直接点击「Register」按钮浏览器会显示错误提示阻止表单提交——这是required属性的作用。验证服务端 flash 错误提示尝试从 register.html 模板中删除required属性再次点击「Register」。此时浏览器不再拦截页面会重新加载并显示视图层通过flash存储的错误消息——基础模板中的get_flashed_messages()循环负责渲染这条错误。这直观地验证了「客户端校验只是体验优化服务端校验才是安全底线」。完整注册与登录流程填写用户名和密码提交后会被重定向到登录页面尝试输入错误的用户名或正确的用户名配合错误的密码视图会分别给出「Incorrect username.」与「Incorrect password.」的闪现错误如果登录成功会得到一个错误——因为此时还没有index视图可供重定向url_for(index)的目标将在后续「博客」章节中实现。登录成功后的页面结构可以参考首页渲染效果导航栏会切换到已登录状态显示用户名与「Log Out」这正是基础模板中{% if g.user %}分支的体现自动化测试中的模板验证教程附带的测试代码 examples/tutorial/tests/test_auth.py 会用client.get/client.post直接向/auth/register、/auth/login发起请求并断言响应中包含「Register」、用户名、错误消息等由模板渲染出的内容。这说明模板与视图的正确性可以被自动化测试完整覆盖——即使没有浏览器也可以验证模板是否成功渲染、flash消息是否正确显示。下一步至此模板系统基础布局 两个认证模板已能支撑完整的注册与登录流程。继续阅读 static.rst 一节学习如何为这些页面添加样式而templates目录中博客蓝图的模板blog/index.html等则会在后续「博客蓝图」章节中创建届时你将看到{% for %}循环遍历帖子列表、{{ post[created].strftime(%Y-%m-%d) }}格式化日期等更丰富的模板用法。【免费下载链接】flaskThe Python micro framework for building web applications.项目地址: https://gitcode.com/gh_mirrors/fl/flask创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考