Как переместить модель между двумя приложениями 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 шага:
- прежде чем что-либо делать, измените все свои
ForeignKey
привязка кTheModel
наIntegerfield
. Затем запуститеpython manage.py makemigrations
- после выполнения шагов Озана, повторно конвертируйте внешние ключи: put back
ForeignKey(TheModel)
вместоIntegerField()
. Затем сделайте миграции снова (python manage.py makemigrations
). Затем вы можете перенести, и он должен работать (python manage.py migrate
)
надеюсь, что это помогает. Конечно, проверьте его в местном, прежде чем пытаться в производстве, чтобы избежать плохих сюрпризов:)
Как я это сделал (проверено на Django==1.8, с postgres, так что, вероятно, также 1.7)
ситуация
приложения app1.YourModel
но вы хотите, чтобы перейти к: приложения app2.YourModel
- скопируйте YourModel (код) из app1 в app2.
-
добавьте это в app2.YourModel:
Class Meta: db_table = 'app1_yourmodel'
$ python manage.py makemigrations app2
новая миграция (например, 0009_auto_something.py) производится в app2 с миграциями.Оператор CreateModel (), переместите этот оператор в начальную миграцию app2 (например 0001_initial.py) (это будет так же, как всегда было там). А теперь удалите созданную миграцию = 0009_auto_something.py
Так же, как вы действуете, как app2.YourModel всегда был там, теперь удалите существование app1.YourModel из ваших миграций. Значение: прокомментируйте операторы CreateModel и каждый настройка или datamigration вы использовали после этого.
и, конечно же, каждая ссылка на app1.YourModel должен быть изменен на app2.YourModel через ваш проект. Кроме того, не забывайте, что все возможные внешние ключи в app1.YourModel в миграциях должны быть изменены на app2.YourModel
теперь, если вы делаете $ python manage.py миграция, ничего не изменилось, также Когда вы делаете $ python manage.py makemigrations, ничего нового не было обнаруженный.
-
теперь последний штрих: удалите класс Meta из app2.YourModel и do $ python manage.py makemigrations app2 & & python manage.py миграция app2 (если вы посмотрите на эту миграцию, вы увидите что-то вроде этого:)
migrations.AlterModelTable( name='yourmodel', table=None, ),
table=None, означает, что он будет принимать имя таблицы по умолчанию, которое в этом случае будет app2_yourmodel.
- готово, данные сохранены.
P. S во время миграция он увидит, что это content_type app1.yourmodel был удален и может быть удален. Вы можете сказать да, но только если вы не используете его. Если вы сильно зависите от него, чтобы иметь FKS для этого типа контента, не отвечайте " Да " или "нет", но зайдите в БД на этот раз вручную и удалите приложение contentype app2.yourmodel и переименуйте приложение contenttype app1.yourmodel для app2.yourmodel, а затем продолжить, ответив нет.
я получаю нервные миграции ручного кодирования (как требуется Озан это ответ) Итак, следующее объединяет Озана и Майкл стратегии для минимизации количества ручного кодирования требуется:
- перед перемещением любых моделей убедитесь, что вы работаете с чистой базовой линией, запустив
makemigrations
. - переместить код модели из
app1
toapp2
-
как рекомендовано @Michael, мы указываем новую модель в старой таблице базы данных с помощью
db_table
Meta опция на" новой " модели:class Meta: db_table = 'app1_yourmodel'
выполнить
makemigrations
. Это будет генерироватьCreateModel
наapp2
иDeleteModel
наapp1
. Технически эти миграции относятся к одной и той же таблице и удаляют (включая все данные) и повторно создают таблицу.-
на самом деле мы не хотим (или не должны) что-либо делать со столом. Нам просто нужно, чтобы Джанго поверил, что изменения были сделаны. Ответ на @Озан, то
state_operations
флагSeparateDatabaseAndState
это. Поэтому мы завернем всеmigrations
записи В ОБОИХ ФАЙЛАХ МИГРАЦИИ сSeparateDatabaseAndState(state_operations=[...])
. Например,operations = [ ... migrations.DeleteModel( name='YourModel', ), ... ]
становится
operations = [ migrations.SeparateDatabaseAndState(state_operations=[ ... migrations.DeleteModel( name='YourModel', ), ... ]) ]
-
редактировать: вам также нужно убедиться, что новый "виртуальный"
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
миграция:
-
скопировать модель
app1
toapp2
, setdb_table
, но не меняйте никаких ссылок FK. - выполнить
makemigrations
и обернуть всеapp2
миграция вstate_operations
(см. выше)- как указано выше, добавьте зависимость в
app2
CreateTable
до последнегоapp1
миграция
- как указано выше, добавьте зависимость в
- укажите все ссылки FK на новую модель. Если вы не используете ссылки на строки, переместите старый модель в нижней части
models.py
(не удаляйте его), чтобы он не конкурировал с импортированным классом. -
выполнить
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>'), ]
- добавить зависимость во всех
удалить модели из
app1
- выполнить
makemigrations
и обернутьapp1
миграция вstate_operations
.- добавить зависимость от всех
ForeignKey
миграция (т. е.AlterField
) С предыдущего шага (может включать миграции вapp1
иapp2
). - когда я построил эти миграции,
DeleteTable
уже зависит отAlterField
миграции, поэтому мне не нужно было вручную применять его (т. е.Alter
доDelete
).
- добавить зависимость от всех
на данный момент, Django хорошо идти. Новая модель указывает на старую таблицу, и миграции Django убедили ее в том, что все было перемещено соответствующим образом. Большой нюанс (из ответа @Михаил) заключается в том, что новый ContentType
создается для новой модели. Если вы ссылаетесь (например, по ForeignKey
) для типов контента вам нужно создать миграцию, чтобы обновить ContentType
таблица.
я хотел очистить после себя (мета-параметры и имена таблиц), поэтому я использовал следующую процедуру (от @Michael):
- удалить
db_table
мета запись - выполнить
makemigrations
снова создать переименование базы данных - отредактируйте эту последнюю миграцию и убедитесь, что она зависит от
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
вот и все!
вы можете попробовать следующие (непроверенных):
- переместить модель из
src_app
доdest_app
- перенос
dest_app
; убедитесь, что миграция схемы зависит от последнейsrc_app
миграция (https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files) - добавить миграцию данных в
dest_app
, который копирует все данные изsrc_app
- перенос
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 для моей модели, которая могла бы помочь.
Примечательно, что вы закончите с измененной историей миграции. Это не имеет значения, даже если у вас есть база данных с оригинальной миграции применяется. Если исходные и переписанные миграции заканчиваются одной и той же схемой базы данных, то такие переписать должно быть нормально.
- измените имена старых моделей на "model_name_old"
- makemigrations
- создание новых моделей с именем ‘model_name_new’ с идентичными отношениями на связанных моделях (напр. модель пользователя теперь имеет пользователя.blog_old и пользователь.blog_new)
- makemigrations
- напишите пользовательскую миграцию, которая переносит все данные в новые таблицы модели
- проверьте ад из этих миграций, сравнивая резервные копии с новыми копиями БД до и после запуск миграции
- когда все будет удовлетворительно, удалите старые модели
- makemigrations
- измените новые модели на правильное имя 'model_name_new' - > ‘model_name’
- проверьте все множество миграций на промежуточном сервере
- уберите свой производственный сайт на несколько минут, чтобы запустить все миграции без вмешательства пользователей
сделайте это индивидуально для каждой модели, которую необходимо переместить. Я бы не ... предложите сделать то, что говорит другой ответ, изменив на целые числа и обратно на внешние ключи Существует вероятность того, что новые внешние ключи будут отличаться, а строки могут иметь разные идентификаторы после миграции, и я не хотел рисковать несоответствием идентификаторов при переключении на внешние ключи.
еще одна хакерская альтернатива, если данные не большие или слишком сложные, но все же важно поддерживать, это:
- получить данные светильники с помощью 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))
],
),
]