Learn Python 3:Flask Web开发小记,大神超详细总结,三天学会

最近看了flask web开发:基于python的web应用开发实战,书中详细介绍了web程序的开发、测试、部署过程,值得一读!我在书中例子的基础上做了些更改,实现了一个简单的个人博客:niceblog,仅作为个人学习,还有许多不足的地方待完善,这里做一些简单的记录,方便以后查阅
大家如果喜欢我们文章,可以在小编的学习平台学习,可以在评论区获取
也希望看到积极留言
一、功能
1、对于普通用户,主要有如下功能:
注册、登录、重置密码(邮箱验证)文章列表、详情评论喜欢
2、对于管理员,除了有普通用户的功能,主要有如下功能:
写文章(markdown编辑)用户权限管理(管理喜欢、评论的权限)评论管理(删除、屏蔽)
3、为移动端提供相关api接口
二、项目结构
遵循了书中多文件flask程序的基本结构,下边是niceblog的项目结构:|-niceblog|-app/ 主目录 |-api/ 为移动端提供接口的蓝本 |-auth/ 权限认证的蓝本 |-main/ 主体功能的蓝本 |-manage/ 管理相关功能的蓝本 |-static/ 静态资源目录(icon、js、css) |-templates/ html模板目录 |-__init__.py 初始化项目的工厂函数 |-decorators.py 自定义的装饰器 |-email.py 发送邮件功能 |-excepitions.py 自定义异常处理 |-models.py 数据模型 |-migrations/ 数据库迁移脚本目录 |-nb_env/ 虚拟环境 |-tests/ 单元测试目录 |-config.py 配置文件 |-manage.py 启动程序以及其他的程序任务 |-requirements.txt 项目的依赖包列表
三、实现
1、工厂函数
一个简单的flask web程序可以写在单文件中,
test.py

app = flask(__name__)# 定义的路由@app.route('/')def index():return '
hello world!
'if __name__ == '__main__':app.run()
但是执行程序时,由于在全局作用域创建导致无法动态修改配置,也导致了单元测试时无法在不同配置环境运行程序。所以可以把程序的创建转移到可显示调用的工厂函数中,也就是前边项目结构中的
__init__.py
,在工厂函数中导入需要的flask扩展:
def create_app(config_name):app = flask(__name__) # 导致指定的配置对象 app.config.from_object(config[config_name]) # 调用config.py的init_app() config[config_name].init_app(app) # 初始化扩展 bootstrap.init_app(app) mail.init_app(app) moment.init_app(app) db.init_app(app) login_manager.init_app(app) pagedown.init_app(app) return app
2、蓝本
新的问题来了,使用工厂函数后,程序在运行时创建,而不是在全局作用域,必须等到执行
create_app()
后才能使用
@app.route()
装饰器,这时就要使用蓝本了,在蓝本中也可以定义路由,但是定义的路由处于休眠状态直到蓝本注册到程序后在成为程序一部分,例如main蓝本的目录结构如下:|-niceblog|-app/ 主目录 |-main/ 主体功能的蓝本 |-__init__.py 创建蓝本 |-errors.py 蓝本的错误处理 |-forms.py 蓝本的表单 |-views.py 蓝本的路由
首先看一下
__init__.py

# 两个参数分别指定蓝本的名字、蓝本所在的包或模块(使用 __name__即可)main = blueprint('main', __name__)# 导入路由模块、错误处理模块,将其和蓝本关联起来# 在蓝本的末尾导入在两个模块里还要导入蓝本,防止循环导入依赖from app.main import views, errors
2.1、表单
forms.py
是当前蓝本中的表单,项目中使用了
flaskform
,可以方便的完成表单校验,例如创建、编辑文章的表单:
class blogform(flaskform):title = stringfield('请输入文章标题', validators=[datarequired(), length(1, 128)]) labels = stringfield('文章标签(标签之间用空格隔开)', validators=[datarequired()]) summary = textareafield('文章概要', validators=[datarequired()]) content = textareafield('文章内容', validators=[datarequired()]) preview = textareafield('文章预览', validators=[datarequired()]) publish = submitfield('发布') save = submitfield('保存')
2.2、路由
views.py
就是在蓝本中定义的路由,例如主页的路由:
@main.route('/create-blog', methods=['get', 'post'])@admin_requireddef create_blog(): 写新文章 form = blogform() if form.validate_on_submit(): blog = none if form.publish.data: # 发布 elif form.save.data: # 保存草稿 return redirect(url_for('main.index')) return render_template('markdown_editor.html', form=form, type='create')
注意装饰器为当前蓝本的名字
main
,而不是之前的
app

