1. 程式人生 > 實用技巧 >Python學習第132天(Django2內容增加)

Python學習第132天(Django2內容增加)

在建立外來鍵的過程中,出現了這麼一個錯誤:

TypeError: __init__() missing 1 required positional argument: 'on_delete'

原因如下:

django 升級到2.0之後,表與表之間關聯的時候,必須要寫on_delete引數,否則會報異常:
TypeError: init() missing 1 required positional argument: ‘on_delete’

解決方案:

定義外來鍵的時候需要加上 on_delete=;
即:contract = models.ForeignKey(Contract, on_delete=models.CASCADE)

on_delete=None, # 刪除關聯表中的資料時,當前表與其關聯的field的行為
on_delete=models.CASCADE, # 刪除關聯資料,與之關聯也刪除
on_delete=models.DO_NOTHING, # 刪除關聯資料,什麼也不做
on_delete=models.PROTECT, # 刪除關聯資料,引發錯誤ProtectedError
# models.ForeignKey('關聯表', on_delete=models.SET_NULL, blank=True, null=True)
on_delete=models.SET_NULL, # 刪除關聯資料,與之關聯的值設定為null(前提FK欄位需要設定為可空,一對一同理)
# models.ForeignKey(
'關聯表', on_delete=models.SET_DEFAULT, default='預設值') on_delete=models.SET_DEFAULT, # 刪除關聯資料,與之關聯的值設定為預設值(前提FK欄位需要設定預設值,一對一同理) on_delete=models.SET, # 刪除關聯資料, a. 與之關聯的值設定為指定值,設定:models.SET(值) b. 與之關聯的值設定為可執行物件的返回值,設定:models.SET(可執行物件)

由於多對多(ManyToManyField)沒有 on_delete 引數,所以以上只針對外來鍵(ForeignKey)和一對一(OneToOneField)

Django對資料庫的是通過ORM實現。什麼叫做ORM呢?簡單來說,我們通過sql語句查詢一張表的所有資料的語句如下 select * from test。而Django中用到的表都是在models.py檔案裡定義。所以我們要查查詢test表裡的資料可以通過

test_obj = models.test.objects.all()來獲取test表裡所有資料的物件。再通過

test_obj.values()方法將每一行資料的裡的各個欄位以key和value的字典形式讀取出來。這就叫ORM操作。

既然涉及到資料庫的操作,就必然會用到連表操作。ORM中將連表操作簡單的劃分為一對多 和多對多 這兩種操作。

什麼叫一對多呢?就是抽象的說法就是資料庫的外來鍵操作就是典型的一對多。資料庫裡的某一個欄位可以對應另外一張表裡的多個值。簡單的舉例的說法就是,一個管理員可以管理Host表裡的多個主機。但是每一個主機只能對應一個管理員。這種情況就叫做一對多

1) 正向操作

首先我們先建立2張表。一張是主機表裡面存放主機ip和埠資訊。一張是管理員表,裡面存放管理員姓名和管理的主機資訊。那麼我們的models.py裡的程式碼如下:

from __future__ import unicode_literals
from django.db import models
# Create your models here.
class host(models.Model):
    ip = models.CharField(max_length=32)
    port = models.IntegerField()
class hostadmin(models.Model):
    username = models.CharField(max_length=32)
    host = models.ForeignKey('host')

建立好表之後,我們給表裡填充幾行測試資料。

從有外來鍵的表裡查詢關聯表裡的資料,這就叫正向操作。從我們的例子裡可以看到外來鍵定義在Hostadmin表裡,那麼我們從Hostadmin表裡查詢使用者tom管理的所有的主機ip,這個需求就是正向查詢。我們在views.py裡的正向查詢程式碼如下:

