Django Model 定義語法
版本:1.7
主要來源:https://docs.djangoproject.com/en/1.7/topics/db/models/
簡單用法
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
會自動生成SQL:
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
-默認(rèn)會根據(jù)APP的名稱生成"app名稱"+"_"+"類名"
-會自動增加ID字段
-Django會根據(jù)settings配置中指定的數(shù)據(jù)庫類型來生成相應(yīng)的SQL語句
使用Model
要想使用Model需要在settings配置中INSTALLED_APPS中增加你的APP,例如,你的models在myapp.models中,那么就應(yīng)該使用:
INSTALLED_APPS = (
#...
'myapp',
#...
)
在將app添加到INSTALLD_APPS中后需要執(zhí)行manage.py migrate,也可通過manage.py makemigrations進(jìn)行遷移。
Fields
Django提供了各種數(shù)據(jù)字段類型。需要注意不要使用與API相沖突的字段名稱如clean,save或delete
from django.db import models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
class Album(models.Model):
artist = models.ForeignKey(Musician)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
Field types
django會根據(jù)field類型確定
- 數(shù)據(jù)庫字段類型(如INTEGER,VARCHAR)
- 默認(rèn)的HTML生成什么樣的表單項
- 最低限度的驗證需求。它被用在 Django 管理后臺和自動生成的表單中。
field是可以自定義的。
Field options
每個field類型都有自己特定的參數(shù),但也有一些通用的參數(shù),這些參數(shù)都是可選的:
null
如果為 True , Django 在數(shù)據(jù)庫中會將空值(empty)存儲為 NULL 。默認(rèn)為 False 。
blank
設(shè)置字段是否可以為空,默認(rèn)為False(不允許為空)
和null的區(qū)別在于:null是數(shù)據(jù)庫的范圍,而blank是用于驗證。如果一個字段的 blank=True ,Django 在進(jìn)行表單數(shù)據(jù)驗證時,會允許該字段是空值。如果字段的 blank=False ,該字段就是必填的。
choices
它是一個可迭代的二元組(例如,列表或是元組),用來給字段提供選擇項。如果設(shè)置了 choices, Django會顯示選擇框,而不是標(biāo)準(zhǔn)的文本框,而且這個選擇框的選項就是 choices 中的元組。
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
每個元組中的第一個元素,是存儲在數(shù)據(jù)庫中的值;第二個元素是在管理界面或 ModelChoiceField 中用作顯示的內(nèi)容。在一個給定的 model 類的實例中,想得到某個 choices 字 段的顯示值,就調(diào)用 get_FOO_display 方法(這里的 FOO 就是 choices 字段的名稱 )。
from django.db import models
class Person(models.Model):
SHIRT_SIZES = (
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
u'L'
>>> p.get_shirt_size_display()
u'Large'
default
默認(rèn)值,可以是一個具體的值也可以是一個對象,每次調(diào)用次會創(chuàng)建一個新的對象
help_text
附加的幫助信息。在使用表單時會顯示在字段下面。即使不使用表單也可以起來幫助文檔的作用。
primary_key
如果為True,則表示這個字段是主鍵。
如果你沒有設(shè)置主鍵,Django會自動創(chuàng)建一個自增的IntergerField類型主鍵,可以通過自定義主鍵來覆蓋默認(rèn)的行為。
unique
如果為 True ,那么字段值就必須是全表唯一的。
Automatic primary key fields
默認(rèn)情況下,Django 會給每個 model 添加下面這個字段:
id = models.AutoField(primary_key=True)
這是一個自增主鍵字段。
如果你想指定一個自定義主鍵字段,只要在某個字段上指定 primary_key=True 即可。如果 Django 看到你顯式地設(shè)置了 Field.primary_key,就不會自動添加 id 列。
每個 model 只要有一個字段指定 primary_key=True 就可以了。(可以自定義也可以保持默認(rèn)自動增加的主鍵)
Verbose field names(詳細(xì)名稱)
每個字段的類型,除了ForeignKey, ManyToManyField 和 OneToOneField外,還有一個可選的第一位置參數(shù),這個參數(shù)用于標(biāo)記字段的詳細(xì)名稱。如果Verbose field names沒有顯示的給出,Django會自動創(chuàng)建這一字段屬性,將字段名中的"_"轉(zhuǎn)換成空格。
例如:設(shè)置詳細(xì)名稱為 "person's first name":
first_name = models.CharField("person's first name", max_length=30)
如果沒有設(shè)置詳細(xì)名稱,則詳細(xì)名稱為: "first name":
first_name = models.CharField(max_length=30)
由于ForeignKey, ManyToManyField和 OneToOneField
需要使用第一參數(shù),所以可以顯示的使用 verbose_name來設(shè)置詳細(xì)名稱:
poll = models.ForeignKey(Poll, verbose_name="the related poll")
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(Place, verbose_name="related place")
僅在以上特列中使用verbose_name,Django會自動利用第一個參數(shù)。
Relationships(關(guān)系)
Django支持關(guān)系數(shù)據(jù)庫中常用的many-to-one,many-tomany,one-to-one
Many-to-one(多對一關(guān)系)
定義Many-to-one關(guān)系,使用django.db.models.ForeignKey。使用方法和其他字段類型一樣:在model中定義時包含F(xiàn)oreignKey屬性。
ForeignKey必須要一個參數(shù):需要鏈接到哪個model.
例如:一輛汽車(car)和汽車制造商(manufacturer)的關(guān)系,那么一個汽車制造商會有制造多輛車,但每輛車卻只能有一個汽車制造商:
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer)
你也可以定義一個遞歸的關(guān)系(在對象內(nèi)容實部Many-to-one的定義)和relationships to models not yet defined(沒看明白,看完模型關(guān)系后再修改);
建議但不強制要求ForeignKey字段的名字是模型的小寫字母的名字(例如在上例中使用的manufacturer)。當(dāng)然你可以使用任何你想要的名字,例如:
class Car(models.Model):
company_that_makes_it = models.ForeignKey(Manufacturer)
Many-to-many(多對多關(guān)系)
定義Many-to-many關(guān)系,使用django.db.models.ManyToManyField.使用方法和其他字段類型一樣:在model中定義包含ManyToManyField屬性。
ManyToManyField必須要一個參數(shù):需要鏈接到那個model.
例如,一個Pizza有多個Topping對象——也就是一個Topping可以在多個Pizza上,每個Pizza有多個Toppings——這種情況我們可以這樣定義:
from django.db import models
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
和ForeignKey一樣,可以創(chuàng)建遞歸關(guān)系(在對象內(nèi)部實現(xiàn)Many-to-many的定義)和relationships to models not yet defined
建議但不強制要求ManyToManyField的名字(上面的例子中的toppings)是復(fù)數(shù)形式,復(fù)數(shù)形式是為了描述相關(guān)模型對象的集合。
哪個模型帶有ManyToManyField都沒關(guān)系,但你只能在其中一個模型中使用它,而不能在兩個模型中都使用它。
一般來說,ManyToManyField實例應(yīng)該包含在使用表單編輯的對象中,在上面的例子中,toppings在Pizza中(而不是Topping有pizzas ManyToManyField),因為一個pizzas有多個Topping,比一個Topping在多個pizzas上更容易讓人理解。這就是上面我們使用的方式,Pizza管理表單將讓用戶選擇那種Topping。
還有一些可選參數(shù)。
Many-to-many關(guān)系的額外字段
如果只需要處理簡單的多對多關(guān)系,就像上面pizzas和topping的關(guān)系,那么ManyToManyField字段就可以滿足需要,然而,有些時候你需要讓數(shù)據(jù)在兩個模型之間產(chǎn)生聯(lián)系。
例如,考慮一下跟蹤樂隊和樂隊擁有的音樂家的應(yīng)用程序的例子。這是一個人和這個人所在團(tuán)隊之間的多對多關(guān)系,因此你可以使用ManyToManyField來描述這個關(guān)系。然而,這種成員關(guān)系有很多你想要搜集的細(xì)節(jié),比如這個人加入團(tuán)隊的時間。
對于這種情況,Django讓你可以指定用來管理多對多關(guān)系的模型。然后你可以在中間模型中放入額外的字段。中間模型使用through參數(shù)指向像中間人一樣工作的模型,來和ManyToManyField發(fā)生關(guān)系。對于四個音樂家的例子,代碼可能像這樣子:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
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)
當(dāng)你建立中間模型時,你需要為模型明確地指定外鍵,以建立多對多關(guān)系。這個明確的聲明定義了兩個模型是如何產(chǎn)生聯(lián)系的。
對于中間模型,有一些限制:
中間模型必須包含并且只包含一個到目標(biāo)模型的外鍵(在上面的例子中的Group)。或者使用ManyToManyField.through_fields來明確指定外鍵關(guān)系。如果你有多個外鍵,但沒有指定through_fields,會產(chǎn)生校驗錯誤。類似的限制適用于外鍵的目標(biāo)model(例如Person)
對于一個model通過中間model實現(xiàn)多對多關(guān)系,兩個到同一模型的外鍵是允許的,但會被認(rèn)為是多對多關(guān)系的兩個不同側(cè)面。如果有兩個或以上的外鍵定義,你必須要定義through_fields,否則會產(chǎn)生校驗錯誤。
當(dāng)使用中間模型來定義一個到自己的多對多關(guān)系的模型時,你必須使用symmetrical=False(參閱“模型字段參考”)。
現(xiàn)在你已經(jīng)建立了ManyToManyField來使用中間模型(在這個例子中是MemberShip),你可以開始創(chuàng)建一些多對多的對應(yīng)關(guān)系了。具體來說是創(chuàng)建中間模型的一些實例:
現(xiàn)在已經(jīng)在中間model中設(shè)置了ManyToManyField(例子中的Membership),你可以通過中間model創(chuàng)建關(guān)系實例:
>>> 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()
>>> beatles.members.all()
[<Person: Ringo Starr>]
>>> ringo.group_set.all()
[<Group: The Beatles>]
>>> m2 = Membership.objects.create(person=paul, group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.")
>>> beatles.members.all()
[<Person: Ringo Starr>, <Person: Paul McCartney>]
不同于一般的many-to-many關(guān)系字段,你不能通過直接通過關(guān)系對象進(jìn)行增加、創(chuàng)建或賦值(即:beatles.members = [...])
# THIS WILL NOT WORK
>>> beatles.members.add(john)
# NEITHER WILL THIS
>>> beatles.members.create(name="George Harrison")
# AND NEITHER WILL THIS
>>> beatles.members = [john, paul, ringo, george]
這是因為你需要知道一些Person和Group關(guān)系之外的一些細(xì)節(jié),這些細(xì)節(jié)在中間model--Membership中定義,而不僅僅只是簡單創(chuàng)建了Person和Group之間的關(guān)系。類似關(guān)系的唯一解決辦法是創(chuàng)建中間model
基于同樣的原因 remove() 也是被禁用的,但可以通過 clear() 清除所有多對多關(guān)系實例:
>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
[]
一旦創(chuàng)建了中間model實例,并建立了一個多對多關(guān)系實例,就可以和正常的多對多關(guān)系一樣進(jìn)行查詢:
# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith='Paul')
[<Group: The Beatles>]
因為你正在使用中間模型,你也可以使用它的屬性來進(jìn)行查詢:
# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
... group__name='The Beatles',
... membership__date_joined__gt=date(1961,1,1))
[<Person: Ringo Starr]
如果你需要訪問membership’s 信息,你可以這樣做直接查詢Membership:
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
u'Needed a new drummer.'
當(dāng)然你也可以通過多對多的反向關(guān)系從Person 實例進(jìn)行查詢:
>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
u'Needed a new drummer.'
One-to-one關(guān)系
要定義一對一關(guān)系,請使用OneToOneField。它的使用方法和其它字段一樣:把它包含在模型的類屬性中。
當(dāng)對象以某種方式擴(kuò)展了另一個對象的主鍵時,對象的主鍵是最重要的。
OneToOneField要求一個位置參數(shù):該模型相關(guān)的類。
例如,如果你將創(chuàng)建一個數(shù)據(jù)表places,你可能會在數(shù)據(jù)表中建立一些相當(dāng)標(biāo)準(zhǔn)的東西,就像地址、電話號碼等等。然后,如果你想在places上建立一個飯館,你可以不必重復(fù)勞動,在Restaurant模型中復(fù)制這些字段,你可以建立一個帶有到Place的OneToOneField(因為飯館就是一個place;實際上,對于這種情況典型的做法是使用繼承,這實際上是一種隱式的一對一關(guān)系)。
和外鍵一樣,你可以定義循環(huán)關(guān)系和到未定義模型的關(guān)系;
OneToOneField也接受一個可選的參數(shù)parent_link 這個參數(shù)在“模型字段參考”有介紹。
OneToOneField類曾經(jīng)會自動成為模型的主鍵。現(xiàn)在情況不再如此了(如果你愿意你可以手動傳遞一個primary_key參數(shù))。因此,現(xiàn)在一個模型可以擁有多個OneToOneField類型的字段。
Models across files(跨文件的model)
在當(dāng)前model和另一個應(yīng)用程序中的model建立關(guān)系是沒有問題的,只需要引入相關(guān)的model,然后在需要的時候使用就可以了:
from django.db import models
from geography.models import ZipCode
class Restaurant(models.Model):
# ...
zip_code = models.ForeignKey(ZipCode)
Field name restrictions(字段名限制 )
Django對字段名只有兩個限制:
-
字段名不能是Python的保留關(guān)鍵字,不然會導(dǎo)致Python語法錯誤。例如:
class Example(models.Model):
pass = models.IntegerField() # 'pass' is a reserved word! -
字段名在一行中不能包含一個以上的下劃線,這和Django的搜索查詢語法的工作方式有關(guān)。例如:
class Example(models.Model):
foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
這些限制是可以繞過的,因為你的字段名并不需要和數(shù)據(jù)表的列名匹配。
SQL保留字,比如join、where或select,可以用作模型字段名,因為Django在進(jìn)行底層的SQL查詢前會對所有數(shù)據(jù)表名和列名進(jìn)行轉(zhuǎn)義。
Custom field types(自定義字段類型)
如果現(xiàn)有的字段類型不能滿足你的需要,或者你使用的數(shù)據(jù)庫具有一些特殊的類型,你可以創(chuàng)建自己字段類型。
Meta options
定義model的metadata(元數(shù)據(jù))是通過使用一個內(nèi)部類Meta,例:
from django.db import models
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
model元數(shù)據(jù)不是一個字段,例如ordering,db_table,verbose_name 和 verbose_name_plural這些附加選項,都可以放到class Meta中,這些選項都不是必需的。
Model attributes
objects
這是model最重要的Manager屬性,通過它查詢數(shù)據(jù)庫并于model之間形成映射。如果沒有自定義manager,默認(rèn)名稱為objects。managers只能通過model類,而不能通過model類實例來訪問。
Model methods
通過model自定義方法添加自定義"行級"功能,而managers方法是為“表級”添加自定義功能,model自定義方法可以通過model實例來使用。
這樣就可以把業(yè)務(wù)邏輯都放在一個model里。例如:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
def _get_full_name(self):
"Returns the person's full name."
return '%s %s' % (self.first_name, self.last_name)
full_name = property(_get_full_name)
上個實例的最后一個方法是屬性。
這個model實例繼承自models.Model,會自動具備大量的方法,可以覆蓋大部分的方法,但有幾個卻是必須的:
str() (Python 3)
在Python3中相當(dāng)于unicode()
unicode() (Python 2)
這是一個Python的"魔術(shù)方法",它以unicode方式返回任何對象的陳述。Python和Django需要輸出字符串陳述時使用。例如在交互式控制臺或管理后臺顯示的輸出陳述。
默認(rèn)的實現(xiàn)并不能很好的滿足需要,所以最好自定義這個方法,
get_absolute_url()
定義對象的URL.在管理界面中,任何時候都可以通過URL找到一個對象。
任何通過URL訪問的對象,都應(yīng)該有唯一標(biāo)識。
Overriding predefined model methods
還有一些model方法封裝了一些行為。當(dāng)你想自定義數(shù)據(jù)庫行為,尤其是想改變save() 和 delete()方式的時候。
你可以自由地重寫這些方法來改變行為。
如果你想在保存對象的時候,覆蓋內(nèi)置save() 行為:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs):
do_something()
super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
do_something_else()
你也可以阻止保存:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs):
if self.name == "Yoko Ono's blog":
return # Yoko shall never have her own blog!
else:
super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
需要特別注意的要記得調(diào)用父類的方法--super(Blog, self).save(*args, **kwargs),以確保對象仍然被保存到數(shù)據(jù)庫中,如果你忘記調(diào)用父類的方法,默認(rèn)的行為不會發(fā)生,數(shù)據(jù)庫也不會發(fā)生改變。
隨著時間的推移,django會增加或擴(kuò)展一些新的方法,如果你使用*args, **kwargs作為你的方法參數(shù),就必須要保證能正確處理這些參數(shù)的增加。
覆蓋方法大多數(shù)不會用于批量操作
delete()并不一定是在調(diào)用一個QuerySet批量刪除時被觸發(fā)。為了確保自定義的刪除邏輯被執(zhí)行,則需要使用 pre_delete and/or post_delete 信號。
不幸的是,還沒有一個好的方法用于批量的創(chuàng)建和更新,因為沒有save()、pre_save、post_save會被調(diào)用。
Executing custom SQL
另外一種常見的模式是在model方法和module-level方法中執(zhí)行自定義SQL。
Model inheritance(Model繼承)
Model的繼承和普通的Python類繼承幾乎相同,但基類的繼承必須是django.db.models.Model.
在Django中有三種繼承風(fēng)格:
1、如果你想用父類來保存每個子類共有的信息,并且這個類是不會被獨立使用的,那么應(yīng)該使用抽象基類。
2、如果你繼承現(xiàn)有model(甚至可能這個類是在另一個應(yīng)用程序中),并希望每個model都擁有自己對應(yīng)的數(shù)據(jù)庫,那就應(yīng)該使用多表繼承。
3、如果你只是想修改model的Python-level行為,而不改變models fields,則使用代理模式。
Abstract base classes
當(dāng)你想集中一些公共信息,可以使用虛類。你需要在model的元數(shù)據(jù)中設(shè)置 abstract=True ,這個model將不會被用于創(chuàng)建任何數(shù)據(jù)表。相反,當(dāng)他被作為其他子類的基類時,他的字段將被添加到這些子類中。如果抽象類和他的子類有相同名稱的字段會產(chǎn)生一個異常。
例如:
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
class Student(CommonInfo):
home_group = models.CharField(max_length=5)
Student會擁有三個字段:name\age\home_group。CommonInfo將不能像普通的Django model一樣使用,因為他是一個抽象基類。他不會產(chǎn)生一個數(shù)據(jù)表或者擁有一個管理器,也不能被實例化或直接調(diào)用。
在許多情況下這種類型的繼承正是你想要的,它提供了一種用來分解公共信息的方法,雖然只能實現(xiàn)數(shù)據(jù)表級別創(chuàng)建子模型。
Meta inheritance
當(dāng)創(chuàng)建一個抽象基類的時候,Django允許在基類中聲明各種Meta屬性,如果子類沒有聲明自己的Meta元數(shù)據(jù),他將繼承父類的。如果子類想擴(kuò)展父類的Meta元數(shù)據(jù),則可以繼承父類的Meta。例如:
from django.db import models
class CommonInfo(models.Model):
# ...
class Meta:
abstract = True
ordering = ['name']
class Student(CommonInfo):
# ...
class Meta(CommonInfo.Meta):
db_table = 'student_info'
Django對于抽象基類的元數(shù)據(jù)調(diào)整只應(yīng)該在安裝之前設(shè)置abstract=false。這意味著從抽象基類派生的子類不會自動轉(zhuǎn)型成抽象類本身。當(dāng)然你也可以繼承來自別一個抽象基類。只需要記住abstract=True每次都應(yīng)該明確設(shè)置。(這段沒怎么看明白)
在抽象基類中某些屬性幾乎是沒有任何意義的,包括父類的元數(shù)據(jù)。例如使用db_table將意味著所有的子類(那些沒有指定自己的Meta)將使用同一數(shù)據(jù)庫,這肯定不會是你想要的。
Be careful with related_name(注意抽象基類中的反向關(guān)系名稱定義)
如果你在ForeignKey和ManyToManyField的屬性中使用related_name,你必須為字段指定一個唯一反向關(guān)系名稱。在抽象基類中會有一些問題,因為抽象基類的每個字段都會被包括在他的每一個子類中,包括related_name.
要解決這個問題,應(yīng)該在抽象基類中使用related_name時,名稱中應(yīng)包含'%(app_label)s' 和 '%(class)s'.
'%(class)s':小寫的子類名稱
'%(app_label)s':應(yīng)用的小寫名稱(app)。因為每個已安裝的應(yīng)用名稱都是唯一的,所以產(chǎn)生的名稱最終也會不同。
例如:首先定義common/models.py:
from django.db import models
class Base(models.Model):
m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")
class Meta:
abstract = True
class ChildA(Base):
pass
class ChildB(Base):
pass
然后另一個APP rare/models.py:
from common.models import Base
class ChildB(Base):
pass
在這個示例中 common.ChildA.m2m 字段的反向關(guān)系名稱是common_childa_related,而common.ChildB.m2m 的關(guān)系名稱ommon_childb_related。這是因為使用了'%(app_label)s' 和 '%(class)s'產(chǎn)生了不同的反向關(guān)系名稱。如果你定義了related_name,但忘記了使用'%(app_label)s' 和 '%(class)s' Django會系統(tǒng)檢查或運行migrate時引發(fā)錯誤。
如果沒有在抽象基類的字段中定義related_name屬性,默認(rèn)關(guān)系名稱將是子類名稱+"_set"。通常related_name會被直接在子類的字段屬性中被定義。例如上例中,如果related_name屬性被省略。common.ChildA.m2m 的反向關(guān)系名稱應(yīng)該是childa_set,common.ChildB.m2m的反向關(guān)系名稱應(yīng)該是childb_set。
Multi-table inheritance(多表繼承)
Django支持的第二種model繼承是多表繼承,在繼承結(jié)構(gòu)中每個model都是獨立的。都對應(yīng)著自己的數(shù)據(jù)庫表,可以進(jìn)行獨立的查詢等操作。繼承關(guān)系實際是子model和每個父model之間的關(guān)系(通過自動創(chuàng)建OneToOneField)。例如:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
所有Place的字段都可以在Restaurant中使用,雖然數(shù)據(jù)存放在不同的數(shù)據(jù)表中。所以可以如下使用:
>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")
如果一個Place對象存在相應(yīng)的Restaurant對象,那么就可以使用Place對象通過關(guān)系獲得Restaurant對象:
>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>
但如果place對象所對應(yīng)的Restaurant對象不存在,則會引發(fā) Restaurant.DoesNotExist 異常。
Meta and multi-table inheritance
在多表繼承的情況下繼承父類的Meta是沒有意義的。所有的Meta都已經(jīng)被應(yīng)用到父類,再應(yīng)用這些Meta只會導(dǎo)致矛盾。
所以子model不能訪問到父model的Meta,然而也有少數(shù)的情況下,子model會從父model中繼承一些行為,例如子model沒有指定 ordering或 get_latest_by屬性,那么就會從父model中繼承。
如果父model中有一個排序,但你不希望子model有任何的排序規(guī)劃,你可以明確的禁用:
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
Inheritance and reverse relations(繼承與反向關(guān)系)
因為多表繼承實際是隱式的使用OneToOneField來鍵接父Model和子model,在這種關(guān)系有可能會使用父model來調(diào)用子model,比如上面的例子。但是如果你把ForeignKey和ManyToManyField關(guān)系應(yīng)用到這樣一個繼承關(guān)系中,Django會返回一個驗證錯誤,必須要指定一個related_name字段屬性。
例如上面的例子,我們再創(chuàng)建一個子類,其中包含一個到父model的ManyToManyField關(guān)系字段:
class Supplier(Place):
customers = models.ManyToManyField(Place)
這時會產(chǎn)生一個錯誤:
Reverse query name for 'Supplier.customers' clashes with reverse query
name for 'Supplier.place_ptr'.
HINT: Add or change a related_name argument to the definition for
'Supplier.customers' or 'Supplier.place_ptr'.
解決這個問題只需要在customers字段屬性中增加related_name屬性:models.ManyToManyField(Place, related_name='provider').
Specifying the parent link field
如上所述,Django會自動創(chuàng)建一個OneToOneField鏈接你的子model和任何非抽象父model。如果你想自定義子model鍵接回父model的屬性名稱,你可以創(chuàng)建自己的OneToOneField并設(shè)置parent_link=True,表示這個字段是對父model的回鏈。
Proxy models
當(dāng)使用多表繼承時一個新的數(shù)據(jù)表model會在每一個子類中創(chuàng)建,這是因為子model需要存儲父mdoel不存在的一些數(shù)據(jù)字段。但有時只需要改變model的操作行為,可能是為了改變默認(rèn)的管理行為或添加新的方法。
這時就應(yīng)該使用代理模式的繼承:創(chuàng)建原始model的代理。你可以創(chuàng)建一個用于 create, delete 和 update的代理model,使用代理model的時候數(shù)據(jù)將會真實保存。這和使用原始model是一樣的,所不同的是當(dāng)你改變model操作時,不需要去更改原始的model。
代理模式的聲明和正常的繼承聲明方式一樣。你只需要在Meta class 中定義proxy為True就可以了。
例如,你想為Person model添加一個方法:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class MyPerson(Person):
class Meta:
proxy = True
def do_something(self):
# ...
pass
MyPerson這個類將作用于父類Person所對應(yīng)的真實數(shù)據(jù)表。可通過MyPerson進(jìn)行所有相應(yīng)的操作:
>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>
你也可以使用代理模式來定義model的不同默認(rèn)排序,例如:
class OrderedPerson(Person):
class Meta:
ordering = ["last_name"]
proxy = True
這樣當(dāng)使用原始model查詢時結(jié)果是無序的,而使用OrderedPerson進(jìn)行查詢時將按last_name進(jìn)行排序。
QuerySets still return the model that was requested(QuerySets的類型依然會是原始model類型)
當(dāng)你通過MyPerson來查詢Person對象時,返回的QuerySet依然會是Person對象類型的集合。使用代理模式的model是依靠原始model的,是原始model的擴(kuò)展。而不是用來替代父model。
Base class restrictions(基類限制)
代理model必須繼承一個非抽像model。
不能從多個非抽像model繼承,代理模式不能為不同model之間創(chuàng)建鏈接。
代理模式可以從任意沒有定義字段的抽象model繼承。
Proxy model managers
如果沒有指定代理model的管理器(managers),它將繼承父類的管理行為。如果你定義了代理model的管理器,它將會成為默認(rèn)的,當(dāng)然父類中定義的定義的任何管理器仍然是可以使用的。
繼續(xù)上面的例了,增加一個默認(rèn)的管理器:
from django.db import models
class NewManager(models.Manager):
# ...
pass
class MyPerson(Person):
objects = NewManager()
class Meta:
proxy = True
可以通過創(chuàng)建一個含有新的管理器并進(jìn)行繼承,來增加一個新的管理器,而不需要去改變更有的默認(rèn)管理器。
# Create an abstract class for the new manager.
class ExtraManagers(models.Model):
secondary = NewManager()
class Meta:
abstract = True
class MyPerson(Person, ExtraManagers):
class Meta:
proxy = True
Differences between proxy inheritance and unmanaged models
代理model看起來很像一個在Meta class中設(shè)置了manged的非托管模式model。但實際上這兩種方案是不太一樣,應(yīng)該考慮在不同的情況下使用那一個:
兩者區(qū)別在于:你可以設(shè)置model的Meta.managed=False以及通過Meta.db_table指定數(shù)據(jù)表有創(chuàng)建非托管模式model,并對其添加各種方法,但如果你可保持非托管模式和真實數(shù)據(jù)表之間的同步,做任何更改都將是很麻煩的事。
而代理model主要用于管理model的各種行為或方法,他們將繼承父model的管理器等。
曾經(jīng)嘗試將兩種模式合并,但由于API會變得非常復(fù)雜,并且難以理解,所以現(xiàn)在是分離成兩種模式:
一般的使用規(guī)劃是:
1、如果正使用現(xiàn)有的數(shù)據(jù)表,但不想在Django中鏡像所有的列,所以應(yīng)該使用Meta.managed=False,通過這個選項使不在django控制下的數(shù)據(jù)表或視圖是可用的。
2、如果你想改變一個model的操作行為,但希望保持原始model不被改變,就應(yīng)該使用Meta.proxy=True.
Multiple inheritance(多重繼承)
和Python的繼承方式一樣,django中的model也可以從多個父model繼承,當(dāng)然也和Python的繼承方式 一樣,如果出現(xiàn)相同名字的時候只有第一個將被使用。例如:如果多個父model中都包含Meta類,將只有第一個將被使用,其他會被忽略。
通常情況下是不會用到多重繼承的。主是用于“混合式”model:增加一個特殊的額外字段或方式是由多個父model組合而來。應(yīng)該盡量保持繼承層次的簡單,不然會很難排查某個信息是從那里來的。
在django1.7以前,多個父model中有id主鍵字段時雖然不會引發(fā)錯誤,但有可能導(dǎo)致數(shù)據(jù)的丟失。例如像下面的model:
class Article(models.Model):
headline = models.CharField(max_length=50)
body = models.TextField()
class Book(models.Model):
title = models.CharField(max_length=50)
class BookReview(Book, Article):
pass
下面的使用方法演示了怎么用一個子對象來覆蓋父對象的值:
>>> article = Article.objects.create(headline='Some piece of news.')
>>> review = BookReview.objects.create(
... headline='Review of Little Red Riding Hood.',
... title='Little Red Riding Hood')
>>>
>>> assert Article.objects.get(pk=article.pk).headline == article.headline
Traceback (most recent call last):
File "<console>", line 1, in <module>
AssertionError
>>> # the "Some piece of news." headline has been overwritten.
>>> Article.objects.get(pk=article.pk).headline
'Review of Little Red Riding Hood.'
要正確的使用多重繼承,你應(yīng)該使用一個明確的 AutoField 在父model中:
class Article(models.Model):
article_id = models.AutoField(primary_key=True)
...
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
...
class BookReview(Book, Article):
pass
或者使用一個共同的父類來定義AutoField:
class Piece(models.Model):
pass
class Article(Piece):
...
class Book(Piece):
...
class BookReview(Book, Article):
pass
Field name “hiding” is not permitted
正常的Python類繼承,允許一個子類覆蓋父類的任何屬性。在Django中是不允許覆蓋父類的屬性字段的。如果一個父類中定義了一個叫author的字段,你就不能在子model中創(chuàng)建別一個叫author的字段。
這種限制僅適用于字段(field),普通的python屬性是可以的。也有一種情況是可以覆蓋的:多表繼承的時候進(jìn)行手動指定數(shù)據(jù)庫列名,可以出現(xiàn)子model和父model有同名的字段名稱,因為他們屬于不同的數(shù)據(jù)表。
如果你覆蓋了父model的字段屬性,django會拋出FieldError異常。