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

143 投票
4 回答
30937 浏览
提问于 2025-04-15 22:52

我在South的网站、谷歌和Stack Overflow上找了很久,想找到一个简单的方法来解决这个问题。

我想用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)

为了简单起见,我只是想把名字从Foo改成Bar,暂时不考虑FooTwo里的foo成员。

使用South,最简单的方法是什么呢?

  1. 我可能可以做一个数据迁移,但这似乎有点复杂。
  2. 写一个自定义迁移,比如db.rename_table('city_citystate', 'geo_citystate'),但我不太确定在这种情况下怎么处理外键。
  3. 你知道更简单的方法吗?

4 个回答

5

南方(South)不能自己完成这个任务——它怎么知道 Bar 代表的是什么 Foo 曾经代表的呢?这就是我会写一个自定义迁移的原因。你可以像上面那样在代码中更改你的 ForeignKey,然后只需要重命名相关的字段和表,这个你可以随意处理。

最后,你真的需要这样做吗?我还没有遇到需要重命名模型的情况——模型名称只是实现的细节——尤其是考虑到有 verbose_name 这个 Meta 选项可用。

67

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,并使用新的模型名称。但如果数据库中有对象指向这个类型(比如通过 GenericForeignKey),最好还是更新你已经存在的 ContentType

另外,如果你重命名了一些作为外键的列,指向重命名后的模型,别忘了

db.rename_column(myapp_model, foo_id, bar_id)
131

针对你的第一个问题,简单的模型/表重命名其实很简单。你只需要运行这个命令:

./manage.py schemamigration yourapp rename_foo_to_bar --empty

(更新2:试试用 --auto 代替 --empty,这样可以避免下面的警告。感谢 @KFB 的建议。)

如果你用的是旧版本的 south,你需要用 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'.

我回答“否”,然后一切似乎都没问题。

撰写回答