如何将模型从一个 django 应用程序迁移到一个新的应用程序中?

我有个姜戈应用,里面有四个模特。我现在意识到这些模型中的一个应该在一个单独的应用程序中。我确实为迁移安装了 South,但我不认为这是它可以自动处理的东西。我怎样才能将旧的应用程序中的一个模型迁移到一个新的模型中呢?

另外,请记住,我将需要这是一个可重复的过程,以便我可以迁移生产系统等。

40716 次浏览

模型与应用程序的耦合不是很紧密,所以移动相当简单。Django 使用数据库表名称中的应用程序名称,所以如果你想移动应用程序,你可以通过一个 SQL ALTER TABLE语句重命名数据库表,或者——更简单的——只是使用模型的 Meta类中的 db_table参数来引用旧名称。

如果您已经在代码中的任何地方使用了 ContentType 或泛型关系,那么您可能希望重命名指向正在移动的模型的内容类型的 app_label,以便保留现有的关系。

当然,如果根本没有任何数据需要保存,最简单的做法是完全删除数据库表并再次运行 ./manage.py syncdb

如何利用南方迁徙。

让我们假设我们有两个应用程序: 共同的和特定的:

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

会把东西迁移下来。

请注意,为了升级模式,我使用了通用应用程序,为了降级,我使用了特定的应用程序。这是因为这里的依赖关系是如何工作的。

为了构建在 波特捷克人回答上,涉及 ForeignKeys 的情况更加复杂,应该以略微不同的方式进行处理。

(下面的示例基于当前答案中提到的 commonspecific应用程序)。

# 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_cat0009_auto__del_cat 在向前迁徙的时候之前但在 向后迁移时顺序相反中运行。如果我们将 db.rename_table('specific_cat', 'common_cat')留在 specific回滚中,那么在试图迁移 ForeignKey 时,common回滚将会失败,因为引用的表不存在。

希望这比现有的解决方案更接近于“真实世界”的情况,有人会发现这很有帮助。干杯!

因此,使用上面@Potr 的原始响应不起作用 为我在南0.8.1和姜戈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 Meta 属性,你已经移动到指向现有的表名(而不是新名称 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表中 MyModelapp_label,然后就可以开始了..。

运行 ./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()

这里是 Potr 的优秀解决方案的另一个补丁

depends_on = (
('common', '0002_create_cat'),
)

除非将这个依赖项设置为 South,否则不能保证 common_cat表在运行 具体/0003 _ create _ cat时存在,从而向您抛出一个 django.db.utils.OperationalError: no such table: common_cat错误。

South 在 字典序中运行迁移,除非显式设置了依赖项。由于 commonspecific之前,所有的 common迁移都会在表重命名之前运行,所以它可能不会在 Potr 所示的原始示例中重现。但是如果将 common重命名为 app2,将 specific重命名为 app1,就会遇到这个问题。

自从我回到这里几次之后,这个过程我已经决定了,并且决定正式确定下来。

这原本是建立在 波特 · 恰丘尔的回答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。只有当你需要向后()工作,并得到一个键错误运行向后。

# 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']})
},
...
}

第七步: 测试一下——对我有效的方法可能对你的现实生活来说不够:)

python manage.py migrate


# If you need backwards to work
python manage.py migrate specific <the_one_before_create_kittycat>