遷移操作

遷移檔案由一個或多個 Operation 組成,這些物件以宣告方式記錄遷移應該對您的資料庫執行的操作。

Django 也使用這些 Operation 物件來找出您的模型在歷史上的外觀,並計算您自上次遷移以來對模型所做的變更,以便它可以自動編寫您的遷移;這就是為什麼它們是宣告式的,因為這意味著 Django 可以輕鬆地將它們全部載入記憶體並執行它們,而無需接觸資料庫,以找出您的專案應該是什麼樣子。

還有更多專門的 Operation 物件,用於 資料遷移 和進階的手動資料庫操作。您也可以編寫自己的 Operation 類別,如果您想封裝您經常進行的自訂變更。

如果您需要一個空的遷移檔案來寫入您自己的 Operation 物件,請使用 python manage.py makemigrations --empty yourappname,但請注意,手動新增變更綱要的操作可能會混淆遷移自動偵測器,並導致執行 makemigrations 輸出不正確的程式碼。

所有 Django 核心操作都可從 django.db.migrations.operations 模組取得。

如需入門教材,請參閱遷移主題指南

綱要操作

CreateModel

class CreateModel(name, fields, options=None, bases=None, managers=None)[原始碼]

在專案歷史記錄中建立新的模型,並在資料庫中建立對應的資料表以與之匹配。

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

class DeleteModel(name)[原始碼]

從專案歷史記錄中刪除模型,並從資料庫中刪除其資料表。

RenameModel

class RenameModel(old_name, new_name)[原始碼]

將模型從舊名稱重新命名為新名稱。

如果您一次變更模型名稱及其許多欄位,您可能必須手動新增此項目;對於自動偵測器而言,這看起來像是您刪除了具有舊名稱的模型並新增了一個具有不同名稱的新模型,並且它建立的遷移將會遺失舊資料表中的任何資料。

AlterModelTable

class AlterModelTable(name, table)[原始碼]

變更模型的資料表名稱(db_table Meta 子類別上的選項)。

AlterModelTableComment

class AlterModelTableComment(name, table_comment)[原始碼]

變更模型的資料表註解(db_table_comment Meta 子類別上的選項)。

AlterUniqueTogether

class AlterUniqueTogether(name, unique_together)[原始碼]

變更模型的一組唯一約束(unique_together Meta 子類別上的選項)。

AlterIndexTogether

class AlterIndexTogether(name, index_together)[原始碼]

變更模型的一組自訂索引(index_together Meta 子類別上的選項)。

警告

AlterIndexTogether 僅正式支援 Django 4.2 之前的遷移檔案。出於向後相容性的原因,它仍然是公開 API 的一部分,並且沒有計劃要棄用或移除它,但它不應在新的遷移中使用。請改用 AddIndexRemoveIndex 操作。

AlterOrderWithRespectTo

class AlterOrderWithRespectTo(name, order_with_respect_to)[原始碼]

建立或刪除 order_with_respect_to 選項在 Meta 子類別上所需的 _order 欄位。

AlterModelOptions

class AlterModelOptions(name, options)[原始碼]

儲存對雜項模型選項(模型 Meta 上的設定)的變更,例如 permissionsverbose_name。不會影響資料庫,但會保留這些變更以供 RunPython 實例使用。options 應該是一個字典,將選項名稱對應到值。

AlterModelManagers

class AlterModelManagers(name, managers)[原始碼]

變更遷移期間可用的管理器。

AddField

class AddField(model_name, name, field, preserve_default=True)[原始碼]

將欄位新增至模型。model_name 是模型的名稱,name 是欄位的名稱,而 field 是一個未綁定的 Field 實例(您將在 models.py 中的欄位宣告中放入的內容 - 例如,models.IntegerField(null=True))。

preserve_default 引數表示欄位的預設值是永久的,應該被烘焙到專案狀態中 (True),還是暫時的,僅用於此遷移 (False) - 通常是因為遷移將非可為空的欄位新增至表格,並且需要一個預設值來放入現有資料列。它不會影響直接在資料庫中設定預設值的行為 - Django 永遠不會設定資料庫預設值,而始終在 Django ORM 程式碼中套用它們。

警告

在較舊的資料庫上,新增具有預設值的欄位可能會導致完整重寫表格。即使是可為空的欄位也會發生這種情況,並且可能會對效能產生負面影響。為了避免這種情況,應採取以下步驟。

  • 新增不帶預設值的可為空欄位,並執行 makemigrations 命令。這應該會產生一個具有 AddField 操作的遷移。

  • 將預設值新增至您的欄位,並執行 makemigrations 命令。這應該會產生一個具有 AlterField 操作的遷移。

RemoveField

class RemoveField(model_name, name)[原始碼]

從模型中移除欄位。

請記住,當還原時,這實際上是將欄位新增至模型。如果欄位可為空,或者如果它具有可用於填入重新建立之資料行的預設值,則該操作是可逆的(除了任何資料遺失之外,這是不可逆的)。如果欄位不可為空且沒有預設值,則該操作是不可逆的。

PostgreSQL

RemoveField 也會刪除與移除欄位相關的任何其他資料庫物件(例如檢視)。這是因為產生的 DROP COLUMN 陳述式將包含 CASCADE 子句,以確保也會刪除表格外部的相依物件

AlterField

class AlterField(model_name, name, field, preserve_default=True)[原始碼]

變更欄位的定義,包括變更其類型、nulluniquedb_column 和其他欄位屬性。

