Django 1.10 中文文檔------3.2.1 模型Models

歡迎你們訪問個人我的網站《劉江的博客和教程》:www.liujiangblog.com

主要分享Python 及Django教程以及相關的博客


3.2.1 models模型

一般一個模型映射一張單獨的數據表。
基本概念:python

  • 每一個model都是django.db.models.Model的子類
  • model的每一個屬性表明數據表的某一列
  • Django將自動爲你生成數據庫訪問API

3.2.1.1 快速展現:

下面的模型定義了一個「人」,它具備first_name和last_name屬性git

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

上面的代碼,至關於下面的原生sql語句:sql

CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

注意:數據庫

  • 表名myapp_person由Django自動生成:項目名稱+下劃線+小寫類名。你能夠重寫這部分功能的機制。
  • Django自動建立id自增主鍵,固然,你也能夠本身指定主鍵,
  • 上面的sql語句基於PostgreSQL 語法,可能與你的實際狀況有差異。

3.2.1.2 使用模型

建立了model以後,在使用它以前,你須要先在settings文件中的INSTALLED_APPS 處,註冊models.py文件所在的APP。例如:django

INSTALLED_APPS = [
#...
'myapp',
#...
]

當你每次在INSTALLED_APPS處增長新的APP時,請務必執行命令python manage.py migrate。有可能要先make migtrations。app

3.2.1.3 Fields字段

model中最重要也是必須定義的部分。請不要使用clean、save、delete等model API內置的名字,防止命名衝突。
範例:ide

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, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    release_date = models.DateField()
    num_stars = models.IntegerField()
Fields types字段類型

fileds類型的做用:工具

  • 決定數據庫中對應列的數據類型
  • HTML中對應的form field類型,例如<input type=「text」 />
  • 在admin後臺和自動生成的form表單中最小的數據驗證需求網站

    3.22節《Making queries》
    6.15節《model field reference》《Field API reference》
    4.3節《Writing custom model fields.》ui

AutoField
一個自動增長的整數類型。一般你不須要使用它,Django自動幫你添加下面的字段:
id = models.AutoField(primary_key=True)

BigAutoField
(1.10新增)
64位整數,相似AutoField。1 to 9223372036854775807

BigIntegerField
64位整數,相似IntegerField ,-9223372036854775808 to 9223372036854775807。
在默認的form類型是textinput(用django的form功能自動生成的input標籤)。

BinaryField
二進制數據類型。使用受限,慎用。

BooleanField
布爾值類型。默認值是None。
默認的form類型是input checkbox。
若是要接收null值,請使用NullBooleanField。

CharField
字符串類型。必須接收一個max_length參數。
默認的form類型是input text。
最經常使用的filed!

CommaSeparatedIntegerField
逗號分隔的整數類型。必須接收一個max_length參數。
經常使用於表示較大的金額數目,例如1,000,000元。

DateField
class DateField(auto_now=False, auto_now_add=False, **options)
日期類型。
一個Python中的datetime.date的實例。
在form中的默認類型是text。在admin後臺,Django會幫你自動添加一個js的日曆表和一個「Today」快捷方式,以及附加的日期合法性驗證。
參數:(全部參數互斥,不能共存)
auto_now:每當對象被保存時將字段設爲當前日期,經常使用於保存最後修改時間。
注意,只有在使用save()方法時才更新,其它操做不更新。
auto_now_add:每當對象被建立時,設爲當前日期,經常使用於保存建立日期。
注意,它是不可修改的。

設置上面兩個參數就至關於給field添加了editable=False and blank=True屬性。若是想具備修改屬性,請用default參數:
對於 DateField: default=date.today - from datetime.date.today()
對於 DateTimeField: default=timezone.now - from django.utils.timezone.now()

DateTimeField
class DateTimeField(auto_now=False, auto_now_add=False, **options)
日期時間類型。
Python的datetime.datetime的實例。與DateField相比就是多了小時、分和秒的顯示,其它功能、參數、用法、默認值等等都同樣。

