Python全栈(七)Flask框架之10.ORM插件、Script和Migrate

一、Flask-SQLAlchemy插件

之前使用SQLAlchemy都是独立于Flask的,如果需要在Flask中使用SQLAlchemy,可以使用flask-sqlalchemy插件,它是对SQLAlchemy的一个简单封装,以便在flask中更简单方便地使用sqlalchemy。
切换到虚拟环境使用命令pip install flask-sqlalchemy安装即可。

使用flask-sqlalchemy创建数据库模型(创建flask_sqlalchemy_test.py文件)如下:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# 数据库连接配置
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root'
DATABASE = 'flask_sqlalchemy'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# 数据库初始化
db = SQLAlchemy(app)


# ORM类定义
class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(30))


class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(50))

    uid = db.Column(db.Integer, db.ForeignKey('users.id'))

    author = db.relationship('User', backref='articles')


# 映射模型到数据库表
db.create_all()


@app.route('/')
def index():
    return '首页'


if __name__ == '__main__':
    app.run(debug=True)

此时查询数据库如下:

show tables;

打印:

+----------------------------+     
| Tables_in_flask_sqlalchemy |     
+----------------------------+     
| article                    |     
| users                      |     
+----------------------------+     
2 rows in set (0.00 sec)           
                                   

显然,表已经创建成功。
使用Flask-SQLAlchemy时,所有的类都继承自db.Model,并且所有的Column和数据类型也都成为db的一个属性;
在定义模型时也可以不指定 __tablename__属性,不指定时会默认以模型类名的小写形式作为表名。

插入并查询数据测试如下:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# 数据库连接配置
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root'
DATABASE = 'flask_sqlalchemy'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)


class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(30))

    def __repr__(self):
        return 'User(id: {}, name: {})'.format(self.id, self.name)


class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(50))

    uid = db.Column(db.Integer, db.ForeignKey('users.id'))

    author = db.relationship('User', backref='articles')

    def __repr__(self):
        return 'User(id: {}, title: {})'.format(self.id, self.title)


db.drop_all()
db.create_all()

# 添加数据
user = User(name='Corley')
article = Article(title='Python')
article.author = user
db.session.add(article)
db.session.commit()

# 查询数据
user = User.query.all()
print(user)

@app.route('/')
def index():
    return '首页'


if __name__ == '__main__':
    app.run(debug=True)

打印:

[User(id: 1, name: Corley)]
 * Serving Flask app "flask_sqlalchemy_test" (lazy loading)
 * Environment: production

显然,已经成功插入数据并查询到。
添加数据时还是使用session,同时session成了db的属性;
查询数据是通过Model.query的方式进行查询。

插入多条数据并排序查询测试:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# 数据库连接配置
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root'
DATABASE = 'flask_sqlalchemy'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)


class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(30))

    def __repr__(self):
        return 'User(id: {}, name: {})'.format(self.id, self.name)


class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(50))

    uid = db.Column(db.Integer, db.ForeignKey('users.id'))

    author = db.relationship('User', backref='articles')

    def __repr__(self):
        return 'User(id: {}, title: {})'.format(self.id, self.title)


@app.route('/')
def index():
    return '首页'


if __name__ == '__main__':
    # app.run(debug=True)

    db.drop_all()
    db.create_all()

    # 添加数据
    user1 = User(name='Corley')
    user2 = User(name='John')
    article1 = Article(title='Python')
    article2 = Article(title='Flask')
    article1.author = user1
    article2.author = user2

    db.session.add(article1)
    db.session.add(article2)
    db.session.commit()

    # 查询数据
    user = User.query.order_by(User.name.desc()).all()
    print(user)

打印:

[User(id: 2, name: John), User(id: 1, name: Corley)]

SQLALchemy的原生查询方式也可以使用,如下:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# 数据库连接配置
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root'
DATABASE = 'flask_sqlalchemy'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)


class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(30))

    def __repr__(self):
        return 'User(id: {}, name: {})'.format(self.id, self.name)


class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(50))

    uid = db.Column(db.Integer, db.ForeignKey('users.id'))

    author = db.relationship('User', backref='articles')

    def __repr__(self):
        return 'User(id: {}, title: {})'.format(self.id, self.title)


@app.route('/')
def index():
    return '首页'


if __name__ == '__main__':
    # app.run(debug=True)

    db.drop_all()
    db.create_all()

    # 添加数据
    user1 = User(name='Corley')
    user2 = User(name='John')
    article1 = Article(title='Python')
    article2 = Article(title='Flask')
    article1.author = user1
    article2.author = user2

    db.session.add(article1)
    db.session.add(article2)
    db.session.commit()

    # 查询数据
    # users = User.query.order_by(User.name.desc()).all()
    users = db.session.query(User).all()
    print(users)

打印:

[User(id: 1, name: Corley), User(id: 2, name: John)]

显然,也是能查询到数据的,第一种方式更简单易用。

删除数据测试:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# 数据库连接配置
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root'
DATABASE = 'flask_sqlalchemy'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)


class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(30))

    def __repr__(self):
        return 'User(id: {}, name: {})'.format(self.id, self.name)


