1. 程式人生 > 實用技巧 >Django-ORM操作

Django-ORM操作

一、orm概述

1 orm:物件關係對映(跟語言無關)
	資料庫中的表     ----》對應程式的一個類
	資料庫中的一行資料----》對應程式中的一個物件
2 python中常見orm框架
	-django的orm框架
    -sqlachemy的orm框架
    
3 java:(擴充套件),java中寫web專案
	ssh框架 :spring+struts(有漏洞)+hibernate(orm框架)(很早的年代)
	ssm框架:spring+springmvc+mybatis(orm框架,可以寫原生sql)
    springboot:sb框架 ,tomcat內建進去了
    springcloud:微服務
4 orm能幹的事
	-建立表(不能建立資料庫,手動建立資料庫)
    -增加刪除表內欄位
    -增刪查改資料

二、orm簡單使用

1.配置檔案

雖然使用orm對應與許多的資料庫,但是因為我們一般是使用mysql資料庫,所以需要在settings中配置,不然預設使用sqlite。若使用sqlite跳過這一步直接從第二步開始。

1.
#settings.py
DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'day62',
            'HOST': '127.0.0.1',
            'PORT': 3306,
            'USER': 'root',
            'PASSWORD':'123'
            # 下面兩個暫時不用加,也不用管
            'ATOMIC_REQUEST': True,
        	'OPTIONS': {
            	"init_command": "SET storage_engine=MyISAM",
				}
            }
        }
2.django預設情況連結mysql,用的驅動是mysqldb模組,python 3.x以後,這個模組用不了了,咱們用的全都是pymysql,需要做個替換
# app的__init__.py 中寫以下程式碼,其實隨便在哪寫,但是規範是在這寫
# 因為對於django專案,在執行的時候,所有檔案的程式碼都會執行,所以無所謂在哪寫,公司裡可能會換地方寫
import pymysql
pymysql.install_as_MySQLdb()

2.利用orm建立表

# 第一步在models中寫要給類
class Book(models.Model):
    # 如果不寫id,會預設一個id,並且自增
    # primary_key=True  表示該欄位是主鍵,一個表中只能有一個主鍵
    id = models.AutoField(primary_key=True)
    # varchar型別,長度,
    # 欄位是否可以為空:null=True,可以為空
    # 預設值:default='未知書名',如果沒傳,預設是它
    # 設定索引:db_index=True 表示該欄位是輔助索引
    # 是否唯一:unique=True 表示唯一
    name=models.CharField(max_length=32,null=True,default='未知書名',db_index=True,unique=True)
    # float型別
    # max_digits 最大長度是5  4567.5
    # decimal_places=2 小數點後兩位   23.56     999.99
    price=models.DecimalField(max_digits=5,decimal_places=2)

    # DateTimeField年月日時分秒
    # auto_now=True  新增,預設使用當前時間
    # auto_now_add=True 修改,設定當前時間,這兩者只能設定一種
    publish_date=models.DateTimeField(auto_now=True)

'''
    auto_now無論是你新增還是修改物件,時間為你新增或者修改的時間。
    auto_now_add為新增時的時間,更新物件時不會有變動。
'''
    publish=models.CharField(max_length=32)
    
    
# 第二步,把表創建出來(執行兩個命令)
    -python3 manage.py makemigrations  # 這條命令會在migrations建立一條記錄,資料庫變更記錄
    -python3 manage.py migrate         # 把更改同步到資料庫

3.orm單表增加

from app01 import models

# 註冊功能
def register(request):
    if request.method == 'GET':
        return render(request, 'register.html')
    elif request.method == 'POST':
        # 獲取資料
        res = request.POST
        name = res.get('name')
        password = res.get('password')
        gender = res.get('gender')
        province = res.get('province')

        # 通過orm存到資料庫
        # 方式一
        user=models.UserInfo(name=name,password=password,gender=gender,province=province)
        user.save()

        # 方式二
        user=models.UserInfo.objects.create(name=name,password=password,gender=gender,province=province)

        # 註冊完成直接跳轉到使用者列表頁面
        return redirect('/userlist')
      

4.orm單表查詢

# 檢視目前註冊的使用者
def user_list(request):
    # 通過orm查詢所有使用者,返回列表,內部有一個一個的user物件 [user1,user2,user3]
    userlist=models.UserInfo.objects.all()
    return render(request, 'userlist.html',context={'userlist':userlist})

def select_user(request):
    # 查詢名字叫xxx的人(是個列表:QuerySet)
    res = models.UserInfo.objects.filter(name='xxx')
    res = models.UserInfo.objects.filter(name='xxx')[0]
    res = models.UserInfo.objects.filter(name='xxx').first()

    # 利用get方法查詢名字叫xxx的人(UserInfo),如果沒有或者由多個,都報錯
    # 查詢結果必須有且僅有一個才正常,否則報錯
    res=models.Book.objects.get(name='xxx')

三、常用和非常用欄位和引數概覽

1.常用欄位

# 最為常用
IntegerField   整數
AutoField
BooleanField
CharField
DateField
DateTimeField
DecimalField
FileField   上傳檔案,本質是varchar
ImageField   圖片,本質是varchar,繼承了FileField
TextField   存大文字
EmailField   本質是varchar

