Как переместить модель между двумя приложениями 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.
- СОВЕРШЕННО, с сохраненными данными.
P.S во время миграции он увидит, что content_type app1.yourmodel удален и может быть удален. Вы можете сказать "да" этому, но только если вы его не используете. В случае, если вы сильно зависите от него, чтобы иметь FK для этого типа содержимого, нет неповрежденности, не отвечайте "да" или "нет", но заходите в db в это время вручную и удалите Contentype app2.yourmodel и переименуйте приложение contenttype app1. yourmodel в app2.yourmodel, а затем продолжить, ответив no.
Ответ 5
Я получаю нервные переносы с использованием ручного кодирования (как того требует ответ Озана), поэтому следующие сочетания стратегий Озана и Майкла минимизируют требуемое количество ручного кодирования:
- Перед перемещением любых моделей убедитесь, что вы работаете с чистой базой, запустив
makemigrations
. - Переместите код для модели из
app1
в app2
-
Как рекомендовано @Michael, мы указываем новую модель на старую таблицу базы данных, используя db_table
Meta в "новой" модели:
class Meta:
db_table = 'app1_yourmodel'
-
Запустите makemigrations
. Это создаст CreateModel
в app2
и DeleteModel
в app1
. Технически эти миграции относятся к одной и той же таблице и будут удалять (включая все данные) и воссоздавать таблицу.
-
На самом деле мы не хотим (или нуждаемся) делать что-либо с таблицей. Нам просто нужно, чтобы 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',
),
...
])
]
-
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
:
- COPY модель от
app1
до app2
, установите db_table
, но НЕ меняйте никаких ссылок FK. - Запуск
makemigrations
и обернуть все app2
миграции в state_operations
(см выше) - Как указано выше, добавить зависимость в
app2
CreateTable
до последней app1
миграции
- Направьте все ссылки FK на новую модель. Если вы не используете ссылки на строки, переместите старую модель в
models.py
(НЕ удаляйте ее), чтобы она не конкурировала с импортированным классом. -
Запустите 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>'),
]
-
Удалите модели из app1
- Запуск
makemigrations
и обернуть app1
миграции в state_operations
. - Добавьте зависимость от всех миграций
ForeignKey
(т.е. AlterField
) с предыдущего шага (могут включать миграции в app1
и app2
). - Когда я построил эти миграции,
DeleteTable
уже зависела от миграций AlterField
поэтому мне не нужно было ее вручную AlterField
(т.е. Alter
before Delete
).
В этот момент Джанго хорош. Новая модель указывает на старую таблицу, и миграции Django убедили ее, что все было перемещено соответствующим образом. Большое предостережение (от @Michael ответа) является то, что новый ContentType
создан для новой модели. Если вы связываете (например, с помощью ForeignKey
) с типами контента, вам необходимо создать миграцию для обновления таблицы ContentType
.
Я хотел очистить после себя (варианты мета и имена таблиц), поэтому я использовал следующую процедуру (от @Michael):
- Удалить мета-запись
db_table
- Запустите
makemigrations
снова, чтобы сгенерировать переименование базы данных - Отредактируйте эту последнюю миграцию и убедитесь, что она зависит от миграции
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))
],
),
]