Django4.0 模型-字段

2022-03-16 18:05 更新

模型中最重要且唯一必要的是數(shù)據(jù)庫(kù)的字段定義。字段在類屬性中定義。定義字段名時(shí)應(yīng)小心避免使用與 模型 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, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    release_date = models.DateField()
    num_stars = models.IntegerField()

字段類型

模型中每一個(gè)字段都應(yīng)該是某個(gè) ??Field?類的實(shí)例, Django 利用這些字段類來(lái)實(shí)現(xiàn)以下功能:

  • 字段類型用以指定數(shù)據(jù)庫(kù)數(shù)據(jù)類型(如:??INTEGER??, ??VARCHAR??, ??TEXT??)。
  • 在渲染表單字段時(shí)默認(rèn)使用的 HTML 視圖 (如: ??<input type="text">??, ??<select>??)。
  • 基本的有效性驗(yàn)證功能,用于 Django 后臺(tái)和自動(dòng)生成的表單。

Django 內(nèi)置了數(shù)十種字段類型,如果 Django 內(nèi)置類型不能滿足你的需求,你可以很輕松地編寫自定義的字段類型。

字段選項(xiàng)

每一種字段都需要指定一些特定的參數(shù)。 例如, ??CharField??(以及它的子類)需要接收一個(gè) ??max_length??參數(shù),用以指定數(shù)據(jù)庫(kù)存儲(chǔ) ??VARCHAR??數(shù)據(jù)時(shí)用的字節(jié)數(shù)。一些可選的參數(shù)是通用的,可以用于任何字段類型,下面介紹一部分經(jīng)常用到的通用參數(shù):

  • ???null???:如果設(shè)置為 ??True??,當(dāng)該字段為空時(shí),Django 會(huì)將數(shù)據(jù)庫(kù)中該字段設(shè)置為 ??NULL??。默認(rèn)為 ??False??。
  • ??blank??:如果設(shè)置為 ?True?,該字段允許為空。默認(rèn)為 ?False?。注意該選項(xiàng)與 ?null ?不同, ?null ?選項(xiàng)僅僅是數(shù)據(jù)庫(kù)層面的設(shè)置,而 ?blank ?是涉及表單驗(yàn)證方面。如果一個(gè)字段設(shè)置為?blank=True? ,在進(jìn)行表單驗(yàn)證時(shí),接收的數(shù)據(jù)該字段值允許為空,而設(shè)置為?blank=False? 時(shí),不允許為空。
  • ??choices??:一系列二元組,用作此字段的選項(xiàng)。如果提供了二元組,默認(rèn)表單小部件是一個(gè)選擇框,而不是標(biāo)準(zhǔn)文本字段,并將限制給出的選項(xiàng)。

??choices??中一個(gè)選項(xiàng)列表:

YEAR_IN_SCHOOL_CHOICES = [
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    ('GR', 'Graduate'),
]

注意:每當(dāng) ??choices??的順序變動(dòng)時(shí)將會(huì)創(chuàng)建新的遷移。

每個(gè)二元組的第一個(gè)值會(huì)儲(chǔ)存在數(shù)據(jù)庫(kù)中,而第二個(gè)值將只會(huì)用于在表單中顯示。對(duì)于一個(gè)模型實(shí)例,要獲取該字段二元組中相對(duì)應(yīng)的第二個(gè)值,使用 ??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'

你也可以使用枚舉類以簡(jiǎn)潔的方式來(lái)定義 ??choices??:

from django.db import models

class Runner(models.Model):
    MedalType = models.TextChoices('MedalType', 'GOLD SILVER BRONZE')
    name = models.CharField(max_length=60)
    medal = models.CharField(blank=True, choices=MedalType.choices, max_length=10)
  • ?default?:該字段的默認(rèn)值??梢允且粋€(gè)值或者是個(gè)可調(diào)用的對(duì)象,如果是個(gè)可調(diào)用對(duì)象,每次實(shí)例化模型時(shí)都會(huì)調(diào)用該對(duì)象。
  • ?help_text?:額外的“幫助”文本,隨表單控件一同顯示。即便你的字段未用于表單,它對(duì)于生成文檔也是很有用的。
  • ?primary_key?:如果設(shè)置為 ??True??,將該字段設(shè)置為該模型的主鍵。

在一個(gè)模型中,如果你沒(méi)有對(duì)任何一個(gè)字段設(shè)置 ??primary_key=True?? 選項(xiàng)。 Django 會(huì)自動(dòng)添加一個(gè) ??IntegerField??字段,并設(shè)置為主鍵,因此除非你想重寫 Django 默認(rèn)的主鍵設(shè)置行為,你可以不手動(dòng)設(shè)置主鍵。