# 詳細
    AutoField(Field)
        - int自增列,必須填入引數 primary_key=True

    BigAutoField(AutoField)
        - bigint自增列,必須填入引數 primary_key=True

        注:當model中如果沒有自增列,則自動會建立一個列名為id的列
        from django.db import models

        class UserInfo(models.Model):
            # 自動建立一個列名為id的且為自增的整數列
            username = models.CharField(max_length=32)

        class Group(models.Model):
            # 自定義自增列
            nid = models.AutoField(primary_key=True)
            name = models.CharField(max_length=32)

    SmallIntegerField(IntegerField):
        - 小整數 -32768 ~ 32767

    PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
        - 正小整數 0 ~ 32767
    IntegerField(Field)
        - 整數列(有符號的) -2147483648 ~ 2147483647

    PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
        - 正整數 0 ~ 2147483647

    BigIntegerField(IntegerField):
        - 長整型(有符號的) -9223372036854775808 ~ 9223372036854775807

    自定義無符號整數字段

        class UnsignedIntegerField(models.IntegerField):
            def db_type(self, connection):
                return 'integer UNSIGNED'

        PS: 返回值為欄位在資料庫中的屬性,Django欄位預設的值為:
            'AutoField': 'integer AUTO_INCREMENT',
            'BigAutoField': 'bigint AUTO_INCREMENT',
            'BinaryField': 'longblob',
            'BooleanField': 'bool',
            'CharField': 'varchar(%(max_length)s)',
            'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
            'DateField': 'date',
            'DateTimeField': 'datetime',
            'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
            'DurationField': 'bigint',
            'FileField': 'varchar(%(max_length)s)',
            'FilePathField': 'varchar(%(max_length)s)',
            'FloatField': 'double precision',
            'IntegerField': 'integer',
            'BigIntegerField': 'bigint',
            'IPAddressField': 'char(15)',
            'GenericIPAddressField': 'char(39)',
            'NullBooleanField': 'bool',
            'OneToOneField': 'integer',
            'PositiveIntegerField': 'integer UNSIGNED',
            'PositiveSmallIntegerField': 'smallint UNSIGNED',
            'SlugField': 'varchar(%(max_length)s)',
            'SmallIntegerField': 'smallint',
            'TextField': 'longtext',
            'TimeField': 'time',
            'UUIDField': 'char(32)',

    BooleanField(Field)
        - 布林值型別

    NullBooleanField(Field):
        - 可以為空的布林值

    CharField(Field)
        - 字元型別
        - 必須提供max_length引數, max_length表示字元長度

    TextField(Field)
        - 文字型別

    EmailField(CharField):
        - 字串型別,Django Admin以及ModelForm中提供驗證機制

    IPAddressField(Field)
        - 字串型別,Django Admin以及ModelForm中提供驗證 IPV4 機制

    GenericIPAddressField(Field)
        - 字串型別,Django Admin以及ModelForm中提供驗證 Ipv4和Ipv6
        - 引數:
            protocol,用於指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
            unpack_ipv4, 如果指定為True,則輸入::ffff:192.0.2.1時候,可解析為192.0.2.1,開啟刺功能,需要protocol="both"

    URLField(CharField)
        - 字串型別,Django Admin以及ModelForm中提供驗證 URL

    SlugField(CharField)
        - 字串型別,Django Admin以及ModelForm中提供驗證支援 字母、數字、下劃線、連線符(減號)

    CommaSeparatedIntegerField(CharField)
        - 字串型別,格式必須為逗號分割的數字

    UUIDField(Field)
        - 字串型別,Django Admin以及ModelForm中提供對UUID格式的驗證

    FilePathField(Field)
        - 字串,Django Admin以及ModelForm中提供讀取資料夾下檔案的功能
        - 引數:
                path,                      資料夾路徑
                match=None,                正則匹配
                recursive=False,           遞迴下面的資料夾
                allow_files=True,          允許檔案
                allow_folders=False,       允許資料夾

    FileField(Field)
        - 字串,路徑儲存在資料庫,檔案上傳到指定目錄
        - 引數:
            upload_to = ""      上傳檔案的儲存路徑
            storage = None      儲存元件,預設django.core.files.storage.FileSystemStorage

    ImageField(FileField)
        - 字串,路徑儲存在資料庫,檔案上傳到指定目錄
        - 引數:
            upload_to = ""      上傳檔案的儲存路徑
            storage = None      儲存元件,預設django.core.files.storage.FileSystemStorage
            width_field=None,   上傳圖片的高度儲存的資料庫欄位名(字串)
            height_field=None   上傳圖片的寬度儲存的資料庫欄位名(字串)

    DateTimeField(DateField)
        - 日期+時間格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]

    DateField(DateTimeCheckMixin, Field)
        - 日期格式      YYYY-MM-DD

    TimeField(DateTimeCheckMixin, Field)
        - 時間格式      HH:MM[:ss[.uuuuuu]]

    DurationField(Field)
        - 長整數,時間間隔,資料庫中按照bigint儲存,ORM中獲取的值為datetime.timedelta型別

    FloatField(Field)
        - 浮點型

    DecimalField(Field)
        - 10進位制小數
        - 引數:
            max_digits,小數總長度
            decimal_places,小數位長度

    BinaryField(Field)
        - 二進位制型別

2.常用引數

(1)null
 
如果為True,Django 將用NULL 來在資料庫中儲存空值。 預設值是 False.
 
(2)blank
 
如果為True,該欄位允許不填。預設為False。
要注意,這與 null 不同。null純粹是資料庫範疇的,而 blank 是資料驗證範疇的。
如果一個欄位的blank=True,表單的驗證將允許該欄位是空值。如果欄位的blank=False,該欄位就是必填的。
 
(3)default
 
欄位的預設值。可以是一個值或者可呼叫物件。如果可呼叫 ,每有新物件被建立它都會被呼叫。
 
(4)primary_key
 
