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

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

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

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

Ответ 1

Я удаляю старый ответ, так как это может привести к потере данных. Как отметил Озан, мы можем создать 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)
    ]

Вторая миграция, которая зависит от первой миграции и создает новую таблицу во втором приложении. После перевода кода модели во второе приложение

$ 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)
    ]

Ответ 2

Это можно сделать довольно легко, используя 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)
    ]

Ответ 3

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

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

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

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

Ответ 4

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

Положение

app1.YourModel

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

  • Скопируйте свою модель (код) из приложения 1 в приложение2.
  • Добавьте это в app2.YourModel:

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

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

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

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

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

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

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

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

  1. СОВЕРШЕННО, с сохраненными данными.

P.S во время миграции он увидит, что content_type app1.yourmodel удален и может быть удален. Вы можете сказать "да" этому, но только если вы его не используете. В случае, если вы сильно зависите от него, чтобы иметь FK для этого типа содержимого, нет неповрежденности, не отвечайте "да" или "нет", но заходите в db в это время вручную и удалите Contentype app2.yourmodel и переименуйте приложение contenttype app1. yourmodel в app2.yourmodel, а затем продолжить, ответив no.

Ответ 5

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

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

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

  5. На самом деле мы не хотим (или нуждаемся) делать что-либо с таблицей. Нам просто нужно, чтобы Django полагал, что изменения были сделаны. В ответ на state_operations флаг state_operations в SeparateDatabaseAndState делает это. Поэтому мы переносим все записи migrations файлы BOTH SeparateDatabaseAndState(state_operations=[...]) с помощью SeparateDatabaseAndState(state_operations=[...]). Например,

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

    становится

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=[
            ...
            migrations.DeleteModel(
                name='YourModel',
            ),
            ...
        ])
    ]
    
  6. EDIT: вам также необходимо убедиться, что новая "виртуальная" миграция 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.

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

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

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

  1. COPY модель от app1 до app2, установите db_table, но НЕ меняйте никаких ссылок FK.
  2. Запуск makemigrations и обернуть все app2 миграции в state_operations (см выше)
    • Как указано выше, добавить зависимость в app2 CreateTable до последней app1 миграции
  3. Направьте все ссылки FK на новую модель. Если вы не используете ссылки на строки, переместите старую модель в models.py (НЕ удаляйте ее), чтобы она не конкурировала с импортированным классом.
  4. Запустите makemigrations но НЕ state_operations что-либо в state_operations (изменения FK действительно должны произойти). Добавьте зависимость во всех миграциях ForeignKey (то есть AlterField) к миграции CreateTable в app2 (для этого потребуется следующий список, чтобы отслеживать их). Например:

    • Найдите миграцию, которая включает CreateModel например app2.migrations.0002_auto_<date> и скопируйте имя этой миграции.
    • Найти все миграции, которые имеют ForeignKey для этой модели (например, путем поиска 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 поэтому мне не нужно было ее вручную AlterField (т.е. Alter before Delete).

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

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

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

Ответ 6

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

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

Ответ 7

Это проверено примерно так, поэтому не забудьте сделать резервную копию своей базы данных.

Например, есть два приложения: src_app и dst_app, мы хотим переместить модель MoveMe с src_app на 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 top migrations являются 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 до XXX1_src_app_new. XXX1_src_app_new - это не-операционная миграция, которая необходима для того, чтобы будущие миграции src_app выполнялись после XXX1_dst_app_new.

Переместите MoveMe с src_app/models.py на dst_app/models.py. Затем запустите:

python manage.py migrate

Что все!

Ответ 8

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

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

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

Ответ 9

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

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

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

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

Ответ 10

  • изменить имена старых моделей на 'model_name_old
  • makemigrations
  • создать новые модели с именем "model_name_new" с идентичными отношениями на связанных моделях (например, пользовательская модель теперь имеет user.blog_old и user.blog_new)
  • makemigrations
  • написать настраиваемую миграцию, которая переносит все данные в новые таблицы моделей.
  • проведите проверку этих миграций, сравнивая резервные копии с новыми копиями db до и после запуска миграции.
  • когда все будет удовлетворительно, удалите старые модели
  • makemigrations
  • измените новые модели на правильное имя 'model_name_new → ' model_name
  • протестировать весь процесс миграции на промежуточном сервере
  • снимите свой производственный сайт в течение нескольких минут, чтобы выполнить все миграции без вмешательства пользователей.

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

Ответ 11

Скопировано из моего ответа на fooobar.com/info/31684/...

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

В этом примере я передаю "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))
            ],
        ),
    ]