Как переместить модель между двумя приложениями Django (Django 1.7)

Итак, около года назад я начал проект, и, как и все новые разработчики, я не слишком сосредоточился на структуре, однако теперь я дальше вместе с Django начал появляться, что мой макет проекта в основном мои модели ужасны по структуре.

У меня есть модели в основном проводятся в одном приложении, и на самом деле большинство из этих моделей должны быть в своих отдельных приложениях, я попытался решить эту проблему и переместить их с юга, однако я нашел это сложно и очень сложно из-за к внешним ключам ect.

однако из-за Django 1.7 и встроенной поддержки миграции есть ли лучший способ сделать это сейчас?

11 ответов


Я удаляю старый ответ, поскольку это может привести к потере данных. As Озан упомянул, мы можем создать 2 миграции по одному в каждом приложении.

первая миграция для удаления модели из 1-го приложения.

$ python manage.py makemigrations old_app --empty

изменить файл миграции, чтобы включить эти операции.

class Migration(migrations.Migration):

    database_operations = [migrations.AlterModelTable('TheModel', 'newapp_themodel')]

    state_operations = [migrations.DeleteModel('TheModel')]

    operations = [
      migrations.SeparateDatabaseAndState(
        database_operations=database_operations,
        state_operations=state_operations)
    ]

вторая миграция, которая зависит от первой миграции и создания новой таблицы во 2-м приложении. После перемещения кода модели во 2-е приложение

$ python manage.py makemigrations new_app 

редактирование и переноса файла вроде этого.

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]

это можно сделать довольно легко, используя migrations.SeparateDatabaseAndState. В принципе, мы используем операцию базы данных для переименования таблицы одновременно с двумя операциями состояния, чтобы удалить модель из истории одного приложения и создать ее в другом.

удалить из старого приложения

python manage.py makemigrations old_app --empty

в миграции:

class Migration(migrations.Migration):

    dependencies = []

    database_operations = [
        migrations.AlterModelTable('TheModel', 'newapp_themodel')
    ]

    state_operations = [
        migrations.DeleteModel('TheModel')
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=database_operations,
            state_operations=state_operations)
    ]

добавить новое приложение

во-первых, скопируйте модель в новое приложение model.py затем:

python manage.py makemigrations new_app

это создаст миграцию с наивным CreateModel деятельность как единственная деятельность. Заверните это в SeparateDatabaseAndState операция такая, что мы не пытаемся воссоздать таблицу. Также включите предыдущую миграцию как зависимость:

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]

я столкнулся с той же проблемой. ответ Озана помогло мне много, но, к сожалению, было недостаточно. Действительно, у меня было несколько ForeignKey, связанных с моделью, которую я хотел переместить. После некоторой головной боли я нашел решение, поэтому решил опубликовать его, чтобы решить время людей.

вам нужно еще 2 шага:

  1. прежде чем что-либо делать, измените все свои ForeignKey привязка к TheModel на Integerfield. Затем запустите python manage.py makemigrations
  2. после выполнения шагов Озана, повторно конвертируйте внешние ключи: put back ForeignKey(TheModel)вместо IntegerField(). Затем сделайте миграции снова (python manage.py makemigrations). Затем вы можете перенести, и он должен работать (python manage.py migrate)

надеюсь, что это помогает. Конечно, проверьте его в местном, прежде чем пытаться в производстве, чтобы избежать плохих сюрпризов:)


Как я это сделал (проверено на Django==1.8, с postgres, так что, вероятно, также 1.7)

ситуация

приложения app1.YourModel

