Как перенести модель из одного приложения django в новое?
У меня есть приложение Django с четырьмя моделями в нем. Теперь я понимаю, что одна из этих моделей должна быть в отдельном приложении. У меня есть юг, установленный для миграции, но я не думаю, что это то, что он может обрабатывать автоматически. Как я могу перенести одну из моделей из старого приложения в новый?
кроме того, имейте в виду, что мне нужно, чтобы это был повторяемый процесс, чтобы я мог перенести производственную систему и т. д.
7 ответов:
как перейти через юг.
допустим у нас есть два приложения: общие и специфические:
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 ответ, ситуации, которые включают в себя 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 changes-см. ранее упомянутый ответ о том, как справиться с этим):
# 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откат завершится неудачей при попытке переноса внешнего ключа, поскольку таблица, на которую ссылается таблица, не будет существовать.надеюсь, это ближе к "реальному миру" ситуация чем существующие решения и кто-то найдет это полезным. Ура!
модели не очень тесно связаны с приложениями, поэтому перемещение довольно просто. Django использует имя приложения в имени таблицы базы данных, поэтому, если вы хотите переместить свое приложение, вы можете либо переименовать таблицу базы данных через SQL
ALTER TABLEзаявление, или-еще проще-просто используйтеdb_tableпараметр в класс для ссылки на старое имя.если вы использовали ContentTypes или общие отношения в любом месте вашего кода до сих пор, вы, вероятно, захотите переименовать
app_labelcontenttype указывает на модель, которая движется, так что существующие отношения сохраняются.конечно, если у вас нет никаких данных вообще, чтобы сохранить, самое простое, что нужно сделать, это полностью удалить таблицы базы данных и запустить
./manage.py syncdbснова.
вот еще одно исправление для отличного решения 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вы столкнетесь с этой проблемой.
процесс, на котором я в настоящее время остановился, так как я вернулся сюда несколько раз и решил его формализовать.
это было первоначально построено на ответ потра Чачура и ответ Мэтта Бриансона, используя Южной 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'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 выше не сработало для меня на юге 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()
Comments