后台登陆页面,咱们不用本身写,只须要去Bootstrap中文网去找一个模板改一下就行css
这里使用的模板是:https://v3.bootcss.com/examples/signin/html
点击右键查看网页源码,把源码复制下载前端
在项目templates目录下新建目录cmsweb
在cms目录下新建文件cms_login.html,并把源码复制到该文件中flask
cms_login.html会用到样式文件signin.css, 点击它查看源码bootstrap
在项目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, 修改后以下cookie
<!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.pysession
... 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/
后台登陆,前端就须要提交数据给咱们,那么首先,咱们就须要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了
限制存在个问题,就是咱们没有登陆的状况下,也可以访问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']中
在登陆装饰器中也用到
这样作有几个缺点:
一、当咱们fornt也用到session的时候也这样用user_id 就很差区分
二、由于多处都用这个这个值,很容易写错
因此,咱们能够把它写成一个常量来用,编辑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()
编辑主程序文件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>