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)
Вам необходимо создать три миграции:
- В приложении 1: переименуйте таблицу с
app1.BaseModel
с app1_basemodel
на app2_basemodel
.Это также заботится о настройке ограничения внешнего ключа для столбца basemodel_ptr
. - В App2: добавьте
app2.BaseModel
и заново создайте app2.Model1
с app2.BaseModel
в качестве базовой модели.Эти изменения вносятся только в состояние миграции и не затрагивают базу данных! - В приложении 1: удалите
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
В качестве альтернативы вы можете посмотреть написание пользовательской операции миграции .