在前面的学习中,我们已经简单搭建了一个在线股票走势查询系统,并且了解了 Flask 中的上下文,那么今天我们一起来学习下 Flask 中的数据库操作。
Flask-SQLAlchemy
说多数据库,相信大家都是再熟悉不过了,无论是什么程序,都需要和各种各样的数据打交道,那么保存这些数据的地方,就是数据库了。Flask 支持多种数据库,同时我们未来方便安全的操作数据库,这里选择使用 Flask-SQLAlchemy 插件来管理数据库的相关操作。
实战登陆
我们直接从实战出发,来实践下它们的用法。
在上一篇我们定义了一个登陆页面,但是对于登陆我们并没有校验,当然也没有保存任何用户信息,现在我们来完善登陆注册功能。
定义表结构
首先我们定义用户表的表结构,为了方便起见,我们使用插件 flask_login 来进行用户鉴权,在 app.py 文件中添加如下代码
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin, login_user
import hashlib
db = SQLAlchemy(app)
# 用户表结构
class WebUser(UserMixin, db.Model):
__tablename__ = 'webuser'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(64), unique=True, index=True)
username = db.Column(db.String(64), unique=True, index=True)
password_hash = db.Column(db.String(128))
confirmed = db.Column(db.Boolean, default=False)
def __init__(self, **kwargs):
super(WebUser, self).__init__(**kwargs)
if self.email is not None and self.avatar_hash is None:
self.avatar_hash = hashlib.md5(
self.email.lower().encode('utf-8')).hexdigest()
@staticmethod
def insert_user():
users = {
'user1': ['user1@luobo.com', 'test1', 1],
'user2': ['user2@luobo.com', 'test2', 1],
'admin1': ['admin1@luobo.com', 'admin1', 2],
'admin2': ['admin2@luobo.com', 'admin2', 2]
}
for u in users:
user = WebUser.query.filter_by(username=u[0]).first()
if user is None:
user = WebUser(user_id=time.time(), username=u, email=users[u][0],
confirmed=True, role_id=users[u][2])
user.password = users[u][1]
db.session.add(user)
db.session.commit()
@property
def password(self):
raise AttributeError('You can not read the password')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
if self.password_hash is not None:
return check_password_hash(self.password_hash, password)
我们定义了用户表的字段,包括 user_id、emali、username 等,对于用户密码的存储,使用 security 工具进行哈希处理后存储。同时还定义了一个静态方法 insert_user 用于初始化用户。
修改视图函数
接下来我们修改 login 视图函数,进行真正的用户验证
@app.route('/login/', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = WebUser.query.filter_by(email=form.email.data).first()
if user is not None and user.verify_password(form.password.data):
login_user(user)
flash('欢迎回来!')
return redirect(request.args.get('next') or url_for('index'))
flash('用户名或密码不正确!')
return render_template('login.html', form=form)
数据库设置
下面我们还需要设置数据库连接信息
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + 'myweb.sqlite'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
SQLALCHEMY_DATABASE_URI 是数据库的连接地址,我们直接使用轻巧的 sqlite 文件数据库,SQLALCHEMY_COMMIT_ON_TEARDOWN 设置为 True,表示每次请求结束后,都会自动提交数据库的变动。
下面我们在终端进入到 flask shell 中
C:\Work\code\Flask\flask_stock>flask shell
然后使用 Flask-SQLAlchemy 提供的函数 create_all() 创建数据库表
>>> from app import db
>>> db.create_all()
如果不出意外,此时当前目录下应该会生成一个 myweb.sqlite 文件。
之后我们在通过 WebUser 类的静态方法来插入初始用户
>>> from app import WebUser
>>> WebUser.insert_user()
此时如果我们通过数据库连接工具查看 webuser 表的话,会发现数据已经成功插入了。
配置 flask_login 插件
最后为了使用 flask_login 插件,我们还需要通过 LoginManager 对象来初始化 app 实例。LoginManager 对象的 session_protection 属性可以设为 None、‘basic’ 或 ‘strong’,以提供不同的安全等级,防止用户会话遭篡改。
from flask_login import LoginManager
login_manager = LoginManager(app)
login_manager.session_protection = 'strong'
最后,Flask-Login 要求程序实现一个回调函数,使用指定的标识符加载用户。
@login_manager.user_loader
def load_user(user_id):
return WebUser.query.get(int(user_id))
现在我们就可以尝试使用已有的用户和密码去登陆系统了,如果不出意外的话,使用正确的用户名和密码才能成功登陆。
现在再把 flash 消息渲染到 HTML 页面上
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
验证用户
下面我们再来看下如何验证用户是否登陆。
还记得我们的 WebUser 类其实是继承自 flask_login 的 UserMixin 类的,该类已经实现了如下的用户方法
属性/方法 | 说明 |
---|---|
is_authenticated | 如果用户已经认证,返回 True,否则返回 False |
is_active | 如果用户允许登陆,返回 True,否则返回 False |
is_anonymous | 如果当前用户未登录,返回 True,否则返回 False |
get_id() | 返回用户的唯一标识符,使用 Unicode 编码字符串 |
再结合 flask_login 提供的 current_user 对象,就可以判断用户的认证状态了。current_user 是一个和 current_app 类似的代理对象(Proxy), 表示当前用户。
修改 get_kline_chart 的 30 天逻辑
from flask_login import current_user
def get_kline_chart():
...
if int(query_time) > 30:
if current_user.is_authenticated:
pass
else:
abort(403)
...
修改用户认证判断逻辑
因为在上一篇里我们在模板中是通过 {% if not auth %} 来判断用户登陆与否的,现在需要修改下
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('logout') }}">Log Out</a></li>
{% else %}
<li><a href="{{ url_for('login') }}">Log In</a></li>
{% endif %}
</ul>
而对于 logout 视图函数,也做如下修改
from flask_login import logout_user, login_required
@app.route('/logout/')
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
直接调用 logout_user 函数就可以登出用户,同时还需要注意,这里使用了 login_required 装饰器,顾名思义,只有认证了的用户才可以调用该装饰器装饰的视图函数,这样就保证了未登陆的用户无权限访问 /logout 地址。
实战注册
注册我们就不做的过于复杂了,只要用户输入正确的 email 地址且唯一并且两次 password 一致,我们就通过注册。
定义注册表单
创建一个注册表单类
class RegisterForm(FlaskForm):
email = StringField('email', validators=[DataRequired()])
password = PasswordField('password', validators=[DataRequired(),
EqualTo('confirm_pw', message='两次输入的密码需要一致!')])
confirm_pw = PasswordField('confirm_pw', validators=[DataRequired()])
submit = SubmitField('Submit')
def validate_email(self, field):
if WebUser.query.filter_by(email=field.data).first():
raise ValidationError('该邮箱已经存在!')
以 validate_ 开头且后面跟着字段名的方法,是固定写法,用于自定义字段的验证方法。
然后我们再创建一个注册视图函数
@app.route('/register/', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
email = form.email.data
password = form.password.data
user = WebUser.query.filter_by(email=email).first()
if user is None:
newuser = WebUser(email=email, username=email, password=password, user_id=time.time())
db.session.add(newuser)
flash("你可以登陆啦!")
return redirect(url_for('login'))
flash("邮箱已经存在!")
return render_template('register.html', form=form)
在该视图函数中,我们接收表单传递过来的数据,并验证 email 是否存在,如果不存在则插入数据库。并且跳转至登陆页面。
最后我们再编写注册页面,创建 register.html 文件
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}注册{% endblock %}
{% block page_content %}
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{{ wtf.quick_form(form) }}
{% endblock %}
这样,一个注册功能就完成了。
当然我们最好还是给出一个注册的入口,这个入口就在登陆表单的下面
<p>
还没有用户?
<a href="{{ url_for('register') }}">
点击这里注册
</a>
</p>
快来动手实践下吧!