5.flask與數據庫

1.安裝postgresql

注意:在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



2.SQLAlchemy的介紹和基本使用

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



3.orm介紹

orm: object relation mapping,就是能夠把咱們寫的類轉化成表。將類裏面的元素映射到數據庫表裏面程序員



4.定義orm模型並映射到數據庫中

剛纔咱們已經成功的建立了一張表了,可是咱們發現實際上咱們寫的仍是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()



5.SQLAlchemy對數據的增刪改查操做

將表刪除,從新映射數據庫

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()

能夠看到,數據庫裏面的數據全沒了。爲了後面介紹其餘方法,咱們再將數據從新寫上去。



6.SQLAlchemy屬性經常使用數據類型

  • Integer:整形,映射到數據庫是int類型   
  • Float:浮點型,映射到數據庫是float類型,佔32位。   
  • Boolean:布爾類型,傳遞True or False,映射到數據庫是tinyint類型   
  • DECIMAL:頂點類型,通常用來解決浮點數精度丟失的問題   
  • Enum:枚舉類型,指定某個字段只能是枚舉中指定的幾個值,不能爲其餘值,   
  • Date:傳遞datetime.date()進去,映射到數據庫中是Date類型   
  • DateTime:傳遞datetime.datetime()進去,映射到數據庫中是datatime類型   
  • String|VARCHAR:字符類型,映射到數據庫中varchar類型   
  • Text:文本類型,能夠存儲很是長的文本   
  • LONGTEXT:長文本類型(只有mysql纔有這種類型),能夠存儲比Text更長的文本


7.Column經常使用參數

  • default:默認值,若是沒有傳值,則自動爲咱們指定的默認值   
  • nullable:是否可空,False的話,表示不能夠爲空,那麼若是不傳值則報錯   
  • primary_key:是否爲主鍵   
  • unique:是否惟一,若是爲True,表示惟一,那麼傳入的值存在的話則報錯   
  • autoincrement:是否自動增加   
  • name:該屬性在數據庫中的字段映射,若是不寫,那麼默認爲賦值的變量名。

8.query查詢

  • 模型,指定查找這個模型中全部的對象

  • 模型中的屬性,能夠指定查找模型的某幾個屬性

  • 聚合函數

    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,)


9.filter方法經常使用過濾條件

爲了演示,我將表刪除從新建立,添加新的數據

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',)
('動漫地靈殿',)
('櫻花莊的寵物女孩',)
"""


10.外鍵及其四種約束

在mysql、postgresql等數據庫中,外鍵可使表之間的關係更加緊密。而SQLAlchemy中也一樣支持外鍵,經過ForforeignKey來實現,而且能夠指定表的外鍵約束

外鍵約束有如下幾種:

  • 1.RESTRICT:父表數據被刪除,會阻止刪除
  • 2.No Action:在postgresql中,等價於RESTRICT
  • 3.CASCADE:級聯刪除
  • 4.SET NULL:父表數據被刪除,字表數據被設置爲NULL

既然如此的話,咱們是否是要有兩張表啊,我這裏新建立兩張表

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



11.ORM層外鍵和一對多關係

當一張表關聯了另外一張表的外鍵,咱們能夠根據子表中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--解釋型
"""

依舊是能夠訪問成功的



12.一對一關係實現

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來添加便可。


13.多對多關係實現

然而現實中,不少都是多對多的關係。好比博客園的文章,一個標籤下可能會有多篇文章,同理一篇文章也可能會有多個標籤。

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']

# 能夠看到數據所有獲取出來了


14.ORM層面刪除數據注意事項

咱們知道一旦關聯,那麼刪除父表裏面的數據是沒法刪除的,只能先刪除字表的數據,而後才能刪除關聯的父表數據。若是在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



15.relationship中的cascade屬性

orm層面的cascade:

首先咱們知道若是若是數據庫的外鍵設置爲RESTRICT,那麼在orm層面,若是刪除了父表的數據,字表的數據將會被設置爲NULL,若是想避免這一點,那麼只須要將nullable設置爲False便可

