Flask 之旅 (二):表单

背景

上一篇帖子我们使用 Flask 创建了最基本的 web 服务。使用 bootstrap 对页面进行装点,使用 JQuery Ajax 实现了在页面上实时显示 log 的功能。趁着周末,我继续开始学习更多的东西以满足这个 web 服务的需求。

模板继承

之前我们有了首页,有显示 log 的页面。之后我们还需要查看配置详情和创建新配置的页面。 那么我们会在页面中有很多重复的东西。例如我们所有的页面都有导航页,所有页面都要加载 JQuery。我们希望写页面的时候可以像写 python 一样可以使用对象的继承功能以减少重复的代码。所以我们用 Flask 的模板继承功能来写页面。现在我们创建一个 base.html。 代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- 引入 Bootstrap -->
    <link href="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 Shim 和 Respond.js 用于让 IE8 支持 HTML5元素和媒体查询 -->
    <!-- 注意: 如果通过 file://  引入 Respond.js 文件,则该文件无法起效果 -->
    <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
    <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
    <script type=text/javascript src="{{ url_for('static', filename='jquery.js') }}"></script>
    <script type=text/javascript>
    $SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
</script>
</head>

<body>
<div class="container">
    <div class="row clearfix">
        <div class="col-md-12 column">
            <ul class="nav nav-tabs">
                <li>
                    <a href="{{ url_for('index') }}">当前环境</a>
                </li>
                <li>
                    <a href="{{ url_for('create_config') }}">创建环境</a>
                </li>
                <li class="disabled">
                    <a href="#">其他</a>
                </li>

            </ul>
        </div>
    </div>
     {% block content %}{% endblock %}
</div>
</body>
</html>

我们看上面的代码,这是我们所有页面的父页面。 它很普通,跟其他页面貌似是一样的。但我们看到代码的底部,有这样一段的代码:

{% block content %}{% endblock %}

这段代码的意思是告诉继承它的子页面。子页面所有的内容将会添加到这里来。所以我们再看看子页面怎么写的。如下:

{% extends "base.html" %}
{% block content %}
    <div class="row clearfix">
        {% for config in configs %}
            <div class="col-md-4 column">
                <h2>
                    {{ config.name_prefix }}
                </h2>
                <p>
                    <a class="btn" href="{{ url_for('detail', name=config.name_prefix) }}">查看详情 »</a>
                    <a class="btn" href="javascript:if(confirm('确实重新部署环境么?此动作会覆盖现有环境'))location='/restart/{{ config.name_prefix }}'" >重新启动 »</a>

                </p>
                <p>
                    <a class="btn" href="{{ url_for('log', name=config.name_prefix) }}">日志</a>
                </p>

            </div>
        {% endfor %}
    </div>
{% endblock %}

在子页面中,我们开头使用 extends “base.html” 来继承父页面,在结束时使用 endblock。 结合父页面的定义,我们可以知道子页面的所有内容都显示在父页面的定义的 block 中。所以我们就可以看到如下的效果图:

​编辑

这样子我们所有的页面都拥有了上面的导航栏。

url_for

接下来我们再聊一个简单的代码复用功能。url_for 方法, 这个方法的作用是根据函数名称找到正确的路由。 什么意思呢, 好记的我们的路由方法是如何定义的么?

@app.route('/')
@app.route('/index', methods=['GET', 'POST'])
def index():

这就是我们的路由方法,解释器决定了我们通过什么 url 来访问这个方法。但是我们在开发过程中每一个页面跳转或者重定向都使用这种固定的路径就会有问题,假如如果我修改了一下 url 的路径,那么所有其他引用这个路径的页面和方法都要做修改。 这样就很不爽, 所以我们可以使用 url_for(function_name) 的方式来获取正确的 url。 参数是路由方法的名称。 例如我们在其他方法里这样重定向首页。

return redirect(url_for('index'))

这段代码就是一个重定向的功能。我们处理完请求后,重定向为函数名称为 index 的路由上。不管定义路由的 URL 怎么变,只要路由的函数名称 (也就是 index 这个函数名) 没有变化就能获取到正确的 url。 下面看看在页面中如何引用:

<a class="btn" href="{{ url_for('detail', name=config.name_prefix) }}">查看详情 »</a>

在页面上的使用方式也是一样的。 另外如果想给 URL 传递参数,后面可以跟参数名=值得方式传递。

