Flask后端笔记
图书小例
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post">
{
{ form.csrf_token }}
{
{ form.author_name.label }}
<p>{
{form.author_name}}</p>
{% for msg in form.author_name.errors %}
<p>{
{msg}}</p>
{% endfor %}
{
{ form.book_name.label }}
<p>{
{form.book_name}}</p>
{% for msg in form.book_name.errors %}
<p>{
{msg}}</p>
{% endfor %}
{
{ form.submit }}
</form>
<hr/>
<ul>
{% for author in authors %}
<li>作者:{
{ author.name }}</li>
<ul>
{% for book in author.books%}
<li>书籍:{
{ book.name }}</li>
<!--<a href="javascript:;" book-id="{
{book.id}}">删除</a>-->
<a href="/delete_book?book_id={
{book.id}}">GET删除</a>
{% endfor %}
</ul>
{% endfor %}
</ul>
<script type="text/javascript" src="/static/js/jquery.min.js"></script>
<script>
// $("a").click(
// function () {
// var data = {
// book_id: $(this).attr("book-id")
// };
// // 将js中的对象转换为 json字符串
// var req_json = JSON.stringify(data);
// // $.post("/delete_book", req_json, function (resp) {
// // if (resp.code == 0) {
// // location.href = "/";
// // }
// // })
// $.ajax({
// url: "/delete_book", // 请求的后端url
// type: "post", // 请求方式
// data: req_json, // 向后端发送的请求体数据
// contentType: "application/json", // 指明向后端发送的数据格式
// dataType: "json", // 指明后端返回的数据格式
// success: function (resp) {
// if (resp.code == 0) {
// alert("oK");
// location.href = "/";
// }
// }
// })
// }
// )
</script>
</body>
</html>
视图
# coding:utf-8
from flask import Flask, render_template, request, redirect, url_for, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
app = Flask(__name__)
class Config(object):
# sqlalchemy的配置参数
SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/author_book_py04"
# 设置sqlalchemy自动更跟踪数据库
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY = "doiso7fd89fyd9^(fsd"
app.config.from_object(Config)
db = SQLAlchemy(app)
# 定义数据库的模型
class Author(db.Model):
"""作者"""
__tablename__ = "tbl_authors"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32), unique=True)
books = db.relationship("Book", backref="author")
class Book(db.Model):
"""书籍"""
__tablename__ = "tbl_books"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
author_id = db.Column(db.Integer, db.ForeignKey("tbl_authors.id"))
# 创建表单模型类
class AuthorBookForm(FlaskForm):
"""作者数据表单模型类"""
author_name = StringField(label=u"作者", validators=[DataRequired(u"作者必填")])
book_name = StringField(label=u"书籍", validators=[DataRequired(u"书籍必填")])
submit = SubmitField(label=u"保存")
@app.route("/", methods=["GET", "POST"])
def index():
# 创建表单对象
form = AuthorBookForm()
if form.validate_on_submit():
# 验证表单成功
# 提取表单数据
author_name = form.author_name.data
book_name = form.book_name.data
# 保存数据库
author = Author(name=author_name)
db.session.add(author)
db.session.commit()
book = Book(name=book_name, author_id=author.id)
# book = Book(name=book_name, author=author)
db.session.add(book)
db.session.commit()
# 查询数据库
author_li = Author.query.all()
return render_template("author_book.html", authors=author_li, form=form)
# post /delete_book json
# {"book_id":x}
# @app.route("/delete_book", methods=["POST"])
# def delete_book():
# """删除数据"""
# # 提取参数
# # 如果前端发送的请求体数据是json格式,get_json会解析成字典
# # get_json 要求前端传送的数据的Content-Type: application/json
# req_dict = request.get_json()
# book_id = req_dict.get("book_id")
#
# # 删除数据
# book = Book.query.get(book_id)
# db.session.delete(book)
# db.session.commit()
#
# # "Content-Type": "application/json"
# return jsonify(code=0, message="OK")
# /delete_book?book_id=xx
@app.route("/delete_book", methods=["GET"])
def delete_book():
"""删除数据"""
# 提取参数
book_id = request.args.get("book_id")
# 删除数据
book = Book.query.get(book_id)
db.session.delete(book)
db.session.commit()
return redirect(url_for("index"))
if __name__ == '__main__':
# db.drop_all()
# db.create_all()
# au_xi = Author(name='我吃西红柿')
# au_qian = Author(name='萧潜')
# au_san = Author(name='唐家三少')
# db.session.add_all([au_xi, au_qian, au_san])
# db.session.commit()
#
# bk_xi = Book(name='吞噬星空', author_id=au_xi.id)
# bk_xi2 = Book(name='寸芒', author_id=au_qian.id)
# bk_qian = Book(name='飘渺之旅', author_id=au_qian.id)
# bk_san = Book(name='冰火魔厨', author_id=au_san.id)
# db.session.add_all([bk_xi, bk_xi2, bk_qian, bk_san])
# db.session.commit()
app.run(debug=True)
数据库迁移
在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。
更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。
首先要在虚拟环境中安装Flask-Migrate。
pip install flask-migrate
文件:database.py
#coding=utf-8
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate,MigrateCommand
from flask_script import Shell,Manager
app = Flask(__name__)
manager = Manager(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]:3306/Flask_test'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
#第一个参数是Flask的实例,第二个参数是Sqlalchemy数据库实例
migrate = Migrate(app,db)
#manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令
manager.add_command('db',MigrateCommand)
#定义模型Role
class Role(db.Model):
# 定义表名
__tablename__ = 'roles'
# 定义列对象
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
def __repr__(self):
return 'Role:'.format(self.name)
#定义用户
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
def __repr__(self):
return 'User:'.format(self.username)
if __name__ == '__main__':
manager.run()
创建迁移仓库
#这个命令会创建migrations文件夹,所有迁移文件都放在里面。
python database.py db init
创建迁移脚本
自动创建迁移脚本有两个函数,upgrade()函数把迁移中的改动应用到数据库中。downgrade()函数则将改动删除。自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容。对比不一定完全正确,有可能会遗漏一些细节,需要进行检查
#创建自动迁移脚本
python database.py db migrate -m 'initial migration'
更新数据库
python database.py db upgrade
回退数据库
回退数据库时,需要指定回退版本号,由于版本号是随机字符串,为避免出错,建议先使用python database.py db history命令查看历史版本的具体版本号,然后复制具体版本号执行回退。
python database.py db downgrade 版本号
Flask—Mail
在开发过程中,很多应用程序都需要通过邮件提醒用户,Flask的扩展包Flask-Mail通过包装了Python内置的smtplib包,可以用在Flask程序中发送邮件。
Flask-Mail连接到简单邮件协议(Simple Mail Transfer Protocol,SMTP)服务器,并把邮件交给服务器发送。
如下示例,通过开启QQ邮箱SMTP服务设置,发送邮件。
from flask import Flask
from flask_mail import Mail, Message
app = Flask(__name__)
#配置邮件:服务器/端口/传输层安全协议/邮箱名/密码
app.config.update(
DEBUG = True,
MAIL_SERVER='smtp.qq.com',
MAIL_PROT=465,
MAIL_USE_TLS = True,
MAIL_USERNAME = '[email protected]',
MAIL_PASSWORD = 'goyubxohbtzfbidd',
)
mail = Mail(app)
@app.route('/')
def index():
# sender 发送方,recipients 接收方列表
msg = Message("This is a test ",sender='[email protected]', recipients=['[email protected]','[email protected]'])
#邮件内容
msg.body = "Flask test mail"
#发送邮件
mail.send(msg)
print "Mail sent"
return "Sent Succeed"
if __name__ == "__main__":
app.run()
不使用蓝图划分模块
将视图函数划分到不同.py文件
循环导入问题
分别取导入会导致无法完成构建
解决方法一
将导入放入函数中 函数被调入时再进行导入。#解决循环导入的办法:
让一方推迟导入
解决方法二
注释掉视图函数里的装饰器,main.py中调用装饰器的函数
蓝图Blueprint
为什么学习蓝图?
我们学习Flask框架,是从写单个文件,执行hello world开始的。我们在这单个文件中可以定义路由、视图函数、定义模型等等。但这显然存在一个问题:随着业务代码的增加,将所有代码都放在单个程序文件中,是非常不合适的。这不仅会让代码阅读变得困难,而且会给后期维护带来麻烦。
什么是蓝图?
蓝图:用于实现单个应用的视图、模板、静态文件的集合。
蓝图就是模块化处理的类。
简单来说,蓝图就是一个存储操作路由映射方法的容器,主要用来实现客户端请求和URL相互关联的功能。 在Flask中,使用蓝图可以帮助我们实现模块化应用的功能。
蓝图的运行机制:
蓝图是保存了一组将来可以在应用对象上执行的操作。注册路由就是一种操作,当在程序实例上调用route装饰器注册路由时,这个操作将修改对象的url_map路由映射列表。当我们在蓝图对象上调用route装饰器注册路由时,它只是在内部的一个延迟操作记录列表defered_functions中添加了一个项。当执行应用对象的 register_blueprint() 方法时,应用对象从蓝图对象的 defered_functions 列表中取出每一项,即调用应用对象的 add_url_rule() 方法,这将会修改程序实例的路由映射列表。
蓝图的使用
一、创建蓝图对象。
#Blueprint必须指定两个参数,admin表示蓝图的名称,__name__表示蓝图所在模块
# 1. 创建蓝图对象
admin = Blueprint('admin',__name__)
二、注册蓝图路由。
# 2. 创建蓝图的视图函数
@admin.route('/')
def admin_index():
return 'admin_index'
三、在程序实例中注册该蓝图。
# 3.注册蓝图
#第一个参数logins是蓝图对象,url_prefix参数默认值是根路由,如果指定,会在蓝图注册的路由url中添加前缀。访问路径为/admin/
# app.register_blueprint(app_cart)
app.register_blueprint(admin,url_prefix='/admin')
以目录的形式定义蓝图
新建模块包cart
__init.py
# coding:utf-8
from flask import Blueprint
# 创建一个蓝图 template_folder为模板路径 项目目录下的模板路径优先级 要高于 模块包里的
app_cart = Blueprint("carts", __name__, template_folder="templates")
# 在__init.py文件被执行的时候,把视图加载进来,让蓝图与应用程序知道有视图的存在
from.views import get_cart
view.py
# coding:utf-8
from . import app_cart
from flask import render_template
@app_cart.route("/get_cart")
def get_cart():
return render_template("cart.html")
main.py
# coding:utf-8
from flask import Flask
from orders import get_order
from users import register
from goods import app_goods
import cart
# from cart import app_cart
# 解决循环导入的办法:
# 让一方推迟导入
app = Flask(__name__)
# 为视图函数添加装饰器,将url路径与视图函数绑定到一起
app.route("/get_order")(get_order)
app.route("/register")(register)
# 注册蓝图
app.register_blueprint(app_goods, url_prefix="/goods")
# app.register_blueprint(app_cart, url_prefix="/carts")
app.register_blueprint(cart.app_cart, url_prefix="/carts")
@app.route("/")
def index():
return "index page"
if __name__ == '__main__':
print(app.url_map)
app.run()