class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(50))

    uid = db.Column(db.Integer, db.ForeignKey('users.id'))

    author = db.relationship('User', backref='articles')

    def __repr__(self):
        return 'User(id: {}, title: {})'.format(self.id, self.title)


@app.route('/')
def index():
    return '首页'


if __name__ == '__main__':
    # app.run(debug=True)

    db.drop_all()
    db.create_all()

    # 添加数据
    user1 = User(name='Corley')
    user2 = User(name='John')
    article1 = Article(title='Python')
    article2 = Article(title='Flask')
    article1.author = user1
    article2.author = user2

    db.session.add(article1)
    db.session.add(article2)
    db.session.commit()

    # 删除数据
    user = User.query.filter(User.id == 2).first()
    db.session.delete(user)
    db.session.commit()

运行后查询数据库,显然数据已经被删除。

二、Flask-Script

1.flask-script的基本使用

Flask-Script的作用是可以通过命令行的形式来操作Flask,如通过命令运行开发版本的服务器、设置数据库、定时任务等。
在虚拟环境中安装flask-script的命令是pip install flask-script

使用装饰器可以定义命令,创建manage.py如下:

from flask_script import Manager
from flask_sqlalchemy_test import app

manage = Manager(app)


@manage.command
def index():
    print('Hello Flask')


if __name__ == '__main__':
    manage.run()

此时在命令行中切换到当前目录,执行命令python manage.py index,打印:

Hello Flask

显然,通过命令行执行了manage.py中的index()函数。
通常,把脚本命令代码放在manage.py文件中。

除了使用装饰器,还可以继承自Command类,如下:

from flask_script import Manager, Command
from flask_sqlalchemy_test import app

manage = Manager(app)


class Hello(Command):
    def run(self):
        print("Hello Flask")


manage.add_command('hello', Hello())

if __name__ == '__main__':
    manage.run()

执行命令python manage.py hello,打印:

Hello Flask

使用这种方式需要注意:

  • 自定义类必须继承自Command基类;
  • 必须实现run()方法;
  • 必须通过add_command()方法添加命令。

2.flask-script传入参数

在前面定义命令的方法中,可以传入参数,有3种方式。

直接使用@option装饰器

在命令行中接收参数测试如下:

from flask_script import Manager
from flask_sqlalchemy_test import app

manage = Manager(app)


@manage.command
def index():
    print('Hello Flask')


@manage.option('-n', '--name', dest='name')
@manage.option('-u', '--url', dest='url')
def hello(name, url):
    print('Hello,', name, url)


if __name__ == '__main__':
    manage.run()

执行python manage.py hello -n Corley -u www.corley.com,打印如下:

Hello, Corley www.corley.com

显然,接收到了指定的参数值,在定义接收参数时,除了定义-n-u,还指定了--name--url,一种是简写,一种是全写,即还可以写成python manage.py hello --name Corley --url www.corley.com
传参数时,还可以传入默认值,在命令行中没有给定某参数的值时,就可以使用给定的默认值,如下:

@manage.option('-n', '--name', dest='name', default='Corley')
@manage.option('-u', '--url', dest='url', default=None) 

command装饰器中传参

如下:

from flask_script import Manager
from flask_sqlalchemy_test import app

manage = Manager(app)


@manage.command
def hello(name="Corley"):
    print("hello", name)


if __name__ == '__main__':
    manage.run()

在命令行中执行python manage.py hello -n Jack,打印:

Hello Jack

显然这种方式不够灵活。

继承Command类传参

from flask_script import Manager, Command, Option
from flask_sqlalchemy_test import app

manage = Manager(app)


class Hello(Command):
    option_list = (
        Option('-n', '--name', dest='name'),
    )

    def run(self, name):
        print("Hello %s" % name)


manage.add_command('hello', Hello())

if __name__ == '__main__':
    manage.run()

在命令行中执行python manage.py hello -n Corley,打印:

Hello Corley

如果要在指定参数的时候动态执行一些逻辑,可以使用get_options()方法,如下:

from flask_script import Manager, Command, Option
from flask_sqlalchemy_test import app

manage = Manager(app)


class Hello(Command):
    def __init__(self, default_name='Corley'):
        super().__init__()
        self.default_name = default_name

    def get_options(self):
        return [
            Option('-n', '--name', dest='name', default=self.default_name),
        ]

    def run(self, name):
        print("Hello %s" % name)


manage.add_command('hello', Hello())

if __name__ == '__main__':
    manage.run()

3.flask-script练习

通过命令行添加管理员测试如下:
先创建一个目录flask_add_admin,里面先创建config.py:

# 数据库连接配置
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root'
DATABASE = 'flask_sqlalchemy'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)

SQLALCHEMY_DATABASE_URI = DB_URL
SQLALCHEMY_TRACK_MODIFICATIONS = False

再创建script.py:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config

app = Flask(__name__)
app.config.from_object(config)

db = SQLAlchemy(app)

class AdminUser(db.Model):
    __tablename__ = 'admin_users'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(30))
    email = db.Column(db.String(50))


