预计所需阅读时间:10分钟
之前翻译过一篇叫《用乐高解释模型-视图-控制器(MVC)框架》的文章,自己用Flask来做网页开发也有一段时间,这里总结一下个人对于新增或修改一个功能的理解,在MVC框架下,开发流程是怎样的,调试的切入点在哪里。
一、数据库模型
新增或修改一个功能,首先要对它的数据库和数据表进行新建与修改,建立与修改ORM类model.py,让它的每个结构化字段对与业务中的数据,或者需要进行数据分析的数据进行对应,每个字段都有意义,字段类型要符合实际需要,而不简单使用整数、浮点、字符串、日期时间这几种类型,这对于一个个人博客已经足够,但实际业务是不够的。
接着进行数据库初始或迁移,如果迁移过程中出现无法识别字段改变的问题,可以参考这篇文章《解决INFO [alembic.runtime.migration] Will assume non-transactional DDL问题》进行解决。
二、表单类
然后,建立与数据库字段对应的表单类form.py。当然不需要每个字段都要用表单来填写,有些不显示的用隐藏区域段,有些是有默认值,例如:默认是当前的时间。密码要验证,所以要额外多写一个区域段。提交submit这个区域段,通常我不会写在表单类,因为如果用AJAX提交的话,它的类型就是Button不是Submit,提交按钮用默认样式比较丑,或者位置不合适,所以我喜欢在前端对表单的整体和提交按钮进行设置。
如果加入了flask_ckeditor,会有一个CKEditorField可以使用。
三、新增的功能
在后端这里要开始写控制器功能。这里建议视图函数名字、路由解析的地址名、模板名都最好一致,不然项目越来越大,功能越来越多,在前后端引用这些名字的时候,就很容易混乱。
新增功能就是在数据库能实现在数据建立一行或多行,简单来说,就是视图函数将前端提交的表单转为一个ORM对象,再提交到数据库。前端模版就要根据表单类写出来,记得添加上CSRF的值。打开浏览器看看是否为所设想那样。
在浏览器提交内容的过程中,可以会出现一些错误,那就要找开浏览器的开发人员工具。如果服务器的响应码是200,但数据库没增加记录,很大可能前端出现错误。前端出错误可以先看Javascript有没有错误,可以通过console.log()来打印关键的变量值,如果没发现问题可以看看表单渲染出来的HTML语法是怎样的,有可能JQ没有选到对应的网页元素。如果HTML没问题,可以看看有没有form.data提交到服务器,没有CSRF,提交方法是怎样,数据类型是JSON还是其它。这样基本就能排除前端的错误。
另外,表单的验证有两方要注意,在前端方面,前期开发还没写提示,自己写的表单不符合要求,提交只是原地刷新。在后端方面,如果提交的表单数据不通过,不会进入if form.validate_on_submit():里面的代码,也不添加数据记录。
如果提交后,服务器的响应码是500之类,那肯定是控制器有问题。这里检查视图函数,看在处理前端提交过来的数据有没有变成空值。最有效的解决办法是用try/except语句来找到错误。最后也基本能把后端的问题解决。
四、删除的功能
数据库有了记录后,需要有一个列表的界面来显示这些对象。所以控制器的逻辑就是把某个表符合条件的内容读取出来。通过模版语法,如:{{ user.id }}, {{ user.name }}之类显示在前端的一个表格table里。
这时就是可以新增、删除、编辑的按钮先添加到网页上。接着写删除的视图函数。逻辑很简单就是根据id来找到这个要删除的对象,然后提交到数据库。但要注意CSRF保护,后端删除时要验证是否为管理员,是否有对象的删除权限。
五、修改的功能
修改功能的表单,通常Get和Post的请求方式都要用要用到,在写模版和视图函数中都要注意。在默认Get的请求下,是将原来的数据从数据库读取传到网页前端。当前端发生修改,然后用Post请求将修改后的数据写入数据库。
有些修改表单要用用户密码验证,这种情况,在前端和后端都要加上验证的逻辑。
另外,渲染模版时可以加上特定的参数,例如render_template('admin/article/article_edit.html', form=form, category=category....)后面这些变量在前端可以用模版语法来使用,这些变量可以称之为上下文对象。这些对象可以是一般的数字、字符串,也可以是可迭代的序列,如列表、元组、字典、集合,但不能是迭代器的生成器。模版语法能迭代这些序列,迭代器和生成器是惰性的,所以模版语法无法再回到后台,读取下一值。
六、查找的功能
添加查找功能,要有查找的表单,可以自己建一个表单类,或是直接在HTML写这个表单。要注意的是,表单用的是单选、多选、下拉菜单之类的网页元素,记得要在value要有不同的值。
然后,要根据表单传来不同的值,在后台设定不同的判断条件和不同ORM查询语句。
七、分页的功能
这里涉及两方面,一个是列表的内容要分页,二是搜索的结果要分页。
用ORM查询时用分页对象来伟出相关结果即可,以下是参考代码:
@member_module.route('/address_list', methods=['get', 'post']) def address_list(): page = request.args.get('page', 1) # 分页对象 res = UserAddress.query.filter(UserAddress.user_id == current_user.user_id).order_by( UserAddress.id.desc()).paginate(int(page), current_app.config['XPMALL_MANAGE_GOODS_PER_PAGE']) addresses = res.items # 分页对象里面的项目 pageList = res.iter_pages() # 分页地址的列表 total = res.total # 结果项总数 pages = res.pages # 分页总数 return render_template('member/address/address_list.html', addresses=addresses, pageList=pageList, total=total, pages=pages)
初学者者在将搜索表单加上分页功能,在测试时会遇到按第2个分页之后,会成显示所有记录的第2页而不是搜索结果的第2页,关于搜索结果分页的功能,可以参考这篇文章来解决:《Flask中用同一个模板去渲染搜索分页与非搜索分页》。
八、不同权限用户显示的内容
Flask的模板也有面向对象中继承的概念,通常会有一个base.html模板,这是网站给所有人查看要继承的模版;还有的是给注册用的模版,涉及用户中心相关的页面,一般是继承user_base.html模版;最后一类是给管理员的模版,涉及网站后台页面,一般是继承admin_base.html模版。
如果有不同级别的管理员,首先要在数据库上做区分,增加is_admin, is_manage, is_editor之类的字段。然后,如果是管理员在目录下的__init__.py文件写入以下检测代码:
# 需要flask_login插件 # 检测管理员账户 @admin_module.before_request # 在管理员所有的视图函数前执行这个函数 @login_required # 需要要用户登陆 def is_admin(): if not current_user.is_admin: return redirect(url_for("login"))
会员目录下的__init__.py文件则简单一些:
@member_app.before_request @login_required # 装饰器已完成检测用户是否登陆功能,所以函数写什么也不重要 def is_login(): # print(session['user'])
有不同级别的管理员,最好要分开权限使用Flask_login插件来为不同视图函数加上不同权限的装饰器。
如果后台的角色要操作的界面差异明显,例如是给运营、编辑人员,可以建立另外的目录写这个角色的视图函数与模版。
另外,如果不同角色要用的上下文对象,则可以写在角色的__init__.py文件里,例如:
@member_module.context_processor def getCart(): ''' 计算购物车信息 :return: 购物车商品数量总数,总金额 ''' cart = Cart.query.filter_by(user_id=current_user.user_id).all() cart_amount, cart_total = 0, 0 if cart: for item in cart: cart_amount = cart_amount + item.amount cart_total = cart_total + item.goods.price * item.amount print(cart_amount, cart_total) return {'cart_amount': cart_amount, 'cart_total': cart_total} else: return {'cart_amount': cart_amount, 'cart_total': cart_total}
如果是全局都可以用的,那注册在app.py里:
def register_template_context(app): @app.context_processor def getRecomendGood(): ''' 获取推荐商品 :return: 推荐商品上下文对象 ''' recommend_goods = Goods.query.filter_by(is_recommend=1).order_by(Goods.create_time.desc()).limit(10) return {'recommend_goods': recommend_goods}
做网页的全栈开发技术要求还是比较高的,要掌握html、CSS、Javascrip、其它前端框架、Flask框架、Jinja2模板语法、Python语法、数据库的知识,调试时要非常细心才行,通过浏览开发工具,IDE的控制台定位到问题是在前端还是后端,是静态html、CSS部分,还是动态Javascrip、模版语法部分,是Python语法、Flask用户问题,还是数据库、ORM用法的问题。
评论