1. 程式人生 > 程式設計 >Flask 掃盲系列-資料庫

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">&times;</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">&times;</button>
     {{ message }}
 </div>
{% endfor %}
{{ wtf.quick_form(form) }}
{% endblock %}
複製程式碼

這樣,一個註冊功能就完成了。

當然我們最好還是給出一個註冊的入口,這個入口就在登陸表單的下面

<p>
    還沒有使用者?
    <a href="{{ url_for('register') }}">
        點選這裡註冊
    </a>
</p>
複製程式碼

快來動手實踐下吧!