Как мне перенести модель из одного приложения django в новое?
-
12-09-2019 - |
Вопрос
У меня есть приложение django с четырьмя моделями в нем.Теперь я понимаю, что одна из этих моделей должна быть в отдельном приложении.У меня действительно установлен south для миграции, но я не думаю, что это то, с чем он может справиться автоматически.Как я могу перенести одну из моделей из старого приложения в новое?
Кроме того, имейте в виду, что мне нужно, чтобы этот процесс был повторяемым, чтобы я мог перенести производственную систему и тому подобное.
Решение
Как мигрировать с помощью south.
Допустим, у нас есть два приложения:общее и специфическое:
myproject/
|-- common
| |-- migrations
| | |-- 0001_initial.py
| | `-- 0002_create_cat.py
| `-- models.py
`-- specific
|-- migrations
| |-- 0001_initial.py
| `-- 0002_create_dog.py
`-- models.py
Теперь мы хотим переместить модель common.models.cat в конкретное приложение (именно в specific.models.cat).Сначала внесите изменения в исходный код, а затем запустите:
$ python manage.py schemamigration specific create_cat --auto
+ Added model 'specific.cat'
$ python manage.py schemamigration common drop_cat --auto
- Deleted model 'common.cat'
myproject/
|-- common
| |-- migrations
| | |-- 0001_initial.py
| | |-- 0002_create_cat.py
| | `-- 0003_drop_cat.py
| `-- models.py
`-- specific
|-- migrations
| |-- 0001_initial.py
| |-- 0002_create_dog.py
| `-- 0003_create_cat.py
`-- models.py
Теперь нам нужно отредактировать оба файла миграции:
#0003_create_cat: replace existing forward and backward code
#to use just one sentence:
def forwards(self, orm):
db.rename_table('common_cat', 'specific_cat')
if not db.dry_run:
# For permissions to work properly after migrating
orm['contenttypes.contenttype'].objects.filter(
app_label='common',
model='cat',
).update(app_label='specific')
def backwards(self, orm):
db.rename_table('specific_cat', 'common_cat')
if not db.dry_run:
# For permissions to work properly after migrating
orm['contenttypes.contenttype'].objects.filter(
app_label='specific',
model='cat',
).update(app_label='common')
#0003_drop_cat:replace existing forward and backward code
#to use just one sentence; add dependency:
depends_on = (
('specific', '0003_create_cat'),
)
def forwards(self, orm):
pass
def backwards(self, orm):
pass
Теперь обе миграции приложений знают об изменениях, и жизнь становится немного менее отстойной :-) Установление этой взаимосвязи между миграциями является ключом к успеху.Теперь, если вы это сделаете:
python manage.py migrate common
> specific: 0003_create_cat
> common: 0003_drop_cat
будет выполнять как миграцию, так и
python manage.py migrate specific 0002_create_dog
< common: 0003_drop_cat
< specific: 0003_create_cat
перенесет все вниз.
Обратите внимание, что для обновления схемы я использовал общее приложение, а для понижения - конкретное приложение.Это потому, что так работает зависимость здесь.
Другие советы
Чтобы опираться на Potr Czachur's ответ, ситуации, связанные с внешними ключами, более сложны и должны обрабатываться немного по-другому.
(Следующий пример основан на common
и specific
приложения, упомянутые в текущем ответе).
# common/models.py
class Cat(models.Model):
# ...
class Toy(models.Model):
belongs_to = models.ForeignKey(Cat)
# ...
затем изменил бы на
# common/models.py
from specific.models import Cat
class Toy(models.Model):
belongs_to = models.ForeignKey(Cat)
# ...
# specific/models.py
class Cat(models.Model):
# ...
Выполняется
./manage.py schemamigration common --auto
./manage.py schemamigration specific --auto # or --initial
сгенерировал бы следующие миграции (я намеренно игнорирую изменения типа содержимого Django — смотрите ранее упомянутый ответ о том, как с этим справиться):
# common/migrations/0009_auto__del_cat.py
class Migration(SchemaMigration):
def forwards(self, orm):
db.delete_table('common_cat')
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))
def backwards(self, orm):
db.create_table('common_cat', (
# ...
))
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))
# specific/migrations/0004_auto__add_cat.py
class Migration(SchemaMigration):
def forwards(self, orm):
db.create_table('specific_cat', (
# ...
))
def backwards(self, orm):
db.delete_table('specific_cat')
Как вы можете видеть, FK необходимо изменить, чтобы ссылаться на новую таблицу.Нам нужно добавить зависимость, чтобы мы знали порядок, в котором будут применены миграции (и, следовательно, что таблица будет существовать до того, как мы попытаемся добавить к ней FK), но нам также нужно убедиться, что откат назад тоже работает, потому что зависимость применяется в обратном направлении.
# common/migrations/0009_auto__del_cat.py
class Migration(SchemaMigration):
depends_on = (
('specific', '0004_auto__add_cat'),
)
def forwards(self, orm):
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))
def backwards(self, orm):
db.rename_table('specific_cat', 'common_cat')
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))
# specific/migrations/0004_auto__add_cat.py
class Migration(SchemaMigration):
def forwards(self, orm):
db.rename_table('common_cat', 'specific_cat')
def backwards(self, orm):
pass
В соответствии с Южная документация, depends_on
обеспечит, чтобы 0004_auto__add_cat
выполняется до 0009_auto__del_cat
при переносе вперед но в обратный порядок при обратном перемещении.Если бы мы ушли db.rename_table('specific_cat', 'common_cat')
в specific
откат, то common
откат завершится неудачей при попытке перенести ForeignKey, потому что таблица, на которую ссылается таблица, не будет существовать.
Надеюсь, это ближе к ситуации "реального мира", чем существующие решения, и кто-то найдет это полезным.Ваше здоровье!
Модели не очень тесно связаны с приложениями, поэтому перемещение происходит довольно просто.Django использует имя приложения в названии таблицы базы данных, поэтому, если вы хотите переместить свое приложение, вы можете либо переименовать таблицу базы данных с помощью SQL ALTER TABLE
оператор, или - еще проще - просто используйте db_table
параметр в вашей модели Meta
класс для ссылки на старое имя.
Если вы до сих пор использовали ContentTypes или общие отношения где-либо в своем коде, вы, вероятно, захотите переименовать app_label
типа содержимого, указывающего на движущуюся модель, так что существующие отношения сохраняются.
Конечно, если у вас вообще нет никаких данных для сохранения, самое простое, что можно сделать, это полностью удалить таблицы базы данных и запустить ./manage.py syncdb
снова.
Вот еще одно исправление превосходного решения Potr.Добавьте следующее к конкретный/0003_create_cat
depends_on = (
('common', '0002_create_cat'),
)
Если эта зависимость не установлена на юг, это не гарантирует, что common_cat
таблица существует в то время, когда конкретный/0003_create_cat является бегом, бросая django.db.utils.OperationalError: no such table: common_cat
ошибка у вас.
Юг управляет миграциями в лексикографический порядок если только зависимость не задана явно.С тех пор как common
приходит раньше specific
все эти common
миграции выполнялись бы перед переименованием таблицы, так что, вероятно, это не повторилось бы в исходном примере, показанном Potr.Но если вы переименуете common
Для app2
и specific
Для app1
вы столкнетесь с этой проблемой.
Процесс, на котором я в настоящее время остановился, так как я возвращался сюда несколько раз и решил формализовать его.
Первоначально это было построено на Ответ Потра Чачура и Ответ Мэтта Бриансона, использование South 0.8.4
Шаг 1.Откройте для себя дочерние отношения с внешним ключом
# Caution: This finds OneToOneField and ForeignKey.
# I don't know if this finds all the ways of specifying ManyToManyField.
# Hopefully Django or South throw errors if you have a situation like that.
>>> Cat._meta.get_all_related_objects()
[<RelatedObject: common:toy related to cat>,
<RelatedObject: identity:microchip related to cat>]
Итак, в этом расширенном случае мы обнаружили другую связанную модель, такую:
# Inside the "identity" app...
class Microchip(models.Model):
# In reality we'd probably want a ForeignKey, but to show the OneToOneField
identifies = models.OneToOneField(Cat)
...
Шаг 2.Создание миграций
# Create the "new"-ly renamed model
# Yes I'm changing the model name in my refactoring too.
python manage.py schemamigration specific create_kittycat --auto
# Drop the old model
python manage.py schemamigration common drop_cat --auto
# Update downstream apps, so South thinks their ForeignKey(s) are correct.
# Can skip models like Toy if the app is already covered
python manage.py schemamigration identity update_microchip_fk --auto
Шаг 3.Управление версиями:Фиксируйте изменения на данный момент.
Делает этот процесс более повторяемым, если вы сталкиваетесь с конфликтами слияния, например, когда товарищи по команде пишут миграции для обновленных приложений.
Шаг 4.Добавьте зависимости между миграциями.
В основном create_kittycat
зависит от текущего состояния всего, и тогда все зависит от create_kittycat
.
# create_kittycat
class Migration(SchemaMigration):
depends_on = (
# Original model location
('common', 'the_one_before_drop_cat'),
# Foreign keys to models not in original location
('identity', 'the_one_before_update_microchip_fk'),
)
...
# drop_cat
class Migration(SchemaMigration):
depends_on = (
('specific', 'create_kittycat'),
)
...
# update_microchip_fk
class Migration(SchemaMigration):
depends_on = (
('specific', 'create_kittycat'),
)
...
Шаг 5.Изменение переименования таблицы, которое мы хотим внести.
# create_kittycat
class Migration(SchemaMigration):
...
# Hopefully for create_kittycat you only need to change the following
# 4 strings to go forward cleanly... backwards will need a bit more work.
old_app = 'common'
old_model = 'cat'
new_app = 'specific'
new_model = 'kittycat'
# You may also wish to update the ContentType.name,
# personally, I don't know what its for and
# haven't seen any side effects from skipping it.
def forwards(self, orm):
db.rename_table(
'%s_%s' % (self.old_app, self.old_model),
'%s_%s' % (self.new_app, self.new_model),
)
if not db.dry_run:
# For permissions, GenericForeignKeys, etc to work properly after migrating.
orm['contenttypes.contenttype'].objects.filter(
app_label=self.old_app,
model=self.old_model,
).update(
app_label=self.new_app,
model=self.new_model,
)
# Going forwards, should be no problem just updating child foreign keys
# with the --auto in the other new South migrations
def backwards(self, orm):
db.rename_table(
'%s_%s' % (self.new_app, self.new_model),
'%s_%s' % (self.old_app, self.old_model),
)
if not db.dry_run:
# For permissions, GenericForeignKeys, etc to work properly after migrating.
orm['contenttypes.contenttype'].objects.filter(
app_label=self.new_app,
model=self.new_model,
).update(
app_label=self.old_app,
model=self.old_model,
)
# Going backwards, you probably should copy the ForeignKey
# db.alter_column() changes from the other new migrations in here
# so they run in the correct order.
#
# Test it! See Step 6 for more details if you need to go backwards.
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))
db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['common.Cat']))
# drop_cat
class Migration(SchemaMigration):
...
def forwards(self, orm):
# Remove the db.delete_table(), if you don't at Step 7 you'll likely get
# "django.db.utils.ProgrammingError: table "common_cat" does not exist"
# Leave existing db.alter_column() statements here
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.KittyCat']))
def backwards(self, orm):
# Copy/paste the auto-generated db.alter_column()
# into the create_kittycat migration if you need backwards to work.
pass
# update_microchip_fk
class Migration(SchemaMigration):
...
def forwards(self, orm):
# Leave existing db.alter_column() statements here
db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['specific.KittyCat']))
def backwards(self, orm):
# Copy/paste the auto-generated db.alter_column()
# into the create_kittycat migration if you need backwards to work.
pass
Шаг 6.Только если вам нужно, чтобы backward() работал И запускал KeyError в обратном направлении.
# the_one_before_create_kittycat
class Migration(SchemaMigration):
# You many also need to add more models to South's FakeORM if you run into
# more KeyErrors, the trade-off chosen was to make going forward as easy as
# possible, as that's what you'll probably want to do once in QA and once in
# production, rather than running the following many times:
#
# python manage.py migrate specific <the_one_before_create_kittycat>
models = {
...
# Copied from 'identity' app, 'update_microchip_fk' migration
u'identity.microchip': {
'Meta': {'object_name': 'Microchip'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'identifies': ('django.db.models.fields.related.OneToOneField', [], {to=orm['specific.KittyCat']})
},
...
}
Шаг 7.Проверьте это - того, что работает у меня, может быть недостаточно для вашей реальной жизненной ситуации :)
python manage.py migrate
# If you need backwards to work
python manage.py migrate specific <the_one_before_create_kittycat>
Таким образом, использование оригинального ответа от @Potr выше не сработало для меня на South 0.8.1 и Django 1.5.1.Я публикую то, что сработало у меня сработало ниже в надежде, что это будет полезно другим.
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
db.rename_table('common_cat', 'specific_cat')
if not db.dry_run:
db.execute(
"update django_content_type set app_label = 'specific' where "
" app_label = 'common' and model = 'cat';")
def backwards(self, orm):
db.rename_table('specific_cat', 'common_cat')
db.execute(
"update django_content_type set app_label = 'common' where "
" app_label = 'specific' and model = 'cat';")
Я собираюсь привести более четкую версию одной из вещей, предложенных Дэниелом Роузманом в его ответе...
Если вы просто измените db_table
Мета-атрибут модели, которую вы переместили, указывает на существующее имя таблицы (вместо нового имени, которое дал бы ему Django, если бы вы удалили и выполнили syncdb
) тогда вы сможете избежать сложных миграций на Юг.например:
Оригинал:
# app1/models.py
class MyModel(models.Model):
...
После переезда:
# app2/models.py
class MyModel(models.Model):
class Meta:
db_table = "app1_mymodel"
Теперь вам просто нужно выполнить перенос данных, чтобы обновить app_label
для MyModel
в django_content_type
столик, и вы должны быть готовы к отъезду...
Беги ./manage.py datamigration django update_content_type
затем отредактируйте файл, который South создает для вас:
def forwards(self, orm):
moved = orm.ContentType.objects.get(app_label='app1', model='mymodel')
moved.app_label = 'app2'
moved.save()
def backwards(self, orm):
moved = orm.ContentType.objects.get(app_label='app2', model='mymodel')
moved.app_label = 'app1'
moved.save()