5.django-模型ORM

 

 Django中內嵌了ORM框架,不需要直接編寫SQL語句進行資料庫的操作,通過定義模型類來完成對資料庫中表的操作

O:Object,也就是類對象的意思

R:Relation,關係資料庫中表的意思

M:Mapping:映射
模型類:映射的是sql語句中的table表

類對象:映射表中的某一行數據

類成員:映射表的欄位

ORM的優點:

  • 定義模型類,更加容易維護

  • 不必編寫複雜的SQL語句,開發效率高

  • 兼容多種資料庫,可以自由切換資料庫

ORM的缺點:

  • ORM不是輕量級工具,需要花費較大的精力學習

  • 性能相對原生的SQL差一些

ORM的使用主要以下四步

1. 配置資料庫的連接
2. 在model.py中定義模型類
3. 生成資料庫遷移文件並執行遷移文件
4. 通過模型類對象提供的方法操作資料庫

1.配置資料庫連接

1.1 配置單個mysql資料庫

django中默認配置的資料庫是sqlite,如果想切換為mysql,操作如下

  1. 安裝pymysql模組
    pip install pymysql
  2. 在工程項目的__init__.py文件中添加如下語句
    from pymysql import install_as_MySQLdb
    install_as_MySQLdb() # 讓pymysql以MySQLDB的運行模式和Django的ORM對接運行
  3. 修改setting.py中的配置如下
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql', # 指定mysql引擎
            'HOST': '127.0.0.1',  # 資料庫主機
            'PORT': 3306,  # 資料庫埠
            'USER': 'root',  # 資料庫用戶名
            'PASSWORD': '123',  # 資料庫用戶密碼
            'NAME': 'student'  # 資料庫名字
        }
    }
  4. 在mysql中創建對應的資料庫
    ORM 無法主動創建資料庫,我們需要在連接的資料庫中先創建資料庫
    create database student default charset=utf8mb4; # mysql8.0之前的版本
  5. 如果想列印orm轉換過程中的sql,需要在settings.py中進行如下配置
    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            'console':{
                'level':'DEBUG',
                'class':'logging.StreamHandler',
            },
        },
        'loggers': {
            'django.db.backends': {
                'handlers': ['console'],
                'propagate': True,
                'level':'DEBUG',
            },
        }
    }  

     

1.2 配置多個mysql資料庫

jango支援連接多個mysql資料庫

主要在配置文件中添加如下(在上面的基礎上)

DATABASES = {
    "default": {
        'ENGINE': 'dj_db_conn_pool.backends.mysql',
        'NAME': 'day05db',  # 資料庫名字
        'USER': 'root',
        'PASSWORD': 'root123',
        'HOST': '127.0.0.1',  # ip
        'PORT': 3306,
        'POOL_OPTIONS': {
            'POOL_SIZE': 10,  # 最小
            'MAX_OVERFLOW': 10,  # 在最小的基礎上,還可以增加10個,即:最大20個。
            'RECYCLE': 24 * 60 * 60,  # 連接可以被重複用多久,超過會重新創建,-1表示永久。
            'TIMEOUT': 30,  # 池中沒有連接最多等待的時間。
        }
    },
    "bak": {
        'ENGINE': 'dj_db_conn_pool.backends.mysql',
        'NAME': 'day05bak',  # 資料庫名字
        'USER': 'root',
        'PASSWORD': 'root123',
        'HOST': '127.0.0.1',  # ip
        'PORT': 3306,
        'POOL_OPTIONS': {
            'POOL_SIZE': 10,  # 最小
            'MAX_OVERFLOW': 10,  # 在最小的基礎上,還可以增加10個,即:最大20個。
            'RECYCLE': 24 * 60 * 60,  # 連接可以被重複用多久,超過會重新創建,-1表示永久。
            'TIMEOUT': 30,  # 池中沒有連接最多等待的時間。
        }
    },
}

 

2.定義模型類

模型類的定義

模型類一般定義在子項目的models.py

模型類必須直接或者間接的繼承django.db.midels.Model類

2.1 單張表的模型類創建

2.1.1 表名設置

模型類如果未指明表名db_table,Django默認以 小寫app應用名_小寫模型類名 為資料庫表名。

可通過db_table 指明資料庫表名。

2.1.2 關於主鍵

