源代碼: https://github.com/ltoddy/flask-tutorial
技術(shù)交流群:630398887(歡迎一起吹牛)
寫在前面的話:
如果你啟動了項目,要去看本篇的內(nèi)容,需要如下幾個地址:
- localhost:5000/admin
- localhost:5000/admin/login
- localhost:5000/admin/register
還有就是,本篇我們數(shù)據(jù)庫的設(shè)計改變了,所以在修改好app/models.py文件之后,要:
$ python3 manage.py shell
>>> from manage import *
>>> db
<SQLAlchemy engine=sqlite:////home/me/PycharmProjects/blog/data.sqlite>
>>> db.drop_all()
>>> db.create_all()
>>> exit()
如果你啟動項目之后,頁面給出了一個sqlalchemy相關(guān)的異常,看看你有沒有執(zhí)行上面的語句。
OK,這次要好好說明一下藍圖的好處.
一般我們網(wǎng)上的一些網(wǎng)站都是可以用戶登錄的,通過用戶名(郵箱或者手機號)和密碼。
我們也不需要那么麻煩,只需要用戶米和密碼就夠了。
我們需要更改一下 models.py 代碼:
<small>app/models.py</small>
from . import db
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
# 如果你學(xué)過數(shù)據(jù)庫的話就知道我們一般通過id來作為主鍵,來找到對應(yīng)的信息的,通過id來實現(xiàn)唯一性
username = db.Column(db.String(64), unique=True)
password = db.Column(db.String(64))
def __repr__(self):
return 'users表: id為:{}, name為:{}'.format(self.id, self.name)
其實就加上了 password 那一行.因為我們登錄的時候需要帳號和密碼這兩樣?xùn)|西.
我們還需要處理一下我們的表單,畢竟我們需要注冊和登錄,都需要填寫表單。
<small>app/admin/forms.py</small>
from flask.ext.wtf import FlaskForm
from wtforms import StringField
from wtforms import SubmitField
from wtforms import PasswordField
from wtforms.validators import Required
from wtforms.validators import EqualTo
class RegistrationForm(FlaskForm):
username = StringField('用戶名:', validators=[Required()])
password = PasswordField('密碼:', validators=[Required()])
password2 = PasswordField('確認密碼', validators=[Required(), EqualTo('password', message='兩次密碼不一致')])
# 再這里, 一般來說你注冊的時候都要求你輸入兩次相同的密碼的,而且要求是一樣的,所以我們可以使用wtforms幫我們集成好的EqualTo方法.
# 那個message就是當(dāng)你兩次密碼不一樣的時候的提示信息
submit = SubmitField('確認,提交')
class LoginForm(FlaskForm):
username = StringField('用戶名:', validators=[Required()])
password = PasswordField('密碼:', validators=[Required()])
submit = SubmitField('確認,提交')
關(guān)于這兩個表單,我曾經(jīng)思考過:因為這兩個表單中有相同的部分,我想提取出來:
class BaseForm(FlaskForm):
username = StringField('用戶名:', validators=[Required()])
password = PasswordField('密碼:', validators=[Required()])
submit = SubmitField('確認,提交')
class RegisterForm(BaseForm):
password2 = PasswordField('確認密碼', validators=[Required(), EqualTo('password', message='兩次密碼不一致')])
class LoginForm(BaseForm):
pass
就像上面代碼那樣子,然而我否決了自己,這樣子寫大幅度的降低了可讀性,而且,這個表單類,與HTML中的表單是一個一一對應(yīng)的關(guān)系,所以最好不要提取一個父類來寫.
寫完這些,我們創(chuàng)建一下響應(yīng)的藍圖:
<small>app/admin/__init__.py</small>
from flask import Blueprint
admin = Blueprint('admin', __name__)
from . import forms
from . import views
別忘了注冊一下藍圖
<small>app/__init__.py</small>
from flask import Flask
from flask.ext.bootstrap import Bootstrap
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.login import LoginManager
from config import Config
bootstrap = Bootstrap()
db = SQLAlchemy()
login_manager = LoginManager()
login_manager.session_protection = 'strong' # 這行代碼就記住吧,我也不想解釋了
login_manager.login_view = 'admin.login' # 設(shè)置登錄界面, 名為admin這個藍本的login視圖函數(shù)
def create_app():
app = Flask(__name__)
app.config.from_object(Config)
Config.init_app(app)
bootstrap.init_app(app)
db.init_app(app)
login_manager.init_app(app)
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
from .admin import admin as admin_blueprint
app.register_blueprint(admin_blueprint, url_prefix='/admin')
# 這里我們 url_prefix='/admin, 這個樣子我們訪問admin藍本中的路由的時候,就需要加上前綴:admin
return app
這里注意一下啊:
from flask.ext.login import LoginManager
login_manager = LoginManager()
login_manager.session_protection = 'strong' # 這行代碼就記住吧,我也不想解釋了
login_manager.login_view = 'admin.login' # 設(shè)置登錄界面, 名為admin這個藍本的login視圖函數(shù)
我們需要有一個管理用戶登錄的東西—— LoginManager
還是要說一下藍圖,我們要做的這個blog,這個應(yīng)用程序(app),他會有各個模塊。舉個例子,比如你去淘寶網(wǎng)頁。
你會看到:比如主頁各種商品,用戶登錄注冊,銀行卡綁定等等一大堆的模塊。如果像最開始的那個樣子,路由和視圖函數(shù)全部是由那個app實例來做的話,就得把app添加到各個地方去,很麻煩,也很混亂。
采用藍本,就相當(dāng)于每個模塊有了自己構(gòu)件路由和視圖函數(shù)的工具,然后再把藍本注冊到app中,這樣就方便多了。
就比如,北方種植小麥,南方種植水稻,北方就專心種小麥,南方就專心種水稻。
如果你都想吃,那么等他們收割了,集中在一塊就好了,差不多這個意思。
OK,我們來設(shè)計一下我們的視圖函數(shù)。
考慮這么一件事情,比如你去知乎,如果你沒有登錄,它是要求你登錄的。
也就是說:假設(shè)你沒有登錄,那么你去主頁,就會重定向到登錄界面。
這里順便說一下,我們一般會把index.html當(dāng)作主頁,這是默認的。
比如我的技術(shù)博客, http://algo.site 和訪問 http://algo.site/index.php 是一樣的。
為了能夠?qū)⒛愕卿浿蟮臓顟B(tài)記錄下來,我們需要為我們的User模型添加點東西:
from . import db
from . import login_manager
from flask.ext.login import UserMixin
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
# 如果你學(xué)過數(shù)據(jù)庫的話就知道我們一般通過id來作為主鍵,來找到對應(yīng)的信息的,通過id來實現(xiàn)唯一性
username = db.Column(db.String(64), unique=True)
password = db.Column(db.String(64))
def __repr__(self):
return 'users表: id為:{}, name為:{}'.format(self.id, self.name)
def check(self, password):
return password == self.password
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
只是多繼承了一個UserMixin類。
他幫我們提供了一些功能:比如判斷是否登錄,比如獲取你的主鍵(id),為了唯一標(biāo)識用戶,這一點很重要,因為就像一個大型網(wǎng)站,肯定會多人同時在新,如何去確認你,這里非常重要,靠的就是這個id。
還需要加上一個回調(diào)函數(shù),就是為了識別出你來的。
OK,最后是視圖函數(shù):
<small>app/admin/views.py</small>
from . import admin
from ..models import User
from .forms import LoginForm, RegistrationForm
from .. import db
from flask import render_template
from flask import redirect
from flask import url_for
from flask import flash
from flask.ext.login import current_user
from flask.ext.login import login_user
from flask.ext.login import logout_user
from flask.ext.login import login_required
@admin.route('/')
def index():
if not current_user.is_authenticated:
return redirect(url_for('admin.login'))
return render_template('admin/index.html')
@admin.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is not None:
if user.check(form.password.data):
login_user(user)
return redirect(url_for('admin.index'))
else:
flash("用戶名或者密碼錯誤")
return redirect(url_for('admin.login'))
else:
flash('沒有你這個用戶,請注冊')
return render_template('admin/login.html', form=form)
@admin.route('/logout')
@login_required
def logout():
logout_user()
flash('你已經(jīng)退出了。')
return redirect(url_for('admin.login'))
@admin.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
try:
user = User(username=form.username.data, password=form.password.data)
db.session.add(user)
flash('注冊成功')
return redirect(url_for('admin.login'))
except:
flash('帳號已存在')
return redirect(url_for('admin.register'))
return render_template('admin/register.html', form=form)
講一下這些視圖函數(shù)中的業(yè)務(wù)邏輯。
-
第一個index():
就是我們后臺管理的那個頁面,比如你把博客文章寫好之后,在這里提交,然后在blog的主頁顯示出來。
當(dāng)然目前還沒做好。
順便說一下,current_user.is_authenticated 這個東西,是一個標(biāo)志的變量,用來標(biāo)志你有沒有登錄。
如果登錄的,就是True,沒登錄就是False。
在這里,如果你沒有登錄,那么index這個頁面你是看不到的,會強行重定向到登錄頁面 -
第二個login():
這個就是登錄頁面,當(dāng)你提交表單之后,會去數(shù)據(jù)庫查詢,是否有你這個用戶。我們這里有兩層用于判斷的if。
第一層判斷的if,用來判斷數(shù)據(jù)庫里有沒有你這個用戶,如果沒有你這個用戶,會給你一個提示信息。
第二層判斷的if,用來判斷你密碼是否正確,我們在User這個類中添加了check()這個方法。
如果登錄成功會跳轉(zhuǎn)到后臺的主頁,沒成功就提示密碼錯誤。 -
第三個logout():
這個就是用來用戶退出用的。他被@login_required所保護著,什么意思呢?就是你如果沒有登錄的話,你去強行訪問:localhost:5000/admin/logout這個頁面的話,它會重定向到登錄頁面。這里我要說明一下,雖然代碼寫的是:return redirect(url_for('admin.login'))這個,但是實際上阿,如果你沒有登錄訪問了這個logout視圖,它其實是根據(jù)login_manager.login_view = 'admin.login'你的這個設(shè)置來重定向的。
最后register():
我們在數(shù)據(jù)庫設(shè)計中阿,那個username字段設(shè)置的是unique,也就是唯一,用戶名有且僅有一個,如果你創(chuàng)建的用戶已經(jīng)在數(shù)據(jù)庫中存在了,那么添加到數(shù)據(jù)庫會報一個異常,所以用try except語句來處理異常.沒什么好說的……
說點好玩的:你看阿,我們有了藍圖之后,從項目結(jié)構(gòu)上看,一個藍圖對應(yīng)一個文件夾,我們僅僅需要做的是,寫好藍圖管轄下的路由就好,而不需要管其他藍圖的路由,這個樣子,我們就把程序模塊話了,每個模塊各自有各自的功能,不會因為修改了這個模塊而去影響其他模塊,這個是非常好的一件事情,然后只需要最后的時候,把藍本注冊一下,把這些路由添加到應(yīng)用中就好。
還有,
bootstrap.init_app(app)
db.init_app(app)
login_manager.init_app(app)
這些東西,我們把app這個實例當(dāng)作參數(shù)傳了進去,有個技術(shù)叫依賴注入(也叫控制反轉(zhuǎn)),當(dāng)app傳進去之后,那么與app相關(guān)的數(shù)據(jù)也就傳了進去,在比如bootstrap中,比如{% import 'bootstrap/wtf.html' as wtf %},他是怎么憑借一個名字:'bootstrap/wtf.html'來獲得這個頁面的,如果你是用的pycharm來寫的話,軟件會給你提示信息,說找不到這個模板的。很簡單,我們把app給了bootstrap,然后bootstrap中就有了這個app,他識別了這個app,所以就可以用bootstrap了。