将django模型移动到另一个应用程序,该应用程序是另一个模型的父模型



我必须在两个不同的应用程序中调整模型,如app1和app2,在app1中我得到Model1,在app2中我得到BaseModel,Model1就像这个

class Model1(BaseModel):
...

Model1和BaseModel在一个应用程序中,但我将Model1移动到了app2,现在我也想将BaseModel也移动到app2。我的问题是,当我试图将BaseModel移动到app2时,我会收到以下错误:

Cannot resolve bases for [<ModelState: 'app1.model1'>]
This can happen if you are inheriting models from an app with migrations (e.g. contrib.auth)
in an app with no migrations; see https://docs.djangoproject.com/en/2.1/topics/migrations/#dependencies for more

我做的很简单:

  1. 我编写了将BaseModel的表重命名为app2_BaseModel的迁移,然后编写了在app2中创建模型的迁移
  2. 我创建迁移是为了更改用于继承的字段basemodel_ptr
  3. 我将BaseModel代码移动到app2,并通过从app1迁移删除BaseModel

此方法适用于移动Model1,但当我尝试移动此基本模型时,会出现此错误。

我感谢任何帮助,包括任何其他方式来实现将BaseModel移动到app1的重构想法

Django没有内置的迁移操作,但您可以通过将多个迁移操作拼凑在一起来实现这一点。

迁移不仅会更改数据库,还会保持所有模型的状态。您必须确保状态与数据库保持同步,这会使事情变得有点复杂。密钥使用migrations.SeparateDatabaseAndState

假设您有以下模型定义:

# app1/models.py
from django.db import models
class BaseModel(models.Model):
base_field = models.CharField(max_length=64)
# app2/models.py
from django.db import models
from app1.models import BaseModel
class Model1(BaseModel):
model_field = models.CharField(max_length=64)

你想迁移到这个:

# app1/models.py empty
# app2/models.py
from django.db import models
class BaseModel(models.Model):
base_field = models.CharField(max_length=64)
class Model1(BaseModel):
model_field = models.CharField(max_length=64)

您必须创建三个迁移:

  • 在App1中:将app1.BaseModel的表从app1_basemodel重命名为app2_basemodel。这还负责调整basemodel_ptr列上的外键约束
  • 在App2中:添加app2.BaseModel并以app2.BaseModel为基础模型重新创建app2.Model1。这些更改仅对迁移状态执行,并且不会触及数据库
  • 在App1中:将app1.BaseModel从迁移状态中删除。同样,没有数据库更改

以下是代码中的情况:

# app1/migrations/0002_rename_basemodel_table.py
from django.db import migrations, models

class Migration(migrations.Migration):
atomic = False
dependencies = [
('app1', '0001_initial'),
]
operations = [
migrations.AlterModelTable(
name='BaseModel',
table='app2_basemodel'
),
]
# app2/migrations/0002_change_basemodel.py
from django.db import migrations, models
import django.db.models.deletion

class Migration(migrations.Migration):
dependencies = [
('app2', '0001_initial'),
('app1', '0002_rename_basemodel_table')
]
state_operations = [
migrations.CreateModel(
name='BaseModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('base_field', models.CharField(max_length=64)),
],
),
migrations.DeleteModel(
name='Model1',
),
migrations.CreateModel(
name='Model1',
fields=[
('basemodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='app2.BaseModel')),
],
bases=('app2.basemodel',),
),
]
database_operations = [
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations,
state_operations
)
]
# app1/0003_remove_basemodel.py
from django.db import migrations, models

class Migration(migrations.Migration):
dependencies = [
('app1', '0002_rename_basemodel_table'),
('app2', '0002_change_basemodel')
]
state_operations = [
migrations.DeleteModel(
name='BaseModel',
),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=None,
state_operations=state_operations
)
]

显然,您必须调整这些迁移以反映您的实际模型。我担心,如果你有其他与Model1相关的模型,这可能会变得更加复杂。

免责声明:我已经用SQLite和PostgreSQL测试过这些,但使用它的风险自负!在对生产数据运行备份之前,请确保您有备份。

