前言
最近在看Flask Web开发,感觉这本书写的真不错,里面教开发者如何一步步开发一个博客系统。刚开始看的时候,感觉完全看不懂,语法实在太灵活了。耐着性子看了一段时间,大概了解了开发流程,昨天完成了注册登陆发邮件功能,下面讲下我在学习过程中的心得和一些坑。
创建工程的一些配置
-
1.我是在mac下的pycharm进行开发,为了让html文件有Jinja2的提示,进入控制台来到工程目录,输入ls-a,可以看到一个叫.idea的隐藏文件,进入该文件,如下所示
- 2.用vim编辑sample.iml文件,输入命令vim sample.iml,在文本最后加上如下编辑
-
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() }}
{% 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 %}
- 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