1. 程式人生 > >Django Model 數據表

Django Model 數據表

選擇框 amp images first 中間人 set geography 代理模式 tro

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
);

-默認會根據APP的名稱生成"app名稱"+"_"+"類名"
-會自動增加ID字段
-Django會根據settings配置中指定的數據庫類型來生成相應的SQL語句

使用Model

要想使用Model需要在settings配置中INSTALLED_APPS中增加你的APP,例如,你的models在myapp.models中,那麽就應該使用:

INSTALLED_APPS = (
    #...
    ‘myapp‘,
    #...
)

在將app添加到INSTALLD_APPS中後需要執行manage.py migrate,也可通過manage.py makemigrations進行遷移。

Fields

Django提供了各種數據字段類型。需要註意不要使用與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會根據field類型確定

  • 數據庫字段類型(如INTEGER,VARCHAR)
  • 默認的HTML生成什麽樣的表單項
  • 最低限度的驗證需求。它被用在 Django 管理後臺和自動生成的表單中。

field是可以自定義的。

Field options

每個field類型都有自己特定的參數,但也有一些通用的參數,這些參數都是可選的:

null

如果為 True , Django 在數據庫中會將空值(empty)存儲為 NULL 。默認為 False 。

blank

設置字段是否可以為空,默認為False(不允許為空)

和null的區別在於:null是數據庫的範圍,而blank是用於驗證。如果一個字段的 blank=True ,Django 在進行表單數據驗證時,會允許該字段是空值。如果字段的 blank=False ,該字段就是必填的。

choices

它是一個可叠代的二元組(例如,列表或是元組),用來給字段提供選擇項。如果設置了 choices, Django會顯示選擇框,而不是標準的文本框,而且這個選擇框的選項就是 choices 中的元組。

YEAR_IN_SCHOOL_CHOICES = (
    (‘FR‘, ‘Freshman‘),
    (‘SO‘, ‘Sophomore‘),
    (‘JR‘, ‘Junior‘),
    (‘SR‘, ‘Senior‘),
    (‘GR‘, ‘Graduate‘),
)

每個元組中的第一個元素,是存儲在數據庫中的值;第二個元素是在管理界面或 ModelChoiceField 中用作顯示的內容。在一個給定的 model 類的實例中,想得到某個 choices 字 段的顯示值,就調用 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

默認值,可以是一個具體的值也可以是一個對象,每次調用次會創建一個新的對象

help_text

附加的幫助信息。在使用表單時會顯示在字段下面。即使不使用表單也可以起來幫助文檔的作用。

primary_key

如果為True,則表示這個字段是主鍵。

如果你沒有設置主鍵,Django會自動創建一個自增的IntergerField類型主鍵,可以通過自定義主鍵來覆蓋默認的行為。

unique

如果為 True ,那麽字段值就必須是全表唯一的。


Automatic primary key fields

默認情況下,Django 會給每個 model 添加下面這個字段:

id = models.AutoField(primary_key=True)

這是一個自增主鍵字段。

如果你想指定一個自定義主鍵字段,只要在某個字段上指定 primary_key=True 即可。如果 Django 看到你顯式地設置了 Field.primary_key,就不會自動添加 id 列。

每個 model 只要有一個字段指定 primary_key=True 就可以了。(可以自定義也可以保持默認自動增加的主鍵)


Verbose field names(詳細名稱)

每個字段的類型,除了ForeignKey, ManyToManyField 和 OneToOneField外,還有一個可選的第一位置參數,這個參數用於標記字段的詳細名稱。如果Verbose field names沒有顯示的給出,Django會自動創建這一字段屬性,將字段名中的"_"轉換成空格。

例如:設置詳細名稱為 "person‘s first name":

first_name = models.CharField("person‘s first name", max_length=30)

如果沒有設置詳細名稱,則詳細名稱為: "first name":

first_name = models.CharField(max_length=30)

