Published: 2015-09-27

Develop Blog With Flask

Table of Contents

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服务
  • 单元测试
  • 生产环境部署

Author: Nisen

Email: imnisen@163.com