遷移

遷移是 Django 將您對模型所做的變更(新增欄位、刪除模型等等)傳播到資料庫結構描述的方式。它們的設計主要為自動化,但您需要了解何時進行遷移、何時執行它們,以及您可能遇到的常見問題。

命令

您將使用幾個命令與遷移以及 Django 對資料庫結構描述的處理互動

  • migrate,負責應用和取消應用遷移。

  • makemigrations,負責根據您對模型所做的變更建立新的遷移。

  • sqlmigrate,顯示遷移的 SQL 陳述式。

  • showmigrations,列出專案的遷移及其狀態。

您應該將遷移視為資料庫結構描述的版本控制系統。makemigrations 負責將您的模型變更打包成單獨的遷移檔案 - 類似於 commit - 而 migrate 負責將它們應用到您的資料庫。

每個應用程式的遷移檔案都位於該應用程式內的「migrations」目錄中,並且設計為作為其程式碼庫的一部分提交和發布。您應該在您的開發機器上進行一次,然後在您同事的機器、您的預備機器,以及最終在您的生產機器上執行相同的遷移。

注意

可以透過修改 MIGRATION_MODULES 設定,針對每個應用程式覆寫包含遷移的套件名稱。

遷移將以相同的方式在相同的資料集上執行並產生一致的結果,這表示您在開發和預備環境中所看到的內容,在相同的條件下,與在生產環境中發生的情況完全相同。

Django 將會對您模型或欄位的任何變更建立遷移 - 即使是不影響資料庫的選項 - 因為它正確重建欄位的唯一方法是擁有歷史記錄中的所有變更,並且您稍後可能需要在某些資料遷移中使用這些選項(例如,如果您已設定自訂驗證器)。

後端支援

所有 Django 隨附的後端都支援遷移,以及任何第三方後端,如果它們已編寫程式以支援結構描述變更(透過 SchemaEditor 類別完成)。

然而,在結構描述遷移方面,某些資料庫比其他資料庫更具能力;一些注意事項如下所述。

PostgreSQL

在結構描述支援方面,PostgreSQL 是所有資料庫中最有能力的。

MySQL

MySQL 缺乏對結構描述變更操作的交易支援,這表示如果遷移應用失敗,您必須手動取消選取變更才能再次嘗試(無法回滾到較早的點)。

MySQL 8.0 為 DDL 操作引入了顯著的效能增強功能,使其更有效率並減少完整表格重建的需求。但是,它無法保證完全沒有鎖定或中斷。在仍然需要鎖定的情況下,這些操作的持續時間將與涉及的列數成正比。

最後,MySQL 對索引涵蓋的所有列的組合大小有一個相對較小的限制。這表示在其他後端上可能的索引將無法在 MySQL 下建立。

SQLite

SQLite 的內建結構描述變更支援非常少,因此 Django 會嘗試透過以下方式模擬它:

  • 使用新的結構描述建立新的表格

  • 複製資料

  • 刪除舊表格

  • 重新命名新表格以符合原始名稱

此程序通常運作良好,但可能會很慢且偶爾有錯誤。除非您非常清楚其風險和限制,否則不建議您在生產環境中執行和遷移 SQLite;Django 隨附的支援旨在允許開發人員在其本機電腦上使用 SQLite 開發不那麼複雜的 Django 專案,而無需完整的資料庫。

工作流程

Django 可以為您建立遷移。對您的模型進行變更 - 例如,新增欄位並移除模型 - 然後執行 makemigrations

$ python manage.py makemigrations
Migrations for 'books':
  books/migrations/0003_auto.py:
    ~ Alter field author on book

您的模型將被掃描並與目前包含在您的遷移檔案中的版本進行比較,然後將寫出新的遷移集。請務必閱讀輸出,以查看 makemigrations 認為您已變更的內容 - 它並不完美,對於複雜的變更,它可能無法偵測到您預期的內容。