Flask-WTF

OK,我们对之前的代码做了一些小的优化。现在我们来继续完成 web 服务的功能。我们现在能展示所有配置名称,重启环境,查看 log。 我们离能凑合用 的状态还差了查看环境配置详情和创建环境配置的功能。 这两个功能实现起来很相似,首先我们需要一个表单来填写我们的配置项。 所幸我们有 Flask-WTF 这个扩展模块来帮我们做一些事情,省去了很多工作。 闲话不多说,我们通过 pip 安装这个模块后。需要配置一些东西。还记得我们一开始在 init.py 里初始化 Flask 的配置么,现在我们要多加一行。

# 如果想要使用Flask-WTF的表单,需要一个config文件
app.config.from_object('config')

然后我们再创建一个 config.py 文件。

CSRF_ENABLED = False
SECRET_KEY = 'you-will-never-guess'
WTF_CSRF_ENABLED = False

这些都是一些安全设置,如果不设置为 False 的话就需要在每一个表单中添加一个 hidden 的安全选项,我觉得麻烦。所以都禁用了。这样我们就对 Flask-WTF 做好了配置。 现在我们来创建一个表单吧。 首先我们创建一个 forms.py

from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired

class ConfigForm(FlaskForm):
    # base
    name_prefix = StringField(u'环境名称', validators=[DataRequired()])
    package_name = StringField(u'包名称', validators=[DataRequired()])
    env_name = StringField(u'配置管理文件名称', validators=[DataRequired()])

    # branch
    prophet_app = StringField(u'prophet_app分支', validators=[DataRequired()])
    online_app = StringField(u'online_app分支', validators=[DataRequired()])
    prophet_ce = StringField(u'prophet_ce分支', validators=[DataRequired()])
    lamma = StringField(u'lamma分支名称', validators=[DataRequired()])

    # pma
    pma_cpu = StringField(u'pma_CPU', validators=[DataRequired()])
    pma_memory = StringField(u'pma内存限制', validators=[DataRequired()])
    pma_disk = StringField(u'pma硬盘限制', validators=[DataRequired()])

    # ip
    prophet_ip = StringField(u'先知_ip', validators=[DataRequired()])
    pma1_ip = StringField(u'pma1_ip', validators=[DataRequired()])
    pma2_ip = StringField(u'pma2_ip', validators=[DataRequired()])

我们把表单抽象成了一个对象来表示。继承 Flask-WTF 的 FlaskForm 类。可以看到我们把表单元素都用做一个类的属性来定义。 编写这些表单元素的时候我们都有一些设置。例如 StringField 是定义这个属性为一个字符串输入字段,其实在页面上就是一个 input 标签。 里面我们用了两个参数,首先第一个参数为字符串,表示为表单元素的 label 属性,意思是我们自定义的名称。 第二个参数是一个验证器的列表,Flask-WTF 向我们提供了多种验证方式,它会自动帮我们验证页面的表单元素是否符合要求。 这里我使用的是一个 DataRequired,意思是表单不能为空。之后我们会在页面上看到,如果我们这个表单没有填写,Flask-WTF 会自动报错。 好了,现在我们定义好了一个表单。我们需要在路由方法中使用它并渲染到模板页面中。

@app.route('/config/detail/<name>')
def detail(name):
    config = filter(lambda x: x.name_prefix == name, list_all_config())[0]
    form = forms.ConfigForm()
    form.name_prefix.data = config.name_prefix
    form.package_name.data = config.package_name
    form.env_name.data = config.env_name
    form.prophet_app.data = config.branch_info['prophet-app']
    form.prophet_ce.data = config.branch_info['prophet-ce']
    form.online_app.data = config.branch_info['online-app']
    form.lamma.data = config.branch_info['lamma']
    form.prophet_ip.data = config.prophet_ip
    form.pma1_ip.data = config.pma1_ip
    form.pma2_ip.data = config.pma2_ip
    form.pma_disk.data = config.pma_disk
    form.pma_memory.data = config.pma_memory
    form.pma_cpu.data = config.pma_cpu
    return render_template('detail.html', form=form)

