Develop Blog With Flask
1 概述
之前参考《Flask Web开发-–—基于Python的Wen应用开发实战》做了一个博客, 根据所学所了解的做个整理备忘。具体还请参考原书。
Flask按照MVC的架构,和Django在很多地方相似。不同之处在于设计者将flask设计得很小很灵活,如果需要事先一些本身没有的功能,就借助第三方扩展,或者自己写喽。
这是我第一个用Flask做的应用,总结不足之处欢迎指出。
环境 : flask 0.10.1, python 2.7.6, ubuntu 14.04, sqlite 存储数据
2 扩展
- flask-script Flask
的开发web服务器支持很多启动设置选项,但只能在脚本中作为参数传给
app.run()
,比较不方便,所以使用此扩展来为Flask添加一个命令行解释器 - flask-bootstrap Flask中简单集成Bootstrap模板
- flask-moment 将Javascript客户端开源代码库moment.js继承到Jinja2模板中, 使得在Flask中方便地处理不同地区的日期和时间
- flask-wtf 方便Flask处理表单
- flask-sqlalchemy SQLAlchemy是数据库抽象层代码包,可以用来直接处理高级的Python对象,而不用处理如表、文档或查询语言的数据库实体 Flask-sqlalchemy 简化了在Flask中使用SQLAlchemy
- flask-migrate flask-migrate是对 数据库迁移框架/Alembic/的轻量包装,用来方便处理数据库修改、更新、迁移等操作
- flask-mail 处理发送邮件相关,flask-mail 连接到SMTP服务器发送邮件
- flask-login 专门用来管理用户认证系统中的认证状态
- forgerpy 可以用来生成虚拟的数据,供测试用
- flask-pagedown PageDown : Javascript实现的客户端 Markdown 到 HTML 转换程序 flask-pagedown : 用Flask 包装的PageDown, 将PageDown集成到Flask-WTF中 Bleach: 使用Python实现HTML清理器
3 Model
3.1 基本写法
model 用以下方式写:
class User(db.Model): __tablename__ = 'users' ##指定数据库名称 id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True) email = db.Column(db.String(64),unique=True) password_hash = db.Column(db.String(128))
db
是引用了SQLAlchemy初始化后的db
3.2 一对多关系 (One to Many)
class Role(db.Model): #... users = db.relationship('User', backref='role', lazy='dynamic') class User(db.Model): #... role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
- 一个Role对应多个User
backref='role'
向User模型添加role属性,可代替role_id
访问Role模型lazy
指定如何加载相关记录,lazy='dynamic'
指不加载记录,但提供加载记录的query,可参考具体文档说明
3.3 多对多关系 (Many to Many self)
比如用户之间互相关注,有多种方式可以处理这种 many2many 关系,综合考虑利弊,比较推荐的是下面这样:
class Follow(db.Model): __tablename__ = 'follows' id = db.Column(db.Integer, primary_key=True) following_id = db.Column(db.Integer, db.ForeignKey('users.id')) follower_id = db.Column(db.Integer, db.ForeignKey('users.id')) timestamp = db.Column(db.DateTime(), index=True, default=datetime.utcnow) class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True) email = db.Column(db.String(64),unique=True) password_hash = db.Column(db.String(128)) followings = db.relationship('Follow', foreign_keys=[Follow.following_id], backref=db.backref('follower', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') followers = db.relationship('Follow', foreign_keys=[Follow.follower_id], backref=db.backref('following', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan')
- 创建一个中间Model : Follow
- foreignkeys 指定外键
- db.backref()回引 Follow 模型
4 View
4.1 示例
@app.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() if user is not None and user.verify_password(form.password.data): login_user(user, form.remember_me.data) return redirect(request.args.get('next') or url_for('index')) flash('Invalid username or password.') return render_template('login.html', form=form)
- methods指定请求方式
if form.validate_on_submit():
用来判断是否时Post的数据并且是否通过了验证return render_template('login.html', form=form)
渲染视图,指定模板并且传入渲染的数据
5 Form
5.1 示例
class LoginForm(Form): email = StringField('Email', validators=[Required(), Length(1,64), Email()]) password = PasswordField('Password', validators=[Required()]) remember_me = BooleanField('Keep me logged in') submit = SubmitField('Login')
- 使用flask-wtf 提供的函数和元素
6 Skills
6.1 检查Flask版本:
#work on flask version 0.7 or later >> import flask >> flask.__version__ or #If flask was installed via pip or easy_instal: >> pip freeze | grep Flask
Ps : 建议使用virtualenv和virtualenvwapper来安装和配置环境
6.2 Post/重定向/Get 模式
因为Post请求的页面刷新会再向服务器发送数据,所以可以才有Post完后重定向再Get页面即可避免这一问题。页面数据可以存储在Session中
6.3 Flash消息
系统提示之类的信息可以用 flash()
在后端生成。模板中使用函数 get_flashed_messages()
以类似于 {% for message in get_flashed_messages() %}
的方式来渲染消息
6.4 发送邮件
环境变量 处理比如发送邮件时,配置的邮件服务器用户名、密码等要在环境变量中定义,这样好处是安全以及方便维护,Linux中可采用如下方式:
$ export MAIL_USERNAME=xxxxx@gmail.com
- 异步发送电子邮件 可以采用多线程把发送电子邮件的函数移到后台线程中,避免程序停滞 另外,当程序要发送大量电子邮件时,使用比如Celery任务队列或许更加合适
6.5 需求文件
可以使用pip 来生成和安装所需依赖
$ pip freeze >requirements.txt #生成目前环境中的依赖包到requirements.txt文件 $ pip install -r requirements.txt
6.6 Werkzeug 实现密码散列
使用Werkzeug中的/security/模块实现密码散列值计算
generate_password_hash
将原始密码转化为字符串形式的散列值
check_password_hash
比较散列值和用户输入密码
6.7 itsdangerous 生成确认令牌
可以使用itsdangerous 的 TimedJSONWebSignatureSerializer
类生成具有过期时间的Json Web签名。使用 dumps()
和 locads()
方法,配合flask中的 secret key,来生成用在确认用户邮件中加密token
6.8 beforerequest or beforeapprequest
使用 beforerequest or beforeapprequest 可在必要时拦截请求, 直接返回至客户端,而不会调用请求的函数, 可以用来处理比如账号是否验证过的情况
6.9 Gravatar头像
使用 ~hashlib.md5(self.email.encode('utf-8')).hexdigest() 计算邮件的MD5散列值 在用户中创建方法生成Gravatar Url,在模板中加载 另外,生成MD5值是一项CPU密集型操作,所有可以考虑将MD5值缓存在用户模型中,当用户更改电子邮件时再重新计算
6.10 使用Markdown支持富文本文章
在服务器端保存用户输入的markdown,并且将markdown转化成HTML,并且将生成的HTML清除掉不允许的标签,缓存在一个字段中,这样做的好处是防止每次客户端渲染时都要转换一次
渲染HTML时,调用 |safe
,让Jinja2不要转义HTML元素
7 To Imporve
- 使用Blueprint
- REST Web服务
- 单元测试
- 生产环境部署