django會為表創建自動增長的主鍵列,每個模型只能有一個主鍵列。

如果使用選項設置某個欄位的約束屬性為主鍵列(primary_key)後,django不會再創建自動增長的主鍵列。

一般情況下我們不需要主動創建,默認創建的主鍵屬性明為id,或者pk

class Student(models.Model):
    # django會自動在創建數據表的時候生成id主鍵/還設置了一個調用別名 pk
    id = models.AutoField(primary_key=True, null=False, verbose_name="主鍵") # 設置主鍵

 

2.1.3 屬性命名規範

  • 不能是python的保留關鍵字

  • 不允許使用兩個連續的下劃線,這是由django的查詢方式決定的,__是關鍵字

  • 定義屬性時需要指定欄位類型,通過欄位類型的參數自定選項,如下:

屬性名 = models.欄位類型(約束選項, verbose_name="注釋")

 

2.1.4 欄位類型

類型 說明
AutoField 自動增長的IntegerField,通常不用指定,不指定時Django會自動創建屬性名為id的自動增長屬性
BooleanField 布爾欄位,值為True或False
NullBooleanField 支援Null、True、False三種值
CharField 字元串,參數max_length表示最大字元個數,對應mysql中的varchar
TextField 大文本欄位,一般大段文本(超過4000個字元)才使用。
IntegerField 整數
DecimalField 十進位浮點數, 參數max_digits表示總位數, 參數decimal_places表示小數位數,常用於表示分數和價格 Decimal(max_digits=7, decimal_places=2) ==> 99999.99~ 0.00
FloatField 浮點數
DateField 日期 參數auto_now表示每次保存對象時,自動設置該欄位為當前時間。 參數auto_now_add表示當對象第一次被創建時自動設置當前。 參數auto_now_add和auto_now是相互排斥的,一起使用會發生錯誤。
TimeField 時間,參數同DateField
DateTimeField 日期時間,參數同DateField
FileField 上傳文件欄位,django在文件欄位中內置了文件上傳保存類, django可以通過模型的欄位存儲自動保存上傳文件, 但是, 在資料庫中本質上保存的僅僅是文件在項目中的存儲路徑!!
ImageField 繼承於FileField,對上傳的內容進行校驗,確保是有效的圖片

2.1.5 約束選項

null 如果為True,表示允許為空,默認值是False。相當於python的None
blank 如果為True,則該欄位允許為空白,默認值是False。 相當於python的空字元串,「」
db_column 欄位的名稱,如果未指定,則使用屬性的名稱。
db_index 若值為True, 則在表中會為此欄位創建索引,默認值是False。 相當於SQL語句中的key
default 默認值,當不填寫數據時,使用該選項的值作為數據的默認值。
primary_key 如果為True,則該欄位會成為模型的主鍵,默認值是False,一般不用設置,系統默認設置。
unique 如果為True,則該欄位在表中必須有唯一值,默認值是False。相當於SQL語句中的unique
max_length 欄位的最大長度
verbose_name 欄位別名,以後展示在前端可以使用

2.2 關聯表的模型類創建

關聯模型類的關係有以下三種

  • 一對一

  • 一對多

  • 多對多

2.2.1 一對一

兩張表是一對一的關係,也就是一張表的一條數據只能對應另外一張表的一條數據

OneToOneField()

 

如:學生和學生詳細資訊

 

 

 

class Student(models.Model):
    name = models.CharField(max_length=32, verbose_name='學生姓名')


class StudentDetail(models.Model):
    telephone = models.CharField(max_length=16, verbose_name='學生電話')
    address = models.CharField(max_length=16, verbose_name="學生住址")
    # 一對一
    student = models.OneToOneField(to=Student, verbose_name='學生ID', on_delete=models.CASCADE)

 

2.2.2 一對多

兩張表是一對多的關係,也就是一張表的一條數據對應另外一張表的多條數據

ForeignKey

如:學生表和班級表,一個班級可以有很多學生

 

 

 

class Cls(models.Model):
    title = models.CharField(max_length=32, verbose_name='班級名稱')


class Student(models.Model):
    name = models.CharField(max_length=32, verbose_name='學生姓名')
    # 一對多
    cls = models.ForeignKey(to=Cls, verbose_name='班級名稱', on_delete=models.CASCADE)