由於ForeignKey, ManyToManyField和 OneToOneField
需要使用第一參數,所以可以顯示的使用 verbose_name來設置詳細名稱:

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會自動利用第一個參數。

Relationships(關系)

Django支持關系數據庫中常用的many-to-one,many-tomany,one-to-one

Many-to-one(多對一關系)

定義Many-to-one關系,使用django.db.models.ForeignKey。使用方法和其他字段類型一樣:在model中定義時包含ForeignKey屬性。

ForeignKey必須要一個參數:需要鏈接到哪個model.

例如:一輛汽車(car)和汽車制造商(manufacturer)的關系,那麽一個汽車制造商會有制造多輛車,但每輛車卻只能有一個汽車制造商:

from django.db import models

class Manufacturer(models.Model):
    # ...
    pass

class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer)

你也可以定義一個遞歸的關系(在對象內容實部Many-to-one的定義)和relationships to models not yet defined(沒看明白,看完模型關系後再修改);

建議但不強制要求ForeignKey字段的名字是模型的小寫字母的名字(例如在上例中使用的manufacturer)。當然你可以使用任何你想要的名字,例如:

class Car(models.Model):
    company_that_makes_it = models.ForeignKey(Manufacturer)

Many-to-many(多對多關系)

定義Many-to-many關系,使用django.db.models.ManyToManyField.使用方法和其他字段類型一樣:在model中定義包含ManyToManyField屬性。

ManyToManyField必須要一個參數:需要鏈接到那個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一樣,可以創建遞歸關系(在對象內部實現Many-to-many的定義)和relationships to models not yet defined

建議但不強制要求ManyToManyField的名字(上面的例子中的toppings)是復數形式,復數形式是為了描述相關模型對象的集合。

哪個模型帶有ManyToManyField都沒關系,但你只能在其中一個模型中使用它,而不能在兩個模型中都使用它。

一般來說,ManyToManyField實例應該包含在使用表單編輯的對象中,在上面的例子中,toppings在Pizza中(而不是Topping有pizzas ManyToManyField),因為一個pizzas有多個Topping,比一個Topping在多個pizzas上更容易讓人理解。這就是上面我們使用的方式,Pizza管理表單將讓用戶選擇那種Topping。

還有一些可選參數。

Many-to-many關系的額外字段

如果只需要處理簡單的多對多關系,就像上面pizzas和topping的關系,那麽ManyToManyField字段就可以滿足需要,然而,有些時候你需要讓數據在兩個模型之間產生聯系。

例如,考慮一下跟蹤樂隊和樂隊擁有的音樂家的應用程序的例子。這是一個人和這個人所在團隊之間的多對多關系,因此你可以使用ManyToManyField來描述這個關系。然而,這種成員關系有很多你想要搜集的細節,比如這個人加入團隊的時間。

對於這種情況,Django讓你可以指定用來管理多對多關系的模型。然後你可以在中間模型中放入額外的字段。中間模型使用through參數指向像中間人一樣工作的模型,來和ManyToManyField發生關系。對於四個音樂家的例子,代碼可能像這樣子:

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)

當你建立中間模型時,你需要為模型明確地指定外鍵,以建立多對多關系。這個明確的聲明定義了兩個模型是如何產生聯系的。

對於中間模型,有一些限制:

中間模型必須包含並且只包含一個到目標模型的外鍵(在上面的例子中的Group)。或者使用ManyToManyField.through_fields來明確指定外鍵關系。如果你有多個外鍵,但沒有指定through_fields,會產生校驗錯誤。類似的限制適用於外鍵的目標model(例如Person)

對於一個model通過中間model實現多對多關系,兩個到同一模型的外鍵是允許的,但會被認為是多對多關系的兩個不同側面。如果有兩個或以上的外鍵定義,你必須要定義through_fields,否則會產生校驗錯誤。

當使用中間模型來定義一個到自己的多對多關系的模型時,你必須使用symmetrical=False(參閱“模型字段參考”)。