from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # 通過filter方法匹配條件,將返回的物件存入ret_obj變數中
    ret_obj = models.hostadmin.objects.filter(username='tom')
    # 因為ret_obj是一個物件,這個物件是由資料庫裡的多行資料組成
    # 所以每迴圈一次item,這個item就代表一行包含所有欄位的資料庫資料
    for item in ret_obj:
        # ORM中跨表獲取資料的操作用'.'來連線
        # item.host.ip表示通過HostAdmin表的host外來鍵欄位的值去獲取對應的Host表裡的ip欄位
        # 注意資料庫的表結構host外來鍵欄位被Django自動寫成了host_id 
        # 但是使用ORM跨表操作的時候不可以使用host_id,還是應該使用models裡定義的host這個欄位名
        print item.host.ip
    return HttpResponse('ok')

再查詢對ip為1.1.1.1的主機有管理許可權的所有管理員名稱,程式碼如下

from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # 查詢資料的時候通過'__'雙下劃線來進行跨表查詢操作
    ret_obj = models.hostadmin.objects.filter(host__ip='1.1.1.1')
    for item in ret_obj:
        print item.username
    return HttpResponse('ok')

以上的兩種查詢,我們都是通過對hostadmin表的操作實現的查詢功能。至此正向查詢就介紹完了。

2)反向操作

還是使用剛才的兩張表,反向操作顧名思義就是通過不存在的外來鍵的表反過來查詢包含外來鍵的表內資料。按照我們的例子就是通過Host表裡的欄位反過來查詢Hostadmin表裡的資料。

首先我們知道Host表與HostAdmin表是通過HostAdmin裡面的host外來鍵來建立聯絡的,做正向查詢的時候通過host這個外來鍵就可以查詢到Host表裡的資料。其實在這兩個表建立聯絡的時候,Django在Host表裡

也建立了一個隱藏的hostadmin欄位來與HostAdmin表進行關聯。那麼如果我們要在Host表裡檢視tom使用者所對應的所有ip的話,就可以利用Host表裡的隱藏欄位來關聯使用者名稱稱。程式碼如下:

from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # 利用Host表裡隱藏的hostadmin欄位關聯查詢HostAdmin表裡的使用者名稱稱
    # 這種方式和正向查詢很像
    ret_obj = models.host.objects.filter(hostadmin__username='tom')
    # 遍歷查詢的到的所有行
    for line in ret_obj:
        # 列印每一行的ip欄位
        print line.ip
    return HttpResponse('ok')

如果我們希望通過Host表裡的的ip為1.1.1.1的欄位,反向查詢到對應的管理員的名稱。這個和正向查詢的區別就比較大了。有以下2點需要注意

1、在Host表裡必須通過get()方法精確的指定一行資料(不使用get()方法,就不能呼叫hostadmin_set反向獲取資料)

2、獲取的時候通過hostadmin_set.all()獲取符合條件的所有HostAdmin表裡的資料行的集合

功能實現的程式碼如下:

from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # get()方法是隻獲取一行資料,如果獲取不到就報錯
    # 所以使用get方法的時候必須要確保可獲取的條件
    host_obj = models.host.objects.get(ip='1.1.1.1')
    # 反向跨表通過hostadmin_set.all()才能獲得關聯表的所有資料行物件
    admin_obj = host_obj.hostadmin_set.all()
    # 列印獲取到的物件
    print admin_obj
    # 遍歷物件
    for line in admin_obj:
        #列印物件裡的欄位
        print line.username
    return HttpResponse('ok')

至此反向查詢操作也介紹完了。

一對多的優化(select_related()方法)

我們還是以第一個正向查詢作為例子講解,程式碼如何下

from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # 在HostAdmin表中查詢所有符合條件的資料行
    ret_obj = models.hostadmin.objects.filter(username='tom')
    # 列印原始的sql語句
    print ret_obj.query
    for item in ret_obj:
        print item.host.ip
    return HttpResponse('ok')

上面這段程式碼的執行流程應該是這樣子:

查詢HostAdmin表裡所有username='tom'的資料行

-->查詢出來之後獲取這些資料行裡的host_id的值

-->根據host_id的值到Host表裡找到對應的ip

