将 Django 模型移动到另一个应用程序,该应用程序是另一个模型的父模型
move a django model to another app which is parent model for another model
我必须在两个不同的应用程序(如 app1 和 app2)中使用 django 模型,在 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
我做的很简单:
- 我编写迁移以将 BaseModel table 重命名为 app2_basemodel 然后我编写迁移以在 app2
中创建模型
- 我创建迁移以更改用于继承的字段basemodel_ptr
- 我将 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
的 table 从 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
或者,您可以查看 writing a custom migration operation。
我尝试从状态中删除 model1 并且成功了
这是我为使其正常工作所做的工作(一些名称已从我所写的问题中更改):
正在从状态中删除模型 1
state_operations = [migrations.DeleteModel('Model')]
operations =[migrations.SeparateDatabaseAndState(state_operations=state_operations)]
为 BaseModel 重命名 table(您需要为 SQLite 设置 atomic=True)
operations = [
migrations.AlterModelTable(
name='basemodel',
table='first_basemodel',
),
]
将模型移动到第一个应用程序
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
),
]
正在从状态中删除 app2 中的 BaseModel
state_operations = [
migrations.DeleteModel('BaseModel'),
]
operations = [
migrations.SeparateDatabaseAndState(state_operations=state_operations)
]
移动代码并添加回 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 上测试过任何数据丢失。
我必须在两个不同的应用程序(如 app1 和 app2)中使用 django 模型,在 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
我做的很简单:
- 我编写迁移以将 BaseModel table 重命名为 app2_basemodel 然后我编写迁移以在 app2 中创建模型
- 我创建迁移以更改用于继承的字段basemodel_ptr
- 我将 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
的 table 从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
或者,您可以查看 writing a custom migration operation。
我尝试从状态中删除 model1 并且成功了 这是我为使其正常工作所做的工作(一些名称已从我所写的问题中更改):
正在从状态中删除模型 1
state_operations = [migrations.DeleteModel('Model')] operations =[migrations.SeparateDatabaseAndState(state_operations=state_operations)]
为 BaseModel 重命名 table(您需要为 SQLite 设置 atomic=True)
operations = [ migrations.AlterModelTable( name='basemodel', table='first_basemodel', ),
]
将模型移动到第一个应用程序
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 ), ]
正在从状态中删除 app2 中的 BaseModel
state_operations = [ migrations.DeleteModel('BaseModel'), ] operations = [ migrations.SeparateDatabaseAndState(state_operations=state_operations) ]
移动代码并添加回 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 上测试过任何数据丢失。