Flask从入门到做出一个博客的大型教程(四)
Flask从入门到做出一个博客的大型教程(四)
在开始之前,先来看下项目的整体结构。
flask
├── app
│ ├── forms.py
│ ├── __init__.py
│ ├── models.py
│ ├── routes.py
│ └── templates
│ ├── base.html
│ ├── index.html
│ └── login.html
├── config.py
├── migrations
│ ├── alembic.ini
│ ├── env.py
│ ├── README
│ ├── script.py.mako
│ └── versions
│ ├── 3884184ade03_tables.py
├── myblog.py
5 用户登录模块
在上一部分讲了数据库,也建立了表,但是用户表里的密码字段一直没用,所以这里讲一讲密码字段。如果密码不加密就明文存储在数据库中,这是一个非常不安全而且愚蠢的行为,所以咱们要一起先看看密码的加密存储。
(venv) duke@coding:~/flask_tutorial/flask$ flask shell
Python 3.6.4 (default, May 3 2018, 19:35:55)
[GCC 5.4.0 20160609] on linux
App: app [production]
Instance: /home/duke/flask_tutorial/flask/instance
>>> from werkzeug.security import generate_password_hash
>>> hash = generate_password_hash('mima')
>>> hash
'pbkdf2:sha256:50000$S9FPhxbX$4f164ff06b409769e44556bcd9d8f906ca82998e433410f85898245e40ecd4d3'
这样就对密码进行了加密,存储在数据库中。但是用户登录输入的密码要怎么和存储在数据库中的这一大串密码进行比对呢?这是就要用到另外一个函数了。
>>> from werkzeug.security import check_password_hash
>>> check_password_hash(hash,'haha')
False
>>> check_password_hash(hash,'mima')
True
这样就可以根据返回的布尔值来判断用户输入的密码是否正确了。既然掌握了密码的加密和核对,那么就要model进一步完善了。
app/models.py : 完善user模型
# ...
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model):
# ...
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
user模型修改好了,现在到flask shell中去试一试是否成功能用?
(venv) duke@coding:~/flask_tutorial/flask$ flask shell
Python 3.6.4 (default, May 3 2018, 19:35:55)
[GCC 5.4.0 20160609] on linux
App: app [production]
Instance: /home/duke/flask_tutorial/flask/instance
>>> from app.models import User
>>> u = User(username='duke',email='aaa@qq.com')
>>> u.set_password('mytest')
>>> u.check_password('yourtest')
False
>>> u.check_password('mytest')
True
flask中有很多写的非常不错的插件,像flask-migrate就很不错,这里介绍一个flask-login,当然你肯定可以自己从写一个,但是有别人造好的*为什么先不体验一下试试呢?
它会:
- 在会话中存储当前活跃的用户 ID,让你能够*地登入和登出。
- 让你限制登入(或者登出)用户可以访问的视图。
- 处理让人棘手的 “记住我” 功能。
- 帮助你保护用户会话免遭 cookie 被盗的牵连。
- 可以与以后可能使用的 Flask-Principal 或其它认证扩展集成。
但是,它不会:
限制你使用特定的数据库或其它存储方法。如何加载用户完全由你决定。
限制你使用用户名和密码,OpenIDs,或者其它的认证方法。
处理超越 “登入或者登出” 之外的权限。
处理用户注册或者账号恢复。
(venv) duke@coding:~/flask_tutorial/flask$ pip install flask-login
app/_ _ init_ _.py : 登录模块初始化
# ...
from flask_login import LoginManager
app = Flask(__name__)
# ...
login = LoginManager(app)
# ...
你用来表示用户的类需要实现这些属性和方法:
-
is_authenticated
只有通过验证的用户会满足 login_required的条件。
-
is_active
如果这是一个活动用户且通过验证,账户也已**,未被停用,也不符合任何你 的应用拒绝一个账号的条件,返回
True
。不活动的账号可能不会登入(当然, 是在没被强制的情况下)。 -
is_anonymous
如果是一个匿名用户,返回
True
。(真实用户应返回 False 。) -
get_id()
返回一个能唯一识别用户的,并能用于从
user_loader
回调中加载用户的unicode
。注意 必须 是一个unicode
—— 如果 ID 原本是 一个int
或其它类型,你需要把它转换为unicode
。
要简便地实现用户类,你可以从 UserMixin
继承,它提供了对所有这些方法的默认 实现。所以上面哔哔这么多,都可以无视它们,哈哈。
app/models.py : 为了使用flask-login,继承UserMixin
# ...
from flask_login import UserMixin
class User(UserMixin, db.Model):
# ...
因为flask-login对数据库是真的一无所知啊,那怎么获得用户信息呢?所以这里要写一个加载用户信息的函数,因为是从session中读取,所以是id不是int类型,咱们要把它转成int类型哦。
app/models.py : flask-login用户加载函数
# ...
from app import login
# ...
@login.user_loader
def load_user(id):
return User.query.get(int(id))
还记得视图模块中对登录处理过的方法吗?现在已经学习了这么多,把以前写的完善修改一下吧。
app/routes.py : 视图模块登录方法
# ...
from flask_login import current_user, login_user
from app.models import User
# ...
@app.route('/login',methods=['GET','POST'])
def login():
#判断当前用户是否验证,如果通过的话返回首页
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
#对表格数据进行验证
if form.validate_on_submit():
#根据表格里的数据进行查询,如果查询到数据返回User对象,否则返回None
user = User.query.filter_by(username=form.username.data).first()
#判断用户不存在或者密码不正确
if user is None or not user.check_password(form.password.data):
#如果用户不存在或者密码不正确就会闪现这条信息
flash('无效的用户名或密码')
#然后重定向到登录页面
return redirect(url_for('login'))
#这是一个非常方便的方法,当用户名和密码都正确时来解决记住用户是否记住登录状态的问题
login_user(user,remember=form.remember_me.data)
return redirect(url_for('index'))
return render_template('login.html',title='登录',form=form)
登录问题解决了,那么就要解决登出问题了。这个非常简单,直接调用一个方法即可。
app/routes.py : 解决登出问题
# ...
from flask_login import logout_user
# ...
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
好的,现在又新增了一个登出的方法,那么模板也要相应的增加一个登出的地方喽。
app/templates/base.html : 添加登出
<div>博客 :
<a href="{{ url_for('index') }}">首页</a>
<a href="{{ url_for('login') }}">登录</a>
<a href="{{ url_for('logout') }}">退出</a>
</div>
在你浏览过的大部分网站中,有一部分功能是要你登录后才能使用的,那么在flask中怎么实现这个登录才能使用的功能呢?
app/_ _ init _ _.py : 增加登录限制
# ...
# 在login = LoginManager(app)后面加上即可
login.login_view = 'login'
app/routes.py : 继续在视图模块中修改,导入装饰器
from flask_login import login_required
@app.route('/')
@app.route('/index')
#这样,必须登录后才能访问首页了,会自动跳转至登录页
@login_required
def index():
# ...
现在如果你访问首页http:// localhost:5000/ ,你就会发现会自动跳转至登录页,而且地址栏变成http:// localhost:5000/login?next=%2F,但是我想登录后想重定向原本要去的网址,怎么解决?肯定和next这个参数有关系,所以我们要对视图模块的登录函数进行修改。
# ...
from flask import request
from werkzeug.urls import url_parse
#...
@app.route('/login',methods=['GET','POST'])
def login():
# ...
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('无效的用户名或密码')
return redirect(url_for('login'))
login_user(user,remember=form.remember_me.data)
#此时的next_page记录的是跳转至登录页面是的地址
next_page = request.args.get('next')
#如果next_page记录的地址不存在那么就返回首页
if not next_page or url_parse(next_page).netloc != '':
next_page = url_for('index')
#综上,登录后要么重定向至跳转前的页面,要么跳转至首页
return redirect(next_page)
#...
后端管理的代码都写的差不多了,那就改一改前端的页面吧。
app/templates/index.html : 显示当前用户的用户名和他的内容
{% extends 'base.html' %}
{% block content %}
<h1>你好呀, {{ current_user.username }} !</h1>
{% for post in posts %}
<div><p>{{ post.author.username }} 说:<b>{{ post.body }}</b></p></div>
{% endfor %}
{% endblock %}
额,下面咱们就要测试一下了,但是测试之前把首页的登录限制和指定的user去掉去掉
@app.route('/')
@app.route('/index')
def index():
# ...
return render_template('index.html',title='我的',user=user,posts=posts)
先写入一条测试用例:
(venv) duke@coding:~/flask_tutorial/flask$ flask shell
Python 3.6.4 (default, May 3 2018, 19:35:55)
[GCC 5.4.0 20160609] on linux
App: app [production]
Instance: /home/duke/flask_tutorial/flask/instance
>>> from app import db
>>> from app.models import User,Post
>>> u = User(username='duke',email='aaa@qq.com')
>>> u.set_password('123456')
>>> db.session.add(u)
>>> db.session.commit()
好的,访问试一试吧!
登录后的界面!
退出后的界面!
登录写好了,也自己能往里面写数据了,但是不方便啊,一点点敲命令进去,反人类啊,不行不行。所以来写一个注册的方法吧!
首先要在表单模块修改喽。
app/forms.py : 添加注册表格
# ...
from wtforms.validators import ValidationError,Email,EqualTo
from app.models import User
# ...
class RegistrationForm(FlaskForm):
username = StringField('用户名', validators=[DataRequired()])
email = StringField('邮箱', validators=[DataRequired(), Email()])
password = PasswordField('密码', validators=[DataRequired()])
password2 = PasswordField(
'重复密码', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('注册')
#校验用户名是否重复
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user is not None:
raise ValidationError('用户名重复了,请您重新换一个呗!')
#校验邮箱是否重复
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is not None:
raise ValidationError('邮箱重复了,请您重新换一个呗!')
注册表格处理都写好了,那没页面怎么能行?
app/templates/register.html : 写注册界面
{% extends "base.html" %}
{% block content %}
<h1>注册</h1>
<form action="" method="post">
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.email.label }}<br>
{{ form.email(size=64) }}<br>
{% for error in form.email.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}<br>
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password2.label }}<br>
{{ form.password2(size=32) }}<br>
{% for error in form.password2.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
注册页面写好了,那登录界面得给它一个链接才行,所以到登录面添加个超链接吧。
app/templates/login.html : 添加一个注册的链接。
<h1>登 录</h1>
<p>还没注册? <a href="{{ url_for('register') }}">点击一下就可以注册哦!</a></p>
现在什么都准备好了,就差视图模块进行一个总结啦,进入视图模块,添加方法吧!
# ...
from app import db
from app.forms import RegistrationForm
# ...
@app.route('/register', methods=['GET', 'POST'])
def register():
# 判断当前用户是否验证,如果通过的话返回首页
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('恭喜你成为我们网站的新用户!')
return redirect(url_for('login'))
return render_template('register.html', title='注册', form=form)
注册页面哦 !