но вы хотите, чтобы перейти к: приложения app2.YourModel

  1. скопируйте YourModel (код) из app1 в app2.
  2. добавьте это в app2.YourModel:

    Class Meta:
        db_table = 'app1_yourmodel'
    
  3. $ python manage.py makemigrations app2

  4. новая миграция (например, 0009_auto_something.py) производится в app2 с миграциями.Оператор CreateModel (), переместите этот оператор в начальную миграцию app2 (например 0001_initial.py) (это будет так же, как всегда было там). А теперь удалите созданную миграцию = 0009_auto_something.py

  5. Так же, как вы действуете, как app2.YourModel всегда был там, теперь удалите существование app1.YourModel из ваших миграций. Значение: прокомментируйте операторы CreateModel и каждый настройка или datamigration вы использовали после этого.

  6. и, конечно же, каждая ссылка на app1.YourModel должен быть изменен на app2.YourModel через ваш проект. Кроме того, не забывайте, что все возможные внешние ключи в app1.YourModel в миграциях должны быть изменены на app2.YourModel

  7. теперь, если вы делаете $ python manage.py миграция, ничего не изменилось, также Когда вы делаете $ python manage.py makemigrations, ничего нового не было обнаруженный.

  8. теперь последний штрих: удалите класс Meta из app2.YourModel и do $ python manage.py makemigrations app2 & & python manage.py миграция app2 (если вы посмотрите на эту миграцию, вы увидите что-то вроде этого:)

        migrations.AlterModelTable(
        name='yourmodel',
        table=None,
    ),
    

table=None, означает, что он будет принимать имя таблицы по умолчанию, которое в этом случае будет app2_yourmodel.

  1. готово, данные сохранены.

P. S во время миграция он увидит, что это content_type app1.yourmodel был удален и может быть удален. Вы можете сказать да, но только если вы не используете его. Если вы сильно зависите от него, чтобы иметь FKS для этого типа контента, не отвечайте " Да " или "нет", но зайдите в БД на этот раз вручную и удалите приложение contentype app2.yourmodel и переименуйте приложение contenttype app1.yourmodel для app2.yourmodel, а затем продолжить, ответив нет.


я получаю нервные миграции ручного кодирования (как требуется Озан это ответ) Итак, следующее объединяет Озана и Майкл стратегии для минимизации количества ручного кодирования требуется:

  1. перед перемещением любых моделей убедитесь, что вы работаете с чистой базовой линией, запустив makemigrations.
  2. переместить код модели из app1 to app2
  3. как рекомендовано @Michael, мы указываем новую модель в старой таблице базы данных с помощью db_table Meta опция на" новой " модели:

    class Meta:
        db_table = 'app1_yourmodel'
    
  4. выполнить makemigrations. Это будет генерировать CreateModel на app2 и DeleteModel на app1. Технически эти миграции относятся к одной и той же таблице и удаляют (включая все данные) и повторно создают таблицу.

  5. на самом деле мы не хотим (или не должны) что-либо делать со столом. Нам просто нужно, чтобы Джанго поверил, что изменения были сделаны. Ответ на @Озан, то state_operations флаг SeparateDatabaseAndState это. Поэтому мы завернем все migrations записи В ОБОИХ ФАЙЛАХ МИГРАЦИИ с SeparateDatabaseAndState(state_operations=[...]). Например,

    operations = [
        ...
        migrations.DeleteModel(
            name='YourModel',
        ),
        ...
    ]
    

    становится

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=[
            ...
            migrations.DeleteModel(
                name='YourModel',
            ),
            ...
        ])
    ]
    
  6. редактировать: вам также нужно убедиться, что новый "виртуальный"CreateModel миграция зависит от миграции, что фактически создал или изменил исходную таблицу. Например, если ваши новые миграции app2.migrations.0004_auto_<date> (для Create) и app1.migrations.0007_auto_<date> (для Delete), самое простое, что нужно сделать:

    • открыть app1.migrations.0007_auto_<date> и скопировать его app1 зависимостей (например,('app1', '0006...'),). Это" непосредственно предшествующая " миграция в app1 и должен включать зависимости от всей фактической логики построения модели.
    • открыть app2.migrations.0004_auto_<date> и добавьте зависимость, которую вы только что скопировали в его dependencies список.

