使用Django / South重命名模型的最简单方法?

问题:使用Django / South重命名模型的最简单方法?

我一直在South的网站,Google和SO上寻找答案,但是找不到简单的方法来做到这一点。

我想使用South重命名Django模型。说您有以下几点:

class Foo(models.Model):
    name = models.CharField()

class FooTwo(models.Model):
    name = models.CharField()
    foo = models.ForeignKey(Foo)

并且您想要将Foo转换为Bar,即

class Bar(models.Model):
    name = models.CharField()

class FooTwo(models.Model):
    name = models.CharField()
    foo = models.ForeignKey(Bar)

为简单起见,我只是尝试将名称从更改FooBar,但现在忽略其中的foo成员FooTwo

使用South进行此操作最简单的方法是什么?

  1. 我可能可以进行数据迁移,但这似乎很复杂。
  2. 编写一个自定义迁移,例如db.rename_table('city_citystate', 'geo_citystate'),但是在这种情况下我不确定如何修复外键。
  3. 您知道一种更简单的方法吗?

I’ve been hunting for an answer to this on South’s site, Google, and SO, but couldn’t find a simple way to do this.

I want to rename a Django model using South. Say you have the following:

class Foo(models.Model):
    name = models.CharField()

class FooTwo(models.Model):
    name = models.CharField()
    foo = models.ForeignKey(Foo)

and you want to convert Foo to Bar, namely

class Bar(models.Model):
    name = models.CharField()

class FooTwo(models.Model):
    name = models.CharField()
    foo = models.ForeignKey(Bar)

To keep it simple, I’m just trying to change the name from Foo to Bar, but ignore the foo member in FooTwo for now.

What’s the easiest way to do this using South?

  1. I could probably do a data migration, but that seems pretty involved.
  2. Write a custom migration, e.g. db.rename_table('city_citystate', 'geo_citystate'), but I’m not sure how to fix the foreign key in this case.
  3. An easier way that you know?

回答 0

为了回答您的第一个问题,简单的模型/表重命名非常简单。运行命令:

./manage.py schemamigration yourapp rename_foo_to_bar --empty

(更新2:尝试--auto,而不是--empty避免低于警告感谢@KFB的提示。)

如果您使用的是南方的旧版本,则需要startmigration而不是schemamigration

然后手动编辑迁移文件,如下所示:

class Migration(SchemaMigration):

    def forwards(self, orm):
        db.rename_table('yourapp_foo', 'yourapp_bar')


    def backwards(self, orm):
        db.rename_table('yourapp_bar','yourapp_foo')   

您可以使用db_table模型类中的Meta选项来更简单地完成此操作。但是每次这样做,都增加了代码库的旧版权重-类名与表名不同会使代码难以理解和维护。为了清楚起见,我完全支持进行这样的简单重构。

(更新)我刚刚在生产环境中尝试过此操作,并在应用迁移时收到一个奇怪的警告。它说:

The following content types are stale and need to be deleted:

    yourapp | foo

Any objects related to these content types by a foreign key will also
be deleted. Are you sure you want to delete these content types?
If you're unsure, answer 'no'.

我回答“不”,一切似乎都很好。

To answer your first question, the simple model/table rename is pretty straightforward. Run the command:

./manage.py schemamigration yourapp rename_foo_to_bar --empty

(Update 2: try --auto instead of --empty to avoid the warning below. Thanks to @KFB for the tip.)

If you’re using an older version of south, you’ll need startmigration instead of schemamigration.

Then manually edit the migration file to look like this:

class Migration(SchemaMigration):

    def forwards(self, orm):
        db.rename_table('yourapp_foo', 'yourapp_bar')


    def backwards(self, orm):
        db.rename_table('yourapp_bar','yourapp_foo')   

You can accomplish this more simply using the db_table Meta option in your model class. But every time you do that, you increase the legacy weight of your codebase — having class names differ from table names makes your code harder to understand and maintain. I fully support doing simple refactorings like this for the sake of clarity.

(update) I just tried this in production, and got a strange warning when I went to apply the migration. It said:

The following content types are stale and need to be deleted:

    yourapp | foo

Any objects related to these content types by a foreign key will also
be deleted. Are you sure you want to delete these content types?
If you're unsure, answer 'no'.

I answered “no” and everything seemed to be fine.


回答 1

进行更改models.py,然后运行

./manage.py schemamigration --auto myapp

