遷移操作¶
遷移檔案由一個或多個 Operation
組成,這些物件以宣告方式記錄遷移應該對您的資料庫執行的操作。
Django 也使用這些 Operation
物件來找出您的模型在歷史上的外觀,並計算您自上次遷移以來對模型所做的變更,以便它可以自動編寫您的遷移;這就是為什麼它們是宣告式的,因為這意味著 Django 可以輕鬆地將它們全部載入記憶體並執行它們,而無需接觸資料庫,以找出您的專案應該是什麼樣子。
還有更多專門的 Operation
物件,用於 資料遷移 和進階的手動資料庫操作。您也可以編寫自己的 Operation
類別,如果您想封裝您經常進行的自訂變更。
如果您需要一個空的遷移檔案來寫入您自己的 Operation
物件,請使用 python manage.py makemigrations --empty yourappname
,但請注意,手動新增變更綱要的操作可能會混淆遷移自動偵測器,並導致執行 makemigrations
輸出不正確的程式碼。
所有 Django 核心操作都可從 django.db.migrations.operations
模組取得。
如需入門教材,請參閱遷移主題指南。
綱要操作¶
CreateModel
¶
在專案歷史記錄中建立新的模型,並在資料庫中建立對應的資料表以與之匹配。
name
是模型名稱,如在 models.py
檔案中編寫的那樣。
fields
是 (field_name, field_instance)
的 2 元組列表。欄位實例應該是未綁定的欄位(因此僅 models.CharField(...)
,而不是從另一個模型取得的欄位)。
options
是模型 Meta
類別中值的可選字典。
bases
是此模型要繼承的其他類別的可選清單;如果想要依賴另一個模型(因此繼承自歷史版本),則可以同時包含類別物件和 "appname.ModelName"
格式的字串。如果未提供,則預設為繼承標準 models.Model
。
managers
採用 (manager_name, manager_instance)
的 2 元組列表。列表中的第一個管理員將是遷移期間此模型的預設管理員。
DeleteModel
¶
從專案歷史記錄中刪除模型,並從資料庫中刪除其資料表。
RenameModel
¶
將模型從舊名稱重新命名為新名稱。
如果您一次變更模型名稱及其許多欄位,您可能必須手動新增此項目;對於自動偵測器而言,這看起來像是您刪除了具有舊名稱的模型並新增了一個具有不同名稱的新模型,並且它建立的遷移將會遺失舊資料表中的任何資料。
AlterModelTable
¶
變更模型的資料表名稱(db_table
Meta
子類別上的選項)。
AlterModelTableComment
¶
變更模型的資料表註解(db_table_comment
Meta
子類別上的選項)。
AlterUniqueTogether
¶
變更模型的一組唯一約束(unique_together
Meta
子類別上的選項)。
AlterIndexTogether
¶
變更模型的一組自訂索引(index_together
Meta
子類別上的選項)。
警告
AlterIndexTogether
僅正式支援 Django 4.2 之前的遷移檔案。出於向後相容性的原因,它仍然是公開 API 的一部分,並且沒有計劃要棄用或移除它,但它不應在新的遷移中使用。請改用 AddIndex
和 RemoveIndex
操作。
AlterOrderWithRespectTo
¶
建立或刪除 order_with_respect_to
選項在 Meta
子類別上所需的 _order
欄位。
AlterModelOptions
¶
儲存對雜項模型選項(模型 Meta
上的設定)的變更,例如 permissions
和 verbose_name
。不會影響資料庫,但會保留這些變更以供 RunPython
實例使用。options
應該是一個字典,將選項名稱對應到值。
AlterModelManagers
¶
變更遷移期間可用的管理器。
AddField
¶
將欄位新增至模型。model_name
是模型的名稱,name
是欄位的名稱,而 field
是一個未綁定的 Field 實例(您將在 models.py
中的欄位宣告中放入的內容 - 例如,models.IntegerField(null=True)
)。
preserve_default
引數表示欄位的預設值是永久的,應該被烘焙到專案狀態中 (True
),還是暫時的,僅用於此遷移 (False
) - 通常是因為遷移將非可為空的欄位新增至表格,並且需要一個預設值來放入現有資料列。它不會影響直接在資料庫中設定預設值的行為 - Django 永遠不會設定資料庫預設值,而始終在 Django ORM 程式碼中套用它們。
警告
在較舊的資料庫上,新增具有預設值的欄位可能會導致完整重寫表格。即使是可為空的欄位也會發生這種情況,並且可能會對效能產生負面影響。為了避免這種情況,應採取以下步驟。
新增不帶預設值的可為空欄位,並執行
makemigrations
命令。這應該會產生一個具有AddField
操作的遷移。將預設值新增至您的欄位,並執行
makemigrations
命令。這應該會產生一個具有AlterField
操作的遷移。
RemoveField
¶
從模型中移除欄位。
請記住,當還原時,這實際上是將欄位新增至模型。如果欄位可為空,或者如果它具有可用於填入重新建立之資料行的預設值,則該操作是可逆的(除了任何資料遺失之外,這是不可逆的)。如果欄位不可為空且沒有預設值,則該操作是不可逆的。
PostgreSQL
RemoveField
也會刪除與移除欄位相關的任何其他資料庫物件(例如檢視)。這是因為產生的 DROP COLUMN
陳述式將包含 CASCADE
子句,以確保也會刪除表格外部的相依物件。
AlterField
¶
變更欄位的定義,包括變更其類型、null
、unique
、db_column
和其他欄位屬性。
preserve_default
引數表示欄位的預設值是永久的,應該被烘焙到專案狀態中 (True
),還是暫時的,僅用於此遷移 (False
) - 通常是因為遷移正在將可為空的欄位變更為非可為空的欄位,並且需要一個預設值來放入現有資料列。它不會影響直接在資料庫中設定預設值的行為 - Django 永遠不會設定資料庫預設值,而始終在 Django ORM 程式碼中套用它們。
請注意,並非所有變更都可以在所有資料庫上進行 - 例如,在大多數資料庫上,您無法將文字類型欄位(如 models.TextField()
)變更為數字類型欄位(如 models.IntegerField()
)。
RenameField
¶
變更欄位的名稱(並且,除非設定了 db_column
,否則也會變更其資料行名稱)。
AddIndex
¶
在具有 model_name
的模型的資料庫表格中建立索引。index
是 Index
類別的實例。
RemoveIndex
¶
從具有 model_name
的模型中移除名為 name
的索引。
RenameIndex
¶
在具有 model_name
的模型的資料庫表格中重新命名索引。必須提供 old_name
和 old_fields
其中之一。old_fields
是字串的可迭代物件,通常對應於 index_together
的欄位(Django 5.1 之前的選項)。
在不支援索引重新命名陳述式的資料庫(SQLite 和 MariaDB < 10.5.2)上,該操作將會捨棄並重新建立索引,這可能會很耗費資源。
AddConstraint
¶
在具有 model_name
的模型的資料庫表格中建立約束。
RemoveConstraint
¶
從具有 model_name
的模型中移除名為 name
的約束。
特殊操作¶
RunSQL
¶
允許在資料庫上執行任意 SQL - 這對於 Django 不直接支援的資料庫後端的更進階功能很有用。
sql
和 reverse_sql
(如果提供)應該是要在資料庫上執行的 SQL 字串。在大多數資料庫後端(除了 PostgreSQL 之外的所有後端)上,Django 會在執行 SQL 之前將 SQL 分割成個別的語句。
警告
在 PostgreSQL 和 SQLite 上,只有在非原子遷移中使用 SQL 中的 BEGIN
或 COMMIT
,以避免破壞 Django 的交易狀態。
您也可以傳遞字串或 2 元組的清單。後者用於以與 cursor.execute() 相同的方式傳遞查詢和參數。這三個操作是等效的
migrations.RunSQL("INSERT INTO musician (name) VALUES ('Reinhardt');")
migrations.RunSQL([("INSERT INTO musician (name) VALUES ('Reinhardt');", None)])
migrations.RunSQL([("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])])
如果要在查詢中包含文字百分比符號,則在傳遞參數時必須將它們加倍。
當遷移被取消應用時,會執行 reverse_sql
查詢。它們應該撤消 sql
查詢所執行的操作。例如,要使用刪除來撤消上述插入
migrations.RunSQL(
sql=[("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])],
reverse_sql=[("DELETE FROM musician where name=%s;", ["Reinhardt"])],
)
如果 reverse_sql
為 None
(預設值),則 RunSQL
操作是不可逆的。
state_operations
參數允許您提供在專案狀態方面與 SQL 等效的操作。例如,如果您手動建立一個欄位,則應在此處傳遞一個包含 AddField
操作的清單,以便自動偵測器仍然具有模型的最新狀態。 如果您不這樣做,則下次執行 makemigrations
時,它將不會看到任何新增該欄位的操作,因此會嘗試再次執行它。 例如
migrations.RunSQL(
"ALTER TABLE musician ADD COLUMN name varchar(255) NOT NULL;",
state_operations=[
migrations.AddField(
"musician",
"name",
models.CharField(max_length=255),
),
],
)
可選的 hints
參數將作為 **hints
傳遞給資料庫路由器的 allow_migrate()
方法,以協助它們做出路由決策。 有關資料庫提示的更多詳細資訊,請參閱提示。
可選的 elidable
參數決定在壓縮遷移時是否移除(省略)該操作。
- RunSQL.noop¶
當您希望操作在給定的方向上不執行任何操作時,請將
RunSQL.noop
屬性傳遞給sql
或reverse_sql
。 這在使操作可逆時特別有用。
RunPython
¶
在歷史情境中執行自訂 Python 程式碼。code
(以及 reverse_code
,如果提供)應該是可呼叫的物件,它接受兩個引數;第一個是 django.apps.registry.Apps
的實例,其中包含與操作在專案歷史中的位置相符的歷史模型,第二個是 SchemaEditor
的實例。
當取消應用遷移時,會呼叫 reverse_code
引數。這個可呼叫的物件應該撤銷 code
可呼叫物件中執行的操作,以便遷移是可逆的。如果 reverse_code
為 None
(預設值),則 RunPython
操作是不可逆的。
可選的 hints
參數將作為 **hints
傳遞給資料庫路由器的 allow_migrate()
方法,以協助它們做出路由決策。 有關資料庫提示的更多詳細資訊,請參閱提示。
可選的 elidable
參數決定在壓縮遷移時是否移除(省略)該操作。
建議您將程式碼寫成遷移檔案中 Migration
類別上方的單獨函式,並將其傳遞給 RunPython
。 以下是如何使用 RunPython
在 Country
模型上建立一些初始物件的範例
from django.db import migrations
def forwards_func(apps, schema_editor):
# We get the model from the versioned app registry;
# if we directly import it, it'll be the wrong version
Country = apps.get_model("myapp", "Country")
db_alias = schema_editor.connection.alias
Country.objects.using(db_alias).bulk_create(
[
Country(name="USA", code="us"),
Country(name="France", code="fr"),
]
)
def reverse_func(apps, schema_editor):
# forwards_func() creates two Country instances,
# so reverse_func() should delete them.
Country = apps.get_model("myapp", "Country")
db_alias = schema_editor.connection.alias
Country.objects.using(db_alias).filter(name="USA", code="us").delete()
Country.objects.using(db_alias).filter(name="France", code="fr").delete()
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.RunPython(forwards_func, reverse_func),
]
這通常是用於建立資料遷移、執行自訂資料更新和修改以及任何其他需要存取 ORM 和/或 Python 程式碼的操作。
與RunSQL
非常相似,請確保如果您在這裡變更綱要,您要么在 Django 模型系統的範圍之外(例如,觸發器)執行它,要么使用 SeparateDatabaseAndState
來加入將反映您對模型狀態的變更的操作 - 否則,版本化的 ORM 和自動偵測器將停止正常運作。
預設情況下,RunPython
會在不支援 DDL 交易的資料庫(例如,MySQL 和 Oracle)上在交易內部執行其內容。 這應該是安全的,但如果您嘗試在這些後端上使用提供的 schema_editor
,則可能會導致崩潰;在這種情況下,請將 atomic=False
傳遞給 RunPython
操作。
在支援 DDL 交易的資料庫(SQLite 和 PostgreSQL)上,除了為每個遷移建立的交易之外,RunPython
操作沒有自動新增任何交易。 因此,例如在 PostgreSQL 上,您應避免在同一個遷移中組合綱要變更和 RunPython
操作,否則可能會遇到類似 OperationalError: cannot ALTER TABLE "mytable" because it has pending trigger events
的錯誤。
如果您有不同的資料庫,並且不確定它是否支援 DDL 交易,請檢查 django.db.connection.features.can_rollback_ddl
屬性。
如果 RunPython
操作是非原子遷移的一部分,則僅當 atomic=True
傳遞給 RunPython
操作時,該操作才會在交易中執行。
警告
RunPython
不會神奇地為您變更模型的連線;您呼叫的任何模型方法都將轉到預設資料庫,除非您為它們提供目前的資料庫別名(可從 schema_editor.connection.alias
中取得,其中 schema_editor
是您函式的第二個引數)。
SeparateDatabaseAndState
¶
這是一個高度專業化的操作,可讓您混合搭配操作的資料庫(schema 變更)和狀態(自動偵測器驅動)方面。
它接受兩個操作列表。當被要求套用狀態時,它將使用 state_operations
列表(這是 RunSQL
的 state_operations
引數的通用版本)。當被要求對資料庫套用變更時,它將使用 database_operations
列表。
如果資料庫的實際狀態和 Django 對狀態的檢視失去同步,這可能會破壞遷移框架,甚至導致資料遺失。請務必謹慎並仔細檢查您的資料庫和狀態操作。您可以使用 sqlmigrate
和 dbshell
來檢查您的資料庫操作。您可以使用 makemigrations
,尤其是搭配 --dry-run
,來檢查您的狀態操作。
如需使用 SeparateDatabaseAndState
的範例,請參閱 變更 ManyToManyField 以使用 through 模型。
操作類別¶
撰寫您自己的操作¶
操作具有相對簡單的 API,並且它們的設計使您可以輕鬆撰寫自己的操作來補充 Django 內建的操作。Operation
的基本結構如下
from django.db.migrations.operations.base import Operation
class MyCustomOperation(Operation):
# If this is False, it means that this operation will be ignored by
# sqlmigrate; if true, it will be run and the SQL collected for its output.
reduces_to_sql = False
# If this is False, Django will refuse to reverse past this operation.
reversible = False
# This categorizes the operation. The corresponding symbol will be
# displayed by the makemigrations command.
category = OperationCategory.ADDITION
def __init__(self, arg1, arg2):
# Operations are usually instantiated with arguments in migration
# files. Store the values of them on self for later use.
pass
def state_forwards(self, app_label, state):
# The Operation should take the 'state' parameter (an instance of
# django.db.migrations.state.ProjectState) and mutate it to match
# any schema changes that have occurred.
pass
def database_forwards(self, app_label, schema_editor, from_state, to_state):
# The Operation should use schema_editor to apply any changes it
# wants to make to the database.
pass
def database_backwards(self, app_label, schema_editor, from_state, to_state):
# If reversible is True, this is called when the operation is reversed.
pass
def describe(self):
# This is used to describe what the operation does.
return "Custom Operation"
@property
def migration_name_fragment(self):
# Optional. A filename part suitable for automatically naming a
# migration containing this operation, or None if not applicable.
return "custom_operation_%s_%s" % (self.arg1, self.arg2)
您可以採用此範本並以此為基礎進行修改,但我們建議查看 django.db.migrations.operations
中的 Django 內建操作 - 它們涵蓋了遷移框架的半內部方面的許多範例用法,例如 ProjectState
和用於獲取歷史模型的模式,以及 ModelState
和用於在 state_forwards()
中變更歷史模型的模式。
一些需要注意的事項
您不需要了解太多關於
ProjectState
的資訊即可撰寫遷移;只要知道它具有一個apps
屬性,該屬性可讓您存取應用程式註冊表(然後您可以在其上呼叫get_model
)。database_forwards
和database_backwards
都會傳遞兩個狀態;這些狀態表示state_forwards
方法本應套用的差異,但為了方便和速度原因而提供給您。如果您想在
database_forwards()
或database_backwards()
中使用來自from_state
引數的模型類別或模型實例,則必須使用clear_delayed_apps_cache()
方法呈現模型狀態,以使相關模型可用。def database_forwards(self, app_label, schema_editor, from_state, to_state): # This operation should have access to all models. Ensure that all models are # reloaded in case any are delayed. from_state.clear_delayed_apps_cache() ...
database_backwards 方法中的
to_state
是較舊的狀態;也就是說,遷移完成反轉後將成為目前狀態的狀態。您可能會在內建操作中看到
references_model
的實作;這是自動偵測程式碼的一部分,對於自訂操作無關緊要。
警告
出於效能原因,ModelState.fields
中的 Field
實例會在各個遷移中重複使用。您絕不能變更這些實例的屬性。如果您需要在 state_forwards()
中變更欄位,則必須從 ModelState.fields
中移除舊的實例,並在其位置新增新的實例。 ModelState.managers
中的 Manager
實例也是如此。
舉例來說,讓我們建立一個載入 PostgreSQL 擴充功能(其中包含 PostgreSQL 的一些更令人興奮的功能)的操作。由於沒有模型狀態變更,它所做的只是執行一個命令
from django.db.migrations.operations.base import Operation
class LoadExtension(Operation):
reversible = True
def __init__(self, name):
self.name = name
def state_forwards(self, app_label, state):
pass
def database_forwards(self, app_label, schema_editor, from_state, to_state):
schema_editor.execute("CREATE EXTENSION IF NOT EXISTS %s" % self.name)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
schema_editor.execute("DROP EXTENSION %s" % self.name)
def describe(self):
return "Creates extension %s" % self.name
@property
def migration_name_fragment(self):
return "create_extension_%s" % self.name