1. 程式人生 > >Django 的 ORM 有多種關係:一對一,多對一,多對多。

Django 的 ORM 有多種關係:一對一,多對一,多對多。

Django 的 ORM 有多種關係:一對一,多對一,多對多。

各自定義的方式為 :
一對一: OneToOneField
多對一: ForeignKey
多對多: ManyToManyField
上邊的描述太過資料而缺乏人性化,我們來更人性化一些:
多個屬於一個,即 belong to : ForeignKey,多個屬於一個
一個有一個,即 has one: OneToOneField
一個有很多個,即 has many: lots of A belong to B 與 B has many A,在建立 ForeignKey 時,另一個表會自動建立對應的關係
一個既有很多個,又屬於很多個,即 has many and belong to : ManyToManyField,同樣只能在一個model類中說明,關聯表會自動建立。
操作資料表總的來說就是:

一對一和一對多
1、查詢資料使用 __ 連線
2、獲取資料時使用 . 連線

前提條件-正向和反向的定義:

正向:基於存在ForeignKey或ManyToManyField 欄位的表查詢為正向

反向:基於不存在ForeignKey或ManyToManyField表查詢為反向

#反向查詢
_set
注意:xx_set中的xx為小寫的表名

例子如下:

models.py
views.py
一對多

增加
#一 以物件的方式增加
#二 以_id方式增加

查詢:

#正向查(雙下劃線__)

#反向查 (  _set)

多對多

增加

#正向新增
admin_obj = models.HostAdmin.objects.get(username='dali')
host_list = models.Host.objects.filter(id__lt=3)
admin_obj.host.add(*host_list)

#反向新增
host_obj = models.Host.objects.get(id=3)
admin_list = models.HostAdmin.objects.filter(id__gt=1)
host_obj.hostadmin_set.add(*admin_list)

查詢

#正向查
admin_obj = models.HostAdmin.objects.get(id=1)
admin_obj.host.all()

#反向查
host_obj = models.Host.objects.get(id=1)
print host_obj.hostadmin_set.all()

關聯關係欄位 (Relationship fields)
例如,一本書由一家出版社出版,一家出版社可以出版很多書。一本書由多個作者合寫,一個作者可以寫很多書。

1
2
3
4
5
6
7
8
class Author(models.Model):
name=models.CharField(max_length=20)
class Publisher(models.Model):
name=models.CharField(max_length=20)
class Book(models.Model):
name=models.CharField(max_length=20)
pub=models.ForeignKey(Publisher)
authors=models.ManyToManyField(Author)
1.關聯尚未定義的Model

如果你要與某個尚未定義的 model 建立關聯 ,就使用 model 的名稱,而不是使用 model 物件本身。

例子中,如果Publisher與Author在Book後面定義,需要寫成下面的形式:

1
2
3
4
class Book(models.Model):
name=models.CharField(max_length=20)
pub=models.ForeignKey(‘Publisher’)
authors=models.ManyToManyField(‘Author’)
2.Model關聯自身

Model可以與自身做多對一關係

1
2
3
class People(models.Model):
name=models.CharField(max_length=20)
leader=models.ForeignKey(‘self’,blank=True,null=True)
Model也可以與自身做多對多關係

1
2
class Person(models.Model):
friends = models.ManyToManyField(“self”)
預設情況下,這種關聯關係是對稱的,如果Person1是Person2的朋友,那麼Person2也是Person1的朋友

1
2
3
4
5
6
7
p1=Person()
p1.save()
p2=Person()
p2.save()
p3=Person()
p3.save()
p1.friends.add(p2,p3)
上述情況下,要查詢p3的朋友,不用p3.person_set.all(),而直接用p3.friends.all()就可以了

如果想取消這種對稱關係,將symmetrical設為False

