Skip to main content

创建用户

安装flask_login:

pip install flask_login

app/init.py 导入, 代码如下:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager


app = Flask(__name__)
app.config['SECRET_KEY'] = 'daxiongketang'
app.config['SQLALCHEMY_DATABASE_URI'] = (
'mysql+pymysql://root:andy123456@localhost/daxiong?charset=utf8mb4'
)

db = SQLAlchemy(app)
migrate = Migrate(app, db)
login_manager = LoginManager(app)

# 导入路由
from app import routes

models.py创建User类

from app import db,login_manager
from datetime import datetime
from flask_login import UserMixin

@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))

class User(db.Model,UserMixin):
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
username = db.Column(db.String(100),nullable=False)
email = db.Column(db.String(100),nullable=False,unique=True)
password = db.Column(db.String(200),nullable=False)
# 多对多关系
favorites = db.relationship('Course',secondary='collections',
backref=db.backref('user',lazy='dynamic'),
lazy='dynamic')

执行迁移命令:

flask db migrate
flask db upgrade

检查数据库中,会新增一个user表。

使用shell命令创建用户:

flask shell

进入shell命令。

输入如下命令:

>>> from app import db
>>> from app.models import User
>>> user = User()
>>> from werkzeug.security import check_password_hash, generate_password_hash
>>> password = generate_password_hash('123456')
>>> user.username = 'test'
>>> user.password = password
>>> user.email = 'test@qq.com'
>>> db.session.add(user)
>>> db.session.commit()
>>> check_password_hash(password, '123456')

如果要退出,输入exit().

登录页面表单验证

创建login和logout路由

from flask import render_template,request,redirect,url_for,flash
from sqlalchemy import desc,asc,and_,or_
from flask_login import current_user,logout_user,login_user,login_required
from werkzeug.security import check_password_hash,generate_password_hash

from app import app,db
from app.models import Course,User
import json
import datetime
from app.forms import LoginForm,ChangePasswordForm,RegisterForm



@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():
pass
return render_template('login.html',form=form)

@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))

在app下创建forms.py文件,代码如下:

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Length, Email, EqualTo,ValidationError
import email_validator
from app.models import User


class LoginForm(FlaskForm):
email = StringField('邮箱',
validators=[
DataRequired(message="邮箱不能为空"),
Email(message="无效的邮箱地址")])
password = PasswordField('密码',
validators=[
DataRequired(message="密码不能为空"),
Length(min=6, max=25, message='密码长度为6-25个字符'),
])

login.html代码如下:

{% extends 'base.html' %}
{% block content %}

<style>
.login-container {
width: 500px;
margin: 100px auto;
padding: 20px 10px;
background-color: #eef1f4;
border-radius: .5rem;
padding: 20px;
}
.login-title {
text-align: center;
}
.login-form {
padding: 20px;
}
</style>
{% include 'layout.html' %}

<div class="login-container">
<h2 class="login-title">账号密码登录</h2>
<form class="login-form" method="post" action="/login">
<!-- 获取url中的next参数 -->
<input type="hidden" name="next" value="{{ request.args.get('next') }}" />
<div class="form-group">
{{ form.email.label() }}
{% if form.email.errors %}
{{ form.email(class="form-control is-invalid") }}
<div class="invalid-feedback">
{% for error in form.email.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.email(class="form-control") }}
{% endif %}
</div>
<div class="form-group">
{{ form.password.label() }}
{% if form.password.errors %}
{{ form.password(class="form-control is-invalid") }}
<div class="invalid-feedback">
{% for error in form.password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password(class="form-control") }}
{% endif %}
</div>
<div class="form-check" style="padding-bottom:10px">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="remember"> 记住我
</label>
</div>
<button type="submit" class="btn btn-success btn-lg btn-block">登录</button>
{{ form.csrf_token }}
</form>
</div>

{% endblock %}

Flask_login实现登录和退出

在app/routes.py中添加如下代码:

# routes.py
@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 = User.query.filter_by(email=form.email.data).first()
if user and check_password_hash(user.password, form.password.data):
login_user(user)
# 获取next参数
next_page = request.form.get('next')
return redirect(next_page) if next_page else redirect(url_for('index'))
else:
flash('邮箱和密码不匹配', 'danger')
return render_template('login.html', form=form)


@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))

templates下新建layout.html文件,用于显示通用信息。代码如下:

{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category , message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endfor %}
{% endif %}
{% endwith %}

注意: 为了实现next跳转,需要在login页面中获取next参数,可以设置为hidden属性,html代码如下:

<!-- 获取url中的next参数 -->
<input type="hidden" name="next" value="{{ request.args.get('next') }}" />