注意:在一對多中,必須指定on_delete刪除模式,就比如刪除了班級表中的數據,學生表中相關的數據該做何處理,常見的處理如下

  • CASCADE,           # 級聯刪除,即關聯的表刪除某一項數據,此表關聯的數據都會被刪除
  • DO_NOTHING,    # 刪除關聯數據,什麼也不做
  • SET_NULL,          # 刪除關聯數據,與之關聯的值設置為null(前提FK欄位需要設置為可空)
  • SET_DEFAULT    # 設置為默認值,僅在該欄位設置了默認值時可用
  • SET(值),               # 刪除關聯數據, 與之關聯的值設置為指定值

2.2.3 多對多

多對多就是一張表的一行數據可以對應另一張表的多行數據,反之亦然

ManyToManyField
或者自定義第三個類

如學生表和選修課程表,這需要通過第三張表的引入,來關聯二者的關係

 

 

 方式一(推薦)

class Course(models.Model):
    title = models.CharField(max_length=32, verbose_name='課程名稱')


class Student(models.Model):
    name = models.CharField(max_length=32, verbose_name='學生姓名')# 多對多
    course = models.ManyToManyField(to=Course, verbose_name='課程')

方式二

class Course(models.Model):
    title = models.CharField(max_length=32, verbose_name='課程名稱')


class Student(models.Model):
    name = models.CharField(max_length=32, verbose_name='學生姓名')class Student_Course(models.Model):
    student = models.ForeignKey(to=Student, on_delete=models.CASCADE)
    course = models.ForeignKey(to=Course, on_delete=models.CASCADE)

 

3.數據遷移

在django中數據遷移分為兩步

  1. 生成遷移文件
    python manage.py makemigrations

     

     

  2. 同步到資料庫
    python manage.py migrate

     

     

     

4.操作資料庫

4.1 單張表的資料庫基本操作

4.1.1 添加記錄

  • save()方法
    通過創建模型類對象,執行對象的save()方法保存到資料庫中
    role = Role(title='銷售顧問')
    role.save()
  • create()方法
    過模型類.objects.create()保存,返回生成的模型類對象
    obj = Role.objects.create(title='總經理')

4.1.2 修改記錄

  • 使用save更新數據【不建議】
    會將對象的所有值都更新一遍
    student = Student.objects.filter(name='劉德華').first()
    student.age = 19
    student.classmate = "303"
    student.save()
  • update更新(推薦)
    # update是全局更新,只要符合更新的條件,則全部更新,因此強烈建議加上條件!!!
    student = Student.objects.filter(name="趙華",age=22).update(name="劉芙蓉",sex=True)

     

4.1.3 刪除記錄

  • 模型類對象.delete

    student = Student.objects.get(id=13)
    student.delete()
  • 模型類.objects.filter().delete()
    Student.objects.filter(id=14).delete() 

     

4.1.4 基礎查詢