當您擁有新的遷移檔案後,您應該將它們應用到您的資料庫,以確保它們按預期運作

$ python manage.py migrate
Operations to perform:
  Apply all migrations: books
Running migrations:
  Rendering model states... DONE
  Applying books.0003_auto... OK

遷移應用後,將遷移和模型變更作為單一提交提交到您的版本控制系統 - 這樣,當其他開發人員(或您的生產伺服器)簽出程式碼時,他們將同時取得模型變更和隨附的遷移。

如果您想要為遷移提供一個有意義的名稱而不是產生的名稱,您可以使用 makemigrations --name 選項

$ python manage.py makemigrations --name changed_my_model your_app_label

版本控制

因為遷移儲存在版本控制中,您偶爾會遇到您和另一位開發人員同時將遷移提交到同一個應用程式的情況,導致兩個遷移具有相同的編號。

別擔心 - 這些編號只是供開發人員參考,Django 只關心每個遷移都有不同的名稱。遷移會在檔案中指定它們所依賴的其他遷移 - 包括同一應用程式中較早的遷移 - 因此可以偵測到何時同一個應用程式有兩個未排序的新遷移。

當這種情況發生時,Django 會提示您並提供一些選項。如果它認為夠安全,它會提供自動為您線性化兩個遷移。如果沒有,您將必須進入並自行修改遷移 - 別擔心,這並不困難,並且在下面的遷移檔案中會進行更詳細的說明。

交易

在支援 DDL 交易的資料庫(SQLite 和 PostgreSQL)上,所有遷移操作預設都會在單一交易中執行。相反地,如果資料庫不支援 DDL 交易(例如 MySQL、Oracle),則所有操作都將在沒有交易的情況下執行。

您可以透過將 atomic 屬性設定為 False 來防止遷移在交易中執行。例如

from django.db import migrations


class Migration(migrations.Migration):
    atomic = False

也可以使用 atomic() 或透過將 atomic=True 傳遞給 RunPython,在交易內執行部分遷移。請參閱 非原子遷移 以取得更多詳細資訊。

相依性

雖然遷移是針對每個應用程式,但您的模型所暗示的表格和關係太複雜,無法一次為一個應用程式建立。當您進行需要執行其他操作的遷移時 - 例如,您在 books 應用程式中新增一個 ForeignKey 到您的 authors 應用程式 - 產生的遷移將包含對 authors 中遷移的相依性。

這表示當您執行遷移時,authors 遷移會先執行並建立 ForeignKey 參考的表格,然後建立 ForeignKey 欄位的遷移會在之後執行並建立約束。如果沒有發生這種情況,遷移將嘗試建立 ForeignKey 欄位,而沒有它所參考的表格存在,並且您的資料庫會擲回錯誤。

這種相依性行為會影響您限制為單一應用程式的大多數遷移操作。限制為單一應用程式(在 makemigrationsmigrate 中)是一個盡力而為的承諾,而不是保證;任何其他需要用於正確取得相依性的應用程式都將會被使用。

沒有遷移的應用程式不得與有遷移的應用程式建立關聯(ForeignKeyManyToManyField 等)。有時它可能可以運作,但不受支援。

可替換的依賴

django.db.migrations.swappable_dependency(value)

swappable_dependency() 函數在遷移中用來宣告可「替換」的依賴關係,依賴於被替換模型的應用程式中的遷移,目前是在該應用程式的第一次遷移。因此,被替換的模型應該在初始遷移中建立。參數 value 是一個字串 "<應用程式標籤>.<模型>",描述一個應用程式標籤和一個模型名稱,例如 "myapp.MyModel"

透過使用 swappable_dependency(),您通知遷移框架,該遷移依賴於另一個設置可替換模型的遷移,以便未來可以使用不同的實作替換該模型。這通常用於引用那些會被自訂或替換的模型,例如 Django 身份驗證系統中的自訂使用者模型(settings.AUTH_USER_MODEL,預設為 "auth.User")。

