best pracetice of flask development

背景简介


填坑,记录分享在2016年12月使用flask进行web开发遇到的一些问题。

本人只有入门级的python经验,之前只写过一些爬api数据的工具,
入门级Linux经验 —— MOOC edx和Rasp pi 2进行少量实践,
入门级Java coding经验 —— MOOC edx,
入门级Javascript经验 —— codeacadamy, w3cschool,
入门级Machine learning经验 —— MOOC coursera by Octave,
未做过web开发相关的事。

本次事件的背景是要上线一个可以识别屋顶的web应用,
使用场景主要在新能源行业,具体作用是辅助分布式光伏项目开发。
本次web开发主要基于web development of flask
这本书。

局限于个人经历,经过简单咨询,起步时决定使用flask作为后端,也方便对接现有的机器学习包。
由于有传参的需求,因此前端使用AngularJS进行相关数据绑定和传输。
GIS api原计划使用Google map api, 但是由于资金没有到位,暂时无法获取可靠的服务器资源,改用国内使用环境较友善的AMAP(高德地图) api.
初步的项目是在线执行识别程序脚本(python脚本),
但是在项目实际过程中发现脚本的运算时间过长(dell xps13 9350, ubuntu 16 LTS 平均识别时间约20s),
因此决定把识别和渲染分离。识别结果单独进sqlite数据库,web app只进行查询,缩短页面响应时间,改善用户体验。

PS: 后改用aws ec2 t2.micro服务器处理图像识别任务,效率大大提升,每小时可处理大约100 km2区域,涵盖3000+ poi.

开发环境为WSL - windows subsystem linux, dst. Ubuntu 14 LTS
只有一台电脑,之前是把Ubuntu装在另一个外置固态硬盘里,启动不太方便,
而且Ubuntu的中文输入法调制,shadowsocks配置均以失败告终,为了更顺畅地用
google和stack overflow折中于此。

现阶段发现WSL唯二的不好用的地方是:

  1. python virtualenv部署不成功,可以venv active但是实际lib路径仍为系统路径
  2. 网络层没有开发完全,ifconfig及相关usr/network/interface等设置暂时均无法实现。
    WSL currently does not support NETLINK sockets

代码分析


文件目录,典型的flask文件配置:

1
2
3
4
5
6
7
frontiersolar/
├── app
├── migrations
├── requirements
├── tempfiles
├── tests
└── venv

基于上文中提到的教程,
其中主要变更的内容在app/models.py, app/main/views.py和app/templates/newpages.html

在数据库中新增了一个表,并注入数据。
如果不习惯命令行操作,推荐使用SQLiteStudio, 其GUI操作界面非常友善好用。

在views.py和templates中新增了两个html页面以增补额外的功能。

views.py的部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@main.route('/NEWPAGE1/<username>', methods=['GET', 'POST'])
@login_required
def eagleeye(username):
user = User.query.filter_by(username=username).first_or_404()
return render_template('NEWPAGE1.html', user=user)
@main.route('/NEWPAGE2/<username>', methods=['POST', 'GET'])
@login_required
def roofresult(username):
user = User.query.filter_by(username=username).first_or_404()
if request.method == 'POST':
global coords, clist, coord_list, center_coord
coords = request.get_json(force=True)
clist = [coords['minLat'], coords['minLng'], coords['maxLat'], coords['maxLng'], coords['cLng'], coords['cLat']]
coord_list = Roof.query.filter(Roof.lat.between(clist[0], clist[2]), Roof.lon.between(clist[1], clist[3])).order_by(
"area desc").all()
for roof in coord_list:
db.session.expunge(roof)
center_coord = [clist[4], clist[5]]
return render_template('NEWPAGE2.html', coord_list=coord_list, user=user, center_coord=center_coord)

NEWPAGE.html中的部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
AMap.event.addDomListener(document.getElementById('eargleEye'), 'click', function() {
var drawRectangle = mouseTool.rectangle(); //用鼠标工具画矩形
AMap.event.addListener( mouseTool,'draw',function(e){ //添加事件
var rect = e.obj;
var data = {"minLng" : rect.getBounds().getSouthWest( ).getLng(),
"maxLng" : rect.getBounds().getNorthEast( ).getLng(),
"minLat" : rect.getBounds().getSouthWest( ).getLat(),
"maxLat" : rect.getBounds().getNorthEast( ).getLat(),
"cLng" : rect.getBounds().getCenter( ).getLng(),
"cLat" : rect.getBounds().getCenter( ).getLat()
};
if(confirm("are you sure to submit?"))
{
$.post({
url : "{{ url_for('main.NEWPAGE2', username = current_user.username) }}",
data: JSON.stringify(data),
contentType: 'application/json;charset=UTF-8',
success: function(data) {
top.location.href = "{{ url_for('main.NEWPAGE2', username = current_user.username) }}";
},
async:false
});
}
else
{
mouseTool.close(true);
}
});
}, false);

  • 其中第二个页面为了达成传参的目的,使用了全局变量。
  • 个人使用中感受到的flask的局限,除了form等常规的前后端传参方法外,使用POST方法
    传递json或者xml data相对复杂。
  • 前端由于使用jinja2模板,html文件中除了静态模板文件还包含了大量的js代码,
    个人理解这样把视图和功能混放,不便于后期维护,其二也把功能部分的数据放在了前端,
    可以轻易获取,保密性较差。

trouble shooting


传参

在NEWPAGE1.html中使用ajax的POST方法传参给后端的NEWPAGE2,
NEWPAGE2后端处理完这些参数后把从数据库查询的object返回到NEWPAGE2.html渲染。

初始处理的时候发现NEWPAGE2后端程序会先响应一次POST方法接收数据,再响应一次GET方法发送渲染数据。
且只有在POST方法时才能获取前端传入数据。

POST方法获取json数据并存储计算,GET方法时只要进行到解析json数据的指令就会立即报错终止。
因此必须要写if表达式并且return语句要在if外侧,否则无法完成前端模板渲染。

之前也尝试过把需要传递的参数放入url中传递,天然形成api,但是实际操作过程中需要传递的
参数过多,远超url长度限制,后放弃。

考虑到数据读取的便利性还是希望传入obj进行操作,因此最后选择全局变量的方法处理。
由于现在处于demo阶段,无论是数据库的数据量还是网站访问的人员都不多,
该方法的问题还没有充分暴露,稳定性暂时无法判断。

部署

在测试阶段一般直接使用manage.py对flask app进行管理、运行操作,但是在生产环境中不建议
直接使用flask的内置http接口监听处理访问流量。

因此部署的时候还是建议使用gunicorn启动flask app, 并且使用NGINX对内部服务器进行反向代理。

参考文档:Explore flask

注意原生flask对代理支持不足,需要使用Werkzeug ProxyFix修复代理,详见上文档中ProxyFix section.

前端数据操作

这块暂时也没看到很好的解决方案,最近在学习vuejs, 仿MVVC框架进行进一步的拆分,
由于WSL的支持问题无法使用webpack, 因此也无法体验MEAN框架下完整的功能。

看了一些github上vue + flask的blog方案,但是暂时没有深入探索。

MVVC是否可以做到进一步解耦仍有待实际操作研究。

分享到 评论