主鍵字段是只可讀的,如果你修改一個(gè)模型實(shí)例的主鍵并保存,這等同于創(chuàng)建了一個(gè)新的模型實(shí)例。例如:

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)
<QuerySet ['Apple', 'Pear']>

??unique??:如果設(shè)置為 ??True??,這個(gè)字段的值必須在整個(gè)表中保持唯一。

自動(dòng)設(shè)置主鍵

默認(rèn)情況下,Django 給每個(gè)模型一個(gè)自動(dòng)遞增的主鍵,其類型在 ??AppConfig.default_auto_field?? 中指定,或者在 ??DEFAULT_AUTO_FIELD??配置中全局指定。例如:

id = models.BigAutoField(primary_key=True)

如果你想自己指定主鍵, 在你想要設(shè)置為主鍵的字段上設(shè)置參數(shù) ??primary_key=True??。如果 Django 看到你顯式地設(shè)置了 ??Field.primary_key??,將不會(huì)自動(dòng)在表(模型)中添加 ??id??列。每個(gè)模型都需要擁有一個(gè)設(shè)置了 ??primary_key=True?? 的字段(無(wú)論是顯式的設(shè)置還是 Django 自動(dòng)設(shè)置)。

在舊版本中,自動(dòng)創(chuàng)建的主鍵字段總是 ??AutoField??。

字段備注名

除了 ??ForeignKey??, ??ManyToManyField??和 ??OneToOneField??,任何字段類型都接收一個(gè)可選的位置參數(shù) ??verbose_name??,如果未指定該參數(shù)值, Django 會(huì)自動(dòng)使用字段的屬性名作為該參數(shù)值,并且把下劃線轉(zhuǎn)換為空格。

在該例中:備注名為??"?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??接收的第一個(gè)參數(shù)為模型的類名,后面可以添加一個(gè) ??verbose_name??參數(shù):

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",
)

慣例是不將 ??verbose_name??的首字母大寫,必要時(shí) Djanog 會(huì)自動(dòng)把首字母轉(zhuǎn)換為大寫。

關(guān)聯(lián)關(guān)系

顯然,關(guān)系型數(shù)據(jù)庫(kù)的強(qiáng)大之處在于各表之間的關(guān)聯(lián)關(guān)系。 Django 提供了定義三種最常見(jiàn)的數(shù)據(jù)庫(kù)關(guān)聯(lián)關(guān)系的方法:多對(duì)一,多對(duì)多,一對(duì)一。

多對(duì)一關(guān)聯(lián)

定義一個(gè)多對(duì)一的關(guān)聯(lián)關(guān)系,使用 ??django.db.models.ForeignKey?? 類。就和其它 ?Field?字段類型一樣,只需要在你模型中添加一個(gè)值為該類的屬性。??ForeignKey??類需要添加一個(gè)位置參數(shù),即你想要關(guān)聯(lián)的模型類名。例如,如果一個(gè) ??Car??模型有一個(gè)制造者 ??Manufacturer??--就是說(shuō)一個(gè) ??Manufacturer??制造許多輛車,但是每輛車都僅有一個(gè)制造者-- 那么使用下面的方法定義這個(gè)關(guān)系:

from django.db import models

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

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

你也可以創(chuàng)建 自關(guān)聯(lián)關(guān)系 (一個(gè)模型與它本身有多對(duì)一的關(guān)系)和 與未定義的模型間的關(guān)聯(lián)關(guān)系 。建議設(shè)置 ??ForeignKey??字段名(上例中的 ??manufacturer??)為想要關(guān)聯(lián)的模型名,但是你也可以隨意設(shè)置為你想要的名稱,例如:

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

多對(duì)多關(guān)聯(lián)

定義一個(gè)多對(duì)多的關(guān)聯(lián)關(guān)系,使用 ??django.db.models.ManyToManyField?類。就和其他 ??Field??字段類型一樣,只需要在你模型中添加一個(gè)值為該類的屬性。??ManyToManyField??類需要添加一個(gè)位置參數(shù),即你想要關(guān)聯(lián)的模型類名。例如:如果 ??Pizza??含有多種 ??Topping??(配料) -- 也就是一種 ??Topping??可能存在于多個(gè) ??Pizza??中,并且每個(gè) ??Pizza??含有多種 ??Topping??--那么可以這樣表示這種關(guān)系:

from django.db import models

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

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

