Flask 之旅 (二):表单

news2024/12/4 17:37:14

背景

上一篇帖子我们使用 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 服务基本就是可用状态了,我们有环境的增删查改,部署环境,查看日志。虽然我预想的还需要很多功能。但是现在这个样子基本可以凑合使用。之后有时间再慢慢完善吧。

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用Docker安装Spug并实现远程访问本地运维管理界面

文章目录 前言1. Docker安装Spug2 . 本地访问测试3. Linux 安装cpolar4. 配置Spug公网访问地址5. 公网远程访问Spug管理界面6. 固定Spug公网地址 前言 Spug 面向中小型企业设计的轻量级无 Agent 的自动化运维平台&#xff0c;整合了主机管理、主机批量执行、主机在线终端、文件…

算法基础学习|离散化与区间合并

位运算 代码模板 求n的第k位数字: n >> k & 1 返回n的最后一位1&#xff1a;lowbit(n) n & -n 题目&#xff1a;二进制中1的个数 题目 给定一个长度为 的数列&#xff0c;请你求出数列中每个数的二进制表示中 1 的个数。 输入格式 第一行包含整数 。 第…

kubeSphere DevOps自定义容器 指定nodejs版本

✨✨✨✨✨✨ &#x1f380;前言&#x1f381;基于内置镜像构建&#x1f381;把镜像添加基础容器中&#x1f381;检查容器是否配置成功&#x1f381;不生效的原因排查&#x1f381;按步骤执行如下命令 &#x1f380;前言 由于我本地的开发环境node是16.18.1,而自带容器node的版…

体验华为云对话机器人服务 CBS

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 前言一、开通…

【虚拟化 VS 容器化】

目录 1. 虚拟化1.1什么是虚拟化&#xff1f;1.2虚拟化的特点1.3虚拟化主流技术1.4虚拟化的应用场景 2. 容器化2.1什么是容器化&#xff1f;2.2容器化的特点2.3容器化主流技术2.4容器化的应用场景 3. 虚拟化VS容器化3.1图解区别3.2架构区别3.3表式区别 4. 虚拟化的发展趋势参考链…

MSTP协议

目录 MSTP 基本原则 MSTP术语 BPDU变化 三种生成树的比较 MSTP MSTP&#xff08;802.1s&#xff09;多生成树。 多生成树(MSTP)解决&#xff1a; &#xff08;1&#xff09;去掉环 &#xff08;2&#xff09;负载均衡&#xff08;重点&#xff09; &#xff08;3&#xf…

本地Vscode使用SSH连接Linux虚拟机循环输入密码,无法登陆

今天在工作的时候没有在本地关闭Vscode的前提下&#xff0c;重启了虚拟机后&#xff0c;发现ssh连接不上了&#xff0c;症状就是反复输入密码就是进不去系统&#xff0c;查了很多网上的教程都没啥用&#xff1b; 最后就一招彻底解决问题&#xff1a; 第一步&#xff1a;打开虚…

发生内存泄漏后

内存泄漏是指程序在运行过程中分配的内存无法被释放&#xff0c;导致内存使用量不断增加&#xff0c;最终可能导致程序崩溃或系统崩溃。 产生内存泄漏的原因 内存泄漏可能是由多种原因造成的&#xff0c;例如&#xff1a; 忘记释放内存。由于项目比较大&#xff0c;一般申请内…

电脑自动开机播放PPT的解决方案

客户有个需求&#xff0c;要求与LED大屏幕连接的电脑定时自动播放PPT。为了安全电脑在不播放的时段&#xff0c;必须关机。 目录 1、使用“时控插座”并进行设置 2、戴尔电脑BIOS设置&#xff08;上电开机&#xff09; 3、设置Windows自动登录 4、任务计划设置 5、启动Au…

数据结构与算法-二叉树-路径总和lll

路径总和lll 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只能从父节…

华为配置ACL限制用户通过Telnet登录设备