DecimalField
class DecimalField(max_digits=None, decimal_places=None, **options)
固定精度的十進制小數。
至關於Python的Decimal實例,必須提供兩個指定的參數!
參數:
max_digits:最大的位數,必須大於或等於小數點位數
decimal_places:小數點位數,精度。
範例:儲存最大不超過999,帶有2位小數位精度的數,定義以下:
models.DecimalField(..., max_digits=5, decimal_places=2)
5來自3+2!

當 localize=False,它在form中默認爲NumberInput 類型。不然,是text類型。

DurationField
持續時間類型
存儲必定期間的時間長度。相似python中的timedelta。在不一樣的數據庫實現中有不一樣的表示方法。經常使用於進行時間之間的加減運算。可是當心了,這裏有坑,PostgreSQL等數據庫之間有兼容性問題!

EmailField
class EmailField(max_length=254, **options)
郵箱類型。
使用EmailValidator進行合法性驗證。

FileField
class FileField(upload_to=None, max_length=100, **options)
上傳文件類型

filed類型表
自定義filed類型

Field options選項

model field reference

字段選項

詳細看6.15節《field option》
除了相似max_length是對CharFiled的必須參數外。還有一些是可選的經常使用參數:

  • null:True時,Django在數據庫用NULL保存空值。默認False。
  • blank:true時,字段能夠爲空。默認false。和null不一樣的是,null是純數據庫層面的,而blank是驗證相關的,它與表單驗證是否容許輸入框內爲空有關,於數據庫無關。因此要當心一個null爲false,blank爲true的字段接收到一個空值可能會出bug或異常。
  • choices:用於選擇框,須要先提供一個二維的二元元組,第一個元素表示存在數據庫內真實的值,第二個表示頁面上顯示的具體內容。例如:

    YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    ('GR', 'Graduate'),
    )
    要顯示一個choices的值,可使用get_FOO_display()方法,其中的FOO用字段名代替。

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
'L'
>>> p.get_shirt_size_display()
'Large'
  • default:字段的默認值,能夠是值或者一個回調對象。每次建立新對象時該回調都會被執行。注意:在某種緣由不明的狀況下將default設置爲NONE,可能會引起intergyerror:not null constraint failed,即非空約束失敗異常,致使python manage.py migrate失敗,此時可將None改成False或其它的值,只要不是None就行。
  • help_text:額外的幫助文本顯示在表單部件上。
  • primary_key:主鍵。設置爲True時,當前字段變爲主鍵,並關閉Django自動生成的id主鍵功能。另外,主鍵字段不可修改,若是你賦個新值則會建立個新記錄。

    from django.db import models
    class Fruit(models.Model):
    name = models.CharField(max_length=100, primary_key=True)

    fruit = Fruit.objects.create(name='Apple')
    fruit.name = 'Pear'
    fruit.save()
    Fruit.objects.values_list('name', flat=True)
    ['Apple', 'Pear']

unique:true時,在整個表內該字段的數據不可重複。

自動主鍵字段

默認狀況下,Django給你提供了自動的主鍵:
id = models.AutoField(primary_key=True)

字段詳細名稱

除了外鍵、多對多和一對一字段外,全部的字段均可以有一個可選的第一位置參數:verbose name。若是沒給這個參數,Django會利用字段的屬性名自動建立它,並將下劃線轉換爲空格。
下面這個例子verbose name是"person’s first name":
first_name = models.CharField("person's first name", max_length=30)
下面這個例子verbose name是"first name":
first_name = models.CharField(max_length=30)

對於外鍵、多對多和一對一字字段,因爲第一個參數須要用來指定模型類,所以必須用關鍵字參數verbose_name來指定詳細名。以下:

poll = models.ForeignKey(
    Poll,
    on_delete=models.CASCADE,
    verbose_name="the related poll",
    )
sites = models.ManyToManyField(Site, verbose_name="list of sites")
    place = models.OneToOneField(
    Place,
    on_delete=models.CASCADE,
    verbose_name="related place",
)
關係relationships
  1. 多對一:也就是外鍵,使用django.db.models.ForeignKey。須要第一位置參數 爲相關聯的模型類。例如:
from django.db import models
class Manufacturer(models.Model):
    # ...
    pass
    