現在你已經建立了ManyToManyField來使用中間模型(在這個例子中是MemberShip),你可以開始創建一些多對多的對應關系了。具體來說是創建中間模型的一些實例:

現在已經在中間model中設置了ManyToManyField(例子中的Membership),你可以通過中間model創建關系實例:

>>> 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關系字段,你不能通過直接通過關系對象進行增加、創建或賦值(即: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關系之外的一些細節,這些細節在中間model--Membership中定義,而不僅僅只是簡單創建了Person和Group之間的關系。類似關系的唯一解決辦法是創建中間model

基於同樣的原因 remove() 也是被禁用的,但可以通過 clear() 清除所有多對多關系實例:

>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
[]

一旦創建了中間model實例,並建立了一個多對多關系實例,就可以和正常的多對多關系一樣進行查詢:

# Find all the groups with a member whose name starts with ‘Paul‘
>>> Group.objects.filter(members__name__startswith=‘Paul‘)
[<Group: The Beatles>]

因為你正在使用中間模型,你也可以使用它的屬性來進行查詢:

# 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.‘

當然你也可以通過多對多的反向關系從Person 實例進行查詢:

>>> 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關系

要定義一對一關系,請使用OneToOneField。它的使用方法和其它字段一樣:把它包含在模型的類屬性中。

當對象以某種方式擴展了另一個對象的主鍵時,對象的主鍵是最重要的。

OneToOneField要求一個位置參數:該模型相關的類。

例如,如果你將創建一個數據表places,你可能會在數據表中建立一些相當標準的東西,就像地址、電話號碼等等。然後,如果你想在places上建立一個飯館,你可以不必重復勞動,在Restaurant模型中復制這些字段,你可以建立一個帶有到Place的OneToOneField(因為飯館就是一個place;實際上,對於這種情況典型的做法是使用繼承,這實際上是一種隱式的一對一關系)。

和外鍵一樣,你可以定義循環關系和到未定義模型的關系;

OneToOneField也接受一個可選的參數parent_link 這個參數在“模型字段參考”有介紹。

OneToOneField類曾經會自動成為模型的主鍵。現在情況不再如此了(如果你願意你可以手動傳遞一個primary_key參數)。因此,現在一個模型可以擁有多個OneToOneField類型的字段。

Models across files(跨文件的model)

在當前model和另一個應用程序中的model建立關系是沒有問題的,只需要引入相關的model,然後在需要的時候使用就可以了:

from django.db import models
from geography.models import ZipCode

class Restaurant(models.Model):
    # ...
    zip_code = models.ForeignKey(ZipCode)

Field name restrictions(字段名限制 )

Django對字段名只有兩個限制:

1) 字段名不能是Python的保留關鍵字,不然會導致Python語法錯誤。例如:

class Example(models.Model):
    pass = models.IntegerField() # ‘pass‘ is a reserved word!

2) 字段名在一行中不能包含一個以上的下劃線,這和Django的搜索查詢語法的工作方式有關。例如:

class Example(models.Model):
    foo__bar = models.IntegerField() # ‘foo__bar‘ has two underscores!

這些限制是可以繞過的,因為你的字段名並不需要和數據表的列名匹配。

SQL保留字,比如join、where或select,可以用作模型字段名,因為Django在進行底層的SQL查詢前會對所有數據表名和列名進行轉義。

Custom field types(自定義字段類型)

如果現有的字段類型不能滿足你的需要,或者你使用的數據庫具有一些特殊的類型,你可以創建自己字段類型。

Meta options

定義model的metadata(元數據)是通過使用一個內部類Meta,例:

from django.db import models

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

model元數據不是一個字段,例如ordering,db_table,verbose_name 和 verbose_name_plural這些附加選項,都可以放到class Meta中,這些選項都不是必需的。

Model attributes

objects

