最近在Flask Web Development作者博客看到第二版Flask Mega-Tutorial已在2017年底更新,现翻译给大家参考,希望帮助大家学习flask。
在Flask Mega-Tutorial系列的第二章中,我将讨论如何使用模板。
供您参考,以下是本系列文章的列表。
- 第1章:Hello, World!
- 第2章:模板 (本文)
- 第3章:Web表单
- 第4章:数据库
- 第5章:用户登录
- 第6章:配置文件页面和头像
- 第7章:错误处理
- 第8章:关注与被关注
- 第9章:分页
- 第10章:电子邮件支持
- 第11章:整容
- 第12章:日期和时间
- 第13章:I18n和L10n
- 第14章:Ajax
- 第15章:大型应用程序结构
- 第16章:全文搜索
- 第17章:在Linux上部署
- 第18章:在Heroku上部署
- 第19章:Docker容器上的部署
- 第20章:一些JavaScript Magic
- 第21章:用户通知
- 第22章:后台工作
- 第23章:应用程序编程接口(API)
完成第1章后,您应该拥有一个完全正常但简单的Web应用程序,它具有以下文件结构:
microblog\
venv\
app\
__init__.py
routes.py
microblog.py
要运行应用程序,请在终端会话中设置
FLASK_APP=microblog.py
,然后执行flask run
。这将启动flask自带Web服务器,您可以通过在Web浏览器的地址栏中键入http://localhost:5000/ 来访问该服务器。
在本章中,您将继续使用相同的应用程序,特别是,您将学习如何生成具有复杂结构和许多动态组件的更复杂的Web页面。如果到目前为止有关应用程序或开发工作流程的任何内容尚不清楚,请在继续之前再次查看第1章。
什么是模板?
我希望我的microblog的主页有一个欢迎用户的标题。目前,我将忽略应用程序还没有用户概念的事实,因为这迟早会出现。相反,我将使用一个模拟用户,我将用Python字典实现它,如下所示:
user = {'username': 'Miguel'}
创建模拟对象是一种有用的技术,它允许您专注于应用程序的一部分,而不必担心系统中尚不存在的其他部分。我想设计我的应用程序的主页,我不希望我没有用户系统来分散我的注意力,所以我只是编造一个用户对象,以便我可以继续。
应用程序中的视图函数返回一个简单的字符串。我现在要做的是将返回的字符串扩展为完整的HTML页面,看起来是这样的:
# app/routes.py: Return complete HTML page from view function
from app import app
@app.route('/')
@app.route('/index')
def index():
user = {'username': 'Miguel'}
return '''
<html>
<head>
<title>Home Page - Microblog</title>
</head>
<body>
<h1>Hello, ''' + user['username'] + '''!</h1>
</body>
</html>
'''
如果您不熟悉HTML,我建议您阅读维基百科上的HTML标记以获得简要介绍。
如上所示更新视图功能,并尝试查看应用程序在浏览器中的外观。
你应该同意,上面用于向浏览器提供HTML的解决方案并不好。考虑一下,当我收到来自用户的博客文章时,这个视图函数中的代码会变得多么复杂。该应用程序还将具有更多与其他URL相关联的视图函数,因此想象一下,如果有一天我决定更改此应用程序的布局,并且必须在每个视图函数中更新HTML。这显然不是随着应用程序的增长而扩展的选项。
如果你可以将你的应用程序的逻辑与你的网页的布局或内容分离开来,那么事情会更好地组织,你不觉得吗?在使用Python编写应用程序逻辑代码时,您甚至可以聘请Web设计人员来创建杀手级网站。
模板有助于实现HTML和业务逻辑之间的这种分离。在Flask中,模板作为单独的文件编写,存储在应用程序包内的模板文件夹中。因此,在确保您在microblog目录中后,创建将存储模板的目录:
(venv) $ mkdir app/templates
下面你可以看到你的第一个模板,它的功能与index()
上面的view函数返回的HTML页面类似。将此文件写在app/templates/index.html中:
app/templates/index.html:主页面模板
<html>
<head>
<title>{{ title }} - Microblog</title>
</head>
<body>
<h1>Hello, {{ user.username }}!</h1>
</body>
</html>
这是一个大多数标准的,非常简单的HTML页面。这个页面唯一有趣的事情是动态内容有几个占位符,包含在
{{ ... }}
各个部分中。这些占位符表示页面中可变的部分,并且只在运行时才会调用。
现在页面的渲染已转到HTML模板,可以简化视图功能:
# app/routes.py: Use render\_template() function
from flask import render_template
from app import app
@app.route('/')
@app.route('/index')
def index():
user = {'username': 'Miguel'}
return render_template('index.html', title='Home', user=user)
这看起来好多了,对吗?尝试使用此新版本的应用程序来观察模板的工作原理。在浏览器中加载页面后,您可以查看HTML源码并将其与template中原始模板进行比较。
将模板转换为完整HTML页面的操作称为渲染。为了渲染模板,我必须导入一个Flask框架中名为
render_template()的
函数。此函数采用模板文件名和模板参数作为其变量列表,并返回相同的模板,但其中的所有占位符都替换为实际值。
该render_template()
函数调用与Flask框架捆绑在一起的Jinja2模板引擎。Jinja2 {{ ... }}
用相应的值替换块,由调用中提供的参数给出render_template()
。
条件语句
您已经了解了Jinja2如何在渲染过程中用实际值替换占位符,但这只是Jinja2在模板文件中支持的众多强大操作之一。例如,模板还支持在{% ... %}
块内给出的控制语句。
index.html模板的下一个版本添加了一个条件语句:
app/templates/index.html: Conditional statement in template
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog!</title>
{% endif %}
</head>
<body>
<h1>Hello, {{ user.username }}!</h1>
</body>
</html>
现在模板有点聪明了。如果视图函数忘记传递title
占位符变量的值,则模板将提供默认值,而不是显示空标题。您可以通过删除视图函数调用中的title
参数,来尝试render_template()在
此条件下的工作方式。
循环
登录用户可能希望在主页中查看来自已关联用户的最新帖子,因此我现在要做的是扩展应用程序以支持它。
再一次,我将依靠方便的模拟对象技巧来创建一些用户和一些帖子来显示:
# app/routes.py: Fake posts in view function
from flask import render_template
from app import app
@app.route('/')
@app.route('/index')
def index():
user = {'username': 'Miguel'}
posts = [
{
'author': {'username': 'John'},
'body': 'Beautiful day in Portland!'
},
{
'author': {'username': 'Susan'},
'body': 'The Avengers movie was so cool!'
}
]
return render_template('index.html', title='Home', user=user, posts=posts)
为了表示用户帖子,我用了一个字典,其中每个元素都是一个具有author和body
的字典。当我真正实现用户和博客文章时,我将尝试尽可能地保留这些字段名称,以便我使用这些假对象设计和测试主页模板所做的所有工作,将继续在我介绍真实用户和帖子时有效。
在模板方面,我必须解决一个新问题。帖子列表可以包含任意数量的元素,由视图函数决定将在页面中显示多少篇帖子。模板不能对有多少篇帖子做出任何假设,因此需要准备以通用的方式渲染视图发送的尽可能多的帖子。
对于这类问题,Jinja2提供了一种for
控制结构:
app/templates/index.html: for-loop in template
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog</title>
{% endif %}
</head>
<body>
<h1>Hi, {{ user.username }}!</h1>
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
</body>
</html>
简单吧?尝试使用此新版本的应用程序,并确保在帖子列表中添加更多内容,以查看模板如何适应的,并始终渲染视图函数发送的所有帖子。
模板继承
如今,大多数Web应用程序在页面顶部都有一个导航栏,其中包含一些常用链接,例如用于编辑个人资料,登录,注销等的链接。我可以轻松地使用更多的HTML,将导航栏添加到index.html
模板中,但随着应用程序的增长,我将需要在其他页面中使用相同的导航栏。我真的不想在许多HTML模板中维护导航栏的多个副本,如果可能的话,不要重复是一个好习惯。
Jinja2具有模板继承功能,专门解决此问题。实质上,您可以做的是将所有模板共有的页面布局各个部分移动到一个通用模板,让其他模板都可以继承使用它。
所以我现在要定义一个名为的通用模板base.html
,其中包括一个简单的导航栏以及我之前实现的标题逻辑。您需要在app/templates/ base.html文件中编写以下模板:
app/templates/base.html: Base template with navigation bar
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog</title>
{% endif %}
</head>
<body>
<div>Microblog: <a href="/index">Home</a></div>
<hr>
{% block content %}{% endblock %}
</body>
</html>
在这个模板中,我使用block
控制语句来定义派生模板可以插入的位置。block语句被赋予唯一的名称,派生模板在提供其内容时可以引用这些名称。
有了通用模板,我现在可以通过继承base.html来简化index.html:
app/templates/index.html: Inherit from base template
{% extends "base.html" %}
{% block content %}
<h1>Hi, {{ user.username }}!</h1>
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
{% endblock %}
由于base.html模板现在将处理基本页面布局,因此我从index.html中删除了所有这些元素,只留下了内容部分。该extends
语句建立了两个模板之间的继承链接,因此Jinja2知道当它被要求渲染index.html
时需要将其嵌入其中base.html
。这两个模板具有匹配的block
语句名称content
,这就是Jinja2如何将两个模板合并为一个模板的方法。
现在,如果我需要为应用程序创建其他页面,我可以将它们创建为来自相同base.html模板的派生模板,这就是我可以让应用程序的所有页面共享相同外观而无需重复的方式。
原文链接:https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ii-templates