class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    # ...

外鍵關係字段放在多的一方,好比上面的多種汽車出自同一廠商。
也能夠建立遞歸關係(本身多對一關聯本身,使用self做爲指向的模型名),或者關聯到還沒有建立的模型。
建議將外鍵字段名設置爲關聯類的小寫名稱。非強制。
更多ForeignKey相關,請查看6.15節《the model field reference》

  1. 多對多:ManyToManyField。須要一個位置參數,指向關聯的模型。例如:
from django.db import models

class Topping(models.Model):
    # ...
    pass
    
class Pizza(models.Model):
    # ...
    toppings = models.ManyToManyField(Topping)

同一對一同樣,也能夠創建遞歸的本身關聯本身的多對對,或關聯到一個還沒有定義的模型。
建議將字段名取成關聯模型的小寫複數,例如toppings。
多對多字段放在關聯雙方的任何一方均可以,可是隻能在一方,不能同時。但一般會考慮現實的邏輯,將其放在更符合基本狀況的一方。
更多ManyToManyField相關,請查看6.15節《the model field reference》

  1. 多對多的額外字段
    通常狀況,普通的多對多已經夠用,無需本身建立第三張關係表。可是某些狀況可能更復雜一點,好比有音樂家表和音樂家分組表,這是個多對多的關係,可是若是你想保存某個音樂家加入某個音樂家分組的時間呢?
    Ddjango提供了一個through參數,用於指定中間模型,你能夠將相似加入時間等其餘字段放在這個中間模型內。例子以下:
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, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

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

  • 中間模型只能包含一個指向源模型的外鍵關係,上面例子中,也就是在Membership中只能有person和group外鍵關係各一個,不能多。不然,你必須顯式的經過ManyToManyField.through_fields指定關聯的對象。參考下面的例子:
from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=50)

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(
    Person,
    through='Membership',
    through_fields=('group', 'person'),
    )

class Membership(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    inviter = models.ForeignKey(
    Person,
    on_delete=models.CASCADE,
    related_name="membership_invites",
    )
    invite_reason = models.CharField(max_length=64)
  • 對於一個經過中間模型關聯自身的多對多,兩個一樣的外鍵關係是容許的,但此時他們被看作不一樣的兩邊。可是,若是多於2個,同樣要如同上面顯式的經過ManyToManyField.through_fields指定關聯的對象。
  • 當你經過中間模型建立一個關聯自身的多對多,你必須顯式的指出symmetrical=False。這樣,Django纔會關閉默認的對稱特性。具體參考6.15

下面是一些使用例子:

>>> 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()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<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()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>

與普通的多對多不同,使用中間模型的多對多不能使用add(), create(),remove(), 和set()方法來建立、刪除關係,看下面:

>>> # 無效
>>> beatles.members.add(john)
>>> # 無效
>>> beatles.members.create(name="George Harrison")
>>> # 無效
>>> beatles.members.set([john, paul, ringo, george])

爲何?由於上面的方法沒法提供加入時間、邀請緣由等中間模型須要的字段內容。惟一的辦法只能是經過建立中間模型的實例來建立這種類型的多對多關聯。
可是clear()方法是有效的,它能清空全部的多對多關係。

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

一旦你經過建立中間模型實例的方法創建了多對多的關聯,你馬上就能夠像普通的多對多那樣進行查詢操做:

# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith='Paul')
<QuerySet [<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))
<QuerySet [<Person: Ringo Starr]>

能夠像普通模型同樣使用中間模型:

>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
  1. 一對一關係:OneToOneField,一樣須要關聯模型做爲第一位置參數。

舉例,你設計一個「場所」的模型,包括地址、電話等等。而後,你又想建立一個基於「場所」的餐館模型,你不須要重複上面的場所的字段,只須要在餐館模型中創建一個OneToOneField關聯到「場所」模型。(事實上,一般咱們會使用繼承的方法,它隱含了一個一對一關係)。
一樣,一對一也能夠遞歸關聯本身,或關聯未定義模型。
一對一還有一個可選的parent_link參數。

其它模塊的Models

直接在文件頂部導入其它模塊內的模型,而後正常使用!

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

