Flask 掃盲系列-資料庫
在前面的學習中,我們已經簡單搭建了一個線上股票走勢查詢系統,並且瞭解了 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),index=True)
username = db.Column(db.String(64),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': ['[email protected]','test1',1],'user2': ['[email protected]','test2','admin1': ['[email protected]','admin1',2],'admin2': ['[email protected]','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/','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>
複製程式碼
快來動手實踐下吧!