Django Model 定義語法

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對字段名只有兩個限制:

  1. 字段名不能是Python的保留關(guān)鍵字,不然會導(dǎo)致Python語法錯誤。例如:

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

  2. 字段名在一行中不能包含一個以上的下劃線,這和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異常。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內(nèi)容