class Restaurant(models.Model):
    # ...
    zip_code = models.ForeignKey(
        ZipCode,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
field name的命名限制
  • 不能是Python的保留關鍵字
  • 不能包含2個及以上的下劃線。由於2個下劃線在Django查詢語法中有特殊做用。

可是SQL的保留字,如join、where和select是能夠用的。

自定義字段類型

參考4.3《Writing custom model fields》

3.2.1.4 meta 選項

方法:在模型內部建立class Meta

from django.db import models

class Ox(models.Model):
    horn_length = models.IntegerField()
    
    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

metadata:指的是「任何非字段相關的內容」,例如排序依據(ordering),表名(db_table),或者人類可讀的單數、複數名((verbose_name and verbose_name_plural)。全部的都是可選,非必須的。完整的參考表見6.15.6《model option reference》

3.2.1.5 模型屬性

objects:對於一個模型,最重要的屬性是Manager(管理器)。它是模型用來查詢、操做數據的接口。若是沒有自定義Manager,那麼它的默認名字就是「objects」。它只能經過類名進行訪問,不能經過類的實例訪問。

3.2.1.6 模型方法

下面是在模型中自定義方法的實例:

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) # 將方法轉換爲屬性

下面是2個經常使用的常常被定義的方法:

str():python3版本,在打印模型時,指定顯示的內容。
get_absolute_url():Django用它來獲取對象的URL,每個包含URL的對象都必須定義這個方法。參考6.15.

重寫內置的模型方法

例如,你想在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()
        # 調用內置的save()方法
        super(Blog, self).save(*args, **kwargs) 
        do_something_else()

或者阻止某些人沒法進行save()。

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
        else:
            # 其它的正常保存
            super(Blog, self).save(*args, **kwargs)

注意三點:

  1. 必定要調用父類方法super(Blog, self).save(*args,
    **kwargs)保證正常的工做
  2. 必定要用*args,**kwargs的傳參方式,保證不管如何,參數都被正確的傳遞給父類方法。
  3. 在進行批量處理時,自定義方法可能無論用。

3.2.1.7 模型的繼承

全部的模型類必須繼承django.db.models.Model。
Django有三種繼承的方式:

  • 抽象類:被用來繼承的類,Abstract base classes,將共同的數據抽離出來,供子類繼承重用,它不會建立表
  • 多表類:Multi-table inheritance,每個模型都有本身的數據庫表。
  • 代理模型:Proxy models
繼承抽象基類:

值須要在Meta類裏添加abstract=True,就能夠將一個模型轉換爲抽象基類。Django不會爲這種類建立實際的數據庫表,他們也沒有管理器,不能被實例化也沒法直接保存,它們就是用來被繼承的。例如:

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三個字段。基類和子類不能有同樣的字段名。

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'

抽象基類也能夠繼承別的抽象基類,但不要忘記在Meta類裏添加abstract=True。這個選項纔是決定一個類是普通的類仍是抽象基類的根本。

區別related_name和related_query_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",
    related_query_name="%(app_label)s_%(class)ss",
    )
    
    class Meta:
        abstract = True
        
class ChildA(Base):
    pass

class ChildB(Base):
    pass

對於另一個模塊rare/models.py:

from common.models import Base

class ChildB(Base):
    pass

對於上面的繼承關係:

  • common.ChildA.m2m字段的reverse name(反向關係名)應該是common_childa_related;reverse query name(反向查詢名)應該是common_childas。
  • common.ChildB.m2m字段的reverse name(反向關係名)應該是common_childb_related;reverse query name(反向查詢名)應該是common_childbs。
  • rare.ChildB.m2m字段的reverse name(反向關係名)應該是rare_childb_related;reverse query name(反向查詢名)應該是rare_childbs。

具體時候什麼名字,取決你如何經過‘%(class)s’‘ and ’%(app_label)s構造名稱字符串。可是,若是你忘了這個技術細節,那麼在使用時會彈出異常。

multi-table inheritance多表繼承

這種繼承方式,父類和子類都有本身的數據庫表,內部隱含了一個一對一的關係。例如:

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)