可是在SQLAlchemy中,咱們只須要將一個數據添加到session中,提交以後,與其關聯的數據也被自動地添加到數據庫當中了,這是怎麼辦到的呢?實際上是經過relationship的時候,其關鍵字參數cascade設置了這些屬性:

  • 1.save-update:默認選項,在添加一條數據的時候,會自動把與其相關聯的數據也添加到數據庫當中。這種行爲就是save-update所影響的
  • 2.delete:表示刪除某一個模型的數據時,是否也刪掉使用relationship與其相關聯的數據。
  • 3.delete-orphan:表示當對一個orm對象解除了父表中的關聯對象的時候,本身便會被刪掉。固然父表中的數據被刪除了,本身也會被刪除。這個選項只能用在一對多上,不能用在多對多以及多對一上。而且還須要在子模型的relationship中,添加一個single_parent=True的選項
  • 4.merge:默認選項,當使用session.merge選項合併一個對象的時候,會將使用了relationship相關聯的對象也進行merge操做
  • 5.expunge:移除操做的時候,會將相關聯的對象也進行移除。這個操做只會從session中刪除,並不會從數據庫當中移除
  • 6.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)


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()



16.排序

  • 正序:session.query(Model).order_by("table.column").all()
  • 倒敘:session.query(Model).order_by("table.column.desc").all()

此外能夠在模型中定義

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()便可,自動按照咱們指定的排序


17.limit、offset以及切片操做

  • limit:session.query(Model).limit(10).all 從開頭開始取十條數據
  • offset:session.query(Model).offset(10).limit(10).all() 從第十條開始取10條數據
  • slice:session.query(Model).slice(1,8).all() 從第一條開始取到第七條數據,或者session.query(Model)[1:8],這樣更簡單,連all()都不用了


18.數據查詢懶加載技術

在一對一或者多對多的時候,若是想要獲取多的這一部分數據的時候,每每經過一個屬性就能夠所有獲取了。好比有一個做者,咱們要獲取這個做者的全部文章,那麼經過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對象,相似一個生成器,能夠動態添加,查找等等。
主要使用
"""


19.group_by和having字句

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)]
"""


20.join實現複雜查詢

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)]


21.subquery實現複雜查詢

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]

能夠看到事實上沒太大區別,貌似代碼量還多了一丟丟。可是在數據庫裏面,咱們只須要進行一次查詢,效率會高一些



22.flask-sqlalchemy的使用

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)沒有啥區別
'''


23.alembic數據庫遷移工具的使用

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}"

總結一下,就是五個步驟:

  • 1.定義好本身的模型
  • 2.使用alembic init 倉庫名,新建一個倉庫,會在當前路徑生成
  • 3.修改兩個配置文件,在alembic.ini中配置數據庫的鏈接方式,在env中將model導入,再將target_metadata = None換成model裏面的Base.metadata
  • 4.使用alembic revision --autogenerate -m "message"生成遷移文件
  • 5.使用alembic upgrade head將遷移文件映射到數據庫

能夠看出,和Django比較相似,若是之後修改了模型的話,那麼重複4和5便可



24.alembic經常使用命令和經典錯誤解決辦法

經常使用命令:

  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中全部的遷移文件的代碼,修改遷移腳本中建立表的代碼



25.flask-script講解

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

將flask-script和alembic集成

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也幫咱們封裝好了



26.flask-migrate

在實際的數據庫開發中,常常會出現數據表修改的行爲。通常咱們不會手動修改數據庫,而是去修改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()



27.實戰項目-中國工商銀行註冊功能

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

發現數據已經被添加到數據庫裏面了



28.實戰項目-中國工商銀行登陸和轉帳實現

首頁,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()

下面進行轉帳

咱們接下來看看數據庫裏面的金額

此時繼續轉帳

顯示資金不夠了

相關文章
相關標籤/搜索