Flask Todo项目:邮箱注册(1)

Flask Todo项目:邮箱注册(1)
10343

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') }}">&nbsp;登录</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(124"请输入24位字符之内")])
  email = StringField(validators=[Email(message="请输入正确的邮箱."), DataRequired(message="请输入内容."), Length(164'请输入正确长度的邮箱地址.')])
  password = PasswordField(validators=[Regexp(regex='^(?:(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])).{6,32}', message="密码应包含字母数字大小写,至少6位."), Length(632"密码长于6位"), DataRequired("密码应包含字母数字大小写,至少6位.")])
  password2 = PasswordField(validators=[EqualTo('password', message="密码不一致")])
  code = StringField(validators=[Regexp(regex='^[A-Za-z0-9]+$', message="验证码只包含字母数字大小写"), Length(44 , '请输入正确的验证码.'), 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("用户名已存在")

需要注意的是,_usernameUser类的私有属性,同样要映射一个属性名.

# 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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!