如果為True,那麼這個欄位就是模型的主鍵。如果你沒有指定任何一個欄位的primary_key=True,
Django 就會自動新增一個IntegerField欄位做為主鍵,所以除非你想覆蓋預設的主鍵行為,
否則沒必要設定任何一個欄位的primary_key=True。
 
(5)unique
 
如果該值設定為 True, 這個資料欄位的值在整張表中必須是唯一的
 
(6)choices
由二元組組成的一個可迭代物件(例如,列表或元組),用來給欄位提供選擇項。如果設定了choices,預設的表單將是一個選擇框而不是標準的文字框,而且這個選擇框的選項就是choices中的選項。

3.元資訊

class UserInfo(models.Model):
    nid = models.AutoField(primary_key=True)
    username = models.CharField(max_length=32)

    class Meta:

    # 資料庫中生成的表名稱 預設 app名稱 + 下劃線 + 類名
    db_table = "table_name"

    # 聯合索引
    index_together = [
        ("pub_date", "deadline"),
    ]

    # 聯合唯一索引
    unique_together = (("driver", "restaurant"),)
    # admin中顯示的表名稱
    verbose_name = '使用者資訊表'
    # 只寫上面一行的話verbose_name會加s,即使用者資訊表s,因為在外國s代表複數,如果要完全改過來寫下面這句
    verbose_name_plural = '使用者資訊表'
    # 一般這兩句是連用的,但是真正起效果的是下面那句

四、列印原生sql

有時候為了我們方便排查需要打印出原生sql檢視

配置檔案貼上
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

五、查詢表記錄API

<1> all():                  查詢所有結果
<2> filter(**kwargs):       它包含了與所給篩選條件相匹配的物件
<3> get(**kwargs):          返回與所給篩選條件相匹配的物件,返回結果有且只有一個,如果符合篩選條件的物件超過一個或者沒有都會丟擲錯誤。
<4> exclude(**kwargs):      它包含了與所給篩選條件不匹配的物件,即排除
<5> order_by(*field):       對查詢結果排序,如果要對某個欄位逆排序,可以在欄位前加-號('-id')
<6> reverse():              對查詢結果反向排序,相當於上面加-號
<7> count():                返回資料庫中匹配查詢(QuerySet)的物件數量。count操作是在資料庫的時候就統計好的,而不是拿到結果再用python統計
<8> first():                返回第一條記錄,底層相當於asc,limit1
<9> last():                返回最後一條記錄,底層相當於desc,limit1
<10> exists():              如果QuerySet包含資料,就返回True,否則返回False
<11> values(*field):        返回一個ValueQuerySet——一個特殊的QuerySet,執行後得到的並不是一系列model的例項化物件,而是一個可迭代的字典序列
<12> values_list(*field):   它與values()非常相似,它返回的是一個元組序列,values返回的是一個字典序列
<13> distinct():            從返回結果中剔除重複紀錄

六、基於雙下劃線的模糊查詢

# 1 價格在[100,200,300]這個範圍內
Book.objects.filter(price__in=[100,200,300])
# 2 大於,小於,大於等於,小於等於
Book.objects.filter(price__gt=100)
Book.objects.filter(price__lt=100)
Book.objects.filter(price__gte=100)
Book.objects.filter(price__lte=100)
# 3 範圍
Book.objects.filter(price__range=[100,200])
# 4 包含
Book.objects.filter(title__contains="python")
# 5 忽略大小寫包含
Book.objects.filter(title__icontains="python")
# 6 以xx開頭
Book.objects.filter(title__startswith="py")
# 7 時間型別,年份是2012年的
Book.objects.filter(pub_date__year=2012)

七、刪除表記錄

# 第一種:queryset的delete方法
res=models.Book.objects.all().delete()
print(res)
# 第二種:物件自己的delete方法
book = models.Book.objects.all().filter(name='金梅').first()
print(type(book))
res=book.delete()

八、修改表記錄

# 第一種:queryset的update方法
res=models.Book.objects.filter(publish='東京').update(name='金梅1')
print(res)
# 第二種:物件自己的
book = models.Book.objects.filter(name='xxx').last()
book.name='asdfasd'
book.save()

九、python指令碼中呼叫django環境

可以讓我們不需要開啟瀏覽器輸入資料,方便測試

# 在指令碼中呼叫djagno服務
import os
if __name__ == '__main__':
    #1  引入django配置檔案
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day67.settings')
    # 2 讓djagno啟動
    import django
    django.setup()
    # 3 使用表模型
    from app01 import models
    models.Book.objects.create(name='測試書籍',publish='xx出版社')

十、多表操作

1.多表模型建立注意點

多表因為要建立外來鍵或者對應關係,所以和單表的建立有些許不同

1 圖書表:book,作者表:author,作者詳情表:authordetail,出版社表:publish,(第三張中間表)
2 作者跟作者詳情:是一對一,關聯欄位寫在哪一方都可以
3 圖書跟出版社:是一對多,一對多關係一旦確立,關聯欄位寫在多的一方
4 圖書和作者:是多對多,多對多的關係需要建立第三張表(可以自動生成)

5 models.py中把關係建立出來
from django.db import models
### django版本:目前使用1.11.x或者12.0.x,使用django3會出問題

