注意:在flask中,操做数据库仍是经过orm调用驱动来操做。sqlalchemy是python下的一款工业级的orm,比Django自带的orm要强大不少,至于什么类型的数据库不重要,经过orm上层操做的代码是同样的。咱们目前介绍的是sqlalchemy,与flask没有太大关系,后面会介绍如何将sqlalchemy与flask集成,也有一个开源的第三方组件,叫flask-sqlalchemy。 关于数据库,我这里选择的是postgresql,固然mysql、Oracle、Mariadb等数据库也是能够的。
安装postgresql能够直接到这个网站下载,https://www.enterprisedb.com/downloads/postgres-postgresql-downloads,我这里下载的是Windows版本的。
html
直接去下载便可,一路next便可。我已经安装过了,这里就再也不演示了。
java
使用navicat进行链接是没问题的
python
sqlalchemy是一款orm框架
注意:SQLAlchemy自己是没法处理数据库的,必须依赖于第三方插件,比方说pymysql,cx_Oracle等等
SQLAlchemy等因而一种更高层的封装,里面封装了许多dialect(至关因而字典),定义了一些组合,比方说: 能够用pymysql处理mysql,也能够用cx_Oracle处理Oracle,关键是程序员使用什么,而后会在dialect里面进行匹配,同时也将咱们高层定义的类转化成sql语句,而后交给对应的DBapi去执行。
除此以外,SQLAlchemy还维护一个数据库链接池,数据库的连接和断开是很是耗时的 SQLAlchemy维护了一个数据库链接池,那么就能够拿起来直接用mysql
首先要安装sqlalchemy和psycopg2(python中用于链接postgresql的驱动),直接pip install 便可
linux
# 链接数据库须要建立引擎 from sqlalchemy import create_engine username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 # 建立一个引擎,能够用来执行sql语句 # 链接方式:数据库+驱动://用户名:密码@ip:端口/数据库 # 固然驱动也能够不指定,会选择默认的,若是有多种驱动的话,想选择具体的一种,能够指定。 # 但对于目前的postgresql来讲,不指定驱动,默认就是psycopg2 # 这里去掉driver的话,也能够这么写engine = create_engine(f"{dialect}://{username}:{password}@{hostname}:{port}/{database}") engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") create_table_sql = """ create table girls( name varchar(255), age int, gender varchar(1) ); comment on column girls.name is '少女的姓名'; comment on column girls.age is '少女的年龄'; comment on column girls.gender is '少女的性别'; """ engine.execute(create_table_sql)
显然是执行成功的,而后将表删掉也是没问题的。
git
orm: object relation mapping,就是能够把咱们写的类转化成表。将类里面的元素映射到数据库表里面程序员
刚才咱们已经成功的建立了一张表了,可是咱们发现实际上咱们写的仍是sql语句,只不过是用python来执行的。换句话说,我sql语句写好,我还能够在终端、Navicat等等地方执行。web
下面,咱们将使用python,经过定义一个类,而后经过sqlalchemy,映射为数据库中的一张表。sql
from sqlalchemy import create_engine username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") # 须要如下步骤 ''' 1.建立一个orm模型,这个orm模型必须继承SQLAlchemy给咱们提供好的基类 2.在orm中建立一些属性,来跟表中的字段进行一一映射,这些属性必须是SQLAlchemy给咱们提供好的数据类型 3.将建立好的orm模型映射到数据库中 ''' # 1.生成一个基类,而后定义模型(也就是orm中的o),继承这个基类 from sqlalchemy.ext.declarative import declarative_base # 这个declarative_base是一个函数,传入engine,生成基类 Base = declarative_base(bind=engine) class Girl(Base): # 指定表名:__tablename__ __tablename__ = "girls" # 指定schema,postgresql是由schema的,如何指定呢 __table_args__ = { "schema": "public" # 其实若是不指定,那么public默认是public,这里为了介绍,因此写上 } # 2.建立属性,来和数据库表中的字段进行映射 from sqlalchemy import Column, Integer, VARCHAR # 要以 xx = Column(type)的形式,那么在表中,字段名就叫xx,类型就是type id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(255)) age = Column(Integer) gender = Column(VARCHAR(1)) class Boy(Base): __tablename__ = "boys" from sqlalchemy import Column, Integer, VARCHAR id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(255)) age = Column(Integer) gender = Column(VARCHAR(1)) # 3.模型建立完毕,那么将模型映射到数据库中 # 这行代码就表示将全部继承了Base的类映射到数据库中变成表,咱们这里有两个类。不出意外的话,数据库中应该会有两张表,叫girls和boys Base.metadata.create_all() # 一旦映射,即便改变模型以后再次执行,也不会再次映射。 # 好比,我在模型中新增了一个字段,再次映射,数据库中的表是不会多出一个字段的 # 除非将数据库中与模型对应的表删了,从新映射。 # 若是模型修改了,能够调用Base.metadata.drop_all(),而后再create_all()
将表删除,从新映射
数据库
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR from sqlalchemy.orm import sessionmaker username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class Girls(Base): __tablename__ = "girls" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(30)) age = Column(Integer) gender = Column(VARCHAR(1)) anime = Column(VARCHAR(50)) Base.metadata.create_all() # 将对象实例化,而后对对象进行操做,会映射为对数据库表的操做 girls = Girls() # 建立Session类,绑定引擎 Session = sessionmaker(bind=engine) # 实例化获得session对象,经过session来操做数据库的表 session = Session()
girls.name = "satori" girls.age = 16 girls.gender = "f" girls.anime = "动漫地灵殿" # 一个Girls实例在数据库的表中对应一条记录,进行添加 session.add(girls) # 将对girls作的操做提交执行 # 像增、删、改这些设计数据变动的操做,必需要提交 session.commit() # 也能够批量进行操做 # 另外能够生成Girls实例,获得girls,经过girls.xx的方式赋值 # 也能够经过Girl(xx="")的方式 session.add_all([ Girls(name="koishi", age=15, gender="f", anime="东方地灵殿"), Girls(name="mashiro", age=16, gender="f", anime="樱花庄的宠物女孩"), Girls(name="matsuri", age=400, gender="f", anime="sola"), Girls(name="kurisu", age=20, gender="f", anime="命运石之门") ]) session.commit()
# 调用session.query(Girls),注意里面传入的不是数据库的表名,而是模型。 # 映射到数据库至关于select * from girls # 但获得是一个query对象,还能够加上filter进行过滤,至关于where,获得了还是一个query对象 # 对query对象调用first()会拿到第一条数据,没有的话为None。调用all()会拿到全部知足条件的数据,没有的话则是一个空列表 query = session.query(Girls).filter(Girls.name == "satori") print(type(query)) # <class 'sqlalchemy.orm.query.Query'> # 直接打印query对象,会生成对应的sql语句 print(query) """ SELECT girls.id AS girls_id, girls.name AS girls_name, girls.age AS girls_age, girls.gender AS girls_gender, girls.anime AS girls_anime FROM girls WHERE girls.name = %(name_1)s """ # 调用first()或者all(),则能够获取具体的Girls类的对象。里面的属性,如:name、age等等则存放了数据库表中取的值。 # 可是咱们没有写__str__方法,因此打印的是一个类对象 print(query.first()) # <__main__.Girls object at 0x000000000AF57908> # 咱们能够手动查找属性 girl1 = query.first() print(girl1.name) # satori print(girl1.age) # 16 print(girl1.gender) # f print(girl1.anime) # 动漫地灵殿
# 改和查比较相似,首先要查想要修改的记录 # 熟悉东方的小伙伴会发现,我手癌把东方地灵殿写成动漫地灵殿了,咱们这里改回来 # 将数据库的对应记录获取出来了。 """ 以前说过,Girls这个模型对应数据库的一张表 Girls的一个实例对应数据库表中的一条记录 那么反过来同理,数据库表中的一条记录对应Girls的一个实例 咱们经过修改实例的属性,再同步回去,就能够反过来修改数据库表中的记录 """ # 获取相应记录对应的实例 girl2 = session.query(Girls).filter(Girls.anime == "动漫地灵殿").first() # 修改属性 girl2.anime = "东方地灵殿" # 而后提交,因为是改,因此不须要add # 而后girl2这个实例就会映射回去,从而把对应字段修改 session.commit()
顺序无所谓啦,至少记录已经被咱们修改了
# 筛选出要删除的字段 girl = session.query(Girls).filter(Girls.gender == "f").all() for g in girl: # 调用session.delete session.delete(g) # 一样须要提交 session.commit()
能够看到,数据库里面的数据全没了。为了后面介绍其余方法,咱们再将数据从新写上去。
模型,指定查找这个模型中全部的对象
模型中的属性,能够指定查找模型的某几个属性
聚合函数
func.count:统计行的数量 func.avg:求平均值 func.max:求最大值 func.min:求最小值 func.sum:求总和
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, func from sqlalchemy.orm import sessionmaker username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class Girls(Base): __tablename__ = "girls" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(30)) age = Column(Integer) gender = Column(VARCHAR(1)) anime = Column(VARCHAR(50)) def __str__(self): return f"{self.name}-{self.age}-{self.gender}-{self.anime}" Base.metadata.create_all() # 建立Session类,绑定引擎 Session = sessionmaker(bind=engine) # 实例化获得session对象,经过session来操做数据库的表 session = Session() query = session.query(Girls).all() print(query) """ [<__main__.Girls object at 0x000000000AF9F5C0>, <__main__.Girls object at 0x000000000AF9F630>, <__main__.Girls object at 0x000000000AF9F6A0>, <__main__.Girls object at 0x000000000AF9F710>, <__main__.Girls object at 0x000000000AF9F7B8>] """ for _ in query: print(_) """ satori-16-f-动漫地灵殿 koishi-15-f-东方地灵殿 mashiro-16-f-樱花庄的宠物女孩 matsuri-400-f-sola kurisu-20-f-命运石之门 """ # 若是我不想一会儿查出全部的字段,而是只要某几个字段,该怎么办呢? # 只写调用模型.属性便可,那么到数据库就会选择相应的字段 print(session.query(Girls.name, Girls.age).all()) # [('satori', 16), ('koishi', 15), ('mashiro', 16), ('matsuri', 400), ('kurisu', 20)] # 聚合函数 # 获得的query对象,直接打印是sql语句,加上first取第一个数据 print(session.query(func.count(Girls.name)).first()) # (5,) print(session.query(func.avg(Girls.age)).first()) # (Decimal('93.4000000'),) print(session.query(func.max(Girls.age)).first()) # (400,) print(session.query(func.min(Girls.age)).first()) # (15,) print(session.query(func.sum(Girls.age)).first()) # (467,)
为了演示,我将表删除从新建立,添加新的数据
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, and_, or_ from sqlalchemy.orm import sessionmaker username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class Girls(Base): __tablename__ = "girls" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(30)) age = Column(Integer) gender = Column(VARCHAR(1)) anime = Column(VARCHAR(50)) def __str__(self): return f"{self.name}-{self.age}-{self.gender}-{self.anime}" Base.metadata.create_all() # 建立Session类,绑定引擎 Session = sessionmaker(bind=engine) # 实例化获得session对象,经过session来操做数据库的表 session = Session() # 1. == print(session.query(Girls).filter(Girls.age == 16).first()) """ 古明地觉-16-f-动漫地灵殿 """ # 2.!= print(session.query(Girls).filter(Girls.age != 16).first()) """ 四方茉莉-400-f-sola """ # 3.like,和sql里面的like同样。除此以外,还有ilike,表示不区分大小写 print([str(q) for q in session.query(Girls).filter(Girls.name.like("%宫%")).all()]) """ ['森宫苍乃-20-f-sola', '雨宫优子-16-f-悠久之翼', '宫村宫子-15-f-悠久之翼'] """ print([str(q) for q in session.query(Girls).filter(Girls.name.like("_宫%")).all()]) """ ['森宫苍乃-20-f-sola', '雨宫优子-16-f-悠久之翼'] """ # 4.in_,至于为何多了多了一个_,这个bs4相似,为了不和python里的关键字冲突。 print([str(q) for q in session.query(Girls).filter(Girls.age.in_([16, 400])).all()]) """ ['古明地觉-16-f-动漫地灵殿', '椎名真白-16-f-樱花庄的宠物女孩', '四方茉莉-400-f-sola', '春日野穹-16-f-缘之空', '雨宫优子-16-f-悠久之翼'] """ # 5.notin_, Girls.age.notin_也等价于~Girls.age.in_ print([str(q) for q in session.query(Girls).filter(Girls.age.notin_([16, 20, 400])).all()]) """ ['立华奏-18-f-angelbeats', '古河渚-19-f-Clannad', '坂上智代-18-f-Clannad', '古明地恋-15-f-东方地灵殿', '宫村宫子-15-f-悠久之翼'] """ # 6.isnull print(session.query(Girls).filter(Girls.age is None).first()) """ None """ # 7.isnotnull print(session.query(Girls).filter(Girls.age is not None).first()) """ 古明地觉-16-f-动漫地灵殿 """ # 8.and_, from sqlachemy import and_ print(session.query(Girls).filter(and_(Girls.age == 16, Girls.anime == "樱花庄的宠物女孩")).first()) """ 椎名真白-16-f-樱花庄的宠物女孩 """ # 9.or_, from sqlalchemy import or_ print([str(q) for q in session.query(Girls).filter(or_(Girls.age == 16, Girls.anime == "悠久之翼")).all()]) """ ['古明地觉-16-f-动漫地灵殿', '椎名真白-16-f-樱花庄的宠物女孩', '春日野穹-16-f-缘之空', '雨宫优子-16-f-悠久之翼', '宫村宫子-15-f-悠久之翼'] """ # 10.count,统计数量 print(session.query(Girls).filter(Girls.age == 16).count()) # 4 # 11.切片 for g in session.query(Girls).filter(Girls.age == 16)[1: 3]: print(g) """ 椎名真白-16-f-樱花庄的宠物女孩 春日野穹-16-f-缘之空 """ # 12.startswith for g in session.query(Girls).filter(Girls.anime.startswith("悠久")): print(g) """ 雨宫优子-16-f-悠久之翼 宫村宫子-15-f-悠久之翼 """ # 13.endswith for g in session.query(Girls).filter(Girls.anime.endswith("孩")): print(g) """ 椎名真白-16-f-樱花庄的宠物女孩 """ # 14.+ - * / for g in session.query(Girls.name, Girls.age, Girls.age+3).filter((Girls.age + 3) >= 20).all(): print(g) """ ('四方茉莉', 400, 403) ('牧濑红莉栖', 20, 23) ('立华奏', 18, 21) ('古河渚', 19, 22) ('坂上智代', 18, 21) ('森宫苍乃', 20, 23) """ # 15.concat for g in session.query(Girls.name, Girls.anime.concat('a').concat('b')).all(): print(g) """ ('古明地觉', '动漫地灵殿ab') ('椎名真白', '樱花庄的宠物女孩ab') ('四方茉莉', 'solaab') ('牧濑红莉栖', '命运石之门ab') ('春日野穹', '缘之空ab') ('立华奏', 'angelbeatsab') ('古河渚', 'Clannadab') ('坂上智代', 'Clannadab') ('古明地恋', '东方地灵殿ab') ('森宫苍乃', 'solaab') ('雨宫优子', '悠久之翼ab') ('宫村宫子', '悠久之翼ab') """ # 16.between,等价于and_(Girls.age >= 17, Girls.age <= 21) for g in session.query(Girls).filter(Girls.age.between(17, 21)).all(): print(g) """ 牧濑红莉栖-20-f-命运石之门 立华奏-18-f-angelbeats 古河渚-19-f-Clannad 坂上智代-18-f-Clannad 森宫苍乃-20-f-sola """ # 17.contains,等价于Girls.anime.like("%la%") for g in session.query(Girls).filter(Girls.anime.contains("la")): print(g) """ 四方茉莉-400-f-sola 古河渚-19-f-Clannad 坂上智代-18-f-Clannad 森宫苍乃-20-f-sola """ # 18.distinct for g in session.query(Girls.anime.distinct()): print(g) """ ('angelbeats',) ('sola',) ('悠久之翼',) ('缘之空',) ('东方地灵殿',) ('命运石之门',) ('Clannad',) ('动漫地灵殿',) ('樱花庄的宠物女孩',) """
在mysql、postgresql等数据库中,外键可使表之间的关系更加紧密。而SQLAlchemy中也一样支持外键,经过ForforeignKey来实现,而且能够指定表的外键约束
外键约束有如下几种:
既然如此的话,咱们是否是要有两张表啊,我这里新建立两张表
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) # 我如今新建两张表 # 表People--表language ==》父表--子表 class People(Base): __tablename__ = "People" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可为空 age = Column(Integer, nullable=False) class Language(Base): __tablename__ = "Language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 还记得约束的种类吗 # 1.RESTRICT:父表删除数据,会阻止。 ''' 由于在子表中,引用了父表的数据,若是父表的数据删除了,那么字表就会懵逼,不知道该找谁了。 ''' # 2.NO ACTION ''' 和RESTRICT做用相似 ''' # 3.CASCADE ''' 级联删除:Language表的pid关联了People表的id。若是People中id=1的记录被删除了,那么Language中对应的pid=1的记录也会被删除 就是咱们关联了,个人pid关联了你的id,若是你删除了一条记录,那么根据你删除记录的id,我也会相应的删除一条,要死一块死。 ''' # 4.SET NULL ''' 设置为空:和CASCADE相似,就是个人pid关联你的id,若是id没了,那么pid会被设置为空,不会像CASCADE那样,把相应pid所在整条记录都给删了 ''' # 这个pid关联的是People表里面的id,因此要和People表里的id属性保持一致 # 注意这里的People是表名,不是咱们的类名,怪我,把两个写成同样的了 pid = Column(Integer, ForeignKey("People.id", ondelete="RESTRICT")) # 引用的表.引用的字段,约束方式 Base.metadata.create_all() # 而后添加几条记录吧 Session = sessionmaker(bind=engine) session = Session() session.add_all([People(name="Guido van Rossum", age=62), People(name="Dennis Ritchie", age=77), People(name="James Gosling", age=63), Language(name="Python", birthday=1991, type="解释型", pid=1), Language(name="C", birthday=1972, type="编译型", pid=2), Language(name="Java", birthday=1995, type="解释型", pid=3)]) session.commit()
咱们下面删除数据,直接在Navicat里面演示
显示没法删除,由于在Language表中,pid关联了该表的id。而pid和id保持了一致,因此没法删除。换句话说,若是这里id=1,在字表中也有pid=1,那么这里的记录是没法删除的。
若是将字表中pid=1的记录进行修改,把pid=1改为pid=10,这样父表中id=1,在子表就没有pid与之对应了。可是:
剩下的便再也不演示了,都是比较相似的。另外,若是再ForeignKey中不指定ondelete,那么默认就是RESTRICT
当一张表关联了另外一张表的外键,咱们能够根据子表中pid,从而找到父表与之对应的id的所在的记录。 可是有没有方法,可以直接经过子表来查询父表的内容呢?可使用relationship
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): # 这里把表名改为小写,否则和类名同样,容易引发歧义 __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可为空 age = Column(Integer, nullable=False) class Language(Base): __tablename__ = "language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 这里的people.id指定的是数据库中people表的id字段 pid = Column(Integer, ForeignKey("people.id", ondelete="RESTRICT")) # 若是我想经过Language表查询People表的数据呢? # 能够经过relationship进行关联,表示关联的是数据库中的people表 # 而后取出language表中的记录,即可以经过 .父表.xxx 去取people表中的内容 # 这里的填入的再也不是people表的表名了,而是对应的模型,也就是咱们定义的People这个类,以字符串的形式 # 由于咱们打印值,确定是将数据库表的记录变成模型的实例来获取并打印的。 父表 = relationship("People") # 删了从新建立 Base.metadata.drop_all() Base.metadata.create_all() # 而后添加几条记录吧 Session = sessionmaker(bind=engine) session = Session() session.add_all([People(name="Guido van Rossum", age=62), People(name="Dennis Ritchie", age=77), People(name="James Gosling", age=63), Language(name="Python", birthday=1991, type="解释型", pid=1), Language(name="C", birthday=1972, type="编译型", pid=2), Language(name="Java", birthday=1995, type="解释型", pid=3)]) session.commit() for obj in session.query(Language).all(): # obj获取的即是language表中的记录,能够找到在父表中id与字表的pid相对应的记录 print(obj.父表.name, "发明了", obj.name) """ Guido van Rossum 发明了 Python Dennis Ritchie 发明了 C James Gosling 发明了 Java """
目前数据是一对一的,咱们也能够一对多
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): # 这里把表名改为小写,否则和类名同样,容易引发歧义 __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可为空 age = Column(Integer, nullable=False) # 在父表中关联子表 子表 = relationship("Language") def __str__(self): return f"{self.name}--{self.age}--{self.字表}" class Language(Base): __tablename__ = "language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 这里的people.id指定的是数据库中people表的id字段 pid = Column(Integer, ForeignKey("people.id", ondelete="RESTRICT")) 父表 = relationship("People") def __str__(self): return f"{self.name}--{self.birthday}--{self.type}" Session = sessionmaker(bind=engine) session = Session() for obj in session.query(Language).all(): print(obj.父表.name, "发明了", obj.name) """ Guido van Rossum 发明了 python Dennis Ritchie 发明了 C James Gosling 发明了 java Dennis Ritchie 发明了 unix """ # 咱们来遍历父表,那么因为我在父表中关联了字表 # 那么也能够经过父表来找到字表 for obj in session.query(People).all(): print(obj.子表) """ [<__main__.Language object at 0x000000000AFF4278>] [<__main__.Language object at 0x000000000AFF4320>, <__main__.Language object at 0x000000000AFF4390>] [<__main__.Language object at 0x000000000AFF4438>] """ # 由于父表的id是惟一的,不会出现字表的一条记录对应父表中多条记录 # 可是反过来是彻底能够的,所以这里打印的是一个列表,即使只有一个元素,仍是以列表的形式打印 for obj in session.query(People).all(): for o in obj.子表: print(o) """ python--1991--解释型 C--1972--编译型 unix--1973--操做系统 java--1995--解释型 """
能够看到,咱们能经过在子表中定义relationship("父表模型"),这样查询子表,也能够经过子表来看父表的记录。那么同理,我也能够在父表中定义relations("子表模型"),查询父表,也能够经过父表来看子表的记录
可是这样仍是有点麻烦,因此在relationship中还有一个反向引用
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): # 这里把表名改为小写,否则和类名同样,容易引发歧义 __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可为空 age = Column(Integer, nullable=False) # 我把这行代码注释掉了 # 子表 = relationship("Language") def __str__(self): return f"{self.name}--{self.age}" class Language(Base): __tablename__ = "language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 这里的people.id指定的是数据库中people表的id字段 pid = Column(Integer, ForeignKey("people.id", ondelete="RESTRICT")) # 我在子表(或者说子表对应的模型)的relationship中指定了backref="我屮艸芔茻" # 就等价于在父表对应的模型中,指定了 我屮艸芔茻=relationship("Language") 父表 = relationship("People", backref="我屮艸芔茻") def __str__(self): return f"{self.name}--{self.birthday}--{self.type}" Session = sessionmaker(bind=engine) session = Session() for obj in session.query(Language).all(): print(obj.父表.name, "发明了", obj.name) """ Guido van Rossum 发明了 python Dennis Ritchie 发明了 C James Gosling 发明了 java Dennis Ritchie 发明了 unix """ for obj in session.query(People).all(): for o in obj.我屮艸芔茻: print(o) """ python--1991--解释型 C--1972--编译型 unix--1973--操做系统 java--1995--解释型 """
依旧是能够访问成功的
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): # 这里把表名改为小写,否则和类名同样,容易引发歧义 __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可为空 age = Column(Integer, nullable=False) # 我把这行代码注释掉了 # 子表 = relationship("Language") def __str__(self): return f"{self.name}--{self.age}" class Language(Base): __tablename__ = "language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 这里的people.id指定的是数据库中people表的id字段 pid = Column(Integer, ForeignKey("people.id", ondelete="RESTRICT")) # 我在子表(或者说子表对应的模型)的relationship中指定了backref="我屮艸芔茻" # 就等价于在父表对应的模型中,指定了 我屮艸芔茻=relationship("Language") 父表 = relationship("People", backref="我屮艸芔茻") def __str__(self): return f"{self.name}--{self.birthday}--{self.type}" Session = sessionmaker(bind=engine) session = Session() # 在父表people添加一条属性 # 在子表language中添加两条属性 people = People(id=666, name="KenThompson", age=75) language1 = Language(id=5, name="B", birthday=1968, type="编译型", pid=666) language2 = Language(id=6, name="Go", birthday=2009, type="编译型", pid=666) # 因为People和Language是关联的,而且经过"people.我屮艸芔茻"能够访问到Language表的属性 # 那么能够经过people.我屮艸芔茻.append将Language对象添加进去 people.我屮艸芔茻.append(language1) people.我屮艸芔茻.append(language2) # 那么我只须要提交people便可,会自动提交language1和language2 session.add(people) session.commit()
能够看到添加people的同时,language1和language2也被成功地添加进去了
经过往父表添加记录的同时,把子表的记录也添进去叫作正向添加。同理,若是在往子表添加记录的时候,把父表的记录也添加进去,叫作反向添加
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): # 这里把表名改为小写,否则和类名同样,容易引发歧义 __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) # 不可为空 age = Column(Integer, nullable=False) # 我把这行代码注释掉了 # 子表 = relationship("Language") def __str__(self): return f"{self.name}--{self.age}" class Language(Base): __tablename__ = "language" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(20), nullable=False) birthday = Column(Integer, nullable=False) type = Column(VARCHAR(20), nullable=False) # 这里的people.id指定的是数据库中people表的id字段 pid = Column(Integer, ForeignKey("people.id", ondelete="RESTRICT")) # 我在子表(或者说子表对应的模型)的relationship中指定了backref="我屮艸芔茻" # 就等价于在父表对应的模型中,指定了 我屮艸芔茻=relationship("Language") 父表 = relationship("People", backref="我屮艸芔茻") def __str__(self): return f"{self.name}--{self.birthday}--{self.type}" Session = sessionmaker(bind=engine) session = Session() people = People(id=7, name="松本行弘", age=53) language = Language(id=2333, name="ruby", birthday=1995, type="解释型", pid=7) """ 父表 = relationship("People", backref="我屮艸芔茻") 正向添加: people.我屮艸芔茻.append(language) 反向添加: language.父表 = people """ language.父表 = people # 只须要添加language便可 session.add(language) session.commit()
记录依旧能够添加成功
因为父表和字表能够是一对多,咱们经过父表来查询子表那么获得的是一个列表,哪怕只有一个元素,获得的依旧是一个列表。能够若是咱们已经知道只有一对一,不会出现一对多的状况,所以在获取经过父表获取子表记录的时候,获得的是一个值,而再也不是只有一个元素的列表,该怎么办呢? from sqlalchemy.orm import backref 父表 = relationship("People", backref=backref("我屮艸芔茻", uselist=False)) 这时候添加数据的时候,就再也不使用people.我屮艸芔茻.append(language)来添加了,由于是一对一。而是直接使用 people.我屮艸芔茻 = language来添加便可。
然而现实中,不少都是多对多的关系。好比博客园的文章,一个标签下可能会有多篇文章,同理一篇文章也可能会有多个标签。
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) # 既然要实现多对多,确定要借助第三张表 # sqlalchemy已经为咱们提供了一个Table,让咱们去使用 Article_Tag = Table("article_tag", Base.metadata, # 一个列叫作article_id,关联article里面的id字段 Column("article_id", Integer, ForeignKey("article.id"), primary_key=True), # 一个列叫作tag_id,关联tag里面的id字段 Column("tag_id", Integer, ForeignKey("tag.id"), primary_key=True) ) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) # 只须要在一个模型中,定义relationship便可,由于有反向引用。 # 经过Article().tags拿到对应的[tag1, tag2...],也能够经过Tag().articles拿到对应的[article1, article2] tags = relationship("Tag", backref="articles", secondary=Article_Tag) """ 或者在Tag中定义articles = relationship("Article", backref="tags", secondary=Article_Tag) 同样的道理,可是必定要指定中间表,secondary=Article_Tags """ class Tag(Base): __tablename__ = "tag" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(50), nullable=False) """ 总结一下: 1.先把两个须要多对多的模型创建出来 2.使用Table定义一个中间表,参数是:表名、Base.metadata、关联一张表的id的列、关联另外一张表的id的列。而且都作为主键 3.在两个须要作多对多的模型中随便选择一个模型,定义一个relationship属性,来绑定三者之间的关系,在使用relationship的时候,须要传入一个secondary="中间表" """ Base.metadata.create_all()
添加数据
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Article_Tag = Table("article_tag", Base.metadata, Column("article_id", Integer, ForeignKey("article.id"), primary_key=True), Column("tag_id", Integer, ForeignKey("tag.id"), primary_key=True) ) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) tags = relationship("Tag", backref="articles", secondary=Article_Tag) def __str__(self): return f"{self.title}" class Tag(Base): __tablename__ = "tag" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(50), nullable=False) def __str__(self): return f"{self.name}" Base.metadata.create_all() article1 = Article(title="article1") article2 = Article(title="article2") tag1 = Tag(name="tag1") tag2 = Tag(name="tag2") # 每一篇文章,添加两个标签 article1.tags.append(tag1) article1.tags.append(tag2) article2.tags.append(tag1) article2.tags.append(tag2) Session = sessionmaker(bind=engine) session = Session() # 只需添加article便可,tag会被自动添加进去 session.add_all([article1, article2]) session.commit()
获取数据
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Article_Tag = Table("article_tag", Base.metadata, Column("article_id", Integer, ForeignKey("article.id"), primary_key=True), Column("tag_id", Integer, ForeignKey("tag.id"), primary_key=True) ) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) tags = relationship("Tag", backref="articles", secondary=Article_Tag) def __str__(self): return f"{self.title}" class Tag(Base): __tablename__ = "tag" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(50), nullable=False) def __str__(self): return f"{self.name}" Session = sessionmaker(bind=engine) session = Session() # 获取tag表的第一行 tag = session.query(Tag).first() # 经过tag.articles获取对应的article表的内容 print([str(obj) for obj in tag.articles]) # ['article1', 'article2'] # 获取article表的第一行 article = session.query(Article).first() # 经过article.tags获取对应的tag表的内容 print([str(obj) for obj in article.tags]) # ['tag1', 'tag2'] # 能够看到数据所有获取出来了
咱们知道一旦关联,那么删除父表里面的数据是没法删除的,只能先删除字表的数据,而后才能删除关联的父表数据。若是在orm层面的话,能够直接删除父表数据,由于这里等同于两步。先将字表中关联的字段设置为NULL,而后删除父表中的数据
咱们将表所有删除,创建新表。
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) # uid是为了创建外键,咱们要和use表的id进行关联,因此类型也要和user表的id保持一致 uid = Column(Integer, ForeignKey("user.id")) # 这个是为了咱们可以经过一张表访问到另一张表 # 之后User对象即可以经过articles来访问Articles对象的属性,Article对象也能够经过author访问User对象的属性 author = relationship("User", backref="articles") Base.metadata.create_all() Session = sessionmaker(bind=engine) session = Session() # 建立几条记录 user = User(id=1, username="guido van rossum") article = Article(title="Python之父谈论python的将来", uid=1) article.author = user # 而后使用session添加article便可,会自动添加user session.add(article) session.commit()
咱们在数据库层面删除一下数据
咱们来试试从orm层面删除数据
删除父表的数据,这个过程至关于先将article中的uid设置为Null,而后删除父表的数据。可是这样也有危险,若是不熟悉sqlalchemy的话,会形成不可避免的后果,怎么办呢?直接将uid设置为不可为空便可便可, 加上nullable=False
orm层面的cascade:
首先咱们知道若是若是数据库的外键设置为RESTRICT,那么在orm层面,若是删除了父表的数据,字表的数据将会被设置为NULL,若是想避免这一点,那么只须要将nullable设置为False便可
可是在SQLAlchemy中,咱们只须要将一个数据添加到session中,提交以后,与其关联的数据也被自动地添加到数据库当中了,这是怎么办到的呢?实际上是经过relationship的时候,其关键字参数cascade设置了这些属性:
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("user.id")) # 其余不变,这里显示的指定了cascade="",那么便不会再使用默认地save-update了 author = relationship("User", backref="articles", cascade="") Base.metadata.drop_all() Base.metadata.create_all() Session = sessionmaker(bind=engine) session = Session() # 建立几条记录 user = User(username="guido van rossum") article = Article(title="Python之父谈论python的将来") article.author = user # 而后使用session添加article便可,此时就不会添加user了 session.add(article) session.commit()
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("user.id")) # 指定save-update和delete,以逗号分隔便可 author = relationship("User", backref="articles", cascade="save-update,delete") Base.metadata.drop_all() Base.metadata.create_all() Session = sessionmaker(bind=engine) session = Session() # 建立几条记录 user = User(username="guido van rossum") article = Article(title="Python之父谈论python的将来") article.author = user # 而后使用session添加article便可,此时就不会添加user了 session.add(article) session.commit()
如今添加数据是没问题的,可是删除数据呢?若是是默认状况的话,那么删除父表的记录会将字表对应的记录设为空,但若是删除子表的记录,是不会影响父表的。可如今我在cascade加上了delete,那么再删除子表中的记录呢?
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("user.id")) # 指定save-update和delete,以逗号分隔便可 author = relationship("User", backref="articles", cascade="save-update,delete") Session = sessionmaker(bind=engine) session = Session() article = session.query(Article).first() session.delete(article) session.commit()
此外能够在模型中定义
class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(50), nullable=False) age = Column(Integer) # 就以User为例,在SQLAlchemy中有一个属性叫作__mapper_args__ # 是一个字典,咱们能够指定一个叫作order_by的key,value则为一个字段 __mapper_args__ = {"order_by": age} # 倒序的话,__mapper_args__ = {"order_by": age.desc()} # 之后再查找的时候直接session.query(table).all()便可,自动按照咱们指定的排序
在一对一或者多对多的时候,若是想要获取多的这一部分数据的时候,每每经过一个属性就能够所有获取了。好比有一个做者,咱们要获取这个做者的全部文章,那么经过user.articles就能够所有获取了,可是有时咱们不想获取全部的数据,好比只获取这个做者今天发表的文章,那么这个时候就能够给relationship中的backref传递一个lazy=“dynamic”,之后经过user.articles获取到的就不是一个列表,而是一个AppendQuery对象了。之后就能够对这个对象再进行过滤和排序工做。
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) age = Column(Integer) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("user.id")) author = relationship("User", backref=backref("articles")) def __str__(self): return f"{self.title}" Base.metadata.drop_all() Base.metadata.create_all() Session = sessionmaker(bind=engine) session = Session() user = User(username="guido", age=63) for i in range(10): article = Article(title=f"title{i}") article.author = user session.add(article) session.commit() user = session.query(User).first() for art in user.articles: print(art) """ title0 title1 title2 title3 title4 title5 title6 title7 title8 title9 """
以上获取了所有数据,若是指向获取一部分呢?
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, Table from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) age = Column(Integer) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("user.id")) # 若是获取部分数据, 只须要加上一个lazy="dynamic"便可 # 注意这里必定要写在backref里面,咱们的目的是为了经过User模型实例取Article模型实例的属性,要写在backref里面 # 同理backref=backref("articles")和backref="articles"是同样的,可是之因此要加上里面的这个backref,是为了给user提供更好的属性,好比这里的懒加载 author = relationship("User", backref=backref("articles", lazy="dynamic")) def __str__(self): return f"{self.title}" Base.metadata.drop_all() Base.metadata.create_all() Session = sessionmaker(bind=engine) session = Session() user = User(username="guido", age=63) for i in range(10): article = Article(title=f"title{i}") article.author = user session.add(article) session.commit() user = session.query(User).first() print(user.articles) """ SELECT article.id AS article_id, article.title AS article_title, article.uid AS article_uid FROM article WHERE %(param_1)s = article.uid """ # 此时打印的是一个sql语句,若是不加lazy="dynamic"的话,打印的是一个列表,准确的说是InstrumentList对象,里面存储了不少的Article对象 # 可是如今再也不是了,加上lazy="dynamic"以后,获得的是一个AppendQuery对象。 # 能够对比列表和生成器,只有执行的时候才会产出值 print(type(user.articles)) # <class 'sqlalchemy.orm.dynamic.AppenderQuery'> # 查看一下源码发现,AppenderQuery这个类继承在Query这个类,也就是说Query可以使用的,它都能使用 # 而user.articles已是一个Query对象了,至关于session.query(XXX),能够直接调用filter print([str(obj) for obj in user.articles.filter(Article.id > 5).all()]) # ['title5', 'title6', 'title7', 'title8', 'title9'] # 也能够动态添加数据 article = Article(title="100") user.articles.append(article) # 这个时候不须要add,只须要commit便可,由于这个user已经在里面了 # 咱们append以后,只须要commit,那么append的新的article就提交到数据库里面了 session.commit() # 继续获取 print([str(obj) for obj in user.articles.filter(Article.id > 5).all()]) # ['title5', 'title6', 'title7', 'title8', 'title9', '100'] """ lazy有如下选择: 1.select:默认选项,以user.articles为例,若是没有访问user.articles属性,那么SQLAlchemy就不会从数据库中查找文章。一旦访问,就会查找全部文章,最为InstrumentList返回 2.dynamic:返回的不是一个InstrumentList,而是一个AppendQuery对象,相似一个生成器,能够动态添加,查找等等。 主要使用 """
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, Enum, func from sqlalchemy.orm import sessionmaker username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Session = sessionmaker(bind=engine) session = Session() class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) age = Column(Integer) # 注意:类型为Enum的时候,必定要指定name gender = Column(Enum("male", "female", "secret", default="male", name="我擦")) Base.metadata.drop_all() Base.metadata.create_all() user1 = User(username="神田空太", age=16, gender="male") user2 = User(username="椎名真白", age=16, gender="female") user3 = User(username="四方茉莉", age=400, gender="female") user4 = User(username="木下秀吉", age=15, gender="secret") user5 = User(username="牧濑红莉栖", age=18, gender="female") session.add_all([user1, user2, user3, user4, user5]) session.commit() # group_by:分组,比方说我想查看每一个年龄对应的人数 print(session.query(User.age, func.count(User.id)).group_by(User.age).all()) ''' 输出结果: [(16, 2), (400, 1), (15, 1), (18, 1) ''' # having:在group_by分组的基础上进行进一步查询,比方说我想查看年龄大于16的每个年龄段对应的人数 print(session.query(User.age, func.count(User.id)).group_by(User.age).having(User.age > 16).all()) ''' 输出结果: [(18, 1), (400, 1)] ''' # 想查看人数大于1的那一组 print(session.query(User.age, func.count(User.id)).group_by(User.age).having(func.count(User.id) > 1).all()) """ 输出结果: [(16, 2)] """
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Session = sessionmaker(bind=engine) session = Session() class People(Base): __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("people.id")) author = relationship("People", backref=backref("articles", lazy="dynamic")) def __repr__(self): return f"{self.title}" Base.metadata.drop_all() Base.metadata.create_all() people1 = People(username="guido") people2 = People(username="ken") article1 = Article(title="python") article1.author = people1 article2 = Article(title="B") article2.author = people2 article3 = Article(title="go") article3.author = people2 session.add_all([article1, article2, article3]) session.commit()
此时两张表创建完成,数据已经添加成功
先在数据库层面上进行查询,查询每一个做者发表了多少文章
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, func from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Session = sessionmaker(bind=engine) session = Session() class People(Base): __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) username = Column(VARCHAR(50), nullable=False) class Article(Base): __tablename__ = "article" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(50), nullable=False) uid = Column(Integer, ForeignKey("people.id")) author = relationship("People", backref=backref("articles", lazy="dynamic")) def __repr__(self): return f"{self.title}" # 找到全部用户,按照发表文章的数量进行排序 # 此外还能够为字段起一个别名,好比把筛选出来的username改成姓名,能够调用People.username.label("姓名") # 若是不指定别名,那么sqlachemy会默认将类名、下划线、字段名进行组合做为别名, res = session.query(People.username, func.count(Article.id)).join(Article, People.id == Article.uid).\ group_by(People.id).order_by(func.count(Article.id)) print(res) """ SELECT people.username AS people_username, count(article.id) AS count_1 FROM people JOIN article ON people.id = article.uid GROUP BY people.id ORDER BY count(article.id) """ res = session.query(People.username.label("姓名"), func.count(Article.id).label("次数")).join(Article, People.id == Article.uid).\ group_by(People.id).order_by("次数") print(res) """ SELECT people.username AS "姓名", count(article.id) AS "次数" FROM people JOIN article ON people.id = article.uid GROUP BY people.id ORDER BY "次数" """ print(res.all()) # [('guido', 1), ('ken', 2)]
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, func from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Session = sessionmaker(bind=engine) session = Session() class Girl(Base): __tablename__ = "girl" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(50), nullable=False) anime = Column(VARCHAR(50), nullable=False) age = Column(Integer, nullable=False) def __repr__(self): return f"{self.name}--{self.anime}--{self.age}" Base.metadata.create_all() girl1 = Girl(name="雨宫优子", anime="悠久之翼", age=16) girl2 = Girl(name="宫村宫子", anime="悠久之翼", age=16) girl3 = Girl(name="古河渚", anime="clannad", age=19) girl4 = Girl(name="牧濑红莉栖", anime="命运石之门", age=18) session.add_all([girl1, girl2, girl3, girl4]) session.commit()
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, func from sqlalchemy.orm import sessionmaker, relationship, backref username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) Session = sessionmaker(bind=engine) session = Session() class Girl(Base): __tablename__ = "girl" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(VARCHAR(50), nullable=False) anime = Column(VARCHAR(50), nullable=False) age = Column(Integer, nullable=False) def __repr__(self): return f"{self.name}--{self.anime}--{self.age}" # 下面要寻找和雨宫优子在同一anime,而且age相同的记录,固然这里只有一条 girl = session.query(Girl).filter(Girl.name == "雨宫优子").first() # filter里面若是没有指定and_或者or_,那么默认是and_ expect_girls = session.query(Girl).filter(Girl.anime == girl.anime, Girl.age == girl.age, Girl.name != "雨宫优子").all() print(expect_girls) # [宫村宫子--悠久之翼--16]
这种查找方式,等于先筛选出name="雨宫优子"对应的记录,而后再从全表搜索出Girl.anime == girl.anime而且Girl.age == girl.age的记录,写成sql的话就相似于
也可使用subquery
# 建立一个subquery girl = session.query(Girl).filter(Girl.name == "雨宫优子").subquery() # 这里的girls.c的c代指的是column,是一个简写 expect_girls = session.query(Girl).filter(Girl.anime == girl.c.anime, Girl.age == girl.c.age, Girl.name != "雨宫优子").all() print(expect_girls) # [宫村宫子--悠久之翼--16]
能够看到事实上没太大区别,貌似代码量还多了一丢丢。可是在数据库里面,咱们只须要进行一次查询,效率会高一些
flask-sqlalchemy是flask的一个插件,能够更加方便咱们去使用。sqlalchemy是能够独立于flask而存在的,这个插件是将sqlalchemy集成到flask里面来。咱们以前使用sqlalchemy的时候,要定义Base,session,各个模型之类的,使用这个插件能够简化咱们的工做。
这个插件首先须要安装,直接pip install flask-sqlalchemy便可。你们注意到没,flask虽然自己内容较少,可是有不少的第三方插件,扩展性极强。其实flask写到最后,感受和Django没太大区别了。提到框架,首先想到的就是flask、Django、tornado,其中tornado是异步的。这里也主要想说的就是python中的异步框架,自从python3.5引入了async和await关键字、能够定义原生协程以后,python中的异步框架也是层出不穷,可是并无一个怪兽级别的一步框架一统江湖。python中的tornado是一个,还有一个sanic,这是一个仿照flask接口设计的异步框架,只能部署在linux上,听说能够达到媲美go语言的性能,具体没有测试过,但缺点是第三方扩展没有flask这么多,质量也良莠不齐。但愿python能出现优秀的异步框架,目前的话仍是推荐tornado,由于在python尚未引入原生协程的时候,就有了tornado,当时tornado是本身根据生成器实现协程、手动实现了一套事件循环机制。可是当python引入了原生的协程以后,咱们在定义视图类的时候,就不须要在使用装饰器@tornado.gen.coroutine了,直接经过async def来定义函数便可,也不须要yield了,直接await便可,并且底层的事件循环也再也不使用原来的哪一套了,而是直接使用的asyncio。目前异步框架,仍是推荐tornado。
flask-sqlalchemy也是能够独立于flask而存在的,或者不以web的方式。
from flask import Flask from flask_sqlalchemy import SQLAlchemy username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 db_uri = f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}" # 这个配置不直接与咱们的SQLAlchemy这个类发生关系,而是要添加到app.config里面,这一步是少不了的 # 至于这里的key,做者规定就是这么写的 app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = db_uri # 而且还要加上这一段,否则会弹出警告 app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # 接收一个app,今后db便具备了app的功能,但咱们只用数据库的功能 db = SQLAlchemy(app) # 创建模型,确定要继承,那么继承谁的,继承自db.Module,至关于以前的Base。这里操做简化了,不须要咱们去建立了 class User(db.Model): __tablename__ = "user" # 能够看到,以前须要导入的统统不须要导入了,都在db下面。不过本质上调用的仍是sqlalchemy模块里的类。 id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(50), nullable=False) class Article(db.Model): __tablename__ = "article" id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String(50), nullable=False) uid = db.Column(db.Integer, db.ForeignKey("user.id")) author = db.relationship("User", backref="articles") # 因此咱们发现这和SQLAlchemy框架中的使用方法基本上是一致的,只不过咱们在SQLAlchemy中须要导入的,如今所有能够经过db来访问 # 那么如何映射到数据库里面呢?这里也不须要Base.metadata了,直接使用db便可。 db.create_all() # 下面添加数据 user = User(name="guido") article = Article(title="python之父谈python的将来") user.articles.append(article) # 这里的session也不须要建立了,由于在app中咱们指定了SQLALCHEMY_DATABASE_URI # 会自动根据配置建立session db.session.add(user) ''' 或者 article.author = user db.session.add(article) ''' db.session.commit() # 跟咱们以前使用SQLAlchemy的流程基本一致
那么问题来了,如何查找数据呢?首先咱们能够想到db.session.query(User),这毫无疑问是能够的,可是咱们的模型继承了db.Model,那么咱们有更简单的方法
# 直接使用User.query便可,就等价于db.session.query(User) user = User.query.first() print(user.name) # guido print(user.articles[0].title) # python之父谈python的将来 ''' 能够看到,使用方法和session.query(Model)没有啥区别 '''
alembic是SQLAlchemy做者所写的一款用于作ORM与数据库的迁移和映射的一个框架,相似于git。
首先确定要安装,pip install alembic
注意这个目录
进入文件夹里面,输入alembic init xxxx
接下来创建模型,这个是独立于flask的,因此咱们此次仍是使用SQLAlchemy作演示
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import create_engine, func from sqlalchemy import Column, Integer, String username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(20), nullable=False) # 不可为空 age = Column(Integer, nullable=False) def __repr__(self): return f"{self.name}--{self.age}"
下面修改配置文件,alembic.ini
而后修改env.py
接下来生成迁移文件,alembic revision --autogenerate -m "message",这里的"message"是咱们的注释信息
下面就要更新数据库了,将刚才生成的迁移文件映射到数据库。至于为何须要迁移文件,那是由于没法直接映射orm模型,须要先转化为迁移文件,而后才能映射到数据库当中。
alembic upgrade head,将刚刚生成的迁移文件映射到数据库当中
若是须要修改表的结构,不须要再drop_all,create_all了,若是里面有大量数据,不可能清空以后从新建立。那么在修改以后,直接再次生成迁移文件而后映射到数据库就能够了
先来看看数据库的表
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import create_engine, func from sqlalchemy import Column, Integer, String username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 engine = create_engine(f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}") Base = declarative_base(bind=engine) class People(Base): __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(20), nullable=False) age = Column(Integer, nullable=False) serifu = Column(String(100)) # 增长一列 def __repr__(self): return f"{self.name}--{self.age}"
我给模型添加了一个字段serifu,而后从新生成迁移文件,并再次映射
能够看到,自动增长了一列,而且原来的数据也没有被破坏。所以也能够发现,咱们再增长列的时候,不能设置nullable=False,否则添加不进去
class People(Base): __tablename__ = "people" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(20), nullable=False) # 不可为空 age = Column(Integer, nullable=False) serifu = Column(String(100), nullable=False) # 不添加默认值,设置不能为空 def __repr__(self): return f"{self.name}--{self.age}"
总结一下,就是五个步骤:
能够看出,和Django比较相似,若是之后修改了模型的话,那么重复4和5便可
经常使用命令:
init:建立一个alembic仓库
revision:建立一个新的版本文件
--autogenerate:自动将当前模型的修改,生成迁移脚本
-m:本次迁移作了哪些修改,用户能够指定这个参数,方便回顾
upgrade:将指定版本的迁移文件映射到数据库中,会执行版本文件中的upgrade函数。若是有多个迁移脚本没有被映射到数据库中,那么会执行多个迁移脚本
[head]:表明最新的迁移脚本的版本号
downgrade:降级,咱们每个迁移文件都有一个版本号,若是想退回之前的版本,直接使用alembic downgrade version_id
heads:展现head指向的脚本文件
history:列出全部的迁移版本及其信息
current:展现当前数据库的版本号
经典错误:
FAILED:Target database is not up to date。缘由:主要是heads和current不相同。current落后于heads的版本。解决办法:将current移动到head上,alembic upgrade head
FAILED:can’t locate revision identified by “78ds75ds7s”。缘由:数据库中村的版本号不在迁移脚本文件中。解决办法:删除数据库中alembic_version表的数据,而后从新执行alembic upgrade head
执行upgrade head 时报某个表已经存在的错误。解决办法:1.删除version中全部的迁移文件的代码,修改迁移脚本中建立表的代码
flask-script的做用是能够经过命令行的方式来操做flask。例如经过命令来跑一个开发版本的服务器,设置数据库,定时任务等等。要使用的话,首先要安装,pip install flask-script
而后要作什么呢?
from flask_script import Manager from app import app # 传入app。生成manager manager = Manager(app) # 加上一个manager.command,使其成为命令行 @manager.command def hello(): print("你好啊") # 也能够添加参数,而且此时manager.command也能够不要了 @manager.option("--name", dest="username") @manager.option("--age", dest="age") def foo(username, age): # 相似于python里的optparse,能够见个人python经常使用模块,里面有介绍 return f"name={username}, age={age}" if __name__ == '__main__': manager.run() # 这里是manager.run,不是app.run
start.py
from flask import Flask app = Flask(__name__) username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 app.config["SQLALCHEMY_DATABASE_URI"] = f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}" @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run()
model.py
from app import app from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy(app) # 话说回来,这个db.Model就至关于以前的Base # 咱们将env里面的target_metadata = None,也能够换成db.Model.metadata class User(db.Model): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.VARCHAR(100)) age = db.Column(db.Integer) class Score(db.Model): __tablename__ = "score" id = db.Column(db.Integer, primary_key=True, autoincrement=True) math = db.Column(db.Integer) english = db.Column(db.Integer) history = db.Column(db.Integer)
manage.py
from flask_script import Manager from app import app # 从模型里面导入User和Score模型,以及db from model import User, Score, db # 传入app。生成manager manager = Manager(app) # 建立表 @ manager.command def create_table(): db.create_all() # 往user表里面添加数据 @manager.option("--n", dest="name") @manager.option("--a", dest="age") def add_user(name, age): user = User() user.name = name user.age = age db.session.add(user) db.session.commit() # 往score表里面添加数据 @manager.option("--m", dest="math") @manager.option("--e", dest="english") @manager.option("--h", dest="history") def add_score(math, english, history): score = Score() score.math = math score.english = english score.history = history db.session.add(score) db.session.commit() if __name__ == '__main__': manager.run() # 这里是manager.run,不是app.run
生成表
给user表添加数据
给score表添加数据
并且咱们还能够模拟数据库迁移,映射等等。Django的manage.py不就是这么作的吗?python manage.py makemigrations迁移,而后再python manage.py migrate映射。
而这种方式的实现,flask也帮咱们封装好了
在实际的数据库开发中,常常会出现数据表修改的行为。通常咱们不会手动修改数据库,而是去修改orm模型,而后再把模型映射到数据库中。这个时候若是能有一个工具专门作这件事情就很是好了,而flask-migrate就是用来干这个的。flask-migrate是基于alembic的一个封装,并集成到flask当中,而全部的操做都是alembic作的,它能跟踪模型的变化,并将模型映射到数据库中。
显然也要安装,pip install flask-migrate,因此flask有着丰富的第三方插件,能够本身定制。因此写到最后,真的和Django没啥区别了。
model.py
from flask_script import Manager from app import app # 从模型里面导入User和Score模型,以及db from model import User, Score, db from flask_migrate import Migrate, MigrateCommand # 传入app。生成manager manager = Manager(app) # 传入app和db,将app和db绑定在一块儿 migrate = Migrate(app, db) # 把MigrateCommand命令添加到manager中 manager.add_command("db", MigrateCommand) if __name__ == '__main__': # 此时就能够了,你们可能看到咱们这里导入了User和Score模型,可是没有使用 # 其实不是,生成数据库的表,是在命令行中操做的 # 为了在映射的时候可以找到这两个模型,因此要导入,只不过找模型咱们不须要作,flask-migrate会自动帮咱们处理 # 咱们只负责导入就能够了 manager.run() # 这里是manager.run,不是app.run
执行python manage.py db init。这里的db就是咱们manager.add_command("db", MigrateCommand)的db,咱们也能够起其余的名字
此时里面的文件,咱们也不须要手动去改了,都帮咱们弄好了
python manage.py db migrate,生成迁移文件
python manage.py db upgrade,将迁移文件映射到数据库中
总结以下: 介绍:由于采用db.create_all在后期修改字段的时候,不会自动的映射到数据库中,必须删除表,而后从新运行db.craete_all,或者先db.drop_all,才会从新映射,这样不符合咱们的需求。所以flask-migrate就是为了解决这个问题,它能够在每次修改模型后,能够将修改的东西映射到数据库中。 使用flask_migrate必须借助flask_scripts,这个包的MigrateCommand中包含了全部和数据库相关的命令。 flask_migrate相关的命令: python manage.py db init:初始化一个迁移脚本的环境,只须要执行一次。 python manage.py db migrate:将模型生成迁移文件,只要模型更改了,就须要执行一遍这个命令。 python manage.py db upgrade:将迁移文件真正的映射到数据库中。每次运行了migrate命令后,就记得要运行这个命令。 注意点:须要将你想要映射到数据库中的模型,都要导入到manage.py文件中,若是没有导入进去,就不会映射到数据库中。
咱们来使用web的方式,添加数据吧
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/add" method="post"> <table> <tr> <td>用户名:</td> <td><input name="username" type="text"/></td> </tr> <tr> <td>年龄:</td> <td><input name="age" type="text"/></td> </tr> <tr> <td>提交:</td> <td><input type="submit"/></td> </tr> </table> </form> </body> </html>
咱们将代码的结构从新修改一下
config.py:存放配置
username = "postgres" # 用户名 password = "zgghyys123" # 密码 hostname = "localhost" # ip port = 5432 # 端口 db_type = "postgresql" # 数据库种类 driver = "psycopg2" # 驱动 database = "postgres" # 链接到哪一个数据库 SQLALCHEMY_DATABASE_URI = f"{db_type}+{driver}://{username}:{password}@{hostname}:{port}/{database}" SQLALCHEMY_TRACK_MODIFICATIONS = False
exts.py
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()
model.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.VARCHAR(100)) age = db.Column(db.Integer) class Score(db.Model): __tablename__ = "score" id = db.Column(db.Integer, primary_key=True, autoincrement=True) math = db.Column(db.Integer) english = db.Column(db.Integer) history = db.Column(db.Integer)
看到这里可能发现了,这里的db并无传入app啊,那么它是如何找到的呢?别急
manage.py
from flask_script import Manager from app import app from model import User, Score from exts import db from flask_migrate import Migrate, MigrateCommand # 传入app。生成manager manager = Manager(app) # 传入app和db,将app和db绑定在一块儿 migrate = Migrate(app, db) # 把MigrateCommand命令添加到manager中 manager.add_command("db", MigrateCommand) if __name__ == '__main__': manager.run()
app.py
from flask import Flask, request, render_template from model import User from exts import db import config app = Flask(__name__) # 导入配置 app.config.from_object(config) # 经过db.init_app(app)会自动地将app里面的信息绑定到db里面去 db.init_app(app) @app.route('/') def hello_world(): return 'Hello World!' @app.route("/add", methods=["GET", "POST"]) def add(): if request.method == 'GET': return render_template("add.html") else: name = request.form.get("username", None) age = request.form.get("age", None) user = User() user.name = name user.age = age db.session.add(user) db.session.commit() return f"数据添加成功" if __name__ == '__main__': app.run()
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中国工商银行首页</title> </head> <body> <h1>欢迎来到中国工商银行</h1> <ul> <li><a href="{{ url_for('register') }}">当即注册</a></li> </ul> </body> </html>
register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中国银行注册页面</title> </head> <body> <form action="/register" method="post"> <table> <tbody> <tr> <td>邮箱:</td> <td><input type="email" name="email"></td> </tr> <tr> <td>用户名:</td> <td><input type="text" name="username"></td> </tr> <tr> <td>密码:</td> <td><input type="password" name="password"></td> </tr> <tr> <td>重复密码:</td> <td><input type="password" name="repeat_password"></td> </tr> <tr> <td>余额:</td> <td><input type="text", name="deposit"></td> </tr> <tr> <td></td> <td><input type="submit" value="当即注册"></td> </tr> </tbody> </table> </form> </body> </html>
exts.py
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()
model.py
from exts import db class User(db.Model): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True, autoincrement=True) email = db.Column(db.String(50), nullable=False) username = db.Column(db.String(50), nullable=False) password = db.Column(db.String(50), nullable=False) deposit = db.Column(db.Float(50), default=10)
manage.py
from flask_script import Manager from app import app from model import User from exts import db from flask_migrate import Migrate, MigrateCommand # 传入app。生成manager manager = Manager(app) # 传入app和db,将app和db绑定在一块儿 migrate = Migrate(app, db) # 把MigrateCommand命令添加到manager中 manager.add_command("db", MigrateCommand) if __name__ == '__main__': manager.run()
forms.py
from wtforms import Form, StringField, FloatField from wtforms.validators import Length, EqualTo, Email, InputRequired class RegisterForm(Form): email = StringField(validators=[Email()]) username = StringField(validators=[Length(6, 20)]) password = StringField(validators=[Length(6, 20)]) repeat_password = StringField(validators=[EqualTo("password")]) deposit = FloatField(validators=[InputRequired()])
app.py
from flask import Flask, render_template, request, views from forms import RegisterForm from exts import db from model import User import config app = Flask(__name__) app.config.from_object(config) db.init_app(app) # 这个和db = SQLAlchemy(app)效果是同样的 @app.route('/') def index(): return render_template("index.html") class RegisterView(views.MethodView): def get(self): return render_template("register.html") def post(self): form = RegisterForm(request.form) if form.validate(): email = form.email.data username = form.username.data password = form.password.data deposit = form.deposit.data user = User(email=email, username=username, password=password, deposit=deposit) db.session.add(user) db.session.commit() return "注册成功" else: return f"注册失败,{form.errors}" app.add_url_rule("/register", view_func=RegisterView.as_view("register")) if __name__ == '__main__': app.run()
将以前的表所有删除,而后执行python manage.py db init,而后执行python manage.py db migrate 而后执行python manage.py db upgrade,而后会发现数据库多了一张user表
访问localhost:5000
发现数据已经被添加到数据库里面了
首页,index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中国工商银行首页</title> </head> <body> <h1>欢迎来到中国工商银行</h1> <ul> <li><a href="{{ url_for('register') }}">当即注册</a></li> <li><a href="{{ url_for('login') }}">当即登陆</a></li> <li><a href="{{ url_for('transfer') }}">当即转帐</a></li> </ul> </body> </html>
注册页面,register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中国银行注册页面</title> </head> <body> <form action="/register" method="post"> <table> <tbody> <tr> <td>邮箱:</td> <td><input type="email" name="email"></td> </tr> <tr> <td>用户名:</td> <td><input type="text" name="username"></td> </tr> <tr> <td>密码:</td> <td><input type="password" name="password"></td> </tr> <tr> <td>重复密码:</td> <td><input type="password" name="repeat_password"></td> </tr> <tr> <td>余额:</td> <td><input type="text", name="deposit"></td> </tr> <tr> <td></td> <td><input type="submit" value="当即注册"></td> </tr> </tbody> </table> </form> </body> </html>
登陆页面,login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>中国工商银行登陆页面</title> </head> <body> <form action="/login" method="post"> <table> <tbody> <tr> <td>邮箱:</td> <td><input name="email" type="email"></td> </tr> <tr> <td>密码:</td> <td><input name="password" type="password"></td> </tr> <tr> <td></td> <td><input type="submit" value="当即登陆"></td> </tr> </tbody> </table> </form> </body> </html>
转帐页面,transfer.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/transfer" method="post"> <table> <tbody> <tr> <td>转到邮箱:</td> <td><input type="email" name="email"></td> </tr> <tr> <td>转帐金额:</td> <td><input type="text" name="money"></td> </tr> <tr> <td></td> <td><input type="submit" value="当即转帐"></td> </tr> </tbody> </table> </form> </body> </html>
py文件只有forms.py和app.py发生了变化,其余的没变
forms.py
from wtforms import Form, StringField, FloatField from wtforms.validators import Length, EqualTo, Email, InputRequired, NumberRange from model import User class RegisterForm(Form): email = StringField(validators=[Email()]) username = StringField(validators=[Length(6, 20)]) password = StringField(validators=[Length(6, 20)]) repeat_password = StringField(validators=[EqualTo("password")]) deposit = FloatField(validators=[InputRequired()]) class LoginForm(Form): email = StringField(validators=[Email()]) password = StringField(validators=[Length(6, 20)]) class TransferForm(Form): email = StringField(validators=[Email()]) money = FloatField(validators=[NumberRange(min=1, max=10000)])
app.py
from flask import Flask, render_template, request, views, session, redirect, url_for from forms import RegisterForm, LoginForm, TransferForm from exts import db from model import User import secrets app = Flask(__name__) app.config["SECRET_KEY"] = secrets.token_bytes() app.config.from_object(config) db.init_app(app) # 这个和db = SQLAlchemy(app)效果是同样的 @app.route('/') def index(): return render_template("index.html") # 注册 class RegisterView(views.MethodView): def get(self): return render_template("register.html") def post(self): form = RegisterForm(request.form) if form.validate(): email = form.email.data username = form.username.data password = form.password.data deposit = form.deposit.data user = User(email=email, username=username, password=password, deposit=deposit) db.session.add(user) db.session.commit() return "注册成功" else: return f"注册失败,{form.errors}" app.add_url_rule("/register", view_func=RegisterView.as_view("register")) # 登陆 class LoginView(views.MethodView): def get(self): return render_template("login.html") def post(self): form = LoginForm(request.form) if form.validate(): email = form.email.data password = form.password.data user = User.query.filter(User.email == email, User.password == password).first() if user: session["session_id"] = user.id return "登陆成功" else: return "邮箱或密码错误" else: return f"{form.errors}" app.add_url_rule("/login", view_func=LoginView.as_view("login")) # 转帐 class TransferView(views.MethodView): def get(self): # 只有登陆了才能转帐,不然让其滚回登陆页面 if session.get("session_id"): return render_template("transfer.html") else: return redirect(url_for("login")) def post(self): form = TransferForm(request.form) if form.validate(): email = form.email.data money = form.money.data user = User.query.filter_by(email=email).first() if user: session_id = session.get("session_id") myself = User.query.get(session_id) if myself.deposit >= money: user.deposit += money myself.deposit -= money db.session.commit() return f"转帐成功,您向{user.email}转了{money}" else: return "您的资金不足,没法完成当前转帐" else: return "该用户不存在" else: return "数据填写不正确" app.add_url_rule("/transfer", view_func=TransferView.as_view("transfer")) if __name__ == '__main__': app.run()
下面进行转帐
咱们接下来看看数据库里面的金额
此时继续转帐
显示资金不够了