Python Flask开发之注册登陆功能

前言

最近在看Flask Web开发,感觉这本书写的真不错,里面教开发者如何一步步开发一个博客系统。刚开始看的时候,感觉完全看不懂,语法实在太灵活了。耐着性子看了一段时间,大概了解了开发流程,昨天完成了注册登陆发邮件功能,下面讲下我在学习过程中的心得和一些坑。

创建工程的一些配置

  • 1.我是在mac下的pycharm进行开发,为了让html文件有Jinja2的提示,进入控制台来到工程目录,输入ls-a,可以看到一个叫.idea的隐藏文件,进入该文件,如下所示


  • 2.用vim编辑sample.iml文件,输入命令vim sample.iml,在文本最后加上如下编辑
</component>
  <component name="TemplatesService">
    <option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
    <option name="TEMPLATE_FOLDERS">
      <list>
        <option value="$MODULE_DIR$/templates" />
      </list>
    </option>
  </component>
  • 3.这样就可以在pycharm的html文件中有Jinja2的提示,点击pycharm最下角的医生头标,可以看到下面这个样子就配置成功了。


  • 4.pycharm里可能有波浪线感觉很别扭,选择上面的Inspection到Syntax波浪线就消失了。

程序结构组织

  • 1.在flask开发中使用蓝图对程序进行重构,我的蓝图结构为
|-sample
   |-app
     |-auth
       |-__init__.py
       |- forms.py
       |- views.py
     |-main
       |-__init__.py
       |- views.py
     |-static
     |-templates
     |-__init__.py
     |- config
     |- db.sqlite
     |- email.py
     |- models.py
   |- doc
   |- migrations
   |- test
   |- venv
   |- manager.py
   |- requirements.txt
  • 2.把应用程序都放在app包里,app里的auth包专门处理用户登陆注册这一块,app里的main包放其余的路由视图函数,包括错误(404,500)视图函数。在auth和main中的__init__.py文件中创建蓝图。
  • 3.static文件夹放静态文件,像css和js文件。templates 文件夹中模板文件,就是用于视图函数加载的html文件。
  • 4.在与app同层的__init__ 函数用于创建初始变量,并创建工厂函数。
  • 5.config文件中用于存储配置参数。
  • 6.migrations是数据库迁移所创建中的文件夹
  • 7.test文件夹里可做单元测试
  • 8.venv是创建工程中所创建的虚拟环境,我是利用pycharm进行操作的。
  • 9.manager.py文件中调用上面__init__ 文件中的工厂函数,编写这个文件是为了调试方便。requirements.txt文件中是所有工程所依赖的库文件,可以在该文件上编辑需要安装的三方库,然后在命令台先激活虚拟坏境. venv/bin/activate ,然后执行pip freeze > requirements.txt 安装。

Web首页搭建

  • 1.效果如下:


  • 2.首页的模板index.html 是继承base.html,而base.html 是继承flask-bootstrap库中bootstrap/base.html,首先安装flask-bootstrap,在base.html 利用Jinja2的{% block xxx%} {% end block %} 语法重定义了几个块
  • {% extends 'bootstrap/base.html' %} 继承基模板
  • 重定义基模板中的head块
  {% block head %}
        {{ super() }}
        {% include 'include/_header.html' %}
   {% endblock %}
  • 重定义基模板中的style块, 这里一定要先初始化父类,下面这个bootstrap链接可以去百度下bootstrap的cdn,网上资源有很多,有各种各样的样式。
{% block styles %}
    {{ super() }}
   <link rel="stylesheet"
         href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.5/cerulean/bootstrap.min.css">
   {% endblock %}
  • 重定义基模板的navbar块,_navbar_html 就是上面页面的导航栏
{% block navbar %}
      {% include 'include/_navbar.html' %}
      {# {{ nav.top.render() }} #}
   {% endblock %}
  • 3.首页的视图函数很简单,只要加载这个index.html 即可
@main.route('/')
def index():
    return render_template('index.html')

注册页面

  • 1.效果如下:


  • 2.采用flask-wtf模块创建表单,每个Web表单都由一个继承自FlaskForm的类表示,这个类定义表单中的一组字段,每个字段都由一个对象表示,例如注册表单

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField, BooleanField
from wtforms.validators import DataRequired, Email, Length, EqualTo
#注册表
class RegisterForm(FlaskForm):
    email = StringField(label=u'邮箱地址',validators=[DataRequired(), Length(1,64), Email()])
    username = StringField(label=u'用户名',validators=[DataRequired(), Length(1,64)])
    password = PasswordField(label=u'密码',validators=[DataRequired(), EqualTo('password2', message=u'密码必须相同')])
    password2 = PasswordField(label=u'确认密码',validators=[DataRequired()])
    submit = SubmitField(label=u'马上注册')
  • 3.将表单渲染成HTML,表单字段是可以调用的,在模板中调用会渲染成HTML。例如将RegisterForm实例对象通过参数form传入模板,flask-bootstrap提供了一个函数可以使用Bootstrap中预先定义好的表单样式渲染整个Flask-WTF表单。
{% import 'bootstrap/wtf.html' as wtf %}
     <form method="post">
            {{ wtf.quick_form(form) }}
     </form>
  • 4.在注册成功后,要将用户的邮箱、密码保存到数据库中,并发送一份确认邮件到注册的邮箱中,数据库就是程序结构组织中的db.sqlite文件,下面介绍下如何使用数据库和发送邮件。

数据库的使用

  • 1.使用flask-sqlalchemy,在ORM中,模型一般是一个Python类,类中的属性对应数据表中的列,在models.py文件中创建了两个类,用户类和角色类,角色类在这里暂时用不到,先与对象类建立一对多的关系
  • 角色类模型

    class Role(db.Model):
     __tablename__ = 'roles'
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.String, nullable=True)
     users = db.relationship('User', backref='itsrole')  # Role对象引用users,User对象引用itsrole,是隐形存在的属性
    
  • 用户类模型

