我有一个django应用程序,里面有四个模型。我现在意识到其中一个模型应该在一个单独的应用程序中。我确实安装了南部进行迁移,但我不认为这是它可以自动处理的东西。如何将其中一个模型从旧应用程序迁移到新应用程序中?
另外,请记住,我将需要这个是一个可重复的过程,以便我可以迁移生产系统等。
答案 0 :(得分:182)
让我们说我们有两个应用程序:常见和具体:
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移动到特定应用程序(确切地说是特定于.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
会将事情迁移下来。
请注意,为了升级架构,我使用了常见的应用程序并降级,我使用了特定的应用程序。这是因为这里的依赖关系如何运作。
答案 1 :(得分:35)
要构建Potr Czachur的answer,涉及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之前表格将存在)但是我们还需要确保向后滚动也是有效的,因为< strong>依赖性适用于反方向。
# 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 documentation,depends_on
将确保0004_auto__add_cat
在向前迁移时0009_auto__del_cat
之前运行,但在相反的顺序时向后迁移。如果我们在db.rename_table('specific_cat', 'common_cat')
回滚中保留specific
,则在尝试迁移ForeignKey时common
回滚将失败,因为表引用表将不存在。
希望这比现有解决方案更接近“现实世界”的情况,有人会觉得这很有帮助。干杯!
答案 2 :(得分:7)
模型与应用程序的联系并不紧密,因此移动非常简单。 Django在数据库表的名称中使用应用程序名称,因此如果要移动应用程序,可以通过SQL ALTER TABLE
语句重命名数据库表,或者 - 甚至更简单 - 只需使用{{3}在模型的Meta
类中引用旧名称。
如果您到目前为止在代码中的任何位置使用了ContentTypes或泛型关系,您可能希望重命名指向正在移动的模型的contenttype app_label
,以便保留现有关系。
当然,如果您根本没有任何数据要保留,最简单的方法是完全删除数据库表并再次运行./manage.py syncdb
。
答案 3 :(得分:4)
这是Potr优秀解决方案的另一个解决方案。将以下内容添加到特定/ 0003_create_cat
depends_on = (
('common', '0002_create_cat'),
)
除非设置此依赖项,否则南方不保证在运行特定/ 0003_create_cat 时存在common_cat
表,从而向您抛出django.db.utils.OperationalError: no such table: common_cat
错误。< / p>
South在lexicographical order中运行迁移,除非明确设置依赖项。由于common
出现在specific
之前,所有common
的迁移都会在表重命名之前运行,因此它可能无法在Potr显示的原始示例中重现。但是,如果您将common
重命名为app2
而将specific
重命名为app1
,则会遇到此问题。
答案 4 :(得分:4)
自从我回到这里几次并决定将其正式化以来,我目前已经确定了这个过程。
这最初建立在 Potr Czachur's answer 和Matt Briançon's answer, 使用南0.8.4
# 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)
...
# 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
如果您遇到合并冲突,例如团队成员在更新的应用上编写迁移,则会使其成为一个更可重复的过程。
基本上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'),
)
...
# 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
# 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>
答案 5 :(得分:3)
因此使用上面@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';")
答案 6 :(得分:1)
我将给出一个更明确的版本,丹尼尔罗斯曼在他的回答中提出的一个......
如果您只是更改模型的db_table
Meta属性,那么您已移动到指向现有的表名(而不是新名称,如果您删除并执行syncdb
,Django会给出它那么你可以避免复杂的南迁移。例如:
原件:
# app1/models.py
class MyModel(models.Model):
...
搬家后:
# app2/models.py
class MyModel(models.Model):
class Meta:
db_table = "app1_mymodel"
现在您只需要进行数据迁移,以更新app_label
表格中MyModel
的{{1}},您应该好好去...
运行django_content_type
,然后编辑South为您创建的文件:
./manage.py datamigration django update_content_type