ORM中針對查詢結果的限制,提供了一個查詢集[QuerySet].這個QuerySet,是ORM中針對查詢結果進行保存數據的一個類型,我們可以通過了解這個QuerySet進行使用,達到查詢優化,或者限制查詢結果數量的作用。

  • all()
    查詢所有對象,返回一個queryset對象,所有對象的集合
    queryset = Role.objects.all()
    # <QuerySet [<Role: Role object (1)>, <Role: Role object (2)>]>
  • filter()
    篩選條件相匹配的對象,返回queryset對象。
    queryset = Role.objects.filter(title='總經理')
    # <QuerySet [<Role: Role object (1)>]>
  • get()
    返回與所給篩選條件相匹配的對象,返回結果有且只有一個, 如果符合篩選條件的對象超過一個或者沒有都會拋出錯誤。
    try:
        student = Student.objects.get(name="kunmzhao")
        print(student)
        print(student.description)
    except Student.MultipleObjectsReturned:
        print("查詢得到多個結果!")
    except Student.DoesNotExist:
        print("查詢結果不存在!")
  • first()/last()
    分別為查詢集的第一條記錄和最後一條記錄,返回一個對象
    stu01 = Student.objects.first()
    stu02 = Student.objects.last()
  • exclude()
    篩選條件不匹配的對象,返回queryset對象。
    # 查詢張三以外的所有的學生
    students = Student.objects.exclude(name="張三")
  • order_by()
    對查詢結果排序,返回一個queryset對象
    # order_by("欄位")  # 按指定欄位正序顯示,相當於 asc  從小到大
    # order_by("-欄位") # 按欄位倒序排列,相當於 desc 從大到小
    # order_by("第一排序","第二排序",...)
    
    # 查詢所有的男學生按年齡從高到低展示
    # students = Student.objects.all().order_by("-age","-id")
    students = Student.objects.filter(sex=1).order_by("-age", "-id")
  • count()
    查詢集中對象的個數
    # 查詢所有男生的個數
    count = Student.objects.filter(sex=1).count()
    print(count)
  • exists()
    判斷查詢集中是否有數據,如果有則返回True,沒有則返回False
    # 查詢Student表中是否存在學生
    Student.objects.exists()
  • values()/values_list()
    • value()把結果集中的模型對象轉換成字典,並可以設置轉換的欄位列表,達到減少記憶體損耗,提高性能

    • values_list(): 把結果集中的模型對象轉換成列表,並可以設置轉換的欄位列表(元祖),達到減少記憶體損耗,提高性能

    # values 把查詢結果中模型對象轉換成字典
    student_list = student_list.order_by("-age")
    ret1 = student_list.values() # 默認把所有欄位全部轉換並返回
    ret2 = student_list.values("id","name","age") # 可以通過參數設置要轉換的欄位並返回
    ret3 = student_list.values_list() # 默認把所有欄位全部轉換並返回
    ret4 = student_list.values_list("id","name","age") # 可以通過參數設置要轉換的欄位並返回
  • distinct()
    從返回結果中剔除重複紀錄。返回queryset。
    # 查詢所有學生出現過的年齡
    print(Student.objects.values("age").distinct())

     

4.1.5 模糊查詢

基於雙下劃線查詢

    • contains
      例:查詢姓名包含’華’的學生。
      tudent.objects.filter(name__contains='')
    • startswith、endswith
      查詢姓名以’文’結尾的學生
      Student.objects.filter(name__endswith='')
    • isnull
      查詢個性簽名不為空的學生
      student_list = Student.objects.filter(description__isnull=True)
    • in
      查詢編號為1或3或5的學生
      Student.objects.filter(id__in=[1, 3, 5])
    • 比較查詢

      • gt 大於 (greater then)

      • gte 大於等於 (greater then equal)

      • lt 小於 (less then)

      • lte 小於等於 (less then equal)

      查詢編號大於3的學生

Student.objects.filter(id__gt=3)

 

  • 日期查詢
    year、month、day、week_day、hour、minute、second:對日期時間類型的屬性進行運算。
    查詢2010年被添加到數據中的學生。
    Student.objects.filter(born_date__year=1980)

    例:查詢2016年6月20日後,2017年6月21號之前添加的學生資訊

    from django.utils import timezone as datetime
    Student.objects.filter(created_time__gte=datetime.datetime(2016,6,20),created_time__lt=datetime.datetime(2017,6,21)).all()

     

4.1.6 進階查詢

4.1.6.1 F查詢

之前的查詢都是對象的屬性與常量值比較,兩個屬性怎麼比較呢? 答:使用F對象,被定義在django.db.models中。

語法如下:

"""F對象:2個欄位的值比較"""
# 獲取從添加數據以後被改動過數據的學生
from django.db.models import F
# SQL: select * from db_student where created_time=updated_time;
student_list = Student.objects.exclude(created_time=F("updated_time"))
print(student_list)

6.1.6.2 Q 查詢

多個過濾器逐個調用表示邏輯與關係,同sql語句中where部分的and關鍵字。

例:查詢年齡大於20,並且編號小於30的學生

Student.objects.filter(age__gt=20,id__lt=30)
或
Student.filter(age__gt=20).filter(id__lt=30)

如果需要實現邏輯或or的查詢,需要使用Q()對象結合|運算符,Q對象被義在django.db.models中。

語法如下:

Q(屬性名__運算符=值)
Q(屬性名__運算符=值) | Q(屬性名__運算符=值)

例:查詢年齡小於19或者大於20的學生,使用Q對象如下。

from django.db.models import Q
student_list = Student.objects.filter( Q(age__lt=19) | Q(age__gt=20) ).all()

 

6.1.6.3 聚合查詢

