Как перенести модель из одного приложения django и в новое?
У меня есть приложение django с четырьмя моделями. Теперь я понимаю, что одна из этих моделей должна быть в отдельном приложении. У меня есть юг, установленный для миграции, но я не думаю, что это то, что он может обрабатывать автоматически. Как перенести одну из моделей из старого приложения в новую?
Кроме того, имейте в виду, что мне нужно, чтобы это было повторяемым процессом, чтобы я мог переносить производственную систему и т.д.
Ответы
Ответ 1
Как перейти на юг.
Допустим, у нас есть два приложения: общее и специфическое:
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
будет мигрировать.
Обратите внимание, что для обновления схемы я использовал обычное приложение и для понижения, я использовал конкретное приложение. Это потому, что здесь работает зависимость.
Ответ 2
Чтобы опираться на Potr Czachur ответить, ситуации, в которых задействованы ForeignKeys, сложнее и их нужно обрабатывать немного по-разному.
(Следующий пример основан на приложениях 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 ContentType - см. ранее упоминавшийся ответ о том, как с этим справиться):
# 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, потому что таблица, на которую ссылается таблица, не существовала.
Надеюсь, это ближе к ситуации "реального мира", чем существующие решения, и кто-то найдет это полезным. Ура!
Ответ 3
Модели не очень тесно связаны с приложениями, поэтому перемещение довольно просто. Django использует имя приложения в имени таблицы базы данных, поэтому, если вы хотите переместить приложение, вы можете либо переименовать таблицу базы данных с помощью инструкции SQL ALTER TABLE
, либо, что еще проще, просто используйте db_table
в вашем классе Meta
для обозначения старого имени.
Если вы использовали ContentTypes или общие отношения где-нибудь в вашем коде, вы, вероятно, захотите переименовать app_label
типа контента, указывающего на движущуюся модель, чтобы сохранились существующие отношения.
Конечно, если у вас нет каких-либо данных для сохранения, проще всего отказаться от таблиц базы данных и снова запустить ./manage.py syncdb
.
Ответ 4
Здесь еще одно исправление для превосходного решения Potr. Добавьте следующее в specific/0003_create_cat
depends_on = (
('common', '0002_create_cat'),
)
Если эта зависимость не установлена, Юг не гарантирует, что таблица common_cat
существует в то время, когда выполняется specific/0003_create_cat, бросая на вас ошибку django.db.utils.OperationalError: no such table: common_cat
.
Юг запускает миграции в лексикографический порядок, если явно не установлена зависимость. Поскольку common
предшествует specific
, все миграции common
будут запускаться перед переименованием таблицы, поэтому, вероятно, это не будет воспроизводиться в исходном примере, показанном Potr. Но если вы переименуете common
в app2
и specific
в app1
, вы столкнетесь с этой проблемой.
Ответ 5
Процесс, который я сейчас установил, так как я вернулся сюда несколько раз и решил его формализовать.
Это изначально было построено на
Ответ Пот Чачур
и ответ Matt Briançon,
используя Юг 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. Только если вам нужно обратное(), чтобы работать И получить KeyError, работающий назад.
# the_one_before_create_kittycat
class Migration(SchemaMigration):
# You many also need to add more models to South FakeORM if you run into
# more KeyErrors, the trade-off chosen was to make going forward as easy as
# possible, as that 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>
Ответ 6
Итак, использование исходного ответа от @Potr выше не сработало
для меня на юге 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';")
Ответ 7
Я собираюсь дать более явную версию одной из вещей, предложенных Даниэлем Роземаном в его ответе...
Если вы просто измените атрибут db_table
Meta модели, который вы переместили, чтобы указать на существующее имя таблицы (вместо нового имени 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()