上一節(jié)我們已經(jīng)創(chuàng)建了一個(gè)用戶(hù)應(yīng)用,并創(chuàng)建了用戶(hù)模型,那么我們這節(jié)就開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)單的用戶(hù)登錄注冊(cè)功能!
登錄注冊(cè)功能Flask有一個(gè)非常優(yōu)秀的擴(kuò)展Flask-login,我們可以選擇使用這個(gè)擴(kuò)展來(lái)實(shí)現(xiàn),但為了學(xué)習(xí)我們暫時(shí)不使用這個(gè)第三方擴(kuò)展,而是選擇使用session來(lái)實(shí)現(xiàn)!
實(shí)現(xiàn)用戶(hù)的登錄功能
首先,我們需要完善登錄的html頁(yè)面, 路徑為:app/auth/templates/login.html
{% extends ‘base.html’ %}{% block title %} 登錄頁(yè) {% endblock title %}{% block hero %}{% endblock hero %}{% block main %} {% block auth_form %} {% endblock auth_form %} {% endblock main %}
代碼詳解:
這個(gè)登陸模板繼承了base.html的樣式,這個(gè)base.html中的模塊及代碼其實(shí)就是我們之前實(shí)現(xiàn)的首頁(yè),只是我們把他作為一個(gè)模板基類(lèi)來(lái)繼承他!
這段代碼中其實(shí)就是寫(xiě)了一個(gè)輸入賬號(hào)密碼的表單,其他多余的代碼都是為了實(shí)現(xiàn)表單的樣式而存在的!
這里要特別說(shuō)明的是這個(gè)input表單必須設(shè)置name屬性,因?yàn)楹蠖艘鶕?jù)此name屬性來(lái)獲取用戶(hù)輸入的值!其他屬性則需要大家自行去了解學(xué)習(xí)!
登錄功能的后端邏輯視圖, 路徑為:app/auth/views/auth.py
@bp.route(‘/login’, methods=[‘GET’, ‘POST’])def login(): # 登錄視圖 if request.method == ‘POST’: username = request.form[‘username’] password = request.form[‘password’] error = None user = auth.User.query.filter_by(username=username).first() if user is None: error = ‘該用戶(hù)不存在!’ elif not check_password_hash(user.password, password): error = ‘密碼不正確.’ if error is None: session.clear() session[‘user_id’] = user.id return redirect(url_for(‘index’)) flash(error) return render_template(‘login.html’)
代碼詳解: – request.method == ‘POST’判斷當(dāng)前請(qǐng)求是否為post請(qǐng)求方式 – error = None 來(lái)初始化一個(gè)錯(cuò)誤變量,如果未通過(guò)登錄驗(yàn)證,把錯(cuò)誤信息通過(guò)消息傳送到頁(yè)面提示用戶(hù)
user = auth.User.query.filter_by(username=username).first()if user is None: error = ‘該用戶(hù)不存在!’elif not check_password_hash(user.password, password): error = ‘密碼不正確.’
這段代碼首先在數(shù)據(jù)庫(kù)通過(guò)用戶(hù)提交的用戶(hù)名去查詢(xún)?cè)撚脩?hù),用戶(hù)不存在就會(huì)返回None返回錯(cuò)誤提示,用戶(hù)存在則判斷密碼是否正確,這里用到了一個(gè)check_password_hash()的方法,這是用來(lái)將密文密碼解密后與用戶(hù)輸入密碼比對(duì)方法,與之對(duì)應(yīng)的有一個(gè)generate_password_hash()的方法用來(lái)加密明文密碼保存到數(shù)據(jù)庫(kù)!
if error is None: session.clear() session[‘user_id’] = user.id return redirect(url_for(‘index’))flash(error)
這段代碼則是如果沒(méi)有返回任何錯(cuò)誤提示,說(shuō)明該提交的表單符合我們的要求,并且數(shù)據(jù)庫(kù)也存在該用戶(hù)信息,那么我們只需要清空session,重新將session中的user_id設(shè)置為當(dāng)前登錄的id即可!
因此在實(shí)現(xiàn)登錄注冊(cè)邏輯之前就必須引入這兩個(gè)方法:
from werkzeug.security import check_password_hash, generate_password_hash
登錄功能雖然實(shí)現(xiàn)了,但我們數(shù)據(jù)庫(kù)目前還沒(méi)有任何一個(gè)用戶(hù),所以此時(shí)就應(yīng)該要去實(shí)現(xiàn)用戶(hù)的注冊(cè)功能,向數(shù)據(jù)庫(kù)新增用戶(hù),大概的邏輯是,用戶(hù)輸入用戶(hù)名及兩次密碼,先判斷該用戶(hù)是否已經(jīng)存在,存在則提示更換用戶(hù)名,不存在則向數(shù)據(jù)庫(kù)創(chuàng)建該用戶(hù)信息,并清空session,重新設(shè)置user_id的值為注冊(cè)用戶(hù)的id,以達(dá)到注冊(cè)成功后自動(dòng)登錄的目的!
實(shí)現(xiàn)用戶(hù)的注冊(cè)功能
首先,我們需要完善注冊(cè)的html頁(yè)面, 路徑為:app/auth/templates/register.html
{% extends ‘login.html’ %}{% block title %}注冊(cè){% endblock title %}{% block auth_form %}{% endblock auth_form %}
這是注冊(cè)頁(yè)面的html,大家自行理解下,這里著重說(shuō)一個(gè)我們?cè)谝晥D中通過(guò)flash()傳遞出來(lái)的消息,在模板中由以下代碼接收!
{% with messages = get_flashed_messages() %} {% if messages %}
- {% for message in messages %}
- {{ message }}
{% endfor %}
{% endif %} {% endwith %}
注冊(cè)功能的后端邏輯視圖, 路徑為:app/auth/views/auth.py
@bp.route(‘/register’, methods=[‘GET’, ‘POST’])def register(): # 注冊(cè)視圖 if request.method == ‘POST’: username = request.form[‘username’] password = request.form[‘password’] password1 = request.form[‘password1’] if password != password1: flash(‘兩次密碼輸入不一致!’) return redirect(url_for(‘auth.register’)) exists_user = auth.User.query.filter_by(username=username).first() if exists_user: flash(‘該用戶(hù)名已經(jīng)存在,請(qǐng)更換其他用戶(hù)名!’) return redirect(url_for(‘auth.register’)) else: user = auth.User(username=username, password=generate_password_hash(password)) db.session.add(user) db.session.commit() session.clear() session[‘user_id’] = user.id return redirect(url_for(‘index’)) return render_template(‘register.html’)
這個(gè)注冊(cè)的邏輯基本上涵蓋了我們之前所有章節(jié)學(xué)到的知識(shí)點(diǎn),這里就不再過(guò)多地去一一解釋代碼,大家可自行理解并完善注釋?zhuān)?/p>
實(shí)現(xiàn)用戶(hù)退出登錄功能
通過(guò)登錄和注冊(cè)功能的實(shí)現(xiàn),我們已經(jīng)清楚地知道,用戶(hù)是否登錄其實(shí)是判斷session會(huì)話(huà)中是否存在用戶(hù)的id來(lái)決定,那么推出登錄,我們只需要清除session會(huì)話(huà)中的用戶(hù)id即可,這里我們直接選擇清空session的方式實(shí)現(xiàn)推出功能!
@bp.route(‘/logout’)def logout(): # 注銷(xiāo) session.clear() return redirect(url_for(‘index’))
在模板中獲取用戶(hù)信息
@bp.before_app_requestdef load_logged_in_user(): # 每個(gè)請(qǐng)求之前都回去session中查看user_id來(lái)獲取用戶(hù) user_id = session.get(‘user_id’) if user_id is None: g.user = None else: g.user = auth.User.query.get(int(user_id))
bp.before_app_request()注冊(cè)一個(gè)在視圖函數(shù)之前運(yùn)行的函數(shù),無(wú)論請(qǐng)求什么 URL。 都會(huì)先檢查用戶(hù) ID 是否存儲(chǔ)在會(huì)話(huà)中,并從數(shù)據(jù)庫(kù)獲取該用戶(hù)的數(shù)據(jù),將其存儲(chǔ)在 g.user 上,該數(shù)據(jù)在請(qǐng)求期間持續(xù)。
注冊(cè)完這個(gè)函數(shù)之后,我們就可以在base.html中的導(dǎo)航的最右側(cè)通過(guò)g.user的返回值,判斷用戶(hù)是否已經(jīng)登錄,顯示不同的信息!
{% block navbar %} Home … 省略部分代碼 {% if g.user %} 歡迎您 {{ g.user[‘username’] }} 個(gè)人中心 退出 {% else %} Sign up Log in {% endif %} {% endblock navbar %}
實(shí)現(xiàn)login_required裝飾器
對(duì)于像下一章節(jié)我們要實(shí)現(xiàn)的用戶(hù)中心以及管理后臺(tái),則必須是帶有權(quán)限的訪(fǎng)問(wèn),最基本的權(quán)限應(yīng)該是必須是登錄用戶(hù),那么所以說(shuō)對(duì)于那些未登錄的用戶(hù)我們需要拒絕訪(fǎng)問(wèn)的功能!
這個(gè)其實(shí)思路也非常簡(jiǎn)單,既然在實(shí)現(xiàn)模板中調(diào)用用戶(hù)信息的時(shí)候,我們把當(dāng)前登錄的用戶(hù)信息添加到了g對(duì)象,那么我們只需要判斷g.user的返回值是否為None即可判斷用戶(hù)是否登陸!
def login_required(view): # 限制必須登錄才能訪(fǎng)問(wèn)的頁(yè)面裝飾器 @functools.wraps(view) def wrapped_view(**kwargs): if g.user is None: return redirect(url_for(‘auth.login’)) return view(**kwargs) return wrapped_view
到這里關(guān)于用戶(hù)登錄注冊(cè)相關(guān)的基本權(quán)限問(wèn)題我們就完成了,注意這些視圖函數(shù)都在app/auth/views/auth.py文件中!