Flask Todo代办事项项目
1.创建基本视图
2.注册功能]
2.1 基本注册
2.2 邮箱注册(1)
2.邮箱注册
0.代码
https://github.com/ningwenyan/Flask_Todo_Demo/tree/v1.03
1.创建register.html
{% extends 'base.html' %}
{% from 'bootstrap/utils.html' import render_messages %}
{% from 'macro/form_errors.html' import print_error %}
{% block title %}
注册
{% endblock %}
{% block content %}
<div class="container">
<div class="row clearfix">
<div class="col-md-4 column">
</div>
<div class="col-md-4 column">
{{ render_messages(container=False, dismissible=True, dismiss_animate=True) }}
<form action="" class="form-signin" method="post">
{{ form.csrf_token() }}
<fieldset>
<div class="form-group clearfix">
<label for="usernameInput">用户名</label>
<input type="text" class="form-control" name="username" aria-describedby="emailHelp" placeholder="不少于2个字符" id="usernameInput">
{{ print_error(form, name="username") }}
<label for="exampleInputEmail1">邮箱</label>
<input type="email" class="form-control" name="email" aria-describedby="emailHelp" placeholder="使用邮箱激活账户" id="exampleInputEmail">
{{ print_error(form, name="email") }}
<label for="exampleInputPassword1" style="float: left;">密码</label>
<input type="password" class="form-control" id="exampleInputPassword1" placeholder="至少6位,包含字母数字大小写" name="password">
{{ print_error(form, name="password") }}
<label for="exampleInputPassword2" style="float: left;">重复密码</label>
<input type="password" class="form-control" id="exampleInputPassword2" placeholder="确认密码" name="password2">
{{ print_error(form, name="password2") }}
<label for="Code">验证码</label>
<div class="clearfix">
<input type="text" class="form-control" name="code" placeholder="验证码" style="width: 70%; float:left">
<img src="" alt="code" style="float: right; width: 27%">
</div>
{{ print_error(form, name="code") }}
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="confirmed">
我已阅读并同意<a href="{{ url_for('auth.auth_protocol') }}">TodoList用户注册协议</a>
</label>
</div>
<button type="submit" class="btn btn-success btn-lg" style="margin: 10px 80px; width:50%; padding: 8px 12px">注册</button>
</fieldset>
</form>
<br>
<small>已有账户?</small><a href="{{ url_for('auth.auth_login') }}"> 登录</a>
</div>
<div class="col-md-4 column">
</div>
</div>
</div>
{% endblock %}创建用户注册协议路由
# auth/views.py
# 注册
@auth_bp.route('/register/', methods=['GET', 'POST'])
def auth_register():
form = AuthRegisterForm()
if form.validate_on_submit():
"""创建用户"""
pass
return render_template('auth/register.html', form=form)
@auth_bp.route('/protocol/')
def auth_protocol():
return render_template('auth/protocol.html')创建页面
{% extends 'base.html' %}
{% block title %}
注册协议
{% endblock %}
{% block content %}
<div class="container">
<div class="row clearfix">
<div class="col-md-3 column">
</div>
<div class="col-md-6 column">
<div class="card text-white bg-danger mb-3" style="max-width: 65rem;">
<div class="card-header">免费注册</div>
<div class="card-body">
<h4 class="card-title">声明</h4>
<p class="card-text">
一、不得利用本站危害国家安全、泄露国家秘密,不得侵犯国家社会集体的和公民的合法权益,不得利用本站制作、复制和传播下列信息:
</p>
<p class="card-text">(一)煽动抗拒、破坏宪法和法律、行政法规实施的;</p>
<p class="card-text">(二)煽动颠覆国家政权,推翻社会主义制度的;</p>
<p class="card-text">(三)煽动分裂国家、破坏国家统一的;</p>
<p class="card-text">(四)煽动民族仇恨、民族歧视,破坏民族团结的;</p>
<p class="card-text">(五)捏造或者歪曲事实,散布谣言,扰乱社会秩序的;</p>
<p class="card-text">(六)宣扬封建迷信、淫秽、色情、赌博、暴力、凶杀、恐怖、教唆犯罪的;</p>
<p class="card-text">(七)公然侮辱他人或者捏造事实诽谤他人的,或者进行其他恶意攻击的;</p>
<p class="card-text">(八)损害国家机关信誉的;</p>
<p class="card-text">(九)其他违反宪法和法律行政法规的;</p>
<p class="card-text">(十)进行商业广告行为的. </p>
<p><a type="button" class="btn btn-success" href="{{ url_for('auth.auth_register') }}">返回注册</a></p>
</div>
</div>
<div class="col-md-3 column">
</div>
</div>
</div>
{% endblock %}表单验证:我们希望在用户创建时,就提供是否已存在用户和邮箱,所以这里创建了自定义的
Field
# auth/forms.py
class AuthRegisterForm(FlaskForm):
username = StringField(validators=[Length(1, 24, "请输入24位字符之内")])
email = StringField(validators=[Email(message="请输入正确的邮箱."), DataRequired(message="请输入内容."), Length(1, 64, '请输入正确长度的邮箱地址.')])
password = PasswordField(validators=[Regexp(regex='^(?:(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])).{6,32}', message="密码应包含字母数字大小写,至少6位."), Length(6, 32, "密码长于6位"), DataRequired("密码应包含字母数字大小写,至少6位.")])
password2 = PasswordField(validators=[EqualTo('password', message="密码不一致")])
code = StringField(validators=[Regexp(regex='^[A-Za-z0-9]+$', message="验证码只包含字母数字大小写"), Length(4, 4 , '请输入正确的验证码.'), DataRequired("请输入4位验证码")])
confirmed = BooleanField(validators=[])
# 自定义Field,验证邮箱和用户
def validate_email(self, field):
"""将邮箱地址全部转换为小写"""
if User.query.filter_by(email = field.data.lower()).first():
raise ValidationError("邮箱已注册")
def validata_username(self, field):
"""注意: _username 是类的私有属性,要在sqlModel中添加映射"""
if User.query.filter_by(username=field.data).first():
raise ValidationError("用户名已存在")
需要注意的是,
_username
是User
类的私有属性,同样要映射一个属性名.# sqlModel.py
class User:
# -- username 正则匹配,长度约束
@property
def username(self):
return self._username
@username.setter
def username(self, raw_username):
"""检查有效长度"""
is_valid_length = check_length(raw_username, 24)
if not is_valid_length or not bool(USERNAME_REGEX.match(raw_username)):
raise ValueError('{} 不是有效的用户名,不允许超过24位字符'.format(raw_username))
self._username = raw_username
username = synonym("_username", descriptor=username)
2.生成token
确认用户
我们希望发送给用户一个链接:
http://xx.com/auth/confirm/<token>
让用户在邮箱中点击激活,这就需要一个token
数据,使用它来唯一确定用户,并设置超时时间.使用
itsdangerous
来满足JWS
序列化签名.# sqlModel.py
from itsdangerous import TimedJSONWebSignatureSerializer as Serialize
class User():
...
# -- 创建邮箱token
# 生成token,使用itsdangerous序列化token,确定唯一用户,并设置超时时间
def generate_confirmation_token(self, expiration=3600):
s = Serializer(current_app._get_current_object().config['SECRET_KEY'], expires_in=expiration)
return s.dumps({'confirm':self.id}).decode('utf-8')
# 接受序列化信息并检测
def check_confirmation_token(self, token):
s = Serializer(current_app._get_current_object().config['SECRET_KEY'])
try:
data = s.loads(token.encode('utf-8'))
except:
return False
if data.get('confirm') != self.id:
# 检测唯一id
return False
# 所有检测通过,证明唯一用户,设置标志位
self.confirmed = True
# 没有执行db.session.commit(), 因为用户这里并不能确定用户点击了激活的超链接
# 这将会在 views.py 中定义
db.session.add(self)
return True配置
Flask_mail
并初始化# config.py
# 配置Flask Mail
# 设置邮箱
MAIL_SERVER = 'smtp.qq.com'
MAIL_PORT = 465
MAIL_USERNAME = '2573@qq.com'
MAIL_PASSWORD = "imkzuyo"
MAIL_USE_SSL = True
MAIL_USE_TLS = False
MAIL_DEFAULT_SENDER = '2573@qq.com'# app/__init__.py
from .commons.exts import bootstrap, login_manager, db, bcrypt, csrf, mail
from .utils import utils_bp
def create_app():
app = Flask(__name__)
app.config.from_object(config)
# 初始化插件
bootstrap.init_app(app)
login_manager.init_app(app)
db.init_app(app)
bcrypt.init_app(app)
csrf.init_app(app)
mail.init_app(app)
# 注册蓝图
app.register_blueprint(auth_bp) # 登录
app.register_blueprint(main_bp) # 逻辑
app.register_blueprint(commons_bp) # 公共
app.register_blueprint(utils_bp) # 工具
return a邮箱发送将放置在
utils
工具蓝图中.# utils/sendMail.py
from flask import render_template, current_app
from threading import Thread
from flask_mail import Message
from app.commons.exts import mail
# 定义异步发送邮件
def async_send_mail(app, msg):
# 要求在Flask的一次访问中发送邮件,下面代码中新建的线程中并
# 不包含 上下文结构,手动推送
with app.app_context():
mail.send(msg)
def sendMail(to, subject, template, **kwargs):
try:
# 创建邮件
msg = Message(subject, recipients=[to])
# 回传浏览器
msg.body = render_template('auth/mail/'+ template + '.txt', **kwargs)
msg.html = render_template('auth/mail/' + template + '.html', **kwargs)
# 创建一个新线程,发送邮件
# 根据flask上下文,如果不再同一个 app 中,将无法发送邮件
app = current_app._get_current_object()
thread = Thread(target=async_send_mail, args=[app, msg])
thread.start()
return thread
except Exception as e:
print(e)更新视图
# auth/views.py
# 注册
@auth_bp.route('/register/', methods=['GET', 'POST'])
def auth_register():
form = AuthRegisterForm()
if form.validate_on_submit():
if form.confirmed.data:
"""创建用户"""
user = User(email=form.email.data.lower(), username=form.username.data, password=form.password.data)
user.save()
# 邮件激活
# 生成token
token = user.generate_confirmation_token()
# 将token发送给用户邮箱
sendMail(user.email, "激活账户", 'confirm', user=user, token=token)
flash("请通过注册邮箱激活账户!", 'info')
return redirect(url_for('main.index'))
else:
"""同意协议"""
flash("请阅读同意注册协议", 'info')
redirect(url_for('auth.auth_register'))
return render_template('auth/register.html', form=form)
# 登录状态下,激活邮件
@auth_bp.route('/confirm/<token>')
@login_required
def auth_confirm(token):
"""通过current_user flask_login代理访问用户, 如果激活,直接跳转首页"""
if current_user.confirmed:
return redirect(url_for('main.index'))
# 如果没有激活,验证序列化内容
if current_user.check_confirmation_token(token):
# 设置标志位为True
db.session.commit()
flash('您的账户已激活.', 'success')
else:
flash('激活链接已过期,请重新激活.', 'warning')
return redirect(url_for('main.index'))注意,在
sqlModel
中并没有直接提交激活的db
,而是在确认路由auth_confirm
中提交保存,同时要求必须在登录的状态下进行激活.邮件发送
<!-- auth/mail/confirm.html-->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>激活</title>
</head>
<body>
<p>您好,{{ user.username }}</p>
<p>感谢您的注册,点击<a href="{{ url_for('auth.auth_confirm', token=token, _external=True) }}">激活</a>按钮激活你的账户.</p>
<p>您也可以双击以下连接激活账户:</p>
<p>{{ url_for('auth.auth_confirm', token=token, _external=True) }}</p>
<p>如果已激活,请忽略此邮件.</p>
</body>
</html># confirm.txt
您好,{{ user.username }}
感谢您的注册,点击<a href="{{ url_for('auth.auth_confirm', token=token, _external=True) }}">激活</a>按钮激活你的账户.
您也可以双击以下连接激活账户:
{{ url_for('auth.auth_confirm', token=token, _external=True) }}
如果已激活,请忽略此邮件.
– END –
原文始发于微信公众号(Flask学习笔记):Flask Todo项目:邮箱注册(1)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/36366.html