如何將 Django 模型移動到另一個應用程序(如何將pdf轉為word使用)
目錄
示例案例:將 django 模型移動到另一個應用程序
設置:準備您的環境
設置 Python 虛擬環境
創建一個 django 項目
創建 Django 應用程序
生成并應用初始遷移
生成樣本數據
漫長的道路:將數據復制到新的 Django 模型
創建新模型
將數據復制到新模型
更新新模型的外鍵
刪除舊模型
獎勵:逆轉遷移
處理特殊情況
總結:復制數據的利弊
捷徑:將新的 Django 模型引用到舊表
創建新模型
消除對數據庫的更改
獎勵:對新模型進行更改
總結:更改模型參考的利弊
Django 方式:重命名表
創建新模型
重命名舊表
獎勵:了解內省
總結:重命名表的利弊
指南:選擇最佳方法
結論
如果您曾經考慮過重構Django 應用程序,那么您可能會發現自己需要移動 Django 模型。有幾種方法可以使用 Django?migrations將 Django 模型從一個應用程序移動到另一個應用程序,但不幸的是,它們都不是直接的。
在Django 應用程序之間移動模型通常是一項非常復雜的任務,涉及復制數據、更改約束和重命名對象。由于這些復雜性,Django對象關系映射器 (ORM)不提供可以檢測和自動化整個過程的內置遷移操作。相反,ORM 提供了一組低級遷移操作,允許 Django 開發人員在遷移框架中自己實現流程。
在本教程中,您將學習:
如何將 Django 模型從一個應用程序移動到另一個應用程序
如何使用高級功能Django的遷移命令行界面(CLI),例如sqlmigrate,showmigrations和sqlsequencereset
如何制作和檢查遷移計劃
如何使遷移可逆以及如何逆向遷移
什么是自省以及 Django 如何在遷移中使用它
完成本教程后,您將能夠根據您的特定用例選擇將 Django 模型從一個應用程序移動到另一個應用程序的最佳方法。
示例案例:將 Django 模型移動到另一個應用程序
在本教程中,您將在商店應用程序上工作。您的商店將從兩個 Django 應用程序開始:
catalog:此應用程序用于存儲有關產品和產品類別的數據。
sale:此應用程序用于記錄和跟蹤產品銷售。
完成這兩個應用程序的設置后,您要將一個名為 Django 的模型移動Product到一個名為的新應用程序中product。在此過程中,您將面臨以下挑戰:
被移動的模型與其他模型具有外鍵關系。
其他模型與被移動的模型具有外鍵關系。
被移動的模型在其中一個字段上有一個索引(除了主鍵)。
這些挑戰的靈感來自現實生活中的重構過程。克服這些問題后,您就可以為您的特定用例規劃類似的遷移過程了。
設置:準備您的環境
在開始移動之前,您需要設置項目的初始狀態。本教程使用在 Python 3.8 上運行的 Django 3,但您可以在其他版本中使用類似的技術。
設置 Python 虛擬環境
首先,在新目錄中創建您的虛擬環境:
$ mkdir django-move-model-experiment $ cd django-move-model-experiment $ python -m venv venv
有關創建虛擬環境的分步說明,請查看Python 虛擬環境:入門。
創建一個 Django 項目
在您的終端中,激活虛擬環境并安裝 Django:
$ source venv/bin/activate $ pip install django Collecting django Collecting pytz (from django) Collecting asgiref~=3.2 (from django) Collecting sqlparse>=0.2.2 (from django) Installing collected packages: pytz, asgiref, sqlparse, django Successfully installed asgiref-3.2.3 django-3.0.4 pytz-2019.3 sqlparse-0.3.1
您現在已準備好創建 Django 項目。使用django-admin startproject創建了一個名為django-move-model-experiment:
$ django-admin startproject django-move-model-experiment $ cd django-move-model-experiment
運行此命令后,您將看到 Django 創建了新文件和目錄。有關如何啟動新 Django 項目的更多信息,請查看啟動 Django 項目。
創建 Django 應用程序
現在你有了一個新的 Django 項目,用你商店的產品目錄創建一個應用程序:
$ python manage.py startapp catalog
接下來,將以下模型添加到新catalog應用程序:
# catalog/models.py from django.db import models class Category(models.Model): name = models.CharField(max_length=100) class Product(models.Model): name = models.CharField(max_length=100, db_index=True) category = models.ForeignKey(Category, on_delete=models.CASCADE)
您已成功在您的應用程序中創建Category和Product建模catalog。現在您有了目錄,您想開始銷售您的產品。創建另一個用于銷售的應用程序:
$ python manage.py startapp sale
將以下Sale模型添加到新sale應用程序:
# sale/models.py from django.db import models from catalog.models import Product class Sale(models.Model): created = models.DateTimeField() product = models.ForeignKey(Product, on_delete=models.PROTECT)
請注意,Sale模型引用的Product模型使用ForeignKey。
生成并應用初始遷移
要完成設置,請生成遷移并應用它們:
$ python manage.py makemigrations catalog sale Migrations for 'catalog': catalog/migrations/0001_initial.py - Create model Category - Create model Product Migrations for 'sale': sale/migrations/0001_initial.py - Create model Sale $ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, catalog, contenttypes, sale, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying catalog.0001_initial... OK Applying sale.0001_initial... OK Applying sessions.0001_initial... OK
有關 Django 遷移的更多信息,請查看Django 遷移:入門。遷移完成后,您現在可以創建一些示例數據了!
生成樣本數據
為了使遷移場景盡可能真實,請從終端窗口激活Django shell:
$ python manage.py shell
接下來,創建以下對象:
>>>
>>> from catalog.models import Category, Product >>> clothes = Category.objects.create(name='Clothes') >>> shoes = Category.objects.create(name='Shoes') >>> Product.objects.create(name='Pants', category=clothes) >>> Product.objects.create(name='Shirt', category=clothes) >>> Product.objects.create(name='Boots', category=shoes)
您創建了兩個類別,'Shoes'并且'Clothes'.?接下來,您將兩種產品'Pants'和'Shirt',添加到'Clothes'類別中,將一種產品'Boots', 添加到'Shoes'類別中。
恭喜!您已完成項目初始狀態的設置。在現實生活中,這是您開始計劃重構的地方。本教程中介紹的三種方法中的每一種都將從這一點開始。
漫長的道路:將數據復制到新的 Django 模型
要開始工作,您將走很長的路:
創建新模型
將數據復制到它
放下舊桌子
這種方法有一些你應該注意的陷阱。您將在以下部分詳細探索它們。
創建新模型
首先創建一個新的product應用程序。從終端執行以下命令:
$ python manage.py startapp product
運行此命令后,您會注意到一個名為的新目錄product已添加到項目中。
要將新應用程序注冊到現有的 Django 項目,請將其添加到INSTALLED_APPSDjango 的列表中settings.py:
--- a/store/store/settings.py +++ b/store/store/settings.py @@ -40,6 +40,7 @@ INSTALLED_APPS = [ 'catalog', 'sale', + 'product', ] MIDDLEWARE = [
您的新product應用現已注冊到 Django。接下來,Product在新product應用中創建一個模型。您可以從catalog應用程序復制代碼:
# product/models.py from django.db import models from catalog.models import Category class Product(models.Model): name = models.CharField(max_length=100, db_index=True) category = models.ForeignKey(Category, on_delete=models.CASCADE)
現在您已經定義了模型,嘗試為它生成遷移:
$ python manage.py makemigrations product SystemCheckError: System check identified some issues: ERRORS: catalog.Product.category: (fields.E304) Reverse accessor for 'Product.category' clashes with reverse accessor for 'Product.category'. HINT: Add or change a related_name argument to the definition for 'Product.category' or 'Product.category'. product.Product.category: (fields.E304) Reverse accessor for 'Product.category' clashes with reverse accessor for 'Product.category'. HINT: Add or change a related_name argument to the definition for 'Product.category' or 'Product.category'.
該錯誤表示 Django 發現兩個模型具有相同的字段反向訪問器category。這是因為有兩個命名Product的Category模型引用了該模型,從而產生了沖突。
當您向模型添加外鍵時,Django 會在相關模型中創建一個反向訪問器。在這種情況下,反向訪問器是products。反向訪問,您可以訪問相關的對象是這樣的:category.products。
新模型是您想要保留的模型,因此要解決此沖突,請從 中的舊模型中刪除反向訪問器catalog/models.py:
--- a/store/catalog/models.py +++ b/store/catalog/models.py @@ -7,4 +7,4 @@ class Category(models.Model): class Product(models.Model): name = models.CharField(max_length=100, db_index=True) - category = models.ForeignKey(Category, on_delete=models.CASCADE) + category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='+')
該屬性related_name可用于顯式設置反向訪問器的相關名稱。在這里,您使用特殊值+,它指示 Django 不要創建反向訪問器。
現在為catalog應用程序生成遷移:
$ python manage.py makemigrations catalog Migrations for 'catalog': catalog/migrations/0002_auto_20200124_1250.py - Alter field category on product
暫時不要應用此遷移!一旦發生這種變化,使用反向訪問器的代碼可能會中斷。
現在反向訪問器之間沒有沖突,嘗試為新product應用程序生成遷移:
$ python manage.py makemigrations product Migrations for 'product': product/migrations/0001_initial.py - Create model Product
偉大的!您已準備好進入下一步。
將數據復制到新模型
在上一步中,您創建了一個新product應用程序,其Product模型與您要移動的模型相同。下一步是將數據從舊模型移動到新模型。
要創建數據遷移,請從終端執行以下命令:
$ python manage.py makemigrations product --empty Migrations for 'product': product/migrations/0002_auto_20200124_1300.py
編輯新的遷移文件,并添加從舊表復制數據的操作:
from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('product', '0001_initial'), ] operations = [ migrations.RunSQL(""" INSERT INTO product_product ( id, name, category_id ) SELECT id, name, category_id FROM catalog_product; """, reverse_sql=""" INSERT INTO catalog_product ( id, name, category_id ) SELECT id, name, category_id FROM product_product; """) ]
要在遷移中執行 SQL,請使用特殊的RunSQL遷移命令。第一個參數是要應用的 SQL。您還可以使用reverse_sql參數提供一個操作來逆轉遷移。
當您發現錯誤并想要回滾更改時,反向遷移會派上用場。大多數內置遷移操作都可以撤消。例如,添加字段的反向操作是刪除該字段。創建新表的相反操作是刪除該表。通常最好提供reverse_SQL給,RunSQL以便在出現問題時可以回溯。
在這種情況下,正向遷移操作會將數據從product_product插入到 中catalog_product。向后操作將執行完全相反的操作,將數據從catalog_productinto 插入product_product。通過向 Django 提供反向操作,您將能夠在發生災難時反向遷移。
此時,您仍處于遷移過程的一半。但是這里有一個教訓要學習,所以繼續應用遷移:
$ python manage.py migrate product Operations to perform: Apply all migrations: product Running migrations: Applying product.0001_initial... OK Applying product.0002_auto_20200124_1300... OK
在繼續下一步之前,請嘗試創建一個新產品:
>>>
>>> from product.models import Product >>> Product.objects.create(name='Fancy Boots', category_id=2) Traceback (most recent call last): File "/venv/lib/python3.8/site-packages/django/db/backends/utils.py", line 86, in _execute return self.cursor.execute(sql, params) psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "product_product_pkey" DETAIL: Key (id)=(1) already exists.
當您使用自動遞增的主鍵時,Django在數據庫中創建一個序列來為新對象分配唯一標識符。請注意,例如,您沒有提供新產品的 ID。您通常不想提供 ID,因為您希望數據庫使用序列為您分配主鍵。但是,在這種情況下,新表為新產品提供了 ID,1即使該 ID 已存在于表中。
那么,出了什么問題?當您將數據復制到新表時,您沒有同步序列。要同步序列,您可以使用另一個名為sqlsequencereset.?該命令生成一個腳本,用于根據表中的現有數據設置序列的當前值。此命令通常用于使用預先存在的數據填充新模型。
使用sqlsequencereset產生腳本同步序列:
$ python manage.py sqlsequencereset product BEGIN; SELECT setval(pg_get_serial_sequence('"product_product"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "product_product"; COMMIT;
該命令生成的腳本是特定于數據庫的。在這種情況下,數據庫是 PostgreSQL。該腳本將序列的當前值設置為序列應產生的下一個值,即表中的最大 ID 加一。
最后,將代碼片段添加到數據遷移中:
--- a/store/product/migrations/0002_auto_20200124_1300.py +++ b/store/product/migrations/0002_auto_20200124_1300.py @@ -22,6 +22,8 @@ class Migration(migrations.Migration): category_id FROM catalog_product; + + SELECT setval(pg_get_serial_sequence('"product_product"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "product_product"; """, reverse_sql=""" INSERT INTO catalog_product ( id,
該代碼段將在您應用遷移時同步序列,從而解決您在上面遇到的序列問題。
這種了解同步序列的繞道在您的代碼中造成了一些混亂。要清理它,請從 Django shell 中刪除新模型中的數據:
>>>
>>> from product.models import Product >>> Product.objects.all().delete() (3, {'product.Product': 3})
現在您復制的數據已被刪除,您可以反向遷移。要撤消遷移,請遷移到以前的遷移:
$ python manage.py showmigrations product product [X] 0001_initial [X] 0002_auto_20200124_1300 $ python manage.py migrate product 0001_initial Operations to perform: Target specific migration: 0001_initial, from product Running migrations: Rendering model states... DONE Unapplying product.0002_auto_20200124_1300... OK
您首先使用該命令showmigrations列出應用到應用程序的遷移product。輸出顯示兩個遷移都已應用。然后,您0002_auto_20200124_1300通過遷移到先前的遷移來逆轉遷移0001_initial。
如果showmigrations再次執行,您將看到第二次遷移不再標記為已應用:
$ python manage.py showmigrations product product [X] 0001_initial [ ] 0002_auto_20200124_1300
空框確認第二次遷移已被撤消。現在你有了一個干凈的石板,用新代碼運行遷移:
$ python manage.py migrate product Operations to perform: Apply all migrations: product Running migrations: Applying product.0002_auto_20200124_1300... OK
遷移已成功應用。確保您現在可以Product在 Django shell 中創建一個新的:
>>>
>>> from product.models import Product >>> Product.objects.create(name='Fancy Boots', category_id=2)
驚人的!您的辛勤工作得到了回報,您已準備好進行下一步。
更新新模型的外鍵
舊表當前有其他表使用ForeignKey字段引用它。在刪除舊模型之前,您需要更改引用舊模型的模型,以便它們引用新模型。
一個仍然引用舊模型的模型Sale在sale應用程序中。更改Sale模型中的外鍵以引用新Product模型:
--- a/store/sale/models.py +++ b/store/sale/models.py @@ -1,6 +1,6 @@ from django.db import models -from catalog.models import Product +from product.models import Product class Sale(models.Model): created = models.DateTimeField()
生成遷移并應用它:
$ python manage.py makemigrations sale Migrations for 'sale': sale/migrations/0002_auto_20200124_1343.py - Alter field product on sale $ python manage.py migrate sale Operations to perform: Apply all migrations: sale Running migrations: Applying sale.0002_auto_20200124_1343... OK
該Sale模型現在引用Product了product應用程序中的新模型。因為您已經將所有數據復制到新模型中,所以沒有違反約束。
刪除舊模型
上一步消除了對舊Product模型的所有引用。現在可以安全地從catalog應用程序中刪除舊模型:
--- a/store/catalog/models.py +++ b/store/catalog/models.py @@ -3,8 +3,3 @@ from django.db import models class Category(models.Model): name = models.CharField(max_length=100) - - -class Product(models.Model): - name = models.CharField(max_length=100, db_index=True) - category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='+')
生成遷移但尚未應用它:
$ python manage.py makemigrations Migrations for 'catalog': catalog/migrations/0003_delete_product.py - Delete model Product
為了確保只有在復制數據后才刪除舊模型,請添加以下依賴項:
--- a/store/catalog/migrations/0003_delete_product.py +++ b/store/catalog/migrations/0003_delete_product.py @@ -7,6 +7,7 @@ class Migration(migrations.Migration): dependencies = [ ('catalog', '0002_auto_20200124_1250'), + ('sale', '0002_auto_20200124_1343'), ] operations = [
添加此依賴項非常重要。跳過這一步可能會產生可怕的后果,包括丟失數據。有關遷移文件和遷移之間依賴關系的更多信息,請查看深入挖掘 Django 遷移。
注意:遷移的名稱包括它的生成日期和時間。如果您遵循自己的代碼,則名稱的這些部分將有所不同。
現在您已經添加了依賴項,請應用遷移:
$ python manage.py migrate catalog Operations to perform: Apply all migrations: catalog Running migrations: Applying catalog.0003_delete_product... OK
轉移現已完成!通過創建新模型并將數據處理到其中,您已成功地將Product模型從catalog應用程序移至新product應用程序。
獎勵:逆轉遷移
Django 遷移的好處之一是它們是可逆的。遷移可逆是什么意思?如果您犯了錯誤,那么您可以撤消遷移,數據庫將恢復到應用遷移之前的狀態。
還記得你之前是如何提供reverse_sql給的RunSQL嗎?嗯,這就是值得的地方。
在新數據庫上應用所有遷移:
$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, catalog, contenttypes, product, sale, sessions Running migrations: Applying product.0001_initial... OK Applying product.0002_auto_20200124_1300... OK Applying sale.0002_auto_20200124_1343... OK Applying catalog.0003_delete_product... OK
現在,使用特殊關鍵字zero反轉它們:
$ python manage.py migrate product zero Operations to perform: Unapply all migrations: product Running migrations: Rendering model states... DONE Unapplying catalog.0003_delete_product... OK Unapplying sale.0002_auto_20200124_1343... OK Unapplying product.0002_auto_20200124_1300... OK Unapplying product.0001_initial... OK
數據庫現在恢復到其原始狀態。如果你部署了這個版本,發現有錯誤,那么就可以逆向了!
處理特殊情況
當您將模型從一個應用程序移動到另一個應用程序時,某些 Django 功能可能需要特別注意。特別是,添加或修改數據庫約束以及使用泛型關系都需要格外小心。
向帶有數據的表添加約束可能是在實時系統上執行的危險操作。要添加約束,數據庫必須首先驗證它。在驗證期間,數據庫獲取表上的鎖,這可能會阻止其他操作,直到進程完成。
某些約束(例如NOT NULL和CHECK)可能需要對表進行全面掃描以驗證新數據是否有效。其他約束(例如FOREIGN KEY)需要使用另一個表進行驗證,這可能需要一些時間,具體取決于引用表的大小。
如果您使用的是通用關系,那么您可能需要一個額外的步驟。通用關系使用模型的主鍵和內容類型 ID 來引用任何模型表中的行。舊模型和新模型沒有相同的內容類型 ID,因此通用連接可能會中斷。這有時會被忽視,因為通用外鍵的完整性不是由數據庫強制執行的。
有兩種處理通用外鍵的方法:
將新模型的內容類型 ID 更新為舊模型的內容類型 ID。
將任何引用表的內容類型 ID 更新為新模型的內容類型 ID。
無論您選擇哪種方式,請確保在部署到生產之前對其進行正確測試。
總結:復制數據的利弊
通過復制數據將 Django 模型移動到另一個應用程序有其優點和缺點。以下是與此方法相關的一些優點:
它由 ORM 支持:使用內置遷移操作執行此轉換可確保正確的數據庫支持。
它是可逆的:如有必要,可以逆轉此遷移。
以下是與此方法相關的一些缺點:
它很慢:復制大量數據可能需要時間。
它需要停機:在將舊表復制到新表時更改舊表中的數據將導致轉換期間數據丟失。為了防止這種情況發生,停機是必要的。
它需要手動工作來同步數據庫:將數據加載到現有表需要同步序列和通用外鍵。
正如您將在以下部分中看到的,使用這種方法將 Django 模型移動到另一個應用程序比其他方法花費的時間要長得多。
捷徑:將新的 Django 模型引用到舊表
在前面的方法中,您將所有數據復制到新表中。遷移需要停機,并且可能需要很長時間才能完成,具體取決于要復制的數據量。
如果不是復制數據,而是更改新模型以引用舊表會怎樣?
創建新模型
這一次,您將一次對模型進行所有更改,然后讓 Django 生成所有遷移。
首先,Product從catalog應用程序中刪除模型:
--- a/store/catalog/models.py +++ b/store/catalog/models.py @@ -3,8 +3,3 @@ from django.db import models class Category(models.Model): name = models.CharField(max_length=100) - - -class Product(models.Model): - name = models.CharField(max_length=100, db_index=True) - category = models.ForeignKey(Category, on_delete=models.CASCADE)
您已從應用程序中刪除Product模型catalog。現在將Product模型移動到新product應用程序:
# store/product/models.py from django.db import models from catalog.models import Category class Product(models.Model): name = models.CharField(max_length=100, db_index=True) category = models.ForeignKey(Category, on_delete=models.CASCADE)
現在該Product模型存在于product應用程序中,您可以更改對舊Product模型的任何引用以引用新Product模型。在這種情況下,您需要將外鍵更改sale為引用product.Product:
--- a/store/sale/models.py +++ b/store/sale/models.py @@ -1,6 +1,6 @@ from django.db import models -from catalog.models import Product +from product.models import Product class Sale(models.Model): created = models.DateTimeField()
在繼續生成遷移之前,您需要對新Product模型再做一個小的更改:
--- a/store/product/models.py +++ b/store/product/models.py @@ -5,3 +5,6 @@ from catalog.models import Category class Product(models.Model): name = models.CharField(max_length=100, db_index=True) category = models.ForeignKey(Category, on_delete=models.CASCADE) + + class Meta: + db_table = 'catalog_product'
Django 模型有一個Meta名為db_table.?使用此選項,您可以提供一個表名來代替 Django 生成的表名。在表名與 Django 的命名約定不匹配的現有數據庫模式上設置 ORM 時,最常使用此選項。
在這種情況下,您在product應用程序中設置表的名稱以引用應用程序中的現有表catalog。
要完成設置,請生成遷移:
$ python manage.py makemigrations sale product catalog Migrations for 'catalog': catalog/migrations/0002_remove_product_category.py - Remove field category from product catalog/migrations/0003_delete_product.py - Delete model Product Migrations for 'product': product/migrations/0001_initial.py - Create model Product Migrations for 'sale': sale/migrations/0002_auto_20200104_0724.py - Alter field product on sale
在繼續之前,請使用標志生成遷移計劃:--plan
$ python manage.py migrate --plan Planned operations: catalog.0002_remove_product_category Remove field category from product product.0001_initial Create model Product sale.0002_auto_20200104_0724 Alter field product on sale catalog.0003_delete_product Delete model Product
命令的輸出列出了 Django 將應用遷移的順序。
消除對數據庫的更改
這種方法的主要好處是您實際上不對數據庫進行任何更改,只對代碼進行了更改。要消除對數據庫的更改,您可以使用特殊的遷移操作SeparateDatabaseAndState。
SeparateDatabaseAndState可用于修改 Django 在遷移期間執行的操作。有關如何使用的更多信息SeparateDatabaseAndState,請查看如何在不停機的情況下在 Django 中創建索引。
如果您查看 Django 生成的遷移內容,您會看到 Django 創建了一個新模型并刪除了舊模型。如果執行這些遷移,那么數據將丟失,并且表將創建為空。為避免這種情況,您需要確保 Django 在遷移期間不會對數據庫進行任何更改。
您可以通過將每個遷移操作包裝在一個SeparateDatabaseAndState操作中來消除對數據庫的更改。要告訴 Django 不要對數據庫應用任何更改,您可以設置db_operations為空列表。
您計劃重用舊表,因此您需要防止 Django 丟棄它。在刪除模型之前,Django 將刪除引用模型的字段。因此,首先,防止 Django 將外鍵從saleto 中刪除product:
--- a/store/catalog/migrations/0002_remove_product_category.py +++ b/store/catalog/migrations/0002_remove_product_category.py @@ -10,8 +10,14 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RemoveField( - model_name='product', - name='category', + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name='product', + name='category', + ), + ], + # You're reusing the table, so don't drop it + database_operations=[], ), ]
現在 Django 已經處理了相關對象,它可以刪除模型。你想保留Product表,所以防止 Django 刪除它:
--- a/store/catalog/migrations/0003_delete_product.py +++ b/store/catalog/migrations/0003_delete_product.py @@ -11,7 +11,13 @@ class Migration(migrations.Migration): ] operations = [ - migrations.DeleteModel( - name='Product', - ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.DeleteModel( + name='Product', + ), + ], + # You want to reuse the table, so don't drop it + database_operations=[], + ) ]
您曾經database_operations=[]阻止 Django 刪除表。接下來,阻止 Django 創建新表:
--- a/store/product/migrations/0001_initial.py +++ b/store/product/migrations/0001_initial.py @@ -13,15 +13,21 @@ class Migration(migrations.Migration): ] operations = [ - migrations.CreateModel( - name='Product', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(db_index=True, max_length=100)), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catalog.Category')), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(db_index=True, max_length=100)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catalog.Category')), + ], + options={ + 'db_table': 'catalog_product', + }, + ), ], - options={ - 'db_table': 'catalog_product', - }, - ), + # You reference an existing table + database_operations=[], + ) ]
在這里,您曾經database_operations=[]阻止 Django 創建新表。最后,您希望防止 DjangoSale從新Product模型重新創建外鍵約束。由于您正在重用舊表,因此約束仍然存在:
--- a/store/sale/migrations/0002_auto_20200104_0724.py +++ b/store/sale/migrations/0002_auto_20200104_0724.py @@ -12,9 +12,14 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AlterField( - model_name='sale', - name='product', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='product.Product'), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.AlterField( + model_name='sale', + name='product', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='product.Product'), + ), + ], + database_operations=[], ), ]
現在您已完成對遷移文件的編輯,請應用遷移:
$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, catalog, contenttypes, product, sale, sessions Running migrations: Applying catalog.0002_remove_product_category... OK Applying product.0001_initial... OK Applying sale.0002_auto_20200104_0724... OK Applying catalog.0003_delete_product... OK
此時,您的新模型指向舊表。Django沒有對數據庫做任何改動,所有改動都是在代碼中對Django的模型狀態進行的。但是在您稱其為成功并繼續前進之前,值得確認新模型的狀態與數據庫的狀態相匹配。
獎勵:對新模型進行更改
為了確保模型的狀態與數據庫的狀態一致,嘗試對新模型進行更改,并確保 Django 正確檢測到它。
該Product模型在該name字段上定義了一個索引。刪除該索引:
--- a/store/product/models.py +++ b/store/product/models.py @@ -3,7 +3,7 @@ from django.db import models from catalog.models import Category class Product(models.Model): - name = models.CharField(max_length=100, db_index=True) + name = models.CharField(max_length=100) category = models.ForeignKey(Category, on_delete=models.CASCADE) class Meta:
您通過消除db_index=True.?接下來,生成遷移:
$ python manage.py makemigrations Migrations for 'product': product/migrations/0002_auto_20200104_0856.py - Alter field name on product
在繼續之前,檢查 Django 為這次遷移生成的 SQL:
$ python manage.py sqlmigrate product 0002 BEGIN; -- -- Alter field name on product -- DROP INDEX IF EXISTS "catalog_product_name_924af5bc"; DROP INDEX IF EXISTS "catalog_product_name_924af5bc_like"; COMMIT;
偉大的!Django 檢測到舊索引,如"catalog_*"前綴所示。現在您可以執行遷移:
$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, catalog, contenttypes, product, sale, sessions Running migrations: Applying product.0002_auto_20200104_0856... OK
確保您在數據庫中獲得了預期的結果:
django_migration_test=# \d catalog_product Table "public.catalog_product" Column | Type | Nullable | Default -------------+------------------------+----------+--------------------------------------------- id | integer | not null | nextval('catalog_product_id_seq'::regclass) name | character varying(100) | not null | category_id | integer | not null | Indexes: "catalog_product_pkey" PRIMARY KEY, btree (id) "catalog_product_category_id_35bf920b" btree (category_id) Foreign-key constraints: "catalog_product_category_id_35bf920b_fk_catalog_category_id" FOREIGN KEY (category_id) REFERENCES catalog_category(id) DEFERRABLE INITIALLY DEFERRED Referenced by: TABLE "sale_sale" CONSTRAINT "sale_sale_product_id_18508f6f_fk_catalog_product_id" FOREIGN KEY (product_id) REFERENCES catalog_product(id) DEFERRABLE INITIALLY DEFERRED
成功!name列上的索引已刪除。
總結:更改模型參考的利弊
更改模型以引用另一個模型有其優點和缺點。以下是與此方法相關的一些優點:
速度很快:這種方法不會對數據庫進行任何更改,因此速度非常快。
不需要停機:這種方法不需要復制數據,因此可以在實時系統上執行而無需停機。
它是可逆的:如有必要,可以逆轉此遷移。
它由 ORM 支持:使用內置遷移操作執行此轉換可確保正確的數據庫支持。
它不需要與數據庫進行任何同步:使用這種方法,相關對象(例如索引和序列)保持不變。
這種方法的唯一主要缺點是它打破了命名約定。使用現有表意味著該表仍將使用舊應用程序的名稱。
請注意,這種方法比復制數據要簡單得多。
Django 方式:重命名表
在前面的示例中,您使新模型引用了數據庫中的舊表。結果,您打破了 Django 使用的命名約定。在這種方法中,您執行相反的操作:您使舊表引用新模型。
更具體地說,您創建新模型并為其生成遷移。然后,您從 Django 創建的遷移中獲取新表的名稱,而不是為新模型創建表,而是使用特殊遷移操作將舊表重命名為新表的名稱AlterModelTable。
創建新模型
就像以前一樣,您首先要創建一個新product應用程序來一次性完成所有更改。首先,Product從catalog應用程序中刪除模型:
--- a/store/catalog/models.py +++ b/store/catalog/models.py @@ -3,8 +3,3 @@ from django.db import models class Category(models.Model): name = models.CharField(max_length=100) - - -class Product(models.Model): - name = models.CharField(max_length=100, db_index=True) - category = models.ForeignKey(Category, on_delete=models.CASCADE)
你已經Product從catalog.?接下來,將Product模型移動到一個新的product應用程序:
# store/product/models.py from django.db import models from catalog.models import Category class Product(models.Model): name = models.CharField(max_length=100, db_index=True) category = models.ForeignKey(Category, on_delete=models.CASCADE)
該Product模型現在存在于您的product應用程序中。現在將外鍵更改Sale為引用product.Product:
--- a/store/sale/models.py +++ b/store/sale/models.py @@ -1,6 +1,6 @@ from django.db import models -from catalog.models import Product +from product.models import Product class Sale(models.Model): created = models.DateTimeField() --- a/store/store/settings.py +++ b/store/store/settings.py @@ -40,6 +40,7 @@ INSTALLED_APPS = [ 'catalog', 'sale', + 'product', ]
接下來,讓 Django 為您生成遷移:
$ python manage.py makemigrations sale catalog product Migrations for 'catalog': catalog/migrations/0002_remove_product_category.py - Remove field category from product catalog/migrations/0003_delete_product.py - Delete model Product Migrations for 'product': product/migrations/0001_initial.py - Create model Product Migrations for 'sale': sale/migrations/0002_auto_20200110_1304.py - Alter field product on sale
您想阻止 Django 刪除該表,因為您打算重命名它。
要獲取應用程序中Product模型的名稱,請product為創建的遷移生成 SQL?Product:
$ python manage.py sqlmigrate product 0001 BEGIN; -- -- Create model Product -- CREATE TABLE "product_product" ("id" serial NOT NULL PRIMARY KEY, "name" varchar(100) NOT NULL, "category_id" integer NOT NULL); ALTER TABLE "product_product" ADD CONSTRAINT "product_product_category_id_0c725779_fk_catalog_category_id" FOREIGN KEY ("category_id") REFERENCES "catalog_category" ("id") DEFERRABLE INITIALLY DEFERRED; CREATE INDEX "product_product_name_04ac86ce" ON "product_product" ("name"); CREATE INDEX "product_product_name_04ac86ce_like" ON "product_product" ("name" varchar_pattern_ops); CREATE INDEX "product_product_category_id_0c725779" ON "product_product" ("category_id"); COMMIT;
Django 為應用程序中的Product模型生成的表的名稱product是product_product.
重命名舊表
既然您已經為模型生成了名稱 Django,您就可以重命名舊表了。為了Product從catalog應用程序中刪除模型,Django 創建了兩個遷移:
catalog/migrations/0002_remove_product_category?從表中刪除外鍵。
catalog/migrations/0003_delete_product?刪除模型。
在重命名表之前,您希望防止 Django 將外鍵刪除為Category:
--- a/store/catalog/migrations/0002_remove_product_category.py +++ b/store/catalog/migrations/0002_remove_product_category.py @@ -10,8 +10,13 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RemoveField( - model_name='product', - name='category', + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name='product', + name='category', + ), + ], + database_operations=[], ), ]
使用SeparateDatabaseAndStatewith?database_operationsset 為空列表可防止 Django 刪除該列。
Django 提供了一個特殊的遷移操作 ,AlterModelTable來重命名模型的表。編輯刪除舊表的遷移,并將表重命名為product_product:
--- a/store/catalog/migrations/0003_delete_product.py +++ b/store/catalog/migrations/0003_delete_product.py @@ -11,7 +11,17 @@ class Migration(migrations.Migration): ] operations = [ - migrations.DeleteModel( - name='Product', - ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.DeleteModel( + name='Product', + ), + ], + database_operations=[ + migrations.AlterModelTable( + name='Product', + table='product_product', + ), + ], + ) ]
您使用SeparateDatabaseAndStatewithAlterModelTable為 Django 提供不同的遷移操作以在數據庫中執行。
接下來,您需要阻止 Django 為新Product模型創建表。相反,您希望它使用您重命名的表。對product應用程序中的初始遷移進行以下更改:
--- a/store/product/migrations/0001_initial.py +++ b/store/product/migrations/0001_initial.py @@ -13,12 +13,18 @@ class Migration(migrations.Migration): ] operations = [ - migrations.CreateModel( - name='Product', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(db_index=True, max_length=100)), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catalog.Category')), - ], + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(db_index=True, max_length=100)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catalog.Category')), + ], + ), + ], + # Table already exists. See catalog/migrations/0003_delete_product.py + database_operations=[], ), ]
遷移會在 Django 的狀態下創建模型,但由于database_operations=[].?還記得您將舊表重命名為product_product嗎?通過將舊表重命名為 Django 為新模型生成的名稱,您可以強制 Django 使用舊表。
最后,您要防止 Django 在Sale模型中重新創建外鍵約束:
--- a/store/sale/migrations/0002_auto_20200110_1304.py +++ b/store/sale/migrations/0002_auto_20200110_1304.py @@ -12,9 +12,15 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AlterField( - model_name='sale', - name='product', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='product.Product'), - ), + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.AlterField( + model_name='sale', + name='product', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='product.Product'), + ), + ], + # You're reusing an existing table, so do nothing + database_operations=[], + ) ]
您現在已準備好運行遷移:
$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, catalog, contenttypes, product, sale, sessions Running migrations: Applying catalog.0002_remove_product_category... OK Applying product.0001_initial... OK Applying sale.0002_auto_20200110_1304... OK Applying catalog.0003_delete_product... OK
偉大的!遷移成功。但在繼續之前,請確保它可以逆轉:
$ python manage.py migrate catalog 0001 Operations to perform: Target specific migration: 0001_initial, from catalog Running migrations: Rendering model states... DONE Unapplying catalog.0003_delete_product... OK Unapplying sale.0002_auto_20200110_1304... OK Unapplying product.0001_initial... OK Unapplying catalog.0002_remove_product_category... OK
驚人的!遷移是完全可逆的。
注意:?由于一些原因,AlterModelTable通常最好使用RunSQL。
首先,AlterModelTable可以處理名稱基于模型名稱的字段之間的多對多關系。使用RunSQL重命名表可能需要一些額外的工作。
此外,內置遷移操作(例如AlterModelTable數據庫不可知)而RunSQL并非如此。例如,如果您的應用程序需要在多個數據庫引擎上運行,那么您在編寫與所有數據庫引擎兼容的 SQL 時可能會遇到一些麻煩。
獎勵:了解內省
Django ORM 是一個抽象層,它將 Python 類型轉換為數據庫表,反之亦然。例如,當您Product在product應用程序中創建模型時,Django 創建了一個名為product_product.?除了表之外,ORM 還創建其他數據庫對象,例如索引、約束、序列等。Django 根據應用程序和模型的名稱為所有這些對象制定了命名約定。
要更好地了解它的外觀,請檢查catalog_category數據庫中的表:
django_migration_test=# \d catalog_category Table "public.catalog_category" Column | Type | Nullable | Default --------+------------------------+----------+---------------------------------------------- id | integer | not null | nextval('catalog_category_id_seq'::regclass) name | character varying(100) | not null | Indexes: "catalog_category_pkey" PRIMARY KEY, btree (id)
該表是由 Django 為Category應用程序中的模型生成的catalog,因此名稱為catalog_category.?您還可以注意到其他數據庫對象的類似命名約定。
catalog_category_pkey?指主鍵索引。
catalog_category_id_seq指為主鍵字段生成值的序列id。
接下來,檢查Product您從中移動catalog到的模型的表product:
django_migration_test=# \d product_product Table "public.product_product" Column | Type | Nullable | Default -------------+------------------------+----------+--------------------------------------------- id | integer | not null | nextval('catalog_product_id_seq'::regclass) name | character varying(100) | not null | category_id | integer | not null | Indexes: "catalog_product_pkey" PRIMARY KEY, btree (id) "catalog_product_category_id_35bf920b" btree (category_id) "catalog_product_name_924af5bc" btree (name) "catalog_product_name_924af5bc_like" btree (name varchar_pattern_ops) Foreign-key constraints: "catalog_product_category_id_35bf920b_fk_catalog_category_id" FOREIGN KEY (category_id) REFERENCES catalog_category(id) DEFERRABLE INITIALLY DEFERRED
乍一看,相關的對象比較多。然而,仔細觀察會發現相關對象的名稱與表的名稱不一致。例如,表的名稱是product_product,但主鍵約束的名稱是catalog_product_pkey。您從名為 的應用程序復制了模型catalog,這意味著遷移操作AlterModelTable不會更改所有相關數據庫對象的名稱。
為了更好地了解AlterModelTable工作原理,請檢查此遷移操作生成的 SQL:
$ python manage.py sqlmigrate catalog 0003 BEGIN; -- -- Custom state/database change combination -- ALTER TABLE "catalog_product" RENAME TO "product_product"; COMMIT;
這表明AlterModelTable只重命名表。如果是這種情況,那么如果您嘗試更改與這些對象的表相關的數據庫對象之一,會發生什么情況?Django 能夠處理這些變化嗎?
為了找到答案,嘗試刪除賽場上的指數name在Product模型:
--- a/store/product/models.py +++ b/store/product/models.py @@ -3,5 +3,5 @@ from django.db import models from catalog.models import Category class Product(models.Model): - name = models.CharField(max_length=100, db_index=True) + name = models.CharField(max_length=100, db_index=False) category = models.ForeignKey(Category, on_delete=models.CASCADE)
接下來,生成遷移:
$ python manage.py makemigrations Migrations for 'product': product/migrations/0002_auto_20200110_1426.py - Alter field name on product
命令成功——這是一個好兆頭。現在檢查生成的 SQL:
$ python manage.py sqlmigrate product 0002 BEGIN; -- -- Alter field name on product -- DROP INDEX IF EXISTS "catalog_product_name_924af5bc"; DROP INDEX IF EXISTS "catalog_product_name_924af5bc_like"; COMMIT;
生成的 SQL 命令刪除索引catalog_product_name_924af5bc。Django 能夠檢測到現有索引,即使它與表名不一致。這稱為內省。
自省在 ORM 內部使用,因此您不會找到太多關于它的文檔。每個數據庫后端都包含一個內省模塊,可以根據其屬性識別數據庫對象。自省模塊通常會使用數據庫提供的元數據表。使用自省,ORM 可以在不依賴命名約定的情況下操作對象。這就是 Django 能夠檢測到要刪除的索引名稱的方式。
總結:重命名表的利弊
重命名表有其優點和缺點。以下是與此方法相關的一些優點:
速度很快:這種方法只重命名數據庫對象,所以速度非常快。
不需要停機:使用這種方法,數據庫對象在重命名時只會被鎖定一小段時間,因此可以在實時系統上執行而無需停機。
它是可逆的:如有必要,可以逆轉此遷移。
它由 ORM 支持:使用內置遷移操作執行此轉換可確保正確的數據庫支持。
與這種方法相關的唯一潛在缺點是它打破了命名約定。僅重命名表意味著其他數據庫對象的名稱將與 Django 的命名約定不一致。當直接使用數據庫時,這可能會引起一些混亂。但是,Django 仍然可以使用自省來識別和管理這些對象,因此這不是主要問題。
指南:選擇最佳方法
在本教程中,您學習了如何以三種不同的方式將 Django 模型從一個應用程序移動到另一個應用程序。以下是本教程中描述的方法的比較:
注意:上表建議重命名表保留 Django 的命名約定。雖然嚴格來說這不是真的,但您之前了解到 Django 可以使用自省來克服與此方法相關的命名問題。
上述每種方法都有其自身的優點和缺點。那么,您應該使用哪種方法?
作為一般經驗法則,您應該在處理小表時復制數據,并且您可以承受一些停機時間。否則,最好的辦法是重命名表并將新模型引用到它。
也就是說,每個項目都有自己獨特的要求。您應該選擇對您和您的團隊最有意義的方法。
結論
閱讀本教程后,您可以更好地根據您的特定用例、限制和要求做出關于如何將 Django 模型移動到另一個應用程序的正確決定。
在本教程中,您學習了:
如何將 Django 模型從一個應用程序移動到另一個應用程序
如何利用先進的功能Django的遷移CLI,如sqlmigrate,showmigrations和sqlsequencereset
如何制作和檢查遷移計劃
如何使遷移可逆,以及如何逆向遷移
什么是自省以及 Django 如何在遷移中使用它
要深入了解,請查看完整的數據庫教程和Django 教程。
Django 數據復制服務 DRS
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。