Flask 掃盲系列-數據庫

在前面的學習中,咱們已經簡單搭建了一個在線股票走勢查詢系統,而且瞭解了 Flask 中的上下文,那麼今天咱們一塊兒來學習下 Flask 中的數據庫操做。html

Flask-SQLAlchemy

說多數據庫,相信你們都是再熟悉不過了,不管是什麼程序,都須要和各類各樣的數據打交道,那麼保存這些數據的地方,就是數據庫了。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 插件

最後爲了使用 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/', 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">&times;</button>
     {{ message }}
 </div>
{% endfor %}
{{ wtf.quick_form(form) }}
{% endblock %}
複製代碼

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

固然咱們最好仍是給出一個註冊的入口,這個入口就在登錄表單的下面

<p>
    尚未用戶?
    <a href="{{ url_for('register') }}">
        點擊這裏註冊
    </a>
</p>
複製代碼

快來動手實踐下吧!

相關文章
相關標籤/搜索