редактировать: если у вас есть ForeignKey отношение(ы) к модели, которую вы перемещаете, выше может не работать. Это происходит потому, что:

  • зависимости не создаются автоматически для ForeignKey изменения
  • мы не хотим, чтобы обернуть ForeignKey изменения state_operations поэтому нам нужно убедиться, что они отделены от операций таблицы.

"минимальный" набор операций различаются в зависимости от ситуации, но следующая процедура должна работать для большинства/всех ForeignKey миграция:

  1. скопировать модель app1 to app2, set db_table, но не меняйте никаких ссылок FK.
  2. выполнить makemigrations и обернуть все app2 миграция в state_operations (см. выше)
    • как указано выше, добавьте зависимость в app2 CreateTable до последнего app1 миграция
  3. укажите все ссылки FK на новую модель. Если вы не используете ссылки на строки, переместите старый модель в нижней части models.py (не удаляйте его), чтобы он не конкурировал с импортированным классом.
  4. выполнить makemigrations но не заворачивайте ничего в state_operations (изменения FK действительно должны произойти)

    • добавить зависимость во всех ForeignKey миграция (т. е. AlterField) к CreateTable миграция в app2 (вам понадобится этот список для следующего шага, поэтому следите за ними). Например:
    • найти миграцию, которая включает в себя CreateModel например,app2.migrations.0002_auto_<date> и скопируйте имя этой миграции.
    • найти все миграции, которые имеют внешний ключ к этой модели (например, путем поиска app2.YourModel чтобы найти миграции, такие как:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
          ]
      
          operations = [
              migrations.AlterField(
                  model_name='relatedmodel',
                  name='fieldname',
                  field=models.ForeignKey(... to='app2.YourModel'),
              ),
          ]
      
    • добавить CreateModel миграция, как зависимость:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
              ('app2', '0002_auto_<date>'),
          ]  
      
  5. удалить модели из app1

  6. выполнить makemigrations и обернуть app1 миграция в state_operations.
    • добавить зависимость от всех ForeignKey миграция (т. е. AlterField) С предыдущего шага (может включать миграции в app1 и app2).
    • когда я построил эти миграции,DeleteTable уже зависит от AlterField миграции, поэтому мне не нужно было вручную применять его (т. е. Alter до Delete).

на данный момент, Django хорошо идти. Новая модель указывает на старую таблицу, и миграции Django убедили ее в том, что все было перемещено соответствующим образом. Большой нюанс (из ответа @Михаил) заключается в том, что новый ContentType создается для новой модели. Если вы ссылаетесь (например, по ForeignKey) для типов контента вам нужно создать миграцию, чтобы обновить ContentType таблица.

я хотел очистить после себя (мета-параметры и имена таблиц), поэтому я использовал следующую процедуру (от @Michael):

  1. удалить db_table мета запись
  2. выполнить makemigrations снова создать переименование базы данных
  3. отредактируйте эту последнюю миграцию и убедитесь, что она зависит от DeleteTable миграция. Похоже, необходимо как Delete должно быть чисто логичным, но я столкнулся с ошибками (например,app1_yourmodel не существует) если я не буду.

это тестируется грубо, так что не забудьте сделать резервную копию БД!!!

например, есть два приложения: src_app и dst_app, мы хотим перенести модель MoveMe С src_app to dst_app.

создать пустые миграции для обоих приложений:

python manage.py makemigrations --empty src_app
python manage.py makemigrations --empty dst_app

предположим, что новые миграции XXX1_src_app_new и XXX1_dst_app_new, previuos топ миграции являются XXX0_src_app_old и XXX0_dst_app_old.

добавить операцию, которая переименовывает таблицу для MoveMe модель и переименовывает его app_label в ProjectState в XXX1_dst_app_new. Не забудьте добавить зависимость от XXX0_src_app_old миграция. В результате XXX1_dst_app_new миграция-это:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

