更新于 

Flask Project

项目结构

  • blueprint/views 存放各个路由映射模块
  • static 存放静态文件
  • templates 存放jinjia模板
  • config 存放配置文件信息
    • config.py app配置信息
    • db_config.py 数据库配置信息
  • app.py 入口文件
  • decorator.py 装饰器文件
  • exts.py 扩展文件
  • models.py ORM文件

功能简述

  • 用户注册后生成账号
  • 登录账号
  • 参与心理测试
  • 心理测试结果发送到用户邮箱

数据库

数据库配置

数据库配置

将config信息单独提取到config文件下进行配置:

config/config.py app配置
1
SECRET_KEY = 'oh its secrect key' # flask.session需要用到的密钥
config/db_config.py 数据库配置
1
2
3
4
5
6
HOSTNAME = "127.0.0.1"
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root123'
DATABASE = 'psychological_test'
SQLALCHEMY_DATABASE_URL = f"mysql+pymysql://{USERNAME}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
app.py 配置信息注入
1
2
3
from config import db_config, config
app.config.from_object(db_config) # 数据库配置
app.config.from_object(config) # app配置

exts.js

如果我们把db的创建放在app文件中,如下代码:

app.py
1
2
3
db = SQLAlchemy()
db.init_app(app)
migrate = Migrate(app, db)

同时,如果想migrate在进行数据迁移时注意到ORM模型的更新,就必须要在app.py中引用模型类:

app.py
1
from models import UserModel, EmailModel, TestModel, OptionModel, ResultModel

在models.py中,为了使ORM模型继承db.Model类,也必须将db引入:

app.py
1
2
3
from app import db
class UserModel(db.Model):
pass

这时,引用循环的问题就出现了:

1
ImportError: cannot import name 'XXX' from partially initialized module 'XXX' (most likely due to a circular import)

db的更新与从models中引用来的模型相关,
而models中的模型又与db的Model类相关,
此处需要引入扩展文件exts.py
专门用于创建一些app需要用到的扩展对象

exts.py
1
2
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

ORM映射

ORM

EmailModel

OptionModel

Blueprint视图拆分

Blueprint蓝图 color

Blueprint可以为路由、视图、静态文件等需要url映射的功能提供便捷的模块拆分方法

在blueprint文件夹下创建视图模块文件:

blueprint/index.py
1
2
3
4
5
from flask import Blueprint
index = Blueprint('index', __name__, url_prefix="")
@index.route('/')
def welcome():
return ''

blueprint文件下的蓝图对象需要在app中注册

app.py
1
2
from blueprint.index import index
app.register_blueprint(index)

wtforms 表单验证

wtforms主要用于验证前端提交的数据是否符合规范
flask-wtf是flask对wtforms的封装

wtforms的使用步骤如下:

  • 定义表单
    • 注意表单项name字段要和验证类中的验证属性名对应
  • 定义验证类
    • 一定要继承自wtforms.Form
    • 验证属性
    • 验证方法
      • 需要用validate_验证属性名的格式为方法命名
      • 方法必须包含self和field两个参数,用field访问获取验证属性值,用self访问其他字段
  • 使用验证类
表单定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<form action="" method="post">
<div class="form-item">
<p>LIAME</p>
<input type="text" name="email">
</div>
<div class="form-item">
<p>EMAN</p>
<input type="text" name="name">
</div>
<div class="form-item">
<p>NOITATNACNI</p>
<input type="password" name="password">
</div>
<input type="submit" value="TCEJNI NOITATNACNI">
</form>
wtforms类定义
1
2
3
4
5
6
7
8
9
10
11
12
13
import wtforms
from wtforms.validators import Email, Length
from models import UserModel
class RegisterForm(wtforms.Form):
email = wtforms.StringField(validators=[Email(message='strange, something seems going wrong')])
name = wtforms.StringField(validators=[Length(min=1,max=20,message='strange, something seems going wrong')])
password = wtforms.StringField(validators=[Length(min=4,max=20,message='strange, something seems going wrong')])
def validate_emial(self, field):
email = field.data
user = UserModel.query.filter_by(email=email).first()
if user:
raise wtforms.ValidationError(message='strange, something seems going wrong')

使用验证类
1
2
3
4
5
6
7
8
9
10
11
from .forms import RegisterForm
@index.route("/register", methods=["POST", "GET"])
def register():
if request.method == 'POST':
form = RegisterForm(request.form)
if form.validate():
...
return ...
else:
print(form.errors)
return ...

werkzeug

WSGL

WSGL Web Server Gateway Interface

WSGL是Python Web应用程序和Web服务器之间的标准接口

Werkzeug是python提供的WSGL库

使用werkzeug.security进行数据加密
  • generate_password_hash 加密
  • check_password_hash 比较加密数据和未加密数据是否包含相同信息
