实验2、Flask模板、表单、视图和重定向示例
实验内容
1. 实验内容
表单功能与页面跳转功
能是Web应用程序的基础功能,学习并使用他们能够更好的完善应用程序的功能。Flask使用了名为Jinja2的模板引擎,该引擎根据用户的交互级别显示应用程序的行为。Jinja模板使用变量,表达式和标签。在浏览器中呈现页面之前,运行时期间将变量和表达式替换为值。Jinja标签有助于编写逻辑,并控制Flask模板中的语句。
2. 实验要点
- 掌握Flask模板是使用规则
- 学习并掌握Flask表单的用法
- 学习并掌握Flask页面跳转的实现
- 尝试对代码进行调试
3.实验环境
- Centos 7.9
4. 工作目录
本实验的工作目录为: /experiment
Flask 视图
Flask视图的概念来源于一种流行的web应用程序设计模式,称为模型-视图-控制器(Model-View-Controller)。视图是该范式中三个相互关联的元素之一,处理应用程序逻辑。视图负责向用户表示信息。
在上一篇教程中,我们通过继承Flask-Appbuilder
的BaseView
类来设计一个视图。在本教程的后续部分,我们将扩展我们的最后一个例子,并展示视图可以定制的方法。
Flask 模板
启动MongoDB服务
mongod --dbpath /var/lib/mongodb --logpath /var/log/mongodb/mongod.log --fork
在template
目录下创建hello.html
文件
写入下面内容并保存:
<!doctype html>
<html>
<h1>LiuYang</h1>
<p> Hello World!</p>
</html>
之后修改view.py
中的HelloWorld
类:
from flask import render_template
from flask_appbuilder import ModelView, BaseView, expose
from flask_appbuilder.models.mongoengine.interface import MongoEngineInterface
class HelloWorld(BaseView):
route_base = "/hello"
@expose("/")
def hello(self):
return "Hello, World!"
@expose("/template")
def hello_template(self):
return render_template("hello.html")
appbuilder.add_view_no_menu(HelloWorld())
我们已经为路由添加了一个路径/hello/template
,并为此创建了一个单独的处理程序方法。注意,我们在return语句中使用了Flask显示模板方法。此外,请注意我们用于导入render_template
的Python语句。
转到项目的根目录并创建一个名为run.py
的文件,并在该文件中输入给定的代码片段。
注意,我们已经将端口更改为8080。如果需要,您可以在run.py
中更改端口。
from app import app
app.run(host='0.0.0.0', port=8080, debug=True)
现在开发服务器将在所有网络接口上运行,因为我们已经指定0.0.0.0
作为主机。
使用下面给出的命令启动开发服务器。记得激活虚拟环境。
python run.py
请注意,这是运行开发服务器的另一种方式。它为运行开发服务器提供了更大的灵活性,如果需要,我们可以使用定制的配置值。接下来,我们将始终使用相同的命令来运行开发服务器。
现在用浏览器访问IP:8080/hello/template
以查看浏览器中的输出。
Flask404处理器
Web应用程序通常有404处理。此处理程序显示一条自定义消息,并通知用户web应用程序没有特定的烧瓶模板或页面。让我们创建一个应用程序范围的控制器来发布一条自定义消息来覆盖这个用例。
修改view.py
文件如下:
from flask import render_template
from flask_appbuilder import ModelView, BaseView, expose
from flask_appbuilder.models.mongoengine.interface import MongoEngineInterface
from app import appbuilder
class HelloWorld(BaseView):
route_base = "/hello"
@expose("/")
def hello(self):
return "Hello, World!"
@expose("/template")
def hello_template(self):
return render_template("hello.html")
appbuilder.add_view_no_menu(HelloWorld())
@appbuilder.app.errorhandler(404)
def page_not_found(e):
"""
404 handler
"""
return render_template("404.html", base_template=appbuilder.base_template, appbuilder=appbuilder), 404
在错误处理程序中,Flask响应对象由return语句中返回的值组成。响应被修改为自定义返回代码404,而不是200。
在template
目录下创建或修改404.html
{% extends "appbuilder/base.html" %} {# Inherit from base.html #}
{% block content %}
{## double `{{` are used to interpolate variables ##}
<h2><center>{{_('Page not found')}}<center></h2>
<p>Please search the website for something else.</p>
{% endblock %}
上面代码中{# #}
用于写入注释,{{}}
用于变量、值或表达式。
现在我们启动服务器访问一个不存在的url,例如IP:8080/123
通过使用extend关键字继承base.html
模板的大部分内容,并且{% block content %}
帮助覆盖了base.html
模板的那部分内容。
Flask模板循环
然而,除了上面提到的符号外,我们还使用line语句
和{%
在模板中编写控制语句。让我们看看下面的例子。
修改hello world
视图,以理解更多的概念。打开views.py
并使用另一个方法更新HelloWorld
类,如下所示。
@expose("/greetings")
def hello_greetings(self):
greetings = [
'Good Morning',
'Good Afternoon',
'Good Evening',
'Good Night',
]
return render_template("hello.html", greetings=greetings)
修改hello.html
<!doctype html>
<html>
<h1>LiuYang</h1>
<p> Hello World! </p>
{% for item in greetings %}
{% if "Morning" in item %}
<i><p><b>{{item}}</b></p></i>
{% else %}
<p>{{item}}</p>
{% endif %}
{% endfor %}
</html>
之后启动服务器,访问IP:8080/hello/greetings
在上面的Flask模板中,我们使用了一个for循环来迭代列表中的项目。在我们的控制器或处理程序中,我们向模板传递了一个包含问候语值的列表。在模板内部,我们使用{{item}}语法访问每个项。
此外,if语句的用法也很重要。在这里,我们为Morning测试项目,并将其加粗和斜体。
Flask表单
现在让我们继续学习更多关于Flask表单的概念。
模板最重要的一个方面是获取用户的输入,并基于该输入编写后端逻辑。让我们创建一个表单。
我们使用Flask-Appbuilder SimpleFormView
来呈现表单。但是,让我们先创建一个表单。除了创建表单之外,我们还需要使用flask fab create-admin
命令来创建一个管理用户。
因此,在启动开发服务器之前使用该命令,以便随后创建的视图和表单可以通过登录用户进行验证。我们使用admin用户登录,并继续验证创建的视图是否在菜单下可见,如屏幕截图所示。
使用下面命令创建管理员
flask fab create-admin
除密码外其他内容均可设为默认(admin),密码设置为admin
输入python run.py
启动服务,访问IP:8080
点击右上角Login
登录系统输入刚刚创建的管理员账户admin
密码admin
即可登录。
在应用程序目录下创建一个名为forms.py
的文件,并将以下代码写入其中。
from wtforms import Form, StringField
from wtforms.validators import DataRequired
from flask_appbuilder.fieldwidgets import BS3TextFieldWidget
from flask_appbuilder.forms import DynamicForm
class GreetingsForm(DynamicForm):
greeting1 = StringField(('Morning'),
description = ('Your morning Greeting'),
validators = [DataRequired()],
widget = BS3TextFieldWidget())
greeting2 = StringField(('Afternoon'),
description = ('Your Afternoon Greeting'),
validators = [DataRequired()],
widget = BS3TextFieldWidget())
greeting3 = StringField(('Evening'),
description = ('Your Evening Greeting'),
widget = BS3TextFieldWidget())
greeting4 = StringField(('Night'),
description = ('Your Night Greeting'),
widget = BS3TextFieldWidget())
我们已经基于Flask-Appbuilder
的DynamicForm
创建了一个表单。有四个文本字段。我们扩展问候示例。在这四个字段中,两个是必填字段,两个是可选字段,因为对于前两个问候,我们提到了验证器的值。
现在让我们为该表单创建一个视图。将以下这些代码行写入文件views.py中。
from flask import render_template, flash, redirect, url_for, session
from flask_appbuilder import SimpleFormView, ModelView, BaseView, expose, SimpleFormView
from app.forms import GreetingsForm
from app import appbuilder
class HelloWorld(BaseView):
route_base = "/hello"
@expose("/")
def hello(self):
return "Hello, World!"
@expose("/template")
def hello_template(self):
return render_template("hello.html")
@expose("/greetings")
def hello_greetings(self):
greetings = [
'Good Morning',
'Good Afternoon',
'Good Evening',
'Good Night',
]
return render_template("hello.html", greetings=greetings)
class GreetingsView(SimpleFormView):
form = GreetingsForm
form_title = 'This is a Greetings form'
message = 'Your Greetings are submitted'
def form_get(self, form):
form.greeting1.data = 'Your Morning Greeting'
form.greeting2.data = 'Your Afternoon Greeting'
form.greeting3.data = 'Your Evening Greeting'
form.greeting4.data = 'Your Night Greeting'
def form_post(self, form):
flash(self.message, 'info')
greetings = [
form.greeting1.data,
form.greeting2.data,
form.greeting3.data,
form.greeting4.data,
]
session['greetings'] = greetings
return redirect(url_for('HelloWorld.hello_greetings2'))
appbuilder.add_view_no_menu(HelloWorld())
appbuilder.add_view(
GreetingsView,
"Greetings View",
icon="fa-group",
category="My Forms",
category_icon="fa-cogs"
)
@appbuilder.app.errorhandler(404)
def page_not_found(e):
"""
404 handler
"""
return render_template("404.html", base_template=appbuilder.base_template, appbuilder=appbuilder), 404
在上面的视图中,我们有两种称为form_get
和form_post
的方法,用于分别填充表单字段中的默认值和在从浏览器提交表单后读取输入的值。
之后在直接添加导航栏My Forms
启动服务器,点开My Forms
下的Greeting View
,编辑完成之后记得点击保存。
我们还利用Flask会话对象将字段值存储在form_post
中,以便我们可以在将要编写的相应新视图中访问它们,之后我们创建映射。
class HelloWorld(BaseView):
## 其他方法
@expose("/greetings2")
def hello_greetings2(self):
greetings = session['greetings']
return render_template("hello.html", greetings=greetings)
再次启动服务器进入Greeting View
并点击保存
在此视图中,我们从会话对象中读取值,并使用Flask渲染模板在面向用户的HTML中显示这些值。请注意,hello_greetings2
是实现与hello_greetings
类似的相同功能的另一种方法。
唯一的区别是,使用hello_greetings2
可以显示用户输入的值,而在hello_greetings
中,我们在写入映射到相应路径的视图时未从用户那里获取任何输入并编码。
Flask响应
在代码中,您很少会看到对Flask响应的显式使用。Flask中的Response类只是Werkzueg的Response类的一个子类,而Werkzueg又继承了ResponseBase类的子类。
每当我们调用return语句或方法(如render_template
)时,Flask都会在内部形成Flask响应对象。
此外,如果需要,我们可以在视图的return语句中自定义响应代码和内容类型,如下面的修改过的HelloWorld
视图所示。
class HelloWorld(BaseView):
## 其他方法
@expose("/greetings2")
def hello_greetings2(self):
greetings = session['greetings']
return render_template("hello.html", greetings=greetings), 201,
{"Content-Type" : "application/json"}
Flask重定向
应用程序并非总是能够根据来自客户端的不同请求来预先定义响应。
在程序中,我们可以使用Flask Redirect
,在这种情况下,可以响应其他请求提供其他视图或位置可以实现的内容。我们将Flask Redirect
与标准HTTP
返回代码一起中止使用。
例如,在下面的代码中,我们对HTTP代码301
使用了重定向,而对401
使用了中止。
from flask import render_template, flash, redirect, url_for, session, abort
from flask_appbuilder import SimpleFormView, ModelView, BaseView, expose, SimpleFormView
from app.forms import GreetingsForm, LoginForm
from app import appbuilder
class HelloWorld(BaseView):
route_base = "/hello"
@expose("/")
def hello(self):
return "Hello, World!"
@expose("/template")
def hello_template(self):
return render_template("hello.html")
@expose("/greetings")
def hello_greetings(self):
greetings = [
'Good Morning',
'Good Afternoon',
'Good Evening',
'Good Night',
]
return render_template("hello.html", greetings=greetings)
@expose("/greetings2")
def hello_greetings2(self):
greetings = session['greetings']
return render_template("hello.html", greetings=greetings), 201, {"Content-Type" : "application/json"}
@expose("/success")
def success(self):
return "success"
class GreetingsView(SimpleFormView):
form = GreetingsForm
form_title = 'This is a Greetings form'
message = 'Your Greetings are submitted'
def form_get(self, form):
form.greeting1.data = 'Your Morning Greeting'
form.greeting2.data = 'Your Afternoon Greeting'
form.greeting3.data = 'Your Evening Greeting'
form.greeting4.data = 'Your Night Greeting'
def form_post(self, form):
flash(self.message, 'info')
greetings = [
form.greeting1.data,
form.greeting2.data,
form.greeting3.data,
form.greeting4.data,
]
session['greetings'] = greetings
return redirect(url_for('HelloWorld.hello_greetings2'))
class LoginView(SimpleFormView):
form = LoginForm
def form_get(self, form):
form.username.data = 'username'
form.password.data = 'password'
def form_post(self, form):
if form.username.data == 'admin':
return redirect(url_for('HelloWorld.success'))
else:
abort(401)
appbuilder.add_view_no_menu(HelloWorld())
appbuilder.add_view(
GreetingsView,
"Greetings View",
icon="fa-group",
category="My Forms",
category_icon="fa-cogs"
)
appbuilder.add_view(
LoginView,
"Login View",
icon="fa-group",
category="My Forms",
category_icon="fa-cogs",
)
@appbuilder.app.errorhandler(404)
def page_not_found(e):
"""
404 handler
"""
return render_template("404.html", base_template=appbuilder.base_template, appbuilder=appbuilder), 404
在视图中添加success
映射。
@expose("/success")
def success(self):
return "success"
表单修改为:
from wtforms import Form, StringField
from wtforms.validators import DataRequired
from flask_appbuilder.fieldwidgets import BS3TextFieldWidget
from flask_appbuilder.forms import DynamicForm
class GreetingsForm(DynamicForm):
greeting1 = StringField(('Morning'),
description = ('Your morning Greeting'),
validators = [DataRequired()],
widget = BS3TextFieldWidget())
greeting2 = StringField(('Afternoon'),
description = ('Your Afternoon Greeting'),
validators = [DataRequired()],
widget = BS3TextFieldWidget())
greeting3 = StringField(('Evening'),
description = ('Your Evening Greeting'),
widget = BS3TextFieldWidget())
greeting4 = StringField(('Night'),
description = ('Your Night Greeting'),
widget = BS3TextFieldWidget())
class LoginForm(DynamicForm):
username = StringField(('username'),
description = ('Your username'),
validators = [DataRequired()],
widget = BS3TextFieldWidget())
password = StringField(('password'),
description = ('Your password'),
validators = [DataRequired()],
widget = BS3TextFieldWidget())
启动服务器,登录admin后选择Login Form
输入username:admin,password:xxx
输入``username:xxx,password:xxx`
Flask调试工具栏
在上一教程中,我们已经介绍了Flask的交互式调试器。在本教程中,我们将采取进一步的步骤来简化Flask应用程序的调试。安装后,Flask Debug
工具栏将在Flask应用程序上覆盖显示。
安装Flask Debugtoolbar(安装过程已完成,无需再次操作)
pip3 install flask-debugtoolbar
要激活调试工具栏,请在我们的项目中打开__init__.py
文件,并通过添加以下代码行来修改代码。
from flask_debugtoolbar import DebugToolbarExtension
app.debug = True
toolbar = DebugToolbarExtension(app)
请注意,Flask调试工具栏仅在调试模式下启用。启用后,当您重新加载应用程序时,将看到下面两种现象:
- 调试工具栏出现在浏览器的右侧。单击并展开它以查看工具栏提供的各种功能。
- 每当有新的POST请求发送到应用程序时,它就会被工具栏拦截,以便我们可以检查与应用程序调试有关的变量和其他参数。
可以在run.py中添加以下配置禁用此默认拦截。
app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False
测试Flask服务视图
我们需要组织测试代码以使其更易于管理。在目录中创建一个名为conftest.py
的文件,并将以下提到的行从test_hello.py
移至该文件,这个文件就算Pytest的配置文件。
from app import appbuilder
import pytest
@pytest.fixture
def client():
""" A pytest fixture for test client """
appbuilder.app.config["TESTING"] = True
with appbuilder.app.test_client() as client:
yield client
pytest模块在运行时由pytest加载。这些模块均可用,并与所有测试共享。在任何项目的根路径中定义conftest.py被认为是最佳实践,因为pytest可以识别项目中的所有模块而无需指定显式的PYTHONPATH。
为test_hello文件再添加一个测试。下面给出一个示例测试。我们调用客户端对象的get方法,并在resp.data
中存储的响应数据中声明期望值。
同样,您可以编写更多指向各种视图的测试。我们将在后续教程中编写更多测试。
def test_greetings(client):
""" A test method to test view hello_greetings"""
resp = client.get("/hello/greetings", follow_redirects=True)
assert b"Good Morning" in resp.data
使用以下命令运行测试
pytest -v test_hello.py
还没有出现失败。让我们再设计一个测试,如下所述。
def test_greetings2(client):
""" A test method to test view hello_greetings2 """
resp = client.get("/hello/greetings2", follow_redirects=True)
assert b"Good Morning" in resp.data
由于我们未在views.py
文件的HelloWorld
类中定义任何消息属性,因此该测试将失败。
使用pytest -v test_hello.py
运行测试后,与以下所示图片类似的结果将再次显示在控制台上。
实验总结
在本教程中,我们了解了模板如何在Flask框架中工作。我们概述了使用变量和表达式使用用户定义的值创建和渲染Flask模板的步骤。
我们还看到了Flask Appbuilder插件的预定义视图BaseView的示例。Flask开发人员可以轻松地将此视图子类化以创建自定义视图。
到目前为止所涵盖的概念可帮助读者使用Flask在没有数据库后端的情况下快速创建静态和动态网站。将在下一个教程中说明我们介绍将数据库与Flask结合使用时以及如何使用ModelView从数据库读取数据和向数据库写入数据。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了