模型實例參考¶
本文件描述了 Model
API 的詳細資訊。它建立在 模型 和 資料庫查詢 指南中介紹的內容之上,因此您可能需要在閱讀本文之前先閱讀並理解這些文件。
在本參考資料中,我們將使用 資料庫查詢指南 中介紹的範例部落格模型。
建立物件¶
要建立模型的新實例,請像其他 Python 類別一樣實例化它。
關鍵字引數是您在模型上定義的欄位名稱。請注意,實例化模型不會觸及您的資料庫;為此,您需要使用 save()
。
注意
您可能會想透過覆寫 __init__
方法來自訂模型。但是,如果您這樣做,請注意不要變更呼叫簽名,因為任何變更都可能會阻止儲存模型實例。此外,在 __init__
中參照模型欄位可能會在某些情況下導致無限遞迴錯誤。不要覆寫 __init__
,請嘗試使用下列方法之一:
在模型類別上新增類別方法
from django.db import models class Book(models.Model): title = models.CharField(max_length=100) @classmethod def create(cls, title): book = cls(title=title) # do something with the book return book book = Book.create("Pride and Prejudice")
在自訂管理員上新增方法 (通常偏好)
class BookManager(models.Manager): def create_book(self, title): book = self.create(title=title) # do something with the book return book class Book(models.Model): title = models.CharField(max_length=100) objects = BookManager() book = Book.objects.create_book("Pride and Prejudice")
自訂模型載入¶
當從資料庫載入時,可以使用 from_db()
方法來自訂模型實例的建立。
db
引數包含載入模型的資料庫別名,field_names
包含所有已載入欄位的名稱,而 values
則包含 field_names
中每個欄位的已載入值。field_names
的順序與 values
的順序相同。如果存在所有模型的欄位,則保證 values
的順序是 __init__()
預期的順序。也就是說,可以透過 cls(*values)
建立實例。如果有任何欄位被延遲,它們將不會出現在 field_names
中。在這種情況下,請為每個遺失的欄位指派值 django.db.models.DEFERRED
。
除了建立新的模型之外,from_db()
方法還必須在新實例的 _state
屬性中設定 adding
和 db
旗標。
以下範例說明如何記錄從資料庫載入的欄位的初始值。
from django.db.models import DEFERRED
@classmethod
def from_db(cls, db, field_names, values):
# Default implementation of from_db() (subject to change and could
# be replaced with super()).
if len(values) != len(cls._meta.concrete_fields):
values = list(values)
values.reverse()
values = [
values.pop() if f.attname in field_names else DEFERRED
for f in cls._meta.concrete_fields
]
instance = cls(*values)
instance._state.adding = False
instance._state.db = db
# customization to store the original field values on the instance
instance._loaded_values = dict(
zip(field_names, (value for value in values if value is not DEFERRED))
)
return instance
def save(self, **kwargs):
# Check how the current values differ from ._loaded_values. For example,
# prevent changing the creator_id of the model. (This example doesn't
# support cases where 'creator_id' is deferred).
if not self._state.adding and (
self.creator_id != self._loaded_values["creator_id"]
):
raise ValueError("Updating the value of creator isn't allowed")
super().save(**kwargs)
上面的範例顯示了完整的 from_db()
實作,以釐清其完成方式。在這種情況下,可以在 from_db()
方法中使用 super()
呼叫。
從資料庫重新整理物件¶
如果您從模型實例中刪除一個欄位,再次存取它會從資料庫重新載入該值。
>>> obj = MyModel.objects.first()
>>> del obj.field
>>> obj.field # Loads the field from the database
- Model.arefresh_from_db(using=None, fields=None, from_queryset=None)¶
非同步版本:arefresh_from_db()
如果您需要從資料庫重新載入模型的值,可以使用 refresh_from_db()
方法。當呼叫此方法時不帶引數時,會執行以下操作:
模型的所有非延遲欄位都會更新為目前資料庫中的值。
所有快取的關聯都會從重新載入的實例中清除。
只有模型的欄位會從資料庫重新載入。其他與資料庫相關的值 (例如註釋) 不會重新載入。任何 @cached_property
屬性也不會清除。
重新載入發生在載入實例的資料庫,如果實例不是從資料庫載入,則從預設資料庫重新載入。可以使用 using
引數來強制用於重新載入的資料庫。
可以使用 fields
引數來強制載入的欄位集。
例如,若要測試 update()
呼叫是否產生預期的更新,您可以撰寫類似於以下的測試:
def test_update_result(self):
obj = MyModel.objects.create(val=1)
MyModel.objects.filter(pk=obj.pk).update(val=F("val") + 1)
# At this point obj.val is still 1, but the value in the database
# was updated to 2. The object's updated value needs to be reloaded
# from the database.
obj.refresh_from_db()
self.assertEqual(obj.val, 2)
請注意,當存取延遲欄位時,延遲欄位的值的載入會透過此方法進行。因此,可以自訂延遲載入的方式。以下範例說明當重新載入延遲欄位時,如何重新載入實例的所有欄位。
class ExampleModel(models.Model):
def refresh_from_db(self, using=None, fields=None, **kwargs):
# fields contains the name of the deferred field to be
# loaded.
if fields is not None:
fields = set(fields)
deferred_fields = self.get_deferred_fields()
# If any deferred field is going to be loaded
if fields.intersection(deferred_fields):
# then load all of them
fields = fields.union(deferred_fields)
super().refresh_from_db(using, fields, **kwargs)
from_queryset
引數允許使用與從 _base_manager
建立的查詢集不同的查詢集。它讓您可以更精確地控制模型的重新載入方式。例如,當您的模型使用軟刪除時,您可以讓 refresh_from_db()
考慮到這一點。
obj.refresh_from_db(from_queryset=MyModel.active_objects.all())
您可以快取相關的物件,否則這些物件會從重新載入的實例中清除。
obj.refresh_from_db(from_queryset=MyModel.objects.select_related("related_field"))
您可以在重新載入模型的值之前,鎖定列直到交易結束。
obj.refresh_from_db(from_queryset=MyModel.objects.select_for_update())
新增了 from_queryset
引數。
一個輔助方法,傳回一個集合,其中包含此模型目前延遲的所有欄位的屬性名稱。
驗證物件¶
驗證模型涉及四個步驟
驗證模型欄位 -
Model.clean_fields()
將整個模型做驗證 -
Model.clean()
驗證欄位的唯一性 -
Model.validate_unique()
驗證約束 -
Model.validate_constraints()
當您呼叫模型的 full_clean()
方法時,會執行所有這四個步驟。
當您使用 ModelForm
時,呼叫 is_valid()
將會對表單中包含的所有欄位執行這些驗證步驟。請參閱 ModelForm 文件 以取得更多資訊。如果您計劃自行處理驗證錯誤,或您已從 ModelForm
中排除需要驗證的欄位,則您只需要呼叫模型的 full_clean()
方法。
此方法會依序呼叫 Model.clean_fields()
、Model.clean()
、Model.validate_unique()
(如果 validate_unique
為 True
) 和 Model.validate_constraints()
(如果 validate_constraints
為 True
),並引發一個 ValidationError
,其中具有一個 message_dict
屬性,其中包含來自所有四個階段的錯誤。
可選的 exclude
引數可用於提供一組 set
欄位名稱,這些欄位名稱可以排除在驗證和清理之外。ModelForm
使用此引數來排除表單上不存在的欄位,以免驗證,因為任何引發的錯誤都無法由使用者更正。
請注意,當您呼叫模型的 save()
方法時,不會自動呼叫 full_clean()
。當您想要為自己手動建立的模型執行一步模型驗證時,您需要手動呼叫它。例如:
from django.core.exceptions import ValidationError
try:
article.full_clean()
except ValidationError as e:
# Do something based on the errors contained in e.message_dict.
# Display them to a user, or handle them programmatically.
pass
full_clean()
執行的第一個步驟是清理每個個別欄位。
此方法會驗證模型上的所有欄位。可選的 exclude
引數可讓您提供一組 set
欄位名稱,以排除在驗證之外。如果任何欄位未通過驗證,它將引發 ValidationError
。
full_clean()
執行的第二個步驟是呼叫 Model.clean()
。應該覆寫此方法,以對模型執行自訂驗證。
此方法應使用於提供自訂模型驗證,並在需要時修改模型上的屬性。例如,您可以使用它來自動為欄位提供值,或執行需要存取多個欄位的驗證。
import datetime
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
class Article(models.Model):
...
def clean(self):
# Don't allow draft entries to have a pub_date.
if self.status == "draft" and self.pub_date is not None:
raise ValidationError(_("Draft entries may not have a publication date."))
# Set the pub_date for published items if it hasn't been set already.
if self.status == "published" and self.pub_date is None:
self.pub_date = datetime.date.today()
但是,請注意,與 Model.full_clean()
類似,當您呼叫模型的 save()
方法時,不會呼叫模型的 clean()
方法。
在上面的範例中,由 Model.clean()
引發的 ValidationError
例外是用字串實例化的,因此它將儲存在一個特殊的錯誤字典索引鍵 NON_FIELD_ERRORS
中。此索引鍵用於繫結到整個模型而不是特定欄位的錯誤。
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
try:
article.full_clean()
except ValidationError as e:
non_field_errors = e.message_dict[NON_FIELD_ERRORS]
若要將例外指派給特定欄位,請使用字典實例化 ValidationError
,其中索引鍵為欄位名稱。我們可以更新先前的範例,以將錯誤指派給 pub_date
欄位。
class Article(models.Model):
...
def clean(self):
# Don't allow draft entries to have a pub_date.
if self.status == "draft" and self.pub_date is not None:
raise ValidationError(
{"pub_date": _("Draft entries may not have a publication date.")}
)
...
如果您在 Model.clean()
期間偵測到多個欄位中的錯誤,您也可以傳遞一個將欄位名稱對應到錯誤的字典。
raise ValidationError(
{
"title": ValidationError(_("Missing title."), code="required"),
"pub_date": ValidationError(_("Invalid date."), code="invalid"),
}
)
然後,full_clean()
將檢查模型上的唯一約束。
如果欄位未出現在 ModelForm
中,如何引發特定於欄位的驗證錯誤
您無法在 Model.clean()
中針對未出現在模型表單中的欄位引發驗證錯誤 (表單可能會使用 Meta.fields
或 Meta.exclude
限制其欄位)。這樣做將會引發 ValueError
,因為驗證錯誤將無法與排除的欄位建立關聯。
若要解決此困境,請改為覆寫 Model.clean_fields()
,因為它會收到從驗證中排除的欄位清單。例如:
class Article(models.Model):
...
def clean_fields(self, exclude=None):
super().clean_fields(exclude=exclude)
if self.status == "draft" and self.pub_date is not None:
if exclude and "status" in exclude:
raise ValidationError(
_("Draft entries may not have a publication date.")
)
else:
raise ValidationError(
{
"status": _(
"Set status to draft if there is not a publication date."
),
}
)
此方法與 clean_fields()
類似,但會驗證透過 Field.unique
、Field.unique_for_date
、Field.unique_for_month
、Field.unique_for_year
或模型上的 Meta.unique_together
定義的唯一性約束,而不是個別欄位值。可選的 exclude
引數可讓您提供一組 set
欄位名稱,以排除在驗證之外。如果任何欄位未通過驗證,它將引發 ValidationError
。
在 UniqueConstraint
中定義的 Meta.constraints
會由 Model.validate_constraints()
進行驗證。
請注意,如果您向 validate_unique()
提供 exclude
參數,則任何涉及您提供的欄位的 unique_together
約束條件將不會被檢查。
最後,full_clean()
將檢查模型上的任何其他約束條件。
此方法會驗證在 Meta.constraints
中定義的所有約束條件。可選的 exclude
參數允許您提供一個欄位名稱的 set
,將其排除在驗證之外。如果任何約束條件驗證失敗,它將引發 ValidationError
。
儲存物件¶
要將物件儲存回資料庫,請呼叫 save()
- Model.save(*, force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)[原始碼]¶
- Model.asave(*, force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)¶
非同步版本: asave()
有關使用 force_insert
和 force_update
參數的詳細資訊,請參閱 強制執行 INSERT 或 UPDATE。有關 update_fields
參數的詳細資訊,請參閱 指定要儲存的欄位 章節。
如果您想要自訂儲存行為,您可以覆寫此 save()
方法。有關更多詳細資訊,請參閱 覆寫預定義的模型方法。
模型儲存過程也有一些微妙之處;請參閱下面的章節。
自 5.1 版本起已棄用: 對位置參數的支援已被棄用。
自動遞增主鍵¶
如果模型具有 AutoField
— 自動遞增主鍵 — 則該自動遞增的值將在您第一次呼叫 save()
時計算並作為物件的屬性儲存。
>>> b2 = Blog(name="Cheddar Talk", tagline="Thoughts on cheese.")
>>> b2.id # Returns None, because b2 doesn't have an ID yet.
>>> b2.save()
>>> b2.id # Returns the ID of your new object.
在您呼叫 save()
之前,無法知道 ID 的值,因為該值是由您的資料庫計算,而不是由 Django 計算。
為了方便起見,每個模型預設都有一個名為 id
的 AutoField
,除非您在模型中的欄位上明確指定 primary_key=True
。有關更多詳細資訊,請參閱 AutoField
的文件。
pk
屬性¶
- Model.pk¶
無論您是自己定義主鍵欄位,還是讓 Django 為您提供主鍵欄位,每個模型都會有一個名為 pk
的屬性。它的行為就像模型上的普通屬性,但實際上是模型主鍵欄位的別名。您可以像對待任何其他屬性一樣讀取和設定此值,它將更新模型中的正確欄位。
明確指定自動主鍵值¶
如果模型具有 AutoField
,但您希望在儲存時明確定義新物件的 ID,則在儲存之前明確定義它,而不是依賴 ID 的自動分配。
>>> b3 = Blog(id=3, name="Cheddar Talk", tagline="Thoughts on cheese.")
>>> b3.id # Returns 3.
>>> b3.save()
>>> b3.id # Returns 3.
如果您手動分配自動主鍵值,請確保不要使用已存在的主鍵值!如果您建立具有資料庫中已存在的主鍵值的新物件,Django 會假設您正在變更現有記錄,而不是建立新記錄。
考慮到上面的 'Cheddar Talk'
網誌範例,此範例將覆寫資料庫中的先前記錄。
b4 = Blog(id=3, name="Not Cheddar", tagline="Anything but cheese.")
b4.save() # Overrides the previous blog with ID=3!
有關發生這種情況的原因,請參閱下面的 Django 如何知道要 UPDATE 與 INSERT。
明確指定自動主鍵值主要用於大量儲存物件,當您確信不會發生主鍵衝突時。
如果您使用 PostgreSQL,則可能需要更新與主鍵關聯的序列;請參閱 手動指定自動遞增主鍵的值。
當您儲存時會發生什麼?¶
當您儲存物件時,Django 會執行以下步驟:
發出 pre-save 訊號。發送
pre_save
訊號,允許任何監聽該訊號的函式執行某些操作。預處理資料。呼叫每個欄位的
pre_save()
方法,以執行任何所需的自動資料修改。例如,日期/時間欄位會覆寫pre_save()
以實作auto_now_add
和auto_now
。準備資料以供資料庫使用。要求每個欄位的
get_db_prep_save()
方法以資料庫可寫入的資料類型提供其目前值。大多數欄位不需要資料準備。簡單的資料類型(例如整數和字串)作為 Python 物件「已準備好寫入」。但是,更複雜的資料類型通常需要進行一些修改。
例如,
DateField
欄位使用 Python 的datetime
物件來儲存資料。資料庫不會儲存datetime
物件,因此欄位值必須轉換成符合 ISO 標準的日期字串,才能插入資料庫。將資料插入資料庫。 預處理過的、準備好的資料會被組合成 SQL 陳述式,以便插入資料庫。
發出儲存後訊號。 會發送
post_save
訊號,允許任何監聽該訊號的函式執行某些操作。
Django 如何判斷要執行 UPDATE 還是 INSERT¶
您可能已經注意到 Django 資料庫物件使用相同的 save()
方法來建立和變更物件。Django 將使用 INSERT
或 UPDATE
SQL 陳述式的需求抽象化了。具體來說,當您呼叫 save()
並且物件的主鍵屬性未定義 default
或 db_default
時,Django 會遵循此演算法
如果物件的主鍵屬性被設定為評估結果為
True
的值(也就是說,不是None
或空字串的值),Django 會執行UPDATE
。如果物件的主鍵屬性未設定,或者如果
UPDATE
沒有更新任何內容(例如,如果主鍵被設定為資料庫中不存在的值),Django 會執行INSERT
。
如果物件的主鍵屬性定義了 default
或 db_default
,那麼如果它是現有的模型實例並且主鍵被設定為資料庫中存在的值,Django 會執行 UPDATE
。否則,Django 會執行 INSERT
。
這裡需要注意的一點是,當儲存新物件時,如果無法保證主鍵值未被使用,則應小心不要明確指定主鍵值。有關此細微差別的更多資訊,請參閱上方「明確指定自動主鍵值」和下方「強制執行 INSERT 或 UPDATE」。
在 Django 1.5 和更早版本中,當設定主鍵屬性時,Django 會執行 SELECT
。如果 SELECT
找到一列,則 Django 會執行 UPDATE
,否則會執行 INSERT
。舊的演算法在 UPDATE
情況下會多執行一個查詢。在極少數情況下,即使資料庫包含該物件主鍵值的列,資料庫也不會回報已更新列。例如,PostgreSQL 的 ON UPDATE
觸發器會傳回 NULL
。在這種情況下,可以將 select_on_save
選項設定為 True
,以恢復舊的演算法。
新增了 Field.db_default
參數。
強制執行 INSERT 或 UPDATE¶
在某些極少數情況下,有必要能夠強制 save()
方法執行 SQL INSERT
,而不是退回到執行 UPDATE
。反之亦然:如果可能,執行更新,但不插入新列。在這些情況下,您可以將 force_insert=True
或 force_update=True
參數傳遞給 save()
方法。同時傳遞兩個參數是錯誤的:您不能同時執行插入和更新!
當使用多表繼承時,也可以為 force_insert
提供父類別的元組,以便強制為每個基類執行 INSERT
陳述式。例如
Restaurant(pk=1, name="Bob's Cafe").save(force_insert=(Place,))
Restaurant(pk=1, name="Bob's Cafe", rating=4).save(force_insert=(Place, Rating))
您可以傳遞 force_insert=(models.Model,)
以強制為所有父類別執行 INSERT
陳述式。預設情況下,force_insert=True
僅強制為目前模型插入新列。
您很少需要使用這些參數。Django 幾乎總是會做正確的事情,而嘗試覆寫它會導致難以追蹤的錯誤。此功能僅供進階使用。
使用 update_fields
會強制執行更新,類似於 force_update
。
新增了對將父類別的元組傳遞給 force_insert
的支援。
根據現有欄位更新屬性¶
有時,您需要在欄位上執行簡單的算術運算,例如遞增或遞減目前值。實現此目的的一種方法是在 Python 中執行算術運算,如下所示:
>>> product = Product.objects.get(name="Venezuelan Beaver Cheese")
>>> product.number_sold += 1
>>> product.save()
如果從資料庫檢索的舊 number_sold
值為 10,則值 11 將寫回資料庫。
透過將更新表示為相對於原始欄位值,而不是明確指定新值,可以使該過程更加穩健,避免競爭條件,並略微加快速度。Django 提供了 F 運算式
來執行這種相對更新。使用 F 運算式
,前面的範例表示為
>>> from django.db.models import F
>>> product = Product.objects.get(name="Venezuelan Beaver Cheese")
>>> product.number_sold = F("number_sold") + 1
>>> product.save()
指定要儲存的欄位¶
如果在關鍵字引數 update_fields
中傳遞欄位名稱清單給 save()
,則只會更新該清單中命名的欄位。如果您只想更新物件的一個或幾個欄位,這可能是理想的選擇。防止所有模型欄位在資料庫中被更新會帶來輕微的效能提升。例如
product.name = "Name changed again"
product.save(update_fields=["name"])
update_fields
引數可以是任何包含字串的可迭代物件。空的 update_fields
可迭代物件將會跳過儲存。值 None
將會對所有欄位執行更新。
指定 update_fields
將強制執行更新。
當儲存透過延遲模型載入擷取的模型(only()
或 defer()
)時,只會更新從資料庫載入的欄位。實際上,在這種情況下會有自動的 update_fields
。如果您指定或變更任何延遲欄位值,該欄位將會新增到已更新的欄位中。
Field.pre_save()
和 update_fields
如果傳遞了 update_fields
,則只會呼叫 update_fields
的 pre_save()
方法。例如,這表示除非包含在 update_fields
中,否則 auto_now=True
的日期/時間欄位將不會更新。
刪除物件¶
- Model.adelete(using=DEFAULT_DB_ALIAS, keep_parents=False)¶
非同步版本: adelete()
對物件發出 SQL DELETE
指令。這只會刪除資料庫中的物件;Python 實例仍然存在,並且其欄位中仍會保留資料,除了主鍵會被設定為 None
。此方法會回傳刪除的物件數量以及一個字典,其中包含每個物件類型刪除的數量。
如需更多詳細資訊,包括如何批量刪除物件,請參閱 刪除物件。
如果您想要自訂刪除行為,可以覆寫 delete()
方法。請參閱 覆寫預定義的模型方法 以了解更多詳細資訊。
有時使用 多表格繼承 時,您可能只想刪除子模型的資料。指定 keep_parents=True
將會保留父模型的資料。
封存物件¶
當您 pickle
模型時,其目前的狀態會被封存。當您解封存它時,它會包含封存時的模型實例,而不是資料庫中當前的資料。
其他模型實例方法¶
一些物件方法有特殊用途。
__str__()
¶
當您在物件上呼叫 str()
時,會呼叫 __str__()
方法。Django 在許多地方使用 str(obj)
。最值得注意的是,在 Django 管理站點中顯示物件,以及在顯示物件時作為插入到模板中的值。因此,您應該始終從 __str__()
方法回傳模型的一個良好、人類可讀的表示。
例如
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
def __str__(self):
return f"{self.first_name} {self.last_name}"
__eq__()
¶
相等方法的定義是,具有相同主鍵值和相同具體類別的實例被認為是相等的,除了主鍵值為 None
的實例與除了它們本身之外的任何事物都不相等。對於代理模型,具體類別定義為模型的第一個非代理父類別;對於所有其他模型,它只是模型的類別。
例如
from django.db import models
class MyModel(models.Model):
id = models.AutoField(primary_key=True)
class MyProxyModel(MyModel):
class Meta:
proxy = True
class MultitableInherited(MyModel):
pass
# Primary keys compared
MyModel(id=1) == MyModel(id=1)
MyModel(id=1) != MyModel(id=2)
# Primary keys are None
MyModel(id=None) != MyModel(id=None)
# Same instance
instance = MyModel(id=None)
instance == instance
# Proxy model
MyModel(id=1) == MyProxyModel(id=1)
# Multi-table inheritance
MyModel(id=1) != MultitableInherited(id=1)
__hash__()
¶
__hash__()
方法基於實例的主鍵值。它實際上是 hash(obj.pk)
。如果實例沒有主鍵值,則會引發 TypeError
(否則,__hash__()
方法會在實例儲存前後回傳不同的值,但更改實例的 __hash__()
值在 Python 中是被禁止的)。
get_absolute_url()
¶
- Model.get_absolute_url()¶
定義一個 get_absolute_url()
方法來告訴 Django 如何計算物件的規範 URL。對於呼叫者,此方法應顯示為回傳一個可用於透過 HTTP 參照物件的字串。
例如
def get_absolute_url(self):
return "/people/%i/" % self.id
雖然此程式碼正確且簡單,但它可能不是編寫此類方法最可移植的方式。reverse()
函式通常是最佳方法。
例如
def get_absolute_url(self):
from django.urls import reverse
return reverse("people-detail", kwargs={"pk": self.pk})
Django 使用 get_absolute_url()
的一個地方是在管理應用程式中。如果物件定義了此方法,則物件編輯頁面將會有一個「在網站上檢視」連結,該連結會將您直接跳轉到物件的公開檢視,如 get_absolute_url()
所提供。
同樣地,Django 的其他一些部分,例如 聯合訂閱框架,在定義 get_absolute_url()
時也會使用它。如果您的模型實例都各自擁有唯一 URL 是有意義的,則應該定義 get_absolute_url()
。
警告
您應該避免從未驗證的使用者輸入建構 URL,以減少連結或重新導向中毒的可能性
def get_absolute_url(self):
return "/%s/" % self.name
如果 self.name
是 '/example.com'
,這會回傳 '//example.com/'
,而這反過來又是一個有效的綱要相對 URL,但不是預期的 '/%2Fexample.com/'
。
在模板中使用 get_absolute_url()
,而不是硬式編碼您的物件 URL 是一種良好的做法。例如,此模板程式碼是不好的
<!-- BAD template code. Avoid! -->
<a href="/people/{{ object.id }}/">{{ object.name }}</a>
此模板程式碼更好
<a href="{{ object.get_absolute_url }}">{{ object.name }}</a>
這裡的邏輯是,如果您變更物件的 URL 結構,即使是像更正拼寫錯誤這樣的小變更,您也不想追蹤每個可能建立 URL 的位置。在 get_absolute_url()
中指定一次,並讓您的所有其他程式碼呼叫該位置。
注意
您從 get_absolute_url()
回傳的字串必須僅包含 ASCII 字元(URI 規範所要求,RFC 3986 第 2 節)並且在必要時進行 URL 編碼。
呼叫 get_absolute_url()
的程式碼和模板應該能夠直接使用結果,而無需任何進一步處理。如果您使用包含 ASCII 範圍之外的字元的字串,您可能會希望使用 django.utils.encoding.iri_to_uri()
函式來協助處理此問題。
額外的實例方法¶
除了 save()
、delete()
之外,模型物件可能還具有以下一些方法
- Model.get_FOO_display()¶
對於每個設定了 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=2, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
- Model.get_next_by_FOO(**kwargs)¶
- Model.get_previous_by_FOO(**kwargs)¶
對於每個沒有設定 null=True
的 DateField
和 DateTimeField
,物件將會有 get_next_by_FOO()
和 get_previous_by_FOO()
方法,其中 FOO
是欄位的名稱。這會返回相對於日期欄位的下一個和上一個物件,並在適當的時候引發 DoesNotExist
例外。
這兩個方法都會使用模型的預設管理器執行查詢。如果您需要模擬自訂管理器使用的篩選,或是想要執行一次性的自訂篩選,這兩個方法也接受選用的關鍵字參數,這些參數應該採用 欄位查找 中描述的格式。
請注意,在日期值相同的情況下,這些方法會使用主鍵作為平手打破器。這保證不會跳過或重複任何記錄。這也意味著您不能在未儲存的物件上使用這些方法。
覆寫額外的實例方法
在大多數情況下,覆寫或繼承 get_FOO_display()
、get_next_by_FOO()
和 get_previous_by_FOO()
應該會如預期般運作。然而,由於它們是由元類別添加的,因此要考慮所有可能的繼承結構是不切實際的。在更複雜的情況下,您應該覆寫 Field.contribute_to_class()
來設定您需要的方法。
其他屬性¶
_state
¶
- Model._state¶
_state
屬性指的是一個ModelState
物件,該物件追蹤模型實例的生命週期。ModelState
物件有兩個屬性:adding
,一個旗標,如果模型尚未儲存到資料庫,則為True
;以及db
,一個字串,指的是實例從哪個資料庫別名載入或儲存。新建立的實例具有
adding=True
和db=None
,因為它們尚未儲存。從QuerySet
取得的實例將具有adding=False
且db
設定為相關資料庫的別名。