使用aggregate()過濾器調用聚合函數。聚合函數包括:**Avg** 平均,**Count** 數量,**Max** 最大,**Min** 最小,**Sum** 求和,被定義在django.db.models中。

例:查詢學生的平均年齡。

from django.db.models import Sum,Count,Avg,Max,Min

Student.objects.aggregate(Avg('age'))

注意:aggregate的返回值是一個字典類型,格式如下

  {'屬性名__聚合類小寫':值}

使用count時一般不使用aggregate()過濾器。

例:查詢學生總數。

Student.objects.count() # count函數的返回值是一個數字。

6.1.6.4 分組查詢

QuerySet對象.annotate()
# annotate() 進行分組統計,按前面select 的欄位進行 group by
# annotate() 返回值依然是 queryset對象,增加了分組統計後的鍵值對
模型對象.objects.values("id").annotate(course=Count('course__sid')).values('id','course')
# 查詢指定模型, 按id分組 , 將course下的sid欄位計數,返回結果是 name欄位 和 course計數結果 

# SQL原生語句中分組之後可以使用having過濾,在django中並沒有提供having對應的方法,但是可以使用filter對分組結果進行過濾
# 所以filter在annotate之前,表示where,在annotate之後代表having
# 同理,values在annotate之前,代表分組的欄位,在annotate之後代表數據查詢結果返回的欄位

 

6.1.6.5 原生查詢

執行原生SQL語句,也可以直接跳過模型,才通用原生pymysql.

 ret = Student.objects.raw("SELECT id,name,age FROM db_student")  # student 可以是任意一個模型
 # 這樣執行獲取的結果無法通過QuerySet進行操作讀取,只能循環提取
 print(ret,type(ret))
 for item in ret:
    print(item,type(item))

 

4.2 關聯查詢

基於雙下劃線查詢(join查詢)

    ret = Student.objects.filter(name="張三").values("age")

    # (1) 查詢年齡大於22的學生的姓名以及所在名稱班級

    # 方式1 : Student作為基表
    ret = Student.objects.filter(age__gt=22).values("name","clas__name")
    # 方式2 :Clas表作為基表
    ret = Clas.objects.filter(student_list__age__gt=22).values("student_list__name","name")

    # (2) 查詢電腦科學與技術2班有哪些學生
    ret = Clas.objects.filter(name="電腦科學與技術2班").values("student_list__name")

    # (3) 查詢張三所報課程的名稱
    ret = Student.objects.filter(name="張三").values("courses__title")
    print(ret) # <QuerySet [{'courses__title': '近代史'}, {'courses__title': '籃球'}]>

    # (4) 查詢選修了近代史這門課程學生的姓名和年齡
    ret = Course.objects.filter(title="近代史").values("students__name","students__age")

    # (5) 查詢李四的手機號
    ret = Student.objects.filter(name='李四').values("stu_detail__tel")


    # (6) 查詢手機號是110的學生的姓名和所在班級名稱
    # 方式1
    ret = StudentDetail.objects.filter(tel="110").values("stu__name","stu__clas__name")
    print(ret) # <QuerySet [{'stu__name': '張三', 'stu__clas__name': '電腦科學與技術2班'}]>
    # 方式2:
    ret = Student.objects.filter(stu_detail__tel="110").values("name","clas__name")
    print(ret) # <QuerySet [{'name': '張三', 'clas__name': '電腦科學與技術2班'}]>

 

4.3 關聯添加

  • 一對多與一對一
     stu = Student.objects.create(name="王五", clas_id=9, stu_detail_id=6)
  • 多對多
    # 添加多對多方式1
        c1 = Course.objects.get(title="思修")
        c2 = Course.objects.get(title="邏輯學")
        stu.courses.add(c1,c2)
    
        # 添加多對多方式2
        stu = Student.objects.get(name="張三")
        stu.courses.add(5,7)
    
        # 添加多對多方式3
        stu = Student.objects.get(name="李四")
        stu.courses.add(*[6,7])

     

4.4 關聯刪除

    # 刪除多對多記錄
     stu = Student.objects.get(name="李四")
     stu.courses.remove(7)

    # 清空多對多記錄:clear方法
    stu = Student.objects.get(name="rain")
    stu.courses.clear()

    #重置多對多記錄:set方法
    stu = Student.objects.get(name="李四")
    stu.courses.set([5,8])