遷移檔案

遷移以磁碟格式儲存,這裡稱為「遷移檔案」。這些檔案實際上是普通的 Python 檔案,具有約定的物件佈局,以宣告式風格編寫。

一個基本的遷移檔案看起來像這樣

from django.db import migrations, models


class Migration(migrations.Migration):
    dependencies = [("migrations", "0001_initial")]

    operations = [
        migrations.DeleteModel("Tribble"),
        migrations.AddField("Author", "rating", models.IntegerField(default=0)),
    ]

當 Django 載入遷移檔案(作為一個 Python 模組)時,它會尋找一個名為 Migrationdjango.db.migrations.Migration 的子類別。然後它會檢查這個物件的四個屬性,其中只有兩個屬性在大多數情況下使用

  • dependencies,這個遷移所依賴的遷移列表。

  • operations,一個定義這個遷移所做事情的 Operation 類別列表。

操作是關鍵;它們是一組宣告式的指令,告訴 Django 需要進行哪些架構變更。Django 會掃描它們,並建立所有應用程式所有架構變更的記憶體表示,並使用它來產生進行架構變更的 SQL。

該記憶體結構也用於計算您的模型與目前的遷移狀態之間的差異;Django 會按照順序在記憶體中的模型集上執行所有變更,以得出您上次執行 makemigrations 時的模型狀態。然後,它會使用這些模型與 models.py 檔案中的模型進行比較,以計算出您所做的變更。

您應該很少需要手動編輯遷移檔案,但如果您需要,完全可以手動編寫它們。一些更複雜的操作是無法自動偵測到的,只能透過手寫的遷移來使用,所以如果您必須編輯它們,請不要感到害怕。

自訂欄位

您無法在不引發 TypeError 的情況下修改已遷移的自訂欄位中的位置參數數量。舊的遷移將使用舊的簽章呼叫修改後的 __init__ 方法。因此,如果您需要一個新的參數,請建立一個關鍵字參數,並在建構子中新增類似 assert 'argument_name' in kwargs 的程式碼。

模型管理員

您可以選擇將管理員序列化到遷移中,並在 RunPython 操作中使用它們。這是透過在管理員類別上定義 use_in_migrations 屬性來完成的

class MyManager(models.Manager):
    use_in_migrations = True


class MyModel(models.Model):
    objects = MyManager()

如果您使用 from_queryset() 函數來動態產生管理員類別,您需要繼承產生的類別以使其可匯入

class MyManager(MyBaseManager.from_queryset(CustomQuerySet)):
    use_in_migrations = True


class MyModel(models.Model):
    objects = MyManager()

請參閱關於遷移中歷史模型的注意事項,以了解隨之而來的影響。

初始遷移

Migration.initial

應用程式的「初始遷移」是建立該應用程式資料表第一個版本的遷移。通常,一個應用程式會有一個初始遷移,但在某些複雜的模型相互依賴的情況下,可能會有多個初始遷移。

初始遷移在遷移類別上使用 initial = True 類別屬性標記。如果找不到 initial 類別屬性,如果一個遷移是應用程式中的第一個遷移(即如果它不依賴於同一應用程式中的任何其他遷移),則該遷移將被視為「初始」遷移。

當使用 migrate --fake-initial 選項時,這些初始遷移會被特殊處理。對於建立一個或多個資料表(CreateModel 操作)的初始遷移,Django 會檢查所有這些資料表是否已存在於資料庫中,如果是,則假應用該遷移。同樣地,對於新增一個或多個欄位(AddField 操作)的初始遷移,Django 會檢查所有各自的欄是否已存在於資料庫中,如果是,則假應用該遷移。如果沒有 --fake-initial,初始遷移的處理方式與任何其他遷移沒有區別。

歷史一致性