检查迁移文件时,您会看到它删除了一个表并创建了一个新表。

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Deleting model 'Foo'                                                                                                                      
        db.delete_table('myapp_foo')

        # Adding model 'Bar'                                                                                                                        
        db.create_table('myapp_bar', (
        ...
        ))
        db.send_create_signal('myapp', ['Bar'])

    def backwards(self, orm):
        ...

这不是您想要的。而是编辑迁移,使其看起来像:

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Renaming model from 'Foo' to 'Bar'                                                                                                                      
        db.rename_table('myapp_foo', 'myapp_bar')                                                                                                                        
        if not db.dry_run:
            orm['contenttypes.contenttype'].objects.filter(
                app_label='myapp', model='foo').update(model='bar')

    def backwards(self, orm):
        # Renaming model from 'Bar' to 'Foo'                                                                                                                      
        db.rename_table('myapp_bar', 'myapp_foo')                                                                                                                        
        if not db.dry_run:
            orm['contenttypes.contenttype'].objects.filter(app_label='myapp', model='bar').update(model='foo')

在没有该update语句的情况下,该db.send_create_signal调用将ContentType使用新的模型名称创建一个新的模型。但它最好只updateContentType你已经拥有的情况下有数据库对象指向它(例如,通过一GenericForeignKey)。

另外,如果您已经重命名了某些列,这些列是重命名模型的外键,请不要忘记

db.rename_column(myapp_model, foo_id, bar_id)

Make the changes in models.py and then run

./manage.py schemamigration --auto myapp

When you inspect the migration file, you’ll see that it deletes a table and creates a new one

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Deleting model 'Foo'                                                                                                                      
        db.delete_table('myapp_foo')

        # Adding model 'Bar'                                                                                                                        
        db.create_table('myapp_bar', (
        ...
        ))
        db.send_create_signal('myapp', ['Bar'])

    def backwards(self, orm):
        ...

This is not quite what you want. Instead, edit the migration so that it looks like:

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Renaming model from 'Foo' to 'Bar'                                                                                                                      
        db.rename_table('myapp_foo', 'myapp_bar')                                                                                                                        
        if not db.dry_run:
            orm['contenttypes.contenttype'].objects.filter(
                app_label='myapp', model='foo').update(model='bar')

    def backwards(self, orm):
        # Renaming model from 'Bar' to 'Foo'                                                                                                                      
        db.rename_table('myapp_bar', 'myapp_foo')                                                                                                                        
        if not db.dry_run:
            orm['contenttypes.contenttype'].objects.filter(app_label='myapp', model='bar').update(model='foo')

In the absence of the update statement, the db.send_create_signal call will create a new ContentType with the new model name. But it’s better to just update the ContentType you already have in case there are database objects pointing to it (e.g., via a GenericForeignKey).

Also, if you’ve renamed some columns which are foreign keys to the renamed model, don’t forget to

db.rename_column(myapp_model, foo_id, bar_id)

回答 2

南方本身不能做-怎么知道这Bar代表Foo过去?我将为此编写自定义迁移。您可以ForeignKey像上面所做的那样更改in代码,然后只是重命名适当的字段和表的一种情况,您可以根据需要进行任何操作。

最后,您真的需要这样做吗?我还不需要重命名模型-模型名称只是实现细节-特别是考虑到verbose_nameMeta选项的可用性。

South can’t do it itself – how does it know that Bar represents what Foo used to? This is the sort of thing I’d write a custom migration for. You can change your ForeignKey in code as you’ve done above, and then it’s just a case of renaming the appropriate fields and tables, which you can do any way you want.

Finally, do you really need to do this? I’ve yet to need to rename models – model names are just an implementation detail – particularly given the availability of the verbose_name Meta option.


回答 3

我遵循了上面Leopd的解决方案。但是,这并没有更改型号名称。我在代码中手动更改了它(在相关模型中也将其称为FK)。并进行了另一个南迁,但带有–fake选项。这使得模型名称和表名称相同。

刚意识到,可以先从更改模型名称开始,然后在应用迁移文件之前编辑迁移文件。干净得多。

I followed Leopd’s solution above. But, that did not change the model names. I changed it manually in the code (also in related models where this is referred as FK). And done another south migration, but with –fake option. This makes model names and table names to be same.

Just realized, one could first start with changing model names, then edit the migrations file before applying them. Much cleaner.