作者:Dozing 链接:https://www.jianshu.com/p/b729e84fae4f 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Flask-SQLAlchemy的使用: ORM的好处:可以让我们操作数据库跟操作对象是一样的,非常方便,因为一个表就抽象成一个类,一条数据就抽象成该类的一个对象
1.初始化和设置数据库配置信息
1 2 3 DB_URI = 'mysql+mysqldb://{}:{}@{}/{}.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)' engine = create_engine(DB_URI)
首先从sqlalchemy中导入create_engine
,用这个函数来创建引擎,然后用engine.connect()
来连接数据库,其中一个比较重要的点是,通过create_engine函数的时候,需要传递一个满足某种格式 的字符串,对这个字符串的格式来进行解释:
dialect是数据库的实现,比如MySQL,PostgreSQL,SQLite,并且转换为小写,driver是python对应的驱动,如果不指定,会选择默认的驱动,比如MySQL的默认驱动是Mysqldb,username是数据库的用户名,password是连接数据库的密码,host是连接数据的域名,port是数据库监听的端口号,database是连接哪个数据库的名字。
1 2 3 app = Flask(__name__) app.config.from_object(config) db = SQLAlchemy(app)
如果没有报错,说明配置没有问题,如果有错误,可以根据错误进行修改
Python语言不像Java, C,C++会有main()
函数,Python是脚本语言,只会由上往下执行。
Flask-SQLAlchemy的使用
初始化和设置数据库配置信息
使用flask_sqlalchemy中的SQLAlchemy经行初始化:
1 2 3 from flask_sqlalchemy import SQLAlchemyapp = Flask(__name__) db = SQLAlchemy(app)
设置配置信息:config.py
文件中添加配置信息
在主app
文件中,添加配置文件:
1 2 3 app = Flask(__name__) app.config.from_object(config) db = SQLAlchemy(app)
做测试,看有没有问题
1 2 class Article (db.Model) : id = db.Column(db.Integer)
使用Flask-SQLAlchemy创建模型与表的映射:
模型需要继承自db.Model
,然后需要映射到表中的属性,必须写成db.Column
的数据类型。
数据类型:
db.Integer
代表的是整形.
db.String
代表的是varchar
,需要指定最长的长度.
db.Text
代表的是text
.
其他参数:
primary_key
:代表的是将这个字段设置为主键。
autoincrement
:代表的是这个主键为自增长的。
nullable
:代表的是这个字段是否为空,默认可以为空,可以将这个值设置为False
,在数据库中,这个值就不能为空了。
最后需要调用db.create_all
来将模型真正地创建到数据库中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from flask import Flaskimport pymysqlimport configfrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__) app.config.from_object(config) db = SQLAlchemy(app) class Article (db.Model) : __tablename__ = 'article' id = db.Column(db.Integer,primary_key = True ,autoincrement = True ) title = db.Column(db.String(100 ),nullable = False ) content = db.Column(db.Text,nullable = False ) db.create_all() @app.route('/') def index () : return 'index' if __name__ == '__main__' : app.run(debug=True )
Flask-SQLAlchemy的增,删,改,查
增:
1 2 3 4 5 article1 = Article(title = u'aaa' ,content = u'bbb' ) db.session.add(article1) db.session.commit()
查:
1 2 3 4 article1 = Article.query.filter(Article.title == 'aaa' ).first() print('title:%s' % article1.title) print('content:%s' % article1.content)
1 2 3 4 5 article1 = Article.query.filter(Article.title == 'aaa' , Article.author == 'bbb' ).first() article1 = Article.query.filter(Article.title == 'aaa' ).filter(Article.author == 'bbb' ).first()
改:
1 2 3 4 5 6 7 article1 = Article.query.filter(Article.title == 'aaa' ).first() article1.title = 'New title' db.session.commit()
删:
1 2 3 4 5 6 7 article1 = Article.query.filter(Article.title == 'New title' ).first() db.session.delete(article1) db.session.commit()
Flask-Script的介绍与安装
Flask-Script
的作用是可以通过命令行的形式来操作Flask.例如通过命令跑一个开发版本的服务器、设置数据库、定时任务等。
如果直接在主manager.py
中写命令,那么在终端就只需要python manager.py command_name
就可以了。
如果把一些命令集中在一个文件中,那么在终端就需要输入一个父命令,比如python manager.py db init
例子:flask_script_demo.py 和manager.py
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import Flaskapp = Flask(__name__) db.create_all() @app.route('/') def hello_world () : return 'hello_world' if __name__ == '__main__' : app.run(debug=True )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask_script import Managerfrom flask_script_demo import appfrom db_script import DBmanagermanager = Manager(app) @manager.command def runserver () : print('服务器跑起来了' ) manager.add_command('db' ,DBmanager) if __name__ == '__main__' : manager.run()
有子命令的例子:db_script.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from flask_script import ManagerDBmanager = Manager() @DBmanager.command def init () : print('数据库初始化完成' ) @DBmanager.command def migrate () : print('数据表重新迁移成功' ) ''' if __name__ == '__main__': manager.run() '''
有关Flask-Script还是有很多的不理解,例如代码当中的解释器`@DBmanager.command `这个可以Flask-Script的官网。Flask-Script
分开models和解决循环引用
分开Models的目的:是为了让代码更加方便地管理。
什么是循环引用?
[循环引用] 当一个单元格内的公式直接或间接地应用了这个公式本身所在的单元格时,就称为循环引用。
为什么会报错?因为结果还没出来,却又要去调用它。所以会报错! 假设有如下的文件结构:
1 2 models_sep .py models .py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import Flaskfrom flask_sqlalchemy import SQLAlchemyfrom models import Articleapp = Flask(__name__) db = SQLAlchemy(app) db.create_all() @app.route('/') def hello_world () : return 'hello_world' if __name__ == '__main__' : app.run(debug=True )
1 2 3 4 5 6 7 from models_sep import db class Article (db.Model) : __tablename__ = 'article' id = db.Column(db.Integer,primary_key = True ,autoincrement = True ) title = db.Column(db.String(100 ),nullable = False )
上面的代码会造成一个循环引用:
循环引用
主文件app引入models文件中的Article.但models文件要引入app中的db,才可以生成,做成了循环引用。
如何解决循环引用:把db
放在一个单独的文件中,切断循环引用的线条就可以 假设有如下的文件结构:
1 2 3 app .py exts .py models .py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from flask import Flaskfrom models import Articlefrom exts import dbimport configapp = Flask(__name__) app.config.from_object(config) db.init_app(app) db.create_all() if __name__ == '__main__' : app.run(debug = True ) from flask_sqlalchemy import SQLAlchemydb = SQLAlchemy() from exts import dbclass Article (db.Model) : __tablename__ = 'article' id = db.Column(db.Integer,primary_key = True ,autoincrement = True ) title = db.Column(db.String(100 ),nullable = False )
Flask-SQLAlchemy的外键约束
外键:
1 2 3 4 5 6 7 8 class Article (db.Model) : __tablename__ = 'article' id = db.Column(db.Integer,primary_key = True ,autoincrement = True ) title = db.Column(db.String(100 ),nullable = False ) content = db.Column(db.Text,nullable = False ) author_id = db.Column(db.Integer,db.ForeignKey('user.id' )) author = db.relationship('User' ,backref = db.backref('articles' ))
1 author = db.relationship('User',backref = db.backref('articles'))
解释:
给Article
这个模型添加一个author
属性,可以访问这篇文章的作者的数据,像访问普通模型一样。
backref
是定义反向引用的,可以通过User.articles
访问这个模型所写的所有文章
这里有一个知识点,当db.create_all()
运行了后,数据库的表已经创建好了。若要再对表(类)修改,添加外键,关系等。将不会生效。因为表,已经在数据库中创建好了。 例子代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 from flask import Flaskimport pymysqlimport configfrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__) app.config.from_object(config) db = SQLAlchemy(app) class User (db.Model) : __tablename__ = 'user' id = db.Column(db.Integer,primary_key = True ,autoincrement = True ) username = db.Column(db.String(100 ),nullable = False ) class Article (db.Model) : __tablename__ = 'article' id = db.Column(db.Integer,primary_key = True ,autoincrement = True ) title = db.Column(db.String(100 ),nullable = False ) content = db.Column(db.Text,nullable = False ) author_id = db.Column(db.Integer,db.ForeignKey('user.id' )) author = db.relationship('User' ,backref = db.backref('articles' )) db.create_all() @app.route('/') def index () : article = Article.query.filter(Article.content == 'This is an Article2' ).first() print("author is %s" %(article.author.username)) user = User.query.filter(User.id == 1 ).first() print("The Articles are:" ) for arti in user.articles: print('--------' ) print(arti.title) return 'Hello World!' if __name__ == '__main__' : app.run(debug=True )
表多对多的关系:
多对多的关系,需要通过一个中间表进行关联。
中间表,不能通过class
的方式实现,只能通过db.Table
的方式实现
设置关联: tags = db.relationship('Tag',secondary = article_tag,backref = db.backref('articles'))
需要使用一个关键字参数secondary=中间表
数据的添加与访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 article1 = Article(title = 'aaa' ) article2 = Article(title = 'bbb' ) tag1 = Tag(name = '999' ) tag2 = Tag(name = '888' ) article1.tags.append (tag1) # 留意可以用append ()来添加数据 article1.tags.append (tag2) article2.tags.append (tag1) article2.tags.append (tag2) db.session.add(article1) db.session.add(article2) db.session.add(tag1) db.session.add(tag2) db.session.commit() `` ` - 数据访问: ` `` python article1 = Article.query.filter(Article.title == 'aaa' ).first() tags = article1.tags for tag in tags: print ('---------' ) print ('Tag: %s' %(tag.name)) tag1 = Tag.query.filter(Tag.name == '999' ).first() articles = tag1.articles for article in articles: print ('********' ) print ('article: %s' %(article.title)) `` `
例子代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 from flask import Flaskimport pymysqlimport configfrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__) app.config.from_object(config) db = SQLAlchemy(app) article_tag = db.Table('article_tag' , db.Column('article_id' ,db.Integer,db.ForeignKey('article.id' ),primary_key = True ), db.Column('tag_id' ,db.Integer,db.ForeignKey('tag.id' ),primary_key = True ), ) class Article (db.Model) : __tablename__ = 'article' id = db.Column(db.Integer,primary_key = True ,autoincrement = True ) title = db.Column(db.String(100 ),nullable = False ) tags = db.relationship('Tag' ,secondary = article_tag,backref = db.backref('articles' )) class Tag (db.Model) : __tablename__ = 'tag' id = db.Column(db.Integer,primary_key = True ,autoincrement = True ) name = db.Column(db.String(100 ),nullable = False ) db.create_all() @app.route('/') def index () : ''' article1 = Article(title = 'aaa') article2 = Article(title = 'bbb') tag1 = Tag(name = '999') tag2 = Tag(name = '888') article1.tags.append(tag1) article1.tags.append(tag2) article2.tags.append(tag1) article2.tags.append(tag2) db.session.add(article1) db.session.add(article2) db.session.add(tag1) db.session.add(tag2) db.session.commit() ''' article1 = Article.query.filter(Article.title == 'aaa' ).first() tags = article1.tags for tag in tags: print('---------' ) print('Tag: %s' %(tag.name)) tag1 = Tag.query.filter(Tag.name == '999' ).first() articles = tag1.articles for article in articles: print('********' ) print('article: %s' %(article.title)) return 'Hello World!' if __name__ == '__main__' : app.run(debug=True )
Flask-Migrate的介绍与安装:
介绍:因为采用db.create_all
在后期修改字段的时候,不会自动的映射到数据库中,必须删除表,然后重新运行db.create_all
才会重新映射,这样不符合实际的工作要求。因此flask-migrate就是为了解决这个问题,它可以在每次修改模型后,可以将修改的东西映射到数据库中。
使用flask_migrate
必须借助flask_scripts
,这个包的MigrateCommand
中包含了所有和数据库相关的命令。
相关的命令:
python manager.py db init
:初始化一个迁移脚本的环境,只需要执行一次
python manager.py db migrate
:将模型生成迁移文件,只要模型更改了,就需要执行一遍这个命令
python manager.py db upgrade
:将迁移文件真正映射到数据库中,每次运行migrate
命令后,记得要运行这个命令
注意点:需要将你想要的映射到数据库中的模型,都要导入到manager.py
文件中,如果没有导入进去,就不会映射到数据库中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from flask import Flaskfrom exts import dbimport configfrom models import Articleapp = Flask(__name__) app.config.from_object(config) db.init_app(app) with app.app_context(): db.create_all() @app.route('/') def hello_world () : return 'hello_world' if __name__ == '__main__' : app.run(debug=True )
Flask-SQLAlchemy模型的理解 1 2 3 4 5 6 7 8 9 10 class User (db.Model) : id = db.Column(db.Integer(), primary_key=True ) username = db.Column(db.String(255 )) password = db.Column(db.String(255 )) def __init__ (self, username) : self.username = username def __repr__ (self) : return "<User `{}`>" .format(self.username)
这段代码做了什么呢?实际上,我们已经得到了User
模型,它基于一个user
表,该表拥有3个字段。当我们继承db.Model
时,与数据库连接和通信的工作已经自动完成了。User
的某些类的属性值是db.Column
类的实例,每个这样的属性都代表了数据库表里的一个字段。在db.Column
的构造函数里,第1个参数是可选的,通过这个参数我们可以指定该属性在数据库中的字段名。如果没有指定,则SQLAlchemy会认为字段名与这个属性的名字是一样的。如果要指定这个可选参数,则可以这样写:
1 username = db.Column('user_name', db.String(255))
传给db.Column
的第2个参数会告诉SQLAlchemy,应该把这个字段作为什么类型来处理。我们在书中将会用到的主要类型有:
1 2 3 4 5 6 7 8 db.String db.Text db.Integer db.Float db.Boolean db.Date db.DateTime db.Time`
每种类型的含义都很简单。String
和Text
类型会接收Python
的字符串,并且把它们转为varchar
和text
类型的字段。Integer
和Float
类型则会接收Python
的任意数值类型,把它们分别转换为对应的正确类型,再插入数据库中。Boolean
类型会接收Python
的布尔值转换成Boolean
类型的字段;如果数据库不支持Boolean
类型的字段,则SQLAlchemy
会自动把Python的布尔值转换为0和1保存在数据库中。Date
,DateTime
和Time类型使用了Python的datetime
原生包中的同名类,并把它们转换后保存到数据库中。String
,Integer
和Float
类型都会接收一个额外的参数,来告诉SQLAlchemy该字段的存储长度限制。
primary_key
参数会告诉SQLAlchemy
,这个字段需要做主键索引。每个SQLAlchemy
模型类型都必须有一个主键才能正常工作。
SQLAlchemy
会假设你的表名就模型类型的小写版本。但是,如果你想给表起个别的名字,你可以给类添加__tablename__
的类属性。另外,通过采用这种方式,你也可以使用在数据库中已经存在的表,只需把表名设为该属性的值:
1 2 3 4 5 6 class User (db.Model) : __tablename__ = 'user_table_name' id = db.Column(db.Integer(), primary_key=True ) username = db.Column(db.String(255 )) password = db.Column(db.String(255 ))
我们不需要定义__init__
或__repr__
方法,如果我们没有定义,则SQLAlchemy会自动创建__init__
方法。你定义的所有字段名将会成为此方法所接收的关键字的参数名。
参考原文
自己当初在用Flask
来创建用户角色时,对下面的代码的代码中的`role = Role(name = r)思考了很久。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Role (db.Model) : __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True ) name = db.Column(db.String(64 ), unique=True ) default = db.Column(db.Boolean,default=False ,index=True ) permissions = db.Column(db.Integer) users = db.relationship('User' , backref='role' , lazy='dynamic' ) @staticmethod def insert_roles () : roles = { 'User' :[Permission.FOLLOW,Permission.COMMENT,Permission.WRITE], 'Moderator' :[Permission.FOLLOW,Permission.COMMENT,Permission.WRITE,Permission.MODERATE], 'Administrator' :[Permission.FOLLOW,Permission.COMMENT,Permission.WRITE,Permission.MODERATE,Permission.ADMIN] } default_role = 'User' for r in roles: role = Role.query.filter_by(name = r).first() if role is None : role = Role(name=r) role.reset_permissions() for perm in roles[r]: role.add_permission(perm) role.default = (role.name == default_role) db.session.add(role) db.session.commit()
角色Role
模型明明有好几个字段,为什么但一个关键字参数name = r
就可以用来创建一个实例?知道在SQLAlchemy中,不需要定义__init__
或__repr__
方法,如果没有定义,则SQLAlchemy会自动创建__init__
方法。定义过的所有字段名将会成为此方法所接收的关键字的参数名。
当若作为__init__()
构造函数的参数,不是要一次性全部赋值的吗?为什么就只有’name=r’作为关键字传入就可以了? 知道看到用户User
模型中的构造模型。发现到了关键字参数**kwargs
。一切都突然明白了。还是自己python基础不太熟悉。要找找源代码来看一看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class User (UserMixin, db.Model) : __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True ) email = db.Column(db.String(64 ), unique=True , index=True ) username = db.Column(db.String(64 ), unique=True , index=True ) role_id = db.Column(db.Integer, db.ForeignKey('roles.id' )) password_hash = db.Column(db.String(128 )) confirmed = db.Column(db.Boolean,default = False ) name = db.Column(db.String(64 )) location = db.Column(db.String(64 )) about_me = db.Column(db.Text()) member_since = db.Column(db.DateTime(),default = datetime.utcnow) last_seen = db.Column(db.DateTime(),default = datetime.utcnow) avatar_hash = db.Column(db.String(32 )) posts = db.relationship('Post' ,backref = 'author' ,lazy = 'dynamic' ) followed = db.relationship('Follow' , foreign_keys=[Follow.follower_id], backref=db.backref('follower' ,lazy='joined' ), lazy='dynamic' ,r. cascade='all,delete-orphan' ) followers = db.relationship('Follow' , foreign_keys=[Follow.followed_id], backref=db.backref('followed' ,lazy='joined' ), lazy='dynamic' , cascade = 'all,delete-orphan' ) def __init__ (self,**kwargs) : super(User,self).__init__(**kwargs) if self.role is None : if self.email == current_app.config['FLASKY_ADMIN' ]: self.role = Role.query.filter_by(name = 'Administrator' ).first() if self.role is None : self.role = Role.query.filter_by(default = True ).first() if self.email is not None and self.avatar_hash is None : self.avatar_hash = self.gravatar_hash()
还有就是对为什么在用户的confirm
函数中,只有db.session.add(self)
而没有db.session.commit().
1 2 3 4 5 6 7 8 9 10 11 def confirm(self ,token): s = Serializer(current_app.config['SECRET_KEY' ]) try : data = s.loads(token) except: return False if data.get('confirm' )!=self .id: return False self .confirmed = True db.session.add(self ) return True
这个这是模型,真正实现db.session.commit()
的是在视图函数里面。
1 2 3 4 5 6 7 8 9 10 11 @auth .route('/confirm/<token>' )@login_required def confirm(token): if current_user.confirmed: return redirect('main.index' ) if current_user.confirm(token): db.session.commit() flash('You have confirmed your account.Thanks' ) else : flash('The confirmation link is invalid or has expired.' ) return redirect(url_for('main.index' ))