总结是学习的重要环节,能让掌握的知识更加稳固 —— 佚名
Flask
是Python
用于WEB开发
的一个wheel,虽然在这类轮子中Django
才是老大,但麻雀虽小,五脏俱全,Django
能做的Flask
也能胜任,从我自己的体验来讲Flask
学习成本比Django
更低,更适合用作web入门框架学习。
以下是我用Flask
开发的十分简单的图书管理,本文旨在回顾本次开发,巩固Flask开发知识。
准备工作
IDE我选择了PyCharm
,PyCharm的安装很简单,这里不再记录,本次开发使用了mysql
,Mac OS下mysql可以用homebrew安装,不过听说是这样安装坑有点多,下面记录一下另一种安装方式,在官网下载安装的方法。
进入官网按以下步骤下载系统对应安装包
下载完之后按照安装引导步骤一直下一步即可,最后会自动生成一个root
数据库,需要你设置密码,设置完后请记住设置的密码,登录数据库时需要使用。
以上都操作完之后,在系统偏好设置
中可以看到多了一个MySQL
了,点进去发现数据库已经自动开启了,
接下来需要配置一下环境变量才能真正使用,打开终端
cd /usr/local/mysql/bin
ls
查看是否存在mysql
,如果有继续
vim ~/.bash_profile
在文件中添加一句PATH=$PATH:/usr/local/mysql/bin
,wq保存,然后
source ~/.bash_profile
重新打开终端,或者cd到根目录执行mysql -uroot -p
看是否会提示输入数据库登录密码,如果提示则表示配置成功
注:
用set password for 用户名@localhost=password('新密码')
命令修改安装时设置的root数据库密码时,遇到了错误ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
,采用
mysql>set password =password('新密码');
mysql>flush privileges;
依然错误,最后用ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码'
解决
PyCharm创建Flask项目
打开PyCharm
点击Create New Project
出现下面界面,选择新建Flask
指定位置后将untitled
修改为项目名称然后Create
即可。
创建完后会生成这样一个目录结构,他们的大概作用如下
接下来在templates
路径下创建一个books.html
文件,并修改路由返回该模板,返回模板需要导入render_template
,由于之后添加书籍涉及到表单提交,默认路由只支持GET请求,需要添加POST请求方式
app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/',methods=['GET', 'POST'])
def index():
return render_template('books.html')
if __name__ == '__main__':
app.run()
配置数据库
数据库使用SQLAlchemy
扩展,由于我的虚拟环境指向项目中的环境路径(可以指定到系统环境中),没有SQLAlchemy扩展,所以需要下载安装SQLAlchemy,方法如下:点击PyCharm->Preferences
然后
注
新环境中部署依赖包可以采用requirements
文件部署
1.在旧环境中用pip freeze > requirements.txt
导出依赖包及版本记录
2.在新环境中用pip install -r requirements.txt
安装依赖包
完成后from flask_sqlalchemy import SQLAlchemy
然后添加如下配置
# 数据库配置
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]/flask_books'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# 创建数据库
db = SQLAlchemy(app)
其中SQLALCHEMY_DATABASE_URI
是指定数据库位置,root:123456
分别对应root数据库名和root数据库登录密码,flask_books
是本项目需要使用的数据库名字。SQLALCHEMY_TRACK_MODIFICATIONS
是设置数据库跟踪,设置为False
,因为在未来的版本此配置会被废弃。
数据库配置完后,实质上flask_books
并未被创建,需要打开终端用mysql -uroot -p
登录数据库,然后
create database flask_books charset=utf8;
use flask_books;
到此,数据库配置完成,接下来创建书籍
和作者
模型
class Author(db.Model):
# 表名
__tablename__ = 'authors'
# 字段
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(16), unique=True)
# 关系引用
# books是给自己(Author模型)用的,author是给Book模型用的
books = db.relationship('Book', backref='author')
def __repr__(self):
return 'Author: %s' % self.name
# 书籍模型
class Book(db.Model):
__tablename__ = 'books'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(16), unique=True)
author_id = db.Column(db.Integer, db.ForeignKey('authors.id'))
def __repr__(self):
return 'Book: %s %s' % (self.name, self.author_id)
模型继承db.Model
,__name__
是表名db.Column
代表字段db.relationship
表示关系引用,即书籍有作者属性,一个作者可以写很多书,所以作者应该涵盖书籍。
在if __name__ == '__main__':
中添加几条数据
if __name__ == '__main__':
db.drop_all()
db.create_all()
au1 = Author(name='老王')
au2 = Author(name='老李')
au3 = Author(name='老刘')
db.session.add_all([au1, au2, au3])
db.session.commit()
bk1 = Book(name='老王回忆录', author_id=au1.id)
bk2 = Book(name='老王回忆录第二部', author_id=au1.id)
bk3 = Book(name='钢铁是怎样炼成的', author_id=au2.id)
bk4 = Book(name='坏蛋是怎样炼成的', author_id=au3.id)
bk5 = Book(name='你若安好便是晴天', author_id=au3.id)
db.session.add_all([bk1, bk2, bk3, bk4, bk5])
db.session.commit()
app.run()
注
问题1:每次执行py文件时首先执行if __name__ == '__main__':
中的代码,但是我遇到程序启动第一次会执行,之后再也不会执行,不明白原因,猜测是Flask并不是直接执行app.py文件,而是import后再调用,但是第一次为什么执行了又解释不清楚,很奇怪的一个bug。问题2:在用mysql插入数据时报错
No module named MYSQLdb
,此时安装扩展PyMySQL
,再添加import pymysql pymysql.install_as_MySQLdb()
即可解决
使用模板显示数据库查询数据
查询所有作者信息,让信息传递给模板,模板中按格式信息,一次for循环作者和书籍,修改index函数
def index():
# 查询所有作者信息并传递给模板
authors = Author.query.all()
return render_template('books.html', authors=authors)
books.html
Book
{#先遍历作者,然后在作者里遍历书籍#}
{% for author in authors %}
- {{ author.name }}
{% for book in author.books %}
- {{ book.name }}
{% else %}
- 无
{% endfor %}
{% endfor %}
现在运行项目可以看到如下效果:
数据成功查询到,并传递给了模板。
使用WTF显示表单
使用WTF需要安装Flask-WTF
,使用时from flask_wtf import FlaskForm
由于我们需要使用文本输入框和提交按钮,所以还需要from wtforms import StringField, SubmitField
,我们可以通过WTF对填入的数据进行验证,例如DataRequired
判断是否输入为空,equal_to
输入是否一致等,只需要类似from wtforms.validators import DataRequired
,使用WTF时默认是没有开启CSRF的,如果不开启会报错KeyError: 'A secret key is required to use CSRF.'
,此时需要给APP添加secret_key
并在模板中打开CSRF
添加secret_key
app.secret_key = 'tbPython'
创建自定义表单
# 自定义表单类
class AuthorForm(FlaskForm):
author = StringField('作者', validators=[DataRequired()])
book = StringField('书籍', validators=[DataRequired()])
submit = SubmitField('提交')
将表单传递给模板显示:
def index():
def index():
# 创建自定义表单类
author_form = AuthorForm()
# 查询所有作者信息并传递给模板
authors = Author.query.all()
return render_template('books.html', authors=authors, form=author_form)
修改模板
Book
{#先遍历作者,然后在作者里遍历书籍#}
{% for author in authors %}
- {{ author.name }}
{% for book in author.books %}
- {{ book.name }}
{% else %}
- 无
{% endfor %}
{% endfor %}
完成后刷新网页:
通过表单添加数据
在填写完表单点击提交
时可以通过WTF的validate_on_submit
方法实现验证,验证不通过则提示错误,验证通过后获取数据,判断作者是否存在,如果作者不存在,就直接添加数据。如果作者存在,则继续判断书籍是否存在,如果没有则添加,有则给出相应提示。为了让用户能看到提示消息,需要从Flask中引入flash
进行消息闪现。而且只需要在POST方式请求时才会进行消息闪现,因此还需要引入request
判断当前是什么请求方式。
def index():
# 创建自定义表单类
author_form = AuthorForm()
# 1.调用wtf的函数实现验证
if author_form.validate_on_submit():
# 2.验证通过后获取数据
author_name = author_form.author.data
book_name = author_form.book.data
# 3.判断作者是否存在
author = Author.query.filter_by(name=author_name).first()
# 4.如果作者存在
if author:
book = Book.query.filter_by(name=book_name).first()
if book:
flash('已存在同名书籍')
else:
try:
new_book = Book(name=book_name, author_id=author.id)
db.session.add(new_book)
db.session.commit()
except Exception as e:
print(e)
flash('添加书籍失败')
# 数据库回滚
db.session.rollback()
# 5.如果作者不存在
else:
try:
new_author = Author(name=author_name)
db.session.add(new_author)
db.session.commit()
new_book = Book(name=book_name, author_id=new_author.id)
db.session.add(new_book)
db.session.commit()
except Exception as e:
print(e)
flash('添加作者和书籍失败')
db.session.rollback()
else:
# 6.验证不通过则提示错误
if request.method == 'POST':
flash('参数错误')
# 查询所有作者信息并传递给模板
authors = Author.query.all()
return render_template('books.html', authors=authors, form=author_form)
模板中显示闪现消息
完成后测试一下,当输入存在的信息时,会提示出错误
数据删除
删除数据实质是先从数据库查询该条数据,如果有就删除,没有就提示相应错误,在删除完成后,需要将地址重定向到index
,所以需要从Flask导入redirect
和url_for
。
分别定义删除书籍和作者的路由,路由需要接受参数,知道当前需要删除哪一本书或者哪一个作者。
# 删除书籍,路由接受参数
@app.route('/delete_book/')
def delete_book(book_id):
# 查询数据库是否有该id的书,如果有就删除,没有就提示错误
book = Book.query.get(book_id)
if book:
try:
db.session.delete(book)
db.session.commit()
except Exception as e:
print(e)
flash('删除书籍失败')
db.session.rollback()
else:
flash('书籍找不到')
# 如何返回当前网址 -->重定向
# return redirect('www.baidu.com')
# redirect:重定向,需要传入网址/路由地址
# return redirect('/')
# url_for('index'):需要传入视图函数名,返回该视图函数对应的路由地址
return redirect(url_for('index'))
# 删除作者,路由接受参数
@app.route('/delete_author/')
def delete_author(author_id):
# 查询数据库是否有该作者,如果有,先删书,再删作者
author = Author.query.get(author_id)
if author:
try:
# 先删书,查询之后直接删除
Book.query.filter_by(author_id=author.id).delete()
# 删除作者
db.session.delete(author)
db.session.commit()
except Exception as e:
print(e)
flash('删除作者失败')
db.session.rollback()
else:
flash('作者找不到')
return redirect(url_for('index'))
在模板中添加删除地址并链接需要删除的id
{#先遍历作者,然后在作者里遍历书籍#}
到此,整个Demo完成!
附
app.py
from flask import Flask, render_template, request, flash, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
import pymysql
pymysql.install_as_MySQLdb()
app = Flask(__name__)
'''
1. 配置数据库
a.导入SQLAlchemy扩展
b.创建db对象,并配置参数
c.用终端创建数据库:
mysql -uroot -p
create database flask_books charset=utf8;
use flask_books
2. 添加书和作者模型
a.模型继承db.Model
b.__name__:表名
c.db.Column:字段
d.db.relationship:关系引用
3. 添加数据
4. 使用模板显示数据库查询数据
a.查询所有作者信息,让信息传递给模板
b.模板中按格式信息,一次for循环作者和书籍(作者获取书籍,用的是关系引用)
5. 使用WTF显示表单
a.自定义表单类
b.模板中显示
c.secret_key/编码/csrf_token
6. 实现相关的增删逻辑
a.增加数据
b.删除书籍 --> 网页中删除 --> 点击给需要发送书籍的id的路由 --> 路由接受参数
url_for的使用、for else的使用、redirect的使用
c.删除作者
'''
# 数据库配置
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]/flask_books'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.secret_key = 'tbPython'
# 创建数据库
db = SQLAlchemy(app)
# 定义书和作者模型
# 作者模型
class Author(db.Model):
# 表名
__tablename__ = 'authors'
# 字段
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(16), unique=True)
# 关系引用
# books是给自己(Author模型)用的,author是给Book模型用的
books = db.relationship('Book', backref='author')
def __repr__(self):
return 'Author: %s' % self.name
# 书籍模型
class Book(db.Model):
__tablename__ = 'books'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(16), unique=True)
author_id = db.Column(db.Integer, db.ForeignKey('authors.id'))
def __repr__(self):
return 'Book: %s %s' % (self.name, self.author_id)
# 自定义表单类
class AuthorForm(FlaskForm):
author = StringField('作者', validators=[DataRequired()])
book = StringField('书籍', validators=[DataRequired()])
submit = SubmitField('提交')
# 删除书籍,路由接受参数
@app.route('/delete_book/')
def delete_book(book_id):
# 查询数据库是否有该id的书,如果有就删除,没有就提示错误
book = Book.query.get(book_id)
if book:
try:
db.session.delete(book)
db.session.commit()
except Exception as e:
print(e)
flash('删除书籍失败')
db.session.rollback()
else:
flash('书籍找不到')
# 如何返回当前网址 -->重定向
# return redirect('www.baidu.com')
# redirect:重定向,需要传入网址/路由地址
# return redirect('/')
# url_for('index'):需要传入视图函数名,返回该视图函数对应的路由地址
return redirect(url_for('index'))
# 删除作者,路由接受参数t
@app.route('/delete_author/')
def delete_author(author_id):
# 查询数据库是否有该作者,如果有,先删书,再删作者
author = Author.query.get(author_id)
if author:
try:
# 先删书,查询之后直接删除
Book.query.filter_by(author_id=author.id).delete()
# 删除作者
db.session.delete(author)
db.session.commit()
except Exception as e:
print(e)
flash('删除作者失败')
db.session.rollback()
else:
flash('作者找不到')
return redirect(url_for('index'))
@app.route('/', methods=['GET', 'POST'])
def index():
# 创建自定义表单类
author_form = AuthorForm()
'''
验证逻辑:
1.调用wtf的函数实现验证
2.验证通过后获取数据
3.判断作者是否存在
4.如果作者存在,判断书籍是否存在,如果没有重复数据则添加,有则提示
5.如果作者不存在,则添加数据
6.验证不通过则提示错误
'''
# 1.调用wtf的函数实现验证
if author_form.validate_on_submit():
# 2.验证通过后获取数据
author_name = author_form.author.data
book_name = author_form.book.data
# 3.判断作者是否存在
author = Author.query.filter_by(name=author_name).first()
# 4.如果作者存在
if author:
book = Book.query.filter_by(name=book_name).first()
if book:
flash('已存在同名书籍')
else:
try:
new_book = Book(name=book_name, author_id=author.id)
db.session.add(new_book)
db.session.commit()
except Exception as e:
print(e)
flash('添加书籍失败')
# 数据库回滚
db.session.rollback()
# 5.如果作者不存在
else:
try:
new_author = Author(name=author_name)
db.session.add(new_author)
db.session.commit()
new_book = Book(name=book_name, author_id=new_author.id)
db.session.add(new_book)
db.session.commit()
except Exception as e:
print(e)
flash('添加作者和书籍失败')
db.session.rollback()
else:
# 6.验证不通过则提示错误
if request.method == 'POST':
flash('参数错误')
# 查询所有作者信息并传递给模板
authors = Author.query.all()
return render_template('books.html', authors=authors, form=author_form)
def creat_table():
db.drop_all()
db.create_all()
au1 = Author(name='老王')
au2 = Author(name='老李')
au3 = Author(name='老刘')
db.session.add_all([au1, au2, au3])
db.session.commit()
bk1 = Book(name='老王回忆录', author_id=au1.id)
bk2 = Book(name='老王回忆录第二部', author_id=au1.id)
bk3 = Book(name='钢铁是怎样炼成的', author_id=au2.id)
bk4 = Book(name='坏蛋是怎样炼成的', author_id=au3.id)
bk5 = Book(name='你若安好便是晴天', author_id=au3.id)
db.session.add_all([bk1, bk2, bk3, bk4, bk5])
db.session.commit()
if __name__ == '__main__':
creat_table()
app.run()
book.html
Books
{#先遍历作者,然后在作者里遍历书籍#}