重命名 Django 超类模型并正确更新子类指针
Renaming a Django superclass model and updating the subclass pointers correctly
我在 Django v2.2.12 中重构涉及三个模型的超类时遇到问题,其中一个超类模型和两个子类模型:
class BaseProduct(models.Model):
name = models.CharField()
description = models.CharField()
class GeneralProduct(BaseProduct):
pass
class SoftwareProduct(BaseProduct):
pass
BaseProduct
模型需要重命名为 Product
,所以我将此代码更改为:
class Product(models.Model):
name = models.CharField()
description = models.CharField()
class GeneralProduct(Product):
pass
class SoftwareProduct(Product):
pass
然后是 运行 python manage.py makemigrations
,其中 Django 似乎正确地看到了更改的内容:
Did you rename the yourapp.BaseProduct model to Product? [y/N] y
Did you rename generalproduct.baseproduct_ptr to generalproduct.product_ptr (a OneToOneField)? [y/N] y
Did you rename softwareproduct.baseproduct_ptr to softwareproduct.product_ptr (a OneToOneField)? [y/N] y
Migrations for 'yourapp':
.../yourapp/migrations/002_auto_20200507_1830.py
- Rename model BaseProduct to Product
- Rename field baseproduct_ptr on generalproduct to product_ptr
- Rename field baseproduct_ptr on softwareproduct to product_ptr
到目前为止一切顺利。 Django 看到超类已重命名,它知道它自己用于跟踪模型继承的自动生成的 ..._ptr
值也需要在数据库中更新。
它提出的最终迁移看起来很简洁:
# Generated by Django 2.2.12 on 2020-05-07 18:30
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('yourapp', '0001_initial'),
]
operations = [
migrations.RenameModel(
old_name='BaseProduct',
new_name='Product',
),
migrations.RenameField(
model_name='generalproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
migrations.RenameField(
model_name='softwareproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
]
这一切看起来都很完美,但是使用 python manage.py migrate
应用该迁移会崩溃:
Running migrations:
Applying yourapp.0002_auto_20200507_1830...Traceback (most recent call last):
[...]
File ".../python3.7/site-packages/django/db/migrations/executor.py", line 245, in apply_migration
state = migration.apply(state, schema_editor)
File ".../python3.7/site-packages/django/db/migrations/migration.py", line 114, in apply
operation.state_forwards(self.app_label, project_state)
File ".../python3.7/site-packages/django/db/migrations/operations/models.py", line 340, in state_forwards
state.reload_models(to_reload, delay=True)
File ".../python3.7/site-packages/django/db/migrations/state.py", line 165, in reload_models
self._reload(related_models)
File ".../python3.7/site-packages/django/db/migrations/state.py", line 191, in _reload
self.apps.render_multiple(states_to_be_rendered)
File ".../python3.7/site-packages/django/db/migrations/state.py", line 308, in render_multiple
model.render(self)
File ".../python3.7/site-packages/django/db/migrations/state.py", line 579, in render
return type(self.name, bases, body)
File ".../python3.7/site-packages/django/db/models/base.py", line 253, in __new__
base.__name__,
django.core.exceptions.FieldError: Auto-generated field 'baseproduct_ptr' in class 'SoftwareProduct' for
parent_link to base class 'BaseProduct' clashes with declared field of the same name.
我在网上搜索了该错误,并在网上搜索了重命名作为其他模型的超类的 Django 模型,但似乎没有任何(可发现的)文档、博客文章或 SO 答案说说这个问题。
规范答案
出现此错误的原因是即使 Django 看到 模型已重命名并且子classes 需要指针更新,它不能正确地 对这些更新执行。在撰写本文时,有一个开放的 PR 将其添加到 Django(https://github.com/django/django/pull/13021,最初是 11222),但在此之前,解决方案是暂时“欺骗”Django 认为子 class es 实际上是没有任何继承的普通模型,并且 运行 通过以下步骤手动实现更改:
- 手动将自动生成的继承指针从
superclass_ptr
重命名为newsuperclass_ptr
(在这种情况下,baseproduct_ptr
变为product_prt
),然后
- 通过重写
.bases
属性 并告诉 Django 重新加载它们,然后
- 将 superclass 重命名为新名称(在本例中,
BaseProduct
变为 Product
),最后
- 更新
newsuperclass_ptr
字段,使它们指向新的 superclass 名称,确保指定 auto_created=True
以及 parent_link=True
.
在最后一步,第一个 属性 应该在那里,主要是因为 Django 自动生成指针,我们不希望 Django 能够告诉我们曾经欺骗过它并做了我们自己的事情,第二个 属性 是因为 parent_link 是 Django 在 运行s.
时正确连接模型继承所依赖的字段
因此,比 manage makemigrations
多了几个步骤,但每个步骤都很简单,我们可以通过编写一个自定义迁移文件来完成所有这些操作。
使用问题中的名字 post:
# Custom Django 2.2.12 migration for handling superclass model renaming.
from django.db import migrations, models
import django.db.models.deletion
# with a file called custom_operations.py in our migrations dir:
from .custom_operations import AlterModelBases
class Migration(migrations.Migration):
dependencies = [
('yourapp', '0001_initial'),
# Note that if the last real migration starts with 0001,
# this migration file has to start with 0002, etc.
#
# Django simply looks at the initial sequence number in
# order to build its migration tree, so as long as we
# name the file correctly, things just work.
]
operations = [
# Step 1: First, we rename the parent links in our
# subclasses to match their future name:
migrations.RenameField(
model_name='generalproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
migrations.RenameField(
model_name='softwareproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
# Step 2: then, temporarily set the base model for
# our subclassses to just `Model`, which makes
# Django think there are no parent links, which
# means it won't try to apply crashing logic in step 3.
AlterModelBases("GeneralProduct", (models.Model,)),
AlterModelBases("SoftwareProduct", (models.Model,)),
# Step 3: Now we can safely rename the superclass without
# Django trying to fix subclass pointers:
migrations.RenameModel(
old_name="BaseProduct",
new_name="Product"
),
# Step 4: Which means we can now update the `parent_link`
# fields for the subclasses: even though we altered
# the model bases earlier, this step will restore
# the class hierarchy we actually need:
migrations.AlterField(
model_name='generalproduct',
name='product_ptr',
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True, primary_key=True,
serialize=False,
to='buyersguide.Product'
),
),
migrations.AlterField(
model_name='softwareproduct',
name='product_ptr',
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to='buyersguide.Product'
),
),
]
关键的一步是继承“销毁”:我们告诉 Django subclass继承自models.Model
,这样重命名super class 会让 subclasses 完全不受影响(而不是 Django 试图更新继承指针本身),但我们实际上并没有改变数据库中的任何东西。我们只对当前 运行ning 代码进行更改,因此如果我们退出 Django,就好像从未进行过更改一样。
因此,为了实现这一点,我们使用自定义 ModelOperation 可以将 a(ny) class 的继承更改为不同的 superclass 的(ny 集合) (es) 在 运行 时间:
# contents of yourapp/migrations/custom_operations.py
from django.db.migrations.operations.models import ModelOperation
class AlterModelBases(ModelOperation):
reduce_to_sql = False
reversible = True
def __init__(self, name, bases):
self.bases = bases
super().__init__(name)
def state_forwards(self, app_label, state):
"""
Overwrite a models base classes with a custom list of
bases instead, then force Django to reload the model
with this (probably completely) different class hierarchy.
"""
state.models[app_label, self.name_lower].bases = self.bases
state.reload_model(app_label, self.name_lower)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
pass
def database_backwards(self, app_label, schema_editor, from_state, to_state):
pass
def describe(self):
return "Update %s bases to %s" % (self.name, self.bases)
有了这个自定义迁移文件和我们的 custom_operations.py
,我们需要做的就是更新我们的代码以反映新的命名方案:
class Product(models.Model):
name = models.CharField()
description = models.CharField()
class GeneralProduct(Product):
pass
class SoftwareProduct(Product):
pass
然后应用 manage migrate
,这将 运行 并根据需要更新所有内容。
注意:取决于您是否“预先分解”了您的代码以准备重命名,使用如下内容:
class BaseProduct(models.Model):
name = models.CharField()
description = models.CharField()
# "handy" aliasing so that all code can start using `Product`
# even though we haven't renamed actually renamed this class yet:
Product = BaseProduct
class GeneralProduct(Product):
pass
class SoftwareProduct(Product):
pass
您可能必须将 ForeignKey 和 ManyToMany 关系更新为 Product
在其他 classes 中,添加显式添加 models.AlterField
指令以将 BaseProduct 更新为 Product:
...
migrations.AlterField(
model_name='productrating',
name='product',
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to='yourapp.Product'
),
),
...
原回答
哦,是的,这是一个棘手的问题。但是我已经在我的项目中解决了这里是我做的方式。
1) 删除新创建的迁移并回滚模型更改
2) 使用 parent_link 选项将隐式父 link 字段更改为显式。我们需要它在后面的步骤中手动将我们的字段重命名为适当的名称
class BaseProduct(models.Model):
...
class GeneralProduct(BaseProduct):
baseproduct_ptr = models.OneToOneField(BaseProduct, django.db.models.deletion.CASCADE, parent_link=True, primary_key=True)
class SoftwareProduct(BaseProduct):
baseproduct_ptr = models.OneToOneField(BaseProduct, django.db.models.deletion.CASCADE, parent_link=True, primary_key=True)
3) 通过makemigrations
生成迁移并得到类似这样的东西
...
migrations.AlterField(
model_name='generalproduct',
name='baseproduct_ptr',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='BaseProduct'),
),
migrations.AlterField(
model_name='softwareproduct',
name='baseproduct_ptr',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='BaseProduct'),
)
...
4) 现在你的父模型有明确的 links 你可以将它们重命名为 product_ptr
这将匹配你想要的 link 名称
class GeneralProduct(BaseProduct):
product_ptr = models.OneToOneField(BaseProduct, django.db.models.deletion.CASCADE, parent_link=True, primary_key=True)
class SoftwareProduct(BaseProduct):
product_ptr = models.OneToOneField(BaseProduct, django.db.models.deletion.CASCADE, parent_link=True, primary_key=True)
5) 通过makemigrations
生成迁移并得到类似这样的东西
...
migrations.RenameField(
model_name='generalproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
migrations.RenameField(
model_name='softwareproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
...
6) 现在最棘手的部分是我们需要添加新的迁移操作(源代码可以在这里找到 https://github.com/django/django/pull/11222)并放入我们的代码,我个人有 contrib
包在我的项目中,我把所有员工都这样
文件在contrib/django/migrations.py
# https://github.com/django/django/pull/11222/files
# https://code.djangoproject.com/ticket/26488
# https://code.djangoproject.com/ticket/23521
# https://code.djangoproject.com/ticket/26488#comment:18
# https://github.com/django/django/pull/11222#pullrequestreview-233821387
from django.db.migrations.operations.models import ModelOperation
class DisconnectModelBases(ModelOperation):
reduce_to_sql = False
reversible = True
def __init__(self, name, bases):
self.bases = bases
super().__init__(name)
def state_forwards(self, app_label, state):
state.models[app_label, self.name_lower].bases = self.bases
state.reload_model(app_label, self.name_lower)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
pass
def database_backwards(self, app_label, schema_editor, from_state, to_state):
pass
def describe(self):
return "Update %s bases to %s" % (self.name, self.bases)
7) 现在我们准备重命名我们的父模型
class Product(models.Model):
....
class GeneralProduct(Product):
pass
class SoftwareProduct(Product):
pass
8) 通过 makemigrations
生成迁移。确保添加 DisconnectModelBases
步骤,它不会自动添加,即使成功生成迁移。如果这没有帮助,您可以尝试手动创建一个 --empty
。
from django.db import migrations, models
import django.db.models.deletion
from contrib.django.migrations import DisconnectModelBases
class Migration(migrations.Migration):
dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("products", "0071_auto_20200122_0614"),
]
operations = [
DisconnectModelBases("GeneralProduct", (models.Model,)),
DisconnectModelBases("SoftwareProduct", (models.Model,)),
migrations.RenameModel(
old_name="BaseProduct", new_name="Product"
),
migrations.AlterField(
model_name='generalproduct',
name='product_ptr',
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='products.Product'),
),
migrations.AlterField(
model_name='softwareproduct',
name='product_ptr',
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='proudcts.Product'),
),
]
注意:毕竟,您不需要明确的 parent_link
字段。所以你可以删除它们。我实际上在第 7 步做了。
我在 Django v2.2.12 中重构涉及三个模型的超类时遇到问题,其中一个超类模型和两个子类模型:
class BaseProduct(models.Model):
name = models.CharField()
description = models.CharField()
class GeneralProduct(BaseProduct):
pass
class SoftwareProduct(BaseProduct):
pass
BaseProduct
模型需要重命名为 Product
,所以我将此代码更改为:
class Product(models.Model):
name = models.CharField()
description = models.CharField()
class GeneralProduct(Product):
pass
class SoftwareProduct(Product):
pass
然后是 运行 python manage.py makemigrations
,其中 Django 似乎正确地看到了更改的内容:
Did you rename the yourapp.BaseProduct model to Product? [y/N] y
Did you rename generalproduct.baseproduct_ptr to generalproduct.product_ptr (a OneToOneField)? [y/N] y
Did you rename softwareproduct.baseproduct_ptr to softwareproduct.product_ptr (a OneToOneField)? [y/N] y
Migrations for 'yourapp':
.../yourapp/migrations/002_auto_20200507_1830.py
- Rename model BaseProduct to Product
- Rename field baseproduct_ptr on generalproduct to product_ptr
- Rename field baseproduct_ptr on softwareproduct to product_ptr
到目前为止一切顺利。 Django 看到超类已重命名,它知道它自己用于跟踪模型继承的自动生成的 ..._ptr
值也需要在数据库中更新。
它提出的最终迁移看起来很简洁:
# Generated by Django 2.2.12 on 2020-05-07 18:30
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('yourapp', '0001_initial'),
]
operations = [
migrations.RenameModel(
old_name='BaseProduct',
new_name='Product',
),
migrations.RenameField(
model_name='generalproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
migrations.RenameField(
model_name='softwareproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
]
这一切看起来都很完美,但是使用 python manage.py migrate
应用该迁移会崩溃:
Running migrations:
Applying yourapp.0002_auto_20200507_1830...Traceback (most recent call last):
[...]
File ".../python3.7/site-packages/django/db/migrations/executor.py", line 245, in apply_migration
state = migration.apply(state, schema_editor)
File ".../python3.7/site-packages/django/db/migrations/migration.py", line 114, in apply
operation.state_forwards(self.app_label, project_state)
File ".../python3.7/site-packages/django/db/migrations/operations/models.py", line 340, in state_forwards
state.reload_models(to_reload, delay=True)
File ".../python3.7/site-packages/django/db/migrations/state.py", line 165, in reload_models
self._reload(related_models)
File ".../python3.7/site-packages/django/db/migrations/state.py", line 191, in _reload
self.apps.render_multiple(states_to_be_rendered)
File ".../python3.7/site-packages/django/db/migrations/state.py", line 308, in render_multiple
model.render(self)
File ".../python3.7/site-packages/django/db/migrations/state.py", line 579, in render
return type(self.name, bases, body)
File ".../python3.7/site-packages/django/db/models/base.py", line 253, in __new__
base.__name__,
django.core.exceptions.FieldError: Auto-generated field 'baseproduct_ptr' in class 'SoftwareProduct' for
parent_link to base class 'BaseProduct' clashes with declared field of the same name.
我在网上搜索了该错误,并在网上搜索了重命名作为其他模型的超类的 Django 模型,但似乎没有任何(可发现的)文档、博客文章或 SO 答案说说这个问题。
规范答案
出现此错误的原因是即使 Django 看到 模型已重命名并且子classes 需要指针更新,它不能正确地 对这些更新执行。在撰写本文时,有一个开放的 PR 将其添加到 Django(https://github.com/django/django/pull/13021,最初是 11222),但在此之前,解决方案是暂时“欺骗”Django 认为子 class es 实际上是没有任何继承的普通模型,并且 运行 通过以下步骤手动实现更改:
- 手动将自动生成的继承指针从
superclass_ptr
重命名为newsuperclass_ptr
(在这种情况下,baseproduct_ptr
变为product_prt
),然后 - 通过重写
.bases
属性 并告诉 Django 重新加载它们,然后 - 将 superclass 重命名为新名称(在本例中,
BaseProduct
变为Product
),最后 - 更新
newsuperclass_ptr
字段,使它们指向新的 superclass 名称,确保指定auto_created=True
以及parent_link=True
.
在最后一步,第一个 属性 应该在那里,主要是因为 Django 自动生成指针,我们不希望 Django 能够告诉我们曾经欺骗过它并做了我们自己的事情,第二个 属性 是因为 parent_link 是 Django 在 运行s.
时正确连接模型继承所依赖的字段因此,比 manage makemigrations
多了几个步骤,但每个步骤都很简单,我们可以通过编写一个自定义迁移文件来完成所有这些操作。
使用问题中的名字 post:
# Custom Django 2.2.12 migration for handling superclass model renaming.
from django.db import migrations, models
import django.db.models.deletion
# with a file called custom_operations.py in our migrations dir:
from .custom_operations import AlterModelBases
class Migration(migrations.Migration):
dependencies = [
('yourapp', '0001_initial'),
# Note that if the last real migration starts with 0001,
# this migration file has to start with 0002, etc.
#
# Django simply looks at the initial sequence number in
# order to build its migration tree, so as long as we
# name the file correctly, things just work.
]
operations = [
# Step 1: First, we rename the parent links in our
# subclasses to match their future name:
migrations.RenameField(
model_name='generalproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
migrations.RenameField(
model_name='softwareproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
# Step 2: then, temporarily set the base model for
# our subclassses to just `Model`, which makes
# Django think there are no parent links, which
# means it won't try to apply crashing logic in step 3.
AlterModelBases("GeneralProduct", (models.Model,)),
AlterModelBases("SoftwareProduct", (models.Model,)),
# Step 3: Now we can safely rename the superclass without
# Django trying to fix subclass pointers:
migrations.RenameModel(
old_name="BaseProduct",
new_name="Product"
),
# Step 4: Which means we can now update the `parent_link`
# fields for the subclasses: even though we altered
# the model bases earlier, this step will restore
# the class hierarchy we actually need:
migrations.AlterField(
model_name='generalproduct',
name='product_ptr',
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True, primary_key=True,
serialize=False,
to='buyersguide.Product'
),
),
migrations.AlterField(
model_name='softwareproduct',
name='product_ptr',
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to='buyersguide.Product'
),
),
]
关键的一步是继承“销毁”:我们告诉 Django subclass继承自models.Model
,这样重命名super class 会让 subclasses 完全不受影响(而不是 Django 试图更新继承指针本身),但我们实际上并没有改变数据库中的任何东西。我们只对当前 运行ning 代码进行更改,因此如果我们退出 Django,就好像从未进行过更改一样。
因此,为了实现这一点,我们使用自定义 ModelOperation 可以将 a(ny) class 的继承更改为不同的 superclass 的(ny 集合) (es) 在 运行 时间:
# contents of yourapp/migrations/custom_operations.py
from django.db.migrations.operations.models import ModelOperation
class AlterModelBases(ModelOperation):
reduce_to_sql = False
reversible = True
def __init__(self, name, bases):
self.bases = bases
super().__init__(name)
def state_forwards(self, app_label, state):
"""
Overwrite a models base classes with a custom list of
bases instead, then force Django to reload the model
with this (probably completely) different class hierarchy.
"""
state.models[app_label, self.name_lower].bases = self.bases
state.reload_model(app_label, self.name_lower)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
pass
def database_backwards(self, app_label, schema_editor, from_state, to_state):
pass
def describe(self):
return "Update %s bases to %s" % (self.name, self.bases)
有了这个自定义迁移文件和我们的 custom_operations.py
,我们需要做的就是更新我们的代码以反映新的命名方案:
class Product(models.Model):
name = models.CharField()
description = models.CharField()
class GeneralProduct(Product):
pass
class SoftwareProduct(Product):
pass
然后应用 manage migrate
,这将 运行 并根据需要更新所有内容。
注意:取决于您是否“预先分解”了您的代码以准备重命名,使用如下内容:
class BaseProduct(models.Model):
name = models.CharField()
description = models.CharField()
# "handy" aliasing so that all code can start using `Product`
# even though we haven't renamed actually renamed this class yet:
Product = BaseProduct
class GeneralProduct(Product):
pass
class SoftwareProduct(Product):
pass
您可能必须将 ForeignKey 和 ManyToMany 关系更新为 Product
在其他 classes 中,添加显式添加 models.AlterField
指令以将 BaseProduct 更新为 Product:
...
migrations.AlterField(
model_name='productrating',
name='product',
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to='yourapp.Product'
),
),
...
原回答
哦,是的,这是一个棘手的问题。但是我已经在我的项目中解决了这里是我做的方式。
1) 删除新创建的迁移并回滚模型更改
2) 使用 parent_link 选项将隐式父 link 字段更改为显式。我们需要它在后面的步骤中手动将我们的字段重命名为适当的名称
class BaseProduct(models.Model):
...
class GeneralProduct(BaseProduct):
baseproduct_ptr = models.OneToOneField(BaseProduct, django.db.models.deletion.CASCADE, parent_link=True, primary_key=True)
class SoftwareProduct(BaseProduct):
baseproduct_ptr = models.OneToOneField(BaseProduct, django.db.models.deletion.CASCADE, parent_link=True, primary_key=True)
3) 通过makemigrations
生成迁移并得到类似这样的东西
...
migrations.AlterField(
model_name='generalproduct',
name='baseproduct_ptr',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='BaseProduct'),
),
migrations.AlterField(
model_name='softwareproduct',
name='baseproduct_ptr',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='BaseProduct'),
)
...
4) 现在你的父模型有明确的 links 你可以将它们重命名为 product_ptr
这将匹配你想要的 link 名称
class GeneralProduct(BaseProduct):
product_ptr = models.OneToOneField(BaseProduct, django.db.models.deletion.CASCADE, parent_link=True, primary_key=True)
class SoftwareProduct(BaseProduct):
product_ptr = models.OneToOneField(BaseProduct, django.db.models.deletion.CASCADE, parent_link=True, primary_key=True)
5) 通过makemigrations
生成迁移并得到类似这样的东西
...
migrations.RenameField(
model_name='generalproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
migrations.RenameField(
model_name='softwareproduct',
old_name='baseproduct_ptr',
new_name='product_ptr',
),
...
6) 现在最棘手的部分是我们需要添加新的迁移操作(源代码可以在这里找到 https://github.com/django/django/pull/11222)并放入我们的代码,我个人有 contrib
包在我的项目中,我把所有员工都这样
文件在contrib/django/migrations.py
# https://github.com/django/django/pull/11222/files
# https://code.djangoproject.com/ticket/26488
# https://code.djangoproject.com/ticket/23521
# https://code.djangoproject.com/ticket/26488#comment:18
# https://github.com/django/django/pull/11222#pullrequestreview-233821387
from django.db.migrations.operations.models import ModelOperation
class DisconnectModelBases(ModelOperation):
reduce_to_sql = False
reversible = True
def __init__(self, name, bases):
self.bases = bases
super().__init__(name)
def state_forwards(self, app_label, state):
state.models[app_label, self.name_lower].bases = self.bases
state.reload_model(app_label, self.name_lower)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
pass
def database_backwards(self, app_label, schema_editor, from_state, to_state):
pass
def describe(self):
return "Update %s bases to %s" % (self.name, self.bases)
7) 现在我们准备重命名我们的父模型
class Product(models.Model):
....
class GeneralProduct(Product):
pass
class SoftwareProduct(Product):
pass
8) 通过 makemigrations
生成迁移。确保添加 DisconnectModelBases
步骤,它不会自动添加,即使成功生成迁移。如果这没有帮助,您可以尝试手动创建一个 --empty
。
from django.db import migrations, models
import django.db.models.deletion
from contrib.django.migrations import DisconnectModelBases
class Migration(migrations.Migration):
dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("products", "0071_auto_20200122_0614"),
]
operations = [
DisconnectModelBases("GeneralProduct", (models.Model,)),
DisconnectModelBases("SoftwareProduct", (models.Model,)),
migrations.RenameModel(
old_name="BaseProduct", new_name="Product"
),
migrations.AlterField(
model_name='generalproduct',
name='product_ptr',
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='products.Product'),
),
migrations.AlterField(
model_name='softwareproduct',
name='product_ptr',
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='proudcts.Product'),
),
]
注意:毕竟,您不需要明确的 parent_link
字段。所以你可以删除它们。我实际上在第 7 步做了。