# this operations is almost the same as RenameModel
# https://github.com/django/django/blob/1.7/django/db/migrations/operations/models.py#L104
class MoveModelFromOtherApp(migrations.operations.base.Operation):

    def __init__(self, name, old_app_label):
        self.name = name
        self.old_app_label = old_app_label

    def state_forwards(self, app_label, state):

        # Get all of the related objects we need to repoint
        apps = state.render(skip_cache=True)
        model = apps.get_model(self.old_app_label, self.name)
        related_objects = model._meta.get_all_related_objects()
        related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
        # Rename the model
        state.models[app_label, self.name.lower()] = state.models.pop(
            (self.old_app_label, self.name.lower())
        )
        state.models[app_label, self.name.lower()].app_label = app_label
        for model_state in state.models.values():
            try:
                i = model_state.bases.index("%s.%s" % (self.old_app_label, self.name.lower()))
                model_state.bases = model_state.bases[:i] + ("%s.%s" % (app_label, self.name.lower()),) + model_state.bases[i+1:]
            except ValueError:
                pass
        # Repoint the FKs and M2Ms pointing to us
        for related_object in (related_objects + related_m2m_objects):
            # Use the new related key for self referential related objects.
            if related_object.model == model:
                related_key = (app_label, self.name.lower())
            else:
                related_key = (
                    related_object.model._meta.app_label,
                    related_object.model._meta.object_name.lower(),
                )
            new_fields = []
            for name, field in state.models[related_key].fields:
                if name == related_object.field.name:
                    field = field.clone()
                    field.rel.to = "%s.%s" % (app_label, self.name)
                new_fields.append((name, field))
            state.models[related_key].fields = new_fields

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        old_apps = from_state.render()
        new_apps = to_state.render()
        old_model = old_apps.get_model(self.old_app_label, self.name)
        new_model = new_apps.get_model(app_label, self.name)
        if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
            # Move the main table
            schema_editor.alter_db_table(
                new_model,
                old_model._meta.db_table,
                new_model._meta.db_table,
            )
            # Alter the fields pointing to us
            related_objects = old_model._meta.get_all_related_objects()
            related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
            for related_object in (related_objects + related_m2m_objects):
                if related_object.model == old_model:
                    model = new_model
                    related_key = (app_label, self.name.lower())
                else:
                    model = related_object.model
                    related_key = (
                        related_object.model._meta.app_label,
                        related_object.model._meta.object_name.lower(),
                    )
                to_field = new_apps.get_model(
                    *related_key
                )._meta.get_field_by_name(related_object.field.name)[0]
                schema_editor.alter_field(
                    model,
                    related_object.field,
                    to_field,
                )

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        self.old_app_label, app_label = app_label, self.old_app_label
        self.database_forwards(app_label, schema_editor, from_state, to_state)
        app_label, self.old_app_label = self.old_app_label, app_label

    def describe(self):
        return "Move %s from %s" % (self.name, self.old_app_label)


class Migration(migrations.Migration):

    dependencies = [
       ('dst_app', 'XXX0_dst_app_old'),
       ('src_app', 'XXX0_src_app_old'),
    ]

    operations = [
        MoveModelFromOtherApp('MoveMe', 'src_app'),
    ]

добавить зависимость на XXX1_dst_app_new to XXX1_src_app_new. XXX1_src_app_new нет-op миграции, которая необходима, чтобы убедиться, что будущее src_app миграции будут выполняться после XXX1_dst_app_new.

движение MoveMe С src_app/models.py to dst_app/models.py. Тогда беги:

python manage.py migrate

вот и все!


вы можете попробовать следующие (непроверенных):

  1. переместить модель из src_app до dest_app
  2. перенос dest_app; убедитесь, что миграция схемы зависит от последней src_app миграция (https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files)
  3. добавить миграцию данных в dest_app, который копирует все данные из src_app
  4. перенос src_app; убедитесь, что миграция схемы зависит от последней (данные) миграция dest_app -- то есть: миграция шага 3

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