create_blog()
称为视图函数,一个路由保存了url到视图函数的映射关系。
redirect(url_for('main.index'))
代表重定向到主页,
url_for()
的参数为要跳转到的url对应的视图函数名,但需要加上视图函数所在的蓝本名,即
main.index

render_template()
是flask提供的函数,把
jinja2
模板引擎集成到了程序中,第一个参数是模板名称对应一个html文件,即执行该视图函数后最终要渲染的页面,后边的参数为传递给模板的参数。
2.3、错误处理
errors.py
是蓝本中的错误处理程序,例如:
@main.app_errorhandler(404)def page_not_found(e):if request.url.find('api') != -1: return jsonify({'error': '请求的资源不存在', 'code': '404', 'data': ''}) return render_template('error/404.html'), 404
如果使用
@main.errorhandler
装饰器只有当前蓝本的错误才能触发,为了使其他错误也能触发所以使用了
@main.app_errorhandler
装饰器
2.4、注册蓝本
其它蓝本的定义也类似,最后需要在工厂函数中重注册蓝本,例如:
def create_app(config_name):# ...... # 注册main蓝本 from app.main import main as main_blueprint app.register_blueprint(main_blueprint) # 注册auth蓝本 from app.auth import auth as auth_blueprint # 使用url_prefix注册后,蓝本中定义的所有路由都会加上指定前缀,/login --> /auth/login app.register_blueprint(auth_blueprint, url_prefix='/auth') return app
3、前端
3.1、jinja2
flask使用
jinja2
作为模板引擎,模板是一个包含响应文本的html文件,其中包含只有在请求的上下文才知道的动态占位变量。默认情况下,模板保存在
templates
目录。

jinja2
模板中
{{ 变量名 }}
代表一个变量(注意变量名两边有一个空格,可以识别任意类型的变量),从渲染模板时使用的数据中获取。如果变量的值是html,由于转义的原因导致浏览器不能正常显示html代码,所以需要使用
safe
过滤器,例如文章详情的html显示就需要这样处理,过滤器写在变量名后用竖线隔开
{{ 变量名|过滤器名 }}
jinja2
中用
{% 控制语句 %}
代表控制结构来改变模板的渲染流程,例如:
# 条件控制{% if xxx %}
android
{% else %}
ios
{% endif %}
# for循环{% for x in xs %}
{{ x }}
{% endfor %}
# 导入{% import 'xxx.html' %}
# 包含{% include 'xxx.html' %}
导入、包含的目的都是为了复用,还可以通过继承实现复用,类似于类的继承:
# 继承{% extends base.html %}
通过继承,模板中重复的代码都可以写在父模板里,例如导航条和页面底部footer就可以放在父模板里。
3.2、bootstrap
前端使用了bootstrap框架,它提供了良好的css规范,可以帮助我们更好的美化界面,具体的可参考:{% extends bootstrap/base.html %}
bootstrap的基类模板
base.html
提供了一个网页框架,包含了bootstrap中的所有css和js文件。除此之外基类模板还定义了许多可在其子类模板中重定义的块,使用格式如下:
{% block 块名称 %}{% endblock %}
常用的块如下:
块名称
含义
head标签中的内容
title
标签中的内容
body标签中的内容
stylescss样式单的定义
navbar自定义的导航条
content自定义的页面内容
page_content定义content在内部
scriptsjs声明,一般在模板尾部
注意如在子模板在模板已有的块中添加新内容,需要使用
super()
函数:
{% block scripts %}{{ super() }} {% endblock %}
3.3、flask-wtf
在2.1中我们已经看到了用
flask-wtf
定义表单的方式,即自定义的表单类继承
flaskform
类,并添加需要的类变量,
flask-wtf
定义了许多标准字段可以被渲染成指定的表单类html标签,例如:
字段名
对应的h5标签
stringfield文本框
textareafield多行文本框
passwordfield密码输入框
booleanfield复选框
submitfield表单提交按钮
同时
flask-wtf
还提供了许多常用的表单校验函数,例如:
email()

