Итак, примерно год назад я начал проект и, как и все новые разработчики, я не слишком сильно фокусировался на структуре, однако теперь я продолжаю вместе с Django, что стало очевидно, что мой макет проекта в основном мои модели ужасны по структуре,
У меня есть модели, которые в основном хранятся в одном приложении, и действительно большинство из этих моделей должны быть в своих собственных приложениях, я попытался разрешить это и перенести их с юга, однако мне показалось сложным и очень сложным из-за внешних ключей и т.д.
Однако из-за Django 1.7 и встроенной поддержки миграции есть лучший способ сделать это сейчас?
Я удаляю старый ответ, так как это может привести к потере данных. Как отметил Озан, мы можем создать 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)
]
Это можно сделать довольно легко, используя 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
ForeignKey(TheModel)
вместо IntegerField()
. Затем выполните миграцию снова (python manage.py makemigrations
). Затем вы можете выполнить миграцию и работать (python manage.py migrate
)Надеюсь, это поможет. Конечно, проверьте его на местном уровне, прежде чем пытаться в производстве, чтобы избежать неприятных сюрпризов:)
Как я это сделал (тестировался на Django == 1.8, с postgres, а значит, и 1.7)
Положение
app1.YourModel
но вы хотите, чтобы он переходил к: app2.YourModel
Добавьте это в 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.
app_label = 'app1'
.
Я получаю нервные переносы с использованием ручного кодирования (как того требует ответ Озана), поэтому следующие сочетания стратегий Озана и Майкла минимизируют требуемое количество ручного кодирования:
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
:
app1
до app2
, установите db_table
, но НЕ меняйте никаких ссылок FK.makemigrations
и обернуть все app2
миграции в state_operations
(см выше) app2
CreateTable
до последней app1
миграции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
не существует), если я этого не делаю.Другая хакерская альтернатива, если данные не большие или слишком сложные, но все же важные для поддержания, заключается в следующем:
Скопировано из моего ответа на https://stackoverflow.com/questions/30601107/move-models-between-django-1-8-apps-with-required-foreignkey-references
Если вам нужно переместить модель, и у вас больше нет доступа к приложению (или вам не нужен доступ), вы можете создать новую операцию и подумать о создании новой модели, только если перенесенная модель не существует.
В этом примере я передаю "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))
],
),
]
Сделайте это индивидуально для каждой модели, которая должна быть перемещена. Я бы не предложил делать то, что говорит другой ответ, перейдя на целые числа и обратно на внешние ключи Существует вероятность того, что новые внешние ключи будут разными, а строки могут иметь разные идентификаторы после миграции, и я не хотел запускать какой-либо риск несоответствия идентификаторов при переключении на внешние ключи.
Предположим, вы перемещаете модель 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 для моей модели, которая могла бы помочь.
В частности, вы закончите измененную историю миграции. Это не имеет значения, даже если у вас есть база данных с исходными миграциями. Если исходная и перезаписанная миграция заканчиваются одной и той же схемой базы данных, то такая переписывание должна быть в порядке.
Вы можете попробовать следующее (непроверенное):
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Обратите внимание, что вы будете копировать всю таблицу, а не перемещать ее, но при этом оба приложения не должны касаться таблицы, принадлежащей другому приложению, что, я думаю, более важно.
Это проверено примерно так, поэтому не забудьте сделать резервную копию своей базы данных.
Например, есть два приложения: 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
Что все!