class Publish(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    phone = models.CharField(max_length=64)
    email = models.EmailField()


class Book(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=6, decimal_places=2)
    publish_date = models.DateTimeField(auto_now_add=True)
    read_num=models.IntegerField(default=0)
    commit_num=models.IntegerField(default=0)

    # to='Publish'跟Publish表做關聯(ForeignKey,一對多)
    # to_field='id'跟哪個欄位做關聯
    # publish=models.CharField(max_length=32)
    # publish=models.ForeignKey(to='Publish',to_field='id')
    # publish = models.ForeignKey(to='Publish')  # 不寫的話,預設跟主鍵做關聯
    publish = models.ForeignKey(to=Publish)

    # 自動創建出第三張表(這句話會自動建立第三張表)
    # authors在資料庫中不存在該欄位,沒有to_field
    # 預設情況:第三張表有id欄位,還有Book表的id和Author表的id欄位
    authors=models.ManyToManyField(to='Author')


class Author(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.SmallIntegerField()
    # 一對一的本質是  ForeignKey+unique
    author_detail=models.OneToOneField(to='AuthorDetail',to_field='id')
    # author_detail=models.ForeignKey(to='AuthorDetail',to_field='id',unique=True)


class AuthorDetail(models.Model):
    id = models.AutoField(primary_key=True)
    sex = models.SmallIntegerField()
    addr = models.CharField(max_length=64)
    phone = models.BigIntegerField()
    
 
6 同步到mysql資料庫
	-配置檔案
    -pymysql.install_as_mysqldb()
		-g的mysqlclient
    -兩條命令
    
7 2.x版本的django
	-外來鍵欄位必須加引數:on_delete
    -1.x版本不需要,預設就是級聯刪除
    -假設,
    	刪除出版社,該出版社出版的所有圖書也都刪除,on_delete=models.CASCADE
        刪除出版社,該出版社出版的圖書不刪除,設定為空on_delete=models.SET_NULL,null=True
        刪除出版社,該出版社出版的圖書不刪除,設定為預設on_delete=models.SET_DEFAULT,default=0

2.一對多新增記錄

當建立了外來鍵關係之後,我們在插入表格的時候,被關聯的表格需要先有資料,然後才能在多關係的表格里加資料,如下的book表,publish欄位需要和publish表的id欄位關聯。

publish=models.Publish.objects.create(name='北京出版社',addr='北京',phone='0536-12345678',email='郵箱地址')
# 新增圖書三種方式
book=models.Book.objects.create(name='金梅',price='23.45',publish=publish)# publish=物件
book=models.Book.objects.create(name='西遊記',price='23.55',publish_id=1)# publish_id=數字
book=models.Book.objects.create(name='西遊記',price='23.55',publish_id=publish.id)# publish_id=數字


# 總結:
1 email可以不傳email,本質就是varchar(admin中會判斷)
2 新增圖書:
    -publish=publish
    -publish_id=publish.id
3 寫在表模型中的publish欄位,到資料庫中會變成publish_id(ForeignKey)
4 查到book物件以後
    -book.publish     物件
    -book.publish_id  id號,數字

3.多對多新增記錄,修改,刪除

自動建立的表,表模型就拿不到,就如同我們在book類裡建立的authors。book.authors代指表模型

# 多對多,作者和書
# 給西遊記這本書新增兩個作者lqz和egon
# 去到西遊記這本書
book=models.Book.objects.get(name='西遊記')
# 代指中間表book.authors
lqz=models.Author.objects.get(id=2)
egon=models.Author.objects.get(id=3)
# 新增作者需要傳id,有以下三種方式
book.authors.add(2,3) # 新增作者,通過id新增
book.authors.add(lqz,egon) # 新增作者,通過物件新增
book.authors.add(2,egon) # 新增作者,通過物件新增

# 西遊記刪除一個作者
book = models.Book.objects.get(name='西遊記')
book.authors.remove(2)
egon = models.Author.objects.get(id=3)
book.authors.remove(egon)

# clear 清空所有作者
book = models.Book.objects.get(name='西遊記')
book.authors.clear()

# set先清空,再add,前提是不存在的作者,即如果這個作者原來在表裡,又使用set,原存在的資料不會被刪除,然後把不存在的資料加進去
book.authors.set([4,])

# add ,remove,set clear

十一、多表操作之查詢

跨表查詢有兩種方式

-基於物件的跨表查詢:相當於我們原生sql子查詢
-基於雙下劃線的跨表查詢:相當於我們原生sql的關聯查詢,連表查詢(就是left join等)

1.基於物件的跨表查詢

基於物件的跨表查詢,先查物件,通過物件再去查另一個物件(正向:欄位名,反向:表名小寫/表名小寫_set.all())

正向查詢:最後查出來的物件有我們需要的屬性,那麼直接,物件.欄位名
反向查詢:最後查出來的物件沒有我們需要的屬性,而是與我們想要的欄位有關聯(比如外來鍵關係),那麼我們可以,物件.類名小寫.屬性,獲取單個數據,物件.類名小寫_set.all(),獲取多個數據返回的列表。

技巧:熟練後,不需要管什麼正向反向,能點出屬性就直接點出來,不能的話就點類名小寫,再看資料是一條還是多條。

一對多

# 查詢主鍵為1的書籍的出版社所在的城市
# 先查主鍵為1的書,下面有兩種方式
book=models.Book.objects.get(id=1) # 第一次查詢
book=models.Book.objects.filter(id=1).first() # 第一次查詢
publish=book.publish  # 第二次查詢,內部又執行了一次查詢,根據publish_id查詢publish
print(publish.addr)

# 北京出版社出版的所有書籍,反向
publish=models.Publish.objects.get(name='北京出版社')  # 第一次查詢了出版社
books=publish.book_set.all()    # 表名小寫_set,第二次,根據出版社id,查詢所有書
print(books)

# 地址為山東的作者寫的所有書
author_detail=models.AuthorDetail.objects.get(addr='山東')
author=author_detail.author
books=author.book_set.all()
print(books[0].name)

# 正向查詢:book表內有publish欄位 直接物件.欄位名
# 反向查詢:publish表內沒有book欄位,出版社物件.Book小寫_set.all()或者Book直接小寫。

一對一

# 查詢所有住址在山東的作者的姓名,反向
# 反向查詢:author_detail沒有author欄位,需要使用author_detail.表名小寫
author_detail=models.AuthorDetail.objects.filter(addr__contains='山東').first()
print(author_detail.author.name)

# 查詢egon作者的地址,正向
author=models.Author.objects.get(name='egon')
print(author.author_detail.addr)

多對多

# 金梅所有作者的名字以及手機號,正向
book=models.Book.objects.get(name='金梅')
authors=book.authors.all()
for author in authors:
	print(author.name)
	print(author.author_detail.phone)

# 查詢egon出過的所有書籍的名字,反向
egon=models.Author.objects.get(name='egon')
books=egon.book_set.all()
for book in books:
	print(book.name)

2.基於雙下劃線的跨表查詢

一對多

# 格式 models.型別.objects.filter,values/values_list(寫 __ 跨表)

# 查詢北京出版社出版過的所有書籍的名字與價格(一對多)
res=models.Publish.objects.filter(name='北京出版社').values('book__name','book__price')
print(res)
res=models.Book.objects.filter(publish__name='北京出版社').values('name','price')
print(res)

多對多

# 查詢egon出過的所有書籍的名字,價格(多對多)
# 反向
res=models.Author.objects.filter(name='egon').values('book__name','book__price')
print(res)

# 正向
res=models.Book.objects.filter(authors__name='egon').values('name','price')
print(res)

# 查詢egon的手機號
res=models.Author.objects.filter(name='egon').values('author_detail__phone')
print(res)
res=models.AuthorDetail.objects.filter(author__name='egon').values('phone')
print(res)

3.進階連續跨表查詢

# 查詢北京出版社出版過的所有書籍的名字以及作者的姓名
res=models.Publish.objects.filter(name='北京出版社').values('book__name','book__authors__name')
print(res)

res=models.Book.objects.filter(publish__name='北京出版社').values('name','authors__name')
print(res)

res=models.Author.objects.filter(book__publish__name='北京出版社').values('book__name','name')
print(res)

# 手機號以189開頭的作者出版過的所有書籍名稱以及出版社名稱
res=models.AuthorDetail.objects.filter(phone__startswith='189').values('author__book__name','author__book__publish__name')
print(res)

res = models.Author.objects.filter(author_detail__phone__startswith='189').values('book__name','book__publish__name')
print(res)

十二、聚合查詢

聚合查詢包括:Count,Avg,Min,Max,Sum,和我們原生sql中的聚合函式是一樣的。同時使用也相同,我們在使用了聚合函式之後,只能拿到聚合的欄位。

aggregate()QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。鍵的名稱是聚合值的識別符號,值是計算出來的聚合值。鍵的名稱是按照欄位和聚合函式的名稱自動生成出來的。如果你想要為聚合值指定一個名稱,可以向聚合子句提供它。

from django.db.models import Avg,Max,Min,Count,Sum
# 要用aggregate藥包裹聚合函式,當aggregate結束,已經不是queryset物件了,我們也可以在aggrepate裡用as起別名
# 1 計算所有圖書的平均價格
book=models.Book.objects.all().aggregate(Avg('price'))
book=models.Book.objects.all().aggregate(avg=Avg('price'))
# 2 計算總圖書數
book = models.Book.objects.all().aggregate(count=Count('id'))
# 3 計算最低價格的圖書
book = models.Book.objects.all().aggregate(min_price=Min('price'))
# 4 計算最大價格圖書
book = models.Book.objects.all().aggregate(max_price=Max('price'))
# 5 多個查詢
Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))