如先前所討論,當兩個開發分支合併時,您可能需要手動線性化遷移。在編輯遷移依賴項時,您可能會不小心建立不一致的歷史狀態,其中一個遷移已應用,但其某些依賴項尚未應用。這強烈表明依賴項不正確,因此 Django 將拒絕執行遷移或進行新的遷移,直到它被修復。當使用多個資料庫時,您可以使用 allow_migrate() 方法的 資料庫路由器 來控制 makemigrations 檢查哪個資料庫以取得一致的歷史記錄。

將遷移新增到應用程式

新的應用程式會預先配置為接受遷移,因此您可以在進行一些變更後執行 makemigrations 來新增遷移。

如果您的應用程式已經有模型和資料庫表,但還沒有遷移(例如,您是針對以前的 Django 版本建立的),您需要透過執行以下命令將其轉換為使用遷移

$ python manage.py makemigrations your_app_label

這會為您的應用程式建立一個新的初始遷移。現在,執行 python manage.py migrate --fake-initial,Django 會偵測到您有一個初始遷移,而且它想要建立的資料表已存在,並將遷移標記為已應用。(如果沒有 migrate --fake-initial 標誌,該命令會因為它想要建立的資料表已存在而錯誤退出。)

請注意,這僅在滿足以下兩個條件的情況下才有效

  • 自從您建立資料表以來,您沒有變更您的模型。為了使遷移正常運作,您必須建立初始遷移,然後再進行變更,因為 Django 會將變更與遷移檔案進行比較,而不是資料庫。

  • 您沒有手動編輯您的資料庫 - Django 將無法偵測到您的資料庫與您的模型不符,您只會在遷移嘗試修改這些資料表時收到錯誤。

反轉遷移

可以透過傳遞上一個遷移的編號,使用 migrate 反轉遷移。例如,要反轉遷移 books.0003

$ python manage.py migrate books 0002
Operations to perform:
  Target specific migration: 0002_auto, from books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0003_auto... OK
...\> py manage.py migrate books 0002
Operations to perform:
  Target specific migration: 0002_auto, from books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0003_auto... OK

如果要反轉應用程式的所有已應用遷移,請使用名稱 zero

$ python manage.py migrate books zero
Operations to perform:
  Unapply all migrations: books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0002_auto... OK
  Unapplying books.0001_initial... OK
...\> py manage.py migrate books zero
Operations to perform:
  Unapply all migrations: books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0002_auto... OK
  Unapplying books.0001_initial... OK

如果遷移包含任何不可逆的操作,則該遷移是不可逆的。嘗試反轉此類遷移將會引發 IrreversibleError

$ python manage.py migrate books 0002
Operations to perform:
  Target specific migration: 0002_auto, from books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0003_auto...Traceback (most recent call last):
django.db.migrations.exceptions.IrreversibleError: Operation <RunSQL  sql='DROP TABLE demo_books'> in books.0003_auto is not reversible
...\> py manage.py migrate books 0002
Operations to perform:
  Target specific migration: 0002_auto, from books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0003_auto...Traceback (most recent call last):
django.db.migrations.exceptions.IrreversibleError: Operation <RunSQL  sql='DROP TABLE demo_books'> in books.0003_auto is not reversible

歷史模型

當您執行遷移時,Django 會根據儲存在遷移檔案中的模型的歷史版本來運作。如果您使用 RunPython 操作編寫 Python 程式碼,或者您的資料庫路由器上有 allow_migrate 方法,您需要使用這些歷史模型版本,而不是直接匯入它們。

警告

如果您直接匯入模型而不是使用歷史模型,您的遷移最初可能會正常運作,但在未來當您嘗試重新執行舊的遷移時(通常,當您設定新安裝並執行所有遷移以設定資料庫時)將會失敗。

這表示歷史模型問題可能不會立即顯現出來。如果您遇到這類失敗,可以編輯遷移以使用歷史模型而不是直接匯入,並提交這些變更。

