перенести модель django в другое приложение, которое является родительской моделью для другой модели - PullRequest
0 голосов
/ 16 декабря 2018

Мне нужно 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 и удаляю BaseModel с миграцией из app1

Этот метод работал для перемещения Model1, но когда я пытаюсь переместить эту базовую модель, я получаю эту ошибку.

Я ценю любую помощь, в том числе любой другой способ достижения этой идеи рефакторинга перемещения BaseModel вapp1

Ответы [ 2 ]

0 голосов
/ 19 декабря 2018

Я попытался удалить model1 из состояния, и это сработало. Вот что я сделал, чтобы заставить его работать (некоторые имена изменены по сравнению с тем, что я написал в вопросе):

  1. удаление model1из состояния

    state_operations = [migrations.DeleteModel('Model')]
    operations =[migrations.SeparateDatabaseAndState(state_operations=state_operations)]
    
  2. переименование таблицы для BaseModel (необходимо установить atomic = True для SQLite)

    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. удаление BaseModel в приложении 2 из состояния

    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.

0 голосов
/ 18 декабря 2018

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

В качестве альтернативы вы можете посмотреть написание пользовательской операции миграции .

...