配置ACL限制用户通过Telnet登录设备示例 组网需求 如图1所示&#xff0c;PC与设备之间路由可达&#xff0c;用户希望简单方便的配置和管理远程设备&#xff0c;可以在服务器端配置Telnet用户使用AAA验证登录&#xff0c;并配置安全策略&#xff0c;保证只有符合安全策略的用户才…

Zoomit 安装与使用

Zoomit 安装与使用 1&#xff09;工具介绍 ZoomIt 是一款非常实用的投影演示辅助软件 ZoomIt 是一种在所有 Windows 设备上运行的工作的注释和缩放工具 2&#xff09;下载地址 地址&#xff1a;https://zoomit.en.softonic.com/ 3&#xff09;安装教程 第一步 第二步 …

演讲《罗振宇2024“时间的朋友”跨年演讲》观后感

虽然看罗老师的跨年演讲&#xff0c;已经过去快一个月了&#xff0c;但是自己认为还是非常值得写观后感的&#xff0c;本身罗老师的演讲&#xff0c;也是自己非常喜欢的&#xff0c;所以每年元旦&#xff0c;跟随罗老师过跨年&#xff0c;已经是自己的习惯了。 就像赵本山大叔…

低代码:为消防数字化管理系统建设插上高效的翅膀

随着科技的不断进步&#xff0c;数字化转型已经成为各行各业发展的必然趋势。消防行业作为保障公共安全的重要领域&#xff0c;也面临着数字化转型的迫切需求。 “2023年10月27日&#xff0c;国家综合性消防救援队伍信息化工作暨现场联合作战指挥通信体系示范建设现场会在内蒙古…

Linux基础指令大汇总

Linux的指令比较多&#xff0c;在学习的过程中要学会总结和归纳&#xff0c;同时结合实践多多使用&#xff0c;就像学数学一样&#xff0c;不是背过公式就等于掌握的&#xff0c;而是要知道在什么时候用&#xff0c;怎么用才是关键。 这篇文章会列举一系列常用的指令&#xff0…

HTTP3/QUIC 性能测试与配套组件

背景 最近一年很多关于QUIC的文章层出&#xff0c;但是发现一个问题&#xff0c;这些文章都是在介绍QUIC或HTTP3是怎样的一个东西&#xff0c;以及它的优点和机制&#xff0c;将它夸的近乎上天了。然而有心的人估计会亲手做一些测试&#xff0c;就会发现这个被捧上天的东西性能…

物联网IOT视频设备如何快速对接阿里云生活物联网(Link Visual)并成功上云?

原文永久更新地址&#xff1a;https://www.yundashi168.com/472.html 文章来源&#xff1a;猿视野 如果有图片看不清楚&#xff0c;加载不出来&#xff0c;请阅读原文。 什么是Link Visual、 Link Visual是生活物联网平台针对视频产品推出的增值服务&#xff0c;提供视频数据上…

php下curl发送cookie

目录 一&#xff1a;使用 CURLOPT_COOKIE 选项 二&#xff1a;CURLOPT_COOKIEFILE 三&#xff1a;CURLOPT_HTTPHEADER php curl发送cookie的几种方式,下面来介绍下 一&#xff1a;使用 CURLOPT_COOKIE 选项 通过设置 CURLOPT_COOKIE 选项&#xff0c;你可以将 cookie 字符…

将 Amazon Bedrock 与 Elasticsearch 和 Langchain 结合使用

Amazon Bedrock 是一项完全托管的服务&#xff0c;通过单一 API 提供来自 AI21 Labs、Anthropic、Cohere、Meta、Stability AI 和 Amazon 等领先 AI 公司的高性能基础模型 (FMs) 选择&#xff0c;以及广泛的 构建生成式 AI 应用程序所需的功能&#xff0c;简化开发&#xff0c;…

基于Grafana+Prometheus搭建可视化监控系统实践

基本介绍 Grafana&#xff1a;一个监控仪表系统&#xff0c;可以根据提供的监控数据&#xff0c;生产可视化仪表盘&#xff0c;同时也具有告警通知功能。这里的监控数据来源&#xff0c;目前主要以Prometheus为主&#xff08;也支持其它数据源&#xff09;&#xff0c;每次展现…