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 DataRequiredclass ConfigForm(FlaskForm):# basename_prefix = StringField(u'环境名称', validators=[DataRequired()])package_name = StringField(u'包名称', validators=[DataRequired()])env_name = StringField(u'配置管理文件名称', validators=[DataRequired()])# branchprophet_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()])# pmapma_cpu = StringField(u'pma_CPU', validators=[DataRequired()])pma_memory = StringField(u'pma内存限制', validators=[DataRequired()])pma_disk = StringField(u'pma硬盘限制', validators=[DataRequired()])# ipprophet_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_prefixform.package_name.data = config.package_nameform.env_name.data = config.env_nameform.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_ipform.pma1_ip.data = config.pma1_ipform.pma2_ip.data = config.pma2_ipform.pma_disk.data = config.pma_diskform.pma_memory.data = config.pma_memoryform.pma_cpu.data = config.pma_cpureturn 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.hqwc.cn/news/433836.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MYSQL库和表的操作(修改字符集和校验规则,备份和恢复数据库及库和表的增删改查)

文章目录 一、MSYQL库的操作1.连接MYSQL2.查看当前数据库3.创建数据库4.字符集和校验规则5.修改数据库6.删除数据库7.备份和恢复8.查看连接 二、表的操作1.创建表2.查看表结构3.修改表4.删除表 一、MSYQL库的操作 1.连接MYSQL 我们使用下面的语句来连接MSYQL&#xff1a; my…

【阻塞队列】阻塞队列的模拟实现及在生产者和消费者模型上的应用

文章目录 &#x1f4c4;前言一. 阻塞队列初了解&#x1f346;1. 什么是阻塞队列&#xff1f;&#x1f345;2. 为什么使用阻塞队列&#xff1f;&#x1f966;3. Java标准库中阻塞队列的实现 二. 阻塞队列的模拟实现&#x1f35a;1. 实现普通队列&#x1f365;2. 实现队列的阻塞功…

JVM篇----第十篇

系列文章目录 文章目录 系列文章目录前言一、JAVA 强引用二、JAVA软引用三、JAVA弱引用四、JAVA虚引用五、分代收集算法前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧…

怎么获取二维码的链接?二维码转链接只需3步

怎么从二维码中提取内容呢&#xff1f;现在很多内容都会用二维码方式来存储&#xff0c;但是有些场景下二维码是无法使用的时候&#xff0c;想要查看二维码中的内容&#xff0c;就需要分解二维码成链接后使用。那么二维码分解成链接具体该怎么做呢&#xff1f;今天就将在线二维…

在DevEco开发工具中,使用Previewer预览界面中的UI组件

1、在DevEco工具中&#xff0c;点击并展开PreViewer预览器 2、在PreViewer预览器中&#xff0c;点击Tt按钮&#xff08;Inspector&#xff09;切换至组件查看模式 3、在组件查看模式下选择组件&#xff0c;代码呈现选中状态&#xff0c;右侧呈现组件树&#xff0c;右下方呈现组…

JAVA输入任意一个数字,实现递减求和(计算任意整数n的和)

摘要&#xff1a;本文介绍了使用Java编程语言计算任意整数n及其之前所有整数的和的示例代码。代码使用了Scanner类来读取用户输入的整数值&#xff0c;并通过循环计算出和结果并生成计算公式字符串。 内容&#xff1a; 在这个示例中&#xff0c;我们将展示如何使用Java编程语言…

低代码开发会是前端程序员的下一个春天吗?

最近前端的大环境不太行&#xff0c;之前身处在前端的自己薪资也越来越无望了&#xff0c;隐隐约约感觉前端做不下去了&#xff0c;2024前端找不到工作要转行吗&#xff1f; 但是别担心啊老铁们&#xff0c;前端技术精微渊深&#xff0c;除了基础的 HTML、CSS 和 JavaScript 技…

Java基础—面向对象OOP—17类与对象(创建、构造器、创建对象时简单内存分析)

把握重点&#xff0c;重点已标注&#xff0c;这篇笔记分了4个章节&#xff0c;重点看二、三、四 一、整体思维--重点把握面向对象的本质和特点 1、面向对象编程OOP&#xff1a; Object-Oriented programming 2、面向过程与面向对象 面向过程&#xff1a;线性思维 面向对象…

网络编程(Day23)

TCP/IP 面向连接&#xff0c;可重传&#xff0c;不丢包&#xff0c;可靠&#xff0c;有序 使用方法 服务端 客户端 多线程服务端 多线程客户端 多线程方法区 UDP/IP 无连接&#xff0c;可能丢包&#xff0c;不保证可靠&#xff0c;速度快 服务端 客户端 正则表达式 概述 正则…

随机点名--好玩哦

大屏滚动&#xff0c;随机点名&#xff0c;可刺激哦 想屏幕名字滚动得快一点&#xff0c;sleep时间就小一点 效果图 代码 #!/bin/bash namefile"/opt/name.txt" linenum$(sed -n $ $namefile) while : docleartmp$(sed -n "$[RANDOM%linenum1]p" $namefi…

Git+Gitee代码管理

前言 本教程最后要实现的效果是&#xff0c;将自己电脑里面的工程或者文件&#xff0c;通过git上传到gitee仓库中。 博主默认你电脑已经下载好了git&#xff0c;并注册好了gitee。 步骤 首先在gitee上创建一个仓库 创建仓库成功之后&#xff0c;你后面所要用到的命令行&am…

Linux------进程状态

前言 在之前&#xff0c;我们学习了Linux为什么要有PCB-----冯诺依曼体系结构与操作系统&#xff0c;先描述&#xff0c;在组织。使用PCB将系统中的资源组织起来&#xff0c;方便操作系统和用户进行管理访问。还学习了Linux中进程的创建和fork操作&#xff0c;现在我们来讲一讲…