由於無法序列化任意的 Python 程式碼,這些歷史模型不會包含您定義的任何自訂方法。然而,它們會具有相同的欄位、關聯、管理員(僅限於具有 use_in_migrations = True 的管理員)和 Meta 選項(也會進行版本控制,因此它們可能與您目前的選項不同)。

警告

這表示當您在遷移中存取物件時,將不會呼叫自訂的 save() 方法,而且您將不會有任何自訂的建構函式或實例方法。請妥善規劃!

在欄位選項(例如 upload_tolimit_choices_to)中以及具有 use_in_migrations = True 的管理員的模型管理員宣告中對函式的參照,都會在遷移中進行序列化,因此只要有遷移參照它們,就需要保留這些函式和類別。任何自訂模型欄位也需要保留,因為這些欄位是由遷移直接匯入的。

此外,模型的具體基底類別會儲存為指標,因此只要有遷移包含對它們的參照,您就必須始終保留基底類別。好處是,這些基底類別的方法和管理員會正常繼承,因此如果您絕對需要存取這些方法和管理員,您可以選擇將它們移至超類別中。

若要移除舊的參照,您可以壓縮遷移,或者,如果參照不多,則可以將它們複製到遷移檔案中。

移除模型欄位時的考量

與上一節中描述的「參照歷史函式」的考量類似,如果您從專案或協力廠商應用程式中移除自訂模型欄位,若這些欄位在舊遷移中被參照,則會導致問題。

為了協助解決此情況,Django 提供了一些模型欄位屬性,以利用系統檢查框架來協助模型欄位的棄用。

system_check_deprecated_details 屬性新增至您的模型欄位,如下所示

class IPAddressField(Field):
    system_check_deprecated_details = {
        "msg": (
            "IPAddressField has been deprecated. Support for it (except "
            "in historical migrations) will be removed in Django 1.9."
        ),
        "hint": "Use GenericIPAddressField instead.",  # optional
        "id": "fields.W900",  # pick a unique ID for your field.
    }

在您選擇的棄用期間(Django 本身欄位的兩個或三個功能版本)之後,將 system_check_deprecated_details 屬性變更為 system_check_removed_details,並更新字典,如下所示

class IPAddressField(Field):
    system_check_removed_details = {
        "msg": (
            "IPAddressField has been removed except for support in "
            "historical migrations."
        ),
        "hint": "Use GenericIPAddressField instead.",
        "id": "fields.E900",  # pick a unique ID for your field.
    }

您應該保留欄位在資料庫遷移中運作所需的方法,例如 __init__()deconstruct()get_internal_type()。只要存在任何參照該欄位的遷移,請保留此存根欄位。例如,在壓縮遷移並移除舊遷移後,您應該可以完全移除該欄位。

資料遷移

除了變更資料庫綱要之外,您也可以使用遷移來變更資料庫中的資料本身,如果需要,也可以與綱要結合使用。

變更資料的遷移通常稱為「資料遷移」;最好將它們寫成單獨的遷移,與您的綱要遷移並列。

Django 無法像處理綱要遷移一樣自動為您產生資料遷移,但編寫它們並不難。Django 中的遷移檔案是由操作組成,而您用於資料遷移的主要操作是RunPython

首先,建立一個您可以從中工作的空遷移檔案(Django 會將該檔案放在正確的位置,建議一個名稱,並為您新增相依性)

python manage.py makemigrations --empty yourappname

然後,開啟該檔案;它應該看起來像這樣

# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations


class Migration(migrations.Migration):
    dependencies = [
        ("yourappname", "0001_initial"),
    ]

    operations = []

現在,您只需要建立一個新函式,並讓RunPython使用它。RunPython期望一個可呼叫物件作為其引數,該可呼叫物件接受兩個引數 - 第一個是應用程式註冊表,其中已載入所有模型的歷史版本,以符合遷移在您的歷史記錄中所處的位置,第二個是SchemaEditor,您可以使用它來手動執行資料庫綱要變更(但請注意,這樣做可能會混淆遷移自動偵測器!)

