Flask入门-图书管理

总结是学习的重要环节,能让掌握的知识更加稳固 —— 佚名

FlaskPython用于WEB开发的一个wheel,虽然在这类轮子中Django才是老大,但麻雀虽小,五脏俱全,Django能做的Flask也能胜任,从我自己的体验来讲Flask学习成本比Django更低,更适合用作web入门框架学习。

以下是我用Flask开发的十分简单的图书管理,本文旨在回顾本次开发,巩固Flask开发知识。

效果图

准备工作

IDE我选择了PyCharm,PyCharm的安装很简单,这里不再记录,本次开发使用了mysql,Mac OS下mysql可以用homebrew安装,不过听说是这样安装坑有点多,下面记录一下另一种安装方式,在官网下载安装的方法。
进入官网按以下步骤下载系统对应安装包

Flask入门-图书管理_第1张图片
1

Flask入门-图书管理_第2张图片
2
Flask入门-图书管理_第3张图片
3
Flask入门-图书管理_第4张图片
4

下载完之后按照安装引导步骤一直下一步即可,最后会自动生成一个root数据库,需要你设置密码,设置完后请记住设置的密码,登录数据库时需要使用。

以上都操作完之后,在系统偏好设置中可以看到多了一个MySQL了,点进去发现数据库已经自动开启了,

Flask入门-图书管理_第5张图片

接下来需要配置一下环境变量才能真正使用,打开终端

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即可。

Flask入门-图书管理_第6张图片

创建完后会生成这样一个目录结构,他们的大概作用如下


Flask入门-图书管理_第7张图片

接下来在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然后

Flask入门-图书管理_第8张图片

Flask入门-图书管理_第9张图片


新环境中部署依赖包可以采用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 %}

现在运行项目可以看到如下效果:


Flask入门-图书管理_第10张图片

数据成功查询到,并传递给了模板。

使用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



{{ form.csrf_token() }} {{ form.author.label }}{{ form.author }}
{{ form.book.label }}{{ form.book }}
{{ form.submit }}

{#先遍历作者,然后在作者里遍历书籍#}
    {% for author in authors %}
  • {{ author.name }}
    • {% for book in author.books %}
    • {{ book.name }}
    • {% else %}
    • {% endfor %}
    {% endfor %}

完成后刷新网页:


Flask入门-图书管理_第11张图片

通过表单添加数据

在填写完表单点击提交时可以通过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)

模板中显示闪现消息

{{ form.csrf_token() }} {{ form.author.label }}{{ form.author }}
{{ form.book.label }}{{ form.book }}
{{ form.submit }}
{# 显示消息闪现的内容 #} {% for message in get_flashed_messages() %} {{ message }}
{% endfor %}

完成后测试一下,当输入存在的信息时,会提示出错误


Flask入门-图书管理_第12张图片

数据删除

删除数据实质是先从数据库查询该条数据,如果有就删除,没有就提示相应错误,在删除完成后,需要将地址重定向到index,所以需要从Flask导入redirecturl_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

{#先遍历作者,然后在作者里遍历书籍#}
    {% for author in authors %}
  • {{ author.name }} 删除
    • {% for book in author.books %}
    • {{ book.name }} 删除
    • {% else %}
    • {% endfor %}
    {% endfor %}

到此,整个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



{{ form.csrf_token() }} {{ form.author.label }}{{ form.author }}
{{ form.book.label }}{{ form.book }}
{{ form.submit }}
{# 显示消息闪现的内容 #} {% for message in get_flashed_messages() %} {{ message }}
{% endfor %}

{#先遍历作者,然后在作者里遍历书籍#}
    {% for author in authors %}
  • {{ author.name }} 删除
    • {% for book in author.books %}
    • {{ book.name }} 删除
    • {% else %}
    • {% endfor %}
    {% endfor %}

你可能感兴趣的:(Flask入门-图书管理)