1
2
class Person2(models.Model):
friends=(models.ManyToManyField(“self”,symmetrical=False)
這樣查詢p3的朋友,就需要p3.person_set.all()了

3.反向名稱related_name

反向名稱,用來從被關聯欄位指向關聯欄位。

注意,在你定義 抽象 model (abstract models) 時,你必須顯式指定反向名稱; 只有在你這麼做了之後, 某些特別語法 (some special syntax) 才能正常使用。

#當關聯同一個模型的欄位大於一個時,要使用related_name引數來指定表名

1
2
3
4
class Book(models.Model):
name=models.CharField(max_length=20)
pub=models.ForeignKey(Publisher,related_name=‘pub’)
authors=models.ManyToManyField(Author,related_name=‘author’)
這樣用Publisher或者Author反向查詢Book時可以用related_name了:publisher1.pub.all()或者author1.author.all()。

如果不想設定反向關係,設定related_name為’+‘或者以’+'結束。

1
user = models.ForeignKey(User, related_name=’+’)
如果有多個ManyToManyField指向同一個Model,這樣反向查詢FOO_set的時候就無法弄清是哪個ManyToManyField欄位了,可以禁止反向關係:

1
2
users = models.ManyToManyField(User, related_name=‘u+’)
referents = models.ManyToManyField(User, related_name=‘ref+’)
4.資料庫表現 (Database Representation)

多對一:Django 使用ForeignKey欄位名稱+ “_id” 做為資料庫中的列名稱。在上面的例子中,BOOK model 對應的資料表中會有 一個 publisher_id 列。

你可以通過顯式地指定 db_column 來改變該欄位的列名稱,不過,除非你想自定 義 SQL ,否則沒必要更改資料庫的列名稱。

多對多:Django 建立一箇中間表來表示ManyToManyField關係。預設情況下,中間表的名稱由兩個關係表名結合而成。

由於某些資料庫對錶名的長度有限制,所以中間表的名稱會自動限制在64個字元以內,幷包含一個不重複的雜湊字串。這

意味著,你可能看到類似 book_authors_9cdf4 這樣的表名稱。你可以使用 db_table 選項手動指定中間表名稱。

但是,如果你想手動指定中間表,你可以用 through 選項來指定model 使用另外某個 model 來管理多對多關係。而這個 model 就是中間表所對應的 model :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person(models.Model):
name = models.CharField(max_length=128)
def unicode(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through=‘Membership’)
def unicode(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
這樣,就可以記錄某個person何時加入group了。

要建立Person與Group的關係就不能用add,create,remove了,而是需要通過Membership進行。

1
2
3
4
5
6
7

ringo = Person.objects.create(name=“Ringo Starr”)
paul = Person.objects.create(name=“Paul McCartney”)
beatles = Group.objects.create(name=“The Beatles”)
m1 = Membership(person=ringo, group=beatles,
… date_joined=date(1962, 8, 16),
… invite_reason= “Needed a new drummer.”)

m1.save()
clear()還是可以使用的

1

beatles.members.clear()
當多對多關係關聯自身時,中間表的ForeignKey是可以指向同一個Model的,但是它們必須被看做ManyToManyField的兩邊,而不是對稱的,需要設定 symmetrical=False。

5.其它引數 (Arguments)

5.1 ForeignKey 接受下列這些可選引數,這些引數定義了關係是如何執行的。

ForeignKey.limit_choices_to

它是一個包含篩選條件和對應值的字典,用來在 Django 管理後臺篩選 關聯物件。例如,利用 Python 的 datetime 模組,過濾掉不符合篩選條件關聯物件:

limit_choices_to = {‘pub_date__lte’: datetime.date.today}

只有 pub_date 在當前日期之前的關聯物件才允許被選。

也可以使用 Q 物件來代替字典,從而實現更復雜的篩選。當limit_choices_to為Q物件時,如果把此外來鍵欄位放在ModelAdmin的raw_id_fields時是不可用的。

ForeignKey.to_field

指定當前關係與被關聯物件中的哪個欄位關聯。預設情況下,to_field 指向被關聯物件的主鍵。

ForeignKey.on_delete

當一個model物件的ForeignKey關聯的物件被刪除時,預設情況下此物件也會一起被級聯刪除的。

1
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
CASCADE:預設值,model物件會和ForeignKey關聯物件一起被刪除

SET_NULL:將model物件的ForeignKey欄位設為null。當然需要將null設為True。

SET_DEFAULT:將model物件的ForeignKey欄位設為預設值。

Protect:刪除ForeignKey關聯物件時會生成一個ProtectedError,這樣ForeignKey關聯物件就不會被刪除了。

SET():將model物件的ForeignKey欄位設為傳遞給SET()的值。

1
2
3
4
5
def get_sentinel_user():
return User.objects.get_or_create(username=‘deleted’)[0]

class MyModel(models.Model):
user = models.ForeignKey(User, on_delete=models.SET(get_sentinel_user))
DO_NOTHING:啥也不做。

5.2 ManyToManyField 接受下列可選引數,這些引數定義了關係是如何執行的。

ManyToManyField.limit_choices_to

和 ForeignKey.limit_choices_to 用法一樣。

limit_choices_to 對於通過 through 引數指定了中介表的 ManyToManyField 不起作用。

ManyToManyField.symmetrical

只要定義遞迴的多對多關係時起作用。

ManyToManyField.through

手動指定中間表

ManyToManyField.db_table

指定資料庫中儲存多對多關係資料的表名稱。如果沒有提供該選項,Django 就會根據兩個關係表的名稱生成一個新的表名,做為中間表的名稱。

6.OneToOneField

class OneToOneField(othermodel[, parent_link=False, **options])

用來定義一對一關係。籠統地講,它與聲明瞭 unique=True 的 ForeignKey 非常相似,不同的是使用反向關聯的時候,得到的不是一個物件列表,而是一個單獨的物件。

在某個 model 擴充套件自另一個 model 時,這個欄位是非常有用的;例如: 多表繼承 (Multi-tableinheritance) 就是通過在子 model 中新增一個指向父 model 的一對一關聯而實現的。

必須給該欄位一個引數:被關聯的 model 類。工作方式和 ForeignKey 一樣,連遞迴關聯 (recursive) 和 延後關聯 (lazy) 都一樣。

此外,OneToOneField 接受 ForeignKey 可接受的引數,只有一個引數是 OnetoOneField 專有的:OneToOneField.parent_link

如果為 True ,並且作用於繼承自某個父 model 的子 model 上(這裡不能是延後繼承,父 model 必須真實存在 ),那麼該欄位就會變成指向父類例項的引用(或者叫連結),

而不是象其他OneToOneField 那樣用於擴充套件父類並繼承父類屬性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django.db import models, transaction, IntegrityError

class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)

def __unicode__(self):
    return u"%s the place" % self.name

class Restaurant(models.Model):
place = models.OneToOneField(Place, primary_key=True)
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()

def __unicode__(self):
    return u"%s the restaurant" % self.place.name

class Waiter(models.Model):
restaurant = models.ForeignKey(Restaurant)
name = models.CharField(max_length=50)

def __unicode__(self):
    return u"%s the waiter at %s" % (self.name, self.restaurant)

使用反向關聯的時候,得到的不是一個物件列表,而是一個單獨的物件:

1
2
3
4
5
6
7
8
9

p1 = Place(name=‘Demon Dogs’, address=‘944 W. Fullerton’)
p1.save()
r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
r.save()
p1.restaurant
<Restaurant: Demon Dogs the restaurant>

Place.objects.get(restaurant__place__name__startswith=“Demon”)
<Place: Demon Dogs the place>

Waiter.objects.filter(restaurant__place__name__startswith=“Demon”)

7.自定義多對多關係表

Django除了能自動建立多對多的第三張表,同樣也可以自定義建立多對多的第三張表,而且操作和管理擴充套件等難易程度要比自動建立的好許多。所以,在之後的models表結構中,推薦使用自定義的方式。僅僅在建立時新增一個欄位即可:through。

複製程式碼
#=========================== 方式二,自定義關聯表

class Host1(models.Model):
hostname = models.CharField(max_length=32)
port = models.IntegerField()

class HostAdmin1(models.Model):
username = models.CharField(max_length=32)
email = models.CharField(max_length=32)
host = models.ManyToManyField(Host1, through=‘HostRelation’)

class HostRelation(models.Model):
c1 = models.ForeignKey(Host1)
c2 = models.ForeignKey(HostAdmin1)

#自定義建立第三張表,其中的外來鍵都為一對多的關係

增加資料(直接以id的方式向第三張表中新增資料)
models.HostRelation.objects.create(c1_id=2, c2_id=1,)

查詢資料(雙__)
relation_list = models.HostRelation.objects.filter(c2__username=‘dali’)
for item in relation_list:
print item.c1.hostname
print item.c2.username