Restaurant類將包含Place類的字段,而且各有各的數據庫表和字段,好比:

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

若是一個Place同時也是一個Restaurant,你可使用小寫的子類名,在父類中訪問它,例如:

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

可是,若是這個Place是個純粹的Place,並非一個Restaurant,那麼上面的調用方式會報錯Restaurant.DoesNotExist。

Meta和多表繼承

在多表繼承的狀況下,因爲父類和子類都在數據庫內有物理存在的表,父類的Meta類會對子類形成很差的影響,所以,Django在這種狀況下關閉了子類繼承父類的Meta功能。這一點和抽象基類的繼承方式有所不一樣。
可是,還有2個meta元素特殊一點,那就是ordering和get_latest_by,這兩個參數是會被繼承的。所以,若是在多表繼承中,你不想讓你的子類繼承父類的上面兩種參數,就必須在子類中顯示的指出或重寫。以下:

class ChildModel(ParentModel):
    # ...
    
class Meta:
    # 移除父類對子類的排序影響
    ordering = []

繼承和反向關聯

由於多表繼承使用了一個隱含的OneToOneField來連接子類與父類,因此象上例那樣,你能夠用父類來指代子類。可是這個OnetoOneField字段默認的related_name值與ForeignKey和 ManyToManyField默認的反向名稱相同。若是你與該父類的另外一個子類作多對一或是多對多關係,你就必須在每一個多對一和多對多字段上強制指定related_name。若是你沒這麼作,Django就會在你運行或驗證(validation)時拋出異常。

仍以上面Place類爲例,咱們建立一個帶有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')。

指定連接父類的字段

以前提到,Django會自動建立一個OneToOneField字段將子類連接至非抽象的父model 。若是你想指定連接父類的屬性名稱,你能夠建立你本身的OneToOneField字段並設置 parent_link=True,從而使用該字段連接父類。

Proxy models代理模型

使用多表繼承時,父類的每一個子類都會建立一張新數據表,一般狀況下,這正是咱們想要的操做。這是由於子類須要一個空間來存儲不包含在基類中的字段數據。但有時,你可能只想更改model在Python層的行爲。好比:更改默認的manager,或是添加一個新方法。

代理模型能夠實現這一點:爲原始模型建立一個代理 。你能夠建立,刪除,更新代理model 的實例,並且全部的數據均可以像使用原始model同樣被保存。不一樣之處在於:你能夠在代理model中改變默認的排序設置和默認的manager,而不會對原始model產生影響。

聲明代理模型和聲明普通模型沒有什麼不一樣。設置Meta類中proxy的值爲True,就完成了對代理模型的聲明。
舉例,假設你想給Person模型添加一個方法。你能夠這樣作:

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類同樣的數據庫表。而且,任何新的Person實例均可以經過MyPerson類進行訪問,反之亦然。

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

下面是經過代理實現排序,但原父類不排序的方法:

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

如今,普通的Person查詢是無序的,而OrderedPerson查詢會按照last_name排序。

查詢集始終返回請求的模型
也就是說,沒有辦法讓django在查詢Person對象時返回MyPerson對象。Person對象的查詢集會返回相同類型的對象。代理對象的要點是:它會使用依賴於原生Person的代碼,而你可使用你添加進來的擴展對象(它不會依賴其它任何代碼)。而並非將Person模型(或者其它)在全部地方替換爲其它你本身建立的模型。

基類的限制

  • 代理模型必須繼承自一個非抽象基類。而且不能繼承自多個非抽象基類,這是由於一個代理模型不能鏈接不一樣的數據表。
  • 代理模型能夠同時繼承任意多個抽象基類,前提是這些抽象基類沒有定義任何模型字段。
  • 代理模型能夠同時繼承多個別的代理模型,前提是這些代理模型繼承同一個非抽象基類。(早期Django版本不支持這一條)

代理模型管理器
如不指定,則繼承父類的管理器。若是你本身定義了管理器,那它就會成爲默認管理器,可是父類的管理器依然有效。以下例子:

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

代理繼承與非託管模型之間的差別

