在前面的學習中,咱們已經簡單搭建了一個在線股票走勢查詢系統,而且瞭解了 Flask 中的上下文,那麼今天咱們一塊兒來學習下 Flask 中的數據庫操做。html
說多數據庫,相信你們都是再熟悉不過了,不管是什麼程序,都須要和各類各樣的數據打交道,那麼保存這些數據的地方,就是數據庫了。Flask 支持多種數據庫,同時咱們將來方便安全的操做數據庫,這裏選擇使用 Flask-SQLAlchemy 插件來管理數據庫的相關操做。web
咱們直接從實戰出發,來實踐下它們的用法。sql
在上一篇咱們定義了一個登錄頁面,可是對於登錄咱們並無校驗,固然也沒有保存任何用戶信息,如今咱們來完善登錄註冊功能。shell
首先咱們定義用戶表的表結構,爲了方便起見,咱們使用插件 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 用於初始化用戶。flask
接下來咱們修改 login 視圖函數,進行真正的用戶驗證bootstrap
@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,表示每次請求結束後,都會自動提交數據庫的變更。bash
下面咱們在終端進入到 flask shell 中session
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 插件,咱們還須要經過 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>
複製代碼
快來動手實踐下吧!