這是model最重要的Manager屬性,通過它查詢數據庫並於model之間形成映射。如果沒有自定義manager,默認名稱為objects。managers只能通過model類,而不能通過model類實例來訪問。

Model methods

通過model自定義方法添加自定義"行級"功能,而managers方法是為“表級”添加自定義功能,model自定義方法可以通過model實例來使用。

這樣就可以把業務邏輯都放在一個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)

class Tag(models.Model):
    name = models.CharField(unique=True,max_length=32)

  

技術分享

class Tag(models.Model):
    name = models.CharField(unique=True,max_length=32)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "標簽"
        verbose_name_plural = "標簽"

  

技術分享

在Python3中相當於unicode()

unicode() (Python 2)

這是一個Python的"魔術方法",它以unicode方式返回任何對象的陳述。Python和Django需要輸出字符串陳述時使用。例如在交互式控制臺或管理後臺顯示的輸出陳述。

默認的實現並不能很好的滿足需要,所以最好自定義這個方法,

get_absolute_url()

定義對象的URL.在管理界面中,任何時候都可以通過URL找到一個對象。

任何通過URL訪問的對象,都應該有唯一標識。

Overriding predefined model methods

還有一些model方法封裝了一些行為。當你想自定義數據庫行為,尤其是想改變save() 和 delete()方式的時候。

你可以自由地重寫這些方法來改變行為。

如果你想在保存對象的時候,覆蓋內置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.

需要特別註意的要記得調用父類的方法--super(Blog, self).save(args, *kwargs),以確保對象仍然被保存到數據庫中,如果你忘記調用父類的方法,默認的行為不會發生,數據庫也不會發生改變。

隨著時間的推移,django會增加或擴展一些新的方法,如果你使用args, *kwargs作為你的方法參數,就必須要保證能正確處理這些參數的增加。

覆蓋方法大多數不會用於批量操作

delete()並不一定是在調用一個QuerySet批量刪除時被觸發。為了確保自定義的刪除邏輯被執行,則需要使用 pre_delete and/or post_delete 信號。

不幸的是,還沒有一個好的方法用於批量的創建和更新,因為沒有save()、pre_save、post_save會被調用。

Executing custom SQL

另外一種常見的模式是在model方法和module-level方法中執行自定義SQL。

Model inheritance(Model繼承)

Model的繼承和普通的Python類繼承幾乎相同,但基類的繼承必須是django.db.models.Model.

在Django中有三種繼承風格:

1、如果你想用父類來保存每個子類共有的信息,並且這個類是不會被獨立使用的,那麽應該使用抽象基類。

2、如果你繼承現有model(甚至可能這個類是在另一個應用程序中),並希望每個model都擁有自己對應的數據庫,那就應該使用多表繼承。

3、如果你只是想修改model的Python-level行為,而不改變models fields,則使用代理模式。

Abstract base classes

當你想集中一些公共信息,可以使用虛類。你需要在model的元數據中設置 abstract=True ,這個model將不會被用於創建任何數據表。相反,當他被作為其他子類的基類時,他的字段將被添加到這些子類中。如果抽象類和他的子類有相同名稱的字段會產生一個異常。

例如:

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一樣使用,因為他是一個抽象基類。他不會產生一個數據表或者擁有一個管理器,也不能被實例化或直接調用。

在許多情況下這種類型的繼承正是你想要的,它提供了一種用來分解公共信息的方法,雖然只能實現數據表級別創建子模型。

Meta inheritance

當創建一個抽象基類的時候,Django允許在基類中聲明各種Meta屬性,如果子類沒有聲明自己的Meta元數據,他將繼承父類的。如果子類想擴展父類的Meta元數據,則可以繼承父類的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對於抽象基類的元數據調整只應該在安裝之前設置abstract=false。這意味著從抽象基類派生的子類不會自動轉型成抽象類本身。當然你也可以繼承來自別一個抽象基類。只需要記住abstract=True每次都應該明確設置。(這段沒怎麽看明白)

