1. 程式人生 > >一 Django模型層簡介

一 Django模型層簡介

關系 自己 fresh 級聯 影響 clas sha .text 不能

模型

django提供了一個強大的orm(關系映射模型)系統。

模型包含了你要在數據庫中創建的字段信息及對數據表的一些操作

使用模型

定義好模型後,要告訴django使用這些模型,你要做的就是在配置文件中的INSTALLED_APPS中添加模型所在的應用名稱

字段類型

模型中的每個字段都是Field類相應的實例,django根據Field類型來確定以下信息:

  • 列類型,告知數據庫要存儲那種數據
  • 渲染表單時使用的默認HTML widget
  • 驗證,被用在admin和表單中

通用字段參數(常用)

null:如果為True,Django將在數據庫中將該字段存儲為NULL(如果該字段為空),默認False

blank:如果為True,該字段允許為空值,默認False

註意,null是數據庫範疇,blank是表單驗證範疇

choices:如果設置了該選項,在渲染HTML時,將會是一個下拉選擇框。該選項是一個二元組構成的可叠代對象,選擇框中的值就是二元組內的值

如:

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

每個元組中的第一個元素是將被存儲在數據庫中值,第二個元素由窗體小部件顯示

給定一個模型,可以使用get_FOO_display()來訪問字段在窗體中的顯示值

如:

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值是個可調用的對象時,賦值一個對象的引用和調用該對象的區別

help_text:表單部件額外的顯示內容,對生成文檔也很有用

primary_key:如果為true,該字段就是模型的主鍵,如果沒有指定該選項,會默認生成一個IntergerField的自增ID字段作為主鍵字段

註意:主鍵字段時只讀的,如果在一個已經存在的對象上面更改主鍵的值並保存,一個新的對象將會被創建

unique:如果為true,則該字段的值必須唯一

字段別名

除ForeignKey ManyToManyField OneToOneField之外,每個字段都接受一個可選的位置參數(第一個參數),若沒有提供該參數,將根據字段名稱,將字段名稱下劃線替換成空格作為別名

ForeignKey ManyToManyField OneToOneField 第一個參數是關聯的模型類,使用關鍵字參數verbose_name指定別名

關系

多對一:如一個汽車廠生產多種汽車,一輛汽車只有一個生產廠家

代碼如下:

from django.db import models

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

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

多對多:一個披薩上可以放多種配料,一種配料可以放在多個披薩上

代碼如下:

from django.db import models

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

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

一般來說ManyToManyField應該放在要在表單中被編輯的對象(如在admin中該字段會被渲染成多選框),如本例:一個披薩選擇多種配料

多對多關系的額外字段:throuth

例如:這樣一個應用,它記錄音樂家所屬的音樂小組。 我們可以用一個ManyToManyField 表示小組和成員之間的多對多關系。 但是,有時你可能想知道更多成員關系的細節,比如成員是何時加入小組的。

對於這些情況,Django 允許你指定一個中介模型來定義多對多關系。 你可以將其他字段放在中介模型裏面。 源模型的ManyToManyField 字段將使用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)

應用實例如下:

>>> 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()set()創建關系:

>>> # 下列語句都是無法工作的
>>> beatles.members.add(john)
>>> beatles.members.create(name="George Harrison")
>>> beatles.members.set([john, paul, ringo, george])

為什麽不能這樣做? 這是因為你不能只創建 PersonGroup之間的關聯關系,你還要指定 Membership模型中所需要的所有信息; 而簡單的addcreate 和賦值語句是做不到這一點的。 所以它們不能在使用中介模型的多對多關系中使用。 此時,唯一的辦法就是創建中介模型的實例。

remove方法被禁用也是出於同樣的原因。 例如,如果通過中介模型定義的表沒有在源模型(Group)和目標模型(perseon)上強制執行唯一性,則remove()調用將不能提供足夠的信息,說明應該刪除哪個中介模型實例:

>>> Membership.objects.create(person=ringo, group=beatles,
...     date_joined=date(1968, 9, 4),
...     invite_reason="You‘ve been gone for a month and we miss you.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This will not work because it cannot tell which membership to remove
>>> beatles.members.remove(ringo)

但是clear() 方法卻是可用的。它可以清空某個實例所有的多對多關系:

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

通過中介模型建立的m2m關系和普通m2m關系,在查詢方面是相似的:

>>> 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]>

