Flask+web开发

论坛 期权论坛 脚本     
匿名技术用户   2020-12-27 06:41   23   0

第一部分:Flask简介


1.为项目创建虚拟化环境

创建:mkvirtualenv py3env -p python3

worken 查看虚拟环境

workon flask_py2 切换虚拟环境

ps:虚拟化之后,环境之间的安装包是独立的,公有的是python系统上的包。

2.项目基本流程

from flask import Flask
app = Flask(__name__)

ps:处理来自客户端的请求的接口。

@app.route('/')
def index():
return '<h1>Hello World!</h1>'

ps:客户端的请求发给服务器,服务器把请求交给flask,flask通过url定位到路由来执行相应的代码,这个route就叫路由

ps:index()这个函数称作视图函数

@app.route('/user/<name>')
def user(name):
return '<h1>Hello, %s!</h1>' % name

ps:动态的视图函数。尖括号中的内容就是动态部分。

if __name__ == '__main__':
app.run(debug=True) //调试模式才用debug=True

ps:启动服务器,该模块导入会忽略if这个代码块。

@app.route('/')
def index():
return '<h1>Bad Request</h1>', 400

ps:第二个返回值可以是状态码。
@app.route('/')
def index():
response = make_response('<h1>This document carries a cookie!</h1>')
response.set_cookie('answer', '42')
return response

ps:Flask 视图函数还可以返回Response 对象。make_response() 函数可接受1 个、2 个或3 个参数(和视图函数的返回值一样),并返回一个Response 对象

@app.route('/')
def index():
return redirect('http://www.example.com')

ps:重定向功能统一使用redir视图函数。

from flask.ext.script import Manager
manager = Manager(app)
# ...
if __name__ == '__main__':
manager.run()

ps:下载pip install flask-script。

ps:解析命令行的工具。允许同局域网的客户端进行请求python hello.py runserver --host 0.0.0.0

3.模板

模板渲染就是为了把视图函数和HTML代码进行分隔开。

ps:默认文件夹templates/user.html

@app.route('/')
def index():
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)

ps:使用{{ name }} 结构表示一个变量。

ps:而且能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象

ps:变量的过滤器。

{% if user %}
Hello, {{ user }}!
{% else %}
Hello, Stranger!
{% endif %}

ps:条件控制语句。

<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>

ps:循环控制语句。

{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
</ul>

ps:可以定义python的函数。

{% include 'common.html' %}

ps:支持导入模块。实现代码复用。

建一个名为base.html 的基模板: //定义了head,title,body块
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - My Application</title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>

{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }} //获取原有的内容
<style>
</style>
{% endblock %}
{% block body %}
<h1>Hello, World!</h1>
{% endblock %}

ps:首先创建了base.html,在进行了模板继承,进行一定的修改。

@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404 //客户端出错。
@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500 //服务器出错。

ps:自定义错误页面。应该继承base.html文件,然后进行重写div标签即可。

url_for() 函数最简单的用法是以视图函数名作为参数。

ps:调用url_for('index') 得到的结果是/。调用url_for('index', _external=True) 返回的则是绝对地址,在这个示例中是http://localhost:5000/。

ps:url_for('user', name='john', _external=True) 的返回结果是http://localhost:5000/user/john。

ps:url_for('index', page=2) 的返回结果是/?page=2。

静态文件,需要包含static/css/styles.css。

ps:url_for('static', filename='css/styles.css', _external=True) 得到的结果是http://localhost:5000/static/css/styles.css。

ps:url的一切使用都可以当成{{变量}}来使用。

(venv) $ pip install flask-bootstrap

from flask.ext.bootstrap import Bootstrap
bootstrap = Bootstrap(app)

.....................

templates/base.html:使用Flask-Bootstrap 的模板
{% extends "bootstrap/base.html" %}

ps:flask集成bootstrap。模板渲染的方式变了。

(venv) $ pip install flask-moment

from flask.ext.moment import Moment
moment = Moment(app)

......................

{% block scripts %}
{{ super() }}
{{ moment.include_moment() }} //还需要依赖jquery.js,但是Bootstrap已经包含了。
{% endblock %}

ps:渲染时间的包。交给浏览器处理,然后转化为当地时间从浏览器上取出。

时间渲染的一个例子:
from datetime import datetime
@app.route('/')
def index():
return render_template('index.html',current_time=datetime.utcnow())

.............................
<p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>

ps:LLL代表时间的复杂度。refresh可以进行时间刷新,例如一分钟前......。

4.Web表单

下载安装包: pip install flask-wtf

跨站请求伪造,处理CSRF攻击。