十三、分組查詢

分組查詢,即原生sql裡的group by。我們經常是分組和聚合函式搭配使用,如果是單獨使用聚合函式,那麼就是用aggregate()包括聚合函式,如果要和分組搭配使用,就需要annotate()包裹。

annotate的返回值是querySet,如果不想遍歷物件,可以用上value_list:

queryResult= Publish.objects.annotate(MinPrice=Min("book__price")).values_list("name","MinPrice")
print(queryResult)
'''
使用說明:
annotate() 內寫聚合函式
values在前表示group by的欄位
values在後表示取某幾個欄位
filter在前表示where
filter在後表示having

pk 代指主鍵
如果沒有指定group by的欄位,預設就用基表(Publish)主鍵欄位作為group by的欄位

查詢每個作者的名字,以及出版過書籍的最高價格(建議使用分組的表作為基表)
如果不用分組的表作為基表,資料不完整可能會出現問題
'''
from django.db.models import Avg, Count, Max, Min
# 示例一:查詢每一個出版社id,以及出書平均價格
select publish_id,avg(price) from app01_book group by publish_id;
ret=models.Book.objects.values('publish_id').annotate(avg=Avg('price')).values('publish_id','avg')
print(ret)

# 查詢出版社id大於1的出版社id,以及出書平均價格
select publish_id,avg(price) from app01_book where publish_id>1 group by publish_id;
ret=models.Book.objects.values('publish_id').filter(publish_id__gt=1).annotate(avg=Avg('price')).values('publish_id','avg')
print(ret)

# 查詢出版社id大於1的出版社id,以及出書平均價格大於30的
select publish_id,avg(price)as aaa from app01_book where publish_id>1 group by publish_id HAVING aaa>30;
ret = models.Book.objects.values('publish_id').filter(publish_id__gt=1).annotate(avg=Avg('price')).filter(avg__gt=30).values('publish_id', 'avg')
print(ret)