在抽象基類中某些屬性幾乎是沒有任何意義的,包括父類的元數據。例如使用db_table將意味著所有的子類(那些沒有指定自己的Meta)將使用同一數據庫,這肯定不會是你想要的。

Be careful with related_name(註意抽象基類中的反向關系名稱定義)

如果你在ForeignKey和ManyToManyField的屬性中使用related_name,你必須為字段指定一個唯一反向關系名稱。在抽象基類中會有一些問題,因為抽象基類的每個字段都會被包括在他的每一個子類中,包括related_name.

要解決這個問題,應該在抽象基類中使用related_name時,名稱中應包含‘%(app_label)s‘ 和 ‘%(class)s‘.

‘%(class)s‘:小寫的子類名稱
‘%(app_label)s‘:應用的小寫名稱(app)。因為每個已安裝的應用名稱都是唯一的,所以產生的名稱最終也會不同。

例如:首先定義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 字段的反向關系名稱是common_childa_related,而common.ChildB.m2m 的關系名稱ommon_childb_related。這是因為使用了‘%(app_label)s‘ 和 ‘%(class)s‘產生了不同的反向關系名稱。如果你定義了related_name,但忘記了使用‘%(app_label)s‘ 和 ‘%(class)s‘ Django會系統檢查或運行migrate時引發錯誤。

如果沒有在抽象基類的字段中定義related_name屬性,默認關系名稱將是子類名稱+"_set"。通常related_name會被直接在子類的字段屬性中被定義。例如上例中,如果related_name屬性被省略。common.ChildA.m2m 的反向關系名稱應該是childa_set,common.ChildB.m2m的反向關系名稱應該是childb_set。

Multi-table inheritance(多表繼承)

