關聯關系字段 (Relationship fields)
ForeignKey,ManyToManyField與OneToOneField分別在Model中定義多對一,多對多,一對一關系。
例如,一本書由一家出版社出版,一家出版社可以出版很多書。一本書由多個作者合寫,一個作者可以寫很多書。
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后面定義,需要寫成下面的形式:
class Book(models.Model): name=models.CharField(max_length=20) pub=models.ForeignKey('Publisher') authors=models.ManyToManyField('Author')
2.Model關聯自身
Model可以與自身做多對一關系
class People(models.Model): name=models.CharField(max_length=20) leader=models.ForeignKey('self',blank=True,null=True)
Model也可以與自身做多對多關系
class Person(models.Model): friends = models.ManyToManyField("self")
默認情況下,這種關聯關系是對稱的,如果Person1是Person2的朋友,那么Person2也是Person1的朋友
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
class Person2(models.Model): friends=(models.ManyToManyField("self",symmetrical=False)
這樣查詢p3的朋友,就需要p3.person_set.all()了
3.反向名稱related_name
反向名稱,用來從被關聯字段指向關聯字段。
注意,在你定義 抽象 model (abstract models) 時,你必須顯式指定反向名稱; 只有在你這么做了之后, 某些特別語法 (some special syntax) 才能正常使用。
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為'+'或者以'+'結束。
user = models.ForeignKey(User, related_name='+')
如果有多個ManyToManyField指向同一個Model,這樣反向查詢FOO_set的時候就無法弄清是哪個ManyToManyField字段了,可以禁止反向關系:
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 :
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進行。
>>> 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()還是可以使用的
>>> 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關聯的對象被刪除時,默認情況下此對象也會一起被級聯刪除的。
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()的值。
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 那樣用於擴展父類並繼承父類屬性。
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)
使用反向關聯的時候,得到的不是一個對象列表,而是一個單獨的對象:
>>> 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")