和 ??ForeignKey??類一樣,你也可以創(chuàng)建 自關(guān)聯(lián)關(guān)系 (一個(gè)對(duì)象與他本身有著多對(duì)多的關(guān)系)和 與未定義的模型的關(guān)系 。建議設(shè)置 ??ManyToManyField??字段名(上例中的 ??toppings??)為一個(gè)復(fù)數(shù)名詞,表示所要關(guān)聯(lián)的模型對(duì)象的集合。對(duì)于多對(duì)多關(guān)聯(lián)關(guān)系的兩個(gè)模型,可以在任何一個(gè)模型中添加 ??ManyToManyField??字段,但只能選擇一個(gè)模型設(shè)置該字段,即不能同時(shí)在兩模型中添加該字段。一般來(lái)講,應(yīng)該把 ??ManyToManyField??實(shí)例放到需要在表單中被編輯的對(duì)象中。在之前的例子中, ??toppings??被放在 ??Pizza??當(dāng)中(而不是 ??Topping??中有指向 ??pizzas??的 ??ManyToManyField??實(shí)例 )因?yàn)橄噍^于配料被放在不同的披薩當(dāng)中,披薩當(dāng)中有很多種配料更加符合常理。按照先前說(shuō)的,在編輯 ??Pizza??的表單時(shí)用戶可以選擇多種配料。

在多對(duì)多(many-to-many)關(guān)系中添加添加額外的屬性字段

如果你只是想要一個(gè)類似于記錄披薩和配料之間混合和搭配的多對(duì)多關(guān)系,標(biāo)準(zhǔn)的 ??ManyToManyField??就足夠你用了。但是,有時(shí)你可能需要將數(shù)據(jù)與兩個(gè)模型之間的關(guān)系相關(guān)聯(lián)。舉例來(lái)講,考慮一個(gè)需要跟蹤音樂(lè)人屬于哪個(gè)音樂(lè)組的應(yīng)用程序。在人和他們所在的組之間有一個(gè)多對(duì)多關(guān)系,你可以使用 ??ManyToManyField??來(lái)代表這個(gè)關(guān)系。然而,你想要記錄更多的信息在這樣的關(guān)聯(lián)關(guān)系當(dāng)中,比如你想要記錄某人是何時(shí)加入一個(gè)組的。對(duì)于這些情況,Django 允許你指定用于控制多對(duì)多關(guān)系的模型。你可以在中間模型當(dāng)中添加額外的字段。在實(shí)例化 ??ManyToManyField??的時(shí)候使用 ??through??參數(shù)指定多對(duì)多關(guān)系使用哪個(gè)中間模型。對(duì)于我們舉的音樂(lè)家的例子,代碼如下:

from django.db import models

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

    def __str__(self):
        return self.name

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

    def __str__(self):
        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)

你需要在設(shè)置中間模型的時(shí)候,顯式地為多對(duì)多關(guān)系中涉及的中間模型指定外鍵。這種顯式聲明定義了這兩個(gè)模型之間是如何關(guān)聯(lián)的。

在中間模型當(dāng)中有一些限制條件:

  • 你的中間模型要么有且僅有一個(gè)指向源模型(我們例子當(dāng)中的 ??Group??)的外鍵,要么你必須通過(guò) ??ManyToManyField.through_fields?? 參數(shù)在多個(gè)外鍵當(dāng)中手動(dòng)選擇一個(gè)外鍵,如果有多個(gè)外健且沒(méi)有用 ??through_fields?參數(shù)選擇一個(gè)的話,會(huì)出現(xiàn)驗(yàn)證錯(cuò)誤。對(duì)于指向目標(biāo)模型(我們例子當(dāng)中的 ??Person??)的外鍵也有同樣的限制。
  • 在一個(gè)用于描述模型當(dāng)中自己指向自己的多對(duì)多關(guān)系的中間模型當(dāng)中,可以有兩個(gè)指向同一個(gè)模型的外健,但這兩個(gè)外健分表代表多對(duì)多關(guān)系(不同)的兩端。如果外健的個(gè)數(shù) 超過(guò) 兩個(gè),你必須和上面一樣指定 ??through_fields??參數(shù),要不然會(huì)出現(xiàn)驗(yàn)證錯(cuò)誤。

現(xiàn)在你已經(jīng)通過(guò)中間模型完成你的 ??ManyToManyField??(例子中的 ??Membership??),可以開(kāi)始創(chuàng)建一些多對(duì)多關(guān)系了。你通過(guò)實(shí)例化中間模型來(lái)創(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()
<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()?? 創(chuàng)建關(guān)系,只要你為任何必需的字段指定 ??through_defaults??

>>> beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.create(name="George Harrison", through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})