讓我們編寫一個遷移,該遷移會使用 first_namelast_name 的組合值來填入我們新的 name 欄位(我們已經清醒地意識到並非所有人都有名字和姓氏)。我們只需要使用歷史模型並迭代各行

from django.db import migrations


def combine_names(apps, schema_editor):
    # We can't import the Person model directly as it may be a newer
    # version than this migration expects. We use the historical version.
    Person = apps.get_model("yourappname", "Person")
    for person in Person.objects.all():
        person.name = f"{person.first_name} {person.last_name}"
        person.save()


class Migration(migrations.Migration):
    dependencies = [
        ("yourappname", "0001_initial"),
    ]

    operations = [
        migrations.RunPython(combine_names),
    ]

完成後,我們可以照常執行 python manage.py migrate,並且資料遷移將與其他遷移一起執行。

您可以將第二個可呼叫物件傳遞給RunPython,以執行您希望在向後遷移時執行的任何邏輯。如果省略此可呼叫物件,則向後遷移會引發例外。

從其他應用程式存取模型

當編寫使用遷移所在應用程式以外的應用程式中的模型的 RunPython 函式時,遷移的 dependencies 屬性應包含每個相關應用程式的最新遷移,否則當您嘗試使用 apps.get_model()RunPython 函式中檢索模型時,可能會收到類似以下的錯誤:LookupError: No installed app with label 'myappname'

在以下範例中,我們在 app1 中有一個遷移,需要使用 app2 中的模型。我們不關心 move_m1 的詳細資訊,除了它需要存取兩個應用程式中的模型之外。因此,我們新增了一個相依性,指定了 app2 的最後一個遷移

class Migration(migrations.Migration):
    dependencies = [
        ("app1", "0001_initial"),
        # added dependency to enable using models from app2 in move_m1
        ("app2", "0004_foobar"),
    ]

    operations = [
        migrations.RunPython(move_m1),
    ]

更進階的遷移

如果您對更進階的遷移操作感興趣,或者想要能夠編寫自己的遷移操作,請參閱遷移操作參考和有關編寫遷移的「操作指南」。

壓縮遷移

建議您隨意進行遷移,而無需擔心有多少個遷移;遷移程式碼經過最佳化處理,可以一次處理數百個遷移,而不會有太大的速度下降。但是,最終您會希望從數百個遷移縮減為少數幾個遷移,這就是壓縮的用武之地。

壓縮是指將現有的多個遷移縮減為一個(或有時是幾個)仍然表示相同變更的遷移。

Django 通過取得您所有現有的遷移、提取它們的 Operation,然後將它們按順序排列,然後在其上執行最佳化工具以嘗試縮短列表的長度來執行此操作 - 例如,它知道 CreateModelDeleteModel 相互抵消,並且它知道 AddField 可以捲入 CreateModel 中。

一旦操作順序被盡可能縮減(可能縮減的量取決於您的模型之間的緊密程度,以及您是否有任何 RunSQLRunPython 操作(除非它們被標記為 elidable,否則無法透過它們進行最佳化)),Django 會將其寫回一組新的遷移檔案中。

這些檔案會被標記為取代先前壓縮的遷移檔案,因此它們可以與舊的遷移檔案共存,而 Django 會根據您在歷史記錄中的位置,智能地在它們之間切換。如果您仍在進行壓縮的遷移集合中,它會繼續使用它們,直到到達結尾,然後切換到壓縮後的歷史記錄;而新的安裝將會使用新的壓縮遷移,並跳過所有舊的遷移。

這使您能夠進行壓縮,而不會干擾目前在生產環境中尚未完全更新的系統。建議的流程是先壓縮,保留舊檔案,提交並發布,等待所有系統都使用新版本升級(或者如果您是第三方專案,請確保您的使用者依序升級版本,不要跳過任何版本),然後刪除舊檔案,提交並進行第二次發布。

