上一篇帖子我们使用 Flask 创建了最基本的 web 服务。使用 bootstrap 对页面进行装点,使用 JQuery Ajax 实现了在页面上实时显示 log 的功能。趁着周末,我继续开始学习更多的东西以满足这个 web 服务的需求。
之前我们有了首页,有显示 log 的页面。之后我们还需要查看配置详情和创建新配置的页面。 那么我们会在页面中有很多重复的东西。例如我们所有的页面都有导航页,所有页面都要加载 JQuery。我们希望写页面的时候可以像写 python 一样可以使用对象的继承功能以减少重复的代码。所以我们用 Flask 的模板继承功能来写页面。现在我们创建一个 base.html。 代码如下:
{% block content %}{% endblock %}
我们看上面的代码,这是我们所有页面的父页面。 它很普通,跟其他页面貌似是一样的。但我们看到代码的底部,有这样一段的代码:
{% block content %}{% endblock %}
这段代码的意思是告诉继承它的子页面。子页面所有的内容将会添加到这里来。所以我们再看看子页面怎么写的。如下:
{% extends "base.html" %}
{% block content %}
{% endblock %}
在子页面中,我们开头使用 extends “base.html” 来继承父页面,在结束时使用 endblock。 结合父页面的定义,我们可以知道子页面的所有内容都显示在父页面的定义的 block 中。所以我们就可以看到如下的效果图:
编辑
这样子我们所有的页面都拥有了上面的导航栏。
接下来我们再聊一个简单的代码复用功能。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。 下面看看在页面中如何引用:
查看详情 »
在页面上的使用方式也是一样的。 另外如果想给 URL 传递参数,后面可以跟参数名=值得方式传递。
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/')
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 !={} %}
{{ form.errors }}
{% endif %}