app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'

ps:设置了一个密匙。

from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required
class NameForm(Form):
name = StringField('What is your name?', validators=[Required()]) //
submit = SubmitField('Submit')

ps:定义了一个简单的表单类。有一个文本字段和一个提交按钮。且属于类变量。

ps:StringField类表示属性为type="text" 的<input> 元素。SubmitField 类表示属性为type="submit" 的<input> 元素。字段构造函数的第一个参数是把表单渲染成HTML 时使用的标号。

快速的表单构建:

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

......................

{{ wtf.quick_form(form) }}

@app.route('/', methods=['GET', 'POST'])
def index():
name = None
form = NameForm() //创建表单实例
if form.validate_on_submit(): //进行验证请求,get or post,还会判断是否为空
name = form.name.data
form.name.data = ''
return render_template('index.html', form=form, name=name)

ps:快速构建表单。如果未指定method参数默认会是get请求。

ps:这样设计是会有问题,post出现错误时候会出现警告,体验度不好,可以使用post/重定向/get请求来处理。

@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm() //创建表单实例
if form.validate_on_submit():
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'))

ps:使用session进行存入已经获取的数据。

Flask 的核心特性。flash() 函数可实现用户/用户名输入错误。

@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
old_name = session.get('name')
if old_name is not None and old_name != form.name.data:
flash('Looks like you have changed your name!')
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html',form = form, name = session.get('name'))

{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}

ps:利用flash处理用户名错误的问题。可以使用get_flashed_messages来渲染模板。flash消息基于迭代器/生产器这种。

5.数据库

安装库:pip install flask-sqlalchemy

from flask.ext.sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__)) //获取绝对路径(全路径)
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True //每次结束自动提交
db = SQLAlchemy(app) //得到类的实例

ps:数据库配置。

class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
__tablename__ = 'users' //表名称
id = db.Column(db.Integer, primary_key=True) //建立主键
username = db.Column(db.String(64), unique=True, index=True) //加约束,加索引
def __repr__(self): //实例可以返回可读性的字符串
return '<User %r>' % self.username

class Role(db.Model):
# ...
users = db.relationship('User', backref='role') //建立反向关系
class User(db.Model):
# ...
role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) //创建外键,进行外键关联

ps:建立数据库模型(建表)。角色到用户是一对多关系,所以用户需要外键关联角色。

ps:一对一关系,调用db.relationship() 时要把uselist 设为False,把“多”变成“一”。多对多需要用到第三张表。

(venv) $ python hello.py shell
>>> from hello import db
>>> db.create_all()

>>> db.drop_all() //要更新表,必须先删除在创建,但是会丢失数据

admin_role = Role(name='Admin')

user_john = User(username='john', role=admin_role) //插入数据

>>> db.session.add(admin_role)

>>> db.session.add(user_john) //添加到会话中去

>>> db.session.add_all([admin_role, mod_role, user_role,user_john, user_susan, user_david])

>>> db.session.commit() //提交会话

db.session.rollback() //回滚会话中的所有操作

>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role) //修改行
>>> db.session.commit()

>>> db.session.delete(mod_role) //删除行
>>> db.session.commit()

ps:创建表。更新表。插入数据。回滚操作。修改行。删除行。

>>> User.query.filter_by(role=user_role).all() //在user表中查找角色为xxx的用户。角色到用户是一对多关系。

ps:等于使用不相关子查询。

ps:查询操作。

(venv) $ pip install flask-migrate

from flask.ext.migrate import Migrate, MigrateCommand
# ...
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)

(venv) $ python hello.py db init //创建迁移仓库

(venv) $ python hello.py db migrate -m "initial migration" //创建迁移脚本

ps:可直接 python python hello.py db migrate!

(venv) $ python hello.py db upgrade //更新数据库

ps:数据库的更新操作。downgrade() 函数则将迁移中的改动删除。

6.电子邮件

下载: pip install flask-mail

Flask-Mail 连接到简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)服务器,并把邮件交给这个服务器发送。

ps:默认连接到本机服务器。

import os
# ...
app.config['MAIL_SERVER'] = 'smtp.googlemail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')

(venv) $ export MAIL_USERNAME=<Gmail username> //添加邮箱的系统环境变量
(venv) $ export MAIL_PASSWORD=<Gmail password>

ps:进行配置,并且给Google Gmail 账户发送电子邮件。

(venv) $ python hello.py shell
>>> from flask.ext.mail import Message
>>> from hello import mail
>>> msg = Message('test subject', sender='you@example.com',recipients=['you@example.com'])
>>> msg.body = 'text body'
>>> msg.html = '<b>HTML</b> body'
>>> with app.app_context():
mail.send(msg)