preserve_default 引數表示欄位的預設值是永久的,應該被烘焙到專案狀態中 (True),還是暫時的,僅用於此遷移 (False) - 通常是因為遷移正在將可為空的欄位變更為非可為空的欄位,並且需要一個預設值來放入現有資料列。它不會影響直接在資料庫中設定預設值的行為 - Django 永遠不會設定資料庫預設值,而始終在 Django ORM 程式碼中套用它們。

請注意,並非所有變更都可以在所有資料庫上進行 - 例如,在大多數資料庫上,您無法將文字類型欄位(如 models.TextField())變更為數字類型欄位(如 models.IntegerField())。

RenameField

class RenameField(model_name, old_name, new_name)[原始碼]

變更欄位的名稱(並且,除非設定了 db_column,否則也會變更其資料行名稱)。

AddIndex

class AddIndex(model_name, index)[原始碼]

在具有 model_name 的模型的資料庫表格中建立索引。indexIndex 類別的實例。

RemoveIndex

class RemoveIndex(model_name, name)[原始碼]

從具有 model_name 的模型中移除名為 name 的索引。

RenameIndex

class RenameIndex(model_name, new_name, old_name=None, old_fields=None)[原始碼]

在具有 model_name 的模型的資料庫表格中重新命名索引。必須提供 old_nameold_fields 其中之一。old_fields 是字串的可迭代物件,通常對應於 index_together 的欄位(Django 5.1 之前的選項)。

在不支援索引重新命名陳述式的資料庫(SQLite 和 MariaDB < 10.5.2)上,該操作將會捨棄並重新建立索引,這可能會很耗費資源。

AddConstraint

class AddConstraint(model_name, constraint)[原始碼]

在具有 model_name 的模型的資料庫表格中建立約束

RemoveConstraint

class RemoveConstraint(model_name, name)[原始碼]

從具有 model_name 的模型中移除名為 name 的約束。

特殊操作

RunSQL

class RunSQL(sql, reverse_sql=None, state_operations=None, hints=None, elidable=False)[原始碼]

允許在資料庫上執行任意 SQL - 這對於 Django 不直接支援的資料庫後端的更進階功能很有用。

sqlreverse_sql (如果提供)應該是要在資料庫上執行的 SQL 字串。在大多數資料庫後端(除了 PostgreSQL 之外的所有後端)上,Django 會在執行 SQL 之前將 SQL 分割成個別的語句。

警告

在 PostgreSQL 和 SQLite 上,只有在非原子遷移中使用 SQL 中的 BEGINCOMMIT,以避免破壞 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_sqlNone(預設值),則 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 屬性傳遞給 sqlreverse_sql。 這在使操作可逆時特別有用。

RunPython

class RunPython(code, reverse_code=None, atomic=None, hints=None, elidable=False)[原始碼]

在歷史情境中執行自訂 Python 程式碼。code(以及 reverse_code,如果提供)應該是可呼叫的物件,它接受兩個引數;第一個是 django.apps.registry.Apps 的實例,其中包含與操作在專案歷史中的位置相符的歷史模型,第二個是 SchemaEditor 的實例。

當取消應用遷移時,會呼叫 reverse_code 引數。這個可呼叫的物件應該撤銷 code 可呼叫物件中執行的操作,以便遷移是可逆的。如果 reverse_codeNone(預設值),則 RunPython 操作是不可逆的。

可選的 hints 參數將作為 **hints 傳遞給資料庫路由器的 allow_migrate() 方法,以協助它們做出路由決策。 有關資料庫提示的更多詳細資訊,請參閱提示

可選的 elidable 參數決定在壓縮遷移時是否移除(省略)該操作。

建議您將程式碼寫成遷移檔案中 Migration 類別上方的單獨函式,並將其傳遞給 RunPython。 以下是如何使用 RunPythonCountry 模型上建立一些初始物件的範例

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 是您函式的第二個引數)。

static RunPython.noop()[原始碼]

當您希望操作在給定的方向上不執行任何操作時,請將 RunPython.noop 方法傳遞給 codereverse_code。 這在使操作可逆時特別有用。

SeparateDatabaseAndState

class SeparateDatabaseAndState(database_operations=None, state_operations=None)[原始碼]

這是一個高度專業化的操作,可讓您混合搭配操作的資料庫(schema 變更)和狀態(自動偵測器驅動)方面。

它接受兩個操作列表。當被要求套用狀態時,它將使用 state_operations 列表(這是 RunSQLstate_operations 引數的通用版本)。當被要求對資料庫套用變更時,它將使用 database_operations 列表。

如果資料庫的實際狀態和 Django 對狀態的檢視失去同步,這可能會破壞遷移框架,甚至導致資料遺失。請務必謹慎並仔細檢查您的資料庫和狀態操作。您可以使用 sqlmigratedbshell 來檢查您的資料庫操作。您可以使用 makemigrations,尤其是搭配 --dry-run,來檢查您的狀態操作。

如需使用 SeparateDatabaseAndState 的範例,請參閱 變更 ManyToManyField 以使用 through 模型

操作類別

Django 5.1 新增。
class OperationCategory[原始碼]

makemigrations 命令使用的遷移操作類別,用於顯示有意義的符號。

ADDITION

符號: +

REMOVAL

符號: -

ALTERATION

符號: ~

PYTHON

符號: p

SQL

符號: s

MIXED

符號: ?

撰寫您自己的操作

操作具有相對簡單的 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_forwardsdatabase_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
返回頂端