cms后台登錄界面
后台登錄頁面,我們不用自己寫,只需要去Bootstrap中文網去找一個模板改一下就行
這里使用的模板是:https://v3.bootcss.com/examples/signin/
點擊右鍵查看網頁源碼,把源碼復制下載
在項目templates目錄下新建目錄cms
在cms目錄下新建文件cms_login.html,並把源碼復制到該文件中
cms_login.html會用到樣式文件signin.css, 點擊它查看源碼
在項目static目錄下新建目錄 cms/css
在static/cms/css新建文件signin.css, 並把源碼拷貝進去

body { padding-top: 40px; padding-bottom: 40px; background-color: #eee; } .form-signin { max-width: 330px; padding: 15px; margin: 0 auto; } .form-signin .form-signin-heading, .form-signin .checkbox { margin-bottom: 10px; } .form-signin .checkbox { font-weight: normal; } .form-signin .form-control { position: relative; height: auto; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 10px; font-size: 16px; } .form-signin .form-control:focus { z-index: 2; } .form-signin input[type="email"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .form-signin input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; }
修改cms_login.html, 修改后如下

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! --> <meta name="description" content=""> <meta name="author" content=""> <title>登錄-論壇CMS管理系統</title> <!-- Bootstrap core CSS --> <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this template --> <link href="{{ url_for('static', filename='cms/css/signin.css') }} " rel="stylesheet"> </head> <body> <div class="container"> <form class="form-signin" method="post"> <h2 class="form-signin-heading">請登錄</h2> <label for="inputEmail" class="sr-only">郵箱</label> <input type="email" id="inputEmail" class="form-control" placeholder="郵箱" required autofocus name="email"> <label for="inputPassword" class="sr-only">密碼</label> <input type="password" id="inputPassword" class="form-control" placeholder="密碼" required name="password"> <div class="checkbox"> <label> <input type="checkbox" value="1" name="remember"> 記住我 </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">登錄</button> </form> </div> <!-- /container --> </body> </html>
編寫cms登錄視圖,編輯cms.views.py
... from flask views, render_template class LoginView(views.MethodView): def get(self): return render_template('cms/cms_login.html') def post(self): pass bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
啟動訪問http://127.0.0.1:5000/cms/login/
cms后台登錄功能
后台登錄,前端就需要提交數據給我們,那么首先,我們就需要form驗證
安裝flask-wtf
pip install flask-wtf
編輯cms.forms.py

from wtforms import Form, StringField, IntegerField from wtforms.validators import Email, InputRequired, Length class LoginForm(Form): email = StringField(validators=[Email(message='郵箱格式錯誤'), InputRequired(message='請輸入郵箱')]) password = StringField(validators=[Length(6,20, message='密碼長度為6-20位')]) remember = IntegerField() #這個值不用驗證,這里只是接收
然后就可以寫視圖了,編輯cms.views.py

from flask import Blueprint, views, render_template, request, session from flask import redirect, url_for from .forms import LoginForm from .models import CMSUser bp = Blueprint('cms', __name__, url_prefix='/cms') @bp.route('/') def index(): return 'cms index' class LoginView(views.MethodView): def get(self, message=None): return render_template('cms/cms_login.html', message=message) def post(self): login_form = LoginForm(request.form) if login_form.validate(): email = login_form.email.data password = login_form.password.data remember = login_form.remember.data user = CMSUser.query.filter_by(email=email).first() if user and user.check_password(password): session['user_id'] = user.id if remember: #如果勾選了記住我,則保存session,這樣就算瀏覽器關閉session還是存在的 session.permanent = True return redirect(url_for('cms.index')) #因為是藍圖這里必須使用cms.index,不能使用index else: #return render_template('cms/cms_login.html', message='賬號或密碼錯誤') #等同於以下代碼 return self.get(message='賬號或密碼錯誤') else: #login_form.errors是一個字典,如{"email":['郵箱格式錯誤'], "password":["密碼長度為6-20位"]} #login_form.errors.popitem() 是取出字典的任意一項,結果是元組,如:("password":["密碼長度為6-20位"]) #取出該元組的第2個元素:login_form.errors.popitem()[1], 如:["密碼長度為6-20位"] #最后取出錯誤提示語:login_form.errors.popitem()[1][0] message = login_form.errors.popitem()[1][0] return self.get(message=message) bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
因為用到了session,所以還需要在配置文件,配置一個key為session加密用

... import os from datatime import timedelta #session SECRET_KEY = os.urandom(24) #設置session有效期為2天,若開啟了session.permanent后不設置該參數,則默認為31天 PERMANENT_SESSION_LIFETIME = timedelta(days=7)
前端代碼需要加上錯誤提示,首先要判斷message是否存在
訪問輸入正確的郵箱,就可以登錄成功,跳轉的http://127.0.0.1/cms/並且保存用戶session到cookie了
CMS后台登錄限制
限制存在個問題,就是我們沒有登錄的情況下,也能夠訪問cm后台的首頁http://127.0.0.1/cms/.接下來我們就要給它加上限制,只有登錄后才能訪問。
要實現這個功能,可以使用裝飾器。
首先在cms下面新建個文件decorators.py, 以后此藍圖下的所有裝飾器都放到這個文件里面

from flask import session, redirect, url_for from functools import wraps def login_required(func): @wraps(func) # 保留func的屬性 def inner(*args, **kwargs): if 'user_id' in session: return func(*args, **kwargs) else: #session中沒有user_id表示沒有登錄,則跳轉到登錄頁面 return redirect(url_for('cms.login')) return inner
然后在cms首頁視圖加上login_required裝飾器

... from .decorators import login_required @bp.route('/') #路由裝飾器一定要放在最上面,否則會找不到路由 @login_required def index(): return 'cms index'
這樣,當我們在沒有登錄的情況下訪問http://127.0.0.1/cms/會自動跳轉到登錄頁面了,O(∩_∩)O哈哈~
代碼優化
優化一
可以發現,我們把用戶的session信息(user.id)保存在session['user_id']中
在登錄裝飾器中也用到
這樣做有幾個缺點:
1、當我們fornt也用到session的時候也這樣用user_id 就不好區分
2、因為多處都用這個這個值,很容易寫錯
所以,我們可以把它寫成一個常量來用,編輯config.py
... CMS_USER_ID = 'HEBOANHEHE'
然后在用到user_id的地方都import config ,然后使用CMS_USER_ID
優化二
當表單驗證不通過以后,我們是如下實現的,隨機抽取一個錯誤信息出來(實際項目中可能要列出所有的錯誤信息,要根據需求來定)
message = login_form.errors.popitem()[1][0]
我們可以把獲取錯誤信息的代碼寫到 form中,這樣就避免每次都寫這么長的代碼,也避免容易犯錯
class LoginFrom(Form): email = StringField(validators=[Email(message='郵箱格式錯誤'),InputRequired(message='請輸入郵箱') ]) password = StringField(validators=[Length(6,30, message='密碼長度6-30')]) remember = IntegerField() #添加獲取錯誤信息的方法 def get_error(self): message = self.errors.popitem()[1][0] return message
然后我們視圖中調用這個方法就可以了
message = login_form.get_error()
因為項目中以后還會有其他的form驗證,那么每個form中都要加這個方法,這樣也比較麻煩,所以,我們可以進一步優化。我們可以使用繼承來解決
在apps/下面新建一個forms.py
from wtforms import Form class BaseForm(Form): def get_error(self): message = self.errors.popitem()[1][0] return message
以后我們寫的form繼承BaseForm就擁有get_error方法了
class LoginFrom(BaseForm): email = StringField(validators=[Email(message='郵箱格式錯誤'),InputRequired(message='請輸入郵箱') ]) password = StringField(validators=[Length(6,30, message='密碼長度6-30')]) remember = IntegerField()
CSRF保護
編輯主程序文件bbs.py,開啟csrf
...
from flask_wtf import CSRFProtect
CSRFProtect(app)
加上csrf保護后,我們再嘗試登陸就會失敗
因為還需在提交表單的 form中添加一個input攜帶csrf_token給服務器驗證
<form class="form-signin" method="post"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" /> ... </form>