db.create_all()

if __name__ == '__main__':
    app.run(debug=True)

并运行创建数据表,再创建manage.py:

from flask_script import Manager
from script import app, AdminUser, db

manage = Manager(app)


@manage.option('-u', '--name', dest='name')
@manage.option('-e', '--email', dest='email')
def add_user(name, email):
    user = AdminUser(name=name, email=email)
    db.session.add(user)
    db.session.commit()


if __name__ == '__main__':
    manage.run()

再命令行切换到当前目录,执行python manage.py add_user -u Corley -e [email protected],此时即将管理员插入到数据库,查询数据表:

select * from admin_users;

打印:

+----+--------+-------------+           
| id | name   | email       |           
+----+--------+-------------+           
|  1 | Corley | [email protected] |           
+----+--------+-------------+           
1 row in set (0.00 sec)                 
                                        

显然,此时插入数据成功。

三、Flask-Migrate

在实际开发环境中,经常需要修改数据库,一般修改数据库时不会直接手动修改,而是去修改对应的ORM模型,然后再把模型映射到数据库中。
flask-migrate就是专门实现这个功能的,它是对Alembic的进一步封装,并集成到了Flask中,所有的迁移操作其实都是Alembic做的,能跟踪模型的变化,并将变化映射到数据库中。
在虚拟环境中安装flask-migrate使用pip install flask-migrate命令。

先创建一个新文件夹,如flask_migrate_test,其中创建文件config.py用于保存配置信息:

# 数据库连接配置
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root'
DATABASE = 'flask_migrate'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)

SQLALCHEMY_DATABASE_URI = DB_URL
SQLALCHEMY_TRACK_MODIFICATIONS = False

数据库需要提前创建好。
再创建程序主入口flask_app.py:

import config
from flask import Flask
from exts import db

app = Flask(__name__)

app.config.from_object(config)

db.init_app(app)


@app.route('/')
def index():
    return '首页'

if __name__ == '__main__':
    app.run(debug=True)

再创建模板文件models.py:

from exts import db

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(30))
    email = db.Column(db.String(50))
    password = db.Column(db.String(30))

再创建中间文件exts.py:

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

该文件用于解决互相引用的冲突。
再创建manage.py:

from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from exts import db
from flask_app import app
from models import User

manage = Manager(app)

Migrate(app, db)

manage.add_command('db', MigrateCommand)


if __name__ == '__main__':
    manage.run()

此时运行lask_app.py,可以正常运行,在命令行中切换到当前目录;
要让Flask-Migrate管理app中的数据库,需要使用Migrate(app, db)来绑定app和数据库;
MigrateCommand是flask-migrate集成的命令,使用manage.add_command('db', MigrateCommand)将其添加到脚本命令,以后运行python manage.py db xxx的命令,其实就是执行MigrateCommand。

先执行命令python manage.py db init初始化迁移文件夹,看到

Creating directory XXX\flask_migrate_test\migrations ...  done
Creating directory XXX\flask_migrate_test\migrations\versions ...  done
Generating XXX\flask_migrate_test\migrations\alembic.ini ...  done
Generating XXX\flask_migrate_test\migrations\env.py ...  done
Generating XXX\flask_migrate_test\migrations\README ...  done
Generating XXX\flask_migrate_test\migrations\script.py.mako ...  done

即初始化成功,此时看当前目录可以看到migrates文件夹,下面有打印输出中显示的文件(夹)。
再运行命令python manage.py db migrate把当前的模型添加到迁移文件中,打印:

INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'user'
Generating XXX\flask_migrate_test\migrations\versions\2b779ddbb317_.py ...  done

即迁移成功,此时查看数据库:

show tables;

打印:

+-------------------------+         
| Tables_in_flask_migrate |         
+-------------------------+         
| alembic_version         |         
| user                    |         
+-------------------------+         
2 rows in set (0.00 sec)            
                                    

即数据库表alembic_version和user创建成功。
此时再运行命令python manage.py db upgrade把迁移文件中的数据库操作映射到数据库中,可以看到:

INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 2b779ddbb317, empty message

打印出相应的版本号,查询表:

select * from alembic_version;

打印:

+--------------+          
| version_num  |          
+--------------+          
| 2b779ddbb317 |          
+--------------+          
1 row in set (0.01 sec)   
                          

显然,版本号已经插入数据表。

如果需要修改表,即修改模型,如models.py修改如下:

from exts import db

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(30))
    email = db.Column(db.String(50))
    password = db.Column(db.String(30))
    age = db.Column(db.Integer)

即增加了一个字段age,此时只需要再执行python manage.py db migratepython manage.py db upgrade即可将变化映射到数据库,migrate打印如下:

INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'user.age'
Generating XXX\flask_migrate_test\migrations\versions\b95ca5177a74_.py ...  done

upgrade打印如下:

INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 2b779ddbb317 -> b95ca5177a74, empty message

原创文章 132 获赞 1491 访问量 36万+

猜你喜欢

转载自blog.csdn.net/CUFEECR/article/details/105902727
今日推荐