你可能更傾向直接創(chuàng)建中間模型。如果自定義中間模型沒(méi)有強(qiáng)制? ?(model1, model2)?? 對(duì)的唯一性,調(diào)用 ??remove()?? 方法會(huì)刪除所有中間模型的實(shí)例:

>>> 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 deletes both of the intermediate model instances for Ringo Starr
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>

方法 ??clear()?? 用于實(shí)例的所有多對(duì)多關(guān)系:

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

一旦你建立了自定義多對(duì)多關(guān)聯(lián)關(guān)系,就可以執(zhí)行查詢操作。和一般的多對(duì)多關(guān)聯(lián)關(guān)系一樣,你可以使用多對(duì)多關(guān)聯(lián)模型的屬性來(lái)查詢:

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

當(dāng)你使用中間模型的時(shí)候,你也可以查詢他的屬性:

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

如果你想訪問(wèn)一個(gè)關(guān)系的信息時(shí)你可以直接查詢 ??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.'

另一種訪問(wèn)同樣信息的方法是通過(guò) ??Person?對(duì)象來(lái)查詢多對(duì)多遞歸關(guān)聯(lián)關(guān)系 :

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

一對(duì)一關(guān)聯(lián)

使用 ??OneToOneField??來(lái)定義一對(duì)一關(guān)系。就像使用其他類型的 ??Field??一樣:在模型屬性中包含它。當(dāng)一個(gè)對(duì)象以某種方式“繼承”另一個(gè)對(duì)象時(shí),這對(duì)該對(duì)象的主鍵非常有用。??OneToOneField??需要一個(gè)位置參數(shù):與模型相關(guān)的類。例如,當(dāng)你要建立一個(gè)有關(guān)“位置”信息的數(shù)據(jù)庫(kù)時(shí),你可能會(huì)包含通常的地址,電話等字段。接著,如果你想接著建立一個(gè)關(guān)于關(guān)于餐廳的數(shù)據(jù)庫(kù),除了將位置數(shù)據(jù)庫(kù)當(dāng)中的字段復(fù)制到 ??Restaurant??模型,你也可以將一個(gè)指向 ??Place OneToOneField?? 放到 ??Restaurant??當(dāng)中(因?yàn)椴蛷d“是一個(gè)”地點(diǎn));事實(shí)上,在處理這樣的情況時(shí)最好使用 模型繼承 ,它隱含的包括了一個(gè)一對(duì)一關(guān)系。和 ??ForeignKey??一樣,可以創(chuàng)建 自關(guān)聯(lián)關(guān)系 也可以創(chuàng)建 與尚未定義的模型的關(guān)系 。

??OneToOneField??字段還接受一個(gè)可選的 ??parent_link??參數(shù)。??OneToOneField?? 類通常自動(dòng)的成為模型的主鍵,這條規(guī)則現(xiàn)在不再使用了(然而你可以手動(dòng)指定 ??primary_key??參數(shù))。因此,現(xiàn)在可以在單個(gè)模型當(dāng)中指定多個(gè) ??OneToOneField??字段。

跨文件模型

關(guān)聯(lián)另一個(gè)應(yīng)用中的模型是當(dāng)然可以的。為了實(shí)現(xiàn)這一點(diǎn),在定義模型的文件開(kāi)頭導(dǎo)入需要被關(guān)聯(lián)的模型。接著就可以在其他有需要的模型類當(dāng)中關(guān)聯(lián)它了。比如:

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

字段命名限制

Django 對(duì)模型的字段名有一些限制:

1、一個(gè)字段的名稱不能是 Python 保留字,因?yàn)檫@會(huì)導(dǎo)致 Python 語(yǔ)法錯(cuò)誤。比如:

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

2、一個(gè)字段名稱不能包含連續(xù)的多個(gè)下劃線,原因在于 Django 查詢語(yǔ)法的工作方式。比如:

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

3、字段名不能以下劃線結(jié)尾,原因同上。

但是,這些限制是可以被解決的,因?yàn)樽侄蚊麤](méi)要求和數(shù)據(jù)庫(kù)列名一樣。

SQL保留字,例如 ??join??, ??where??或 ??select??, 是 可以被用在模型字段名當(dāng)中的,因?yàn)?Django 在對(duì)底層的 SQL 查詢當(dāng)中清洗了所有的數(shù)據(jù)庫(kù)表名和字段名,通過(guò)使用特定數(shù)據(jù)庫(kù)引擎的引用語(yǔ)法。

自定義的字段類型

如果已經(jīng)存在的模型字段不能滿足你的需求,或者你希望支持一些不太常見(jiàn)的數(shù)據(jù)庫(kù)列類型,你可以創(chuàng)建自己的字段類。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)