实验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-AppbuilderBaseView类来设计一个视图。在本教程的后续部分,我们将扩展我们的最后一个例子,并展示视图可以定制的方法。

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>

image

之后修改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)

image

现在开发服务器将在所有网络接口上运行,因为我们已经指定0.0.0.0作为主机。

使用下面给出的命令启动开发服务器。记得激活虚拟环境。

python run.py

请注意,这是运行开发服务器的另一种方式。它为运行开发服务器提供了更大的灵活性,如果需要,我们可以使用定制的配置值。接下来,我们将始终使用相同的命令来运行开发服务器。

现在用浏览器访问IP:8080/hello/template以查看浏览器中的输出。

image

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

image

在错误处理程序中,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 %}

image

上面代码中{# #}用于写入注释,{{}}用于变量、值或表达式。
现在我们启动服务器访问一个不存在的url,例如IP:8080/123

image

通过使用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)

image

修改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>

image

之后启动服务器,访问IP:8080/hello/greetings

image

在上面的Flask模板中,我们使用了一个for循环来迭代列表中的项目。在我们的控制器或处理程序中,我们向模板传递了一个包含问候语值的列表。在模板内部,我们使用{{item}}语法访问每个项。
此外,if语句的用法也很重要。在这里,我们为Morning测试项目,并将其加粗和斜体。

Flask表单

现在让我们继续学习更多关于Flask表单的概念。

模板最重要的一个方面是获取用户的输入,并基于该输入编写后端逻辑。让我们创建一个表单。

我们使用Flask-Appbuilder SimpleFormView来呈现表单。但是,让我们先创建一个表单。除了创建表单之外,我们还需要使用flask fab create-admin命令来创建一个管理用户。

因此,在启动开发服务器之前使用该命令,以便随后创建的视图和表单可以通过登录用户进行验证。我们使用admin用户登录,并继续验证创建的视图是否在菜单下可见,如屏幕截图所示。

使用下面命令创建管理员

flask fab create-admin

除密码外其他内容均可设为默认(admin),密码设置为admin

image

image

输入python run.py启动服务,访问IP:8080点击右上角Login登录系统输入刚刚创建的管理员账户admin密码admin即可登录。

image

image

image

在应用程序目录下创建一个名为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())

image

我们已经基于Flask-AppbuilderDynamicForm创建了一个表单。有四个文本字段。我们扩展问候示例。在这四个字段中,两个是必填字段,两个是可选字段,因为对于前两个问候,我们提到了验证器的值。

现在让我们为该表单创建一个视图。将以下这些代码行写入文件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_getform_post的方法,用于分别填充表单字段中的默认值和在从浏览器提交表单后读取输入的值。
之后在直接添加导航栏My Forms

启动服务器,点开My Forms下的Greeting View,编辑完成之后记得点击保存。

image

我们还利用Flask会话对象将字段值存储在form_post中,以便我们可以在将要编写的相应新视图中访问它们,之后我们创建映射。

class HelloWorld(BaseView):
    ## 其他方法   
 
    @expose("/greetings2")
    def hello_greetings2(self):
        greetings = session['greetings']
        return render_template("hello.html", greetings=greetings)

再次启动服务器进入Greeting View并点击保存

image

在此视图中,我们从会话对象中读取值,并使用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"}

image

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

image

输入``username:xxx,password:xxx`

image

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调试工具栏仅在调试模式下启用。启用后,当您重新加载应用程序时,将看到下面两种现象:

  1. 调试工具栏出现在浏览器的右侧。单击并展开它以查看工具栏提供的各种功能。

image

  1. 每当有新的POST请求发送到应用程序时,它就会被工具栏拦截,以便我们可以检查与应用程序调试有关的变量和其他参数。

image

可以在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

image

还没有出现失败。让我们再设计一个测试,如下所述。

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运行测试后,与以下所示图片类似的结果将再次显示在控制台上。

image

实验总结

在本教程中,我们了解了模板如何在Flask框架中工作。我们概述了使用变量和表达式使用用户定义的值创建和渲染Flask模板的步骤。

我们还看到了Flask Appbuilder插件的预定义视图BaseView的示例。Flask开发人员可以轻松地将此视图子类化以创建自定义视图。

到目前为止所涵盖的概念可帮助读者使用Flask在没有数据库后端的情况下快速创建静态和动态网站。将在下一个教程中说明我们介绍将数据库与Flask结合使用时以及如何使用ModelView从数据库读取数据和向数据库写入数据。

posted @ 2021-05-12 22:46  liuyang9643  阅读(400)  评论(0编辑  收藏  举报