# 查詢每一個出版社出版的書籍個數
# pk 代指主鍵
ret=models.Publish.objects.values('pk').annotate(count=Count('book__id')).values('name','count')
print(ret)
# 如果沒有指定group by的欄位,預設就用基表(Publish)主鍵欄位作為group by的欄位.即上面可縮寫為
ret=models.Publish.objects.annotate(count=Count('book__id')).values('name','count')
print(ret)

# 另一種方式實現
ret=models.Book.objects.values('publish').annotate(count=Count('id')).values('publish__name','count')
print(ret)

# 查詢每個作者的名字,以及出版過書籍的最高價格(建議使用分組的表作為基表)
# 如果不用分組的表作為基表,資料不完整可能會出現問題
ret=models.Author.objects.values('pk').annotate(max=Max('book__price')).values('name','max')
ret = models.Author.objects.annotate(max=Max('book__price')).values('name', 'max')
ret= models.Book.objects.values('authors__id').annotate(max=Max('price')).values('authors__name','max')
print(ret)

# 查詢每一個書籍的名稱,以及對應的作者個數
ret=models.Book.objects.values('pk').annotate(count=Count('authors__id')).values('name','count')
ret=models.Book.objects.annotate(count=Count('authors__id')).values('name','count')
ret=models.Author.objects.values('book__id').annotate(count=Count('id')).values('book__name','count')
print(ret)

#統計不止一個作者的圖書
ret=models.Book.objects.values('pk').annotate(count=Count('authors__id')).filter(count__gt=1).values('name','count')
ret = models.Author.objects.values('book__id').annotate(count=Count('id')).filter(count__gt=1).values('book__name', 'count')
print(ret)

# 統計價格數大於10元,作者的圖書
ret = models.Book.objects.values('pk').filter(price__gt=10).annotate(count=Count('authors__id')).values('name', 'count')
print(ret)

#統計價格數大於10元,作者個數大於1的圖書
ret = models.Book.objects.values('pk').filter(price__gt=10).annotate(count=Count('authors__id')).filter(count__gt=1).values('name', 'count')
print(ret)
ret = models.Book.objects.filter(price__gt=10).annotate(count=Count('authors__id')).filter(count__gt=1).values('name', 'count')
print(ret)

十四、F和Q查詢

F查詢:取出資料庫的某個欄位的值,因為

Q查詢:製造"與或非"的條件

# F查詢:取出資料庫的某個欄位的值
from django.db.models import F
# 把read_num都加1
ret=models.Book.objects.all().update(read_num=F('read_num')+1)
print(ret)

#查詢評論數大於閱讀數的書籍
ret=models.Book.objects.all().filter(commit_num__gt=F('read_num'))
for i in ret:
    print(i.name)

# 查詢評論數大於閱讀數2倍的書籍
ret=models.Book.objects.filter(commit_num__gt=F('read_num')*2)
print(ret)
    
    
# Q查詢:製造"與或非"的條件:& | ~
from django.db.models import Q
# 查詢名字叫egon或者價格大於100的書
ret=models.Book.objects.filter(Q(name='egon') | Q(price__gt=100))
# 查詢名字叫egon並且價格大於100的書,並且的用法有兩種方式,一般用第二種,和之前的寫法一樣
ret=models.Book.objects.filter(Q(name='egon') & Q(price__gt=100))
ret=models.Book.objects.filter(name='egon',price__gt=100)
# 查詢名字不為egon的書
ret = models.Book.objects.filter(~Q(name='egon'))
print(ret)

# Q可以巢狀,巢狀就可以使用很多條件的判斷
ret = models.Book.objects.filter((Q(name='egon') & Q(price__lt=100)) | Q(id__lt=3))
print(ret)

十五、原生sql

# 原生sql(有些sql用orm寫不出來,因為水平不到位哈哈)
# 第一種:用的比較少
from django.db import connection

cursor = connection.cursor()

cursor.execute("""SELECT * from app01_book where id = %s""", [1])

row = cursor.fetchone()
row = cursor.fetchall()
print(row)

# 第二種,用的多
books=models.Book.objects.raw('select * from app01_book where id >3')
print(books)#RawQuerySet物件
for book in books:
    print(book.name)

books=models.Book.objects.raw('select * from app01_publish')
for book in books:
    print(book.__dict__)
    print(book.name)
    print(book.addr)
    print(book.email)
    print(book.price)

authors = models.Author.objects.raw('SELECT app01_author.id,app01_author. NAME,app01_authordetail.sex FROM app01_author JOIN app01_authordetail ON app01_author.author_detail_id = app01_authordetail.id WHERE app01_authordetail.sex = 1')

for author in authors:
    print(author.name)
    print(author.__dict__)

十六、defer和only

# defer和only(查詢優化相關)
# only保持是book物件,但是隻能使用only指定的欄位
books = models.Book.objects.all().only('name')
print(books[0].name)
print(books[0].price)  # 能出來,但是要再去資料庫查一遍

books = models.Book.objects.all().only('name')
print(books[0].__dict__)
books = models.Book.objects.all().defer('name','price')
print(books[0].__dict__)

十七、事務(請求,裝飾器,區域性)

# 事物:ACID,事物的隔離級別,鎖, 行級鎖,表級鎖
# djanog orm中使用事物:原子性操作,要麼都成功,要麼都失敗
# 例子:新增一個作者詳情,新增一個作者,這兩者應該同時成功,不可能有作者詳情而沒有作者,反之亦然

# 事物的三個粒度
# 1 區域性使用(常用)
from django.db import transaction

with transaction.atomic():  # 都在事物中,要麼都成功,要麼都失敗
    author_detail = models.AuthorDetail.objects.create(addr='xxx', phone='123', sex=1)
    # raise Exception('拋了異常')
    author = models.Author.objects.create(name='llqz', age=19, author_detail=author_detail)
    