支援這一切的命令是 squashmigrations - 傳遞您要壓縮的應用程式標籤和遷移名稱,它就會開始工作。

$ ./manage.py squashmigrations myapp 0004
Will squash the following migrations:
 - 0001_initial
 - 0002_some_change
 - 0003_another_change
 - 0004_undo_something
Do you wish to proceed? [y/N] y
Optimizing...
  Optimized from 12 operations to 7 operations.
Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001_squashed_0004_undo_something.py
  You should commit this migration but leave the old ones in place;
  the new migration will be used for new installs. Once you are sure
  all instances of the codebase have applied the migrations you squashed,
  you can delete them.

如果您想要設定壓縮遷移的名稱,而不是使用自動產生的名稱,請使用 squashmigrations --squashed-name 選項。

請注意,Django 中的模型相互依賴關係可能變得非常複雜,壓縮可能會導致無法運行的遷移;可能是優化錯誤(在這種情況下,您可以嘗試使用 --no-optimize 再次嘗試,但您也應該報告問題),或者出現 CircularDependencyError,在這種情況下,您可以手動解決它。

若要手動解決 CircularDependencyError,請將循環依賴迴圈中的一個 ForeignKey 分解到一個單獨的遷移中,並將對其他應用程式的依賴關係與其一起移動。如果您不確定,請參閱當被要求從您的模型建立全新的遷移時,makemigrations 如何處理這個問題。在 Django 的未來版本中,將會更新 squashmigrations 以嘗試自行解決這些錯誤。

一旦您壓縮了遷移,您應該將它與它所取代的遷移一起提交,並將此變更分發到您應用程式的所有執行個體,確保它們執行 migrate 以將變更儲存在它們的資料庫中。

然後您必須將壓縮的遷移轉換為正常的遷移,方法是

  • 刪除它所取代的所有遷移檔案。

  • 更新所有依賴已刪除遷移的遷移,使其改為依賴壓縮的遷移。

  • 移除壓縮遷移的 Migration 類別中的 replaces 屬性(這是 Django 判斷它是否為壓縮遷移的方式)。

注意

一旦您壓縮了遷移,在您將其完全轉換為正常遷移之前,您不應再重新壓縮該壓縮的遷移。

修剪對已刪除遷移的參考

如果未來您可能會重複使用已刪除遷移的名稱,您應該使用 migrate --prune 選項,從 Django 的遷移表格中移除對它的參考。

序列化值

遷移是包含您模型舊定義的 Python 檔案 - 因此,為了寫入它們,Django 必須取得您模型的目前狀態,並將它們序列化到檔案中。

雖然 Django 可以序列化大多數東西,但有些東西我們無法將其序列化為有效的 Python 表示形式 - 沒有 Python 標準說明如何將值轉換回程式碼(repr() 僅適用於基本值,並且沒有指定導入路徑)。

