如何将模型从一个django应用程序迁移到新的应用程序中?
我有一个django应用程序,其中有四个模型。 我现在意识到,这些模型之一应该在一个单独的应用程序。 我确实已经安装了南迁,但我不认为这是它可以自动处理的东西。 我怎样才能将其中一个模型迁移出旧应用程序?
另外,请记住,我将需要这是一个可重复的过程,以便我可以迁移生产系统等。
如何使用南迁移。
可以说我们有两个应用程序:通用和特定的:
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的回答为基础,涉及ForeignKeys的情况比较复杂,应该稍有不同。
(以下示例构build在当前答案中引用的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
根据South文档 , depends_on
将确保0004_auto__add_cat
在0009_auto__del_cat
迁移时运行在0009_auto__del_cat
之前,但在向后迁移时以相反的顺序运行 。 如果我们在specific
回滚中保留了db.rename_table('specific_cat', 'common_cat')
,那么当尝试迁移ForeignKey时, common
回滚将失败,因为表引用的表不存在。
希望这比现有解决scheme更接近“真实世界”的情况,有人会发现这有帮助。 干杯!
模型与应用程序耦合不是很紧密,所以移动相当简单。 Django使用数据库表名称中的应用程序名称,因此如果要移动应用程序,可以通过SQL ALTER TABLE
语句重命名数据库表,或者 – 更简单 – 只需在模型的Meta
类中使用db_table
参数参考旧名称。
如果您到目前为止在代码中的任何地方使用了ContentTypes或generics关系,那么您可能需要重命名指向正在移动的模型的app_label
的app_label,以便保留现有的关系。
当然,如果你根本没有任何数据要保存,最简单的方法就是完全删除数据库表, ./manage.py syncdb
再次运行./manage.py syncdb
。
这是Potr优秀解决scheme的另一个解决scheme。 将以下内容添加到特定/ 0003_create_cat
depends_on = ( ('common', '0002_create_cat'), )
除非设置了这个依赖关系,否则南不会保证在运行specific / 0003_create_cat的时候common_cat
表存在,抛出一个django.db.utils.OperationalError: no such table: common_cat
错误。
除非明确设置依赖关系,否则按照字典顺序运行迁移。 由于common
之前就有了,所有common
的迁移都会在表重命名之前运行,所以它可能不会在Potr显示的原始示例中重现。 但是,如果您将app2
重命名为app2
并且specific
于app1
,则会遇到此问题。
目前我已经回到这里,并决定正式确定这个过程。
这最初build立在Potr Czachur的答案和MattBrianç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步。创build迁移
# 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.源代码pipe理:到目前为止提交更改。
如果遇到合并冲突(例如团队成员在更新的应用程序中编写迁移),使其成为更可重复的stream程。
第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步。testing它 – 对我来说什么可能不足以满足您的实际情况:)
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"
现在,您只需要进行数据迁移即可更新django_content_type
表中的django_content_type
并且您应该很好…
运行./manage.py datamigration django update_content_type
然后编辑南为你创build的文件:
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()