这是我们查看一个环境配置的路由方法,首先我们通过 fiter 方法把我们需要的环境配置筛选出来,然后我们挨个的给表单元素的 data 属性赋值。 这里介绍一下 FlaskForm。 这个类的每一个属性都是一个表单元素。每一个属性本身又有各种属性。 例如我们常用的 label(我们之前定义的表单名称),data(这个表单元素的值), name(属性本身的名称)。同样它还是一个可迭代的类,也就是说你可以使用 for 循环来遍历每一个表单元素。 这样就很方便我们操作了。好了,现在我们给每个表单元素都进行了赋值,然后渲染到了 detail.html 上。现在我们看看这个页面怎么定义吧

{% extends "base.html" %}

{% block content %}
    {% if form.errors !={} %}
        <div class="container">
        <div class="row clearfix">
            <div class="col-md-12 column">
                <blockquote>
                    <p>
                       <font color="red">{{ form.errors }}</font>
                    </p>
                </blockquote>
            </div>
        </div>
    </div>
    {% endif %}

    <div class="container">
        <div class="row clearfix">

            <div class="col-md-12 column">
                <form role="form" method="post" action="{{ url_for('save_config') }}">
                    <div class="form-group" hidden="true">
                        <label>{{ form.name_prefix.label }} {{ form.name_prefix(size=20) }}  </label>
                    </div>

                    {% for item in form %}
                        {% if item.name != form.name_prefix.name %}
                        <div class="form-group">
                            <label>{{ item.label }} {{ item(size=20) }}  </label>
                        </div>
                        {% endif %}
                    {% endfor %}

可以看到,我们通过 for 循环,在页面遍历了每一个表单元素 (由于我不希望用户更改环境名称,所以降这个字段设置为了 hidden)。 现在我们看看效果图。

​编辑

可以看到我们不仅拥有了所有的表单元素,每个元素的默认值也都显示了出来(通过 label 属性展示)。不过我们留意到页面最上面有一个判断 form.errors 是否为空的代码块。这是给 Flask-WFT 做表单验证准备的,它会显示表单验证的错误信息。 举个例子,现在我们通过填写表单并提交求情的方式。来保存我们对环境配置的更改,我们写一个路由方法来处理这个请求:

@app.route('/config/save', methods=['POST'])
def save_config():
    form = forms.ConfigForm()

    if form.validate_on_submit():
        update_config(form)
    else:
        return render_template('detail.html', form=form)

    return redirect(url_for('index'))

我们首先引入眼帘的就是 form.validate_on_submit() 方法。 这是一个很好用的方法, 它已经帮我们从 request 中取出了 form,并判断它是否验证通过以及是否是提交状态。 一个 form 的流程就是,先判断是不是 submit,也就是说这个请求是不是通过提交表单提交的。然后判断 validate,好记的我们定义表单的时候使用的 validater 么? 它就是判断现在提交的表单符不符合当初定义的规则。 我之前在定义的时候要求所有表单都不能为空。 所以如果有表单为空,这个方法就会返回 False。 所以上面这个路由方法的逻辑就是先判断表单提交是否合法,如果合法就更新配置,如果不合法就重新渲染表单页面并输出错误信息。我们就可以通过在页面上使用 form.errors 来展示错误信息。 来看一下效果图。

​编辑

由于 validate_on_submit 方法的特性既判断表单是不是提交又检验表单提交是否合法。所以其实我们的渲染表单和处理表单的请求是可以放在一个方法里写的。例如我为创建环境写的路由方法:

@app.route('/config/create', methods=['GET', 'POST'])
def create_config():
    # config = filter(lambda x: x.name_prefix == 'template', list_all_config())[0]
    form = forms.ConfigForm()
    if form.validate_on_submit():
        config_create(form)
        return redirect(url_for('index'))
    else:
        return render_template('create.html', form=form)

解释上面的逻辑:如果是表单提交请求并且验证合法,创建环境并重定向到首页。 如果不是表单请求或者验证不合法,就会重新渲染页面。

总结

好了今天就先到这吧。 进度不快, Flask 的官方文档写的不是很细,踩了一些坑。现在这个 web 服务基本就是可用状态了,我们有环境的增删查改,部署环境,查看日志。虽然我预想的还需要很多功能。但是现在这个样子基本可以凑合使用。之后有时间再慢慢完善吧。

更多手把手教程请加入我的星球, 我最近正在更新手把手教你测试人工智能系列:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/350569.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多线程-单例模式