之前:

$ python manage.py dbshell
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
CREATE TABLE IF NOT EXISTS "app1_basemodel" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "base_field" varchar(64) NOT NULL);
CREATE TABLE IF NOT EXISTS "app2_model1" ("basemodel_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "app1_basemodel" ("id") DEFERRABLE INITIALLY DEFERRED, "model_field" varchar(64) NOT NULL);
...
$ python manage.py shell
Python 3.7.0 (default, Aug 22 2018, 15:22:33)
[Clang 9.1.0 (clang-902.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from app2.models import Model1
>>> Model1.__bases__
(<class 'app1.models.BaseModel'>,)
>>> Model1.objects.create(base_field='a', model_field='A')
<Model1: Model1 object (1)>
>>> Model1.objects.create(base_field='b', model_field='B')
<Model1: Model1 object (2)>
>>>

之后:

sqlite> .schema
...
CREATE TABLE IF NOT EXISTS "app2_basemodel" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "base_field" varchar(64) NOT NULL);
CREATE TABLE IF NOT EXISTS "app2_model1" ("basemodel_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "app2_basemodel" ("id") DEFERRABLE INITIALLY DEFERRED, "model_field" varchar(64) NOT NULL);
...
>>> from app2.models import Model1
>>> Model1.__bases__
(<class 'app2.models.BaseModel'>,)
>>> for obj in Model1.objects.all():
...  print(obj.base_field, obj.model_field)
...
a A
b B

或者,您可以考虑编写一个自定义迁移操作。

我试图从状态中删除model1,它成功了以下是我为使其发挥作用所做的工作(一些名称是从我所写的内容更改的(:

  1. 从状态中删除模型1

    state_operations = [migrations.DeleteModel('Model')]
    operations =[migrations.SeparateDatabaseAndState(state_operations=state_operations)]
    
  2. 重命名BaseModel的表(需要为SQLite设置atomic=True(

    operations = [
    migrations.AlterModelTable(
    name='basemodel',
    table='first_basemodel',
    ),
    

    ]

  3. 将模型移动到第一个应用

    def update_contentypes(apps, schema_editor):
    ContentType = apps.get_model('contenttypes', 'ContentType')
    db_alias = schema_editor.connection.alias
    qs = ContentType.objects.using(db_alias).filter(app_label='second', model='basemodel')
    qs.update(app_label='first')
    
    def update_contentypes_reverse(apps, schema_editor):
    ContentType = apps.get_model('contenttypes', 'ContentType')
    db_alias = schema_editor.connection.alias
    qs = ContentType.objects.using(db_alias).filter(app_label='first', model='basemodel')
    qs.update(app_label='second')
    class Migration(migrations.Migration):
    dependencies = [
    ('first', '0002_delete_model_from_state'),
    ('second', '0002_auto_20181218_0717')
    ]
    state_operenterations = [
    migrations.CreateModel(
    name='BaseModel',
    fields=[
    ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    ('name', models.CharField(max_length=200)),
    ],
    ),
    ]
    database_operations = [
    migrations.RunPython(update_contentypes, update_contentypes_reverse),
    ]
    operations = [
    migrations.SeparateDatabaseAndState(
    state_operations=state_operations,
    database_operations=database_operations
    ),
    ]
    
  4. 从状态中删除app2中的BaseModel

    state_operations = [
    migrations.DeleteModel('BaseModel'),
    ]
    operations = [
    migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]
    
  5. 移动代码并添加回Model1

    state_operations = [migrations.CreateModel(
    name='Model',
    fields=[
    ('basemodel_ptr', models.OneToOneField(auto_created=True, on_delete=models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='first.BaseModel')),
    ('title', models.CharField(max_length=200)),
    ],
    bases=('first.basemodel',),),]
    operations = [migrations.SeparateDatabaseAndState(state_operations=state_operations)]
    

这个解决方案对我有效,在SQLite和PSQL上没有任何数据丢失测试。

最新更新