如何将 Django 模型移动到另一个应用程序(如何将pdf转为word使用)

网友投稿 732 2022-05-30

目录

示例案例:将 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 模型移动到另一个应用程序(如何将pdf转为word使用)

既然您已经为模型生成了名称 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小时内删除侵权内容。

上一篇:[Python人工智能] 二十.基于Keras+RNN的文本分类vs基于传统机器学习的文本分类(python人工智能需要学什么)
下一篇:在敏捷实践中加速成长(敏捷实践的方式)
相关文章