写在前面:
近期,博主通过Flask Web框架,利用python语言实现多线程ping,数据库采用SQLite实现了针对IP地址管理功能的初步实现。应用整体框架如下:
在应用中,希望能够实现IP地址的使用登记,这时需要在前台页面中实现根据一级菜单选择的内容,动态关联出二级菜单可以选择的内容。
其中一级菜单的内容是通过在数据库轮询得到的,二级菜单的内容对应的需要根据一级菜单选择的内容作为查询条件,再去数据库中搜索关联内容。
依然由于博主不是专门做开发的,被这个问题困扰了很久,直到搜索到优快云上另一篇文章才明白了解决这个问题的思路和方法,附上文章连接,并在此表示感谢。
https://blog.youkuaiyun.com/qq_38787214/article/details/86319271
博主的实现思路和很多代码也与上述完全一致,此篇文章仅用于整理思路。
实现思路:
实现的思路其实很简单
首先:通过Flask-WTF构造表单,一级菜单和二级菜单默认选项均为数据库中对应字段的所有值。
之后: 在用户选择了一级菜单以后,调用ajax,将用户在表单中选择的一级菜单的内容发送到flask视图函数中
再后:在视图函数对应的路由处理函数中,实现根据一级表单内容在数据库中过滤查询二级表单内容,并将查询结果返回
最后:ajax读取返回内容,将内容添加到下一级列表的选项中
实现代码:
数据库结构如下:
class IPResources(db.Model):
id = db.Column(db.Integer, primary_key=True)
IP = db.Column(db.String(20), unique=True)
Network = db.Column(db.String(200))
Vlan_ID = db.Column(db.Integer)
Area_Name = db.Column(db.String(200))
SubArea_Name = db.Column(db.String(200))
Option_Func = db.Column(db.String(200))
Using_Status = db.Column(db.String(20),default='未使用')
Info_Status = db.Column(db.String(20),default='未登记')
Check_Time = db.Column(db.DateTime,default=datetime.now())
表单结构如下:
class IPUsingCreate(FlaskForm):
area_name = SelectField(u'请选择一级区域', validators=[DataRequired()], render_kw={ 'class': 'form-control'})
subarea_name = SelectField(u'请选择二级区域', validators=[DataRequired()], render_kw={'class': 'form-control'})
sys_name = StringField(u'用途描述', validators=[DataRequired()], render_kw={'class': 'form-control'})
submit = SubmitField(u'添加', render_kw={'class': 'btn btn-primary'})
首先,我们要实现一级二级菜单的默认选项为该字段的所有值
要实现这一点的代码如下:
分别创建能够生成指定字段所有值并添加到列表中的函数,实现代码如下:
查询函数
def select_area_query():
a0 = [(IPResource.Area_Name, IPResource.Area_Name) for IPResource in IPResources.query.all()]
a = list(set(a0))
a.sort(key=a0.index)
print(a)
return a
将查询结果赋值给表单选项的方法:
form.area_name.choices = select_area_query()
二级菜单默认选项的获取方法与一级表单相同,在此不再解释了
之后需要通过flask的render template方法将form传入模板中,并将模板进行渲染
前端代码如下:
<form class="form-inline" method="post">
{{ form.csrf_token }} <!-- 渲染CSRF令牌隐藏字段 -->
{{ form.area_name.label }}
{{ form.area_name }}
{{ form.subarea_name.label }}
{{ form.subarea_name }}
{{ form.sys_name.label }}
{{ form.sys_name }}
{{ form.submit }}
</form>
这样,我们在页面中就可以得到如下表单,表单中的默认选项是对应数据库字段的所有值
之后,我们希望实现在用户选择了一级菜单以后,调用ajax,将用户在表单中选择的一级菜单的内容发送到flask视图函数中
这里实现代码如下:
我们首先将模板中的form进行修改,采用Bootstrap_flask支持的render_field方法快速渲染表单
<form class="form-inline" method="post">
{{ form.csrf_token }} <!-- 渲染CSRF令牌隐藏字段 -->
{{ render_field(form.area_name,onchange="Select('area_name','#subarea_name','/dashboard/ipusing_create/select_area')") }}
{{ form.subarea_name.label }}
{{ form.subarea_name }}
{{ form.sys_name.label }}
{{ form.sys_name }}
{{ form.submit }}
</form>
通过HTML DOM事件中的表单事件onchange事件,当检测到表单内容发生变化的时候触发对应的javascript函数。
我们在这里定义一个Javascript函数,Select,我们需要在select这个函数中传入三个参数,分别是一级菜单的ID,二级菜单的ID和将数据传导flask后台对应的路由。
下面我们看具体的JavaScript代码:
<script>
function Select(choose,id,register_url){
var data;
var select = document.getElementById(choose);
$(id).html("");
for (i=0;i<select.length;i++){
if (select[i].selected){
Area_Name = select[i].text;
data = {
"area_name":Area_Name
};
$.ajax({
url:register_url,
type:"POST",
data:JSON.stringify(data),
contentType:"application/json; charset=UTF-8",
success:function (data) {
if (data){
for (i=0;i<data.length;i++){
$("<option value='"+data[i]+"'>" + data[i] + "</option>").appendTo(id);
}
}
else{
alert('error');
}
}
});
}
}
}
</script>
下面我们对上面的代码做简单解释,当一级菜单的某一项被选中的时候,将对应的内容传入data中,之后将data转换为JSON数据,将该数据通过POST的方法发送到指定的URL,对应到flask应用中,之后将flask返回的数据添加到二级菜单的选项中,如果没有返回数据则抛出错误。
最后,我们来看后端处理代码
@app.route('/dashboard/ipusing_create/select_area',methods = ['GET', 'POST'])
def selectArea():
if request.method == 'POST':
data = request.get_json()
area_name = data['area_name']
subarea_name = select_subarea(area_name)
return jsonify(subarea_name)
当后端收到到这个url的POST请求的时候,获取数据,将数据内容作为参数去数据库中查询对应的二级表单内容,查询语句如下:
def select_subarea(area_name):
a0 = [IPResource.SubArea_Name for IPResource in IPResources.query.filter_by(Area_Name = area_name).all()]
a = list(set(a0))
a.sort(key=a0.index)
print(a)
return a
这样flask返回的是一个列表形式的数据,我们通过JavaScript中的函数,通过循环方法将他依次添加到二级表单的选项中就可以了。这个功能在前面的JS函数中实现了。
至此,便完成了一个二级表单的联动功能
同理,也能够完成多级表单的联动,只需要在第二个表单内容选中后,将一级和二级数据均传送到后台,后台再根据内容在数据库中进行过滤查询,再将查询结果返回即可。
实现效果如下:
遗留问题:
目前,博主虽然通过这样的方式实现了多级表单的联动,但这之中仍然存在着部分问题。
包括必须当一级表单内容发生变化时才会触发JS函数,当一级表单第一个选项就是我想要选择的内容是,不会去后台触发更新,需要先选择其他内容再反过来选择第一个选项,触发表单内容变化。
还有一个问题就是之前博主提到的那篇文章中说需要重新设置CSRF做表单验证参数,博主这里没有做也没有出现问题,不确定是不是有安全隐患。
尾巴:
有的时候在其他商业化产品中很简单的功能,或者一般开发来讲很容易的需求。作为菜鸟需要研究很长时间,进行反复尝试。不过,好在问题都能最终解决,博主也是在这样的过程中不断提升自己的能力和对自己的信心。
有的时候碰到网上如果有现成的思路和案例,耐心研究和尝试,还是能够实现我们希望达到的效果的。