代理繼承看上去和使用Meta類中的managed屬性的非託管模型很是類似。
在建立非託管模型時要謹慎設置Meta.db_table,這是由於建立的非託管模型映射某個已存在的模型,而且有本身的方法。若是你要保證這兩個模型同步並對程序進行改動,那麼就會變得繁冗而脆弱。
通常規則是:

  • 若是你要借鑑一個已有的模型或數據表,且不想涉及全部的原始數據表的列,那就使用 Meta.managed=False。一般狀況下,對模型數據庫建立視圖和表格不須要由Django控制時,就使用這個選項。
  • 若是你想對模型作Python層級的改動,又想保留字段不變,那就令Meta.proxy=True。所以在數據保存時,代理模型至關於徹底複製了原始模型的存儲結構。
多重繼承

注意,多重繼承和多表繼承是兩碼事,兩個概念。

Django的模型體系支持多重繼承,就像Python同樣。同時,通常的Python名稱解析規則也會適用。出現特定名稱的第一個基類(好比Meta)是所使用的那個,這意味着若是多個父類都含有 Meta類,只有第一個父類的會被使用,剩下的會忽略掉。

一般,你不須要使用多重繼承。最經常使用的狀況是「混入」(mix-in):爲每個繼承了minx-in的類添加一個特別額外的字段或方法。
可是,儘可能讓你的繼承關係簡單和直接,避免沒必要要的混亂和複雜。

請注意,繼承同時含有相同id主鍵域的類將拋出異常。爲了解決這個問題,你能夠在基類模型中顯式的使用AutoField字段。以下例子所示:

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
重寫字段

在python中,子類能夠重寫全部的父類屬性。但在Django中卻不必定。若是一個非抽象模型基類有一個叫作author的字段,那麼在它的子類中,你不能建立任何也叫作author的模型字段或屬性(這個限制對抽象模型無效)。這些字段有可能被另外的字段或值重寫,或經過設置filed_name=None而被移除。

警告:模型管理器繼承自抽象基類。重寫一個被繼承管理器引用的繼承字段可能致使小bug。參考3.2《Custom managers and model inheritance》

注意:一些字段會在模型上定義一些額外的屬性,例如ForeighKey會定義一個額外的屬性(將"_id"附加在字段名上)。就像related_name和related_query_name同樣。這些額外的屬性不可被重寫,除非定義他們的字段被改變或移除了。

重寫父類的字段會致使不少麻煩,好比:初始化實例(指定在Model.__init__中被實例化的字段)和序列化。而普通的Python類繼承機制並不能處理好這些特性。因此Django的繼承機制被設計成與Python有所不一樣,這樣作並非隨意而爲的。

這些限制僅僅針對作爲屬性使用的Field實例,並非針對Python屬性,Python屬性還是能夠被重寫的。 在 ython看來,上面的限制僅僅針對字段實例的名稱:若是你手動指定了數據庫的列名稱,那麼在多重繼承中,你就能夠在子類和某個祖先類當中使用同一個列名稱。(由於它們使用的是兩個不一樣數據表的字段)。

若是你在任何一個祖先類中重寫了某個 model 字段,Django 都會拋出 FieldError異常。

3.2.1.8 在包中組織模型

manage.py startapp命令的執行會建立一個app的文件結構,幷包含一個models.py文件。可是,若是你有不少模型,那麼將它們分隔放在不一樣的文件中會是個好主意。

想這麼作,只須要建立一個模型包。首先,移除models.py文件,再創建一個myapp/models/目錄,在目錄裏建立個__init__.py文件,最後建立存放模型的文件。你必須在__init__.py中導入那些模型models。
例如,若是你有一個organic.py和synthetic.py文件在models目錄裏,那麼,init.py文件裏應該這麼寫代碼:
myapp/models/init.py

from .organic import Person
from .synthetic import Robot

在文件中顯式的導入每個模型比使用from .models import * 這種一股腦的導入方式更好,這樣不會致使命名空間的混亂,讓代碼更可讀,更利於代碼分析工具進行檢查。

更多請查看6.15節的Models,這裏包含了全部的API和細節,很長很長的文檔.....

相關文章
相關標籤/搜索