ps:测试配置是否正确。

from threading import Thread
def send_async_email(app, msg):
with app.app_context():
mail.send(msg)
def send_email(to, subject, template, **kwargs):
msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + '.html', **kwargs)
thr = Thread(target=send_async_email, args=[app, msg])
thr.start()
return thr

ps:发送邮件需要实现异步,尽管有全局锁,但是一定是会一经阻塞就切换线程的,几乎不影响。Celey任务队列也可!

7.大型程序的结构

|-flasky
|-app/ //flask程序一般都在这里
|-templates/
|-static/
|-main/
|-__init__.py
|-errors.py
|-forms.py
|-views.py
|-__init__.py
|-email.py //电子邮件
|-models.py //数据库模型
|-migrations/ //包含数据库迁移脚本
|-tests/ //单元测试的编写
|-__init__.py
|-test*.py
|-venv/ //虚拟环境
|-requirements.txt //列出所有需要的依赖包
|-config.py //存储配置
|-manage.py //用于启动程序

config.py 文件是开发、测试和生产环境要使用不同的配置文件。

app/__init__.py是延迟创建程序实例,把创建过程移到可显式调用的工厂函数中。

from flask import Flask, render_template
from flask.ext.bootstrap import Bootstrap
from flask.ext.mail import Mail
from flask.ext.moment import Moment
from flask.ext.sqlalchemy import SQLAlchemy
from config import config
bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()
def create_app(config_name): //工厂函数
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
return app

app/main/__init__.py //创建蓝本,让路由暂时睡眠直到注册,避免错误

from flask import Blueprint
main = Blueprint('main', __name__)
from . import views, errors

app/_init_.py: //注册蓝本
def create_app(config_name):
# ...
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app

app/main/errors.py: //注册蓝本中的错误处理程序
from flask import render_template
from . import main
@main.app_errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@main.app_errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500

app/main/views.py: //注册蓝本中定义的程序路由
from datetime import datetime
from flask import render_template, session, redirect, url_for
from . import main
from .forms import NameForm
from .. import db
from ..models import User
@main.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()

#..........

return redirect(url_for('.index')) //等价main.index

ps:Flask 会为蓝本中的全部端点加上一个命名空间,这样就可以在不同的蓝本中使用相同的端点名定义视图函数。

ps:表单对象也要移到蓝本中,保存于app/main/forms.py 模块。

manage.py: //启动脚本
import os
from app import create_app, db
from app.models import User, Role
from flask.ext.script import Manager, Shell
from flask.ext.migrate import Migrate, MigrateCommand
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
manager = Manager(app)
migrate = Migrate(app, db)
def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role)
manager.add_command("shell", Shell(make_context=make_shell_context)) //Python shell 定义的上下文
manager.add_command('db', MigrateCommand)

ps:把对象加入到上下文,不用自己在导入。
if __name__ == '__main__':
manager.run()

(venv) $ pip freeze >requirements.txt //生成版本库需求文件

(venv) $ pip install -r requirements.txt //可以直接进行下载

ps:生成需求文件。

tests/test_basics.py: //单元测试
import unittest
from flask import current_app
from app import create_app, db
class BasicsTestCase(unittest.TestCase):
def setUp(self):
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
def test_app_exists(self):
self.assertFalse(current_app is None)
def test_app_is_testing(self):
self.assertTrue(current_app.config['TESTING'])

manage.py: //启动单元测试的命令
@manager.command
def test():
"""Run the unit tests."""
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)

(venv) $ python manage.py test

ps:以test_ 开头的函数都作为测试执行。

第二部分 实例:社交博客程序


8.用户认证

Werkzeug:计算密码散列值并进行核对。

ps:存储的不是用户的密码,而是密码的一个哈希值。通常会进行一些加盐来计算哈希值。

app/models.py: //在User 模型中加入密码散列
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model):
# ...
password_hash = db.Column(db.String(128))
@property //使方法可以像属性一样被调用可读,直接报错
def password(self):
raise AttributeError('password is not a readable attribute')
@password.setter //使方法可以被设置可修改,会设置密码
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)

ps:即使用户u 和u2 使用了相同的密码,它们的密码散列值也完全不一样。因为默认是加盐了,salt_length=8。

创建认证蓝本:创建程序的过程移入工厂函数后,可以使用蓝本在全局作用域中定义路由。与用户认证系统相关的路由可在auth 蓝本中定义。对于不同的程序功能,我们要使用不同的蓝本。

 app/auth/__init__.py: //创建蓝本