如果你需要訪問一個成員的信息,你可以直接獲取Membership模型:

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

另一種獲取相同信息的方法是,在Person對象上反向查詢:

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

一對一:和其他關系一樣,當某個對象擴展自另一個對象時,最常用的方式就是在這個對象的主鍵上添加一對一關系

模型屬性

objects模型最重要的屬性是Manager它是Django 模型進行數據庫查詢操作的接口,並用於從數據庫提取實例。 默認的名稱為objectsManager只能通過模型類訪問,而不能通過模型實例訪問。

模型方法

可以在模型上定義自定義方法來給對象添加自定義底層功能。 Manager 方法用於“表範圍”的事務,模型的方法應該著眼於特定的模型實例。這是一個非常有價值的技術,讓業務邏輯位於同一個地方 — 模型中。

例如,下面的模型具有一些自定義的方法:

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"

覆蓋預定義的模型方法

models.Model中封裝了對數據庫的各種操作,特別是,你將要經常改變save()delete() 的工作方式

覆蓋內建模型方法的一個典型的使用場景是,你想在保存一個對象時做一些其它事情:

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

註意:批量操作中被覆蓋的模型方法不會被調用

使用QuerySet批量刪除對象或由於級聯刪除時,對象的delete()方法不一定被調用。 為確保自定義的刪除邏輯得到執行,你可以使用pre_delete和/或post_delete信號。

不幸的是,當批量creatingupdating 對象時沒有變通方法,因為不會調用save()pre_savepost_save

模型繼承

在Django 中有3種風格的繼承:

  1. 通常,你只想使用父類來持有一些信息,你不想在每個子模型中都敲一遍。 這個父類永遠不會單獨使用,所以你要使用抽象的基類
  2. 如果你繼承一個已經存在的模型且想讓每個模型具有它自己的數據庫表,那麽應該使用多表繼承
  3. 最後,如果你只是想改變一個模塊Python 級別的行為,而不用修改模型的字段,你可以使用代理模型

抽象基類

需要在父類中編寫一個Meta類,設置abstract=True,要註意,如果父類和子類有相同的字段名,會出現錯誤(ps:難道不是重寫嗎?為毛會報錯)

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

註意:abstract屬性不會被繼承

多表繼承

django會在子類中自動創建一個 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中也是有效的,只不過沒有保存在數據庫中的Restaurant表中。 所以下面兩個語句都是可以運行的:

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

Meta和多表繼承

在多表繼承中,子類繼承父類的 Meta類是沒什麽意義的。 所有的 Meta選項已經對父類起了作用,再次使用只會起反作用(這與使用抽象基類的情況正好相反,因為抽象基類並沒有屬於它自己的內容)。

代理模型

有時你可能只想更改 model 在 Python 層的行為實現。比如:更改默認的 manager ,或是添加一個新方法,而這,正是代理繼承要做的:為原始模型創建一個代理你可以創建,刪除,更新代理 model 的實例,而且所有的數據都可以像使用原始 model 一樣被保存。 不同之處在於:你可以在代理 model 中改變默認的排序設置和默認的 manager ,更不會對原始 model 產生影響。聲明代理 model 和聲明普通 model 沒有什麽不同。 設置Meta類中 proxy的值為 True,就完成了對代理 model 的聲明。

舉個例子,假設你想給 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")

代理模型管理器:如果你沒有在代理模型中定義管理器,代理模型會繼承基類管理器,如果代理模型中定義了管理器,它就會變成默認管理器,不過在父類定義的管理器仍然有效

如果你想在代理模型中添加新的管理器,並非替換基類管理器,可以這樣,創建一個含有新管理器的基類,作為代理模型的基類放在後面:

# 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

模型繼承中隱藏的規則

普通的python類允許子類覆蓋父類的任何屬性,在Django中,模型字段不允許這樣做,如果非抽象基類有一個A字段,那麽不能在任何繼承自該基類的類中創建A字段。對於抽象基類沒有這個限制,抽象基類的子類中會被覆蓋,也可以通過設置field_name=None 來刪除字段

在包中組織模型

如果有多個模型文件,可以使用一個名為models的python包來替換原有的models.py文件,但是必須將模型文件導入到所在包的__init__.py文件中:

#myapp/models/__init__.py

from .organic import Person
from .synthetic import Robot

更多細節可參照django內置模塊,如django.db.models

一 Django模型層簡介