目录 1.单例模式 举例&#xff1a; 1.1单例模式的实现 饿汉模式&#xff1a; 懒汉模式&#xff1a; 1.单例模式 单例模式是一种设计模式。单例模式能保证某个类在程序中只存在唯⼀⼀份实例, ⽽不会创建出多个实例. 举例&#xff1a; package 多线程;import javax.manage…

Django从入门到精通(三)

目录 七、ORM操作 7.1、表结构 常见字段 参数 示例 7.2、表关系 一对多 多对多 第一种方式 第二种方式 7.3、连接MYSQL 7.4、数据库连接池 7.5、多数据库 读写分离 分库&#xff08;多个app ->多数据库&#xff09; 分库&#xff08;单app&#xff09; 注意…

微服务入门篇:Eureka注册中心(作用,搭建Eureka客户端和服务端)

目录 1.提供者与消费者2.Eureka的作用3.搭建EurekaServer1.配置服务端2.配置客户端3.复制实例操作4.服务拉取 1.提供者与消费者 ①服务提供者:一次业务中&#xff0c;被其它微服务调用的服务。&#xff08;提供接口给其它微服务) ②服务消费者:一次业务中&#xff0c;调用其它微…

【机器学习】全网最全模型评价指标(性能指标、YOLOv5训练结果分析、轻量化指标、混淆矩阵详解)【基础收藏】

&#x1f951; Welcome to Aedream同学 s blog! &#x1f951; 文章目录 模型性能指标常见指标ROC/AUCROC & PRC多分类问题——混淆矩阵 计算结果分析——以YOLO v5为例1. confusion_matrix.png(混淆矩阵)2. F1_curve&#xff1a;3. labels.jpg4. labels_corrrelogram.jpg5…

【unity实战】实现蓄力丢手榴弹、烟雾弹、燃烧弹的效果

文章目录 爆炸燃烧烟雾效果资产手榴弹丢手雷烟雾弹、燃烧弹实现手雷每次撞墙弹发出音效&#xff08;补充&#xff09;完结 爆炸燃烧烟雾效果资产 https://assetstore.unity.com/packages/vfx/particles/war-fx-5669 手榴弹 手榴弹配置好刚体&#xff0c;碰撞体 新增脚本Th…

Dify学习笔记-工具(七)

1、工具 工具定义 工具可以扩展 LLM 的能力&#xff0c;比如联网搜索、科学计算或绘制图片&#xff0c;赋予并增强了 LLM 连接外部世界的能力。Dify 提供了两种工具类型&#xff1a;第一方工具和自定义工具。 你可以直接使用 Dify 生态提供的第一方内置工具&#xff0c;或者轻…

Likeshop多商户商城源码系统,支持二开

在电商行业高速发展的当下&#xff0c;拥有一套功能强大、易于操作的开源商城系统至关重要。Likeshop多商户商城系统正是这样一款集H5、小程序、独立APP于一体的开源电商解决方案&#xff0c;助力商家实现智能营销。 一、产品简介 Likeshop多商户商城系统为商家提供了丰富的营…

广州市荔湾区区长谭明鹤、广州工控副总经理张劲泉一行莅临上海毅速考察交流

2024年1月23日&#xff0c;广州市荔湾区区长谭明鹤、副区长阮伟致、广州工业投资控股集团有限公司&#xff08;简称&#xff1a;广州工控&#xff09;副总经理张劲泉等一行来到毅速公司上海总部考察交流。毅速公司董事长张占波、总经理陈烨、常务副总经理王利军等亲切接待了考察…

Sublime Text 3配置 Java 开发环境

《开发工具系列》 《开发语言-Java》 Sublime Text 3配置 Java 开发环境 一、引言二、主要内容1. 初识 Sublime Text 32. 初识 Java3. 接入 Java3.1 JDK 下载3.2 安装和使用 java3.3 环境变量配置 4. 配置 Java 开发环境5. 编写 Java 代码6. 编译和运行 Java 代码7. 乱码问题 三…

智能体AI Agent的极速入门:从ReAct到AutoGPT、QwenAgent、XAgent

前言 如这两天在微博上所说&#xff0c;除了已经在七月官网上线的AIGC模特生成系统外&#xff0c;我正在并行带多个项目组 第二项目组&#xff0c;论文审稿GPT第2版的效果已经超过了GPT4&#xff0c;详见《七月论文审稿GPT第2版&#xff1a;用一万多条paper-review数据集微调…