equalto()

datarequired()

length()
等等,当点击提交按钮时,会自动校验表单是否满足预定义的条件。
在2.2中,我们通过参数把表单类的实例同步
form
参数传入模板:
render_template('markdown_editor.html', form=form, type='create')
在模板中可以通过如下方式生表单(只保留了部分核心代码):
{{ form.hidden_tag() }} {{ form.title(id=title, class=form-control editor-blog-title, placeholder=form.title.label.text) }} {{ form.labels(class=form-control editor-blog-area, placeholder=form.labels.label.text) }} {{ form.summary(class=form-control editor-blog-area, placeholder=form.summary.label.text, rows=3) }} {{ form.publish(class=btn btn-info) }} {{ form.save(class=btn btn-success) }}
这样的好处是我们能自定义表单的样式等等,但是工作量蛮大的,如果对表单样式没有特殊的需求,bootstrap中的表单样式可以满足需求,可以通过
flask-bootstrap
提供的辅助函数快速的渲染表单,只需要如下两步:
{% import bootstrap/wtf.html as wtf %}{{ wtf.quick_form(form) }}
例如登录的h5模板就是这样做的。
form.hidden_tag()
模板参数将被替换为一个隐藏字段,用来实现在配置中激活的 csrf 保护。如果你已经激活了csrf,这个字段需要出现在你所有的表单中。
在2.2中,如果点击表单提交按钮,所有的表单都能成功通过校验,则
form.validate_on_submit()
的值为
true
,否则校验失败,网页会出现对应提示。如果有两个提交按钮,那么在校验成功后,还需要判断点击的是哪个按钮,否则所有的按钮都执行了同一个操作。例如我们的文章发布和保存按钮,当表单类中的按钮字段的
data
属性为
true
则代表该按钮被点击,例如:
if form.publish.data: # 发布elif form.save.data: # 保存草稿
3.4、jquery
有些页面需要在相关操作后修改控件的css样式,例如文章详情的喜欢和取消喜欢按钮,最简单的方式是操作成功后直接刷新整个页面,但这样体验并不好,更好的方式是局部刷新。这里直接使用jquery(bootstrap也提供了类似的操作,同时包含了jquery,不需要单独导入jquery)来实现。使用
jquery
强大的选择器功能可以方便的得到要操作的
dom节点
,按钮的点击也是发起一个请求,
jquery
也集成了
ajax
,可以方便的处理请求,在请求完成后根据响应结果来更改
dom节点
的样式。看下按钮的点击事件:
favourite = function (id) { if ($('.blog-favourite-btn').length > 0) {//取消喜欢$.get('/manage/blog/cancel_favourite', { id: id }).done(function (data) { $('.blog-favourite-btn span').removeclass('glyphicon-heart').addclass('glyphicon-heart-empty'); $('.blog-favourite-btn').removeclass('blog-favourite-btn').addclass('blog-unfavourite-btn'); }) } else if ($('.blog-unfavourite-btn').length > 0) {//喜欢 $.get('/manage/blog/favourite', { id: id }).done(function (data) { if ('200' === data) { $('.blog-unfavourite-btn span').removeclass('glyphicon-heart-empty').addclass('glyphicon-heart'); $('.blog-unfavourite-btn').removeclass('blog-unfavourite-btn').addclass('blog-favourite-btn'); } if ('403' === data) { alert('没有操作权限'); } }) } }
4、markdown
书中使用的是
flask-pagedown

markdown
两个库来实现对markdown功能的支持,但是不够理想,有些markdown语法并不能很好的支持,例如
flask-pagedown
实时预览时并不支持代码块和表格等。最后使用了marked这个库,它是一个全功能的markdown解析器和编译器,用javascript编写,构建速度快,其实就是实时将用markdown语法编辑的内容转换成对应的html预览,但是没有css样式的html还是有点丑,github-markdown-css是一个不错的选择,可以帮助我们实现github风格的markdwon预览。既然是要编辑文章那么直接使用html里的
肯定难以实现理想的效果,这里使用了ace,它是一个用javascript编写的独立代码编辑器,下载
...