注:本文主要是参照李辉大大《Flask Web开发实战》一书第7章——留言板所写,基本上都是照抄啦,主要是做笔记用,有不清楚的地方可以去查阅原文,同时附上留言板项目的Github仓库地址。
1、使用包组织程序代码
使用包组织程序代码,创建程序实例,初始化扩展等操作都可以在程序包的构造文件(init.py)中实现,代码如下
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask('sayhello')
# 在单脚本中可以直接传入__name__,Flask由此来确定程序路径,所以在使用包组织代码的时候,以硬编码的方式传入包名称作为程序名称
# 在创建程序对象后,使用config对象的from_pyfile方法即可加载配置文件,传入配置文件名作为参数
app.config.from_pyfile('settings.py')
app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True
db = SQLAlchemy(app) # 使用SQLAIchemy读取app对象生成数据库对象
from sayhello import views,errors,commands
为什么要在最后其他脚本呢?
1、在init构造文件中导入这些模块,将这些函数和程序实例关联起来
2、因为这些模块也需要从构造函数中导入程序实例,所以为了避免循环依赖,这些导入语句在构造文件的末尾定义
- 小贴士
在其他文件中导入构造文件中的变量,不需要注明注明构造文件的路径,只需要通过通过包名称导入,举个例子,在构造文件中定义的程序实例app,可以使用from sayhello import app
Flask在通过FLASK_APP环境变量定义的模块中寻找程序实例。所以在程序启动之前,我们需要给.flaskenv中的环境变量FLASK_APP重新赋值,这里只写出包名称即可FLASK_APP=sayhello
2、Web程序开发流程 遇事不决,先写注释,哈哈哈哈
- 分析需求,列出功能清单或写需求说明书
- 设计程序功能,写功能规格书和技术规格书 虽然不懂是啥
- 进入开发和测试迭代
- 调试和性能等专项测试
- 部署上线
- 运行维护与营销
写好功能规格书后,我们就可以开始实际的代码编写。具体分为前端页面(front end)和后端程序(back end)。
- 前端开发主要流程
- 根据功能规格书画页面草图
- 根据草图做交互式原型图
- 根据原型图开发前端页面(HTML CSS Javascript)
- 后端开发主要流程
- 数据库建模
- 编写表单类
- 编写视图函数和相关的处理函数
- 在页面使用Jinja2替换虚拟数据
前期考虑和规划越周全,在实际开发时就可以越高效和省力
程序功能设计
因为本程序比较简单,所以写一个非常简短的功能规格书就行
- 咋居中
- 概述:用户输入问候信息和姓名,按下提交按钮,就可以将问候加入到页面的消息列表中。
- 主页:主页是SayHello唯一的页面,页面中包含创建留言的表单和所有的问候消息。页面上方是程序的标题"SayHello",使用大字号和鲜艳的颜色,页面底部包含程序的版权标志、编写者、源码等相关信息
- 问候表单:这个表单包含姓名和问候消息两个字段,其中姓名是普通的文本字段
,消息字段是文本区域字段
。为了获得良好的样式效果,需要对这两个字段进行长度限制,姓名最长为20个字符,消息最长为200个字符。
- 用户提交发布表单以后:
- 如果验证出错,错误消息以红色小字的形式显示在字段下面
- 如果通过验证,在程序标题下面显示一个提示消息,用户可以通过消息右侧的按钮关闭提示。
- 问候消息列表:问候列表的上方显示所有消息的数量。每一条问候消息都要包含发布者姓名、消息正文、发布时间、消息编号。消息发布时间要显示相对时间,比如"3分钟前",当鼠标悬停在时间上时,弹出窗口显示具体的时间值。消息根据时间先后顺序,最新发布的排在上面。为了方便用户查询最早的消息,我们提供一个前往页面底部的按钮,同时提供一个回到页面顶部的按钮。
- 错误页面:错误页面包括404和500,和主页包含相同的部分——程序标题。程序标题下显示错误信息以及一个返回主页的
"Go Back"
链接。
3、前端页面开发
首先我们使用纸币画草图,然后使用原型设计软件画出原型图,最后编写对应的HTML页面。
-
主页原型图
常用的原型设计工具有Axure RP,Mockplus
-
错误原型图
为了简化工作量,采用Bootstrap来编写页面样式。
在传统的Flask程序中,后端完成功能后会操作HTML代码,在其中添加Jinja2语句。比如,将页面的临时URL替换为url_for函数调用,把虚拟数据换成通过视图函数传入模板的变量,或是使用模板继承等技术组织这些HTML文件
4、后端页面开发
- 数据库建模
编写完功能规格书以后,我们也就确定了需要使用哪些表来存储数据,表中需要创建哪些字段,以及各表之间的关系。对于复杂的数据库结构,可以通过建模工具来辅助建立数据库关系。
class Message(db.Model): # 定义一个Message类用来保存信息,因为使用的是SQLAIchemy,所以创建的数据库类继承自Model类
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(200))
name = db.Column(db.String(20))
timestamp = db.Column(db.DateTime,default=datetime.now,index=True)
使用index参数为timestamp建立索引,一般来说,取值较多的列(比如姓名)以及经常被用来作为排序参照的列(比如时间戳),适合建立索引。使用default参数设置默认值,传入函数名,在执行时自动调用datetime.now()
- 创建表单类
问候表单由表单类HelloForm表示,表单中使用了文本区域字段TextAreaField表示HTML中的
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField
from wtforms.validators import Length, DataRequired
class HelloForm(FlaskForm):
name = StringField('Name', validators=[DataRequired(), Length(1, 20)])
body = TextAreaField('Message', validators=[DataRequired(), Length(1, 200)])
submit = SubmitField()
创建表单类继承自FlaskForm,从wtforms中导入所需字段类型,从wtforms.validators导入所需验证器
- 编写视图函数
编写index函数,它可以处理GET请求和POST请求
- GET请求,从数据库中查询所有消息记录,返回渲染后包含消息列表的主页模板index.html
- POST请求,问候表单提交以后,验证表单数据,通过验证后将表单数据保存至数据库,使用flash函数显示一条提示,然后重定向到index视图,渲染页面。
from flask import flash, redirect, url_for, render_template
from sayhello import app, db
from sayhello.models import Message
from sayhello.forms import HelloForm
@app.route('/', methods=['GET', 'POST'])
def index():
# 加载所有记录
messages = Message.query.order_by(Message.timestamp.desc()).all()
form = HelloForm()
if form.validate_on_submit(): # 当表单被提交时进行验证
name = form.name.data
body = form.body.data
message = Message(body=body, name=name) # 实例化模型类,创建记录
db.session.add(message) # 添加记录到数据库会话
db.session.commit() # 提交会话
flash('信息提交成功')
return redirect(url_for('index')) # 重定向到index视图
return render_template('index.html', form=form, messages=messages)
- 在route装饰器中可以使用methods方法,表明所装饰的函数可以接受的请求方法,不传此参数默认使用GET方法。order_by()过滤器对数据库记录进行排序,参数是排序规则,这里根据Message模型的timestamp字段值排序,字段上附加排序方法desc()代表降序,asc()代表升序。
- 不管是POST请求还是GET请求都需要获取通过数据库模型类Message到所有的消息记录,以及实例化HelloForm表单模型,生成页面表单,将数据传给index.html模板,生成完整的HTML页面,使用render_template渲染。
- 这里通过form.validate_on_submit()方法来判断当前是否提交数据(发送POST请求),这里因为使用的是wtform,所以直接使用表单变量的data属性获取内容。
- 编写模板
可以将index.html,404.html和500.html中共有的部分抽离合并为基模板base.html。基模板包含一个完整的HTMl结构,我们在其中创建几个块,title、content和footer
url_for('index'),url_for函数指向指定的字符串视图函数
- base.html
{% block title %}Say Hello!{% endblock %}
{# 此处使用url_for函数定位到端点,通过filename来指定具体的文件,rel="icon"表示网页左上角的网站图标 #}
{# #}
{# 导入bootstrap和自己写的style.css,设置界面样式 #}
Say Hello
to the world
{# 使用get_flashed_messages函数获取到返回的flash消息 #}
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}
{% block content %}{% endblock %}
{##}
{##}
{##}
这里个人比较懒,就没有下载jquery和bootstrap的文件,直接使用cdn加载。
对基模板进行说明,首先在head标签中引入了css样式文件,通过meta设置页面属性,这里使用到main标签创建一个container容器,其中包含header、content和footer,定义了两个块分别用来修改content和footer,最后导入js文件。
- index.html
{% extends 'base.html' %}
{% from 'macros.html' import form_field %}
{% block content %}
{{ messages|length }} messages
↓
{% for message in messages %}
{{ message.name }}
#{{ loop.revindex }}
{{ message.timestamp.strftime('%Y/%m/%d %H:%M') }}
{{ message.body }}
{% endfor %}
{% endblock %}
使用form_field宏渲染表单,然后迭代传入messages变量,渲染消息列表
5、使用Bootstrap-Flask简化页面编写
扩展Bootstrap-Flask内置了快速渲染Bootstrap样式HTML组件的宏,并提供了内置的Bootstrap资源,方便快速开发,使用它可以简化在Web程序李使用Bootstrap的过程。
首先需要使用pip安装bootstrap-flask,使用方法同其他的flask插件
from flask import Flask
from flask_bootstrap import Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)
- 加载资源文件
Bootstrap-Flask在模板中提供了一个bootstrap对象,这个对象提供了两个方法可以用来生成资源引用代码:用来加载css文件的bootstrap.load_css(),用来加载js文件的bootstrap.load_js()方法。Bootstrap-Flask默认使用CDN来加载资源,同时也提供了内置的本地资源。如果你想使用Bootstrap-Flask提供的本地资源,可以将配置变量BOOTSTRAP_SERVE_LOCAL设为True。同时,当FLASK_ENV环境变量设为development时,会自动使用本地资源。 - 快捷渲染表单
Bootstrap-Flask内置了两个用于渲染WTForms表单类的宏,一个是与form_field类似的render_field()宏,另一个是用来快速渲染整个表单的render_form()宏。render_form()宏直接从bootstrap/form.html模板导入,这两个宏都会自动渲染错误的消息,渲染表单的验证状态样式。render_form()宏使用起来非常简单,使用一行代码就可以渲染整个表单,而且会自动帮我们渲染CSRF令牌字段form.csrf_token。下面就利用这个宏在index.html模板中渲染问候表单。
{% extends 'base.html' %}
{% from 'bootstrap/form.html' import render_form %}
{% block content %}
{{ render_form(form),action=request.full_path}}
{% endblock %}
6、使用Flask-Moment本地化日期和时间
- 本地化之前的准备
为了使世界各地的用户访问都能看到自己的本地时间,我们需要在服务器端提供纯正的,不包含时区信息的时间戳。datetime.utcnow()方法可以生成当前的UTC,我们将使用它来替代之前的datetime.now方法,作为时间戳timestamp字段的默认值。 - 使用Flask-Moment集成Moment.js
Moment.js是一个用于处理时间和日期的开源JavaScript库,它可以对时间和日期进行各种方式的处理。它会根据用户电脑中的时区设置在客户端使用JavaScript来渲染时间和日期,另外它还提供了丰富的时间渲染格式支持。
为了使用Moment.js,我们需要在基模板中加载Moment.js资源。Flask-Moment在模板中提供了moment对象,这个对象提供了两个方法来加载资源:moment.include_moment()方法用来加载Moment.js的JavaScript资源,moment.include_jquery()方法用来加载jQuery。这两个方法默认从CDN加载资源,传入local_js参数可以指定本地资源URL。
因为我们在使用Bootstrap时已经加载了jQuery,所以这里只需要加载Moment.js的JavaScript文件。
{{ moment.include_moment() }}
Flask-Moment默认以英文显示时间,我们可以通过locale()方法来设置显示的语言,将auto_detect参数设为True,会自动探测客户端语言设置并选择合适的区域设置。
- 渲染时间日期
Moment.js提供了非常丰富、灵活的时间日期格式化方式。在模板中我们可以通过对moment类调用format()方法来格式化日期和时间,moment的构造方法接收使用utcnow()方法创建的datetime对象作为参数,也就是Message对象的timestamp属性。format()方法接收特定格式字符串来渲染时间格式。
{{ moment(timestamp).format('格式字符串') }}
-
Moment.js提供的一些内置格式字符串
- 除了输出普通的时间日期,Moment.js还支持输出相对时间,只需要通过fromNow()方法实现,设置refresh参数为True可以让时间戳在不重载页面的情况下,随着时间的变化自动刷新。
{ moment(message.timestamp).fromNow(refresh=True) }
在显示相对时间的HTML元素中创建一个data-timestamp属性储存原始的时间戳,以便在JavaScript中获取。
7. 使用Faker生成虚拟数据
from faker import Faker
fake=Faker('zh_CN') #传入locale参数用来设置生成数据的语言
@app.cli.command()
@click.option('--count', default=20, help="创建数据,默认为20条")
def forge(count):
"""生成虚假的消息"""
from faker import Faker
db.drop_all()
db.create_all()
fake = Faker('zh_CN')
click.echo('working...')
for i in range(count):
message = Message(name=fake.name(), body=fake.sentence(), timestamp=fake.date_time_this_year())
db.session.add(message)
db.session.commit()
click.echo(f'创建了{count}条虚假信息')
最后使用flask forge生成一些虚拟的数据,flask run运行就可以了。