Confluence 的文章导入到 YouTrack KB 中

YouTrack 是有一个 KB 的&#xff0c;我们可以吧 Confluence 的文章全部导入到 YouTrack 的 KB 中。 首先&#xff0c;你需要具有管理员权限&#xff0c;然后选择导入。 然后可以在打开的界面中新增一个导入。 在新增导入中输入 Confluence 在随后的界面中输入你 Confluence …

OpenCV书签 #差值哈希算法的原理与相似图片搜索实验

1. 介绍 差值哈希算法&#xff08;Difference Hash Algorithm&#xff0c;简称dHash&#xff09; 是哈希算法的一种&#xff0c;主要可以用来做以图搜索/相似图片的搜索工作。 2. 原理 差值哈希算法通过计算相邻像素的差异来生成哈希&#xff0c;即通过缩小图像的每个像素与平…

关于IDEA中sout、fori无法自动生成的解决方案

方案一.通过idea中的设置修改 搜索栏搜索sout 点击change&#xff0c;全部勾选Java下的选项 最后别忘了点击右下角的Apply 方案二&#xff1a;写main函数 不能直接在类里生成&#xff0c;需要写一个main函数。&#xff08;我就是忘了写main函数才无法生成的。&#xff09…

Soul CEO张璐积极履行反诈责任,倡导共建安全网络

近期,备受期待的反诈电影《鹦鹉杀》热映,深入剖析杀猪盘这一网络诈骗行为。为协助更多人增强反诈意识,备受欢迎的社交应用Soul App积极响应,在Soul CEO张璐的带领下,邀请电影中的演员和平台的反诈中心共同参与反诈宣传。此外,一旦用户在平台搜索“诈骗”、“杀猪盘”、“鹦鹉杀…

Spring Boot3整合MyBatis Plus

目录 1.前置条件 2.导坐标 3.配置数据源 4.mybatis-plus基础配置 5.配置mapper扫描路径 6.MyBatis Plus代码生成器整合 1.导坐标 2.编写代码生成逻辑 7.整合Druid连接池 1.前置条件 已经初始化好一个spring boot项目且版本为3X&#xff0c;项目可正常启动 初始化教程…

Halcon指定区域的形状匹配

Halcon指定区域的形状匹配 文章目录 Halcon指定区域的形状匹配1.在参考图像中选择目标2.创建模板3.搜索目标 在这个实例中&#xff0c;会介绍如何根据选定的ROI选择合适的图像金字塔参数&#xff0c;创建包含这个区域的形状模板&#xff0c;并进行精确的基于形状模板的匹配。最…

echarts-wordcloud词云

echarts-wordcloud是基于echarts的一个插件&#xff0c;所以我们要首先安装echarts包&#xff0c;然后再安装echarts-wordcloud的包&#xff0c;这里我的练习项目安装的版本&#xff1b;当然&#xff0c;你可以随意安装你需要的版本&#xff1b; “echarts”: “^5.3.3”, “ec…

PCB制板基础知识

一、PCB概念 PCB&#xff08;PrintedCircuitBoard&#xff09;&#xff0c;中文名称为印制电路板&#xff0c;又称印刷电路板、印刷线路板&#xff0c;是重要的电子部件&#xff0c;是电子元器件的支撑体&#xff0c;是电子元器件电气连接的提供者。由于它是采用电子印刷术制作…

前端工程化基础(一):Node模块化

Node模块化 Node.js是什么 官方定义&#xff1a;Node.js是一个基于V8 JavaScript引擎的JavaScript运行时的环境 Node.js基于V8引擎来执行 JavaScript代码&#xff0c;但是Node.js中不仅仅有V8 我们知道&#xff0c;V8可以嵌入到C应用程序中&#xff0c;因此无论是Chrome还是No…

Redis快的秘密,高性能设计epoll和IO多路复用探究

Redis快的原因&#xff0c;高性能设计epoll和IO多路复用探究 1、多路复用需要解决的问题 并发多客户端连接&#xff0c;在多路复用之前最简单和典型的方案&#xff1a;同步阻塞网络IO模型 这种模式的特点就是用一个进程来处理一个网络连接&#xff08;即一个用户请求&#x…