class User(UserMixin, db.Model):
   __tablename__ = 'users'
   id = db.Column(db.Integer, primary_key=True)
   username = db.Column(db.String, nullable=True)
   password = db.Column(db.String, nullable=True)
   email = db.Column(db.String, nullable=True, unique=True)     # 新建一个邮箱字段
   role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
   password_hash = db.Column(db.String, nullable=True)          # 模型中加入密码散列值
   confirmed = db.Column(db.Boolean, default=False)             # 邮箱令牌是否点击

为了保护用户输入的密码,User表中的password_hash字段是密码散列化的结果,这个后面会提到。

  • 2.上面一对多的关系是如何构建的可以看下我博客中的sqlalchemy文章,里面介绍了数据库各种映射关系,下面创建下数据库。在控制台输入命令python manager.py shell 进入交互式坏境(首先要激活虚拟venv坏境)
>>>from app import db
>>>db.drop_all()         #删除数据库中的表
>>>db.create_all()       #创建数据库中的表,如果没有数据库文件会自动创建
  • 3.pycharm提供了很好用的数据库可视化工具



    点击右边的Database可以导入一个数据库文件,图中已经导入了db.sqlite文件

  • 4.关于python的sqlalchemy可以看下我博客中的sqlalchemy文章,里面页介绍了一些基本的数据库操作

用户密码的保护

  • 1.数据库User表的字段password_hash就是用于存储散列化的密码,使用werkzeug模块实现,每次注册的时候创建了一个用户对象,利用python的@property和@setter属性,在创建对象对密码赋值时实现密码散列化
@property                 # 试图读取password的值,返回错误, 因为password已经不可能恢复了
   def password(self):
       raise AttributeError('password is not a readable attribute')

   @password.setter      # 设置password属性的值时,赋值函数会调用generate_password_hash函数
   def password(self, password):
       self.password_hash = generate_password_hash(password)

如果试图读取用户密码就会抛出一个错误,在注册调用user = User(username=form.username.data, password=form.password.data, email=form.email.data)时会自动来到setter函数,利用werkzeug的generate_password_hash产生散列化密码。

邮件的发送

  • 1.利用flask-mail模块完成邮件的发送,重要的是你的邮箱要开启smtp服务,gmail和国内的163和qq邮箱有不同,这里以我的163邮箱做下解释。

  • 2.邮箱开启smtp服务,开启的时候会让你设置一个密码,这个密码就是程序里面需要设置的MAIL_PASSWORD,这个密码不需要是你的登陆密码


  • 3.程序中配置坏境变量,如下所示:

    这里写图片描述

    163邮箱的端口号为465,注意开启的是TLS协议,国内邮箱使用SSL会失败的,国外的邮箱例如gmail 是SSL协议。
    从坏境变量中导入邮箱账号密码export MAIL_USERNAME=export MAIL_PASSWORD= , 导入后可用echo MAIL_USERNAME 查看邮箱名,echo MAIL_PASSWORD 查看密码。

确认账户

  • 1.在用户注册完成后,会往邮箱发送一份确认邮件,新用户的状态是待确认状态,按照邮箱的说明操作后,状态变为确认。往往邮箱要求用户点击一个特殊的URL 链接。

  • 2.我们采用的确认链接 方式为为http://www.example.com/auth/confirm/id, 其中id为数据库中用户的id,通过使用itsdangerous包对id进行加密处理。导入相关包from itsdangerous import TimedJSONWebSignatureSerializer as Serializer代码如下:


    itsdangerous提供了多种生成令牌的方法,TimedJSONWebSignatureSerializer类生成具有过期时间的JSON Web签名,这个类的构造函数接收的参数为一个钥匙,使用SECRET_KEY设置。expires_in设置令牌过期时间,单位为秒。

  • 3.上面已经生成了加密签名,解码签名采用loads()方法,唯一的参数就是令牌字符串,这个方法会检验签名和过期时间,如果通过则返回原始id,异常或过期则抛出异常。解码代码如下:



    解码成功后,用户的确认字段设为True

  • 4.发送确认邮件,当前的/register路由把用户添加到数据库中,会重定向到/index,重定向之前这个路由要发送确认邮件,把产生的令牌token传入模板,代码如下:


  • 5.点击确认邮件的链接后,来到/confirm/token这个链接,首先要保证用户已登陆,flask_login 包中的login_required 就是用来保证用户已经登陆的。

Github链接

基于flask的个人博客系统代码我已经上传到github了,链接为https://github.com/happyte/flask-blog

推荐阅读更多精彩内容