Django 可以序列化以下內容

  • intfloatboolstrbytesNoneNoneType

  • listsettupledictrange

  • datetime.datedatetime.timedatetime.datetime 實例(包括那些具有時區意識的實例)

  • decimal.Decimal 實例

  • enum.Enumenum.Flag 實例

  • uuid.UUID 實例

  • 具有可序列化 funcargskeywords 值的 functools.partial()functools.partialmethod 實例。

  • 來自 pathlib 的純粹和具體路徑物件。具體路徑會轉換為它們的純粹路徑等效項,例如 pathlib.PosixPath 轉換為 pathlib.PurePosixPath

  • os.PathLike 實例,例如 os.DirEntry,它們會使用 os.fspath() 轉換為 strbytes

  • 封裝可序列化值的 LazyObject 實例。

  • 列舉類型(例如 TextChoicesIntegerChoices)的實例。

  • 任何 Django 欄位

  • 任何函式或方法參考(例如 datetime.datetime.today)(必須在模組的頂層範圍內)

  • 從類別主體內使用的未綁定方法

  • 任何類別參考(必須在模組的頂層範圍內)

  • 任何具有自訂 deconstruct() 方法的項目(請參閱下方

在 Django 5.0 中變更

新增了對使用 functools.cache()functools.lru_cache() 裝飾的函式的序列化支援。

Django 無法序列化

  • 巢狀類別

  • 任意類別實例(例如 MyClass(4.3, 5.7)

  • Lambda 函式

自訂序列化器

您可以透過撰寫自訂序列化器來序列化其他類型。例如,如果 Django 預設不序列化 Decimal,您可以執行以下操作

from decimal import Decimal

from django.db.migrations.serializer import BaseSerializer
from django.db.migrations.writer import MigrationWriter


class DecimalSerializer(BaseSerializer):
    def serialize(self):
        return repr(self.value), {"from decimal import Decimal"}


MigrationWriter.register_serializer(Decimal, DecimalSerializer)

MigrationWriter.register_serializer() 的第一個引數是一個類型或可迭代的類型,應使用該序列化器。

您的序列化器的 serialize() 方法必須傳回一個字串,說明該值在遷移中應如何顯示,以及遷移中需要的任何導入集合。

新增 deconstruct() 方法

您可以透過為類別提供 deconstruct() 方法,讓 Django 序列化您自己的自訂類別實例。它不接受任何引數,應傳回一個包含三個項目的元組 (path, args, kwargs)

  • path 應該是該類別的 Python 路徑,類別名稱包含為最後一部分(例如,myapp.custom_things.MyClass)。如果您的類別在模組的頂層不可用,則它是不可序列化的。

  • args 應該是一個位置引數列表,傳遞給您類別的 __init__ 方法。此清單中的所有內容本身都應該是可序列化的。

  • kwargs 應該是一個字典,其中包含要傳遞給類別 __init__ 方法的關鍵字參數。每個值本身都應該是可序列化的。

注意

這個回傳值與 deconstruct() 方法(自訂欄位的)不同,後者回傳包含四個元素的元組。

Django 會將該值寫成使用給定參數實例化的類別,類似於寫出對 Django 欄位的參照方式。

為了避免每次執行 makemigrations 時都建立新的遷移,您還應該在裝飾的類別中加入一個 __eq__() 方法。這個函式將由 Django 的遷移框架呼叫,以偵測狀態之間的變更。

只要您的類別建構子的所有參數本身都是可序列化的,您就可以使用來自 django.utils.deconstruct@deconstructible 類別裝飾器來新增 deconstruct() 方法。

from django.utils.deconstruct import deconstructible


@deconstructible
class MyCustomClass:
    def __init__(self, foo=1):
        self.foo = foo
        ...

    def __eq__(self, other):
        return self.foo == other.foo

裝飾器會加入邏輯來捕獲和保留傳入建構子的參數,然後在呼叫 deconstruct() 時,原封不動地傳回這些參數。

支援多個 Django 版本

如果您是具有模型的第三方應用程式的維護者,您可能需要發布支援多個 Django 版本的遷移。在這種情況下,您應該 **使用您希望支援的最低 Django 版本** 執行 makemigrations

遷移系統將根據與 Django 其餘部分相同的策略來維持向後相容性,因此在 Django X.Y 上產生的遷移檔案應該在 Django X.Y+1 上保持不變地執行。但是,遷移系統不保證向前相容性。可能會新增新功能,並且使用較新版本的 Django 產生的遷移檔案可能無法在較舊的版本上執行。

另請參閱

遷移操作參考

涵蓋綱要操作 API、特殊操作以及撰寫您自己的操作。

撰寫遷移「操作指南」

說明如何針對您可能遇到的不同情境,組織和撰寫資料庫遷移。

返回頂部