допустим, вы перемещаете модель TheModel из app_a в app_b.

альтернативным решением является изменение существующих миграций вручную. Идея заключается в том, что каждый раз, когда вы видите операцию, изменяющую TheModel в миграциях app_a, вы копируете эту операцию до конца начальной миграции app_b. И каждый раз, когда вы видите ссылку " app_a.TheModel "в миграциях app_a вы меняете его на" app_b.Модель.

Я просто сделал это для существующего проекта, где я хотел извлечь определенную модель в приложение повторно. Процедура прошла гладко. Я думаю, что все было бы намного сложнее, если бы были ссылки из app_b в app_a. Кроме того, у меня была мета, определенная вручную.этим db_table для моей модели, которая могла бы помочь.

Примечательно, что вы закончите с измененной историей миграции. Это не имеет значения, даже если у вас есть база данных с оригинальной миграции применяется. Если исходные и переписанные миграции заканчиваются одной и той же схемой базы данных, то такие переписать должно быть нормально.


  1. измените имена старых моделей на "model_name_old"
  2. makemigrations
  3. создание новых моделей с именем ‘model_name_new’ с идентичными отношениями на связанных моделях (напр. модель пользователя теперь имеет пользователя.blog_old и пользователь.blog_new)
  4. makemigrations
  5. напишите пользовательскую миграцию, которая переносит все данные в новые таблицы модели
  6. проверьте ад из этих миграций, сравнивая резервные копии с новыми копиями БД до и после запуск миграции
  7. когда все будет удовлетворительно, удалите старые модели
  8. makemigrations
  9. измените новые модели на правильное имя 'model_name_new' - > ‘model_name’
  10. проверьте все множество миграций на промежуточном сервере
  11. уберите свой производственный сайт на несколько минут, чтобы запустить все миграции без вмешательства пользователей

сделайте это индивидуально для каждой модели, которую необходимо переместить. Я бы не ... предложите сделать то, что говорит другой ответ, изменив на целые числа и обратно на внешние ключи Существует вероятность того, что новые внешние ключи будут отличаться, а строки могут иметь разные идентификаторы после миграции, и я не хотел рисковать несоответствием идентификаторов при переключении на внешние ключи.


еще одна хакерская альтернатива, если данные не большие или слишком сложные, но все же важно поддерживать, это:

  • получить данные светильники с помощью manage.py dumpdata
  • приступайте к моделированию изменений и миграций должным образом, не связывая изменения
  • Global замените светильники из старой модели и имен приложений на новые
  • загрузка данных с помощью manage.py loaddata

скопировано из моего ответа в https://stackoverflow.com/a/47392970/8971048

Если вам нужно переместить модель, и у вас больше нет доступа к приложению (или вы не хотите доступа), вы можете создать новую операцию и рассмотреть возможность создания новой модели, только если перенесенная модель не существует.

в этом примере я передаю 'MyModel' из old_app в myapp.

class MigrateOrCreateTable(migrations.CreateModel):
    def __init__(self, source_table, dst_table, *args, **kwargs):
        super(MigrateOrCreateTable, self).__init__(*args, **kwargs)
        self.source_table = source_table
        self.dst_table = dst_table

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        table_exists = self.source_table in schema_editor.connection.introspection.table_names()
        if table_exists:
            with schema_editor.connection.cursor() as cursor:
                cursor.execute("RENAME TABLE {} TO {};".format(self.source_table, self.dst_table))
        else:
            return super(MigrateOrCreateTable, self).database_forwards(app_label, schema_editor, from_state, to_state)


class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_some_migration'),
    ]

    operations = [
        MigrateOrCreateTable(
            source_table='old_app_mymodel',
            dst_table='myapp_mymodel',
            name='MyModel',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=18))
            ],
        ),
    ]