Django支持的第二種model繼承是多表繼承,在繼承結構中每個model都是獨立的。都對應著自己的數據庫表,可以進行獨立的查詢等操作。繼承關系實際是子model和每個父model之間的關系(通過自動創建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中使用,雖然數據存放在不同的數據表中。所以可以如下使用:

>>> Place.objects.filter(name="Bob‘s Cafe")
>>> Restaurant.objects.filter(name="Bob‘s Cafe")

如果一個Place對象存在相應的Restaurant對象,那麽就可以使用Place對象通過關系獲得Restaurant對象:

>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>

但如果place對象所對應的Restaurant對象不存在,則會引發 Restaurant.DoesNotExist 異常。

Meta and multi-table inheritance

在多表繼承的情況下繼承父類的Meta是沒有意義的。所有的Meta都已經被應用到父類,再應用這些Meta只會導致矛盾。

所以子model不能訪問到父model的Meta,然而也有少數的情況下,子model會從父model中繼承一些行為,例如子model沒有指定 ordering或 get_latest_by屬性,那麽就會從父model中繼承。

如果父model中有一個排序,但你不希望子model有任何的排序規劃,你可以明確的禁用:

class ChildModel(ParentModel):
    # ...
    class Meta:
        # Remove parent‘s ordering effect
        ordering = []

Inheritance and reverse relations(繼承與反向關系)

因為多表繼承實際是隱式的使用OneToOneField來鍵接父Model和子model,在這種關系有可能會使用父model來調用子model,比如上面的例子。但是如果你把ForeignKey和ManyToManyField關系應用到這樣一個繼承關系中,Django會返回一個驗證錯誤,必須要指定一個related_name字段屬性。

例如上面的例子,我們再創建一個子類,其中包含一個到父model的ManyToManyField關系字段:

class Supplier(Place):
    customers = models.ManyToManyField(Place)

這時會產生一個錯誤:

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會自動創建一個OneToOneField鏈接你的子model和任何非抽象父model。如果你想自定義子model鍵接回父model的屬性名稱,你可以創建自己的OneToOneField並設置parent_link=True,表示這個字段是對父model的回鏈。

Proxy models

當使用多表繼承時一個新的數據表model會在每一個子類中創建,這是因為子model需要存儲父mdoel不存在的一些數據字段。但有時只需要改變model的操作行為,可能是為了改變默認的管理行為或添加新的方法。

這時就應該使用代理模式的繼承:創建原始model的代理。你可以創建一個用於 create, delete 和 update的代理model,使用代理model的時候數據將會真實保存。這和使用原始model是一樣的,所不同的是當你改變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所對應的真實數據表。可通過MyPerson進行所有相應的操作:

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

你也可以使用代理模式來定義model的不同默認排序,例如:

class OrderedPerson(Person):
    class Meta:
        ordering = ["last_name"]
        proxy = True

這樣當使用原始model查詢時結果是無序的,而使用OrderedPerson進行查詢時將按last_name進行排序。

QuerySets still return the model that was requested(QuerySets的類型依然會是原始model類型)

當你通過MyPerson來查詢Person對象時,返回的QuerySet依然會是Person對象類型的集合。使用代理模式的model是依靠原始model的,是原始model的擴展。而不是用來替代父model。

Base class restrictions(基類限制)

代理model必須繼承一個非抽像model。

不能從多個非抽像model繼承,代理模式不能為不同model之間創建鏈接。

代理模式可以從任意沒有定義字段的抽象model繼承。

Proxy model managers

如果沒有指定代理model的管理器(managers),它將繼承父類的管理行為。如果你定義了代理model的管理器,它將會成為默認的,當然父類中定義的定義的任何管理器仍然是可以使用的。

繼續上面的例了,增加一個默認的管理器:

from django.db import models

class NewManager(models.Manager):
    # ...
    pass

class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

可以通過創建一個含有新的管理器並進行繼承,來增加一個新的管理器,而不需要去改變更有的默認管理器。

# 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中設置了manged的非托管模式model。但實際上這兩種方案是不太一樣,應該考慮在不同的情況下使用那一個:

兩者區別在於:你可以設置model的Meta.managed=False以及通過Meta.db_table指定數據表有創建非托管模式model,並對其添加各種方法,但如果你可保持非托管模式和真實數據表之間的同步,做任何更改都將是很麻煩的事。

而代理model主要用於管理model的各種行為或方法,他們將繼承父model的管理器等。

曾經嘗試將兩種模式合並,但由於API會變得非常復雜,並且難以理解,所以現在是分離成兩種模式:

一般的使用規劃是:

1、如果正使用現有的數據表,但不想在Django中鏡像所有的列,所以應該使用Meta.managed=False,通過這個選項使不在django控制下的數據表或視圖是可用的。

2、如果你想改變一個model的操作行為,但希望保持原始model不被改變,就應該使用Meta.proxy=True.

Multiple inheritance(多重繼承)

和Python的繼承方式一樣,django中的model也可以從多個父model繼承,當然也和Python的繼承方式 一樣,如果出現相同名字的時候只有第一個將被使用。例如:如果多個父model中都包含Meta類,將只有第一個將被使用,其他會被忽略。

通常情況下是不會用到多重繼承的。主是用於“混合式”model:增加一個特殊的額外字段或方式是由多個父model組合而來。應該盡量保持繼承層次的簡單,不然會很難排查某個信息是從那裏來的。

在django1.7以前,多個父model中有id主鍵字段時雖然不會引發錯誤,但有可能導致數據的丟失。例如像下面的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.‘

要正確的使用多重繼承,你應該使用一個明確的 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中創建別一個叫author的字段。

這種限制僅適用於字段(field),普通的python屬性是可以的。也有一種情況是可以覆蓋的:多表繼承的時候進行手動指定數據庫列名,可以出現子model和父model有同名的字段名稱,因為他們屬於不同的數據表。

如果你覆蓋了父model的字段屬性,django會拋出FieldError異常。

Django Model 數據表