在现有的Django应用程序中,更改主键的最佳方法是什么?

2024-04-19 01:30:44 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个测试模式的应用程序。此应用程序的模型有一些具有显式主键的类。因此,Django使用字段,而不会自动创建id。

class Something(models.Model):
    name = models.CharField(max_length=64, primary_key=True)

我认为这是一个坏主意(参见unicode error when saving an object in django admin),我想移回去,为我的模型的每个类都有一个id。

class Something(models.Model):
    name = models.CharField(max_length=64, db_index=True)

我对模型进行了更改(将每个primary_key=True替换为db_index=True),并希望用south迁移数据库。

不幸的是,迁移失败,并显示以下消息: ValueError: You cannot add a null=False column without a default value.

我正在为这个问题评估不同的解决方法。有什么建议吗?

谢谢你的帮助


Tags: keyname模型idtrue应用程序dbmodel
3条回答

当前您失败,因为您正在添加一个pk列,该列将打破非空和唯一的要求。

您应该将迁移分成several steps,将架构迁移和数据迁移分开:

  • 使用默认值(ddl migration)添加新列(已索引但不是主键)
  • 迁移数据:用正确的值填充新列(数据迁移)
  • 标记新列主键,如果不需要,则删除以前的pk列(ddl迁移)

要使用south更改主键,可以在数据迁移中使用south.db.create_primary_key命令。 要将自定义CharField pk更改为标准AutoField,您应该执行以下操作:

1)在模型中创建新字段

class MyModel(Model):
    id = models.AutoField(null=True)

1.1)如果在该模型的其他模型中有外键,也可以在这些模型上创建新的假fk字段(使用IntegerField,然后将其转换)

class MyRelatedModel(Model):
    fake_fk = models.IntegerField(null=True)

2)创建自动南迁:

./manage.py schemamigration --auto
./manage.py migrate

3)创建新的数据迁移

./manage.py datamigration <your_appname> fill_id

在tis数据迁移中,用数字填充这些新的id和fk字段(只需枚举它们)

    for n, obj in enumerate(orm.MyModel.objects.all()):
        obj.id = n
        # update objects with foreign keys
        obj.myrelatedmodel_set.all().update(fake_fk = n)
        obj.save()

    db.delete_primary_key('my_app_mymodel')
    db.create_primary_key('my_app_mymodel', ['id'])

4)在您的模型中,在新的pk字段上设置primary_key=True

id = models.AutoField(primary_key=True)

5)删除旧主键字段(如果不需要),创建自动迁移和迁移。

5.1)如果您有外键-也删除旧外键字段(迁移)

6)最后一步-恢复fireign密钥关系。再次创建真正的fk字段,并删除您的假fk字段,创建自动迁移但不迁移(!)-您需要修改创建的自动迁移:而不是创建新的fk并删除fake-fk-重命名列fake-fk

# in your models
class MyRelatedModel(Model):
    # delete fake_fk
    # fake_fk = models.InegerField(null=True)
    # create real fk
    mymodel = models.FoeignKey('MyModel', null=True)

# in migration
    def forwards(self, orm):
        # left this without change - create fk field
        db.add_column('my_app_myrelatedmodel', 'mymodel',
                  self.gf('django.db.models.fields.related.ForeignKey')(default=1, related_name='lots', to=orm['my_app.MyModel']),keep_default=False)

        # remove fk column and rename fake_fk
        db.delete_column('my_app_myrelatedmodel', 'mymodel_id')
        db.rename_column('my_app_myrelatedmodel', 'fake_fk', 'mymodel_id')

因此,先前填充的fake_fk变成一个列,其中包含实际的关系数据,并且在完成上述所有步骤后不会丢失。

同意,你的模型可能是错的。

正式主键应该始终是代理键。别再提别的了。[有力的话语。从20世纪80年代开始就从事数据库设计工作。我们学到的重要教训是:一切都是可变的,即使用户在母亲的坟墓上发誓不能改变其值,这确实是一个可以作为主键的自然键。它不是主要的。只有代理才能是主要的。]

你在做心脏直视手术。不要搅乱架构迁移。你在替换这个模式。

  1. 将数据卸载到JSON文件中。为此,请使用Django自己的内部Django-admin.py工具。您应该为每个将要更改的文件和每个依赖于正在创建的键的表创建一个卸载文件。单独的文件使这稍微容易一点。

  2. 从旧模式中删除要更改的表。

    依赖于这些表的表将更改其FK;您可以 就地更新行,或者——可能更简单——删除并重新插入 这些行也是。

  3. 创建新架构。这只会创建正在更改的表。

  4. 编写脚本以使用新键读取和重新加载数据。这些都很短,非常相似。每个脚本都将使用json.load()从源文件读取对象;然后,您将从为您构建的JSON元组行对象创建模式对象。然后可以将它们插入数据库。

    你有两个案子。

    • 将插入主键更改的表并获取新主键。这些表必须“级联”到其他表,以确保另一个表的FK也更改。

    • 具有FK的表进行更改时,必须在外部表中定位行并更新其FK引用。

可供选择。

  1. 重命名所有旧表。

  2. 创建整个新架构。

  3. 编写SQL将所有数据从旧模式迁移到新模式。这将不得不巧妙地重新分配钥匙。

  4. 删除重命名的旧表。

相关问题 更多 >