from flask import Blueprint
auth = Blueprint('auth', __name__)
from . import views

app/auth/views.py: //注册蓝本中的路由和视图函数
from flask import render_template
from . import auth
@auth.route('/login')
def login():
return render_template('auth/login.html')

app/__init__.py: //注册附加蓝本
def create_app(config_name):
# ...
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/auth') // 加上前缀/login 路由会注册成/auth/login
return app

Flask-Login:管理已登录用户的用户会话。下载: pip install flask-login

app/models.py //修改User 模型,支持用户登录

from flask.ext.login import UserMixin
class User(UserMixin, db.Model):
__tablename__ = 'users'

......

ps:可以通过继承来利用默认的方法,能满足大部分需求。

app/__init__.py: //初始化Flask-Login
from flask.ext.login import LoginManager
login_manager = LoginManager()
login_manager.session_protection = 'strong' //安全等级。分为None、“basic”、“strong”
login_manager.login_view = 'auth.login' //登陆路由在蓝本中定义,因此要加上蓝本的名字。
def create_app(config_name):
# ...
login_manager.init_app(app)
# ...

ps:初始化工厂函数。

app/models.py: //加载用户的回调函数
from . import login_manager
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))

ps:必须实现这个回调函数。

from flask.ext.login import login_required
@app.route('/secret') //登录页面
@login_required
def secret():
return 'Only authenticated users are allowed!'

ps:只有验证过的用户才能通过,否则发送到登陆页面去。

app/auth/forms.py: //登录表单
from flask.ext.wtf import Form
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import Required, Length, Email
class LoginForm(Form):
email = StringField('Email', validators=[Required(), Length(1, 64),Email()]) //长度和邮箱的验证函数。
password = PasswordField('Password', validators=[Required()]) //表示属性为type="password" 的<input> 元素
remember_me = BooleanField('Keep me logged in')
submit = SubmitField('Log In')

app/templates/base.html: //导航条中的Sign In 和Sign Out 链接
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_authenticated() %}
<li><a href="{{ url_for('auth.logout') }}">Sign Out</a></li> //蓝本的作用域的问题,而不是直接传入函数。
{% else %}
<li><a href="{{ url_for('auth.login') }}">Sign In</a></li>
{% endif %}
</ul>

app/auth/views.py: //登录路由
from flask import render_template, redirect, request, url_for, flash
from flask.ext.login import login_user
from . import auth
from ..models import User
from .forms import LoginForm
@auth.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm() //先创建一个表单实例
if form.validate_on_submit(): //验证是get or post请求
user = User.query.filter_by(email=form.email.data).first() //查询出用户的信息
if user is not None and user.verify_password(form.password.data): //进行验证
login_user(user, form.remember_me.data) //进行用户登陆,注意有是否记录cookie的。
return redirect(request.args.get('next') or url_for('main.index')) //进行重定向。成功就是到首页了。
flash('Invalid username or password.') //未通过验证,会进行消息提示
return render_template('auth/login.html', form=想·想·) //进行模板渲染

ps:用户访问未授权的URL 时会显示登录表单,Flask-Login会把原地址保存在查询字符串的next 参数中这个参数可从request.args 字典中读取