這個流程要要讀取兩次資料庫才能獲得Host和HostAdmin裡的資料

下面我們優化一下,在models.hostadmin.objects.filter(username='tom')後面新增select_related()方法。

from django.shortcuts import render,HttpResponse
from app01 import models
# Create your views here.
def onetomany(request):
    # 使用select_related()方法優化查詢
    ret_obj = models.hostadmin.objects.filter(username='tom').select_related()
    # 列印原始的sql語句
    print ret_obj.query
    for item in ret_obj:
        print item.host.ip
    return HttpResponse('ok')

再來看看原始的SQL語句有什麼不同

這次查詢的時候直接通過join on語句把HostAdmin表裡查詢結果對應的Host表裡的資料也給一併查詢出來了。這樣後面的item.ip需要用到Host表裡資料的時候直接就可以在記憶體裡獲取到需要的資料,減少了一次對資料庫的訪問。

要注意:select_related()方法只能給一對多這種外來鍵訪問方式提供優化。多對多的操作沒有作用。

一對多的簡單總結

1、查詢資料 也就是通過models.xxx.objects.filter()裡填寫查詢條件的時候。這個時候獲取的結果是一組資料行物件,不是具體的某個資料。跨表查詢用到 物件名稱__欄位名(雙下劃線)

2、獲取資料 也是具體到要獲取某個欄位的資料的時候。跨表操作通過'.'來連線各個表

3、反向查詢的時候,查詢的的表裡會建立一個隱藏掉欄位,這個欄位名就是與建立外來鍵的表同名

4、反向獲取資料的時候,通過xxx_set.all()才能獲取到 xxx所有被匹配到的物件

5、儘量用正向操作,反向的看著就麻煩。

雙下劃線的常用操作

 # models.Tb1.objects.create(c1='xx', c2='oo')  增加一條資料,可以接受字典型別資料 **kwargs
    # obj = models.Tb1(c1='xx', c2='oo')
    # obj.save()
    # 查
    #
    # models.Tb1.objects.get(id=123)         # 獲取單條資料,不存在則報錯(不建議)
    # models.Tb1.objects.all()               # 獲取全部
    # models.Tb1.objects.filter(name='seven') # 獲取指定條件的資料
    # 刪
    #
    # models.Tb1.objects.filter(name='seven').delete() # 刪除指定條件的資料
    # 改
    # models.Tb1.objects.filter(name='seven').update(gender='0')  # 將指定條件的資料更新,均支援 **kwargs
    
    # obj = models.Tb1.objects.get(id=1)    # 修改單條資料
    # obj.c1 = '111'
    # obj.save()                                                 
  
  
    # 獲取個數
    #
    # models.Tb1.objects.filter(name='seven').count()
    # 大於,小於
    #
    # models.Tb1.objects.filter(id__gt=1)              # 獲取id大於1的值
    # models.Tb1.objects.filter(id__lt=10)             # 獲取id小於10的值
    # models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 獲取id大於1 且 小於10的值
    # in
    #
    # models.Tb1.objects.filter(id__in=[11, 22, 33])   # 獲取id等於11、22、33的資料
    # models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in
    # contains
    #
    # models.Tb1.objects.filter(name__contains="ven")
    # models.Tb1.objects.filter(name__icontains="ven") # icontains大小寫不敏感
    # models.Tb1.objects.exclude(name__icontains="ven")
    # range
    #
    # models.Tb1.objects.filter(id__range=[1, 2])   # 範圍bettwen and
    # 其他類似
    #
    # startswith,istartswith, endswith, iendswith,
    # order by
    #
    # models.Tb1.objects.filter(name='seven').order_by('id')    # asc
    # models.Tb1.objects.filter(name='seven').order_by('-id')   # desc
    # limit 、offset
    #
    # models.Tb1.objects.all()[10:20]
    # group by
    from django.db.models import Count, Min, Max, Sum
    # models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num'))
    # SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id"

以上為今日內容,明天去江西抗洪搶險。。。