加密
1
2
3
4
5
6
7
8
9
from werkzeug.security import generate_password_hash, check_password_hash
def register():
....
password = generate_password_hash(form.password.data)
user = UserMode(
name=form.name.data,
email=form.email.data,
password=password
)
比较
1
2
3
4
5
6
7
@index.route("/login", methods=["POST", "GET"])
def login():
...
user = UserModel.query.filter_by(email=form['email']).first()
if user and check_password_hash(user.password, form["password"]):
session["user_id"] = user.id
return ...

钩子函数

Flask中的钩子函数使用装饰器的形式定义

一些常用的钩子函数
  • before_request 请求之前、视图函数执行之前
  • after_request 请求之后、响应发送给用户之前调用
  • teardown_request 请求之后、响应发送给用户之后调用
  • before_first_request 应用启动时/处理第一个请求之前调用
  • after_request 应用关闭时/处理最后一个请求之后调用
before_request使用
1
2
3
4
5
6
7
8
9
# 请求之前全局存入用户信息
@app.before_request
def before_request():
user_id = session.get('user_id')
if user_id:
user = UserModel.query.filter_by(id=user_id).first()
setattr(g, 'user', user)
else:
setattr(g, 'user', None)

Decorator装饰器

装饰器

装饰器实际上是对函数的包装操作的语法糖,
装饰器函数接收一个函数参数,并返回一个新的函数作为输出,
接收的参数类似于callback回调函数

flask提供了一些常用的装饰器:如

@app.errorhandler(error_code) 定义错误处理的装饰器

1
2
3
@app.errorhandler(404)
def page_not_found(error):
return 'Page Not Fount', 404

@app.context_processor 上下文处理器

存放在上下文处理器中的变量值能在所有模板中获取到

1
2
3
@app.context_processor
def my_context_processor():
return {"user":g.user}
自定义装饰器
自定义装饰器方法
1
2
3
4
5
6
def my_decorator(func): # func即被包装的函数
@wraps(func) # 用来保留函数信息
def inner(): # 对func进行包装
pass
return inner # 最终调用实际是inner函数

进入路径映射函数之前对用户信息进行检测的装饰器
1

session

session对象用于在ClientServer之前存储用户会话数据

默认情况下,Flask使用签名的cookie来实现会话存储(浏览器端),
而不是存储在服务器端

浏览器端签过名的cookie数据
浏览器端签过名的cookie数据

为了进行cookie加密,需要配置会话密钥

密钥配置
1
app.secret_key = 'your_secret_key'
session的常见操作

新增/修改session值

1
session['key'] = value

获取session数据

1
temp = session.get('key')

删除session数据

1
2
session.pop('key', None) # 删除键为key的会话数据
session.clear() # 清空所有会话数据

SMTP邮件服务

如何在flask项目中发送邮件给指定邮箱
  1. 获取POP3/SMTP/IMAP服务授权码
  2. 初始化邮箱连接对象
  3. 配置邮件服务参数
  4. 在入口文件app.py中注入邮件服务配置
  5. 发送邮件

和数据库连接对象一样,邮箱连接对象一般也放在扩展文件exts.py中,
需要使用到flask_mail扩展库

Flask-Mail

Flask-Mail支持在flask应用中方便的发送电子邮件,
包括如下功能:

  • 简单文本邮件发送
  • jinjia2模板发送html邮件
  • MIME附件(图片/文档)
  • 异步发送邮件
exts.py
1
2
from flask_mail import Mail
mail = Mail()
mail_config.py
1
2
3
4
5
6
7
8
9
10
11
12
# 邮件服务的服务器地址
MAIL_SERVER = 'smtp.163.com'
# 使用SSL协议加密
MAIL_USE_SSL = True
# SSL端口号
MAIL_PORT = 465
# 邮箱账号
MAIL_USERNAME = "piuyixiu@163.com"
# 开启SMTP服务时生成的授权码
MAIL_PASSWORD = "..."
# 邮箱账号
MAIL_DEFAULT_SENDER = "piuyixiu@163.com"
app.py
1
2
3
4
5
from config import mail_config
from exts import mail

app.config.from_object(mail_config)
mail.init_app(app)

发送时会使用到flask_mail.Message

Message邮件常用属性
  • sender 发送者
  • recipients 接收者
  • subject 主题
  • body 纯文本邮件内容
  • html HTML邮件内容
  • attach 添加附件

添加附件时需注意,代码中读取附件文件的路径是相对于执行该应用程序的当前工作目录的,
即项目主入口app.py所在的文件路径

1
2
3
4
with open('static/img/makima.png','rb') as f:
data = f.read()
message.attach('Makima.png', 'image/png', data)
mail.send(message)
1
2
3
4
5
6
7
8
9
10
11
12
13
from exts import mail
from flask_mail import Message

@app.route('/send_to_email)
def send_to_email():
email = request.args.get('email')
message = Message(
subject='邮件主题',
recipients=[email],
body='邮件内容'
)
mail.send(message)
return jsonify(code=200, message='send success', data=None)