提交表单时传到到login路由。然后再路由中通过如下代码获取:

# routes.py
next_page = request.form.get('next')

Flask_Login登录权限验证

base.html中新增判断是否登录的代码:

<ul class="navbar-nav">
{% if current_user.is_authenticated %}
<li class="nav-item dropdown">
<button class="btn btn-outline-success dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
我的
</button>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="/favorites">我的收藏</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/change_pwd">修改密码</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/logout">退出登录</a>
</div>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" style="color:white;">
<button class="btn btn-outline-success" >
登录
</button>
</a>
</li>
{% endif %}
</ul>

登录权限判断

因为很多路由都需要判断用户是否登录,比较繁琐,所以使用flask_login中的装饰器来统一实现这个功能。

在routes.py中,顶部引入login_required, 代码如下:

from flask_login import current_user, login_user, logout_user, login_required

在需要判断的用户是否登录的地方引入。代码如下:


from flask_login import current_user, login_user, logout_user, login_required


@app.route('/change_pwd')
@login_required
def change_pwd():
return '修改密码'

如果没有登录,直接访问/change_pwd, 运行结果如下:

Unauthorized
The server could not verify that you are authorized to access the URL requested. You either supplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to supply the credentials required.

这种错误提示并不友好,可以设置flask_login, 令其跳转到登录页。

在app/init.py文件中配置flask_login,代码如下:

# __init__.py

from flask_login import LoginManager


login_manager = LoginManager(app)
# 配置flask_login
login_manager.login_view = 'login'
login_manager.login_message = '请先登录'
login_manager.login_message_category = 'danger'

修改密码

创建路由

# routes.py
from app.forms import LoginForm,ChangePasswordForm

@app.route('/change_pwd/',methods=['GET','POST'])
@login_required
def change_pwd():
form = ChangePasswordForm()
if form.validate_on_submit():
if check_password_hash(current_user.password, form.password.data):
# 根据当前用户id获取用户信息
user = User.query.filter_by(id=current_user.id).first()
# 获取新密码
new_password = form.new_password.data
# 对新密码加密
user.password = generate_password_hash(new_password)
# 提交到数据库
db.session.commit()
# 将成功消息存入闪存
flash('修改成功', 'success')
# 保存成功后,跳转到登录页面
return redirect(url_for('change_pwd'))
else:
# 将失败消息存入闪存
flash('原始密码错误', 'danger')
return render_template('change_pwd.html',form=form)

在app/forms.py文件中新增ChangePasswordForm, 代码如下:

# forms.py

class ChangePasswordForm(FlaskForm):
password = PasswordField('原始密码',
validators=[
DataRequired(message="原始密码不能为空"),
Length(min=6, max=25, message='密码长度为6-25个字符'),
])
new_password = PasswordField('新密码',
validators=[
DataRequired(message="新密码不能为空"),
Length(min=6, max=25, message='密码长度为6-25个字符')])
confirm_password = PasswordField('确认密码',
validators=[
DataRequired(message="确认密码不能为空"),
EqualTo('new_password', message="2次输入密码不一致")])

在templates下新增change_pwd.html模板文件,代码如下:

{% extends 'base.html' %}
{% block content%}
<style>
.login-container {
width: 500px;
margin: 100px auto;
padding: 20px 10px;
background-color: #eef1f4;
border-radius: .5rem;
padding: 20px;
}
.login-title {
text-align: center;
}
.login-form {
padding: 20px;
}
</style>

{% include "layout.html" %}

<div class="login-container">
<h2 class="login-title">修改密码</h2>
<form class="login-form" method="post" action="{{url_for('change_pwd')}}">
<div class="form-group">
{{ form.password.label() }}
{% if form.password.errors %}
{{ form.password(class="form-control is-invalid") }}
<div class="invalid-feedback">
{% for error in form.password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password(class="form-control") }}
{% endif %}
</div>
<div class="form-group">
{{ form.new_password.label() }}
{% if form.new_password.errors %}
{{ form.new_password(class="form-control is-invalid") }}
<div class="invalid-feedback">
{% for error in form.new_password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.new_password(class="form-control") }}
{% endif %}
</div>
<div class="form-group">
{{ form.confirm_password.label() }}
{% if form.confirm_password.errors %}
{{ form.confirm_password(class="form-control is-invalid") }}
<div class="invalid-feedback">
{% for error in form.confirm_password.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.confirm_password(class="form-control") }}
{% endif %}
</div>
<button type="submit" class="btn btn-success btn-lg btn-block">提交</button>
{{ form.hidden_tag() }}
</form>
</div>
{% endblock %}