# 2 在一個檢視中使用,檢視函式裝飾器,這一個檢視函式都在一個事物中(偶爾使用)
@transaction.atomic
def index(request):
    return HttpResponse('ok')

# 3 整個http請求,在事物中,在setting.py中配置(基本不用)
DATABASES = {
    'default': {
        ...
        'PORT': 3306,
        'ATOMIC_REQUEST': True,

    }
}
'ATOMIC_REQUEST': True,
# 設定為True統一個http請求對應的所有sql都放在一個事務中執行,百分之九十九不會用這個,因為把整個http請求放進事務太消耗資源,且很多情況下,對於一個http請求我們需要有不同的操作

補充

1.時區和國際化問題

setting.py中
1 後臺管理漢語問題
	LANGUAGE_CODE = 'zh-hans'  # 管理後臺看到的就是中文
2 時區問題(使用東八區)
	TIME_ZONE = 'Asia/Shanghai'
    USE_TZ = False

2.django admin

0 管理後臺是django提供的可以快速對錶進行增刪查改操作

1 建立一個後臺管理賬號
    python3 manage.py createsuperuser
    輸入使用者名稱
    輸入郵箱(可以不填,敲回車)
    輸入密碼
    確認密碼
    # 超級使用者創建出來了,可以登入管理後臺了
2 admin中表中一行一行的資料顯示我們定製的樣子
	重寫模型類的__str__方法
3 建立完賬號後,需要將表註冊到後臺中
    在app下的admin.py中寫
    from app01 import models
    # 把book表註冊,管理後臺就能看到了
    admin.site.register(models.Book)

4 更改時區,這樣django後臺就變成了中文

3.blank引數作用

1 需要把book表註冊到admin中
	在app下的admin.py中寫
    from app01 import models
	# 把book表註冊一些,管理後臺就能看到了
	admin.site.register(models.Book)
2 可以快速的對book表進行增刪查改操作

4.看一看這篇部落格

https://www.cnblogs.com/nokiaguy/p/13803370.html

5.安裝模組相關

pip3 install django    
# 本質是去https://pypi.python.org/simple,搜這個模組,會根據你的平臺下載在一個安裝包(windows平臺是whl),下載完,再安裝

# pip安裝失敗的情況
# 我們可以繞過它,有了whl檔案以後,自己裝
# https://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv
pip3 install django.whl   

# 官方庫沒有上傳到pypi,官方也不給製作whl檔案
#如何安裝  包 (setup.py)
到達安裝目錄,setup.py所在的目錄
python setup.py build
python setup.py install

# 配置清華源,豆瓣源,本質是
豆瓣源會把pypi,包拉到自己的伺服器上,以後你再下,去它的伺服器上下,所以速度快

# 你自己寫的包,如何上傳到pypi上給別人使用?

6.外來鍵關係要不要建立

1 關聯欄位與外來鍵約束沒有必然的聯絡(建管理欄位是為了進行查詢,建約束是為了不出現髒資料)
2 預設情況,關聯關係建好以後,外來鍵約束就自然建立了
3 實際工作中,外來鍵約束一般不建(影響效率),都是人為約束(程式碼約束)
	-db_constraint=False
4 表模型和資料庫表的對應,不要直接修改表(這是可以的,但是不建議),要修改表模型,同步到表中

練習

1.鏈式呼叫

queryset就是鏈式呼叫的使用),實現一個可以支援鏈式呼叫的類

class MyClass:

    def func1(self):
        print('func1')
        return self

    def func2(self):
        print('func2')
        return self


obj = MyClass()
obj.func1().func2()

(重點)2.利用orm的多表查詢完成如下功能

基於物件,基於雙下滑下)

# 查詢所有書名裡包含紅樓的書
    res = models.BookInfo.objects.filter(name__contains='紅樓')
    # 查找出版日期是2017年的書
    res = models.BookInfo.objects.filter(publish_time__year=2017)
    # 查找出版日期是2017年的書名
    books = models.BookInfo.objects.filter(publish_time__year=2017)
    for book in books:
        print(book.name)
    # 查找價格大於10元的書
    res = models.BookInfo.objects.filter(price__gt=10)
    # 查找價格大於10元的書名和價格
    res = models.BookInfo.objects.filter(price__gt=10)
    for i in res:
        print(i.name,i.price)
    # 查詢在北京的出版社
    res = models.Publish.objects.filter(addr='北京')
    # 查詢名字以沙河開頭的出版社
    res = models.Publish.objects.filter(name__startswith='沙河')
    # 查詢作者名字裡面帶“小”字的作者
    res = models.Author.objects.filter(name__contains='小')
    # 查詢年齡大於30歲的作者
    res = models.Author.objects.filter(age__gt=30)
    # 查詢手機號是155開頭的作者
    res = models.AuthorDetail.objects.filter(phone__startswith='155')
    res1 = res.author_set.all()
    # 查詢手機號是155開頭的作者的姓名和年齡
    res = models.AuthorDetail.objects.filter(phone__startswith='155')
    res1 = res.author_set.all()
    for i in res1:
        print(i.name,i.age)
    # 查詢書名是“紅樓夢”的書的出版社
    res = models.BookInfo.objects.filter(name='紅樓夢')
    res1 = res.publish
    # 查詢書名是“紅樓夢”的書的出版社所在的城市
    res = models.BookInfo.objects.filter(name='紅樓夢')
    res1 = res.publish.addr
    res2 = res1.addr
    # 查詢書名是“紅樓夢”的書的出版社的名稱
    res = models.BookInfo.objects.filter(name='紅樓夢')
    res1 = res.publish
    res2 = res1.name
    # 查詢書名是“紅樓夢”的書的所有作者
    res = models.BookInfo.objects.filter(name='紅樓夢')
    res1 = res.author_set.all()
    # 查詢書名是“紅樓夢”的書的作者的年齡
    res = models.BookInfo.objects.filter(name='紅樓夢')
    res1 = res.author_set.all()
    for i in res1:
        print(i.age)
    # 查詢書名是“紅樓夢”的書的作者的手機號碼
    res = models.BookInfo.objects.filter(name='紅樓夢')
    res1 = res.author_set.all()
    res2 = res1.authordetail.phone
    # 查詢書名是“紅樓夢”的書的作者的地址
    res = models.BookInfo.objects.filter(name='紅樓夢')
    res1 = res.author_set.all()
    res2 = res1.authordetail.addr
    # 查詢書名是“紅樓夢”的書的作者的郵箱
    res = models.BookInfo.objects.filter(name='紅樓夢')
    res1 = res.author_set.all()
    res2 = res1.authordetail.email