app/templates/auth/login.html: //渲染登录表单
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky - Login{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Login</h1>
</div>
<div class="col-md-4">
{{ wtf.quick_form(form) }} //快速的生成表单登陆。
</div>
{% endblock %}

app/auth/views.py: //退出路由
from flask.ext.login import logout_user, login_required
@auth.route('/logout')
@login_required
def logout():
logout_user() //使用该函数,删除用户会话。
flash('You have been logged out.')
return redirect(url_for('main.index'))

测试登陆

(venv) $ python manage.py shell
>>> u = User(email='john@example.com', username='john', password='cat')
>>> db.session.add(u)
>>> db.session.commit()

app/auth/forms.py: //用户注册表单
from flask.ext.wtf import Form
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import Required, Length, Email, Regexp, EqualTo
from wtforms import ValidationError
from ..models import User
class RegistrationForm(Form):
email = StringField('Email', validators=[Required(), Length(1, 64),Email()]) //验证邮箱
username = StringField('Username', validators=[Required(), Length(1, 64), Regexp('^[A-Za-z][A-Za-z0-9_.]*$',0,'Usernames must have only letters, ''numbers, dots or underscores')]) //验证名称
password = PasswordField('Password', validators=[Required(), EqualTo('password2', message='Passwords must match.')]) //验证密码,要附属到两个密码字段中的一个上,另一个字段则作为参数传入
password2 = PasswordField('Confirm password', validators=[Required()]) //验证密码
submit = SubmitField('Register') //进行注册
def validate_email(self, field):
if User.query.filter_by(email=field.data).first(): //邮箱不能已经被注册过
raise ValidationError('Email already registered.')
def validate_username(self, field): //用户名不能重复
if User.query.filter_by(username=field.data).first():
raise ValidationError('Username already in use.')

ps:如果表单类中定义了以validate_ 开头且后面跟着字段名的方法,这个方法就和常规的验证函数一起调用。

app/templates/auth/login.html: //链接到注册页面
<p>
New user?
<a href="{{ url_for('auth.register') }}">
Click here to register
</a>
</p>

app/auth/views.py: //用户注册路由
@auth.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit(): //如果判断为1,就是post请求
user = User(email=form.email.data,username=form.username.data,password=form.password.data)
db.session.add(user) //会话进行添加,默认是自动提交
flash('You can now login.')
return redirect(url_for('auth.login'))
return render_template('auth/register.html', form=form)

安全的cookie 使用itsdangerous 包签名。同样的方法也可用于确认令牌上。

令牌token主要是确认邮件,才用的!

app/models.py确认用户账户
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app
from . import db
class User(UserMixin, db.Model):
# ...
confirmed = db.Column(db.Boolean, default=False) //增加一列
def generate_confirmation_token(self, expiration=3600): //生成一个令牌,有效期默认为一小时3600秒
s = Serializer(current_app.config['SECRET_KEY'], expiration)
return s.dumps({'confirm': self.id})
def confirm(self, token): //检验令牌,如果检验通过,则把新添加的confirmed 属性设为True
s = Serializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(token) //把token加密,进行解密。如果过期会报错
except:
return False
if data.get('confirm') != self.id: //令牌不一样也是错
return False
self.confirmed = True //通过了验证
db.session.add(self) //更新一遍用户信息
return True

app/auth/views.py: //能发送确认邮件的注册路由
from ..email import send_email
@auth.route('/register', methods = ['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
# ...
db.session.add(user)
db.session.commit() //需要先提交,因为令牌验证需要id号
token = user.generate_confirmation_token()
send_email(user.email, 'Confirm Your Account','auth/email/confirm', user=user, token=token)
flash('A confirmation email has been sent to you by email.')
return redirect(url_for('main.index'))
return render_template('auth/register.html', form=form)

app/templates/auth/email/confirm.txt: //确认邮件的纯文本正文
Dear {{ user.username }},
Welcome to Flasky!
To confirm your account please click on the following link:
{{ url_for('auth.confirm', token=token, _external=True) }} // 返回的URL '/auth/confirm/token' ,要加external绝对路径。
Sincerely,
The Flasky Team
Note: replies to this email address are not monitored.

app/auth/views.py: //确认用户的账户
from flask.ext.login import current_user
@auth.route('/confirm/<token>')
@login_required
def confirm(token):
if current_user.confirmed: //可以避免用户不小心多次点击确认令牌带来的额外工作。
return redirect(url_for('main.index')) //main是当前目录,index是对应view的函数
if current_user.confirm(token):
flash('You have confirmed your account. Thanks!')
else:
flash('The confirmation link is invalid or has expired.')
return redirect(url_for('main.index'))

//使用钩子

app/auth/views.py: //在before_app_request 处理程序中过滤未确认的账户(未登录的)
@auth.before_app_request
def before_request(): //使用钩子来完成认证用户的前一步确认操作
if current_user.is_authenticated() and not current_user.confirmed and request.blueprint != 'auth.' and request.endpoint != 'static': //验证用户已登陆。用户没有通过验证。请求的端点不在认证蓝本中
return redirect(url_for('auth.unconfirmed'))
@auth.route('/unconfirmed')
def unconfirmed(): //未通过验证的用户进行的模板渲染
if current_user.is_anonymous() or current_user.confirmed: #w
return redirect(url_for('main.index'))
return render_template('auth/unconfirmed.html')

ps:对蓝本来说,before_request 钩子只能应用到属于蓝本的请求上。若想在蓝本中使用针对程序全局请求的钩子,必须使用before_app_request 修饰器

app/auth/views.py: //重新发送账户确认邮件
@auth.route('/confirm')
@login_required
def resend_confirmation():
token = current_user.generate_confirmation_token()
send_email(current_user.email, 'Confirm Your Account','auth/email/confirm', user=current_user, token=token)
flash('A new confirmation email has been sent to you by email.')
return redirect(url_for('main.index'))

最后,还可以修改密码(需要原密码),重设密码(忘记了原密码),修改邮箱地址。

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP