• 用 Flask 来写个轻博客 (21) — 结合 reCAPTCHA 验证码实现用户注册与登录


    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog

    目录

    前文列表

    用 Flask 来写个轻博客 (1) — 创建项目
    用 Flask 来写个轻博客 (2) — Hello World!
    用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQL 和 SQLAlchemy
    用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表
    用 Flask 来写个轻博客 (5) — (M)VC_SQLAlchemy 的 CRUD 详解
    用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)
    用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)
    用 Flask 来写个轻博客 (8) — (M)VC_Alembic 管理数据库结构的升级和降级
    用 Flask 来写个轻博客 (9) — M(V)C_Jinja 语法基础快速概览
    用 Flask 来写个轻博客 (10) — M(V)C_Jinja 常用过滤器与 Flask 特殊变量及方法
    用 Flask 来写个轻博客 (11) — M(V)C_创建视图函数
    用 Flask 来写个轻博客 (12) — M(V)C_编写和继承 Jinja 模板
    用 Flask 来写个轻博客 (13) — M(V)C_WTForms 服务端表单检验
    用 Flask 来写个轻博客 (14) — M(V)C_实现项目首页的模板
    用 Flask 来写个轻博客 (15) — M(V)C_实现博文页面评论表单
    用 Flask 来写个轻博客 (16) — MV(C)_Flask Blueprint 蓝图
    用 Flask 来写个轻博客 (17) — MV(C)_应用蓝图来重构项目
    用 Flask 来写个轻博客 (18) — 使用工厂模式来生成应用对象
    用 Flask 来写个轻博客 (19) — 以 Bcrypt 密文存储账户信息与实现用户登陆表单
    用 Flask 来写个轻博客 (20) — 实现注册表单与应用 reCAPTCHA 来实现验证码

    扩展阅读

    flash 消息闪现

    添加账户管理蓝图

    网站的账户管理功能一般包括最基本的 登录 /注册 /权限, 在本篇中, 我们主要实现前两个功能模块.

    在当前的项目中, 对 URL: http://localhost:5000/ 的路由在 jmilkfansblog.__init__:create_app() 被定义且重定向到蓝图 blog 的视图函数 home() 中. 如果一个 URL /只被一个视图函数处理的话, 这样做是没有问题的, 但现在我们需要在 / 下添加新的视图函数来为项目提供账户管理功能, 因此我们需要添加一个新的 controller, 并把该控制器注册到 app 对象中.

    新建控制器(蓝图)

    • jmilkfansblog/controller/main.py
    from os import path
    from uuid import uuid4
    
    from flask import flash, url_for, redirect, render_template, Blueprint
    from jmilkfansblog.forms import LoginForm, RegisterForm
    
    from jmilkfansblog.models import db, User
    
    
    main_blueprint = Blueprint(
        'main',
        __name__,
        template_folder=path.join(path.pardir, 'templates', 'main'))
    
    @main_blueprint.route('/')
    def index():
        return redirect(url_for('blog.home'))

    NOTE : 当 HTTP Request method 为 GET 的时候, 路由函数 index() 将会被优先调用.

    • jmilkfansblog/__init__.py
    from flask import Flask, redirect, url_for
    
    from jmilkfansblog.models import db
    from jmilkfansblog.controllers import blog, main
    from jmilkfansblog.extensions import bcrypt
    
    
    def create_app(object_name):
        """Create the app instance via `Factory Method`"""
    
        app = Flask(__name__)
        # Set the config for app instance
        app.config.from_object(object_name)
    
        # Will be load the SQLALCHEMY_DATABASE_URL from config.py to db object
        db.init_app(app)
        # Init the Flask-Bcrypt via app object
        bcrypt.init_app(app)
    
        # Register the Blueprint into app object
        app.register_blueprint(blog.blog_blueprint)
        app.register_blueprint(main.main_blueprint)
    
        return app

    NOTE 1: 这样的话, app 对象就拥有了两个蓝图, 其中 blog 提供博客内容的管理和展示功能, main 提供了网站的账户管理功能.
    NOTE 2: 因为在 URL /下包含了博客首页和用户登录两个视图, 所以在两个蓝图中都必须含有对 / 进行处理的视图函数.

    新建表单

    表单主要用于输入登录和注册信息.

    • jmilkfansblog/forms.py
    class LoginForm(Form):
        """Login Form"""
    
        username = StringField('Usermame', [DataRequired(), Length(max=255)])
        password = PasswordField('Password', [DataRequired()])
    
        def validate(self):
            """Validator for check the account information."""
            check_validata = super(LoginForm, self).validate()
    
            # If validator no pass
            if not check_validata:
                return False
    
            # Check the user whether exist.
            user = User.query.filter_by(username=self.username.data).first()
            if not user:
                self.username.errors.append('Invalid username or password.')
                return False
    
            # Check the password whether right.
            if not user.check_password(self.password.data):
                self.username.errors.append('Invalid username or password.')
                return False
    
            return True
    
    
    class RegisterForm(Form):
        """Register Form."""
    
        username = StringField('Username', [DataRequired(), Length(max=255)])
        password = PasswordField('Password', [DataRequired(), Length(min=8)])
        comfirm = PasswordField('Confirm Password', [DataRequired(), EqualTo('password')])
        recaptcha = RecaptchaField()
    
        def validate(self):
            check_validate = super(RegisterForm, self).validate()
    
            # If validator no pass
            if not check_validate:
                return False
    
            # Check the user whether exist.
            user = User.query.filter_by(username=self.username.data).first()
            if user:
                self.username.errors.append('User with that name already exists.')
                return False
            return True

    NOTE 1: 在 class Form 中提供了检验函数 validate(), 该函数会在表单对象调用 validate_on_submit() 的时候被调用, 所以在 LoginForm 和 RegisterForm 子类中都重载了 Form.validate() 并扩展了个性化的检验需求. EG. LoginForm 的 validate() 还需要检验用户是否存在以及用户密码是否正确.

    NOTE 2: validate() 应该返回 boolean 类型.

    NOTE 3: 我们会在注册页面上使用验证码, 所以 RegisterForm 中定义了类属性 recaptcha = RecaptchaField() .

    新建蓝图 main 的视图函数

    在创建了表单之后, 我们需要新建为登录和注册模板提供数据对象的视图函数, 并且将表单对象应用到其中.

    • jmilkfansblog/controller/main.py
    @main_blueprint.route('/login', methods=['GET', 'POST'])
    def login():
        """View function for login."""
    
        # Will be check the account whether rigjt.
        form = LoginForm()
    
        if form.validate_on_submit():
            flash("You have been logged in.", category="success")
            return redirect(url_for('blog.home'))
    
        return render_template('login.html',
                               form=form)
    
    
    @main_blueprint.route('/logout', methods=['GET', 'POST'])
    def logout():
        """View function for logout."""
    
        flash("You have been logged out.", category="success")
        return redirect(url_for('blog.home'))
    
    @main_blueprint.route('/register', methods=['GET', 'POST'])
    def register():
        """View function for Register."""
    
        # Will be check the username whether exist.
        form = RegisterForm()
    
        if form.validate_on_submit():
            new_user = User(id=str(uuid4()),
                            username=form.username.data,
                            password=form.password.data)
    
            db.session.add(new_user)
            db.session.commit()
    
            flash('Your user has been created, please login.',
                  category="success")
    
            return redirect(url_for('main.login'))
        return render_template('register.html',
                               form=form)

    NOTE 1: 当用户注册成功之后会将用户信息写入到数据库中, 然后直接跳转到登录页面.

    NOTE 2: 在登录成功之后会返回 [(‘success’, ‘You have been logged in.’)] 的信息, 这是由 flash() 来实现的. 详见扩展阅读.

    新建模板

    最后我们在创建登录和注册页面的模板.

    • jmilkfansblog/template/base.html: 首先我们需要在 base 模板中加入对 flash() 和 reCAPTCHA 的支持.
    ...
    
      {% block captcha %}                                                      
      {% endblock %}                                                                            
    </head> 
    
    <body>
      <div class="container">
        <div class="jumbotron">
          <!-- Replace the route function to URL: `/` -->
          <h1><a href="{{ url_for('blog.home')}} ">JmilkFan's Blog</a></h1>
            <p>Welcome to the blog!</p>
        </div>
        {% with messages = get_flashed_messages(with_categories=true) %}
          {% if messages %}
            {% for category, message in messages %}
              <div class="button" class="close" data-dismiss="alert" aria-label="Close">
                <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                  <span aria-hidden="true">&times;</span>
                </button>
                {{ messages }}
              </div>
            {% endfor%}
          {% endif %}
        {% endwith %}
        {% block body %}
          body_content
        {% endblock %}
    ...

    NOTE 1: get_flashed_messages(with_categories=true) 逐一取出并消费掉之前在应用中通过 flash() 传入的信息列表,类似实现一个队列。

    NOTE 2: 一般来说 flash() 的消息是全局的, 只要在任意页面中定义了 get_flashed_messages() 就会被获取.

    • jmilkfansblog/template/login.html
    {% extends "base.html" %}
    {% block body %}
    <div class="col-lg-3">
      <!-- Set the form -->
      <form method="POST" action="{{ url_for('main.login')
        }}">
        {{ form.hidden_tag() }}
        <div>
          {{ form.username.label }}
          {% if form.username.errors %}
            {% for e in form.username.errors %}
              <p class="help-block">{{ e }}</p>
            {% endfor %}
          {% endif %}
          {{ form.username(class_="form-control") }}
        </div>
        <div class="form-group">
          {{ form.password.label }}
          {% if form.password.errors %}
            {% for e in form.password.errors %}
              <p class="help-block">{{ e }}</p>
            {% endfor %}
          {% endif %}
          {{ form.password(class_='form-control') }}
        </div>
        <input class="btn btn-primary" type="submit" value="Login">
      </form>
    </div>
    {% endblock %}
    • register.html
    {% extends "base.html" %}                                                  
    
    {% block title %}                                                          
      Register                                                                 
    {% endblock %}                                                             
    
    {% block captcha %}                                                        
      <script src='https://www.google.com/recaptcha/api.js'></script>          
    {% endblock %}
    
    {% block body %}                                                           
    <div class="col-lg-3">
      <!-- Set the form -->                                                    
      <form method="POST" action="{{ url_for('main.register')                  
        }}">
        {{ form.hidden_tag() }}
        <div> 
          {{ form.username.label }}                                            
          {{ form.username(class_="form-control") }}                           
        </div>
        <div class="form-group">                                               
          {{ form.password.label }}                                            
          {{ form.password(class_='form-control') }}                           
        </div>
        <div class="form-group">
          {{ form.comfirm.label }}
          {{ form.comfirm(class_='form-control') }}                            
        </div>
        <input class="btn btn-primary" type="submit" value="Register">         
        <div class="g-recaptcha" data-sitekey="<Your public key>"></div>
      </form>
    </div>
    {% endblock %}           

    NOTE 1: 在模板 register 中需要按照 reCAPTCHA 官档给出的方法将 <script src='https://www.google.com/recaptcha/api.js'></script><div class="g-recaptcha" data-sitekey="<Your public key>"></div> 应用到该模板中, 验证码才会生效.

    NOTE 2: 因为要访问 google 所以在测试是部署的时候都需要使用 VPN 会反向代理来完成.

    页面效果

    注册页面:
    这里写图片描述

    reCHPTCHA 验证码:
    这里写图片描述

    通过验证:
    这里写图片描述

    成功注册并跳转到登录页面:
    这里写图片描述

    登录失败:
    这里写图片描述

  • 相关阅读:
    调试JavaScript/VB Script脚本程序(ASP篇)
    成功接收来自Internet的邮件必须要做到的条件
    (转)Ext与.NET超完美整合 .NET开发者的超级优势
    如何防垃圾邮件用你的邮件服务器转发
    记录书籍名称
    GRE网站
    JAVA线程的缺陷
    【让这些电影给你“治病”】
    zoj题目分类
    Oracle to_char格式化函数
  • 原文地址:https://www.cnblogs.com/jmilkfan-fanguiju/p/10589858.html
Copyright © 2020-2023  润新知