3.完成如下功能

1 查詢老男孩出版社出版過的價格大於200的書籍
res = models.BookInfo.objects.filter(price__gt = 200).first()
2 查詢2017年8月出版的所有以py開頭的書籍名稱
res = models.BookInfo.objects.filter(publish_time__year = 2017).filter(name__startswith = 'py')
3 查詢價格為50,100或者150的所有書籍名稱及其出版社名稱
4 查詢價格在100到200之間的所有書籍名稱及其價格
5 查詢所有人民出版社出版的書籍的價格(從高到低排序,去重)

4.整理

查詢api--14個方法中,哪些返回值是queryset物件,哪些不是queryset物件,是什麼?

5.登入功能,連線mysql

前端頁面略

路由

    url(r'^login', views2.login),
    url(r'^index', views2.index),
    url(r'^test', views.test),

檢視函式

def index(request):
    return render(request, 'index2.html')


def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        name = request.POST.get('name')
        password = request.POST.get('password')
        # 建立一個數據庫連結
        conn = pymysql.connect(host='127.0.0.1', user='root', password='123', database='userinfo', port=3306, )
        # 拿到一個遊標
        cursor = conn.cursor()
        # 執行sql
        cursor.execute('select * from user where name=%s and password=%s ', (name, password))
        # 獲取結果
        ret = cursor.fetchone()
        print(ret)
        if ret:
            return redirect('/index')
        else:
            return HttpResponse('使用者名稱或密碼錯誤')

6.(重點)完成如下查詢

import os

if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE','django6.settings')
    import django
    django.setup()
    from app01 import models
    from django.db.models import Count, Max, Min,Avg,Sum,F,Q
    # 1統計每一本書作者個數
    ret = models.Book.objects.values("id").annotate(count = Count('authors__id')).values("name","count")
    print(ret)
    # 2統計每一個出版社的最便宜的書
    ret = models.Publish.objects.annotate(min_price = Min('book__name')).values("name",'min_price')
    print(ret)
    # 3統計每一本以py開頭的書籍的作者個數:
    ret = models.Book.objects.filter(name__startswith = 'book').annotate(count=Count('authors__id')).values("name", 'count')
    print(ret)
    # 4作者數量大於2的圖書名字和價格
    ret = models.Book.objects.annotate(count = Count('authors__id')).filter(count__gt = 2).values('name','price')
    print(ret)
    # 5根據一本圖書作者數量的多少對查詢集QuerySet進行排序:
    ret = models.Book.objects.annotate(count=Count('authors__id')).values('name','count').order_by('count')
    print(ret)
    # 6查詢各個作者出的書的總價格
    ret = models.Author.objects.annotate(max_price=Max('book__price')).values('name','max_price')
    print(ret)
    # 7查詢每個出版社的名稱和書籍個數
    ret = models.Publish.objects.annotate(count=Count('book__id')).values('name', 'count')
    print(ret)
    # 8查詢所有書籍的平均價格
    ret = models.Book.objects.aggregate(avg_price=Avg('price'))
    print(ret)
    # 9統計圖書平均價格和最大價格
    ret = models.Book.objects.aggregate(avg_price=Avg('price'),max_price = Max('price'))
    print(ret)
    # 10統計圖書總個數和平均價格
    ret = models.Book.objects.aggregate(count_price=Count('pk'),avg_price=Avg('price')).
    print(ret)
    # 11查詢評論數小於等於閱讀數2倍的書籍
    ret = models.Book.objects.filter(commit_num__lte=F('read_num')*2)
    for book in ret:
        print(book.name)
    # 12將每一本書的價格提高30元
    ret = models.Book.objects.all().update(price = F("price")+30)
    print(ret)
    # 13將id大於3的每本書價格提高5元
    ret = models.Book.objects.filter(id__gt=3).update(price = F("price")+5)
    print(ret)
    # 14查詢名字不是西遊,並且價格大於150的書
    ret = models.Book.objects.filter(~Q(name='西遊') & Q(price__gt = 150))
    for book in ret:
        print(book.name)
    # 15查詢名字叫西遊並且價格大於150或者id小於等於5的圖書
    ret = models.Book.objects.filter(Q(name='西遊') & Q(price__gt = 150) | Q(id__lte=5))
    for book in ret:
        print(book.name)
    # 16查詢名字叫西遊或者出版社為北京出版社的圖書
    ret = models.Book.objects.filter(Q(name='西遊') | Q(publish__name = '北京出版社'))
    for book in ret:
        print(book.name)